Stream → Channels

Channels

Define typed binary channels with Stream.Channel(), then call Stream.Init() once after all channels are registered. Channel IDs are derived from a djb2 hash of the channel name — there is no ordering dependency between Channel() calls.

Stream.Channel()

Stream.Channel(name: string, schema: Schema, options?: Options) → Channel

Defines a typed binary channel. Returns a Channel object used to send and subscribe. Must be called before Stream.Init() on both server and client.

OptionTypeDefaultDescription
reliableboolean?falseUse RemoteEvent instead of UnreliableRemoteEvent
maxRatenumber?Max incoming fires/sec per player (server-side only)
onDropfn?Called when a packet is rate-limited and discarded
deltaIntervalnumber?10Force a full resync every N delta packets
Order matters. Stream.Channel() must be called before Stream.Init(). Define all channels first, then call Init once on both server and client.

Stream.Init()

Stream.Init() → ()

Finalises all registered channels and connects the underlying remotes (StreamUnreliable and StreamReliable in ReplicatedStorage/RoExpressStream/). Call exactly once — after all Stream.Channel() definitions — on both server and client.

Channel IDs

Each channel gets a numeric ID from a djb2 hash of its name, stored in bits 0–14 of the wire channelId field. Bit 15 is the delta flag. Because IDs are name-derived, registration order does not matter — the server and client will always agree on the mapping.

channel:On()

channel:On(fn: (data: table, sender: Player?) → ()) → unsubscribe: () → ()

Registers a persistent listener. sender is a Player on the server and nil on the client. Returns an unsubscribe function — call it at any time to stop receiving.

channel:Once()

channel:Once(fn: (data: table, sender: Player?) → ()) → unsubscribe: () → ()

Same as On but automatically unsubscribes after the first fire. The returned unsubscribe function can still be called early to cancel before the first packet arrives.

-- Persistent subscription | fires every time
local unsub = channel:On(function(data, sender)
    -- sender is a Player on the server, nil on the client
    print(data.pos)
end)

-- One-shot | auto-unsubscribes after first fire
local unsub2 = channel:Once(function(data, sender)
    print("first packet from", sender)
end)

unsub()   -- cancel the persistent listener
unsub2()  -- cancel before the first packet arrives

Other channel methods

CallDescription
channel:Destroy()Cleans up a single channel's subscriptions and rate-limit state
Stream.GetChannel(name)Returns the Channel for name, or nil if not defined
Stream.GetChannels()Returns the full channel registry table (read-only)
Stream.Destroy()Disconnects all remotes and clears state — useful for tests

Example — defining and using a channel

-- shared.luau — required on both server and client
local RoExpress = require(game.ReplicatedStorage.RoExpress)
local Stream    = RoExpress("Stream")

local moveSchema = Stream.Schema.New({
    { "pos",   "Vector3" },
    { "vel",   "Vector3" },
    { "state", { "flags", "jumping", "sprinting" } },
})

local move = Stream.Channel("playerMove", moveSchema, {
    maxRate       = 30,   -- server drops client fires above 30/s
    deltaInterval = 15,   -- force full resync every 15 delta packets
})

Stream.Init()  -- call once, after all Channel() definitions

return { move = move }

-- server.luau
local channels = require(script.Parent.shared)

channels.move:On(function(data, player)
    print(player.Name, data.pos, data.state.jumping)
end)

-- client.luau
local channels = require(script.Parent.shared)
local hrp      = LocalPlayer.Character.HumanoidRootPart

RunService.Heartbeat:Connect(function()
    channels.move:Send({
        pos   = hrp.Position,
        vel   = hrp.AssemblyLinearVelocity,
        state = { jumping = isJumping, sprinting = isSprinting },
    })
end)

See also

← Stream  ·  Schema | building the schema passed to Channel()  ·  Server Side | SendTo, Broadcast, delta methods  ·  Client Side | channel:Send and throttling