mirror of
https://github.com/imezx/Warp.git
synced 2026-06-02 12:18:32 +00:00
feat: XOR encoding
This commit is contained in:
parent
39143db390
commit
ce8cfeef5e
4 changed files with 146 additions and 38 deletions
|
|
@ -1 +1 @@
|
||||||
{"name":"warp","className":"ModuleScript","filePaths":["src/init.luau","default.project.json"],"children":[{"name":"Client","className":"ModuleScript","filePaths":["src/Client/init.luau"]},{"name":"Replication","className":"ModuleScript","filePaths":["src/Replication/init.luau"]},{"name":"Server","className":"ModuleScript","filePaths":["src/Server/init.luau"]},{"name":"Util","className":"Folder","children":[{"name":"Buffer","className":"ModuleScript","filePaths":["src/Util/Buffer/init.luau"]},{"name":"Identifier","className":"ModuleScript","filePaths":["src/Util/Identifier.luau"]},{"name":"Thread","className":"ModuleScript","filePaths":["src/Util/Thread.luau"]}]}]}
|
{"name":"warp","className":"ModuleScript","filePaths":["src/init.luau","default.project.json"],"children":[{"name":"Client","className":"ModuleScript","filePaths":["src/Client/init.luau"]},{"name":"Replication","className":"ModuleScript","filePaths":["src/Replication/init.luau"]},{"name":"Server","className":"ModuleScript","filePaths":["src/Server/init.luau"]},{"name":"Util","className":"Folder","children":[{"name":"Buffer","className":"ModuleScript","filePaths":["src/Util/Buffer/init.luau"]},{"name":"Identifier","className":"ModuleScript","filePaths":["src/Util/Identifier.luau"]},{"name":"Thread","className":"ModuleScript","filePaths":["src/Util/Thread.luau"]},{"name":"Xor","className":"ModuleScript","filePaths":["src/Util/Xor.luau"]}]}]}
|
||||||
|
|
@ -3,10 +3,12 @@
|
||||||
--@EternityDev
|
--@EternityDev
|
||||||
local Client = {}
|
local Client = {}
|
||||||
|
|
||||||
local RunService = game:GetService("RunService")
|
|
||||||
local Thread = require("./Util/Thread")
|
local Thread = require("./Util/Thread")
|
||||||
local Buffer = require("./Util/Buffer")
|
local Buffer = require("./Util/Buffer")
|
||||||
local Replication = require("./Replication")
|
local Replication = require("./Replication")
|
||||||
|
local Xor = require("./Util/Xor")
|
||||||
|
|
||||||
|
local RunService = game:GetService("RunService")
|
||||||
local Event: RemoteEvent = script.Parent:WaitForChild("Event")
|
local Event: RemoteEvent = script.Parent:WaitForChild("Event")
|
||||||
local UnreliableEvent: UnreliableRemoteEvent = script.Parent:WaitForChild("UnreliableEvent")
|
local UnreliableEvent: UnreliableRemoteEvent = script.Parent:WaitForChild("UnreliableEvent")
|
||||||
local deltaT: number, cycle: number = 0, 1 / 61
|
local deltaT: number, cycle: number = 0, 1 / 61
|
||||||
|
|
@ -26,6 +28,7 @@ local queueUnreliableEvent: { { any } } = {}
|
||||||
local eventListeners: { [number]: { Event } } = {}
|
local eventListeners: { [number]: { Event } } = {}
|
||||||
local eventSchemas: { [number]: Buffer.SchemaType } = {}
|
local eventSchemas: { [number]: Buffer.SchemaType } = {}
|
||||||
|
|
||||||
|
local lastDelta: { [number]: { any } } = {}
|
||||||
local pendingInvokes: { [string]: thread } = {}
|
local pendingInvokes: { [string]: thread } = {}
|
||||||
local invokeId = 0
|
local invokeId = 0
|
||||||
|
|
||||||
|
|
@ -165,13 +168,12 @@ 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 end
|
||||||
return
|
local decoded: buffer = handleInvokes and Xor.decodeClient(b) or b
|
||||||
end
|
local contents = Buffer.readEvents(decoded, 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 args = content[2]
|
||||||
if handleInvokes then
|
if handleInvokes then
|
||||||
if remote == 0 then
|
if remote == 0 then
|
||||||
local id = content[1]
|
local id = content[1]
|
||||||
|
|
@ -184,17 +186,12 @@ if RunService:IsClient() then
|
||||||
continue
|
continue
|
||||||
end
|
end
|
||||||
if remote == 1 then
|
if remote == 1 then
|
||||||
local remoteName = content[1]
|
local remoteName, id, rargs = args[1], args[2], args[3]
|
||||||
local id = content[2]
|
|
||||||
local args = content[3]
|
|
||||||
local connections = eventListeners[remoteName]
|
local connections = eventListeners[remoteName]
|
||||||
if connections and #connections > 0 then
|
if connections and #connections > 0 then
|
||||||
Thread(function()
|
Thread(function()
|
||||||
local results = { connections[1].c(table.unpack(args)) }
|
local results = { connections[1].c(table.unpack(rargs)) }
|
||||||
table.insert(queueEvent, {
|
table.insert(queueEvent, { 1, { id, results } :: any })
|
||||||
1,
|
|
||||||
{ id, results } :: any,
|
|
||||||
})
|
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
continue
|
continue
|
||||||
|
|
@ -203,7 +200,7 @@ if RunService:IsClient() then
|
||||||
local connections = eventListeners[remote]
|
local connections = eventListeners[remote]
|
||||||
if connections then
|
if connections then
|
||||||
for _, connection in connections do
|
for _, connection in connections do
|
||||||
Thread(connection.c, table.unpack(content))
|
Thread(connection.c, table.unpack(args))
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
@ -219,21 +216,20 @@ if RunService:IsClient() then
|
||||||
|
|
||||||
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 end
|
||||||
return
|
|
||||||
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)
|
||||||
|
local encoded = Xor.encodeClient(buf)
|
||||||
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(encoded)
|
||||||
else
|
else
|
||||||
Event:FireServer(buf, ref)
|
Event:FireServer(encoded, ref)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
table.clear(queueEvent)
|
table.clear(queueEvent)
|
||||||
|
|
|
||||||
|
|
@ -3,12 +3,14 @@
|
||||||
--@EternityDev
|
--@EternityDev
|
||||||
local Server = {}
|
local Server = {}
|
||||||
|
|
||||||
local Players = game:GetService("Players")
|
|
||||||
local RunService = game:GetService("RunService")
|
|
||||||
local Thread = require("./Util/Thread")
|
local Thread = require("./Util/Thread")
|
||||||
local Buffer = require("./Util/Buffer")
|
local Buffer = require("./Util/Buffer")
|
||||||
local Identifier = require("./Util/Identifier")
|
local Identifier = require("./Util/Identifier")
|
||||||
local Replication = require("./Replication")
|
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 Event: RemoteEvent = script.Parent:WaitForChild("Event")
|
||||||
local _repl: RemoteEvent = script.Parent:WaitForChild("_repl")
|
local _repl: RemoteEvent = script.Parent:WaitForChild("_repl")
|
||||||
local UnreliableEvent: UnreliableRemoteEvent = script.Parent:WaitForChild("UnreliableEvent")
|
local UnreliableEvent: UnreliableRemoteEvent = script.Parent:WaitForChild("UnreliableEvent")
|
||||||
|
|
@ -198,14 +200,15 @@ if RunService:IsServer() then
|
||||||
player_bytes[player] = bytes
|
player_bytes[player] = bytes
|
||||||
end
|
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
|
for _, content in contents do
|
||||||
local remote = content[1]
|
local remote = content[1]
|
||||||
local content = content[2]
|
local d = content[2]
|
||||||
if handleInvokes then
|
if handleInvokes then
|
||||||
if remote == 1 then
|
if remote == 1 then
|
||||||
local id = content[1]
|
local id = d[1]
|
||||||
local results = content[2]
|
local results = d[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))
|
||||||
|
|
@ -217,9 +220,9 @@ if RunService:IsServer() then
|
||||||
if #eventListeners == 0 then
|
if #eventListeners == 0 then
|
||||||
continue
|
continue
|
||||||
end
|
end
|
||||||
local remoteName = content[1]
|
local remoteName = d[1]
|
||||||
local id = content[2]
|
local id = d[2]
|
||||||
local args = content[3]
|
local args = d[3]
|
||||||
local connections = eventListeners[remoteName]
|
local connections = eventListeners[remoteName]
|
||||||
if connections and #connections > 0 then
|
if connections and #connections > 0 then
|
||||||
Thread(function()
|
Thread(function()
|
||||||
|
|
@ -239,7 +242,7 @@ if RunService:IsServer() then
|
||||||
local connections = eventListeners[remote]
|
local connections = eventListeners[remote]
|
||||||
if connections then
|
if connections then
|
||||||
for _, connection in connections do
|
for _, connection in connections do
|
||||||
Thread(connection.c, player, table.unpack(content))
|
Thread(connection.c, player, table.unpack(d))
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
@ -268,11 +271,12 @@ if RunService:IsServer() then
|
||||||
Buffer.writeEvents(writer, content, eventSchemas)
|
Buffer.writeEvents(writer, content, eventSchemas)
|
||||||
do
|
do
|
||||||
local buf, ref = Buffer.buildWithRefs(writer)
|
local buf, ref = Buffer.buildWithRefs(writer)
|
||||||
|
local encoded = Xor.encodeServer(player, buf)
|
||||||
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, encoded)
|
||||||
else
|
else
|
||||||
Event:FireClient(player, buf, ref)
|
Event:FireClient(player, encoded, ref)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
player_bytes[player] = 0
|
player_bytes[player] = 0
|
||||||
|
|
@ -324,6 +328,7 @@ if RunService:IsServer() then
|
||||||
table.clear(queueUnreliableEvent[player])
|
table.clear(queueUnreliableEvent[player])
|
||||||
queueUnreliableEvent[player] = nil
|
queueUnreliableEvent[player] = nil
|
||||||
end
|
end
|
||||||
|
Xor.remove(player)
|
||||||
end)
|
end)
|
||||||
for _, player: Player in ipairs(Players:GetPlayers()) do
|
for _, player: Player in ipairs(Players:GetPlayers()) do
|
||||||
onAdded(player)
|
onAdded(player)
|
||||||
|
|
@ -331,9 +336,9 @@ if RunService:IsServer() then
|
||||||
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
|
||||||
|
|
||||||
|
|
|
||||||
107
src/Util/Xor.luau
Normal file
107
src/Util/Xor.luau
Normal file
|
|
@ -0,0 +1,107 @@
|
||||||
|
--!strict
|
||||||
|
--!native
|
||||||
|
--!optimize 2
|
||||||
|
local Xor = {}
|
||||||
|
|
||||||
|
type XorEntry = { buf: buffer, len: number }
|
||||||
|
|
||||||
|
local serverPrev: { [Player]: XorEntry } = {}
|
||||||
|
local clientPrev: { [Player]: XorEntry } = {}
|
||||||
|
|
||||||
|
local clientOutPrev: XorEntry? = nil
|
||||||
|
local clientInPrev: XorEntry? = nil
|
||||||
|
|
||||||
|
local function xorApply(current: buffer, curLen: number, prev: buffer, prevLen: number): buffer
|
||||||
|
local out = buffer.create(curLen)
|
||||||
|
local common = if curLen < prevLen then curLen else prevLen
|
||||||
|
|
||||||
|
local wordEnd = (common // 4) * 4
|
||||||
|
for off = 0, wordEnd - 4, 4 do
|
||||||
|
buffer.writeu32(out, off, bit32.bxor(buffer.readu32(current, off), buffer.readu32(prev, off)))
|
||||||
|
end
|
||||||
|
for i = wordEnd, common - 1 do
|
||||||
|
buffer.writeu8(out, i, bit32.bxor(buffer.readu8(current, i), buffer.readu8(prev, i)))
|
||||||
|
end
|
||||||
|
|
||||||
|
if curLen > common then
|
||||||
|
buffer.copy(out, common, current, common, curLen - common)
|
||||||
|
end
|
||||||
|
return out
|
||||||
|
end
|
||||||
|
|
||||||
|
local function storeEntry(entry: XorEntry?, src: buffer, len: number): XorEntry
|
||||||
|
local e: XorEntry = entry or { buf = buffer.create(len > 64 and len or 64), len = 0 }
|
||||||
|
|
||||||
|
if buffer.len(e.buf) < len then
|
||||||
|
e.buf = buffer.create(len * 2)
|
||||||
|
end
|
||||||
|
|
||||||
|
buffer.copy(e.buf, 0, src, 0, len)
|
||||||
|
e.len = len
|
||||||
|
return e
|
||||||
|
end
|
||||||
|
|
||||||
|
function Xor.remove(player: Player)
|
||||||
|
serverPrev[player] = nil
|
||||||
|
clientPrev[player] = nil
|
||||||
|
end
|
||||||
|
|
||||||
|
function Xor.encodeServer(player: Player, buf: buffer): buffer
|
||||||
|
local len = buffer.len(buf)
|
||||||
|
local entry = serverPrev[player]
|
||||||
|
|
||||||
|
if not entry then
|
||||||
|
serverPrev[player] = storeEntry(nil, buf, len)
|
||||||
|
return buf
|
||||||
|
end
|
||||||
|
|
||||||
|
local encoded = xorApply(buf, len, entry.buf, entry.len)
|
||||||
|
serverPrev[player] = storeEntry(entry, buf, len)
|
||||||
|
return encoded
|
||||||
|
end
|
||||||
|
|
||||||
|
function Xor.decodeClient(buf: buffer): buffer
|
||||||
|
local len = buffer.len(buf)
|
||||||
|
local prev = clientInPrev
|
||||||
|
|
||||||
|
if not prev then
|
||||||
|
clientInPrev = storeEntry(nil, buf, len)
|
||||||
|
return buf
|
||||||
|
end
|
||||||
|
|
||||||
|
local recovered = xorApply(buf, len, prev.buf, prev.len)
|
||||||
|
clientInPrev = storeEntry(prev, recovered, len)
|
||||||
|
return recovered
|
||||||
|
end
|
||||||
|
|
||||||
|
function Xor.encodeClient(buf: buffer): buffer
|
||||||
|
local len = buffer.len(buf)
|
||||||
|
local prev = clientOutPrev
|
||||||
|
|
||||||
|
if not prev then
|
||||||
|
clientOutPrev = storeEntry(nil, buf, len)
|
||||||
|
return buf
|
||||||
|
end
|
||||||
|
|
||||||
|
local encoded = xorApply(buf, len, prev.buf, prev.len)
|
||||||
|
clientOutPrev = storeEntry(prev, buf, len)
|
||||||
|
return encoded
|
||||||
|
end
|
||||||
|
|
||||||
|
function Xor.decodeServer(player: Player, buf: buffer): buffer
|
||||||
|
local len = buffer.len(buf)
|
||||||
|
local entry = clientPrev[player]
|
||||||
|
|
||||||
|
if not entry then
|
||||||
|
clientPrev[player] = storeEntry(nil, buf, len)
|
||||||
|
return buf
|
||||||
|
end
|
||||||
|
|
||||||
|
local recovered = xorApply(buf, len, entry.buf, entry.len)
|
||||||
|
clientPrev[player] = storeEntry(entry, recovered, len)
|
||||||
|
return recovered
|
||||||
|
end
|
||||||
|
|
||||||
|
Xor._apply = xorApply
|
||||||
|
|
||||||
|
return Xor
|
||||||
Loading…
Reference in a new issue