Compare commits

...

17 commits

Author SHA1 Message Date
EternityDev
a27067c8df
Merge pull request #31 from maneetoo/master
Some checks failed
Deploy / Build & Deploy (push) Has been cancelled
Fixed Type Annotation for Client.Connect
2026-05-05 20:16:36 +07:00
manee_too
a6456af3e3
Fixed Type Annotation for Client.Connect 2026-05-04 13:06:58 +07:00
khtsly
a21a1e964b feat: add float32 precision option for vector2 & vector3 type 2026-05-04 08:17:09 +07:00
khtsly
677a13dcc1 feat: add float32 precision option for vector2 & vector3 type 2026-05-04 08:15:52 +07:00
Khietsly Tristan
61deb37bae bump versions to pre.7 2026-04-22 08:52:44 +07:00
Khietsly Tristan
f470f74b29 chore: fix lint error for studio editor 2026-04-22 08:50:59 +07:00
Khietsly Tristan
36137ea940 chore(server): dont pass identifier id for invoke client 2026-04-22 08:49:30 +07:00
Khietsly Tristan
57f381d335 chore: fix client & server processing for invoke 2026-04-22 08:41:14 +07:00
Khietsly Tristan
b425db5f0b chore: fix client & server processing for invoke 2026-04-22 08:40:17 +07:00
khtsly
847b76921a chore(xor): shrinks the buffer when it exceed max oversize to prevents huge memory issue 2026-04-16 13:17:36 +07:00
khtsly
1834e01a24 chore(docs): fix buffer doc 2026-04-11 18:27:23 +07:00
khtsly
0b9bc3d39c chore(docs): oops this shouldnt return anything 2026-04-11 18:19:36 +07:00
khtsly
1f9ae004f4 chore(docs): add .awaitReady() 2026-04-11 18:12:23 +07:00
khtsly
597bfabb3d update .rbxm 2026-04-11 17:32:44 +07:00
khtsly
ce8cfeef5e feat: XOR encoding 2026-04-11 16:56:20 +07:00
khtsly
39143db390 chore(docs): update supported types 2026-04-09 01:01:26 +07:00
khtsly
33477c4b74 present new .rbxm file 2026-04-09 00:39:47 +07:00
13 changed files with 317 additions and 101 deletions

BIN
Warp.rbxm

Binary file not shown.

View file

