rewrite(phase2): unreliable

This commit is contained in:
Khietsly Tristan 2026-02-11 15:00:23 +07:00
parent b9bc52385c
commit 6bc31cb363
4 changed files with 541 additions and 225 deletions

View file

@ -67,31 +67,50 @@ local T_COLSEQ = 0xF2 -- ColorSequence
local T_NUMSEQ = 0xF3 -- NumberSequence local T_NUMSEQ = 0xF3 -- NumberSequence
local TYPED_READERS = { local TYPED_READERS = {
[1] = function(b, o) return buffer.readu8(b, o), o + 1 end, [1] = function(b, o)
[2] = function(b, o) return buffer.readi8(b, o), o + 1 end, return buffer.readu8(b, o), o + 1
[3] = function(b, o) return buffer.readu16(b, o), o + 2 end, end,
[4] = function(b, o) return buffer.readi16(b, o), o + 2 end, [2] = function(b, o)
[5] = function(b, o) return buffer.readu32(b, o), o + 4 end, return buffer.readi8(b, o), o + 1
[6] = function(b, o) return buffer.readi32(b, o), o + 4 end, end,
[7] = function(b, o) return buffer.readf32(b, o), o + 4 end, [3] = function(b, o)
[8] = function(b, o) return buffer.readf64(b, o), o + 8 end, return buffer.readu16(b, o), o + 2
end,
[4] = function(b, o)
return buffer.readi16(b, o), o + 2
end,
[5] = function(b, o)
return buffer.readu32(b, o), o + 4
end,
[6] = function(b, o)
return buffer.readi32(b, o), o + 4
end,
[7] = function(b, o)
return buffer.readf32(b, o), o + 4
end,
[8] = function(b, o)
return buffer.readf64(b, o), o + 8
end,
} }
local F16_MANTISSA_BITS = 1024 local F16_MANTISSA_BITS = 1024
local F16_DENORM do local F16_DENORM
do
F16_DENORM = table.create(1024) F16_DENORM = table.create(1024)
F16_DENORM[1] = 0 F16_DENORM[1] = 0
for m = 1, 1023 do for m = 1, 1023 do
F16_DENORM[m + 1] = math.ldexp(m / F16_MANTISSA_BITS, -14) F16_DENORM[m + 1] = math.ldexp(m / F16_MANTISSA_BITS, -14)
end end
end end
local F16_EXP2 do local F16_EXP2
do
F16_EXP2 = table.create(31) F16_EXP2 = table.create(31)
for e = 1, 30 do for e = 1, 30 do
F16_EXP2[e] = math.ldexp(1, e - 15) F16_EXP2[e] = math.ldexp(1, e - 15)
end end
end end
local F16_NORM_MANTISSA do local F16_NORM_MANTISSA
do
F16_NORM_MANTISSA = table.create(1024) F16_NORM_MANTISSA = table.create(1024)
for m = 0, 1023 do for m = 0, 1023 do
F16_NORM_MANTISSA[m + 1] = 1 + m / F16_MANTISSA_BITS F16_NORM_MANTISSA[m + 1] = 1 + m / F16_MANTISSA_BITS
@ -100,10 +119,18 @@ end
local F32TOF16_SUBNORM_POW2 = { 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024 } local F32TOF16_SUBNORM_POW2 = { 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024 }
local function f32ToF16(value: number): number local function f32ToF16(value: number): number
if value ~= value then return 0x7E00 end if value ~= value then
if value == math.huge then return 0x7C00 end return 0x7E00
if value == -math.huge then return 0xFC00 end end
if value == 0 then return 0 end if value == math.huge then
return 0x7C00
end
if value == -math.huge then
return 0xFC00
end
if value == 0 then
return 0
end
local sign = 0 local sign = 0
if value < 0 then if value < 0 then
@ -115,7 +142,9 @@ local function f32ToF16(value: number): number
exponent += 14 exponent += 14
if exponent <= 0 then if exponent <= 0 then
if exponent < -10 then return sign end if exponent < -10 then
return sign
end
local scale = F32TOF16_SUBNORM_POW2[10 + exponent] or bit32.lshift(1, 10 + exponent) local scale = F32TOF16_SUBNORM_POW2[10 + exponent] or bit32.lshift(1, 10 + exponent)
mantissa = math.floor(mantissa * scale + 0.5) mantissa = math.floor(mantissa * scale + 0.5)
return bit32.bor(sign, mantissa) return bit32.bor(sign, mantissa)
@ -127,7 +156,9 @@ local function f32ToF16(value: number): number
if mantissa == F16_MANTISSA_BITS then if mantissa == F16_MANTISSA_BITS then
mantissa = 0 mantissa = 0
exponent += 1 exponent += 1
if exponent >= 31 then return bit32.bor(sign, 0x7C00) end if exponent >= 31 then
return bit32.bor(sign, 0x7C00)
end
end end
return bit32.bor(sign, bit32.lshift(exponent, 10), mantissa) return bit32.bor(sign, bit32.lshift(exponent, 10), mantissa)
end end
@ -150,10 +181,18 @@ local function f16ToF32(bits: number): number
end end
local function varUIntSize(value: number): number local function varUIntSize(value: number): number
if value < 0x80 then return 1 end if value < 0x80 then
if value < 0x4000 then return 2 end return 1
if value < 0x200000 then return 3 end end
if value < 0x10000000 then return 4 end if value < 0x4000 then
return 2
end
if value < 0x200000 then
return 3
end
if value < 0x10000000 then
return 4
end
return 5 return 5
end end
@ -191,7 +230,9 @@ end
local function ensureSpace(w: Writer, bytes: number) local function ensureSpace(w: Writer, bytes: number)
local needed = w.cursor + bytes local needed = w.cursor + bytes
if needed <= w.capacity then return end if needed <= w.capacity then
return
end
local newCap = w.capacity local newCap = w.capacity
while newCap < needed do while newCap < needed do
@ -357,17 +398,27 @@ end
-- is the table a homogeneous number array -- is the table a homogeneous number array
local function analyzeNumberArray(t: { any }): (boolean, string?, number) local function analyzeNumberArray(t: { any }): (boolean, string?, number)
local count = #t local count = #t
if count == 0 then return false, nil, 0 end if count == 0 then
return false, nil, 0
end
local minVal, maxVal = math.huge, -math.huge local minVal, maxVal = math.huge, -math.huge
local allInt = true local allInt = true
for i = 1, count do for i = 1, count do
local v = t[i] local v = t[i]
if type(v) ~= "number" then return false, nil, count end if type(v) ~= "number" then
if v ~= math.floor(v) then allInt = false end return false, nil, count
if v < minVal then minVal = v end end
if v > maxVal then maxVal = v end if v ~= math.floor(v) then
allInt = false
end
if v < minVal then
minVal = v
end
if v > maxVal then
maxVal = v
end
end end
if not allInt then if not allInt then
@ -391,8 +442,14 @@ end
local TYPED_CODES = { u8 = 1, i8 = 2, u16 = 3, i16 = 4, u32 = 5, i32 = 6, f32 = 7, f64 = 8 } local TYPED_CODES = { u8 = 1, i8 = 2, u16 = 3, i16 = 4, u32 = 5, i32 = 6, f32 = 7, f64 = 8 }
local TYPED_SIZES = { u8 = 1, i8 = 1, u16 = 2, i16 = 2, u32 = 4, i32 = 4, f32 = 4, f64 = 8 } local TYPED_SIZES = { u8 = 1, i8 = 1, u16 = 2, i16 = 2, u32 = 4, i32 = 4, f32 = 4, f64 = 8 }
local TYPED_WRITERS = { local TYPED_WRITERS = {
u8 = buffer.writeu8, i8 = buffer.writei8, u16 = buffer.writeu16, i16 = buffer.writei16, u8 = buffer.writeu8,
u32 = buffer.writeu32, i32 = buffer.writei32, f32 = buffer.writef32, f64 = buffer.writef64, i8 = buffer.writei8,
u16 = buffer.writeu16,
i16 = buffer.writei16,
u32 = buffer.writeu32,
i32 = buffer.writei32,
f32 = buffer.writef32,
f64 = buffer.writef64,
} }
local function packTable(w: Writer, t: { [any]: any }) local function packTable(w: Writer, t: { [any]: any })
@ -403,7 +460,9 @@ local function packTable(w: Writer, t: {[any]: any})
for k in t do for k in t do
count += 1 count += 1
if type(k) == "number" and k > 0 and k == math.floor(k) then if type(k) == "number" and k > 0 and k == math.floor(k) then
if k > maxIdx then maxIdx = k end if k > maxIdx then
maxIdx = k
end
else else
isArray = false isArray = false
end end
@ -667,18 +726,40 @@ unpackValue = function(buf: buffer, pos: number, refs: {any}?): (any, number)
return t - 0x80 - 32, pos return t - 0x80 - 32, pos
end end
if t == T_NIL then return nil, pos end if t == T_NIL then
if t == T_FALSE then return false, pos end return nil, pos
if t == T_TRUE then return true, pos end end
if t == T_FALSE then
return false, pos
end
if t == T_TRUE then
return true, pos
end
if t == T_U8 then return buffer.readu8(buf, pos), pos + 1 end if t == T_U8 then
if t == T_U16 then return buffer.readu16(buf, pos), pos + 2 end return buffer.readu8(buf, pos), pos + 1
if t == T_U32 then return buffer.readu32(buf, pos), pos + 4 end end
if t == T_I8 then return buffer.readi8(buf, pos), pos + 1 end if t == T_U16 then
if t == T_I16 then return buffer.readi16(buf, pos), pos + 2 end return buffer.readu16(buf, pos), pos + 2
if t == T_I32 then return buffer.readi32(buf, pos), pos + 4 end end
if t == T_F32 then return buffer.readf32(buf, pos), pos + 4 end if t == T_U32 then
if t == T_F64 then return buffer.readf64(buf, pos), pos + 8 end return buffer.readu32(buf, pos), pos + 4
end
if t == T_I8 then
return buffer.readi8(buf, pos), pos + 1
end
if t == T_I16 then
return buffer.readi16(buf, pos), pos + 2
end
if t == T_I32 then
return buffer.readi32(buf, pos), pos + 4
end
if t == T_F32 then
return buffer.readf32(buf, pos), pos + 4
end
if t == T_F64 then
return buffer.readf64(buf, pos), pos + 8
end
-- inline str len (0-15) -- inline str len (0-15)
if t >= T_STR_BASE and t <= T_STR_BASE + 15 then if t >= T_STR_BASE and t <= T_STR_BASE + 15 then
@ -695,7 +776,8 @@ unpackValue = function(buf: buffer, pos: number, refs: {any}?): (any, number)
return buffer.readstring(buf, pos + 2, n), pos + 2 + n return buffer.readstring(buf, pos + 2, n), pos + 2 + n
end end
if t == T_STRVAR then if t == T_STRVAR then
local n; n, pos = readVarUInt(buf, pos) local n
n, pos = readVarUInt(buf, pos)
return buffer.readstring(buf, pos, n), pos + n return buffer.readstring(buf, pos, n), pos + n
end end
@ -710,7 +792,8 @@ unpackValue = function(buf: buffer, pos: number, refs: {any}?): (any, number)
end end
if t == T_ARR8 then if t == T_ARR8 then
local count = buffer.readu8(buf, pos); pos += 1 local count = buffer.readu8(buf, pos)
pos += 1
local arr = table.create(count) local arr = table.create(count)
for i = 1, count do for i = 1, count do
arr[i], pos = unpackValue(buf, pos, refs) arr[i], pos = unpackValue(buf, pos, refs)
@ -718,7 +801,8 @@ unpackValue = function(buf: buffer, pos: number, refs: {any}?): (any, number)
return arr, pos return arr, pos
end end
if t == T_ARR16 then if t == T_ARR16 then
local count = buffer.readu16(buf, pos); pos += 2 local count = buffer.readu16(buf, pos)
pos += 2
local arr = table.create(count) local arr = table.create(count)
for i = 1, count do for i = 1, count do
arr[i], pos = unpackValue(buf, pos, refs) arr[i], pos = unpackValue(buf, pos, refs)
@ -726,7 +810,8 @@ unpackValue = function(buf: buffer, pos: number, refs: {any}?): (any, number)
return arr, pos return arr, pos
end end
if t == T_ARRVAR then if t == T_ARRVAR then
local count; count, pos = readVarUInt(buf, pos) local count
count, pos = readVarUInt(buf, pos)
local arr = table.create(count) local arr = table.create(count)
for i = 1, count do for i = 1, count do
arr[i], pos = unpackValue(buf, pos, refs) arr[i], pos = unpackValue(buf, pos, refs)
@ -748,7 +833,8 @@ unpackValue = function(buf: buffer, pos: number, refs: {any}?): (any, number)
end end
if t == T_MAP8 then if t == T_MAP8 then
local count = buffer.readu8(buf, pos); pos += 1 local count = buffer.readu8(buf, pos)
pos += 1
local map = {} local map = {}
for _ = 1, count do for _ = 1, count do
local k, v local k, v
@ -759,7 +845,8 @@ unpackValue = function(buf: buffer, pos: number, refs: {any}?): (any, number)
return map, pos return map, pos
end end
if t == T_MAP16 then if t == T_MAP16 then
local count = buffer.readu16(buf, pos); pos += 2 local count = buffer.readu16(buf, pos)
pos += 2
local map = {} local map = {}
for _ = 1, count do for _ = 1, count do
local k, v local k, v
@ -770,7 +857,8 @@ unpackValue = function(buf: buffer, pos: number, refs: {any}?): (any, number)
return map, pos return map, pos
end end
if t == T_MAPVAR then if t == T_MAPVAR then
local count; count, pos = readVarUInt(buf, pos) local count
count, pos = readVarUInt(buf, pos)
local map = {} local map = {}
for _ = 1, count do for _ = 1, count do
local k, v local k, v
@ -783,8 +871,10 @@ unpackValue = function(buf: buffer, pos: number, refs: {any}?): (any, number)
-- typed array -- typed array
if t == T_TYPED_ARR then if t == T_TYPED_ARR then
local subtype = buffer.readu8(buf, pos); pos += 1 local subtype = buffer.readu8(buf, pos)
local count; count, pos = readVarUInt(buf, pos) pos += 1
local count
count, pos = readVarUInt(buf, pos)
local reader = TYPED_READERS[subtype] local reader = TYPED_READERS[subtype]
local arr = table.create(count) local arr = table.create(count)
for i = 1, count do for i = 1, count do
@ -858,12 +948,14 @@ unpackValue = function(buf: buffer, pos: number, refs: {any}?): (any, number)
end end
if t == T_INSTANCE then if t == T_INSTANCE then
local idx; idx, pos = readVarUInt(buf, pos) local idx
idx, pos = readVarUInt(buf, pos)
return refs and refs[idx] or nil, pos return refs and refs[idx] or nil, pos
end end
if t == T_ENUMITEM then if t == T_ENUMITEM then
local nameLen; nameLen, pos = readVarUInt(buf, pos) local nameLen
nameLen, pos = readVarUInt(buf, pos)
local enumName = buffer.readstring(buf, pos, nameLen) local enumName = buffer.readstring(buf, pos, nameLen)
pos += nameLen pos += nameLen
local val = buffer.readu16(buf, pos) local val = buffer.readu16(buf, pos)
@ -871,7 +963,8 @@ unpackValue = function(buf: buffer, pos: number, refs: {any}?): (any, number)
end end
if t == T_ENUM then if t == T_ENUM then
local nameLen; nameLen, pos = readVarUInt(buf, pos) local nameLen
nameLen, pos = readVarUInt(buf, pos)
local enumName = buffer.readstring(buf, pos, nameLen) local enumName = buffer.readstring(buf, pos, nameLen)
return (Enum :: any)[enumName], pos + nameLen return (Enum :: any)[enumName], pos + nameLen
end end
@ -892,9 +985,12 @@ unpackValue = function(buf: buffer, pos: number, refs: {any}?): (any, number)
if t == T_RECT then if t == T_RECT then
return Rect.new( return Rect.new(
buffer.readf32(buf, pos), buffer.readf32(buf, pos + 4), buffer.readf32(buf, pos),
buffer.readf32(buf, pos + 8), buffer.readf32(buf, pos + 12) buffer.readf32(buf, pos + 4),
), pos + 16 buffer.readf32(buf, pos + 8),
buffer.readf32(buf, pos + 12)
),
pos + 16
end end
if t == T_NUMBERRANGE then if t == T_NUMBERRANGE then
@ -905,11 +1001,13 @@ unpackValue = function(buf: buffer, pos: number, refs: {any}?): (any, number)
return Ray.new( return Ray.new(
Vector3.new(buffer.readf32(buf, pos), buffer.readf32(buf, pos + 4), buffer.readf32(buf, pos + 8)), Vector3.new(buffer.readf32(buf, pos), buffer.readf32(buf, pos + 4), buffer.readf32(buf, pos + 8)),
Vector3.new(buffer.readf32(buf, pos + 12), buffer.readf32(buf, pos + 16), buffer.readf32(buf, pos + 20)) Vector3.new(buffer.readf32(buf, pos + 12), buffer.readf32(buf, pos + 16), buffer.readf32(buf, pos + 20))
), pos + 24 ),
pos + 24
end end
if t == T_COLSEQ then if t == T_COLSEQ then
local count = buffer.readu8(buf, pos); pos += 1 local count = buffer.readu8(buf, pos)
pos += 1
local keypoints = table.create(count) local keypoints = table.create(count)
for i = 1, count do for i = 1, count do
local time = buffer.readf32(buf, pos) local time = buffer.readf32(buf, pos)
@ -923,7 +1021,8 @@ unpackValue = function(buf: buffer, pos: number, refs: {any}?): (any, number)
end end
if t == T_NUMSEQ then if t == T_NUMSEQ then
local count = buffer.readu8(buf, pos); pos += 1 local count = buffer.readu8(buf, pos)
pos += 1
local keypoints = table.create(count) local keypoints = table.create(count)
for i = 1, count do for i = 1, count do
local time = buffer.readf32(buf, pos) local time = buffer.readf32(buf, pos)
@ -936,7 +1035,8 @@ unpackValue = function(buf: buffer, pos: number, refs: {any}?): (any, number)
end end
if t == T_BUFFER then if t == T_BUFFER then
local len; len, pos = readVarUInt(buf, pos) local len
len, pos = readVarUInt(buf, pos)
local b = buffer.create(len) local b = buffer.create(len)
buffer.copy(b, 0, buf, pos, len) buffer.copy(b, 0, buf, pos, len)
return b, pos + len return b, pos + len
@ -999,32 +1099,81 @@ function Schema.struct(fields: {[string]: SchemaType}): SchemaType
for k, v in fields do for k, v in fields do
table.insert(orderedFields, { key = k, schema = v }) table.insert(orderedFields, { key = k, schema = v })
end end
table.sort(orderedFields, function(a, b) return a.key < b.key end) table.sort(orderedFields, function(a, b)
return a.key < b.key
end)
return { type = "struct", fields = orderedFields } return { type = "struct", fields = orderedFields }
end end
local function compilePacker(s: SchemaType): (Writer, any) -> () local function compilePacker(s: SchemaType): (Writer, any) -> ()
if s.type == "u8" then return wByte end if s.type == "u8" then
if s.type == "i8" then return function(w, v) ensureSpace(w, 1) buffer.writei8(w.buf, w.cursor, v) w.cursor += 1 end end return wByte
if s.type == "u16" then return wU16 end end
if s.type == "i16" then return wI16 end if s.type == "i8" then
if s.type == "u32" then return wU32 end return function(w, v)
if s.type == "i32" then return wI32 end ensureSpace(w, 1)
if s.type == "f32" then return wF32 end buffer.writei8(w.buf, w.cursor, v)
if s.type == "f64" then return wF64 end w.cursor += 1
if s.type == "f16" then return wF16 end end
if s.type == "boolean" then return function(w, v) wByte(w, v and 1 or 0) end end end
if s.type == "string" then return function(w, v) local len = #v wVarUInt(w, len) wString(w, v) end end if s.type == "u16" then
return wU16
end
if s.type == "i16" then
return wI16
end
if s.type == "u32" then
return wU32
end
if s.type == "i32" then
return wI32
end
if s.type == "f32" then
return wF32
end
if s.type == "f64" then
return wF64
end
if s.type == "f16" then
return wF16
end
if s.type == "boolean" then
return function(w, v)
wByte(w, v and 1 or 0)
end
end
if s.type == "string" then
return function(w, v)
local len = #v
wVarUInt(w, len)
wString(w, v)
end
end
if s.type == "vector3" then return function(w, v) wF16(w, f32ToF16(v.X)) wF16(w, f32ToF16(v.Y)) wF16(w, f32ToF16(v.Z)) end end if s.type == "vector3" then
if s.type == "vector2" then return function(w, v) wF16(w, f32ToF16(v.X)) wF16(w, f32ToF16(v.Y)) end end return function(w, v)
wF16(w, f32ToF16(v.X))
wF16(w, f32ToF16(v.Y))
wF16(w, f32ToF16(v.Z))
end
end
if s.type == "vector2" then
return function(w, v)
wF16(w, f32ToF16(v.X))
wF16(w, f32ToF16(v.Y))
end
end
if s.type == "cframe" then if s.type == "cframe" then
return function(w, v) return function(w, v)
local pos = v.Position local pos = v.Position
local rx, ry, rz = v:ToOrientation() local rx, ry, rz = v:ToOrientation()
wF16(w, f32ToF16(pos.X)) wF16(w, f32ToF16(pos.Y)) wF16(w, f32ToF16(pos.Z)) wF16(w, f32ToF16(pos.X))
wF16(w, f32ToF16(rx)) wF16(w, f32ToF16(ry)) wF16(w, f32ToF16(rz)) wF16(w, f32ToF16(pos.Y))
wF16(w, f32ToF16(pos.Z))
wF16(w, f32ToF16(rx))
wF16(w, f32ToF16(ry))
wF16(w, f32ToF16(rz))
end end
end end
@ -1036,7 +1185,12 @@ local function compilePacker(s: SchemaType): (Writer, any) -> ()
end end
end end
if s.type == "instance" then return function(w, v) table.insert(w.refs, v) wVarUInt(w, #w.refs) end end if s.type == "instance" then
return function(w, v)
table.insert(w.refs, v)
wVarUInt(w, #w.refs)
end
end
if s.type == "struct" then if s.type == "struct" then
local fields = {} local fields = {}
@ -1044,10 +1198,14 @@ local function compilePacker(s: SchemaType): (Writer, any) -> ()
table.insert(fields, { key = field.key, packer = compilePacker(field.schema) }) table.insert(fields, { key = field.key, packer = compilePacker(field.schema) })
end end
return function(w, v) return function(w, v)
if type(v) ~= "table" then error(`Expected table for struct, got {typeof(v)}`) end if type(v) ~= "table" then
error(`Expected table for struct, got {typeof(v)}`)
end
for _, f in fields do for _, f in fields do
local val = v[f.key] local val = v[f.key]
if val == nil then error(`Schema Error: Missing required field '{f.key}'`) end if val == nil then
error(`Schema Error: Missing required field '{f.key}'`)
end
f.packer(w, val) f.packer(w, val)
end end
end end
@ -1056,11 +1214,15 @@ local function compilePacker(s: SchemaType): (Writer, any) -> ()
if s.type == "array" then if s.type == "array" then
local itemPacker = compilePacker(s.item) local itemPacker = compilePacker(s.item)
return function(w, v) return function(w, v)
if type(v) ~= "table" then error(`Expected table for array, got {typeof(v)}`) end if type(v) ~= "table" then
error(`Expected table for array, got {typeof(v)}`)
end
local len = #v local len = #v
wVarUInt(w, len) wVarUInt(w, len)
for i = 1, len do for i = 1, len do
if v[i] == nil then error(`Schema Error: Array item at index {i} is nil`) end if v[i] == nil then
error(`Schema Error: Array item at index {i} is nil`)
end
itemPacker(w, v[i]) itemPacker(w, v[i])
end end
end end
@ -1071,7 +1233,9 @@ local function compilePacker(s: SchemaType): (Writer, any) -> ()
local valPacker = compilePacker(s.value) local valPacker = compilePacker(s.value)
return function(w, v) return function(w, v)
local count = 0 local count = 0
for _ in v do count += 1 end for _ in v do
count += 1
end
wVarUInt(w, count) wVarUInt(w, count)
for k, val in v do for k, val in v do
keyPacker(w, k) keyPacker(w, k)
@ -1096,19 +1260,60 @@ local function compilePacker(s: SchemaType): (Writer, any) -> ()
end end
local function compileReader(s: SchemaType): (buffer, number, { any }?) -> (any, number) local function compileReader(s: SchemaType): (buffer, number, { any }?) -> (any, number)
if s.type == "u8" then return function(b, c) return buffer.readu8(b, c), c + 1 end end if s.type == "u8" then
if s.type == "i8" then return function(b, c) return buffer.readi8(b, c), c + 1 end end return function(b, c)
if s.type == "u16" then return function(b, c) return buffer.readu16(b, c), c + 2 end end return buffer.readu8(b, c), c + 1
if s.type == "i16" then return function(b, c) return buffer.readi16(b, c), c + 2 end end end
if s.type == "u32" then return function(b, c) return buffer.readu32(b, c), c + 4 end end end
if s.type == "i32" then return function(b, c) return buffer.readi32(b, c), c + 4 end end if s.type == "i8" then
if s.type == "f32" then return function(b, c) return buffer.readf32(b, c), c + 4 end end return function(b, c)
if s.type == "f64" then return function(b, c) return buffer.readf64(b, c), c + 8 end end return buffer.readi8(b, c), c + 1
if s.type == "f16" then return function(b, c) return f16ToF32(buffer.readu16(b, c)), c + 2 end end end
if s.type == "boolean" then return function(b, c) return buffer.readu8(b, c) ~= 0, c + 1 end end end
if s.type == "u16" then
return function(b, c)
return buffer.readu16(b, c), c + 2
end
end
if s.type == "i16" then
return function(b, c)
return buffer.readi16(b, c), c + 2
end
end
if s.type == "u32" then
return function(b, c)
return buffer.readu32(b, c), c + 4
end
end
if s.type == "i32" then
return function(b, c)
return buffer.readi32(b, c), c + 4
end
end
if s.type == "f32" then
return function(b, c)
return buffer.readf32(b, c), c + 4
end
end
if s.type == "f64" then
return function(b, c)
return buffer.readf64(b, c), c + 8
end
end
if s.type == "f16" then
return function(b, c)
return f16ToF32(buffer.readu16(b, c)), c + 2
end
end
if s.type == "boolean" then
return function(b, c)
return buffer.readu8(b, c) ~= 0, c + 1
end
end
if s.type == "string" then if s.type == "string" then
return function(b, c) return function(b, c)
local len; len, c = readVarUInt(b, c) local len
len, c = readVarUInt(b, c)
return buffer.readstring(b, c, len), c + len return buffer.readstring(b, c, len), c + len
end end
end end
@ -1151,7 +1356,8 @@ local function compileReader(s: SchemaType): (buffer, number, {any}?) -> (any, n
end end
if s.type == "instance" then if s.type == "instance" then
return function(b, c, refs) return function(b, c, refs)
local idx; idx, c = readVarUInt(b, c) local idx
idx, c = readVarUInt(b, c)
return refs and refs[idx] or nil, c return refs and refs[idx] or nil, c
end end
end end
@ -1173,7 +1379,8 @@ local function compileReader(s: SchemaType): (buffer, number, {any}?) -> (any, n
if s.type == "array" then if s.type == "array" then
local itemReader = compileReader(s.item) local itemReader = compileReader(s.item)
return function(b, c, refs) return function(b, c, refs)
local len; len, c = readVarUInt(b, c) local len
len, c = readVarUInt(b, c)
local arr = table.create(len) local arr = table.create(len)
for i = 1, len do for i = 1, len do
arr[i], c = itemReader(b, c, refs) arr[i], c = itemReader(b, c, refs)
@ -1186,7 +1393,8 @@ local function compileReader(s: SchemaType): (buffer, number, {any}?) -> (any, n
local keyReader = compileReader(s.key) local keyReader = compileReader(s.key)
local valReader = compileReader(s.value) local valReader = compileReader(s.value)
return function(b, c, refs) return function(b, c, refs)
local count; count, c = readVarUInt(b, c) local count
count, c = readVarUInt(b, c)
local map = {} local map = {}
for _ = 1, count do for _ = 1, count do
local k, val local k, val
@ -1211,7 +1419,9 @@ local function compileReader(s: SchemaType): (buffer, number, {any}?) -> (any, n
end end
end end
return function(_, c) return nil, c end return function(_, c)
return nil, c
end
end end
local function packStrict(w: Writer, s: SchemaType, v: any) local function packStrict(w: Writer, s: SchemaType, v: any)

View file

@ -7,7 +7,7 @@ local RunService = game:GetService("RunService")
local Thread = require("./Thread") local Thread = require("./Thread")
local Buffer = require("./Buffer") local Buffer = require("./Buffer")
local Event: RemoteEvent = script.Parent:WaitForChild("Event") local Event: RemoteEvent = script.Parent:WaitForChild("Event")
local Function: RemoteFunction = script.Parent:WaitForChild("Function") local UnreliableEvent: UnreliableRemoteEvent = script.Parent:WaitForChild("UnreliableEvent")
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()
@ -17,10 +17,11 @@ type Connection = {
} }
type Event = { type Event = {
remote: string, remote: string,
fn: (Player, ...any?) -> (...any?), fn: (Player, ...any?) -> ...any?,
} }
local queueEvent: { { any } } = {} local queueEvent: { { any } } = {}
local queueUnreliableEvent: { { any } } = {}
local eventListeners: { Event } = {} local eventListeners: { Event } = {}
local eventSchemas: { [string]: Buffer.SchemaType } = {} local eventSchemas: { [string]: Buffer.SchemaType } = {}
@ -31,7 +32,7 @@ Client.useSchema = function(remoteName: string, schema: Buffer.SchemaType)
eventSchemas[remoteName] = schema eventSchemas[remoteName] = schema
end end
Client.Connect = function(remoteName: string, fn: (Player, ...any?) -> (...any?)): Connection Client.Connect = function(remoteName: string, fn: (Player, ...any?) -> ...any?): Connection
local detail = { local detail = {
remote = remoteName, remote = remoteName,
fn = fn, fn = fn,
@ -40,7 +41,9 @@ Client.Connect = function(remoteName: string, fn: (Player, ...any?) -> (...any?)
return { return {
Connected = true, Connected = true,
Disconnect = function(self: Connection) Disconnect = function(self: Connection)
if not self.Connected then return end if not self.Connected then
return
end
self.Connected = false self.Connected = false
local idx = table.find(eventListeners, detail) local idx = table.find(eventListeners, detail)
if idx then if idx then
@ -81,10 +84,10 @@ Client.Destroy = function(remoteName: string)
Client.DisconnectAll(remoteName) Client.DisconnectAll(remoteName)
end end
Client.Fire = function(remoteName: string, ...: any?) Client.Fire = function(remoteName: string, reliable: boolean, ...: any?)
table.insert(queueEvent, { table.insert(reliable and queueEvent or queueUnreliableEvent, {
remoteName, remoteName,
{ ... } :: any { ... } :: any,
}) })
end end
@ -101,21 +104,23 @@ Client.Invoke = function(remoteName: string, timeout: number?, ...: any?): ...an
task.spawn(pending, nil) task.spawn(pending, nil)
pendingInvokes[id] = nil pendingInvokes[id] = nil
end) end)
table.insert(queueEvent, { table.insert(queueEvent, {
"\0", "\0",
{ remoteName, id, { ... } :: any } :: any { remoteName, id, { ... } :: any } :: any,
}) })
return coroutine.yield() return coroutine.yield()
end end
if RunService:IsClient() then if RunService:IsClient() then
Event.OnClientEvent:Connect(function(b: buffer, ref: { Instance? }) local function processIncoming(b: buffer, ref: { Instance }?, handleInvokes: boolean)
if type(b) ~= "buffer" then return end if type(b) ~= "buffer" then
return
end
local contents = Buffer.readEvents(b, 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 content = content[2]
if handleInvokes then
if remote == "\1" then if remote == "\1" then
local id = content[1] local id = content[1]
local results = content[2] local results = content[2]
@ -126,8 +131,10 @@ if RunService:IsClient() then
end end
continue continue
end end
if #eventListeners == 0 then continue end
if remote == "\0" then if remote == "\0" then
if #eventListeners == 0 then
continue
end
local remoteName = content[1] local remoteName = content[1]
local id = content[2] local id = content[2]
local args = content[3] local args = content[3]
@ -137,7 +144,7 @@ if RunService:IsClient() then
local results = { connection.fn(table.unpack(args)) } local results = { connection.fn(table.unpack(args)) }
table.insert(queueEvent, { table.insert(queueEvent, {
"\1", "\1",
{ id, results } :: any { id, results } :: any,
}) })
end) end)
break break
@ -145,17 +152,36 @@ if RunService:IsClient() then
end end
continue continue
end end
end
if #eventListeners == 0 then
continue
end
for _, connection in eventListeners do for _, connection in eventListeners do
if connection.remote ~= remote then continue end if connection.remote ~= remote then
continue
end
Thread(connection.fn, table.unpack(content)) Thread(connection.fn, table.unpack(content))
end end
end end
end
Event.OnClientEvent:Connect(function(b: buffer, ref: { Instance }?)
processIncoming(b, ref, true)
end) end)
UnreliableEvent.OnClientEvent:Connect(function(b: buffer, ref: { Instance }?)
processIncoming(b, ref, false)
end)
RunService.PostSimulation:Connect(function(d: number) RunService.PostSimulation:Connect(function(d: number)
deltaT += d deltaT += d
if deltaT < cycle then return end if deltaT < cycle then
return
end
deltaT = 0 deltaT = 0
if #queueEvent == 0 then return end
-- reliable
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)
@ -167,6 +193,21 @@ if RunService:IsClient() then
end end
end end
table.clear(queueEvent) 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)
end end

View file

@ -8,7 +8,7 @@ local RunService = game:GetService("RunService")
local Thread = require("./Thread") local Thread = require("./Thread")
local Buffer = require("./Buffer") local Buffer = require("./Buffer")
local Event: RemoteEvent = script.Parent:WaitForChild("Event") local Event: RemoteEvent = script.Parent:WaitForChild("Event")
local Function: RemoteFunction = script.Parent:WaitForChild("Function") local UnreliableEvent: UnreliableRemoteEvent = script.Parent:WaitForChild("UnreliableEvent")
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()
@ -18,12 +18,15 @@ type Connection = {
} }
type Event = { type Event = {
remote: string, remote: string,
fn: (Player, ...any?) -> (...any?), fn: (Player, ...any?) -> ...any?,
} }
local queueEvent: { local queueEvent: {
[Player]: { { any } }, [Player]: { { any } },
} = {} } = {}
local queueUnreliableEvent: {
[Player]: { { any } },
} = {}
local eventListeners: { Event } = {} local eventListeners: { Event } = {}
local eventSchemas: { [string]: Buffer.SchemaType } = {} local eventSchemas: { [string]: Buffer.SchemaType } = {}
local players_ready: { Player } = {} local players_ready: { Player } = {}
@ -35,7 +38,7 @@ Server.useSchema = function(remoteName: string, schema: Buffer.SchemaType)
eventSchemas[remoteName] = schema eventSchemas[remoteName] = schema
end end
Server.Connect = function(remoteName: string, fn: (Player, ...any?) -> (...any?)): Connection Server.Connect = function(remoteName: string, fn: (Player, ...any?) -> ...any?): Connection
local detail = { local detail = {
remote = remoteName, remote = remoteName,
fn = fn, fn = fn,
@ -44,7 +47,9 @@ Server.Connect = function(remoteName: string, fn: (Player, ...any?) -> (...any?)
return { return {
Connected = true, Connected = true,
Disconnect = function(self: Connection) Disconnect = function(self: Connection)
if not self.Connected then return end if not self.Connected then
return
end
self.Connected = false self.Connected = false
local idx = table.find(eventListeners, detail) local idx = table.find(eventListeners, detail)
if idx then if idx then
@ -86,12 +91,13 @@ Server.Destroy = function(remoteName: string)
end end
Server.Fire = function(remoteName: string, reliable: boolean, player: Player, ...: any?) Server.Fire = function(remoteName: string, reliable: boolean, player: Player, ...: any?)
if not queueEvent[player] then local targetQueue = reliable and queueEvent or queueUnreliableEvent
queueEvent[player] = {} :: any if not targetQueue[player] then
targetQueue[player] = {} :: any
end end
table.insert(queueEvent[player], { table.insert(targetQueue[player], {
remoteName, remoteName,
{ ... } :: any { ... } :: any,
}) })
end end
@ -114,21 +120,26 @@ Server.Invoke = function(remoteName: string, player: Player, timeout: number?, .
task.spawn(pending, nil) task.spawn(pending, nil)
pendingInvokes[id] = nil pendingInvokes[id] = nil
end) end)
if not queueEvent[player] then
queueEvent[player] = {} :: any
end
table.insert(queueEvent[player], { table.insert(queueEvent[player], {
"\0", "\0",
{ remoteName, id, { ... } :: any } :: any { remoteName, id, { ... } :: any } :: any,
}) })
return coroutine.yield() return coroutine.yield()
end end
if RunService:IsServer() then if RunService:IsServer() then
Event.OnServerEvent:Connect(function(player: Player, b: buffer, ref: { Instance? }) local function processIncoming(player: Player, b: buffer, ref: { Instance }?, handleInvokes: boolean)
if type(b) ~= "buffer" then return end if type(b) ~= "buffer" then
return
end
local contents = Buffer.readEvents(b, 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 content = content[2]
if handleInvokes then
if remote == "\1" then if remote == "\1" then
local id = content[1] local id = content[1]
local results = content[2] local results = content[2]
@ -139,8 +150,10 @@ if RunService:IsServer() then
end end
continue continue
end end
if #eventListeners == 0 then continue end
if remote == "\0" then if remote == "\0" then
if #eventListeners == 0 then
continue
end
local remoteName = content[1] local remoteName = content[1]
local id = content[2] local id = content[2]
local args = content[3] local args = content[3]
@ -148,9 +161,12 @@ if RunService:IsServer() then
if connection.remote == remoteName then if connection.remote == remoteName then
Thread(function() Thread(function()
local results = { connection.fn(table.unpack(args)) } local results = { connection.fn(table.unpack(args)) }
if not queueEvent[player] then
queueEvent[player] = {} :: any
end
table.insert(queueEvent[player], { table.insert(queueEvent[player], {
"\1", "\1",
{ id, results } :: any { id, results } :: any,
}) })
end) end)
break break
@ -158,18 +174,39 @@ if RunService:IsServer() then
end end
continue continue
end end
end
if #eventListeners == 0 then
continue
end
for _, connection in eventListeners do for _, connection in eventListeners do
if connection.remote ~= remote then continue end if connection.remote ~= remote then
continue
end
Thread(connection.fn, player, table.unpack(content)) Thread(connection.fn, player, table.unpack(content))
end end
end end
end
Event.OnServerEvent:Connect(function(player: Player, b: buffer, ref: { Instance }?)
processIncoming(player, b, ref, true)
end) end)
UnreliableEvent.OnServerEvent:Connect(function(player: Player, b: buffer, ref: { Instance }?)
processIncoming(player, b, ref, false)
end)
RunService.PostSimulation:Connect(function(d: number) RunService.PostSimulation:Connect(function(d: number)
deltaT += d deltaT += d
if deltaT < cycle then return end if deltaT < cycle then
return
end
deltaT = 0 deltaT = 0
-- reliable
for player: Player, content in queueEvent do for player: Player, content in queueEvent do
if #content == 0 or player.Parent ~= Players then continue end if #content == 0 or player.Parent ~= Players then
continue
end
Buffer.writeEvents(writer, content, eventSchemas) Buffer.writeEvents(writer, content, eventSchemas)
do do
local buf, ref = Buffer.buildWithRefs(writer) local buf, ref = Buffer.buildWithRefs(writer)
@ -182,7 +219,25 @@ if RunService:IsServer() then
end end
table.clear(queueEvent[player]) table.clear(queueEvent[player])
end 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(queueUnreliableEvent[player])
end
end) end)
local function onAdded(player: Player) local function onAdded(player: Player)
if not table.find(players_ready, player) then if not table.find(players_ready, player) then
table.insert(players_ready, player) table.insert(players_ready, player)
@ -190,6 +245,9 @@ if RunService:IsServer() then
if not queueEvent[player] then if not queueEvent[player] then
queueEvent[player] = {} :: any queueEvent[player] = {} :: any
end end
if not queueUnreliableEvent[player] then
queueUnreliableEvent[player] = {} :: any
end
end end
Players.PlayerAdded:Connect(onAdded) Players.PlayerAdded:Connect(onAdded)
Players.PlayerRemoving:Connect(function(player: Player) Players.PlayerRemoving:Connect(function(player: Player)
@ -198,6 +256,10 @@ if RunService:IsServer() then
table.clear(queueEvent[player]) table.clear(queueEvent[player])
queueEvent[player] = nil queueEvent[player] = nil
end end
if queueUnreliableEvent[player] then
table.clear(queueUnreliableEvent[player])
queueUnreliableEvent[player] = nil
end
end) end)
for _, player: Player in ipairs(Players:GetPlayers()) do for _, player: Player in ipairs(Players:GetPlayers()) do
onAdded(player) onAdded(player)

View file

@ -6,6 +6,9 @@ if game.RunService:IsServer() then
if not script:FindFirstChild("Event") then if not script:FindFirstChild("Event") then
Instance.new("RemoteEvent", script).Name = "Event" Instance.new("RemoteEvent", script).Name = "Event"
end end
if not script:FindFirstChild("UnreliableEvent") then
Instance.new("UnreliableRemoteEvent", script).Name = "UnreliableEvent"
end
if not script:FindFirstChild("Function") then if not script:FindFirstChild("Function") then
Instance.new("RemoteFunction", script).Name = "Function" Instance.new("RemoteFunction", script).Name = "Function"
end end