2026-02-10 18:11:25 +00:00
|
|
|
--!optimize 2
|
|
|
|
|
--!strict
|
|
|
|
|
--@EternityDev
|
|
|
|
|
local Client = {}
|
|
|
|
|
|
|
|
|
|
local RunService = game:GetService("RunService")
|
2026-02-13 10:37:23 +00:00
|
|
|
local Thread = require("./Util/Thread")
|
|
|
|
|
local Buffer = require("./Util/Buffer")
|
2026-02-16 07:28:47 +00:00
|
|
|
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 = {
|
2026-02-13 10:37:23 +00:00
|
|
|
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 } = {}
|
2026-02-13 10:37:23 +00:00
|
|
|
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 07:28:47 +00:00
|
|
|
eventSchemas[Replication.get_id(remoteName)] = 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-10 18:11:25 +00:00
|
|
|
local detail = {
|
2026-02-16 07:28:47 +00:00
|
|
|
i = Replication.get_id(remoteName),
|
2026-02-13 10:37:23 +00:00
|
|
|
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)
|
2026-02-16 07:28:47 +00:00
|
|
|
local id = Replication.get_id(remoteName)
|
2026-02-10 18:11:25 +00:00
|
|
|
for idx = #eventListeners, 1, -1 do
|
2026-02-13 10:37:23 +00:00
|
|
|
if eventListeners[idx].i == id then
|
2026-02-10 18:11:25 +00:00
|
|
|
table.remove(eventListeners, idx)
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
2026-02-13 10:37:23 +00:00
|
|
|
Client.Destroy = Client.DisconnectAll
|
2026-02-10 18:11:25 +00:00
|
|
|
|
2026-02-11 08:00:23 +00:00
|
|
|
Client.Fire = function(remoteName: string, reliable: boolean, ...: any?)
|
|
|
|
|
table.insert(reliable and queueEvent or queueUnreliableEvent, {
|
2026-02-16 07:28:47 +00:00
|
|
|
Replication.get_id(remoteName),
|
2026-02-11 08:00:23 +00:00
|
|
|
{ ... } :: any,
|
2026-02-10 18:11:25 +00:00
|
|
|
})
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
Client.Invoke = function(remoteName: string, timeout: number?, ...: any?): ...any?
|
|
|
|
|
invokeId += 1
|
2026-02-13 10:37:23 +00:00
|
|
|
local reqid, thread = `{invokeId}`, coroutine.running()
|
2026-02-10 18:11:25 +00:00
|
|
|
|
2026-02-13 10:37:23 +00:00
|
|
|
pendingInvokes[reqid] = thread
|
2026-02-10 18:11:25 +00:00
|
|
|
task.delay(timeout or 2, function()
|
2026-02-13 10:37:23 +00:00
|
|
|
local pending = pendingInvokes[reqid]
|
2026-02-10 18:11:25 +00:00
|
|
|
if not pending then
|
|
|
|
|
return
|
|
|
|
|
end
|
|
|
|
|
task.spawn(pending, nil)
|
2026-02-13 10:37:23 +00:00
|
|
|
pendingInvokes[reqid] = nil
|
2026-02-10 18:11:25 +00:00
|
|
|
end)
|
|
|
|
|
table.insert(queueEvent, {
|
2026-02-13 10:37:23 +00:00
|
|
|
0,
|
2026-02-16 07:28:47 +00:00
|
|
|
{ Replication.get_id(remoteName), 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
|
2026-02-11 05:07:17 +00:00
|
|
|
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
|
2026-02-13 10:37:23 +00:00
|
|
|
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
|
2026-02-13 10:37:23 +00:00
|
|
|
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
|
2026-02-13 10:37:23 +00:00
|
|
|
if connection.i == remoteName then
|
2026-02-11 08:00:23 +00:00
|
|
|
Thread(function()
|
2026-02-13 10:37:23 +00:00
|
|
|
local results = { connection.c(table.unpack(args)) }
|
2026-02-11 08:00:23 +00:00
|
|
|
table.insert(queueEvent, {
|
2026-02-13 10:37:23 +00:00
|
|
|
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
|
2026-02-13 10:37:23 +00:00
|
|
|
if connection.i ~= remote then
|
2026-02-11 08:00:23 +00:00
|
|
|
continue
|
|
|
|
|
end
|
2026-02-13 10:37:23 +00:00
|
|
|
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)
|