@ -5,7 +5,7 @@ For efficient data serialization and schema definition with optimized packing.
## Getting the Buffer Object ## Getting the Buffer Object
```lua ```lua
local Buffer = Warp.Buffer() local Buffer = Warp.Buffer
``` ```
## Schema System <Badge type="tip" text="v1.1" /> ## Schema System <Badge type="tip" text="v1.1" />
@ -19,10 +19,9 @@ Define strict data schemas for optimized serialization and type safety.
-- Basic types -- Basic types
"boolean", "boolean",
"string", "string",
"nil",
-- Numeric types -- Numeric types
"u8", -- usigned-int "u8", -- unsigned-int
"u16", "u16",
"u32", "u32",
"i8", -- signed-int "i8", -- signed-int
@ -33,12 +32,28 @@ Define strict data schemas for optimized serialization and type safety.
"f64", "f64",
-- Roblox types -- Roblox types
"buffer" "buffer",
"vector2", -- f16 "vector2", -- f16 x/y
"vector3", -- f16 "vector3", -- f16 x/y/z
"cframe", -- f32 & f16 "vector2f32", -- f32 x/y
"color3", -- u8 "vector3f32", -- f32 x/y/z
"vector2int16", -- i16 x/y
"vector3int16", -- i16 x/y/z
"cframe", -- f32 position + compressed rotation (f16)
"color3", -- u8 r/g/b
"color3f16", "color3f16",
"udim",
"udim2",
"rect",
"ray",
"numberrange",
"colorsequence",
"numbersequence",
"brickcolor",
"tweeninfo",
"physicalproperties",
"font",
"datetime",
"instance", "instance",
-- other types -- other types
@ -49,6 +64,10 @@ Define strict data schemas for optimized serialization and type safety.
} }
``` ```
::: info
there is no standalone `"nil"` schema type. To represent a value that can be `nil`, wrap it with `"optional"` (e.g. `Buffer.Schema.optional(Buffer.Schema.u16)`).
:::
## Custom Datatypes ## Custom Datatypes
### `.custom_datatype` ### `.custom_datatype`
@ -64,7 +83,7 @@ Define strict data schemas for optimized serialization and type safety.
``` ```
```luau [Example] ```luau [Example]
local Buffer = Warp.Buffer() local Buffer = Warp.Buffer
-- # this custom datatype must be registered on both server & client side -- # this custom datatype must be registered on both server & client side
Buffer.Schema.custom_datatype("u64", {}, function(w: Buffer.Writer, value: any) -- just for reference Buffer.Schema.custom_datatype("u64", {}, function(w: Buffer.Writer, value: any) -- just for reference
@ -94,7 +113,7 @@ Create a new buffer writer for serializing data.
``` ```
```luau [Example] ```luau [Example]
local Buffer = Warp.Buffer() local Buffer = Warp.Buffer
local writer = Buffer.createWriter(256) -- Pre-allocate 256 bytes local writer = Buffer.createWriter(256) -- Pre-allocate 256 bytes
``` ```
::: :::
@ -111,7 +130,7 @@ Build the final buffer for transmission.
``` ```
```luau [Example] ```luau [Example]
local Buffer = Warp.Buffer() local Buffer = Warp.Buffer
local writer = Buffer.createWriter() local writer = Buffer.createWriter()
-- Write some data -- Write some data
@ -136,7 +155,7 @@ Build the final buffer with instance references for transmission.
``` ```
```luau [Example] ```luau [Example]
local Buffer = Warp.Buffer() local Buffer = Warp.Buffer
local writer = Buffer.createWriter() local writer = Buffer.createWriter()
-- Write some data with instances -- Write some data with instances
@ -161,7 +180,7 @@ Reset a writer for reuse, clearing all data.
``` ```
```luau [Example] ```luau [Example]
local Buffer = Warp.Buffer() local Buffer = Warp.Buffer
local writer = Buffer.createWriter() local writer = Buffer.createWriter()
-- Use writer for first batch -- Use writer for first batch

View file

@ -20,7 +20,7 @@ this is optional and conditional, you may have to use this if you had a problem
```luau [Variable] ```luau [Variable]
( (
namespaces: { string }, namespaces: { string },
) -> Connection )
``` ```
```luau [Example] ```luau [Example]

View file

@ -40,6 +40,8 @@ local Players = game:GetService("Players")
local Warp = require(path.to.warp).Client() local Warp = require(path.to.warp).Client()
local Schemas = require(path.to.schemas) local Schemas = require(path.to.schemas)
Warp.awaitReady() -- this is optional, but recommended if facing any issues with race-condition, or remotes not registered
-- Use schemas -- Use schemas
for eventName, schema in Schemas do for eventName, schema in Schemas do
Warp.useSchema(eventName, schema) Warp.useSchema(eventName, schema)

View file

@ -1,5 +1,5 @@
name = "eternitydev/warp" name = "eternitydev/warp"
version = "1.1.0-pre4" version = "1.1.0-pre7"
description = "A rapidly-fast & powerful networking library." description = "A rapidly-fast & powerful networking library."
authors = ["eternitydev"] authors = ["eternitydev"]
repository = "https://github.com/imezx/Warp" repository = "https://github.com/imezx/Warp"

View file

@ -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"]}]}]}

View file

@ -3,12 +3,14 @@
--@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 Event: RemoteEvent = script.Parent:WaitForChild("Event") local Xor = require("./Util/Xor")
local UnreliableEvent: UnreliableRemoteEvent = script.Parent:WaitForChild("UnreliableEvent")
local RunService = game:GetService("RunService")
local Event: RemoteEvent = script.Parent:WaitForChild("Event") :: RemoteEvent
local UnreliableEvent: UnreliableRemoteEvent = script.Parent:WaitForChild("UnreliableEvent") :: UnreliableRemoteEvent
local deltaT: number, cycle: number = 0, 1 / 61 local deltaT: number, cycle: number = 0, 1 / 61
local writer: Buffer.Writer = Buffer.createWriter() local writer: Buffer.Writer = Buffer.createWriter()
@ -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
@ -48,7 +51,7 @@ end
--@remoteName string --@remoteName string
--@fn function --@fn function
-- Connect to an event to receive incoming data from the server. -- 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: (...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.`)
@ -165,17 +168,16 @@ 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 = args[1]
local results = content[2] local results = args[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))
@ -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 :: any, 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)

View file

@ -55,7 +55,6 @@ if RunService:IsClient() or RunService:IsRunMode() then
-- wait for the identifiers to be replicated from the server -- wait for the identifiers to be replicated from the server
Replication.wait_for_ready = function() Replication.wait_for_ready = function()
if is_ready then return end if is_ready then return end
local thread = coroutine.running() local thread = coroutine.running()
table.insert(ready_yields, thread) table.insert(ready_yields, thread)
coroutine.yield() coroutine.yield()

View file

@ -3,15 +3,17 @@
--@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 Event: RemoteEvent = script.Parent:WaitForChild("Event") local Xor = require("./Util/Xor")
local _repl: RemoteEvent = script.Parent:WaitForChild("_repl")
local UnreliableEvent: UnreliableRemoteEvent = script.Parent:WaitForChild("UnreliableEvent") local Players = game:GetService("Players")
local RunService = game:GetService("RunService")
local Event: RemoteEvent = script.Parent:WaitForChild("Event") :: RemoteEvent
local _repl: RemoteEvent = script.Parent:WaitForChild("_repl") :: RemoteEvent
local UnreliableEvent: UnreliableRemoteEvent = script.Parent:WaitForChild("UnreliableEvent") :: UnreliableRemoteEvent
local deltaT: number, cycle: number = 0, 1 / 61 local deltaT: number, cycle: number = 0, 1 / 61
local writer: Buffer.Writer = Buffer.createWriter() local writer: Buffer.Writer = Buffer.createWriter()
@ -180,7 +182,7 @@ Server.Invoke = function(remoteName: string, player: Player, timeout: number?, .
end end
table.insert(queueEvent[player], { table.insert(queueEvent[player], {
0, 0,
{ Identifier.get_id(remoteName), reqId :: any, { ... } :: any } :: any, { reqId :: any, { ... } :: any } :: any,
}) })
return coroutine.yield() return coroutine.yield()
end end
@ -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))
@ -214,12 +217,12 @@ if RunService:IsServer() then
continue continue
end end
if remote == 0 then if remote == 0 then
if #eventListeners == 0 then if not next(eventListeners) 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 :: any, 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)

View file

@ -50,7 +50,11 @@ local T_BUFFER = 0xDC
local T_BOOL_ARR = 0xDD local T_BOOL_ARR = 0xDD
local T_VEC3 = 0xE0 local T_VEC3 = 0xE0
local T_VEC3F32 = 0xE1
local T_VEC3I16 = 0xF1
local T_VEC2 = 0xE2 local T_VEC2 = 0xE2
local T_VEC2F32 = 0xE3
local T_VEC2I16 = 0xF4
local T_CFRAME = 0xE4 local T_CFRAME = 0xE4
local T_COLOR3 = 0xE6 local T_COLOR3 = 0xE6
local T_COLOR3_F = 0xE7 local T_COLOR3_F = 0xE7
@ -65,8 +69,6 @@ local T_NUMBERRANGE = 0xEF
local T_RAY = 0xF0 local T_RAY = 0xF0
local T_COLSEQ = 0xF2 local T_COLSEQ = 0xF2
local T_NUMSEQ = 0xF3 local T_NUMSEQ = 0xF3
local T_VEC3I16 = 0xF1
local T_VEC2I16 = 0xF4
local T_TWEENINFO = 0xF5 local T_TWEENINFO = 0xF5
local T_PHYSPROP = 0xF6 local T_PHYSPROP = 0xF6
local T_FONT = 0xF7 local T_FONT = 0xF7
@ -732,6 +734,14 @@ local function packVector3(w: Writer, v: Vector3)
rawF16(w, v.Z) rawF16(w, v.Z)
end end
local function packVector3F32(w: Writer, v: Vector3)
ensureSpace(w, 13)
rawU8(w, T_VEC3F32)
rawF32(w, v.X)
rawF32(w, v.Y)
rawF32(w, v.Z)
end
local function packVector2(w: Writer, v: Vector2) local function packVector2(w: Writer, v: Vector2)
ensureSpace(w, 5) ensureSpace(w, 5)
rawU8(w, T_VEC2) rawU8(w, T_VEC2)
@ -739,6 +749,13 @@ local function packVector2(w: Writer, v: Vector2)
rawF16(w, v.Y) rawF16(w, v.Y)
end end
local function packVector2F32(w: Writer, v: Vector2)
ensureSpace(w, 9)
rawU8(w, T_VEC2F32)
rawF32(w, v.X)
rawF32(w, v.Y)
end
local function packCFrame(w: Writer, cf: CFrame) local function packCFrame(w: Writer, cf: CFrame)
local pos = cf.Position local pos = cf.Position
local qi, q0, q1, q2 = compressCFrameRotation(cf) local qi, q0, q1, q2 = compressCFrameRotation(cf)
@ -796,10 +813,33 @@ PACK_DISPATCH["Vector3"] = function(w: Writer, v: any)
packVector3(w, v) packVector3(w, v)
end end
PACK_DISPATCH["Vector3F32"] = function(w: Writer, v: any)
packVector3F32(w, v)
end
PACK_DISPATCH["Vector3int16"] = function(w: Writer, v: any)
ensureSpace(w, 7)
rawU8(w, T_VEC3I16)
rawI16(w, v.X)
rawI16(w, v.Y)
rawI16(w, v.Z)
end
PACK_DISPATCH["Vector2"] = function(w: Writer, v: any) PACK_DISPATCH["Vector2"] = function(w: Writer, v: any)
packVector2(w, v) packVector2(w, v)
end end
PACK_DISPATCH["Vector2F32"] = function(w: Writer, v: any)
packVector2F32(w, v)
end
PACK_DISPATCH["Vector2int16"] = function(w: Writer, v: any)
ensureSpace(w, 5)
rawU8(w, T_VEC2I16)
rawI16(w, v.X)
rawI16(w, v.Y)
end
PACK_DISPATCH["CFrame"] = function(w: Writer, v: any) PACK_DISPATCH["CFrame"] = function(w: Writer, v: any)
packCFrame(w, v) packCFrame(w, v)
end end
@ -924,21 +964,6 @@ PACK_DISPATCH["buffer"] = function(w: Writer, v: any)
w.cursor += len w.cursor += len
end end
PACK_DISPATCH["Vector3int16"] = function(w: Writer, v: any)
ensureSpace(w, 7)
rawU8(w, T_VEC3I16)
rawI16(w, v.X)
rawI16(w, v.Y)
rawI16(w, v.Z)
end
PACK_DISPATCH["Vector2int16"] = function(w: Writer, v: any)
ensureSpace(w, 5)
rawU8(w, T_VEC2I16)
rawI16(w, v.X)
rawI16(w, v.Y)
end
PACK_DISPATCH["TweenInfo"] = function(w: Writer, v: any) PACK_DISPATCH["TweenInfo"] = function(w: Writer, v: any)
ensureSpace(w, 16) -- tag(1) + f32(4) + u8(1) + u8(1) + i32(4) + u8(1) + f32(4) ensureSpace(w, 16) -- tag(1) + f32(4) + u8(1) + u8(1) + i32(4) + u8(1) + f32(4)
rawU8(w, T_TWEENINFO) rawU8(w, T_TWEENINFO)
@ -1066,15 +1091,6 @@ UNPACK[T_F64 + 1] = function(buf: buffer, pos: number, _: { any }?, _: number):
return buffer.readf64(buf, pos), pos + 8 return buffer.readf64(buf, pos), pos + 8
end end
UNPACK[T_VEC3I16 + 1] = function(buf: buffer, pos: number, _: { any }?, _: number): (any, number)
return Vector3int16.new(buffer.readi16(buf, pos), buffer.readi16(buf, pos + 2), buffer.readi16(buf, pos + 4)),
pos + 6
end
UNPACK[T_VEC2I16 + 1] = function(buf: buffer, pos: number, _: { any }?, _: number): (any, number)
return Vector2int16.new(buffer.readi16(buf, pos), buffer.readi16(buf, pos + 2)), pos + 4
end
UNPACK[T_TWEENINFO + 1] = function(buf: buffer, pos: number, _: { any }?, _: number): (any, number) UNPACK[T_TWEENINFO + 1] = function(buf: buffer, pos: number, _: { any }?, _: number): (any, number)
return TweenInfo.new( return TweenInfo.new(
buffer.readf32(buf, pos), buffer.readf32(buf, pos),
@ -1288,6 +1304,20 @@ UNPACK[T_VEC3 + 1] = function(buf: buffer, pos: number, _: { any }?, _: number):
return Vector3.new(x, y, z), pos + 6 return Vector3.new(x, y, z), pos + 6
end end
-- Vector3 float32
UNPACK[T_VEC3F32 + 1] = function(buf: buffer, pos: number, _: { any }?, _: number): (any, number)
local x = buffer.readf32(buf, pos)
local y = buffer.readf32(buf, pos + 4)
local z = buffer.readf32(buf, pos + 8)
return Vector3.new(x, y, z), pos + 12
end
-- Vector3int16
UNPACK[T_VEC3I16 + 1] = function(buf: buffer, pos: number, _: { any }?, _: number): (any, number)
return Vector3int16.new(buffer.readi16(buf, pos), buffer.readi16(buf, pos + 2), buffer.readi16(buf, pos + 4)),
pos + 6
end
-- Vector2 -- Vector2
UNPACK[T_VEC2 + 1] = function(buf: buffer, pos: number, _: { any }?, _: number): (any, number) UNPACK[T_VEC2 + 1] = function(buf: buffer, pos: number, _: { any }?, _: number): (any, number)
local x = readF16(buf, pos) local x = readF16(buf, pos)
@ -1295,6 +1325,18 @@ UNPACK[T_VEC2 + 1] = function(buf: buffer, pos: number, _: { any }?, _: number):
return Vector2.new(x, y), pos + 4 return Vector2.new(x, y), pos + 4
end end
-- Vector2 float32
UNPACK[T_VEC2F32 + 1] = function(buf: buffer, pos: number, _: { any }?, _: number): (any, number)
local x = buffer.readf32(buf, pos)
local y = buffer.readf32(buf, pos + 4)
return Vector2.new(x, y), pos + 8
end
-- Vector2int16
UNPACK[T_VEC2I16 + 1] = function(buf: buffer, pos: number, _: { any }?, _: number): (any, number)
return Vector2int16.new(buffer.readi16(buf, pos), buffer.readi16(buf, pos + 2)), pos + 4
end
-- CFrame -- CFrame
UNPACK[T_CFRAME + 1] = function(buf: buffer, pos: number, _: { any }?, _: number): (any, number) UNPACK[T_CFRAME + 1] = function(buf: buffer, pos: number, _: { any }?, _: number): (any, number)
local px = buffer.readf32(buf, pos) local px = buffer.readf32(buf, pos)
@ -1469,7 +1511,11 @@ Schema.f32 = { type = "f32" }
Schema.f64 = { type = "f64" } Schema.f64 = { type = "f64" }
Schema.boolean = { type = "boolean" } Schema.boolean = { type = "boolean" }
Schema.vector3 = { type = "vector3" } Schema.vector3 = { type = "vector3" }
Schema.vector3f32 = { type = "vector3f32" }
Schema.vector3int16 = { type = "vector3int16" }
Schema.vector2 = { type = "vector2" } Schema.vector2 = { type = "vector2" }
Schema.vector2f32 = { type = "vector2f32" }
Schema.vector2int16 = { type = "vector2int16" }
Schema.cframe = { type = "cframe" } Schema.cframe = { type = "cframe" }
Schema.color3 = { type = "color3" } Schema.color3 = { type = "color3" }
Schema.color3f16 = { type = "color3f16" } Schema.color3f16 = { type = "color3f16" }
@ -1484,8 +1530,6 @@ Schema.colorsequence = { type = "colorsequence" }
Schema.numbersequence = { type = "numbersequence" } Schema.numbersequence = { type = "numbersequence" }
Schema.brickcolor = { type = "brickcolor" } Schema.brickcolor = { type = "brickcolor" }
Schema.buffer = { type = "buffer" } Schema.buffer = { type = "buffer" }
Schema.vector3int16 = { type = "vector3int16" }
Schema.vector2int16 = { type = "vector2int16" }
Schema.tweeninfo = { type = "tweeninfo" } Schema.tweeninfo = { type = "tweeninfo" }
Schema.physicalproperties = { type = "physicalproperties" } Schema.physicalproperties = { type = "physicalproperties" }
Schema.font = { type = "font" } Schema.font = { type = "font" }
@ -1544,7 +1588,11 @@ local SCHEMA_FIXED_SIZES: { [string]: number } = {
f64 = 8, f64 = 8,
boolean = 1, boolean = 1,
vector3 = 6, vector3 = 6,
vector3f32 = 12,
vector3int16 = 6,
vector2 = 4, vector2 = 4,
vector2f32 = 8,
vector2int16 = 4,
cframe = 19, cframe = 19,
color3 = 3, color3 = 3,
color3f16 = 6, color3f16 = 6,
@ -1554,8 +1602,6 @@ local SCHEMA_FIXED_SIZES: { [string]: number } = {
numberrange = 8, numberrange = 8,
ray = 24, ray = 24,
brickcolor = 2, brickcolor = 2,
vector3int16 = 6,
vector2int16 = 4,
tweeninfo = 15, tweeninfo = 15,
physicalproperties = 20, physicalproperties = 20,
datetime = 8, datetime = 8,
@ -1637,7 +1683,6 @@ local function compilePacker(s: SchemaType): (Writer, any) -> ()
rawString(w, v, len) rawString(w, v, len)
end end
end end
if schema_type == "vector3" then if schema_type == "vector3" then
return function(w: Writer, v: any) return function(w: Writer, v: any)
ensureSpace(w, 6) ensureSpace(w, 6)
@ -1646,6 +1691,14 @@ local function compilePacker(s: SchemaType): (Writer, any) -> ()
rawF16(w, v.Z) rawF16(w, v.Z)
end end
end end
if schema_type == "vector3f32" then
return function(w: Writer, v: any)
ensureSpace(w, 12)
rawF32(w, v.X)
rawF32(w, v.Y)
rawF32(w, v.Z)
end
end
if schema_type == "vector2" then if schema_type == "vector2" then
return function(w: Writer, v: any) return function(w: Writer, v: any)
ensureSpace(w, 4) ensureSpace(w, 4)
@ -1653,6 +1706,13 @@ local function compilePacker(s: SchemaType): (Writer, any) -> ()
rawF16(w, v.Y) rawF16(w, v.Y)
end end
end end
if schema_type == "vector2f32" then
return function(w: Writer, v: any)
ensureSpace(w, 8)
rawF32(w, v.X)
rawF32(w, v.Y)
end
end
if schema_type == "cframe" then if schema_type == "cframe" then
return function(w: Writer, v: any) return function(w: Writer, v: any)
@ -2084,6 +2144,14 @@ local function compileReader(s: SchemaType): (buffer, number, { any }?) -> (any,
return Vector3.new(x, y, z), c + 6 return Vector3.new(x, y, z), c + 6
end end
end end
if schema_type == "vector3f32" then
return function(b: buffer, c: number)
local x = buffer.readf32(b, c)
local y = buffer.readf32(b, c + 4)
local z = buffer.readf32(b, c + 8)
return Vector3.new(x, y, z), c + 12
end
end
if schema_type == "vector2" then if schema_type == "vector2" then
return function(b: buffer, c: number) return function(b: buffer, c: number)
local x = readF16(b, c) local x = readF16(b, c)
@ -2091,6 +2159,13 @@ local function compileReader(s: SchemaType): (buffer, number, { any }?) -> (any,
return Vector2.new(x, y), c + 4 return Vector2.new(x, y), c + 4
end end
end end
if schema_type == "vector2f32" then
return function(b: buffer, c: number)
local x = buffer.readf32(b, c)
local y = buffer.readf32(b, c + 4)
return Vector2.new(x, y), c + 8
end
end
if schema_type == "color3" then if schema_type == "color3" then
return function(b: buffer, c: number) return function(b: buffer, c: number)
local r = buffer.readu8(b, c) local r = buffer.readu8(b, c)

View file

@ -1,8 +1,8 @@
--!optimize 2 --!optimize 2
--!strict --!strict
--@EternityDev --@EternityDev
local BITS: number = 8 const BITS: number = 8
local MAX_VALUE: number = bit32.lshift(1, BITS) - 1 const MAX_VALUE: number = bit32.lshift(1, BITS) - 1
local hook_fn: (string, number) -> () local hook_fn: (string, number) -> ()
if not shared.__warp_identifier_registry then if not shared.__warp_identifier_registry then

120
src/Util/Xor.luau Normal file
View file

@ -0,0 +1,120 @@
--!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
const MAX_BUFFER_SIZE: number = 1024
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(math.max(len, 64)), len = 0 }
local cap = buffer.len(e.buf)
if cap < len then
e.buf = buffer.create(math.min(len * 2, MAX_BUFFER_SIZE + len))
elseif cap > len * 4 and cap > 128 then
e.buf = buffer.create(math.max(len, 64))
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

View file

@ -1,6 +1,6 @@
[package] [package]
name = "imezx/warp" name = "imezx/warp"
version = "1.1.0-pre4" version = "1.1.0-pre7"
registry = "https://github.com/UpliftGames/wally-index" registry = "https://github.com/UpliftGames/wally-index"
realm = "shared" realm = "shared"
license = "MIT" license = "MIT"