diff --git a/src/Client/init.luau b/src/Client/init.luau index f266f4a..01e9c8d 100644 --- a/src/Client/init.luau +++ b/src/Client/init.luau @@ -13,12 +13,12 @@ local deltaT: number, cycle: number = 0, 1 / 61 local writer: Buffer.Writer = Buffer.createWriter() type Connection = { - Connected: boolean, - Disconnect: (self: Connection) -> (), + Connected: boolean, + Disconnect: (self: Connection) -> (), } type Event = { - i: number, - c: (Player, ...any?) -> ...any?, + i: number, + c: (Player, ...any?) -> ...any?, } local queueEvent: { { any } } = {} @@ -37,79 +37,79 @@ Client.awaitReady = Replication.wait_for_ready --@schema Buffer.SchemaType -- Define a schema for strict data packing on a specific event. Client.useSchema = function(remoteName: string, schema: Buffer.SchemaType) - 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.`) - return - end - eventSchemas[id] = schema + 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.`) + return + end + eventSchemas[id] = schema end --@remoteName string --@fn function -- Connect to an event to receive incoming data from the server. Client.Connect = function(remoteName: string, fn: (Player, ...any?) -> ...any?): Connection - 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 - local detail = { - i = id, - c = fn, - } - table.insert(eventListeners, detail) - return { - Connected = true, - Disconnect = function(self: Connection) - if not self.Connected then - return - end - self.Connected = false - local idx = table.find(eventListeners, detail) - if idx then - table.remove(eventListeners, idx) - end - end, - } :: Connection + 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 + local detail = { + i = id, + c = fn, + } + table.insert(eventListeners, detail) + return { + Connected = true, + Disconnect = function(self: Connection) + if not self.Connected then + return + end + self.Connected = false + local idx = table.find(eventListeners, detail) + if idx then + table.remove(eventListeners, idx) + end + end, + } :: Connection end --@remoteName string --@fn function -- Similar to :Connect but automatically disconnects after the first firing. 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 + local connection + connection = Client.Connect(remoteName, function(...: any?) + if connection then + connection:Disconnect() + end + fn(...) + end) + return connection end --@remoteName string -- Wait for an event to be triggered. Yields the current thread. Client.Wait = function(remoteName: string): (number, ...any?) - local thread, t = coroutine.running(), os.clock() - Client.Once(remoteName, function(...: any?) - task.spawn(thread, os.clock() - t, ...) - end) - return coroutine.yield() + local thread, t = coroutine.running(), os.clock() + Client.Once(remoteName, function(...: any?) + task.spawn(thread, os.clock() - t, ...) + end) + return coroutine.yield() end --@remoteName string -- Disconnect all connections for a specific event. Client.DisconnectAll = function(remoteName: string) - local id = Replication.get_id(remoteName) - if not id then - return - end - for idx = #eventListeners, 1, -1 do - if eventListeners[idx].i == id then - table.remove(eventListeners, idx) - end - end + local id = Replication.get_id[remoteName] + if not id then + return + end + for idx = #eventListeners, 1, -1 do + if eventListeners[idx].i == id then + table.remove(eventListeners, idx) + end + end end --@remoteName string @@ -120,140 +120,140 @@ Client.Destroy = Client.DisconnectAll --@reliable boolean -- Fire an event to the server. 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 + local id = Replication.get_id[remoteName] + if id then + table.insert(reliable and queueEvent or queueUnreliableEvent, { + Replication.get_id[remoteName], + { ... } :: any, + }) + end end --@remoteName string --@timeout number? -- Invoke the server with timeout support. Yields the current thread. Returns nil if timeout occurs. Client.Invoke = function(remoteName: string, timeout: number?, ...: any?): ...any? - local id = Replication.get_id(remoteName) - if not id then - return nil - end - invokeId += 1 - local reqid, thread = `{invokeId}`, coroutine.running() + local id = Replication.get_id[remoteName] + if not id then + return nil + end + invokeId += 1 + local reqid, thread = `{invokeId}`, coroutine.running() - pendingInvokes[reqid] = thread - task.delay(timeout or 2, function() - local pending = pendingInvokes[reqid] - if not pending then - return - end - task.spawn(pending, nil) - pendingInvokes[reqid] = nil - end) - table.insert(queueEvent, { - 0, - { id, reqid :: any, { ... } :: any } :: any, - }) - return coroutine.yield() + pendingInvokes[reqid] = thread + task.delay(timeout or 2, function() + local pending = pendingInvokes[reqid] + if not pending then + return + end + task.spawn(pending, nil) + pendingInvokes[reqid] = nil + end) + table.insert(queueEvent, { + 0, + { id, reqid :: any, { ... } :: any } :: any, + }) + return coroutine.yield() end if RunService:IsClient() then - local function processIncoming(b: buffer, ref: { Instance }?, handleInvokes: boolean) - if type(b) ~= "buffer" then - return - end - local contents = Buffer.readEvents(b, ref, eventSchemas) - for _, content in contents do - local remote = content[1] - local content = content[2] - if handleInvokes then - if remote == 0 then - 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 - end - if remote == 1 then - 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 - Thread(function() - local results = { connection.c(table.unpack(args)) } - table.insert(queueEvent, { - 1, - { id, results } :: any, - }) - end) - break - end - end - continue - end - end - if #eventListeners == 0 then - continue - end - for _, connection in eventListeners do - if connection.i ~= remote then - continue - end - Thread(connection.c, table.unpack(content)) - end - end - end + local function processIncoming(b: buffer, ref: { Instance }?, handleInvokes: boolean) + if type(b) ~= "buffer" then + return + end + local contents = Buffer.readEvents(b, ref, eventSchemas) + for _, content in contents do + local remote = content[1] + local content = content[2] + if handleInvokes then + if remote == 0 then + 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 + end + if remote == 1 then + 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 + Thread(function() + local results = { connection.c(table.unpack(args)) } + table.insert(queueEvent, { + 1, + { id, results } :: any, + }) + end) + break + end + end + continue + end + end + if #eventListeners == 0 then + continue + end + for _, connection in eventListeners do + if connection.i ~= remote then + continue + end + Thread(connection.c, table.unpack(content)) + end + end + end - Event.OnClientEvent:Connect(function(b: buffer, ref: { Instance }?) - processIncoming(b, ref, true) - 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) - end) + UnreliableEvent.OnClientEvent:Connect(function(b: buffer, ref: { Instance }?) + processIncoming(b, ref, false) + end) - RunService.PostSimulation:Connect(function(d: number) - deltaT += d - if deltaT < cycle then - return - end - deltaT = 0 + RunService.PostSimulation:Connect(function(d: number) + deltaT += d + if deltaT < cycle then + return + end + deltaT = 0 - -- 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 - end - table.clear(queueUnreliableEvent) - end - end) + -- 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 + end + table.clear(queueUnreliableEvent) + end + end) end --[[ diff --git a/src/Server/init.luau b/src/Server/init.luau index 1b7b2cd..c3effd3 100644 --- a/src/Server/init.luau +++ b/src/Server/init.luau @@ -16,19 +16,19 @@ local deltaT: number, cycle: number = 0, 1 / 61 local writer: Buffer.Writer = Buffer.createWriter() type Connection = { - Connected: boolean, - Disconnect: (self: Connection) -> (), + Connected: boolean, + Disconnect: (self: Connection) -> (), } type Event = { - i: number, - c: (Player, ...any?) -> ...any?, + i: number, + c: (Player, ...any?) -> ...any?, } local queueEvent: { - [Player]: { { any } }, + [Player]: { { any } }, } = {} local queueUnreliableEvent: { - [Player]: { { any } }, + [Player]: { { any } }, } = {} local eventListeners: { Event } = {} local eventSchemas: { [number]: Buffer.SchemaType } = {} @@ -41,78 +41,78 @@ local invokeId = 0 --@optional -- Register namespaces to ensure all of the namespaces is being registered earlier on the server to prevent any unexpected issues on the client. Server.reg_namespaces = function(namespaces: { string }) - for _, name: string in namespaces do - Identifier.get_id(name) - end + for _, name: string in namespaces do + Identifier.get_id(name) + end end --@remoteName string --@schema Buffer.SchemaType -- Define a schema for strict data packing on a specific event. Server.useSchema = function(remoteName: string, schema: Buffer.SchemaType) - eventSchemas[Identifier.get_id(remoteName)] = schema + eventSchemas[Identifier.get_id(remoteName)] = schema end --@remoteName string --@fn function -- Connect to an event to receive incoming data from clients. Server.Connect = function(remoteName: string, fn: (Player, ...any?) -> ...any?): Connection - local detail = { - i = Identifier.get_id(remoteName), - c = fn, - } - table.insert(eventListeners, detail) - return { - Connected = true, - Disconnect = function(self: Connection) - if not self.Connected then - return - end - self.Connected = false - local idx = table.find(eventListeners, detail) - if idx then - table.remove(eventListeners, idx) - end - end, - } :: Connection + local detail = { + i = Identifier.get_id(remoteName), + c = fn, + } + table.insert(eventListeners, detail) + return { + Connected = true, + Disconnect = function(self: Connection) + if not self.Connected then + return + end + self.Connected = false + local idx = table.find(eventListeners, detail) + if idx then + table.remove(eventListeners, idx) + end + end, + } :: Connection end --@remoteName string --@fn function -- Similar to :Connect but automatically disconnects after the first firing. Server.Once = function(remoteName: string, fn: (...any?) -> ()): Connection - local connection - connection = Server.Connect(remoteName, function(...: any?) - if connection then - connection:Disconnect() - end - fn(...) - end) - return connection + local connection + connection = Server.Connect(remoteName, function(...: any?) + if connection then + connection:Disconnect() + end + fn(...) + end) + return connection end --@remoteName string -- Wait for an event to be triggered. Yields the current thread. Server.Wait = function(remoteName: string): (number, ...any?) - local thread, t = coroutine.running(), os.clock() - Server.Once(remoteName, function(...: any?) - task.spawn(thread, os.clock() - t, ...) - end) - return coroutine.yield() + local thread, t = coroutine.running(), os.clock() + Server.Once(remoteName, function(...: any?) + task.spawn(thread, os.clock() - t, ...) + end) + return coroutine.yield() end --@remoteName string -- Disconnect all connections for a specific event. Server.DisconnectAll = function(remoteName: string) - local id = Identifier.get_id(remoteName) - if not id then - return - end - for idx = #eventListeners, 1, -1 do - if eventListeners[idx].i == id then - table.remove(eventListeners, idx) - end - end + local id = Identifier.get_id(remoteName) + if not id then + return + end + for idx = #eventListeners, 1, -1 do + if eventListeners[idx].i == id then + table.remove(eventListeners, idx) + end + end end --@remoteName string @@ -124,23 +124,23 @@ Server.Destroy = Server.DisconnectAll --@player Player -- Fire an event to a specific player. Server.Fire = function(remoteName: string, reliable: boolean, player: Player, ...: any?) - local targetQueue = reliable and queueEvent or queueUnreliableEvent - if not targetQueue[player] then - targetQueue[player] = {} :: any - end - table.insert(targetQueue[player], { - Identifier.get_id(remoteName), - { ... } :: any, - }) + local targetQueue = reliable and queueEvent or queueUnreliableEvent + if not targetQueue[player] then + targetQueue[player] = {} :: any + end + table.insert(targetQueue[player], { + Identifier.get_id(remoteName), + { ... } :: any, + }) end --@remoteName string --@reliable boolean -- Fire an event to all connected players. Server.Fires = function(remoteName: string, reliable: boolean, ...: any?) - for _, player: Player in players_ready do - Server.Fire(remoteName, reliable, player, ...) - end + for _, player: Player in players_ready do + Server.Fire(remoteName, reliable, player, ...) + end end --@remoteName string @@ -148,10 +148,10 @@ end --@except { Player } -- Fire an event to all players except specified ones. Server.FireExcept = function(remoteName: string, reliable: boolean, except: { Player }, ...: any?) - for _, player: Player in players_ready do - if table.find(except, player) then continue end - Server.Fire(remoteName, reliable, player, ...) - end + for _, player: Player in players_ready do + if table.find(except, player) then continue end + Server.Fire(remoteName, reliable, player, ...) + end end --@remoteName string @@ -159,172 +159,174 @@ end --@timeout number? -- Invoke a client with timeout support. Yields the current thread. Returns nil if timeout occurs. Server.Invoke = function(remoteName: string, player: Player, timeout: number?, ...: any?): ...any? - invokeId += 1 - local reqId, thread = `{invokeId}`, coroutine.running() + invokeId += 1 + local reqId, thread = `{invokeId}`, coroutine.running() - pendingInvokes[reqId] = thread - task.delay(timeout or 2, function() - local pending = pendingInvokes[reqId] - if not pending then - return - end - task.spawn(pending, nil) - pendingInvokes[reqId] = nil - end) - if not queueEvent[player] then - queueEvent[player] = {} :: any - end - table.insert(queueEvent[player], { - 0, - { Identifier.get_id(remoteName), reqId :: any, { ... } :: any } :: any, - }) - return coroutine.yield() + pendingInvokes[reqId] = thread + task.delay(timeout or 2, function() + local pending = pendingInvokes[reqId] + if not pending then + return + end + task.spawn(pending, nil) + pendingInvokes[reqId] = nil + end) + if not queueEvent[player] then + queueEvent[player] = {} :: any + end + table.insert(queueEvent[player], { + 0, + { Identifier.get_id(remoteName), reqId :: any, { ... } :: any } :: any, + }) + return coroutine.yield() end if RunService:IsServer() then - local function processIncoming(player: Player, b: buffer, ref: { Instance }?, handleInvokes: boolean) - if type(b) ~= "buffer" then - return - end - local bytes: number = (player_bytes[player] or 0) + math.max(buffer.len(b), 800) - if bytes > 8e3 then return end - player_bytes[player] = bytes - - local contents = Buffer.readEvents(b, ref, eventSchemas) - for _, content in contents do - local remote = content[1] - local content = content[2] - if handleInvokes then - if remote == 1 then - 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 - end - if remote == 0 then - 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 - Thread(function() - local results = { connection.c(player, table.unpack(args)) } - if not queueEvent[player] then - queueEvent[player] = {} :: any - end - table.insert(queueEvent[player], { - 0, - { id, results } :: any, - }) - end) - break - end - end - continue - end - end - if #eventListeners == 0 then - continue - end - for _, connection in eventListeners do - if connection.i ~= remote then - continue - end - Thread(connection.c, player, table.unpack(content)) - end - end - end + local function processIncoming(player: Player, b: buffer, ref: { Instance }?, handleInvokes: boolean) + if type(b) ~= "buffer" then + return + end + if not RunService:IsStudio() then + local bytes: number = (player_bytes[player] or 0) + math.max(buffer.len(b), 800) + if bytes > 8e3 then return end + player_bytes[player] = bytes + end - Event.OnServerEvent:Connect(function(player: Player, b: buffer, ref: { Instance }?) - processIncoming(player, b, ref, true) - end) + local contents = Buffer.readEvents(b, ref, eventSchemas) + for _, content in contents do + local remote = content[1] + local content = content[2] + if handleInvokes then + if remote == 1 then + 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 + end + if remote == 0 then + 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 + Thread(function() + local results = { connection.c(player, table.unpack(args)) } + if not queueEvent[player] then + queueEvent[player] = {} :: any + end + table.insert(queueEvent[player], { + 0, + { id, results } :: any, + }) + end) + break + end + end + continue + end + end + if #eventListeners == 0 then + continue + end + for _, connection in eventListeners do + if connection.i ~= remote then + continue + end + Thread(connection.c, player, table.unpack(content)) + end + end + end - UnreliableEvent.OnServerEvent:Connect(function(player: Player, b: buffer, ref: { Instance }?) - processIncoming(player, b, ref, false) - end) + Event.OnServerEvent:Connect(function(player: Player, b: buffer, ref: { Instance }?) + processIncoming(player, b, ref, true) + end) - RunService.PostSimulation:Connect(function(d: number) - deltaT += d - if deltaT < cycle then - return - end - deltaT = 0 + UnreliableEvent.OnServerEvent:Connect(function(player: Player, b: buffer, ref: { Instance }?) + processIncoming(player, b, ref, false) + end) - -- reliable - for player: Player, content in queueEvent do - if #content == 0 or player.Parent ~= Players then - continue - end - Buffer.writeEvents(writer, content, eventSchemas) - do - local buf, ref = Buffer.buildWithRefs(writer) - Buffer.reset(writer) - if not ref or #ref == 0 then - Event:FireClient(player, buf) - else - Event:FireClient(player, buf, ref) - end - end - player_bytes[player] = 0 - table.clear(queueEvent[player]) - end - -- unreliable - for player: Player, content in queueUnreliableEvent do - if #content == 0 or player.Parent ~= Players then - continue - end - Buffer.writeEvents(writer, content, eventSchemas) - do - local buf, ref = Buffer.buildWithRefs(writer) - Buffer.reset(writer) - if not ref or #ref == 0 then - UnreliableEvent:FireClient(player, buf) - else - UnreliableEvent:FireClient(player, buf, ref) - end - end - table.clear(player_bytes) - table.clear(queueUnreliableEvent[player]) - end - end) + RunService.PostSimulation:Connect(function(d: number) + deltaT += d + if deltaT < cycle then + return + end + deltaT = 0 - local function onAdded(player: Player) - if not table.find(players_ready, player) then - table.insert(players_ready, player) - end - if not queueEvent[player] then - queueEvent[player] = {} :: any - end - if not queueUnreliableEvent[player] then - queueUnreliableEvent[player] = {} :: any - end - end - Players.PlayerAdded:Connect(onAdded) - Players.PlayerRemoving:Connect(function(player: Player) - Replication.remove(player) - table.remove(players_ready, table.find(players_ready, player)) - if queueEvent[player] then - table.clear(queueEvent[player]) - queueEvent[player] = nil - end - if player_bytes[player] then - player_bytes[player] = nil - end - if queueUnreliableEvent[player] then - table.clear(queueUnreliableEvent[player]) - queueUnreliableEvent[player] = nil - end - end) - for _, player: Player in ipairs(Players:GetPlayers()) do - onAdded(player) - end + -- reliable + for player: Player, content in queueEvent do + if #content == 0 or player.Parent ~= Players then + continue + end + Buffer.writeEvents(writer, content, eventSchemas) + do + local buf, ref = Buffer.buildWithRefs(writer) + Buffer.reset(writer) + if not ref or #ref == 0 then + Event:FireClient(player, buf) + else + Event:FireClient(player, buf, ref) + end + end + player_bytes[player] = 0 + table.clear(queueEvent[player]) + end + -- unreliable + for player: Player, content in queueUnreliableEvent do + if #content == 0 or player.Parent ~= Players then + continue + end + Buffer.writeEvents(writer, content, eventSchemas) + do + local buf, ref = Buffer.buildWithRefs(writer) + Buffer.reset(writer) + if not ref or #ref == 0 then + UnreliableEvent:FireClient(player, buf) + else + UnreliableEvent:FireClient(player, buf, ref) + end + end + table.clear(player_bytes) + table.clear(queueUnreliableEvent[player]) + end + end) + + local function onAdded(player: Player) + if not table.find(players_ready, player) then + table.insert(players_ready, player) + end + if not queueEvent[player] then + queueEvent[player] = {} :: any + end + if not queueUnreliableEvent[player] then + queueUnreliableEvent[player] = {} :: any + end + end + Players.PlayerAdded:Connect(onAdded) + Players.PlayerRemoving:Connect(function(player: Player) + Replication.remove(player) + table.remove(players_ready, table.find(players_ready, player)) + if queueEvent[player] then + table.clear(queueEvent[player]) + queueEvent[player] = nil + end + if player_bytes[player] then + player_bytes[player] = nil + end + if queueUnreliableEvent[player] then + table.clear(queueUnreliableEvent[player]) + queueUnreliableEvent[player] = nil + end + end) + for _, player: Player in ipairs(Players:GetPlayers()) do + onAdded(player) + end end --[[