VoidSentry2
Efficient SerDes Library for Roblox. Successor
to VoidSentry.
What it is
Sending Luau tables or JSON over remotes is convenient but costly: payloads grow quickly and
every value carries type metadata on the wire. VoidSentry2 converts the same data into a
compact, typed byte layout using the Luau buffer type, so
the bytes you ship match the shape of the data you actually store.
Two usage modes cover most cases. Static: you declare the exact shape once as a type node and reuse it to serialize and deserialize — smallest size, lowest CPU, predictable layout. Dynamic: you pass a variable list of values and the library tags each with a 1-byte type id so the receiver can decode them without a shared schema.
Highlights
- Static (schema) and dynamic (tagged) serialization in one module.
- 45+ built-in types: fixed-width integers and floats (including 16/24-bit variants),
strings, vectors,
CFrame(matrix and quaternion, full and compressed),Color3,EnumItem, arrays, maps, structs,deltaStruct/deltaStruct16,optional,union,any,boolPacked, plusinstance(by-reference) andserInstance(by-snapshot). - Optional Zstd compression through Roblox
EncodingService. - Configurable: buffer cap, strict vs lax failure mode, dynamic inference for instances and vectors, instance-id attribute name.
- Fully typed Luau API so generics, autocomplete, and refactors work through the call sites.
- Manual cursor API (
writeStart/writeFinish,readStart/readFinish) if you want to interleave VoidSentry2 writes with your own bytes.
Quick start
Static
--!strict
local VS = require(path.to.VoidSentry2)
local T = VS.types
local static = VS.static
local row = T.struct({
n = T.int32,
s = T.string,
})
local buf = static.serialize(row, { n = 42, s = "hi" }, nil, nil)
local out = static.deserialize(row, buf, nil, nil)
Dynamic
Each value you pass becomes one tagged chunk in the buffer. The receiver reads the same number of values in the same order.
local VS = require(path.to.VoidSentry2)
local dynamic = VS.dynamic
-- Example: send a name, a user id, a position, and whether sprint is on — no struct definition.
local playerId = 9001
local targetPos = Vector3.new(10, 0, -3)
local buf = dynamic.serialize(nil, nil, "MovePlayer", playerId, targetPos, true)
if not buf then return end
local name, id, pos, sprinting = dynamic.deserialize(nil, nil, buf)
-- name == "MovePlayer", id == 9001, pos == targetPos, sprinting == true