Start typing to search packages!
voidsentry
By @elentium
Roblox
Mirrored
A high-performance buffer serialization library for Roblox
VoidSentry is a powerful, low-level buffer serializer designed for efficient data transmission in Roblox games. It provides both static (schema-based) and dynamic (schemaless) serialization with support for a wide range of data types.
Features
-
Two Serialization Modes
- Static Serializer: Schema-based serialization for maximum performance
- Dynamic Serializer: Flexible schemaless serialization with type inference
-
Rich Type Support: 30+ built-in types including primitives, Roblox types, and complex data structures
-
Optional Compression: Built-in Zstd compression support for reduced bandwidth
-
Type Safety: Full strict-mode Luau type annotations for better IDE support
-
High Performance: Native optimizations and efficient buffer operations
-
Zero Dependencies: Standalone library with no external requirements
Installation
Using Wally
Add VoidSentry to your wally.toml:
[dependencies]
VoidSentry = "elentium/voidsentry@0.0.8"
Then run:
wally install
Manual Installation
- Download the latest release
- Place the
VoidSentryfolder in yourReplicatedStorage.Packages - Require it in your scripts
Quick Start
Static Serializer (Recommended for Performance)
The static serializer requires you to define a schema upfront, but offers the best performance:
--!strict
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local VoidSentry = require(ReplicatedStorage.Packages.VoidSentry)
local Types = VoidSentry.Types
-- Create a serializer with a fixed schema
local Serializer = VoidSentry.Static.new(
nil, -- No compression
Types.int32,
Types.string,
Types.struct({
Hello = Types.string,
World = Types.int32,
})
)
-- Serialize data
local b = Serializer:serialize(
nil, -- No offset
42,
"Hello, world!",
{
Hello = "hi",
World = 999,
}
)
print("Buffer size:", buffer.len(b)) -- 27 bytes
-- Deserialize data
local int, str, struct = Serializer:deserialize(nil, b)
print(int, str, struct) -- 42, "Hello, world!", {Hello = "hi", World = 999}
Dynamic Serializer (Flexible)
The dynamic serializer automatically infers types, offering more flexibility at the cost of performance:
--!strict
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local VoidSentry = require(ReplicatedStorage.Packages.VoidSentry)
local Dynamic = VoidSentry.Dynamic
-- No schema required!
local buffer = Dynamic.serialize(
nil, -- No compression
nil, -- No offset
42,
"Dynamic serialization",
Vector3.new(10, 20, 30),
true
)
-- Deserialize automatically
local int, str, vec, bool = Dynamic.deserialize(nil, nil, buffer)
print(int, str, vec, bool)
API Reference
Static Serializer
VoidSentry.Static.new(compressionLevel: number?, ...TypeNode): StaticObject
Creates a new static serializer with a fixed schema.
Parameters:
compressionLevel(optional): Zstd compression level (-7 to 22, ornilfor no compression)...TypeNode: Variable number of type nodes defining the schema
Returns: A StaticObject with serialize and deserialize methods
StaticObject:serialize(offset: number?, ...values): buffer
Serializes data according to the schema.
Parameters:
offset(optional): Starting byte offset in the buffer (reservesoffsetbytes at the beginning for custom metadata)...values: Values matching the schema types
Returns: A buffer containing the serialized data
StaticObject:deserialize(offset: number?, buffer: buffer): ...values
Deserializes data from a buffer.
Parameters:
offset(optional): Starting byte offset to read from (skipsoffsetbytes at the beginning, e.g., custom metadata)buffer: The buffer to deserialize
Returns: One or multiple values matching the schema
Dynamic Serializer
VoidSentry.Dynamic.serialize(compressionLevel: number?, offset: number?, ...values): buffer
Serializes data with automatic type inference.
Parameters:
compressionLevel(optional): Zstd compression level (-7 to 22)offset(optional): Starting byte offset (reservesoffsetbytes at the beginning for custom metadata)...values: Any serializable values
Returns: A buffer with type information and data
VoidSentry.Dynamic.deserialize(compressionLevel: number?, offset: number?, buffer: buffer): ...values
Deserializes data with embedded type information.
Parameters:
compressionLevel(optional): Must match serialization compression leveloffset(optional): Starting byte offset (skipsoffsetbytes at the beginning, e.g., custom metadata)buffer: The buffer to deserialize
Returns: All values that were serialized
Available Types
Numeric Types
| Type | Description | Size | Range |
|---|---|---|---|
Types.int8 | Signed 8-bit integer | 1 byte | -128 to 127 |
Types.uInt8 | Unsigned 8-bit integer | 1 byte | 0 to 255 |
Types.int16 | Signed 16-bit integer | 2 bytes | -32,768 to 32,767 |
Types.uInt16 | Unsigned 16-bit integer | 2 bytes | 0 to 65,535 |
Types.int32 | Signed 32-bit integer | 4 bytes | -2³¹ to 2³¹-1 |
Types.uInt32 | Unsigned 32-bit integer | 4 bytes | 0 to 2³²-1 |
Types.float32 | 32-bit floating point | 4 bytes | IEEE 754 single precision |
Types.float64 | 64-bit floating point | 8 bytes | IEEE 754 double precision |
Types.float24 | 24-bit floating point | 3 bytes | Reduced precision (custom format) |
String Types
| Type | Description | Max Size |
|---|---|---|
Types.string | Standard string | 65,535 + 2 bytes (16-bit length prefix) |
Types.stringTiny | Compact string | 255 + 1 byte (8-bit length prefix) |
Types.stringFixed | Fixed length string | User-defined length |
Types.stringNullTerminated | Null-terminated string (C-style) | Unlimited |
Boolean & Special Types
Types.bool- Boolean value (1 byte)Types.void- Empty table (0 bytes)Types.nothing- Nothing value (0 bytes)Types.any- Any type (dynamic, includes type information)
Roblox Types
| Type | Description | Size |
|---|---|---|
Types.vector3 | Full precision Vector3 | 12 bytes |
Types.vector3F24 | Reduced precision Vector3 | 9 bytes |
Types.vector3Int16 | Integer Vector3 | 6 bytes |
Types.vector2 | Full precision Vector2 | 8 bytes |
Types.vector2F24 | Reduced precision Vector2 | 6 bytes |
Types.vector2Int16 | Integer Vector2 | 4 bytes |
Types.vector | Full precision vector (Luau native vector type) | 12 bytes |
Types.vectorF24 | Reduced precision vector | 9 bytes |
Types.vectorInt16 | Integer vector | 6 bytes |
Types.cframe | Full precision CFrame | 48 bytes |
Types.cframeQ | Quaternion CFrame | 28 bytes |
Types.color3 | RGB color | 3 bytes |
Types.enum | Enum value (requires Enum parameter) | 2 bytes |
Types.instance | Roblox Instance (requires ClassName) | Variable |
Collection Types
Types.array(elementType)
Creates a variable-size array type with 16-bit length prefix (max 65,535 elements).
local NumberArray = Types.array(Types.int32)
local VectorArray = Types.array(Types.vector3)
Types.arrayTiny(elementType)
Creates a compact array type with 8-bit length prefix (max 255 elements).
local SmallArray = Types.arrayTiny(Types.float32)
Types.arrayFixed(elementType, length)
Creates a fixed-size array with no length prefix.
local FixedArray = Types.arrayFixed(Types.int32, 10) -- Exactly 10 elements
Types.map(keyType, valueType)
Creates a map/dictionary type with 16-bit length prefix (max 65,535 entries).
local StringToIntMap = Types.map(Types.string, Types.int32)
local IdToPlayerMap = Types.map(Types.int32, Types.string)
Types.mapFixed(keyType, valueType, length)
Creates a fixed-size map with no length prefix.
local FixedMap = Types.mapFixed(Types.string, Types.bool, 10) -- Exactly 10 entries
Types.struct(schema)
Creates a fixed structure with named fields.
local PlayerData = Types.struct({
Name = Types.string,
Level = Types.int32,
Position = Types.vector3,
Inventory = Types.array(Types.string)
})
Types.optional(type)
Makes a type optional (nullable).
local OptionalString = Types.optional(Types.string)
local OptionalInt = Types.optional(Types.int32)
Types.boolPacked
Creates an array of exactly 8 booleans, each taking 1 bit of memory (totals 1 byte).
local PackedBools = Types.boolPacked
-- Serialize: {true, false, true, false, true, false, true, false}
Types.bits._8(count), Types.bits._16(count), Types.bits._32(count)
Creates a fixed-size array of bit values (8-bit, 16-bit, or 32-bit unsigned integers).
local Bits8 = Types.bits._8(4) -- Array of 4 bytes (8-bit values)
local Bits16 = Types.bits._16(2) -- Array of 2 shorts (16-bit values)
local Bits32 = Types.bits._32(1) -- Array of 1 int (32-bit values)
Types.enum(EnumType)
Serializes an EnumItem value. Requires the Enum type as a parameter.
local MaterialEnum = Types.enum(Enum.Material)
local HumanoidStateEnum = Types.enum(Enum.HumanoidStateType)
-- Serialize
local b = Serializer:serialize(nil, Enum.Material.Plastic)
-- Deserialize
local material = Serializer:deserialize(nil, b)
Types.instance(ClassName)
Serializes a Roblox Instance by its properties. Requires pre-defined serialization schemas for each class.
Currently supported classes:
Part- Serializes Name, CFrame, Color, Transparency, Material, CanCollide, CanTouch, CanQuery, CastShadowMeshPart- Same properties as Part
-- Static serialization
local PartSerializer = VoidSentry.Static.new(nil, Types.instance("Part"))
local part = Instance.new("Part")
part.Name = "MyPart"
part.CFrame = CFrame.new(10, 5, 0)
part.Color = Color3.new(1, 0, 0)
part.Transparency = 0.5
local b = PartSerializer:serialize(nil, part)
-- Deserialize creates a new Instance with the serialized properties
local deserializedPart = PartSerializer:deserialize(nil, b)
Dynamic serialization also supports Instance types:
local part = workspace.SomePart
local b = VoidSentry.Dynamic.serialize(nil, nil, part)
local deserializedPart = VoidSentry.Dynamic.deserialize(nil, nil, b)
Note: The Instance type creates new instances on deserialization. It does not preserve parent-child relationships or references to other instances. For custom classes, you can extend the serialization data in
src/sections/types/instance/serialize_data.luau.
Usage Examples
Example 1: Player Data Replication
local PlayerDataSerializer = VoidSentry.Static.new(
5, -- Compression level 5
Types.struct({
UserId = Types.int32,
Username = Types.string,
Position = Types.vector3,
Health = Types.float32,
Inventory = Types.array(Types.string),
Level = Types.int32,
Premium = Types.bool
})
)
local data = {
UserId = 123456,
Username = "Player123",
Position = Vector3.new(100, 50, 200),
Health = 75.5,
Inventory = {"Sword", "Shield", "Potion"},
Level = 42,
Premium = true
}
local b = PlayerDataSerializer:serialize(nil, data)
-- Send buffer over RemoteEvent
Example 2: Game State Snapshot
local GameStateSerializer = VoidSentry.Static.new(
nil, -- No compression for speed
Types.int32, -- Timestamp
Types.array(Types.struct({
PlayerId = Types.int32,
Position = Types.vector3F24, -- Reduced precision
Rotation = Types.cframeQ, -- Compact CFrame
})),
Types.map(Types.string, Types.int32) -- Entity counts
)
local timestamp = os.time()
local players = {
{PlayerId = 1, Position = Vector3.new(0, 5, 0), Rotation = CFrame.new()},
{PlayerId = 2, Position = Vector3.new(10, 5, 10), Rotation = CFrame.new()},
}
local entityCounts = {
Zombies = 15,
Treasure = 3,
}
local b = GameStateSerializer:serialize(nil, timestamp, players, entityCounts)
Example 3: Dynamic Configuration
-- When you don't know the data structure ahead of time
local config = {
maxPlayers = 50,
mapName = "Desert Arena",
spawnPoint = Vector3.new(0, 10, 0),
enablePvP = true,
difficulty = 2.5
}
local b = VoidSentry.Dynamic.serialize(10, nil, config)
-- Later, deserialize
local loadedConfig = VoidSentry.Dynamic.deserialize(10, nil, b)
Performance Tips
- Use Static Serializer When Possible: It's significantly faster than dynamic serialization
- Choose Appropriate Types: Use
Vector3F24instead ofVector3if you don't need full precision - Compression Trade-offs: Compression reduces bandwidth but increases CPU usage
- Batch Serialization: Serialize multiple values at once rather than separately
- Reuse Serializers: Create serializer objects once and reuse them
- Use Tiny Variants:
StringTinyandArrayTinysave bytes for small data - Select Appropriate Numeric Types: Use
Int16orInt8when values fit in smaller ranges
Advanced Features
Custom Offsets
You can specify a starting offset to write data at specific positions:
-- Write at offset 20
local b = Serializer:serialize(20, myData)
print(buffer.len(b)) -- Buffer has 20 extra bytes at the beginning for custom use
Compression Levels
Zstd compression levels (-7 to 22):
- -7 to 3: Fast compression, lower ratio
- 5 to 10: Balanced (recommended)
- 15 to 22: Maximum compression, slower
Contributing
Contributions are welcome! Please feel free to submit issues or pull requests.
License
This project is licensed under the Apache 2.0 License. See the LICENSE file for details.
Author
IAMNOTULTRA3 (a.k.a elentium/elite)
Support
For questions, issues, or feature requests, please open an issue on the repository or contact the author.
Package Details
Install command (Click to copy)
Version
0.0.8
License
Apache-2.0
Safe for commercial use
Modified files must carry a notice of changes. If the package ships a NOTICE file, its attributions must be preserved.
Automated license review — not legal advice.
