diff --git a/src/Util/Buffer/init.luau b/src/Util/Buffer/init.luau index ea046e3..66bf3c7 100644 --- a/src/Util/Buffer/init.luau +++ b/src/Util/Buffer/init.luau @@ -9,10 +9,10 @@ export type Writer = { } local DEFAULT_CAPACITY: number = 64 -local NaN_VALUE: number = 0/0 +local NaN_VALUE: number = 0 / 0 --- 0x00-0x7F: + fixint (0-127) - single byte --- 0x80-0x9F: - fixint (-32 to -1) - single byte +-- 0x00-0x7F: + fixint (0-127) — single byte +-- 0x80-0x9F: - fixint (-32 to -1) — single byte local T_NIL = 0xA0 local T_FALSE = 0xA1 local T_TRUE = 0xA2 @@ -47,12 +47,13 @@ local T_MAPVAR = 0xDA -- typed arrays (homogeneous, no per-element tags) local T_TYPED_ARR = 0xDB local T_BUFFER = 0xDC +local T_BOOL_ARR = 0xDD -local T_VEC3 = 0xE0 -- f32 precision (12 bytes) -local T_VEC2 = 0xE2 -- f32 precision (8 bytes) -local T_CFRAME = 0xE4 -- f32 pos (12 bytes) + f16 orient (6 bytes) = 18 bytes total -local T_COLOR3 = 0xE6 -- RGB bytes (3 bytes) -local T_COLOR3_F = 0xE7 -- RGB floats (12 bytes) +local T_VEC3 = 0xE0 +local T_VEC2 = 0xE2 +local T_CFRAME = 0xE4 +local T_COLOR3 = 0xE6 +local T_COLOR3_F = 0xE7 local T_BRICKCOLOR = 0xE8 local T_INSTANCE = 0xE9 local T_ENUMITEM = 0xEA @@ -62,12 +63,17 @@ local T_UDIM = 0xED local T_RECT = 0xEE local T_NUMBERRANGE = 0xEF local T_RAY = 0xF0 -local T_COLSEQ = 0xF2 -- ColorSequence -local T_NUMSEQ = 0xF3 -- NumberSequence -local T_BOOL_ARR = 0xDD +local T_COLSEQ = 0xF2 +local T_NUMSEQ = 0xF3 +local T_VEC3I16 = 0xF1 +local T_VEC2I16 = 0xF4 +local T_TWEENINFO = 0xF5 +local T_PHYSPROP = 0xF6 +local T_FONT = 0xF7 +local T_DATETIME = 0xF8 local F16_LOOKUP: { number } = table.create(65536, 0) -do -- precomputes for readf16 +do for raw = 0, 65535 do local sign: number = bit32.btest(raw, 0x8000) and -1 or 1 local exponent: number = bit32.extract(raw, 10, 5) @@ -83,18 +89,54 @@ do -- precomputes for readf16 if mantissa == 0 then value = sign * math.huge else - value = NaN_VALUE -- nan + value = NaN_VALUE end else value = sign * (1 + mantissa / 1024) * math.ldexp(1, exponent - 15) end F16_LOOKUP[raw + 1] = value - if raw % 1000 == 0 then + if raw % 8192 == 0 then task.wait() end end end +local function encodeF16(value: number): number + if value ~= value then + return 0x7E00 + end + if value == 0 then + return if 1 / value < 0 then 0x8000 else 0 + end + local sign = 0 + if value < 0 then + sign = 0x8000 + value = -value + end + if value >= 65504 then + return sign + 0x7C00 + end + if value < 6.103515625e-05 then + local f = math.floor(value * 16777216 + 0.5) + return sign + (if f >= 1024 then 0x0400 else f) + end + local m, e = math.frexp(value) + local biasedExp = e + 14 + local f = math.floor(m * 2048 - 1024 + 0.5) + if f >= 1024 then + f = 0 + biasedExp += 1 + end + if biasedExp >= 31 then + return sign + 0x7C00 + end + return sign + biasedExp * 1024 + f +end + +local function readF16(b: buffer, pos: number): number + return F16_LOOKUP[buffer.readu16(b, pos) + 1] +end + local function varUIntSize(value: number): number if value < 0x80 then return 1 @@ -148,18 +190,65 @@ local function ensureSpace(w: Writer, bytes: number) if needed <= w.capacity then return end - local newCap = w.capacity while newCap < needed do newCap *= 2 end - local newBuf = buffer.create(newCap) buffer.copy(newBuf, 0, w.buf, 0, w.cursor) w.buf = newBuf w.capacity = newCap end +local function rawU8(w: Writer, v: number) + buffer.writeu8(w.buf, w.cursor, v) + w.cursor += 1 +end + +local function rawU16(w: Writer, v: number) + buffer.writeu16(w.buf, w.cursor, v) + w.cursor += 2 +end + +local function rawI16(w: Writer, v: number) + buffer.writei16(w.buf, w.cursor, v) + w.cursor += 2 +end + +local function rawU32(w: Writer, v: number) + buffer.writeu32(w.buf, w.cursor, v) + w.cursor += 4 +end + +local function rawI32(w: Writer, v: number) + buffer.writei32(w.buf, w.cursor, v) + w.cursor += 4 +end + +local function rawF32(w: Writer, v: number) + buffer.writef32(w.buf, w.cursor, v) + w.cursor += 4 +end + +local function rawF64(w: Writer, v: number) + buffer.writef64(w.buf, w.cursor, v) + w.cursor += 8 +end + +local function rawF16(w: Writer, value: number) + buffer.writeu16(w.buf, w.cursor, encodeF16(value)) + w.cursor += 2 +end + +local function rawVarUInt(w: Writer, v: number) + w.cursor = writeVarUInt(w.buf, w.cursor, v) +end + +local function rawString(w: Writer, s: string, n: number) + buffer.writestring(w.buf, w.cursor, s) + w.cursor += n +end + local function wByte(w: Writer, v: number) ensureSpace(w, 1) buffer.writeu8(w.buf, w.cursor, v) @@ -192,49 +281,7 @@ end local function wF16(w: Writer, value: number) ensureSpace(w, 2) - local raw: number - - if value ~= value then - raw = 0x7E00 -- NaN - elseif value == 0 then - raw = if 1/value < 0 then 0x8000 else 0 - else - local sign = 0 - if value < 0 then - sign = 0x8000 - value = -value - end - - if value >= 65504 then - raw = sign + 0x7C00 -- Infinity - elseif value < 6.103515625e-05 then - -- Subnormal range - local f = math.floor(value * 16777216 + 0.5) - if f >= 1024 then - raw = sign + 0x0400 -- Clamp to smallest normal - else - raw = sign + f - end - else - local m, e = math.frexp(value) - local biasedExp = e + 14 - local f = math.floor(m * 2048 - 1024 + 0.5) - - if f >= 1024 then - f = 0 - biasedExp += 1 - end - - -- Add overflow protection (was missing!) - if biasedExp >= 31 then - raw = sign + 0x7C00 -- Overflow to infinity - else - raw = sign + biasedExp * 1024 + f - end - end - end - - buffer.writeu16(w.buf, w.cursor, raw) + buffer.writeu16(w.buf, w.cursor, encodeF16(value)) w.cursor += 2 end @@ -250,13 +297,8 @@ local function wF64(w: Writer, v: number) w.cursor += 8 end -local function readF16(b: buffer, pos: number): number - local raw = buffer.readu16(b, pos) - return F16_LOOKUP[raw + 1] -end - local function wVarUInt(w: Writer, v: number) - ensureSpace(w, varUIntSize(v)) + ensureSpace(w, 5) -- max varuint = 5 bytes; avoids double-analysis via varUIntSize w.cursor = writeVarUInt(w.buf, w.cursor, v) end @@ -268,126 +310,257 @@ local function wString(w: Writer, s: string) end local packValue: (w: Writer, v: any) -> () +local unpackValue: (buf: buffer, pos: number, refs: { any }?) -> (any, number) + local F32_TEST_BUF = buffer.create(4) + +local TYPED_CODES = { u8 = 1, i8 = 2, u16 = 3, i16 = 4, u32 = 5, i32 = 6, f32 = 7, f64 = 8, f16 = 9 } +local TYPED_SIZES = { u8 = 1, i8 = 1, u16 = 2, i16 = 2, u32 = 4, i32 = 4, f32 = 4, f64 = 8, f16 = 2 } + +local TYPED_WRITERS = { + u8 = buffer.writeu8, + i8 = buffer.writei8, + u16 = buffer.writeu16, + i16 = buffer.writei16, + u32 = buffer.writeu32, + i32 = buffer.writei32, + f32 = buffer.writef32, + f64 = buffer.writef64, + f16 = function(b: buffer, offset: number, value: number) + buffer.writeu16(b, offset, encodeF16(value)) + end, +} + local TYPED_READERS = { - [1] = function(b, o) + [1] = function(b: buffer, o: number) return buffer.readu8(b, o), o + 1 end, - [2] = function(b, o) + [2] = function(b: buffer, o: number) return buffer.readi8(b, o), o + 1 end, - [3] = function(b, o) + [3] = function(b: buffer, o: number) return buffer.readu16(b, o), o + 2 end, - [4] = function(b, o) + [4] = function(b: buffer, o: number) return buffer.readi16(b, o), o + 2 end, - [5] = function(b, o) + [5] = function(b: buffer, o: number) return buffer.readu32(b, o), o + 4 end, - [6] = function(b, o) + [6] = function(b: buffer, o: number) return buffer.readi32(b, o), o + 4 end, - [7] = function(b, o) + [7] = function(b: buffer, o: number) return buffer.readf32(b, o), o + 4 end, - [8] = function(b, o) + [8] = function(b: buffer, o: number) return buffer.readf64(b, o), o + 8 end, - [9] = function(b, o) + [9] = function(b: buffer, o: number) return readF16(b, o), o + 2 end, } +-- CFrame quaternion compression (Shepperd's method + smallest-three encoding). +-- Extracts a unit quaternion directly from the rotation matrix - no trig needed. +-- The three stored components are guaranteed in [-1/√2, 1/√2], so we rescale +-- by √2 to fill the full i16 range, giving ~41% better precision for free. +local CF_I16_SCALE: number = 32767 * math.sqrt(2) +local CF_I16_INV_SCALE: number = 1 / CF_I16_SCALE + +local function compressCFrameRotation(cf: CFrame): (number, number, number, number) + local _, _, _, r00, r01, r02, r10, r11, r12, r20, r21, r22 = cf:GetComponents() + local trace: number = r00 + r11 + r22 + local qx: number, qy: number, qz: number, qw: number + if trace > 0 then + local sq: number = math.sqrt(trace + 1) + local s: number = 0.5 / sq + qw = sq * 0.5 + qx = (r21 - r12) * s + qy = (r02 - r20) * s + qz = (r10 - r01) * s + elseif r00 > r11 and r00 > r22 then + local sq: number = math.sqrt(1 + r00 - r11 - r22) + local s: number = 0.5 / sq + qx = sq * 0.5 + qw = (r21 - r12) * s + qy = (r01 + r10) * s + qz = (r02 + r20) * s + elseif r11 > r22 then + local sq: number = math.sqrt(1 + r11 - r00 - r22) + local s: number = 0.5 / sq + qy = sq * 0.5 + qw = (r02 - r20) * s + qx = (r01 + r10) * s + qz = (r12 + r21) * s + else + local sq: number = math.sqrt(1 + r22 - r00 - r11) + local s: number = 0.5 / sq + qz = sq * 0.5 + qw = (r10 - r01) * s + qx = (r02 + r20) * s + qy = (r12 + r21) * s + end + + local index, value, sign = 1, math.abs(qx), qx >= 0 and 1 or -1 + local abs + abs = math.abs(qy) + if abs > value then + index = 2 + value = abs + sign = qy >= 0 and 1 or -1 + end + abs = math.abs(qz) + if abs > value then + index = 3 + value = abs + sign = qz >= 0 and 1 or -1 + end + abs = math.abs(qw) + if abs > value then + index = 4 + sign = qw >= 0 and 1 or -1 + end + + local scale = CF_I16_SCALE * sign + local v0: number, v1: number, v2: number + if index == 1 then + v0 = math.round(qy * scale) + v1 = math.round(qz * scale) + v2 = math.round(qw * scale) + elseif index == 2 then + v0 = math.round(qx * scale) + v1 = math.round(qz * scale) + v2 = math.round(qw * scale) + elseif index == 3 then + v0 = math.round(qx * scale) + v1 = math.round(qy * scale) + v2 = math.round(qw * scale) + else + v0 = math.round(qx * scale) + v1 = math.round(qy * scale) + v2 = math.round(qz * scale) + end + return index, v0, v1, v2 +end + +local function decompressCFrameRotation( + index: number, + v0: number, + v1: number, + v2: number +): (number, number, number, number) + v0 *= CF_I16_INV_SCALE + v1 *= CF_I16_INV_SCALE + v2 *= CF_I16_INV_SCALE + local sq = 1 - (v0 * v0 + v1 * v1 + v2 * v2) + local d = sq > 0 and math.sqrt(sq) or 0 + if index == 1 then + return d, v0, v1, v2 + elseif index == 2 then + return v0, d, v1, v2 + elseif index == 3 then + return v0, v1, d, v2 + end + return v0, v1, v2, d +end + local function packNumber(w: Writer, n: number) if n ~= n then - wByte(w, T_F64) - wF64(w, n) + ensureSpace(w, 9) + rawU8(w, T_F64) + rawF64(w, n) return end local isInt = n == math.floor(n) if isInt then - -- + fixint: 0-127 in single byte - if n >= 0 and n <= 127 then - wByte(w, n) - return - end - - -- - fixint: -32 to -1 in single byte - if n >= -32 and n < 0 then - wByte(w, bit32.bor(0x80, n + 32)) - return - end - - -- larger ints if n >= 0 then + if n <= 127 then + ensureSpace(w, 1) + rawU8(w, n) + return + end if n <= 255 then - wByte(w, T_U8) - wByte(w, n) + ensureSpace(w, 2) + rawU8(w, T_U8) + rawU8(w, n) elseif n <= 65535 then - wByte(w, T_U16) - wU16(w, n) + ensureSpace(w, 3) + rawU8(w, T_U16) + rawU16(w, n) elseif n <= 4294967295 then - wByte(w, T_U32) - wU32(w, n) + ensureSpace(w, 5) + rawU8(w, T_U32) + rawU32(w, n) else - wByte(w, T_F64) - wF64(w, n) + ensureSpace(w, 9) + rawU8(w, T_F64) + rawF64(w, n) end else - if n >= -128 then - wByte(w, T_I8) + if n >= -32 then ensureSpace(w, 1) + rawU8(w, bit32.bor(0x80, n + 32)) + return + end + if n >= -128 then + ensureSpace(w, 2) + rawU8(w, T_I8) buffer.writei8(w.buf, w.cursor, n) w.cursor += 1 elseif n >= -32768 then - wByte(w, T_I16) - wI16(w, n) + ensureSpace(w, 3) + rawU8(w, T_I16) + rawI16(w, n) elseif n >= -2147483648 then - wByte(w, T_I32) - wI32(w, n) + ensureSpace(w, 5) + rawU8(w, T_I32) + rawI32(w, n) else - wByte(w, T_F64) - wF64(w, n) + ensureSpace(w, 9) + rawU8(w, T_F64) + rawF64(w, n) end end else - -- float: try f32 first buffer.writef32(F32_TEST_BUF, 0, n) local f32Val = buffer.readf32(F32_TEST_BUF, 0) - -- if f32Val == n or math.abs(n - f32Val) < math.abs(n) * 1e-6 then -- since the test is failing so if f32Val == n then - wByte(w, T_F32) - wF32(w, n) + ensureSpace(w, 5) + rawU8(w, T_F32) + rawF32(w, n) else - wByte(w, T_F64) - wF64(w, n) + ensureSpace(w, 9) + rawU8(w, T_F64) + rawF64(w, n) end end end local function packString(w: Writer, s: string) local n = #s - if n <= 15 then - wByte(w, T_STR_BASE + n) + ensureSpace(w, 1 + n) + rawU8(w, T_STR_BASE + n) elseif n <= 255 then - wByte(w, T_STR8) - wByte(w, n) + ensureSpace(w, 2 + n) + rawU8(w, T_STR8) + rawU8(w, n) elseif n <= 65535 then - wByte(w, T_STR16) - wU16(w, n) + ensureSpace(w, 3 + n) + rawU8(w, T_STR16) + rawU16(w, n) else - wByte(w, T_STRVAR) - wVarUInt(w, n) + ensureSpace(w, 6 + n) -- tag(1) + varuint(max5) + data + rawU8(w, T_STRVAR) + rawVarUInt(w, n) end - wString(w, s) + rawString(w, s, n) end --- returns: category ("boolean"|"number"|nil), subtype (number dtype or nil), count local function analyzeArray(t: { any }, count: number): (string?, string?, number) if count < 2 then return nil, nil, count @@ -396,7 +569,6 @@ local function analyzeArray(t: { any }, count: number): (string?, string?, numbe local first = t[1] local firstType = type(first) - -- o(1) if firstType == "boolean" then for i = 2, count do if type(t[i]) ~= "boolean" then @@ -447,62 +619,6 @@ local function analyzeArray(t: { any }, count: number): (string?, string?, numbe return "number", "f64", count end -local TYPED_CODES = { u8 = 1, i8 = 2, u16 = 3, i16 = 4, u32 = 5, i32 = 6, f32 = 7, f64 = 8, f16 = 9 } -local TYPED_SIZES = { u8 = 1, i8 = 1, u16 = 2, i16 = 2, u32 = 4, i32 = 4, f32 = 4, f64 = 8, f16 = 2 } -local TYPED_WRITERS = { - u8 = buffer.writeu8, - i8 = buffer.writei8, - u16 = buffer.writeu16, - i16 = buffer.writei16, - u32 = buffer.writeu32, - i32 = buffer.writei32, - f32 = buffer.writef32, - f64 = buffer.writef64, - f16 = function(b: buffer, offset: number, value: number) - local raw: number - - if value ~= value then - raw = 0x7E00 - elseif value == 0 then - raw = if 1/value < 0 then 0x8000 else 0 - else - local sign = 0 - if value < 0 then - sign = 0x8000 - value = -value - end - - if value >= 65504 then - raw = sign + 0x7C00 - elseif value < 6.103515625e-05 then - local f = math.floor(value * 16777216 + 0.5) - if f >= 1024 then - raw = sign + 0x0400 - else - raw = sign + f - end - else - local m, e = math.frexp(value) - local biasedExp = e + 14 - local f = math.floor(m * 2048 - 1024 + 0.5) - - if f >= 1024 then - f = 0 - biasedExp += 1 - end - - if biasedExp >= 31 then - raw = sign + 0x7C00 - else - raw = sign + biasedExp * 1024 + f - end - end - end - - buffer.writeu16(b, offset, raw) - end, -} - local function packTable(w: Writer, t: { [any]: any }) local count = 0 local maxIdx = 0 @@ -524,14 +640,11 @@ local function packTable(w: Writer, t: { [any]: any }) if isArray then local category, dtype, arrCount = analyzeArray(t, count) - -- boolean bitpacking hacks if category == "boolean" then - wByte(w, T_BOOL_ARR) - wVarUInt(w, arrCount) - local numBytes = math.ceil(arrCount / 8) - ensureSpace(w, numBytes) - + ensureSpace(w, 1 + 5 + numBytes) + rawU8(w, T_BOOL_ARR) + rawVarUInt(w, arrCount) for i = 0, numBytes - 1 do local byte = 0 for bit = 0, 7 do @@ -549,17 +662,16 @@ local function packTable(w: Writer, t: { [any]: any }) return end - -- typed number array (4+ elements) + -- typed num arrays if category == "number" and dtype and arrCount >= 4 then local code = TYPED_CODES[dtype] local size = TYPED_SIZES[dtype] local writer = TYPED_WRITERS[dtype] - wByte(w, T_TYPED_ARR) - wByte(w, code) - wVarUInt(w, arrCount) - ensureSpace(w, arrCount * size) - + ensureSpace(w, 2 + 5 + arrCount * size) + rawU8(w, T_TYPED_ARR) + rawU8(w, code) + rawVarUInt(w, arrCount) for i = 1, arrCount do writer(w.buf, w.cursor, t[i]) w.cursor += size @@ -571,14 +683,17 @@ local function packTable(w: Writer, t: { [any]: any }) if count <= 7 then wByte(w, T_ARR_BASE + count) elseif count <= 255 then - wByte(w, T_ARR8) - wByte(w, count) + ensureSpace(w, 2) + rawU8(w, T_ARR8) + rawU8(w, count) elseif count <= 65535 then - wByte(w, T_ARR16) - wU16(w, count) + ensureSpace(w, 3) + rawU8(w, T_ARR16) + rawU16(w, count) else - wByte(w, T_ARRVAR) - wVarUInt(w, count) + ensureSpace(w, 6) + rawU8(w, T_ARRVAR) + rawVarUInt(w, count) end for i = 1, count do @@ -589,14 +704,17 @@ local function packTable(w: Writer, t: { [any]: any }) if count <= 7 then wByte(w, T_MAP_BASE + count) elseif count <= 255 then - wByte(w, T_MAP8) - wByte(w, count) + ensureSpace(w, 2) + rawU8(w, T_MAP8) + rawU8(w, count) elseif count <= 65535 then - wByte(w, T_MAP16) - wU16(w, count) + ensureSpace(w, 3) + rawU8(w, T_MAP16) + rawU16(w, count) else - wByte(w, T_MAPVAR) - wVarUInt(w, count) + ensureSpace(w, 6) + rawU8(w, T_MAPVAR) + rawVarUInt(w, count) end for k, v in t do @@ -607,481 +725,710 @@ local function packTable(w: Writer, t: { [any]: any }) end local function packVector3(w: Writer, v: Vector3) - wByte(w, T_VEC3) - wF16(w, v.X) - wF16(w, v.Y) - wF16(w, v.Z) + ensureSpace(w, 7) + rawU8(w, T_VEC3) + rawF16(w, v.X) + rawF16(w, v.Y) + rawF16(w, v.Z) end local function packVector2(w: Writer, v: Vector2) - wByte(w, T_VEC2) - wF16(w, v.X) - wF16(w, v.Y) + ensureSpace(w, 5) + rawU8(w, T_VEC2) + rawF16(w, v.X) + rawF16(w, v.Y) end local function packCFrame(w: Writer, cf: CFrame) local pos = cf.Position - local rx, ry, rz = cf:ToOrientation() - - wByte(w, T_CFRAME) - wF32(w, pos.X) - wF32(w, pos.Y) - wF32(w, pos.Z) - wF16(w, rx) - wF16(w, ry) - wF16(w, rz) + local qi, q0, q1, q2 = compressCFrameRotation(cf) + ensureSpace(w, 20) -- tag(1) + 3xf32(12) + u8(1) + 3xi16(6) + rawU8(w, T_CFRAME) + rawF32(w, pos.X) + rawF32(w, pos.Y) + rawF32(w, pos.Z) + rawU8(w, qi) + rawI16(w, q0) + rawI16(w, q1) + rawI16(w, q2) end local function packColor3(w: Writer, c: Color3) local r, g, b = c.R, c.G, c.B - if r > 1 or g > 1 or b > 1 then - wByte(w, T_COLOR3_F) - wF16(w, r) - wF16(w, g) - wF16(w, b) + ensureSpace(w, 7) + rawU8(w, T_COLOR3_F) + rawF16(w, r) + rawF16(w, g) + rawF16(w, b) else - wByte(w, T_COLOR3) - wByte(w, math.clamp(math.round(r * 255), 0, 255)) - wByte(w, math.clamp(math.round(g * 255), 0, 255)) - wByte(w, math.clamp(math.round(b * 255), 0, 255)) + ensureSpace(w, 4) + rawU8(w, T_COLOR3) + rawU8(w, math.clamp(math.round(r * 255), 0, 255)) + rawU8(w, math.clamp(math.round(g * 255), 0, 255)) + rawU8(w, math.clamp(math.round(b * 255), 0, 255)) end end -packValue = function(w: Writer, v: any): () - local t: string = typeof(v) +local PACK_DISPATCH: { [string]: (Writer, any) -> () } = {} - if t == "nil" then - wByte(w, T_NIL) - elseif t == "boolean" then - wByte(w, v and T_TRUE or T_FALSE) - elseif t == "number" then - packNumber(w, v) - elseif t == "string" then - packString(w, v) - elseif t == "table" then - packTable(w, v) - elseif t == "Vector3" then - packVector3(w, v) - elseif t == "Vector2" then - packVector2(w, v) - elseif t == "CFrame" then - packCFrame(w, v) - elseif t == "Color3" then - packColor3(w, v) - elseif t == "Instance" then - wByte(w, T_INSTANCE) - table.insert(w.refs, v) - wVarUInt(w, #w.refs) - elseif t == "EnumItem" then - wByte(w, T_ENUMITEM) - local enumName = `{v.EnumType}` - wVarUInt(w, #enumName) - wString(w, enumName) - wU16(w, v.Value) - elseif t == "BrickColor" then - wByte(w, T_BRICKCOLOR) - wU16(w, v.Number) - elseif t == "Enum" then - wByte(w, T_ENUM) - local name = v.Name - wVarUInt(w, #name) - wString(w, name) - elseif t == "UDim2" then - local X, Y = v.X, v.Y - wByte(w, T_UDIM2) - wF32(w, X.Scale) - wI32(w, X.Offset) - wF32(w, Y.Scale) - wI32(w, Y.Offset) - elseif t == "UDim" then - wByte(w, T_UDIM) - wF32(w, v.Scale) - wI32(w, v.Offset) - elseif t == "Rect" then - local Min, Max = v.Min, v.Max - wByte(w, T_RECT) - wF32(w, Min.X) - wF32(w, Min.Y) - wF32(w, Max.X) - wF32(w, Max.Y) - elseif t == "NumberRange" then - wByte(w, T_NUMBERRANGE) - wF32(w, v.Min) - wF32(w, v.Max) - elseif t == "Ray" then - local Origin, Direction = v.Origin, v.Direction - wByte(w, T_RAY) - wF32(w, Origin.X) - wF32(w, Origin.Y) - wF32(w, Origin.Z) - wF32(w, Direction.X) - wF32(w, Direction.Y) - wF32(w, Direction.Z) - elseif t == "ColorSequence" then - local keypoints = v.Keypoints - wByte(w, T_COLSEQ) - wByte(w, #keypoints) - for _, kp in keypoints do - wF32(w, kp.Time) - local c = kp.Value - wByte(w, math.clamp(math.round(c.R * 255), 0, 255)) - wByte(w, math.clamp(math.round(c.G * 255), 0, 255)) - wByte(w, math.clamp(math.round(c.B * 255), 0, 255)) - end - elseif t == "NumberSequence" then - local keypoints = v.Keypoints - wByte(w, T_NUMSEQ) - wByte(w, #keypoints) - for _, kp in keypoints do - wF32(w, kp.Time) - wF32(w, kp.Value) - wF32(w, kp.Envelope) - end - elseif t == "buffer" then - wByte(w, T_BUFFER) - local len = buffer.len(v) - wVarUInt(w, len) - ensureSpace(w, len) - buffer.copy(w.buf, w.cursor, v, 0, len) - w.cursor += len - else - error(`Unsupported type: {t}`) +PACK_DISPATCH["nil"] = function(w: Writer, _: any) + wByte(w, T_NIL) +end + +PACK_DISPATCH["boolean"] = function(w: Writer, v: any) + wByte(w, v and T_TRUE or T_FALSE) +end + +PACK_DISPATCH["number"] = function(w: Writer, v: any) + packNumber(w, v) +end + +PACK_DISPATCH["string"] = function(w: Writer, v: any) + packString(w, v) +end + +PACK_DISPATCH["table"] = function(w: Writer, v: any) + packTable(w, v) +end + +PACK_DISPATCH["Vector3"] = function(w: Writer, v: any) + packVector3(w, v) +end + +PACK_DISPATCH["Vector2"] = function(w: Writer, v: any) + packVector2(w, v) +end + +PACK_DISPATCH["CFrame"] = function(w: Writer, v: any) + packCFrame(w, v) +end + +PACK_DISPATCH["Color3"] = function(w: Writer, v: any) + packColor3(w, v) +end + +PACK_DISPATCH["Instance"] = function(w: Writer, v: any) + table.insert(w.refs, v) + local refIdx = #w.refs + ensureSpace(w, 6) + rawU8(w, T_INSTANCE) + rawVarUInt(w, refIdx) +end + +PACK_DISPATCH["EnumItem"] = function(w: Writer, v: any) + local enumName = `{v.EnumType}` + local nameLen = #enumName + ensureSpace(w, 1 + 5 + nameLen + 2) + rawU8(w, T_ENUMITEM) + rawVarUInt(w, nameLen) + rawString(w, enumName, nameLen) + rawU16(w, v.Value) +end + +PACK_DISPATCH["BrickColor"] = function(w: Writer, v: any) + ensureSpace(w, 3) + rawU8(w, T_BRICKCOLOR) + rawU16(w, v.Number) +end + +PACK_DISPATCH["Enum"] = function(w: Writer, v: any) + local name = v.Name + local nameLen = #name + ensureSpace(w, 1 + 5 + nameLen) + rawU8(w, T_ENUM) + rawVarUInt(w, nameLen) + rawString(w, name, nameLen) +end + +PACK_DISPATCH["UDim2"] = function(w: Writer, v: any) + local X, Y = v.X, v.Y + ensureSpace(w, 17) + rawU8(w, T_UDIM2) + rawF32(w, X.Scale) + rawI32(w, X.Offset) + rawF32(w, Y.Scale) + rawI32(w, Y.Offset) +end + +PACK_DISPATCH["UDim"] = function(w: Writer, v: any) + ensureSpace(w, 9) + rawU8(w, T_UDIM) + rawF32(w, v.Scale) + rawI32(w, v.Offset) +end + +PACK_DISPATCH["Rect"] = function(w: Writer, v: any) + local Min, Max = v.Min, v.Max + ensureSpace(w, 17) + rawU8(w, T_RECT) + rawF32(w, Min.X) + rawF32(w, Min.Y) + rawF32(w, Max.X) + rawF32(w, Max.Y) +end + +PACK_DISPATCH["NumberRange"] = function(w: Writer, v: any) + ensureSpace(w, 9) + rawU8(w, T_NUMBERRANGE) + rawF32(w, v.Min) + rawF32(w, v.Max) +end + +PACK_DISPATCH["Ray"] = function(w: Writer, v: any) + local Origin, Direction = v.Origin, v.Direction + ensureSpace(w, 25) + rawU8(w, T_RAY) + rawF32(w, Origin.X) + rawF32(w, Origin.Y) + rawF32(w, Origin.Z) + rawF32(w, Direction.X) + rawF32(w, Direction.Y) + rawF32(w, Direction.Z) +end + +PACK_DISPATCH["ColorSequence"] = function(w: Writer, v: any) + local keypoints = v.Keypoints + local kpCount = #keypoints + ensureSpace(w, 2 + kpCount * 7) + rawU8(w, T_COLSEQ) + rawU8(w, kpCount) + for _, kp in keypoints do + rawF32(w, kp.Time) + local c = kp.Value + rawU8(w, math.clamp(math.round(c.R * 255), 0, 255)) + rawU8(w, math.clamp(math.round(c.G * 255), 0, 255)) + rawU8(w, math.clamp(math.round(c.B * 255), 0, 255)) end end -local unpackValue: (buf: buffer, pos: number, refs: { any }?) -> (any, number) +PACK_DISPATCH["NumberSequence"] = function(w: Writer, v: any) + local keypoints = v.Keypoints + local kpCount = #keypoints + ensureSpace(w, 2 + kpCount * 12) + rawU8(w, T_NUMSEQ) + rawU8(w, kpCount) + for _, kp in keypoints do + rawF32(w, kp.Time) + rawF32(w, kp.Value) + rawF32(w, kp.Envelope) + end +end + +PACK_DISPATCH["buffer"] = function(w: Writer, v: any) + local len = buffer.len(v) + ensureSpace(w, 1 + 5 + len) + rawU8(w, T_BUFFER) + rawVarUInt(w, len) + buffer.copy(w.buf, w.cursor, v, 0, len) + w.cursor += len +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) + ensureSpace(w, 16) -- tag(1) + f32(4) + u8(1) + u8(1) + i32(4) + u8(1) + f32(4) + rawU8(w, T_TWEENINFO) + rawF32(w, v.Time) + rawU8(w, v.EasingStyle.Value) + rawU8(w, v.EasingDirection.Value) + rawI32(w, v.RepeatCount) + rawU8(w, v.Reverses and 1 or 0) + rawF32(w, v.DelayTime) +end + +PACK_DISPATCH["PhysicalProperties"] = function(w: Writer, v: any) + ensureSpace(w, 21) -- tag(1) + 5xf32(20) + rawU8(w, T_PHYSPROP) + rawF32(w, v.Density) + rawF32(w, v.Friction) + rawF32(w, v.Elasticity) + rawF32(w, v.FrictionWeight) + rawF32(w, v.ElasticityWeight) +end + +PACK_DISPATCH["Font"] = function(w: Writer, v: any) + local family = v.Family + local familyLen = #family + ensureSpace(w, 1 + 5 + familyLen + 3) -- tag + varuint + str + u16 + u8 + rawU8(w, T_FONT) + rawVarUInt(w, familyLen) + rawString(w, family, familyLen) + rawU16(w, v.Weight.Value) + rawU8(w, v.Style.Value) +end + +PACK_DISPATCH["DateTime"] = function(w: Writer, v: any) + ensureSpace(w, 9) -- tag(1) + f64(8) + rawU8(w, T_DATETIME) + rawF64(w, v.UnixTimestampMillis) +end + +packValue = function(w: Writer, v: any) + local handler = PACK_DISPATCH[typeof(v)] + if handler then + handler(w, v) + else + error(`Unsupported type: {typeof(v)}`) + end +end + +local UNPACK: { (buffer, number, { any }?, number) -> (any, number) } = table.create(256) + +do + local function unpackError(_: buffer, _: number, _: { any }?, tag: number): (any, number) + error(`(serdes) unknown type: {tag}`) + return nil :: any, 0 + end + for i = 1, 256 do + UNPACK[i] = unpackError + end +end + +do + local function handler(_: buffer, pos: number, _: { any }?, tag: number): (any, number) + return tag, pos + end + for i = 0x00, 0x7F do + UNPACK[i + 1] = handler + end +end + +do + local function handler(_: buffer, pos: number, _: { any }?, tag: number): (any, number) + return tag - 0x80 - 32, pos + end + for i = 0x80, 0x9F do + UNPACK[i + 1] = handler + end +end + +-- scalar types +UNPACK[T_NIL + 1] = function(_: buffer, pos: number, _: { any }?, _: number): (any, number) + return nil, pos +end + +UNPACK[T_FALSE + 1] = function(_: buffer, pos: number, _: { any }?, _: number): (any, number) + return false, pos +end + +UNPACK[T_TRUE + 1] = function(_: buffer, pos: number, _: { any }?, _: number): (any, number) + return true, pos +end + +-- numeric types +UNPACK[T_U8 + 1] = function(buf: buffer, pos: number, _: { any }?, _: number): (any, number) + return buffer.readu8(buf, pos), pos + 1 +end + +UNPACK[T_U16 + 1] = function(buf: buffer, pos: number, _: { any }?, _: number): (any, number) + return buffer.readu16(buf, pos), pos + 2 +end + +UNPACK[T_U32 + 1] = function(buf: buffer, pos: number, _: { any }?, _: number): (any, number) + return buffer.readu32(buf, pos), pos + 4 +end + +UNPACK[T_I8 + 1] = function(buf: buffer, pos: number, _: { any }?, _: number): (any, number) + return buffer.readi8(buf, pos), pos + 1 +end + +UNPACK[T_I16 + 1] = function(buf: buffer, pos: number, _: { any }?, _: number): (any, number) + return buffer.readi16(buf, pos), pos + 2 +end + +UNPACK[T_I32 + 1] = function(buf: buffer, pos: number, _: { any }?, _: number): (any, number) + return buffer.readi32(buf, pos), pos + 4 +end + +UNPACK[T_F16 + 1] = function(buf: buffer, pos: number, _: { any }?, _: number): (any, number) + return readF16(buf, pos), pos + 2 +end + +UNPACK[T_F32 + 1] = function(buf: buffer, pos: number, _: { any }?, _: number): (any, number) + return buffer.readf32(buf, pos), pos + 4 +end + +UNPACK[T_F64 + 1] = function(buf: buffer, pos: number, _: { any }?, _: number): (any, number) + return buffer.readf64(buf, pos), pos + 8 +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) + return TweenInfo.new( + buffer.readf32(buf, pos), + Enum.EasingStyle:FromValue(buffer.readu8(buf, pos + 4)), + Enum.EasingDirection:FromValue(buffer.readu8(buf, pos + 5)), + buffer.readi32(buf, pos + 6), + buffer.readu8(buf, pos + 10) ~= 0, + buffer.readf32(buf, pos + 11) + ), + pos + 15 +end + +UNPACK[T_PHYSPROP + 1] = function(buf: buffer, pos: number, _: { any }?, _: number): (any, number) + return PhysicalProperties.new( + buffer.readf32(buf, pos), + buffer.readf32(buf, pos + 4), + buffer.readf32(buf, pos + 8), + buffer.readf32(buf, pos + 12), + buffer.readf32(buf, pos + 16) + ), + pos + 20 +end + +UNPACK[T_FONT + 1] = function(buf: buffer, pos: number, _: { any }?, _: number): (any, number) + local familyLen + familyLen, pos = readVarUInt(buf, pos) + local family = buffer.readstring(buf, pos, familyLen) + pos += familyLen + local weight = buffer.readu16(buf, pos) + local style = buffer.readu8(buf, pos + 2) + return Font.new(family, Enum.FontWeight:FromValue(weight), Enum.FontStyle:FromValue(style)), pos + 3 +end + +UNPACK[T_DATETIME + 1] = function(buf: buffer, pos: number, _: { any }?, _: number): (any, number) + return DateTime.fromUnixTimestampMillis(buffer.readf64(buf, pos)), pos + 8 +end + +do + local function handler(buf: buffer, pos: number, _: { any }?, tag: number): (any, number) + local n = tag - T_STR_BASE + return buffer.readstring(buf, pos, n), pos + n + end + for i = T_STR_BASE, T_STR_BASE + 15 do + UNPACK[i + 1] = handler + end +end + +UNPACK[T_STR8 + 1] = function(buf: buffer, pos: number, _: { any }?, _: number): (any, number) + local n = buffer.readu8(buf, pos) + return buffer.readstring(buf, pos + 1, n), pos + 1 + n +end + +UNPACK[T_STR16 + 1] = function(buf: buffer, pos: number, _: { any }?, _: number): (any, number) + local n = buffer.readu16(buf, pos) + return buffer.readstring(buf, pos + 2, n), pos + 2 + n +end + +UNPACK[T_STRVAR + 1] = function(buf: buffer, pos: number, _: { any }?, _: number): (any, number) + local n + n, pos = readVarUInt(buf, pos) + return buffer.readstring(buf, pos, n), pos + n +end + +do + local function handler(buf: buffer, pos: number, refs: { any }?, tag: number): (any, number) + local count = tag - T_ARR_BASE + local arr = table.create(count) + for i = 1, count do + arr[i], pos = unpackValue(buf, pos, refs) + end + return arr, pos + end + for i = T_ARR_BASE, T_ARR_BASE + 7 do + UNPACK[i + 1] = handler + end +end + +UNPACK[T_ARR8 + 1] = function(buf: buffer, pos: number, refs: { any }?, _: number): (any, number) + local count = buffer.readu8(buf, pos) + pos += 1 + local arr = table.create(count) + for i = 1, count do + arr[i], pos = unpackValue(buf, pos, refs) + end + return arr, pos +end + +UNPACK[T_ARR16 + 1] = function(buf: buffer, pos: number, refs: { any }?, _: number): (any, number) + local count = buffer.readu16(buf, pos) + pos += 2 + local arr = table.create(count) + for i = 1, count do + arr[i], pos = unpackValue(buf, pos, refs) + end + return arr, pos +end + +UNPACK[T_ARRVAR + 1] = function(buf: buffer, pos: number, refs: { any }?, _: number): (any, number) + local count + count, pos = readVarUInt(buf, pos) + local arr = table.create(count) + for i = 1, count do + arr[i], pos = unpackValue(buf, pos, refs) + end + return arr, pos +end + +do + local function handler(buf: buffer, pos: number, refs: { any }?, tag: number): (any, number) + local count = tag - T_MAP_BASE + local map = {} + for _ = 1, count do + local k, v + k, pos = unpackValue(buf, pos, refs) + v, pos = unpackValue(buf, pos, refs) + map[k] = v + end + return map, pos + end + for i = T_MAP_BASE, T_MAP_BASE + 7 do + UNPACK[i + 1] = handler + end +end + +UNPACK[T_MAP8 + 1] = function(buf: buffer, pos: number, refs: { any }?, _: number): (any, number) + local count = buffer.readu8(buf, pos) + pos += 1 + local map = {} + for _ = 1, count do + local k, v + k, pos = unpackValue(buf, pos, refs) + v, pos = unpackValue(buf, pos, refs) + map[k] = v + end + return map, pos +end + +UNPACK[T_MAP16 + 1] = function(buf: buffer, pos: number, refs: { any }?, _: number): (any, number) + local count = buffer.readu16(buf, pos) + pos += 2 + local map = {} + for _ = 1, count do + local k, v + k, pos = unpackValue(buf, pos, refs) + v, pos = unpackValue(buf, pos, refs) + map[k] = v + end + return map, pos +end + +UNPACK[T_MAPVAR + 1] = function(buf: buffer, pos: number, refs: { any }?, _: number): (any, number) + local count + count, pos = readVarUInt(buf, pos) + local map = {} + for _ = 1, count do + local k, v + k, pos = unpackValue(buf, pos, refs) + v, pos = unpackValue(buf, pos, refs) + map[k] = v + end + return map, pos +end + +-- typed number array +UNPACK[T_TYPED_ARR + 1] = function(buf: buffer, pos: number, _: { any }?, _: number): (any, number) + local subtype = buffer.readu8(buf, pos) + pos += 1 + local count + count, pos = readVarUInt(buf, pos) + local reader = TYPED_READERS[subtype] + local arr = table.create(count) + for i = 1, count do + arr[i], pos = reader(buf, pos) + end + return arr, pos +end + +-- boolean bitpacked array +UNPACK[T_BOOL_ARR + 1] = function(buf: buffer, pos: number, _: { any }?, _: number): (any, number) + local count + count, pos = readVarUInt(buf, pos) + local arr = table.create(count) + local numBytes = math.ceil(count / 8) + for i = 0, numBytes - 1 do + local byte = buffer.readu8(buf, pos + i) + for bit = 0, 7 do + local idx = i * 8 + bit + 1 + if idx > count then + break + end + arr[idx] = bit32.band(byte, bit32.lshift(1, bit)) ~= 0 + end + end + return arr, pos + numBytes +end + +-- buffer +UNPACK[T_BUFFER + 1] = function(buf: buffer, pos: number, _: { any }?, _: number): (any, number) + local len + len, pos = readVarUInt(buf, pos) + local b = buffer.create(len) + buffer.copy(b, 0, buf, pos, len) + return b, pos + len +end + +-- Vector3 +UNPACK[T_VEC3 + 1] = function(buf: buffer, pos: number, _: { any }?, _: number): (any, number) + local x = readF16(buf, pos) + local y = readF16(buf, pos + 2) + local z = readF16(buf, pos + 4) + return Vector3.new(x, y, z), pos + 6 +end + +-- Vector2 +UNPACK[T_VEC2 + 1] = function(buf: buffer, pos: number, _: { any }?, _: number): (any, number) + local x = readF16(buf, pos) + local y = readF16(buf, pos + 2) + return Vector2.new(x, y), pos + 4 +end + +-- CFrame +UNPACK[T_CFRAME + 1] = function(buf: buffer, pos: number, _: { any }?, _: number): (any, number) + local px = buffer.readf32(buf, pos) + local py = buffer.readf32(buf, pos + 4) + local pz = buffer.readf32(buf, pos + 8) + local qi = buffer.readu8(buf, pos + 12) + local q0 = buffer.readi16(buf, pos + 13) + local q1 = buffer.readi16(buf, pos + 15) + local q2 = buffer.readi16(buf, pos + 17) + local qx, qy, qz, qw = decompressCFrameRotation(qi, q0, q1, q2) + return CFrame.new(px, py, pz, qx, qy, qz, qw), pos + 19 +end + +-- Color3 (RGB bytes) +UNPACK[T_COLOR3 + 1] = function(buf: buffer, pos: number, _: { any }?, _: number): (any, number) + local r = buffer.readu8(buf, pos) + local g = buffer.readu8(buf, pos + 1) + local b = buffer.readu8(buf, pos + 2) + return Color3.fromRGB(r, g, b), pos + 3 +end + +-- Color3 (f16 floats) +UNPACK[T_COLOR3_F + 1] = function(buf: buffer, pos: number, _: { any }?, _: number): (any, number) + local r = readF16(buf, pos) + local g = readF16(buf, pos + 2) + local b = readF16(buf, pos + 4) + return Color3.new(r, g, b), pos + 6 +end + +-- BrickColor +UNPACK[T_BRICKCOLOR + 1] = function(buf: buffer, pos: number, _: { any }?, _: number): (any, number) + return BrickColor.new(buffer.readu16(buf, pos)), pos + 2 +end + +-- Instance +UNPACK[T_INSTANCE + 1] = function(buf: buffer, pos: number, refs: { any }?, _: number): (any, number) + local idx + idx, pos = readVarUInt(buf, pos) + return refs and refs[idx] or nil, pos +end + +-- EnumItem +UNPACK[T_ENUMITEM + 1] = function(buf: buffer, pos: number, _: { any }?, _: number): (any, number) + local nameLen + nameLen, pos = readVarUInt(buf, pos) + local enumName = buffer.readstring(buf, pos, nameLen) + pos += nameLen + local val = buffer.readu16(buf, pos) + return (Enum :: any)[enumName]:FromValue(val), pos + 2 +end + +-- Enum +UNPACK[T_ENUM + 1] = function(buf: buffer, pos: number, _: { any }?, _: number): (any, number) + local nameLen + nameLen, pos = readVarUInt(buf, pos) + local enumName = buffer.readstring(buf, pos, nameLen) + return (Enum :: any)[enumName], pos + nameLen +end + +-- UDim2 +UNPACK[T_UDIM2 + 1] = function(buf: buffer, pos: number, _: { any }?, _: number): (any, number) + local xs = buffer.readf32(buf, pos) + local xo = buffer.readi32(buf, pos + 4) + local ys = buffer.readf32(buf, pos + 8) + local yo = buffer.readi32(buf, pos + 12) + return UDim2.new(xs, xo, ys, yo), pos + 16 +end + +-- UDim +UNPACK[T_UDIM + 1] = function(buf: buffer, pos: number, _: { any }?, _: number): (any, number) + local s = buffer.readf32(buf, pos) + local o = buffer.readi32(buf, pos + 4) + return UDim.new(s, o), pos + 8 +end + +-- Rect +UNPACK[T_RECT + 1] = function(buf: buffer, pos: number, _: { any }?, _: number): (any, number) + return Rect.new( + buffer.readf32(buf, pos), + buffer.readf32(buf, pos + 4), + buffer.readf32(buf, pos + 8), + buffer.readf32(buf, pos + 12) + ), + pos + 16 +end + +-- NumberRange +UNPACK[T_NUMBERRANGE + 1] = function(buf: buffer, pos: number, _: { any }?, _: number): (any, number) + return NumberRange.new(buffer.readf32(buf, pos), buffer.readf32(buf, pos + 4)), pos + 8 +end + +-- Ray +UNPACK[T_RAY + 1] = function(buf: buffer, pos: number, _: { any }?, _: number): (any, number) + 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 + 12), buffer.readf32(buf, pos + 16), buffer.readf32(buf, pos + 20)) + ), + pos + 24 +end + +-- ColorSequence +UNPACK[T_COLSEQ + 1] = function(buf: buffer, pos: number, _: { any }?, _: number): (any, number) + local count = buffer.readu8(buf, pos) + pos += 1 + local keypoints = table.create(count) + for i = 1, count do + local time = buffer.readf32(buf, pos) + local r = buffer.readu8(buf, pos + 4) + local g = buffer.readu8(buf, pos + 5) + local b = buffer.readu8(buf, pos + 6) + keypoints[i] = ColorSequenceKeypoint.new(time, Color3.fromRGB(r, g, b)) + pos += 7 + end + return ColorSequence.new(keypoints), pos +end + +-- NumberSequence +UNPACK[T_NUMSEQ + 1] = function(buf: buffer, pos: number, _: { any }?, _: number): (any, number) + local count = buffer.readu8(buf, pos) + pos += 1 + local keypoints = table.create(count) + for i = 1, count do + local time = buffer.readf32(buf, pos) + local value = buffer.readf32(buf, pos + 4) + local envelope = buffer.readf32(buf, pos + 8) + keypoints[i] = NumberSequenceKeypoint.new(time, value, envelope) + pos += 12 + end + return NumberSequence.new(keypoints), pos +end unpackValue = function(buf: buffer, pos: number, refs: { any }?): (any, number) - local t = buffer.readu8(buf, pos) - pos += 1 - - -- + fixint (0-127) - if t <= 0x7F then - return t, pos - end - - -- - fixint (-32 to -1) - if t >= 0x80 and t <= 0x9F then - return t - 0x80 - 32, pos - end - - if t == T_NIL then - return nil, pos - 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_U16 then - return buffer.readu16(buf, pos), pos + 2 - end - if t == T_U32 then - 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_F16 then - return readF16(buf, pos), pos + 2 - 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) - if t >= T_STR_BASE and t <= T_STR_BASE + 15 then - local n = t - T_STR_BASE - return buffer.readstring(buf, pos, n), pos + n - end - - if t == T_STR8 then - local n = buffer.readu8(buf, pos) - return buffer.readstring(buf, pos + 1, n), pos + 1 + n - end - if t == T_STR16 then - local n = buffer.readu16(buf, pos) - return buffer.readstring(buf, pos + 2, n), pos + 2 + n - end - if t == T_STRVAR then - local n - n, pos = readVarUInt(buf, pos) - return buffer.readstring(buf, pos, n), pos + n - end - - -- inline array len (0-7) - if t >= T_ARR_BASE and t <= T_ARR_BASE + 7 then - local count = t - T_ARR_BASE - local arr = table.create(count) - for i = 1, count do - arr[i], pos = unpackValue(buf, pos, refs) - end - return arr, pos - end - - if t == T_ARR8 then - local count = buffer.readu8(buf, pos) - pos += 1 - local arr = table.create(count) - for i = 1, count do - arr[i], pos = unpackValue(buf, pos, refs) - end - return arr, pos - end - if t == T_ARR16 then - local count = buffer.readu16(buf, pos) - pos += 2 - local arr = table.create(count) - for i = 1, count do - arr[i], pos = unpackValue(buf, pos, refs) - end - return arr, pos - end - if t == T_ARRVAR then - local count - count, pos = readVarUInt(buf, pos) - local arr = table.create(count) - for i = 1, count do - arr[i], pos = unpackValue(buf, pos, refs) - end - return arr, pos - end - - -- inline map len (0-7) - if t >= T_MAP_BASE and t <= T_MAP_BASE + 7 then - local count = t - T_MAP_BASE - local map = {} - for _ = 1, count do - local k, v - k, pos = unpackValue(buf, pos, refs) - v, pos = unpackValue(buf, pos, refs) - map[k] = v - end - return map, pos - end - - if t == T_MAP8 then - local count = buffer.readu8(buf, pos) - pos += 1 - local map = {} - for _ = 1, count do - local k, v - k, pos = unpackValue(buf, pos, refs) - v, pos = unpackValue(buf, pos, refs) - map[k] = v - end - return map, pos - end - if t == T_MAP16 then - local count = buffer.readu16(buf, pos) - pos += 2 - local map = {} - for _ = 1, count do - local k, v - k, pos = unpackValue(buf, pos, refs) - v, pos = unpackValue(buf, pos, refs) - map[k] = v - end - return map, pos - end - if t == T_MAPVAR then - local count - count, pos = readVarUInt(buf, pos) - local map = {} - for _ = 1, count do - local k, v - k, pos = unpackValue(buf, pos, refs) - v, pos = unpackValue(buf, pos, refs) - map[k] = v - end - return map, pos - end - - -- typed array - if t == T_TYPED_ARR then - local subtype = buffer.readu8(buf, pos) - pos += 1 - local count - count, pos = readVarUInt(buf, pos) - local reader = TYPED_READERS[subtype] - local arr = table.create(count) - for i = 1, count do - arr[i], pos = reader(buf, pos) - end - return arr, pos - end - - if t == T_BOOL_ARR then - local count - count, pos = readVarUInt(buf, pos) - local arr = table.create(count) - local numBytes = math.ceil(count / 8) - for i = 0, numBytes - 1 do - local byte = buffer.readu8(buf, pos + i) - for bit = 0, 7 do - local idx = i * 8 + bit + 1 - if idx > count then - break - end - arr[idx] = bit32.band(byte, bit32.lshift(1, bit)) ~= 0 - end - end - return arr, pos + numBytes - end - - -- Vector3 - if t == T_VEC3 then - local x = readF16(buf, pos) - local y = readF16(buf, pos + 2) - local z = readF16(buf, pos + 4) - return Vector3.new(x, y, z), pos + 6 - end - - -- Vector2 - if t == T_VEC2 then - local x = readF16(buf, pos) - local y = readF16(buf, pos + 2) - return Vector2.new(x, y), pos + 4 - end - - -- CFrame - if t == T_CFRAME then - local px = buffer.readf32(buf, pos) - local py = buffer.readf32(buf, pos + 4) - local pz = buffer.readf32(buf, pos + 8) - local rx = readF16(buf, pos + 12) - local ry = readF16(buf, pos + 14) - local rz = readF16(buf, pos + 16) - return CFrame.new(px, py, pz) * CFrame.fromOrientation(rx, ry, rz), pos + 18 - end - - -- Color3 - if t == T_COLOR3 then - local r = buffer.readu8(buf, pos) - local g = buffer.readu8(buf, pos + 1) - local b = buffer.readu8(buf, pos + 2) - return Color3.fromRGB(r, g, b), pos + 3 - end - if t == T_COLOR3_F then - local r = readF16(buf, pos) - local g = readF16(buf, pos + 2) - local b = readF16(buf, pos + 4) - return Color3.new(r, g, b), pos + 6 - end - - if t == T_BRICKCOLOR then - return BrickColor.new(buffer.readu16(buf, pos)), pos + 2 - end - - if t == T_INSTANCE then - local idx - idx, pos = readVarUInt(buf, pos) - return refs and refs[idx] or nil, pos - end - - if t == T_ENUMITEM then - local nameLen - nameLen, pos = readVarUInt(buf, pos) - local enumName = buffer.readstring(buf, pos, nameLen) - pos += nameLen - local val = buffer.readu16(buf, pos) - return (Enum :: any)[enumName]:FromValue(val), pos + 2 - end - - if t == T_ENUM then - local nameLen - nameLen, pos = readVarUInt(buf, pos) - local enumName = buffer.readstring(buf, pos, nameLen) - return (Enum :: any)[enumName], pos + nameLen - end - - if t == T_UDIM2 then - local xs = buffer.readf32(buf, pos) - local xo = buffer.readi32(buf, pos + 4) - local ys = buffer.readf32(buf, pos + 8) - local yo = buffer.readi32(buf, pos + 12) - return UDim2.new(xs, xo, ys, yo), pos + 16 - end - - if t == T_UDIM then - local s = buffer.readf32(buf, pos) - local o = buffer.readi32(buf, pos + 4) - return UDim.new(s, o), pos + 8 - end - - if t == T_RECT then - return Rect.new( - buffer.readf32(buf, pos), - buffer.readf32(buf, pos + 4), - buffer.readf32(buf, pos + 8), - buffer.readf32(buf, pos + 12) - ), pos + 16 - end - - if t == T_NUMBERRANGE then - return NumberRange.new(buffer.readf32(buf, pos), buffer.readf32(buf, pos + 4)), pos + 8 - end - - if t == T_RAY then - 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 + 12), buffer.readf32(buf, pos + 16), buffer.readf32(buf, pos + 20)) - ), pos + 24 - end - - if t == T_COLSEQ then - local count = buffer.readu8(buf, pos) - pos += 1 - local keypoints = table.create(count) - for i = 1, count do - local time = buffer.readf32(buf, pos) - local r = buffer.readu8(buf, pos + 4) - local g = buffer.readu8(buf, pos + 5) - local b = buffer.readu8(buf, pos + 6) - keypoints[i] = ColorSequenceKeypoint.new(time, Color3.fromRGB(r, g, b)) - pos += 7 - end - return ColorSequence.new(keypoints), pos - end - - if t == T_NUMSEQ then - local count = buffer.readu8(buf, pos) - pos += 1 - local keypoints = table.create(count) - for i = 1, count do - local time = buffer.readf32(buf, pos) - local value = buffer.readf32(buf, pos + 4) - local envelope = buffer.readf32(buf, pos + 8) - keypoints[i] = NumberSequenceKeypoint.new(time, value, envelope) - pos += 12 - end - return NumberSequence.new(keypoints), pos - end - - if t == T_BUFFER then - local len - len, pos = readVarUInt(buf, pos) - local b = buffer.create(len) - buffer.copy(b, 0, buf, pos, len) - return b, pos + len - end - - error(`(serdes) unknown type: {t}`) + local tag = buffer.readu8(buf, pos) + return UNPACK[tag + 1](buf, pos + 1, refs, tag) end local function build(w: Writer): buffer @@ -1093,7 +1440,12 @@ end local function buildWithRefs(w: Writer): (buffer, { any }?) local result = buffer.create(w.cursor) buffer.copy(result, 0, w.buf, 0, w.cursor) - return result, #w.refs > 0 and table.clone(w.refs) or nil + local refs = w.refs + if #refs > 0 then + w.refs = {} + return result, refs + end + return result, nil end local function reset(w: Writer) @@ -1103,6 +1455,7 @@ end local Schema = {} Schema.__custom_datatypes = {} + export type SchemaType = { type: string, [any]: any } Schema.u8 = { type = "u8" } @@ -1122,6 +1475,21 @@ Schema.color3 = { type = "color3" } Schema.color3f16 = { type = "color3f16" } Schema.instance = { type = "instance" } Schema.string = { type = "string" } +Schema.udim2 = { type = "udim2" } +Schema.udim = { type = "udim" } +Schema.rect = { type = "rect" } +Schema.numberrange = { type = "numberrange" } +Schema.ray = { type = "ray" } +Schema.colorsequence = { type = "colorsequence" } +Schema.numbersequence = { type = "numbersequence" } +Schema.brickcolor = { type = "brickcolor" } +Schema.buffer = { type = "buffer" } +Schema.vector3int16 = { type = "vector3int16" } +Schema.vector2int16 = { type = "vector2int16" } +Schema.tweeninfo = { type = "tweeninfo" } +Schema.physicalproperties = { type = "physicalproperties" } +Schema.font = { type = "font" } +Schema.datetime = { type = "datetime" } function Schema.optional(item: SchemaType): SchemaType return { type = "optional", item = item } @@ -1146,7 +1514,12 @@ function Schema.struct(fields: { [string]: SchemaType }): SchemaType return { type = "struct", fields = orderedFields } end -function Schema.custom_datatype(name: string, object: { any }, writer: (w: Writer, v: any) -> (), reader: (b: buffer, c: number, refs: { Instance }?) -> (any, number)) +function Schema.custom_datatype( + name: string, + object: { any }, + writer: (w: Writer, v: any) -> (), + reader: (b: buffer, c: number, refs: { Instance }?) -> (any, number) +) if Schema[name] then warn(`[Warp.Schema]: this 'custom_datatype': "{name}" is already exists, try changing the name.`) return @@ -1159,6 +1532,35 @@ function Schema.custom_datatype(name: string, object: { any }, writer: (w: Write } end +local SCHEMA_FIXED_SIZES: { [string]: number } = { + u8 = 1, + i8 = 1, + u16 = 2, + i16 = 2, + u32 = 4, + i32 = 4, + f16 = 2, + f32 = 4, + f64 = 8, + boolean = 1, + vector3 = 6, + vector2 = 4, + cframe = 19, + color3 = 3, + color3f16 = 6, + udim2 = 16, + udim = 8, + rect = 16, + numberrange = 8, + ray = 24, + brickcolor = 2, + vector3int16 = 6, + vector2int16 = 4, + tweeninfo = 15, + physicalproperties = 20, + datetime = 8, +} + local function compilePacker(s: SchemaType): (Writer, any) -> () local schema_type = s.type @@ -1167,102 +1569,275 @@ local function compilePacker(s: SchemaType): (Writer, any) -> () end if schema_type == "u8" then - return wByte + return function(w: Writer, v: any) + ensureSpace(w, 1) + rawU8(w, v) + end end if schema_type == "i8" then - return function(w, v) + return function(w: Writer, v: any) ensureSpace(w, 1) buffer.writei8(w.buf, w.cursor, v) w.cursor += 1 end end if schema_type == "u16" then - return wU16 + return function(w: Writer, v: any) + ensureSpace(w, 2) + rawU16(w, v) + end end if schema_type == "i16" then - return wI16 + return function(w: Writer, v: any) + ensureSpace(w, 2) + rawI16(w, v) + end end if schema_type == "u32" then - return wU32 + return function(w: Writer, v: any) + ensureSpace(w, 4) + rawU32(w, v) + end end if schema_type == "i32" then - return wI32 + return function(w: Writer, v: any) + ensureSpace(w, 4) + rawI32(w, v) + end end if schema_type == "f16" then - return wF16 + return function(w: Writer, v: any) + ensureSpace(w, 2) + rawF16(w, v) + end end if schema_type == "f32" then - return wF32 + return function(w: Writer, v: any) + ensureSpace(w, 4) + rawF32(w, v) + end end if schema_type == "f64" then - return wF64 + return function(w: Writer, v: any) + ensureSpace(w, 8) + rawF64(w, v) + end end if schema_type == "boolean" then - return function(w, v) - wByte(w, v and 1 or 0) + return function(w: Writer, v: any) + ensureSpace(w, 1) + rawU8(w, v and 1 or 0) end end if schema_type == "string" then - return function(w, v) + return function(w: Writer, v: any) local len = #v - wVarUInt(w, len) - wString(w, v) + ensureSpace(w, 5 + len) + rawVarUInt(w, len) + rawString(w, v, len) end end if schema_type == "vector3" then - return function(w, v) - wF16(w, v.X) - wF16(w, v.Y) - wF16(w, v.Z) + return function(w: Writer, v: any) + ensureSpace(w, 6) + rawF16(w, v.X) + rawF16(w, v.Y) + rawF16(w, v.Z) end end if schema_type == "vector2" then - return function(w, v) - wF16(w, v.X) - wF16(w, v.Y) + return function(w: Writer, v: any) + ensureSpace(w, 4) + rawF16(w, v.X) + rawF16(w, v.Y) end end if schema_type == "cframe" then - return function(w, v) + return function(w: Writer, v: any) + ensureSpace(w, 19) -- 3xf32(12) + u8(1) + 3xi16(6) local pos = v.Position - local rx, ry, rz = v:ToOrientation() - wF32(w, pos.X) - wF32(w, pos.Y) - wF32(w, pos.Z) - wF16(w, rx) - wF16(w, ry) - wF16(w, rz) + local qi, q0, q1, q2 = compressCFrameRotation(v) + rawF32(w, pos.X) + rawF32(w, pos.Y) + rawF32(w, pos.Z) + rawU8(w, qi) + rawI16(w, q0) + rawI16(w, q1) + rawI16(w, q2) end end if schema_type == "color3" then - return function(w, v) - wByte(w, math.clamp(math.round(v.R * 255), 0, 255)) - wByte(w, math.clamp(math.round(v.G * 255), 0, 255)) - wByte(w, math.clamp(math.round(v.B * 255), 0, 255)) + return function(w: Writer, v: any) + ensureSpace(w, 3) + rawU8(w, math.clamp(math.round(v.R * 255), 0, 255)) + rawU8(w, math.clamp(math.round(v.G * 255), 0, 255)) + rawU8(w, math.clamp(math.round(v.B * 255), 0, 255)) end end if schema_type == "color3f16" then - return function(w, v) - wF16(w, math.clamp(v.R * 255, 0, 255)) - wF16(w, math.clamp(v.G * 255, 0, 255)) - wF16(w, math.clamp(v.B * 255, 0, 255)) + return function(w: Writer, v: any) + ensureSpace(w, 6) + rawF16(w, math.clamp(v.R * 255, 0, 255)) + rawF16(w, math.clamp(v.G * 255, 0, 255)) + rawF16(w, math.clamp(v.B * 255, 0, 255)) + end + end + + if schema_type == "udim2" then + return function(w: Writer, v: any) + ensureSpace(w, 16) + rawF32(w, v.X.Scale) + rawI32(w, v.X.Offset) + rawF32(w, v.Y.Scale) + rawI32(w, v.Y.Offset) + end + end + if schema_type == "udim" then + return function(w: Writer, v: any) + ensureSpace(w, 8) + rawF32(w, v.Scale) + rawI32(w, v.Offset) + end + end + if schema_type == "rect" then + return function(w: Writer, v: any) + ensureSpace(w, 16) + rawF32(w, v.Min.X) + rawF32(w, v.Min.Y) + rawF32(w, v.Max.X) + rawF32(w, v.Max.Y) + end + end + if schema_type == "numberrange" then + return function(w: Writer, v: any) + ensureSpace(w, 8) + rawF32(w, v.Min) + rawF32(w, v.Max) + end + end + if schema_type == "ray" then + return function(w: Writer, v: any) + ensureSpace(w, 24) + rawF32(w, v.Origin.X) + rawF32(w, v.Origin.Y) + rawF32(w, v.Origin.Z) + rawF32(w, v.Direction.X) + rawF32(w, v.Direction.Y) + rawF32(w, v.Direction.Z) + end + end + if schema_type == "colorsequence" then + return function(w: Writer, v: any) + local kps = v.Keypoints + local n = #kps + ensureSpace(w, 1 + n * 7) + rawU8(w, n) + for _, kp in kps do + rawF32(w, kp.Time) + local c = kp.Value + rawU8(w, math.clamp(math.round(c.R * 255), 0, 255)) + rawU8(w, math.clamp(math.round(c.G * 255), 0, 255)) + rawU8(w, math.clamp(math.round(c.B * 255), 0, 255)) + end + end + end + if schema_type == "numbersequence" then + return function(w: Writer, v: any) + local kps = v.Keypoints + local n = #kps + ensureSpace(w, 1 + n * 12) + rawU8(w, n) + for _, kp in kps do + rawF32(w, kp.Time) + rawF32(w, kp.Value) + rawF32(w, kp.Envelope) + end + end + end + if schema_type == "brickcolor" then + return function(w: Writer, v: any) + ensureSpace(w, 2) + rawU16(w, v.Number) + end + end + if schema_type == "buffer" then + return function(w: Writer, v: any) + local len = buffer.len(v) + ensureSpace(w, 5 + len) + rawVarUInt(w, len) + buffer.copy(w.buf, w.cursor, v, 0, len) + w.cursor += len + end + end + if schema_type == "vector3int16" then + return function(w: Writer, v: any) + ensureSpace(w, 6) + rawI16(w, v.X) + rawI16(w, v.Y) + rawI16(w, v.Z) + end + end + if schema_type == "vector2int16" then + return function(w: Writer, v: any) + ensureSpace(w, 4) + rawI16(w, v.X) + rawI16(w, v.Y) + end + end + if schema_type == "tweeninfo" then + return function(w: Writer, v: any) + ensureSpace(w, 15) + rawF32(w, v.Time) + rawU8(w, v.EasingStyle.Value) + rawU8(w, v.EasingDirection.Value) + rawI32(w, v.RepeatCount) + rawU8(w, v.Reverses and 1 or 0) + rawF32(w, v.DelayTime) + end + end + if schema_type == "physicalproperties" then + return function(w: Writer, v: any) + ensureSpace(w, 20) + rawF32(w, v.Density) + rawF32(w, v.Friction) + rawF32(w, v.Elasticity) + rawF32(w, v.FrictionWeight) + rawF32(w, v.ElasticityWeight) + end + end + if schema_type == "font" then + return function(w: Writer, v: any) + local family = v.Family + local familyLen = #family + ensureSpace(w, 5 + familyLen + 3) + rawVarUInt(w, familyLen) + rawString(w, family, familyLen) + rawU16(w, v.Weight.Value) + rawU8(w, v.Style.Value) + end + end + if schema_type == "datetime" then + return function(w: Writer, v: any) + ensureSpace(w, 8) + rawF64(w, v.UnixTimestampMillis) end end if schema_type == "instance" then - return function(w, v) + return function(w: Writer, v: any) table.insert(w.refs, v) - wVarUInt(w, #w.refs) + ensureSpace(w, 5) + rawVarUInt(w, #w.refs) end end if schema_type == "struct" then local fields = {} - local optionalIndices = {} -- tracks which fields are optional + local optionalIndices = {} for idx, field in ipairs(s.fields) do local isOpt = field.schema.type == "optional" @@ -1279,7 +1854,36 @@ local function compilePacker(s: SchemaType): (Writer, any) -> () local numOpt = #optionalIndices if numOpt == 0 then - return function(w, v) + -- Pre-calculate total fixed size for all-fixed structs + local totalFixed = 0 + local allFixed = true + for _, field in ipairs(s.fields) do + local size = SCHEMA_FIXED_SIZES[field.schema.type] + if size then + totalFixed += size + else + allFixed = false + break + end + end + + if allFixed and totalFixed > 0 then + return function(w: Writer, v: any) + if type(v) ~= "table" then + error(`Expected table for struct, got {typeof(v)}`) + end + ensureSpace(w, totalFixed) + for _, f in fields do + local val = v[f.key] + if val == nil then + error(`Schema Error: Missing required field '{f.key}'`) + end + f.packer(w, val) + end + end + end + + return function(w: Writer, v: any) if type(v) ~= "table" then error(`Expected table for struct, got {typeof(v)}`) end @@ -1295,12 +1899,11 @@ local function compilePacker(s: SchemaType): (Writer, any) -> () local maskBytes = math.ceil(numOpt / 8) - return function(w, v) + return function(w: Writer, v: any) if type(v) ~= "table" then error(`Expected table for struct, got {typeof(v)}`) end - -- write bitmask for optional fields ensureSpace(w, maskBytes) for i = 0, maskBytes - 1 do local byte = 0 @@ -1318,7 +1921,6 @@ local function compilePacker(s: SchemaType): (Writer, any) -> () w.cursor += 1 end - -- write field values for _, f in fields do local val = v[f.key] if f.optional then @@ -1338,15 +1940,12 @@ local function compilePacker(s: SchemaType): (Writer, any) -> () if schema_type == "array" then local itemSchema = s.item - -- bitpacking hacks if itemSchema.type == "boolean" then - return function(w, v) + return function(w: Writer, v: any) local len = #v - wVarUInt(w, len) - local numBytes = math.ceil(len / 8) - ensureSpace(w, numBytes) - + ensureSpace(w, 5 + numBytes) + rawVarUInt(w, len) for i = 0, numBytes - 1 do local byte = 0 for bit = 0, 7 do @@ -1364,9 +1963,8 @@ local function compilePacker(s: SchemaType): (Writer, any) -> () end end - -- regular array local itemPacker = compilePacker(itemSchema) - return function(w, v) + return function(w: Writer, v: any) if type(v) ~= "table" then error(`Expected table for array, got {typeof(v)}`) end @@ -1384,7 +1982,7 @@ local function compilePacker(s: SchemaType): (Writer, any) -> () if schema_type == "map" then local keyPacker = compilePacker(s.key) local valPacker = compilePacker(s.value) - return function(w, v) + return function(w: Writer, v: any) local count = 0 for _ in v do count += 1 @@ -1399,7 +1997,7 @@ local function compilePacker(s: SchemaType): (Writer, any) -> () if schema_type == "optional" then local itemPacker = compilePacker(s.item) - return function(w, v) + return function(w: Writer, v: any) if v == nil then wByte(w, 0) else @@ -1409,7 +2007,9 @@ local function compilePacker(s: SchemaType): (Writer, any) -> () end end - return function() return end + return function() + return + end end local function compileReader(s: SchemaType): (buffer, number, { any }?) -> (any, number) @@ -1420,110 +2020,247 @@ local function compileReader(s: SchemaType): (buffer, number, { any }?) -> (any, end if schema_type == "u8" then - return function(b, c) + return function(b: buffer, c: number) return buffer.readu8(b, c), c + 1 end end if schema_type == "i8" then - return function(b, c) + return function(b: buffer, c: number) return buffer.readi8(b, c), c + 1 end end if schema_type == "u16" then - return function(b, c) + return function(b: buffer, c: number) return buffer.readu16(b, c), c + 2 end end if schema_type == "i16" then - return function(b, c) + return function(b: buffer, c: number) return buffer.readi16(b, c), c + 2 end end if schema_type == "u32" then - return function(b, c) + return function(b: buffer, c: number) return buffer.readu32(b, c), c + 4 end end if schema_type == "i32" then - return function(b, c) + return function(b: buffer, c: number) return buffer.readi32(b, c), c + 4 end end if schema_type == "f16" then - return function(b, c) + return function(b: buffer, c: number) return readF16(b, c), c + 2 end end if schema_type == "f32" then - return function(b, c) + return function(b: buffer, c: number) return buffer.readf32(b, c), c + 4 end end if schema_type == "f64" then - return function(b, c) + return function(b: buffer, c: number) return buffer.readf64(b, c), c + 8 end end if schema_type == "boolean" then - return function(b, c) + return function(b: buffer, c: number) return buffer.readu8(b, c) ~= 0, c + 1 end end if schema_type == "string" then - return function(b, c) + return function(b: buffer, c: number) local len len, c = readVarUInt(b, c) return buffer.readstring(b, c, len), c + len end end if schema_type == "vector3" then - return function(b, c) + return function(b: buffer, c: number) local x = readF16(b, c) local y = readF16(b, c + 2) local z = readF16(b, c + 4) return Vector3.new(x, y, z), c + 6 end end - if schema_type == "vector2" then - return function(b, c) + return function(b: buffer, c: number) local x = readF16(b, c) local y = readF16(b, c + 2) return Vector2.new(x, y), c + 4 end end - if schema_type == "color3" then - return function(b, c) + return function(b: buffer, c: number) local r = buffer.readu8(b, c) local g = buffer.readu8(b, c + 1) local bVal = buffer.readu8(b, c + 2) return Color3.fromRGB(r, g, bVal), c + 3 end end - if schema_type == "color3f16" then - return function(b, c) + return function(b: buffer, c: number) local r = readF16(b, c) local g = readF16(b, c + 2) local bVal = readF16(b, c + 4) return Color3.fromRGB(r, g, bVal), c + 6 end end - if schema_type == "cframe" then - return function(b, c) + return function(b: buffer, c: number) local px = buffer.readf32(b, c) local py = buffer.readf32(b, c + 4) local pz = buffer.readf32(b, c + 8) - local rx = readF16(b, c + 12) - local ry = readF16(b, c + 14) - local rz = readF16(b, c + 16) - return CFrame.new(px, py, pz) * CFrame.fromOrientation(rx, ry, rz), c + 18 + local qi = buffer.readu8(b, c + 12) + local q0 = buffer.readi16(b, c + 13) + local q1 = buffer.readi16(b, c + 15) + local q2 = buffer.readi16(b, c + 17) + local qx, qy, qz, qw = decompressCFrameRotation(qi, q0, q1, q2) + return CFrame.new(px, py, pz, qx, qy, qz, qw), c + 19 + end + end + + if schema_type == "udim2" then + return function(b: buffer, c: number) + return UDim2.new( + buffer.readf32(b, c), + buffer.readi32(b, c + 4), + buffer.readf32(b, c + 8), + buffer.readi32(b, c + 12) + ), + c + 16 + end + end + if schema_type == "udim" then + return function(b: buffer, c: number) + return UDim.new(buffer.readf32(b, c), buffer.readi32(b, c + 4)), c + 8 + end + end + if schema_type == "rect" then + return function(b: buffer, c: number) + return Rect.new( + buffer.readf32(b, c), + buffer.readf32(b, c + 4), + buffer.readf32(b, c + 8), + buffer.readf32(b, c + 12) + ), + c + 16 + end + end + if schema_type == "numberrange" then + return function(b: buffer, c: number) + return NumberRange.new(buffer.readf32(b, c), buffer.readf32(b, c + 4)), c + 8 + end + end + if schema_type == "ray" then + return function(b: buffer, c: number) + return Ray.new( + Vector3.new(buffer.readf32(b, c), buffer.readf32(b, c + 4), buffer.readf32(b, c + 8)), + Vector3.new(buffer.readf32(b, c + 12), buffer.readf32(b, c + 16), buffer.readf32(b, c + 20)) + ), + c + 24 + end + end + if schema_type == "colorsequence" then + return function(b: buffer, c: number) + local n = buffer.readu8(b, c) + c += 1 + local kps = table.create(n) + for i = 1, n do + local t = buffer.readf32(b, c) + kps[i] = ColorSequenceKeypoint.new( + t, + Color3.fromRGB(buffer.readu8(b, c + 4), buffer.readu8(b, c + 5), buffer.readu8(b, c + 6)) + ) + c += 7 + end + return ColorSequence.new(kps), c + end + end + if schema_type == "numbersequence" then + return function(b: buffer, c: number) + local n = buffer.readu8(b, c) + c += 1 + local kps = table.create(n) + for i = 1, n do + kps[i] = + NumberSequenceKeypoint.new(buffer.readf32(b, c), buffer.readf32(b, c + 4), buffer.readf32(b, c + 8)) + c += 12 + end + return NumberSequence.new(kps), c + end + end + if schema_type == "brickcolor" then + return function(b: buffer, c: number) + return BrickColor.new(buffer.readu16(b, c)), c + 2 + end + end + if schema_type == "buffer" then + return function(b: buffer, c: number) + local len + len, c = readVarUInt(b, c) + local out = buffer.create(len) + buffer.copy(out, 0, b, c, len) + return out, c + len + end + end + if schema_type == "vector3int16" then + return function(b: buffer, c: number) + return Vector3int16.new(buffer.readi16(b, c), buffer.readi16(b, c + 2), buffer.readi16(b, c + 4)), c + 6 + end + end + if schema_type == "vector2int16" then + return function(b: buffer, c: number) + return Vector2int16.new(buffer.readi16(b, c), buffer.readi16(b, c + 2)), c + 4 + end + end + if schema_type == "tweeninfo" then + return function(b: buffer, c: number) + return TweenInfo.new( + buffer.readf32(b, c), + Enum.EasingStyle:FromValue(buffer.readu8(b, c + 4)), + Enum.EasingDirection:FromValue(buffer.readu8(b, c + 5)), + buffer.readi32(b, c + 6), + buffer.readu8(b, c + 10) ~= 0, + buffer.readf32(b, c + 11) + ), + c + 15 + end + end + if schema_type == "physicalproperties" then + return function(b: buffer, c: number) + return PhysicalProperties.new( + buffer.readf32(b, c), + buffer.readf32(b, c + 4), + buffer.readf32(b, c + 8), + buffer.readf32(b, c + 12), + buffer.readf32(b, c + 16) + ), + c + 20 + end + end + if schema_type == "font" then + return function(b: buffer, c: number) + local familyLen + familyLen, c = readVarUInt(b, c) + local family = buffer.readstring(b, c, familyLen) + c += familyLen + return Font.new( + family, + Enum.FontWeight:FromValue(buffer.readu16(b, c)), + Enum.FontStyle:FromValue(buffer.readu8(b, c + 2)) + ), + c + 3 + end + end + if schema_type == "datetime" then + return function(b: buffer, c: number) + return DateTime.fromUnixTimestampMillis(buffer.readf64(b, c)), c + 8 end end if schema_type == "instance" then - return function(b, c, refs) + return function(b: buffer, c: number, refs: { any }?) local idx idx, c = readVarUInt(b, c) return refs and refs[idx] or nil, c @@ -1549,7 +2286,7 @@ local function compileReader(s: SchemaType): (buffer, number, { any }?) -> (any, local numOpt = #optionalIndices if numOpt == 0 then - return function(b, c, refs) + return function(b: buffer, c: number, refs: { any }?) local obj = {} for _, f in fields do obj[f.key], c = f.reader(b, c, refs) @@ -1560,8 +2297,7 @@ local function compileReader(s: SchemaType): (buffer, number, { any }?) -> (any, local maskBytes = math.ceil(numOpt / 8) - return function(b, c, refs) - -- read bitmask + return function(b: buffer, c: number, refs: { any }?) local present = {} for i = 0, maskBytes - 1 do local byte = buffer.readu8(b, c + i) @@ -1575,7 +2311,6 @@ local function compileReader(s: SchemaType): (buffer, number, { any }?) -> (any, end c += maskBytes - -- read fields local obj = {} local optIdx = 0 for _, f in fields do @@ -1596,13 +2331,11 @@ local function compileReader(s: SchemaType): (buffer, number, { any }?) -> (any, if schema_type == "array" then local itemSchema = s.item - -- bitpacking hacks if itemSchema.type == "boolean" then - return function(b, c, refs) + return function(b: buffer, c: number, _: { any }?) local len len, c = readVarUInt(b, c) local arr = table.create(len) - local numBytes = math.ceil(len / 8) for i = 0, numBytes - 1 do local byte = buffer.readu8(b, c + i) @@ -1618,9 +2351,8 @@ local function compileReader(s: SchemaType): (buffer, number, { any }?) -> (any, end end - -- regular array local itemReader = compileReader(itemSchema) - return function(b, c, refs) + return function(b: buffer, c: number, refs: { any }?) local len len, c = readVarUInt(b, c) local arr = table.create(len) @@ -1634,7 +2366,7 @@ local function compileReader(s: SchemaType): (buffer, number, { any }?) -> (any, if schema_type == "map" then local keyReader = compileReader(s.key) local valReader = compileReader(s.value) - return function(b, c, refs) + return function(b: buffer, c: number, refs: { any }?) local count count, c = readVarUInt(b, c) local map = {} @@ -1650,7 +2382,7 @@ local function compileReader(s: SchemaType): (buffer, number, { any }?) -> (any, if schema_type == "optional" then local itemReader = compileReader(s.item) - return function(b, c, refs) + return function(b: buffer, c: number, refs: { any }?) local exists = buffer.readu8(b, c) ~= 0 c += 1 if exists then @@ -1661,7 +2393,7 @@ local function compileReader(s: SchemaType): (buffer, number, { any }?) -> (any, end end - return function(_, c) + return function(_: buffer, c: number) return nil, c end end