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()
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.
| Option | Type | Default | Description |
|---|---|---|---|
reliable | boolean? | false | Use RemoteEvent instead of UnreliableRemoteEvent |
maxRate | number? | — | Max incoming fires/sec per player (server-side only) |
onDrop | fn? | — | Called when a packet is rate-limited and discarded |
deltaInterval | number? | 10 | Force a full resync every N delta packets |
Stream.Channel() must be called before Stream.Init(). Define all channels first, then call Init once on both server and client.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()
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()
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
| Call | Description |
|---|---|
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