Example
Live Streaming
Player movement replication at 20 Hz using Stream's binary channels. Clients send their own CFrame and velocity. The server relays each update to everyone else via channel:SendExcept. Clients interpolate smoothly between received frames.
How it works
1
Client sends
{ userId, cf, vel, anim, health } at 20 Hz via channel:Send()2
Server receives via
channel:On(function(data, player)), overwrites data.userId with the real sender3
Server calls
channel:SendExcept(player, data) — one relay pass, no extra module needed4
Other clients receive via
channel:On(function(data, _)), push frame to interpolation buffer5
Client interpolates between buffered frames on
RunService.HeartbeatShared schema
Define channels in a shared module required by both the server and the client. Both sides must see the same Stream.Channel() calls before Stream.Init() is called.
-- ReplicatedStorage/StreamChannels.luau
local RoExpress = require(game.ReplicatedStorage.RoExpress)
local Stream = RoExpress("Stream")
local stateSchema = Stream.Schema.New({
{ "userId", "f64" }, -- 8 B: set server-side
{ "cf", "CFrame" }, -- 28 B: full rotation
{ "vel", "Vector3" }, -- 12 B: linear velocity
{ "anim", "u8" }, -- 1 B: animation state ID
{ "health", "u8" }, -- 1 B: 0–255
})
local playerState = Stream.Channel("playerState", stateSchema, { maxRate = 30 })
Stream.Init()
return { playerState = playerState }
Server: MovementServer.luau
local channels = require(game.ReplicatedStorage.StreamChannels)
local ch = channels.playerState
ch:On(function(data, player)
-- Override userId so clients can't claim to be someone else
data.userId = player.UserId
-- Relay to every other client in one call
ch:SendExcept(player, data)
end)
Client: MovementClient.luau
local RunService = game:GetService("RunService")
local Players = game:GetService("Players")
local LocalPlayer = Players.LocalPlayer
local channels = require(game.ReplicatedStorage.StreamChannels)
local ch = channels.playerState
local buffers = {} -- { [userId] = { cf, vel, t } }
-- Send own state at 20 Hz
local accum = 0
RunService.Heartbeat:Connect(function(dt)
accum += dt
if accum < 0.05 then return end
accum = 0
local char = LocalPlayer.Character
if not char then return end
local hrp = char:FindFirstChild("HumanoidRootPart")
if not hrp then return end
ch:Send({
userId = LocalPlayer.UserId, -- server overwrites this
cf = hrp.CFrame,
vel = hrp.AssemblyLinearVelocity,
anim = getCurrentAnim(),
health = math.floor(char.Humanoid.Health),
})
end)
-- Receive other players' state
-- Note: second arg is nil on the client — use _ to signal it's intentionally ignored
ch:On(function(data, _)
buffers[data.userId] = { cf = data.cf, vel = data.vel, t = os.clock() }
end)
-- Interpolate on every frame
RunService.Heartbeat:Connect(function(dt)
for userId, buf in buffers do
local player = Players:GetPlayerByUserId(userId)
if player and player.Character then
smoothMove(player.Character, buf.cf, buf.vel, dt)
end
end
end)
See also
Stream | schema, channels, delta compression · FPS Guide | lag compensation with history buffer · Stream: Server Side | all send methods, rate limiting · Stream: Client Side | throttling, unsubscribing