rewrite(phase5): add docstring

This commit is contained in:
khtsly 2026-02-23 12:02:18 +07:00
parent 85f6aa8b77
commit c4a7934bc4
3 changed files with 459 additions and 404 deletions

View file

@ -13,12 +13,12 @@ local deltaT: number, cycle: number = 0, 1 / 61
local writer: Buffer.Writer = Buffer.createWriter() local writer: Buffer.Writer = Buffer.createWriter()
type Connection = { type Connection = {
Connected: boolean, Connected: boolean,
Disconnect: (self: Connection) -> (), Disconnect: (self: Connection) -> (),
} }
type Event = { type Event = {
i: number, i: number,
c: (Player, ...any?) -> ...any?, c: (Player, ...any?) -> ...any?,
} }
local queueEvent: { { any } } = {} local queueEvent: { { any } } = {}
@ -29,214 +29,237 @@ local eventSchemas: { [number]: Buffer.SchemaType } = {}
local pendingInvokes: { [string]: thread } = {} local pendingInvokes: { [string]: thread } = {}
local invokeId = 0 local invokeId = 0
--@optional
-- Yields the current thread until the client has successfully initialized and synchronized with the server's replication data (identifier). Its optionally, but highly recommended to call this before firing or connecting to any events to ensure the network is fully ready.
Client.awaitReady = Replication.wait_for_ready Client.awaitReady = Replication.wait_for_ready
--@remoteName string
--@schema Buffer.SchemaType
-- Define a schema for strict data packing on a specific event.
Client.useSchema = function(remoteName: string, schema: Buffer.SchemaType) Client.useSchema = function(remoteName: string, schema: Buffer.SchemaType)
local id = Replication.get_id(remoteName) local id = Replication.get_id(remoteName)
if not id then if not id then
warn(`[Warp]: ".useSchema"::"{remoteName}" does not exist, likely its not registered on the server yet.`) warn(`[Warp]: ".useSchema"::"{remoteName}" does not exist, likely its not registered on the server yet.`)
return return
end end
eventSchemas[id] = schema eventSchemas[id] = schema
end 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 Client.Connect = function(remoteName: string, fn: (Player, ...any?) -> ...any?): Connection
local id = Replication.get_id(remoteName) local id = Replication.get_id(remoteName)
if not id then if not id then
warn(`[Warp]: ".Connect"::"{remoteName}" does not exist, likely its not registered on the server yet.`) warn(`[Warp]: ".Connect"::"{remoteName}" does not exist, likely its not registered on the server yet.`)
return { Connected = false, Disconnect = function() return end } :: Connection return { Connected = false, Disconnect = function() return end } :: Connection
end end
local detail = { local detail = {
i = id, i = id,
c = fn, c = fn,
} }
table.insert(eventListeners, detail) table.insert(eventListeners, detail)
return { return {
Connected = true, Connected = true,
Disconnect = function(self: Connection) Disconnect = function(self: Connection)
if not self.Connected then if not self.Connected then
return return
end end
self.Connected = false self.Connected = false
local idx = table.find(eventListeners, detail) local idx = table.find(eventListeners, detail)
if idx then if idx then
table.remove(eventListeners, idx) table.remove(eventListeners, idx)
end end
end, end,
} :: Connection } :: Connection
end end
--@remoteName string
--@fn function
-- Similar to :Connect but automatically disconnects after the first firing.
Client.Once = function(remoteName: string, fn: (...any?) -> ()): Connection Client.Once = function(remoteName: string, fn: (...any?) -> ()): Connection
local connection local connection
connection = Client.Connect(remoteName, function(...: any?) connection = Client.Connect(remoteName, function(...: any?)
if connection then if connection then
connection:Disconnect() connection:Disconnect()
end end
fn(...) fn(...)
end) end)
return connection return connection
end end
--@remoteName string
-- Wait for an event to be triggered. Yields the current thread.
Client.Wait = function(remoteName: string): (number, ...any?) Client.Wait = function(remoteName: string): (number, ...any?)
local thread, t = coroutine.running(), os.clock() local thread, t = coroutine.running(), os.clock()
Client.Once(remoteName, function(...: any?) Client.Once(remoteName, function(...: any?)
task.spawn(thread, os.clock() - t, ...) task.spawn(thread, os.clock() - t, ...)
end) end)
return coroutine.yield() return coroutine.yield()
end end
--@remoteName string
-- Disconnect all connections for a specific event.
Client.DisconnectAll = function(remoteName: string) Client.DisconnectAll = function(remoteName: string)
local id = Replication.get_id(remoteName) local id = Replication.get_id(remoteName)
if not id then if not id then
return return
end end
for idx = #eventListeners, 1, -1 do for idx = #eventListeners, 1, -1 do
if eventListeners[idx].i == id then if eventListeners[idx].i == id then
table.remove(eventListeners, idx) table.remove(eventListeners, idx)
end end
end end
end end
--@remoteName string
-- Disconnect all connections and remove the event.
Client.Destroy = Client.DisconnectAll Client.Destroy = Client.DisconnectAll
--@remoteName string
--@reliable boolean
-- Fire an event to the server.
Client.Fire = function(remoteName: string, reliable: boolean, ...: any?) Client.Fire = function(remoteName: string, reliable: boolean, ...: any?)
local id = Replication.get_id(remoteName) local id = Replication.get_id(remoteName)
if id then if id then
table.insert(reliable and queueEvent or queueUnreliableEvent, { table.insert(reliable and queueEvent or queueUnreliableEvent, {
Replication.get_id(remoteName), Replication.get_id(remoteName),
{ ... } :: any, { ... } :: any,
}) })
end end
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? Client.Invoke = function(remoteName: string, timeout: number?, ...: any?): ...any?
local id = Replication.get_id(remoteName) local id = Replication.get_id(remoteName)
if not id then if not id then
return nil return nil
end end
invokeId += 1 invokeId += 1
local reqid, thread = `{invokeId}`, coroutine.running() local reqid, thread = `{invokeId}`, coroutine.running()
pendingInvokes[reqid] = thread pendingInvokes[reqid] = thread
task.delay(timeout or 2, function() task.delay(timeout or 2, function()
local pending = pendingInvokes[reqid] local pending = pendingInvokes[reqid]
if not pending then if not pending then
return return
end end
task.spawn(pending, nil) task.spawn(pending, nil)
pendingInvokes[reqid] = nil pendingInvokes[reqid] = nil
end) end)
table.insert(queueEvent, { table.insert(queueEvent, {
0, 0,
{ id, reqid :: any, { ... } :: any } :: any, { id, reqid :: any, { ... } :: any } :: any,
}) })
return coroutine.yield() return coroutine.yield()
end end
if RunService:IsClient() then if RunService:IsClient() then
local function processIncoming(b: buffer, ref: { Instance }?, handleInvokes: boolean) local function processIncoming(b: buffer, ref: { Instance }?, handleInvokes: boolean)
if type(b) ~= "buffer" then if type(b) ~= "buffer" then
return return
end end
local contents = Buffer.readEvents(b, ref, eventSchemas) local contents = Buffer.readEvents(b, ref, eventSchemas)
for _, content in contents do for _, content in contents do
local remote = content[1] local remote = content[1]
local content = content[2] local content = content[2]
if handleInvokes then if handleInvokes then
if remote == 0 then if remote == 0 then
local id = content[1] local id = content[1]
local results = content[2] local results = content[2]
local pending = pendingInvokes[id] local pending = pendingInvokes[id]
if pending then if pending then
task.spawn(pending :: any, table.unpack(results)) task.spawn(pending :: any, table.unpack(results))
pendingInvokes[id] = nil pendingInvokes[id] = nil
end end
continue continue
end end
if remote == 1 then if remote == 1 then
if #eventListeners == 0 then if #eventListeners == 0 then
continue continue
end end
local remoteName = content[1] local remoteName = content[1]
local id = content[2] local id = content[2]
local args = content[3] local args = content[3]
for _, connection in eventListeners do for _, connection in eventListeners do
if connection.i == remoteName then if connection.i == remoteName then
Thread(function() Thread(function()
local results = { connection.c(table.unpack(args)) } local results = { connection.c(table.unpack(args)) }
table.insert(queueEvent, { table.insert(queueEvent, {
1, 1,
{ id, results } :: any, { id, results } :: any,
}) })
end) end)
break break
end end
end end
continue continue
end end
end end
if #eventListeners == 0 then if #eventListeners == 0 then
continue continue
end end
for _, connection in eventListeners do for _, connection in eventListeners do
if connection.i ~= remote then if connection.i ~= remote then
continue continue
end end
Thread(connection.c, table.unpack(content)) Thread(connection.c, table.unpack(content))
end end
end end
end end
Event.OnClientEvent:Connect(function(b: buffer, ref: { Instance }?) Event.OnClientEvent:Connect(function(b: buffer, ref: { Instance }?)
processIncoming(b, ref, true) processIncoming(b, ref, true)
end) end)
UnreliableEvent.OnClientEvent:Connect(function(b: buffer, ref: { Instance }?) UnreliableEvent.OnClientEvent:Connect(function(b: buffer, ref: { Instance }?)
processIncoming(b, ref, false) processIncoming(b, ref, false)
end) end)
RunService.PostSimulation:Connect(function(d: number) RunService.PostSimulation:Connect(function(d: number)
deltaT += d deltaT += d
if deltaT < cycle then if deltaT < cycle then
return return
end end
deltaT = 0 deltaT = 0
-- reliable -- reliable
if #queueEvent > 0 then if #queueEvent > 0 then
Buffer.writeEvents(writer, queueEvent, eventSchemas) Buffer.writeEvents(writer, queueEvent, eventSchemas)
do do
local buf, ref = Buffer.buildWithRefs(writer) local buf, ref = Buffer.buildWithRefs(writer)
Buffer.reset(writer) Buffer.reset(writer)
if not ref or #ref == 0 then if not ref or #ref == 0 then
Event:FireServer(buf) Event:FireServer(buf)
else else
Event:FireServer(buf, ref) Event:FireServer(buf, ref)
end end
end end
table.clear(queueEvent) table.clear(queueEvent)
end end
-- unreliable -- unreliable
if #queueUnreliableEvent > 0 then if #queueUnreliableEvent > 0 then
Buffer.writeEvents(writer, queueUnreliableEvent, eventSchemas) Buffer.writeEvents(writer, queueUnreliableEvent, eventSchemas)
do do
local buf, ref = Buffer.buildWithRefs(writer) local buf, ref = Buffer.buildWithRefs(writer)
Buffer.reset(writer) Buffer.reset(writer)
if not ref or #ref == 0 then if not ref or #ref == 0 then
UnreliableEvent:FireServer(buf) UnreliableEvent:FireServer(buf)
else else
UnreliableEvent:FireServer(buf, ref) UnreliableEvent:FireServer(buf, ref)
end end
end end
table.clear(queueUnreliableEvent) table.clear(queueUnreliableEvent)
end end
end) end)
end end
--[[ --[[
@class Client @class Client
@schema @schema
define a schema for your data and use a strict packing define a schema for your data and use a strict packing
]] ]]
Client.Schema = Buffer.Schema Client.Schema = Buffer.Schema

