Skip to content

Replicate state to clients

server
Twill.Replication.Set("RoundEndsAt", os.time() + 120)
Twill.Replication.SetFor(player, "Loadout", loadout)
Twill.Replication.SetPathFor(player, "Loadout", "Primary", "rifle")
Twill.Replication.IncrementFor(player, "Loadout", "Ammo", -1)

Every name has a For twin. Without it the value is shared by everybody. With it, the value belongs to one player and no one else ever receives it.

client
local subscription = Twill.Replication.Subscribe("Loadout.Primary", function(weapon)
label.Text = weapon or "none"
end)
bag:Add(subscription)

The callback runs once immediately if the value is already known, so there is no separate “read the current value” step.

local endsAt = Twill.Replication.Get("RoundEndsAt")

Works on both sides. On the client this reads what was last received, so it can be nil early. To wait:

local endsAt, arrived = Twill.Replication.WaitFor("RoundEndsAt", 10)

The first dot separates the key from the path inside it.

Replication.Set("Player.Data", value) -- never matches any subscription
Replication.Set("PlayerData", value) -- correct

A subscription to "Player.Data" reads it as key Player, path Data, and waits forever.

Twill.Replication.SetPathFor(player, "Data", "Stats.Coins", 250)

Only that field travels. Replication keeps a private copy of what each player was last sent and diffs against it, so a deep write inside a large table costs one field, not the table.

SetPath creates any tables missing along the path. It returns false when a step along the path holds something other than a table, or when the key’s validator refuses the write.

Twill.Replication.Freeze("Board")
for _, move in moves do
Twill.Replication.SetPath("Board", move.Path, move.Value)
end
Twill.Replication.Unfreeze("Board")

One diff for the whole rearrangement instead of a message per step.

Twill.Replication.SetThrottle("Position", 0.5)

Writes are already batched on a global interval. This slows one key further, for values that change far more often than a player could notice.

Twill.Replication.SetValidator("RoundEndsAt", function(value)
return type(value) == "number" and value > os.time()
end)

A client cannot publish anything, so this is not a security boundary. It catches the bug where some code path publishes nil at three in the morning.

If you only want a player to see their own saved fields, do not publish by hand. Name them in Data.Configure:

Replicate = { "Coins", "Stats" },

They arrive under the Data key and follow direct mutation. See Store and save player data.

local stats = Twill.Replication.GetStats()
print(stats.Messages, stats.Keys)

The diff is what keeps messages small, and the private per-player copy is what the diff costs in memory. A key holding a very large table for every player is paid for twice.