Warp/src/Client/init.luau

241 lines
5.9 KiB
Text
Raw Normal View History

2026-02-10 18:11:25 +00:00
--!optimize 2
--!strict
--@EternityDev
local Client = {}
local RunService = game:GetService("RunService")
local Thread = require("./Util/Thread")
local Buffer = require("./Util/Buffer")
local Replication = require("./Replication")
2026-02-10 18:11:25 +00:00
local Event: RemoteEvent = script.Parent:WaitForChild("Event")
2026-02-11 08:00:23 +00:00
local UnreliableEvent: UnreliableRemoteEvent = script.Parent:WaitForChild("UnreliableEvent")
2026-02-10 18:11:25 +00:00
local deltaT: number, cycle: number = 0, 1 / 61
local writer: Buffer.Writer = Buffer.createWriter()
type Connection = {
Connected: boolean,
Disconnect: (self: Connection) -> (),
}
type Event = {
i: number,
c: (Player, ...any?) -> ...any?,
2026-02-10 18:11:25 +00:00
}
local queueEvent: { { any } } = {}
2026-02-11 08:00:23 +00:00
local queueUnreliableEvent: { { any } } = {}
2026-02-10 18:11:25 +00:00
local eventListeners: { Event } = {}
local eventSchemas: { [number]: Buffer.SchemaType } = {}
2026-02-10 18:11:25 +00:00
local pendingInvokes: { [string]: thread } = {}
local invokeId = 0
2026-02-11 04:34:45 +00:00
Client.useSchema = function(remoteName: string, schema: Buffer.SchemaType)
2026-02-16 09:50:43 +00:00
local id = Replication.get_id(remoteName)
if not id then
warn(`[Warp]: ".useSchema"::"{remoteName}" does not exist, likely its not registered on the server yet.`)
end
eventSchemas[id] = schema
2026-02-11 04:34:45 +00:00
end
2026-02-11 08:00:23 +00:00
Client.Connect = function(remoteName: string, fn: (Player, ...any?) -> ...any?): Connection
2026-02-16 09:50:43 +00:00
local id = Replication.get_id(remoteName)
if not id then
warn(`[Warp]: ".Connect"::"{remoteName}" does not exist, likely its not registered on the server yet.`)
return { Connected = false, Disconnect = function() return end } :: Connection
end
2026-02-10 18:11:25 +00:00
local detail = {
2026-02-16 09:50:43 +00:00
i = id,
c = fn,
2026-02-10 18:11:25 +00:00
}
table.insert(eventListeners, detail)
return {
Connected = true,
Disconnect = function(self: Connection)
2026-02-11 08:00:23 +00:00
if not self.Connected then
return
end
2026-02-10 18:11:25 +00:00
self.Connected = false
local idx = table.find(eventListeners, detail)
if idx then
table.remove(eventListeners, idx)
end
end,
} :: Connection
end
Client.Once = function(remoteName: string, fn: (...any?) -> ()): Connection
local connection
connection = Client.Connect(remoteName, function(...: any?)
if connection then
connection:Disconnect()
end
fn(...)
end)
return connection
end
Client.Wait = function(remoteName: string): (number, ...any?)
local thread, t = coroutine.running(), os.clock()
Client.Once(remoteName, function(...: any?)
2026-02-11 08:00:23 +00:00
task.spawn(thread, os.clock() - t, ...)
2026-02-10 18:11:25 +00:00
end)
return coroutine.yield()
end
Client.DisconnectAll = function(remoteName: string)
local id = Replication.get_id(remoteName)
2026-02-16 09:50:43 +00:00
if not id then
return
end
2026-02-10 18:11:25 +00:00
for idx = #eventListeners, 1, -1 do
if eventListeners[idx].i == id then
2026-02-10 18:11:25 +00:00
table.remove(eventListeners, idx)
end
end
end
Client.Destroy = Client.DisconnectAll
2026-02-10 18:11:25 +00:00
2026-02-16 09:50:43 +00:00
Client.Fire = function(remoteName: string, reliable: boolean, ...: any?)
local id = Replication.get_id(remoteName)
if id then
table.insert(reliable and queueEvent or queueUnreliableEvent, {
Replication.get_id(remoteName),
{ ... } :: any,
})
end
2026-02-10 18:11:25 +00:00
end
Client.Invoke = function(remoteName: string, timeout: number?, ...: any?): ...any?
2026-02-16 09:50:43 +00:00
local id = Replication.get_id(remoteName)
if not id then
return nil
end
2026-02-10 18:11:25 +00:00
invokeId += 1
local reqid, thread = `{invokeId}`, coroutine.running()
2026-02-10 18:11:25 +00:00
pendingInvokes[reqid] = thread
2026-02-10 18:11:25 +00:00
task.delay(timeout or 2, function()
local pending = pendingInvokes[reqid]
2026-02-10 18:11:25 +00:00
if not pending then
return
end
task.spawn(pending, nil)
pendingInvokes[reqid] = nil
2026-02-10 18:11:25 +00:00
end)
table.insert(queueEvent, {
0,
2026-02-16 09:50:43 +00:00
{ id, reqid :: any, { ... } :: any } :: any,
2026-02-10 18:11:25 +00:00
})
return coroutine.yield()
end
if RunService:IsClient() then
2026-02-11 08:00:23 +00:00
local function processIncoming(b: buffer, ref: { Instance }?, handleInvokes: boolean)
if type(b) ~= "buffer" then
return
end
local contents = Buffer.readEvents(b, ref, eventSchemas)
2026-02-10 18:11:25 +00:00
for _, content in contents do
local remote = content[1]
local content = content[2]
2026-02-11 08:00:23 +00:00
if handleInvokes then
if remote == 0 then
2026-02-11 08:00:23 +00:00
local id = content[1]
local results = content[2]
local pending = pendingInvokes[id]
if pending then
task.spawn(pending :: any, table.unpack(results))
pendingInvokes[id] = nil
end
continue
2026-02-10 18:11:25 +00:00
end
if remote == 1 then
2026-02-11 08:00:23 +00:00
if #eventListeners == 0 then
continue
end
local remoteName = content[1]
local id = content[2]
local args = content[3]
for _, connection in eventListeners do
if connection.i == remoteName then
2026-02-11 08:00:23 +00:00
Thread(function()
local results = { connection.c(table.unpack(args)) }
2026-02-11 08:00:23 +00:00
table.insert(queueEvent, {
1,
2026-02-11 08:00:23 +00:00
{ id, results } :: any,
})
end)
break
end
2026-02-10 18:11:25 +00:00
end
2026-02-11 08:00:23 +00:00
continue
2026-02-10 18:11:25 +00:00
end
2026-02-11 08:00:23 +00:00
end
if #eventListeners == 0 then
2026-02-10 18:11:25 +00:00
continue
end
for _, connection in eventListeners do
if connection.i ~= remote then
2026-02-11 08:00:23 +00:00
continue
end
Thread(connection.c, table.unpack(content))
2026-02-10 18:11:25 +00:00
end
end
2026-02-11 08:00:23 +00:00
end
Event.OnClientEvent:Connect(function(b: buffer, ref: { Instance }?)
processIncoming(b, ref, true)
end)
UnreliableEvent.OnClientEvent:Connect(function(b: buffer, ref: { Instance }?)
processIncoming(b, ref, false)
2026-02-10 18:11:25 +00:00
end)
2026-02-11 08:00:23 +00:00
2026-02-10 18:11:25 +00:00
RunService.PostSimulation:Connect(function(d: number)
deltaT += d
2026-02-11 08:00:23 +00:00
if deltaT < cycle then
return
end
2026-02-10 18:11:25 +00:00
deltaT = 0
2026-02-11 08:00:23 +00:00
-- reliable
if #queueEvent > 0 then
Buffer.writeEvents(writer, queueEvent, eventSchemas)
do
local buf, ref = Buffer.buildWithRefs(writer)
Buffer.reset(writer)
if not ref or #ref == 0 then
Event:FireServer(buf)
else
Event:FireServer(buf, ref)
end
end
table.clear(queueEvent)
end
-- unreliable
if #queueUnreliableEvent > 0 then
Buffer.writeEvents(writer, queueUnreliableEvent, eventSchemas)
do
local buf, ref = Buffer.buildWithRefs(writer)
Buffer.reset(writer)
if not ref or #ref == 0 then
UnreliableEvent:FireServer(buf)
else
UnreliableEvent:FireServer(buf, ref)
end
2026-02-10 18:11:25 +00:00
end
2026-02-11 08:00:23 +00:00
table.clear(queueUnreliableEvent)
2026-02-10 18:11:25 +00:00
end
end)
end
2026-02-11 04:34:45 +00:00
--[[
@class Client
@schema
define a schema for your data and use a strict packing
]]
Client.Schema = Buffer.Schema
2026-02-10 18:11:25 +00:00
return Client :: typeof(Client)