From b425db5f0bfaa05bc30f1fe90ee04ac532542277 Mon Sep 17 00:00:00 2001 From: Khietsly Tristan Date: Wed, 22 Apr 2026 08:40:17 +0700 Subject: [PATCH] chore: fix client & server processing for invoke --- src/Client/init.luau | 95 +++++++++++++++++----------------- src/Replication/init.luau | 5 +- src/Server/init.luau | 104 ++++++++++++++++++++------------------ 3 files changed, 102 insertions(+), 102 deletions(-) diff --git a/src/Client/init.luau b/src/Client/init.luau index 01e9c8d..8d63f50 100644 --- a/src/Client/init.luau +++ b/src/Client/init.luau @@ -3,10 +3,12 @@ --@EternityDev local Client = {} -local RunService = game:GetService("RunService") local Thread = require("./Util/Thread") local Buffer = require("./Util/Buffer") local Replication = require("./Replication") +local Xor = require("./Util/Xor") + +local RunService = game:GetService("RunService") local Event: RemoteEvent = script.Parent:WaitForChild("Event") local UnreliableEvent: UnreliableRemoteEvent = script.Parent:WaitForChild("UnreliableEvent") local deltaT: number, cycle: number = 0, 1 / 61 @@ -23,9 +25,10 @@ type Event = { local queueEvent: { { any } } = {} local queueUnreliableEvent: { { any } } = {} -local eventListeners: { Event } = {} +local eventListeners: { [number]: { Event } } = {} local eventSchemas: { [number]: Buffer.SchemaType } = {} +local lastDelta: { [number]: { any } } = {} local pendingInvokes: { [string]: thread } = {} local invokeId = 0 @@ -52,13 +55,21 @@ Client.Connect = function(remoteName: string, fn: (Player, ...any?) -> ...any?): 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 + return { + Connected = false, + Disconnect = function() + return + end, + } :: Connection end local detail = { i = id, c = fn, } - table.insert(eventListeners, detail) + if not eventListeners[id] then + eventListeners[id] = {} + end + table.insert(eventListeners[id], detail) return { Connected = true, Disconnect = function(self: Connection) @@ -66,9 +77,12 @@ Client.Connect = function(remoteName: string, fn: (Player, ...any?) -> ...any?): return end self.Connected = false - local idx = table.find(eventListeners, detail) - if idx then - table.remove(eventListeners, idx) + local bucket = eventListeners[detail.i] + if bucket then + local idx = table.find(bucket, detail) + if idx then + table.remove(bucket, idx) + end end end, } :: Connection @@ -105,11 +119,7 @@ Client.DisconnectAll = function(remoteName: string) if not id then return end - for idx = #eventListeners, 1, -1 do - if eventListeners[idx].i == id then - table.remove(eventListeners, idx) - end - end + eventListeners[id] = nil end --@remoteName string @@ -158,17 +168,16 @@ 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) + if type(b) ~= "buffer" then return end + local decoded: buffer = handleInvokes and Xor.decodeClient(b) or b + local contents = Buffer.readEvents(decoded, ref, eventSchemas) for _, content in contents do local remote = content[1] - local content = content[2] + local args = content[2] if handleInvokes then if remote == 0 then - local id = content[1] - local results = content[2] + local id = args[1] + local results = args[2] local pending = pendingInvokes[id] if pending then task.spawn(pending :: any, table.unpack(results)) @@ -177,35 +186,22 @@ if RunService:IsClient() then 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 + local remoteName, id, rargs = args[1], args[2], args[3] + local connections = eventListeners[remoteName] + if connections and #connections > 0 then + Thread(function() + local results = { connections[1].c(table.unpack(rargs)) } + table.insert(queueEvent, { 1, { id, results } :: any }) + end) end continue end end - if #eventListeners == 0 then - continue - end - for _, connection in eventListeners do - if connection.i ~= remote then - continue + local connections = eventListeners[remote] + if connections then + for _, connection in connections do + Thread(connection.c, table.unpack(args)) end - Thread(connection.c, table.unpack(content)) end end end @@ -220,21 +216,20 @@ if RunService:IsClient() then RunService.PostSimulation:Connect(function(d: number) deltaT += d - if deltaT < cycle then - return - end + if deltaT < cycle then return end deltaT = 0 - - -- reliable + + --reliable if #queueEvent > 0 then Buffer.writeEvents(writer, queueEvent, eventSchemas) do local buf, ref = Buffer.buildWithRefs(writer) + local encoded = Xor.encodeClient(buf) Buffer.reset(writer) if not ref or #ref == 0 then - Event:FireServer(buf) + Event:FireServer(encoded) else - Event:FireServer(buf, ref) + Event:FireServer(encoded, ref) end end table.clear(queueEvent) diff --git a/src/Replication/init.luau b/src/Replication/init.luau index 224c501..930e941 100644 --- a/src/Replication/init.luau +++ b/src/Replication/init.luau @@ -14,7 +14,7 @@ local warp_identifier_registry = shared.__warp_identifier_registry if RunService:IsClient() or RunService:IsRunMode() then local pending_id_yields, pending_name_yields, ready_yields = {}, {}, {} local is_ready = false - + if RunService:IsClient() then _repl.OnClientEvent:Connect(function(b: buffer) if type(b) ~= "buffer" then @@ -26,7 +26,7 @@ if RunService:IsClient() or RunService:IsRunMode() then local id: number, remote: string = content[1], content[2] warp_identifier_registry.cache[remote] = id warp_identifier_registry.name[id] = remote - + if pending_id_yields[remote] then for _, thread in pending_id_yields[remote] do task.spawn(thread, id) @@ -55,7 +55,6 @@ if RunService:IsClient() or RunService:IsRunMode() then -- wait for the identifiers to be replicated from the server Replication.wait_for_ready = function() if is_ready then return end - local thread = coroutine.running() table.insert(ready_yields, thread) coroutine.yield() diff --git a/src/Server/init.luau b/src/Server/init.luau index c3effd3..5af7b8b 100644 --- a/src/Server/init.luau +++ b/src/Server/init.luau @@ -3,12 +3,14 @@ --@EternityDev local Server = {} -local Players = game:GetService("Players") -local RunService = game:GetService("RunService") local Thread = require("./Util/Thread") local Buffer = require("./Util/Buffer") local Identifier = require("./Util/Identifier") local Replication = require("./Replication") +local Xor = require("./Util/Xor") + +local Players = game:GetService("Players") +local RunService = game:GetService("RunService") local Event: RemoteEvent = script.Parent:WaitForChild("Event") local _repl: RemoteEvent = script.Parent:WaitForChild("_repl") local UnreliableEvent: UnreliableRemoteEvent = script.Parent:WaitForChild("UnreliableEvent") @@ -30,7 +32,7 @@ local queueEvent: { local queueUnreliableEvent: { [Player]: { { any } }, } = {} -local eventListeners: { Event } = {} +local eventListeners: { [number]: { Event } } = {} local eventSchemas: { [number]: Buffer.SchemaType } = {} local players_ready: { Player }, player_bytes: { [Player]: number } = {}, {} @@ -61,7 +63,10 @@ Server.Connect = function(remoteName: string, fn: (Player, ...any?) -> ...any?): i = Identifier.get_id(remoteName), c = fn, } - table.insert(eventListeners, detail) + if not eventListeners[detail.i] then + eventListeners[detail.i] = {} + end + table.insert(eventListeners[detail.i], detail) return { Connected = true, Disconnect = function(self: Connection) @@ -69,9 +74,12 @@ Server.Connect = function(remoteName: string, fn: (Player, ...any?) -> ...any?): return end self.Connected = false - local idx = table.find(eventListeners, detail) - if idx then - table.remove(eventListeners, idx) + local bucket = eventListeners[detail.i] + if bucket then + local idx = table.find(bucket, detail) + if idx then + table.remove(bucket, idx) + end end end, } :: Connection @@ -108,11 +116,7 @@ Server.DisconnectAll = function(remoteName: string) if not id then return end - for idx = #eventListeners, 1, -1 do - if eventListeners[idx].i == id then - table.remove(eventListeners, idx) - end - end + eventListeners[id] = nil end --@remoteName string @@ -149,7 +153,9 @@ end -- 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 + if table.find(except, player) then + continue + end Server.Fire(remoteName, reliable, player, ...) end end @@ -188,18 +194,21 @@ if RunService:IsServer() then 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 + if bytes > 8e3 then + return + end player_bytes[player] = bytes end - local contents = Buffer.readEvents(b, ref, eventSchemas) + local decoded = handleInvokes and Xor.decodeServer(player, b) or b + local contents = Buffer.readEvents(decoded, ref, eventSchemas) for _, content in contents do local remote = content[1] - local content = content[2] + local d = content[2] if handleInvokes then if remote == 1 then - local id = content[1] - local results = content[2] + local id = d[1] + local results = d[2] local pending = pendingInvokes[id] if pending then task.spawn(pending :: any, table.unpack(results)) @@ -208,38 +217,33 @@ if RunService:IsServer() then continue end if remote == 0 then - if #eventListeners == 0 then + if not next(eventListeners) 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 + local remoteName = d[1] + local id = d[2] + local args = d[3] + local connections = eventListeners[remoteName] + if connections and #connections > 0 then + Thread(function() + local results = { connections[1].c(player, table.unpack(args)) } + if not queueEvent[player] then + queueEvent[player] = {} :: any + end + table.insert(queueEvent[player], { + 0, + { id, results } :: any, + }) + end) end continue end end - if #eventListeners == 0 then - continue - end - for _, connection in eventListeners do - if connection.i ~= remote then - continue + local connections = eventListeners[remote] + if connections then + for _, connection in connections do + Thread(connection.c, player, table.unpack(d)) end - Thread(connection.c, player, table.unpack(content)) end end end @@ -267,11 +271,12 @@ if RunService:IsServer() then Buffer.writeEvents(writer, content, eventSchemas) do local buf, ref = Buffer.buildWithRefs(writer) + local encoded = Xor.encodeServer(player, buf) Buffer.reset(writer) if not ref or #ref == 0 then - Event:FireClient(player, buf) + Event:FireClient(player, encoded) else - Event:FireClient(player, buf, ref) + Event:FireClient(player, encoded, ref) end end player_bytes[player] = 0 @@ -292,7 +297,7 @@ if RunService:IsServer() then UnreliableEvent:FireClient(player, buf, ref) end end - table.clear(player_bytes) + player_bytes[player] = 0 table.clear(queueUnreliableEvent[player]) end end) @@ -323,6 +328,7 @@ if RunService:IsServer() then table.clear(queueUnreliableEvent[player]) queueUnreliableEvent[player] = nil end + Xor.remove(player) end) for _, player: Player in ipairs(Players:GetPlayers()) do onAdded(player) @@ -330,9 +336,9 @@ if RunService:IsServer() then 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