View file

@ -86,7 +86,6 @@ if RunService:IsClient() or RunService:IsRunMode() then
if not obj then if not obj then
warn(`[Replication] timeout: could not find identifier '{name}' after {timeout}s.`) warn(`[Replication] timeout: could not find identifier '{name}' after {timeout}s.`)
end end
return obj return obj
end end

View file

@ -16,19 +16,19 @@ local deltaT: number, cycle: number = 0, 1 / 61
local writer: Buffer.Writer = Buffer.createWriter() local writer: Buffer.Writer = Buffer.createWriter()
type Connection = { type Connection = {
Connected: boolean, Connected: boolean,
Disconnect: (self: Connection) -> (), Disconnect: (self: Connection) -> (),
} }
type Event = { type Event = {
i: number, i: number,
c: (Player, ...any?) -> ...any?, c: (Player, ...any?) -> ...any?,
} }
local queueEvent: { local queueEvent: {
[Player]: { { any } }, [Player]: { { any } },
} = {} } = {}
local queueUnreliableEvent: { local queueUnreliableEvent: {
[Player]: { { any } }, [Player]: { { any } },
} = {} } = {}
local eventListeners: { Event } = {} local eventListeners: { Event } = {}
local eventSchemas: { [number]: Buffer.SchemaType } = {} local eventSchemas: { [number]: Buffer.SchemaType } = {}
@ -37,267 +37,300 @@ local players_ready: { Player }, player_bytes: { [Player]: number } = {}, {}
local pendingInvokes: { [string]: thread } = {} local pendingInvokes: { [string]: thread } = {}
local invokeId = 0 local invokeId = 0
--@namespaces { string }
--@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 }) Server.reg_namespaces = function(namespaces: { string })
for _, name: string in namespaces do for _, name: string in namespaces do
Identifier.get_id(name) Identifier.get_id(name)
end end
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) Server.useSchema = function(remoteName: string, schema: Buffer.SchemaType)
eventSchemas[Identifier.get_id(remoteName)] = schema eventSchemas[Identifier.get_id(remoteName)] = schema
end 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 Server.Connect = function(remoteName: string, fn: (Player, ...any?) -> ...any?): Connection
local detail = { local detail = {
i = Identifier.get_id(remoteName), i = Identifier.get_id(remoteName),
c = fn, c = fn,
} }
table.insert(eventListeners, detail) table.insert(eventListeners, detail)
return { return {
Connected = true, Connected = true,
Disconnect = function(self: Connection) Disconnect = function(self: Connection)
if not self.Connected then if not self.Connected then
return return
end end
self.Connected = false self.Connected = false
local idx = table.find(eventListeners, detail) local idx = table.find(eventListeners, detail)
if idx then if idx then
table.remove(eventListeners, idx) table.remove(eventListeners, idx)
end end
end, end,
} :: Connection } :: Connection
end end
--@remoteName string
--@fn function
-- Similar to :Connect but automatically disconnects after the first firing.
Server.Once = function(remoteName: string, fn: (...any?) -> ()): Connection Server.Once = function(remoteName: string, fn: (...any?) -> ()): Connection
local connection local connection
connection = Server.Connect(remoteName, function(...: any?) connection = Server.Connect(remoteName, function(...: any?)
if connection then if connection then
connection:Disconnect() connection:Disconnect()
end end
fn(...) fn(...)
end) end)
return connection return connection
end end
--@remoteName string
-- Wait for an event to be triggered. Yields the current thread.
Server.Wait = function(remoteName: string): (number, ...any?) Server.Wait = function(remoteName: string): (number, ...any?)
local thread, t = coroutine.running(), os.clock() local thread, t = coroutine.running(), os.clock()
Server.Once(remoteName, function(...: any?) Server.Once(remoteName, function(...: any?)
task.spawn(thread, os.clock() - t, ...) task.spawn(thread, os.clock() - t, ...)
end) end)
return coroutine.yield() return coroutine.yield()
end end
--@remoteName string
-- Disconnect all connections for a specific event.
Server.DisconnectAll = function(remoteName: string) Server.DisconnectAll = function(remoteName: string)
local id = Identifier.get_id(remoteName) local id = Identifier.get_id(remoteName)
if not id then if not id then
return return
end end
for idx = #eventListeners, 1, -1 do for idx = #eventListeners, 1, -1 do
if eventListeners[idx].i == id then if eventListeners[idx].i == id then
table.remove(eventListeners, idx) table.remove(eventListeners, idx)
end end
end end
end end
--@remoteName string
-- Disconnect all connections and remove the event.
Server.Destroy = Server.DisconnectAll Server.Destroy = Server.DisconnectAll
--@remoteName string
--@reliable boolean
--@player Player
-- Fire an event to a specific player.
Server.Fire = function(remoteName: string, reliable: boolean, player: Player, ...: any?) Server.Fire = function(remoteName: string, reliable: boolean, player: Player, ...: any?)
local targetQueue = reliable and queueEvent or queueUnreliableEvent local targetQueue = reliable and queueEvent or queueUnreliableEvent
if not targetQueue[player] then if not targetQueue[player] then
targetQueue[player] = {} :: any targetQueue[player] = {} :: any
end end
table.insert(targetQueue[player], { table.insert(targetQueue[player], {
Identifier.get_id(remoteName), Identifier.get_id(remoteName),
{ ... } :: any, { ... } :: any,
}) })
end end
--@remoteName string
--@reliable boolean
-- Fire an event to all connected players.
Server.Fires = function(remoteName: string, reliable: boolean, ...: any?) Server.Fires = function(remoteName: string, reliable: boolean, ...: any?)
for _, player: Player in players_ready do for _, player: Player in players_ready do
Server.Fire(remoteName, reliable, player, ...) Server.Fire(remoteName, reliable, player, ...)
end end
end end
--@remoteName string
--@reliable boolean
--@except { Player }
-- Fire an event to all players except specified ones.
Server.FireExcept = function(remoteName: string, reliable: boolean, except: { Player }, ...: any?) Server.FireExcept = function(remoteName: string, reliable: boolean, except: { Player }, ...: any?)
for _, player: Player in players_ready do for _, player: Player in players_ready do
if table.find(except, player) then continue end if table.find(except, player) then continue end
Server.Fire(remoteName, reliable, player, ...) Server.Fire(remoteName, reliable, player, ...)
end end
end end
--@remoteName string
--@player Player
--@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? Server.Invoke = function(remoteName: string, player: Player, timeout: number?, ...: any?): ...any?
invokeId += 1 invokeId += 1
local reqId, thread = `{invokeId}`, coroutine.running() local reqId, thread = `{invokeId}`, coroutine.running()
pendingInvokes[reqId] = thread pendingInvokes[reqId] = thread
task.delay(timeout or 2, function() task.delay(timeout or 2, function()
local pending = pendingInvokes[reqId] local pending = pendingInvokes[reqId]
if not pending then if not pending then
return return
end end
task.spawn(pending, nil) task.spawn(pending, nil)
pendingInvokes[reqId] = nil pendingInvokes[reqId] = nil
end) end)
if not queueEvent[player] then if not queueEvent[player] then
queueEvent[player] = {} :: any queueEvent[player] = {} :: any
end end
table.insert(queueEvent[player], { table.insert(queueEvent[player], {
0, 0,
{ Identifier.get_id(remoteName), reqId :: any, { ... } :: any } :: any, { Identifier.get_id(remoteName), reqId :: any, { ... } :: any } :: any,
}) })
return coroutine.yield() return coroutine.yield()
end end
if RunService:IsServer() then if RunService:IsServer() then
local function processIncoming(player: Player, b: buffer, ref: { Instance }?, handleInvokes: boolean) local function processIncoming(player: Player, b: buffer, ref: { Instance }?, handleInvokes: boolean)
if type(b) ~= "buffer" then if type(b) ~= "buffer" then
return return
end end
local bytes: number = (player_bytes[player] or 0) + math.max(buffer.len(b), 800) local bytes: number = (player_bytes[player] or 0) + math.max(buffer.len(b), 800)
if bytes > 8e3 then return end if bytes > 8e3 then return end
player_bytes[player] = bytes player_bytes[player] = bytes
local contents = Buffer.readEvents(b, ref, eventSchemas) local contents = Buffer.readEvents(b, ref, eventSchemas)
for _, content in contents do for _, content in contents do
local remote = content[1] local remote = content[1]
local content = content[2] local content = content[2]
if handleInvokes then if handleInvokes then
if remote == 1 then if remote == 1 then
local id = content[1] local id = content[1]
local results = content[2] local results = content[2]
local pending = pendingInvokes[id] local pending = pendingInvokes[id]
if pending then if pending then
task.spawn(pending :: any, table.unpack(results)) task.spawn(pending :: any, table.unpack(results))
pendingInvokes[id] = nil pendingInvokes[id] = nil
end end
continue continue
end end
if remote == 0 then if remote == 0 then
if #eventListeners == 0 then if #eventListeners == 0 then
continue continue
end end
local remoteName = content[1] local remoteName = content[1]
local id = content[2] local id = content[2]
local args = content[3] local args = content[3]
for _, connection in eventListeners do for _, connection in eventListeners do
if connection.i == remoteName then if connection.i == remoteName then
Thread(function() Thread(function()
local results = { connection.c(player, table.unpack(args)) } local results = { connection.c(player, table.unpack(args)) }
if not queueEvent[player] then if not queueEvent[player] then
queueEvent[player] = {} :: any queueEvent[player] = {} :: any
end end
table.insert(queueEvent[player], { table.insert(queueEvent[player], {
0, 0,
{ id, results } :: any, { id, results } :: any,
}) })
end) end)
break break
end end
end end
continue continue
end end
end end
if #eventListeners == 0 then if #eventListeners == 0 then
continue continue
end end
for _, connection in eventListeners do for _, connection in eventListeners do
if connection.i ~= remote then if connection.i ~= remote then
continue continue
end end
Thread(connection.c, player, table.unpack(content)) Thread(connection.c, player, table.unpack(content))
end end
end end
end end
Event.OnServerEvent:Connect(function(player: Player, b: buffer, ref: { Instance }?) Event.OnServerEvent:Connect(function(player: Player, b: buffer, ref: { Instance }?)
processIncoming(player, b, ref, true) processIncoming(player, b, ref, true)
end) end)
UnreliableEvent.OnServerEvent:Connect(function(player: Player, b: buffer, ref: { Instance }?) UnreliableEvent.OnServerEvent:Connect(function(player: Player, b: buffer, ref: { Instance }?)
processIncoming(player, b, ref, false) processIncoming(player, b, ref, false)
end) end)
RunService.PostSimulation:Connect(function(d: number) RunService.PostSimulation:Connect(function(d: number)
deltaT += d deltaT += d
if deltaT < cycle then if deltaT < cycle then
return return
end end
deltaT = 0 deltaT = 0
-- reliable -- reliable
for player: Player, content in queueEvent do for player: Player, content in queueEvent do
if #content == 0 or player.Parent ~= Players then if #content == 0 or player.Parent ~= Players then
continue continue
end end
Buffer.writeEvents(writer, content, eventSchemas) Buffer.writeEvents(writer, content, eventSchemas)
do do
local buf, ref = Buffer.buildWithRefs(writer) local buf, ref = Buffer.buildWithRefs(writer)
Buffer.reset(writer) Buffer.reset(writer)
if not ref or #ref == 0 then if not ref or #ref == 0 then
Event:FireClient(player, buf) Event:FireClient(player, buf)
else else
Event:FireClient(player, buf, ref) Event:FireClient(player, buf, ref)
end end
end end
player_bytes[player] = 0 player_bytes[player] = 0
table.clear(queueEvent[player]) table.clear(queueEvent[player])
end end
-- unreliable -- unreliable
for player: Player, content in queueUnreliableEvent do for player: Player, content in queueUnreliableEvent do
if #content == 0 or player.Parent ~= Players then if #content == 0 or player.Parent ~= Players then
continue continue
end end
Buffer.writeEvents(writer, content, eventSchemas) Buffer.writeEvents(writer, content, eventSchemas)
do do
local buf, ref = Buffer.buildWithRefs(writer) local buf, ref = Buffer.buildWithRefs(writer)
Buffer.reset(writer) Buffer.reset(writer)
if not ref or #ref == 0 then if not ref or #ref == 0 then
UnreliableEvent:FireClient(player, buf) UnreliableEvent:FireClient(player, buf)
else else
UnreliableEvent:FireClient(player, buf, ref) UnreliableEvent:FireClient(player, buf, ref)
end end
end end
table.clear(player_bytes) table.clear(player_bytes)
table.clear(queueUnreliableEvent[player]) table.clear(queueUnreliableEvent[player])
end end
end) end)
local function onAdded(player: Player) local function onAdded(player: Player)
if not table.find(players_ready, player) then if not table.find(players_ready, player) then
table.insert(players_ready, player) table.insert(players_ready, player)
end end
if not queueEvent[player] then if not queueEvent[player] then
queueEvent[player] = {} :: any queueEvent[player] = {} :: any
end end
if not queueUnreliableEvent[player] then if not queueUnreliableEvent[player] then
queueUnreliableEvent[player] = {} :: any queueUnreliableEvent[player] = {} :: any
end end
end end
Players.PlayerAdded:Connect(onAdded) Players.PlayerAdded:Connect(onAdded)
Players.PlayerRemoving:Connect(function(player: Player) Players.PlayerRemoving:Connect(function(player: Player)
Replication.remove(player) Replication.remove(player)
table.remove(players_ready, table.find(players_ready, player)) table.remove(players_ready, table.find(players_ready, player))
if queueEvent[player] then if queueEvent[player] then
table.clear(queueEvent[player]) table.clear(queueEvent[player])
queueEvent[player] = nil queueEvent[player] = nil
end end
if player_bytes[player] then if player_bytes[player] then
player_bytes[player] = nil player_bytes[player] = nil
end end
if queueUnreliableEvent[player] then if queueUnreliableEvent[player] then
table.clear(queueUnreliableEvent[player]) table.clear(queueUnreliableEvent[player])
queueUnreliableEvent[player] = nil queueUnreliableEvent[player] = nil
end end
end) end)
for _, player: Player in ipairs(Players:GetPlayers()) do for _, player: Player in ipairs(Players:GetPlayers()) do
onAdded(player) onAdded(player)
end end
end end
--[[ --[[
@class Server @class Server
@schema @schema
define a schema for your data and use a strict packing define a schema for your data and use a strict packing
]] ]]
Server.Schema = Buffer.Schema Server.Schema = Buffer.Schema