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()
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 } } = {}
@ -29,214 +29,237 @@ local eventSchemas: { [number]: Buffer.SchemaType } = {}
local pendingInvokes: { [string]: thread } = {}
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
--@remoteName string
--@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
-- Disconnect all connections and remove the event.
Client.Destroy = Client.DisconnectAll
--@remoteName string
--@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
--[[
@class Client
@schema
define a schema for your data and use a strict packing
@class Client
@schema
define a schema for your data and use a strict packing
]]
Client.Schema = Buffer.Schema

View file

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

View file

@ -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 } = {}
@ -37,267 +37,300 @@ local players_ready: { Player }, player_bytes: { [Player]: number } = {}, {}
local pendingInvokes: { [string]: thread } = {}
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 })
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
-- Disconnect all connections and remove the event.
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?)
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
--@reliable boolean
--@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
--@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?
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
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
Event.OnServerEvent:Connect(function(player: Player, b: buffer, ref: { Instance }?)
processIncoming(player, b, ref, true)
end)
Event.OnServerEvent:Connect(function(player: Player, b: buffer, ref: { Instance }?)
processIncoming(player, b, ref, true)
end)
UnreliableEvent.OnServerEvent:Connect(function(player: Player, b: buffer, ref: { Instance }?)
processIncoming(player, b, ref, false)
end)
UnreliableEvent.OnServerEvent:Connect(function(player: Player, b: buffer, ref: { Instance }?)
processIncoming(player, 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
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)
-- 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
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
--[[
@class Server
@schema
define a schema for your data and use a strict packing
@class Server
@schema
define a schema for your data and use a strict packing
]]
Server.Schema = Buffer.Schema