Forest Logo
search
package_2

vsb

By @elentium

Roblox

Mirrored

VoidSentry

License Wally Documentation

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

  1. Download the latest release
  2. Place the VoidSentry folder in your ReplicatedStorage.Packages
  3. 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, or nil for 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 (reserves offset bytes 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 (skips offset bytes 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 (reserves offset bytes 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 level
  • offset (optional): Starting byte offset (skips offset bytes at the beginning, e.g., custom metadata)
  • buffer: The buffer to deserialize

Returns: All values that were serialized

Available Types

Numeric Types

TypeDescriptionSizeRange
Types.int8Signed 8-bit integer1 byte-128 to 127
Types.uInt8Unsigned 8-bit integer1 byte0 to 255
Types.int16Signed 16-bit integer2 bytes-32,768 to 32,767
Types.uInt16Unsigned 16-bit integer2 bytes0 to 65,535
Types.int32Signed 32-bit integer4 bytes-2³¹ to 2³¹-1
Types.uInt32Unsigned 32-bit integer4 bytes0 to 2³²-1
Types.float3232-bit floating point4 bytesIEEE 754 single precision
Types.float6464-bit floating point8 bytesIEEE 754 double precision
Types.float2424-bit floating point3 bytesReduced precision (custom format)

String Types

TypeDescriptionMax Size
Types.stringStandard string65,535 + 2 bytes (16-bit length prefix)
Types.stringTinyCompact string255 + 1 byte (8-bit length prefix)
Types.stringFixedFixed length stringUser-defined length
Types.stringNullTerminatedNull-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

TypeDescriptionSize
Types.vector3Full precision Vector312 bytes
Types.vector3F24Reduced precision Vector39 bytes
Types.vector3Int16Integer Vector36 bytes
Types.vector2Full precision Vector28 bytes
Types.vector2F24Reduced precision Vector26 bytes
Types.vector2Int16Integer Vector24 bytes
Types.vectorFull precision vector (Luau native vector type)12 bytes
Types.vectorF24Reduced precision vector9 bytes
Types.vectorInt16Integer vector6 bytes
Types.cframeFull precision CFrame48 bytes
Types.cframeQQuaternion CFrame28 bytes
Types.color3RGB color3 bytes
Types.enumEnum value (requires Enum parameter)2 bytes
Types.instanceRoblox 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, CastShadow
  • MeshPart - 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

  1. Use Static Serializer When Possible: It's significantly faster than dynamic serialization
  2. Choose Appropriate Types: Use Vector3F24 instead of Vector3 if you don't need full precision
  3. Compression Trade-offs: Compression reduces bandwidth but increases CPU usage
  4. Batch Serialization: Serialize multiple values at once rather than separately
  5. Reuse Serializers: Create serializer objects once and reuse them
  6. Use Tiny Variants: StringTiny and ArrayTiny save bytes for small data
  7. Select Appropriate Numeric Types: Use Int16 or Int8 when 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.1.4

License

Apache-2.0

check_circle

Safe for commercial use

infoModified 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.