chore: housekeeping

This commit is contained in:
littensy 2026-04-03 07:26:22 -07:00
parent 3134136e70
commit c842926abe
6 changed files with 44 additions and 36 deletions

View file

@ -640,25 +640,19 @@ return {
} }
``` ```
When a player joins on the server, call `server.addSignalsToClient` with the keyed signals that the client should receive updates for. Once they leave, call `server.removeClient` to unsubscribe them from all updates. When a player notifies the server that they're ready to start syncing, call `server.addSignalsToClient` with the signals that the client should receive updates for. Once they leave, call `server.removeClient` to unsubscribe them from all updates.
Then, use `server.connect` to specify how state updates should be sent to each client. Pass a callback function that fires a remote with the given target player and the state updates they subscribed to. Then, use `server.connect` to specify how state updates should be sent to each client. Pass a callback function that fires a remote with the given target player and the state updates they subscribed to:
```luau ```luau
local function onPlayerAdded(player: Player) playerReadyEvent.OnServerEvent:Connect(function(player)
-- Add signal getters, computed signals, atoms, or reactive objects -- Sync signal getters, computed signals, atoms, or reactive proxies
server.addSignalsToClient(player, { server.addSignalsToClient(player, {
name = nameStore.getName, name = nameStore.getName,
surname = nameStore.getSurname, surname = nameStore.getSurname,
age = nameStore.ageAtom, age = nameStore.ageAtom,
}) })
end end)
for _, player in Players:GetPlayers() do
onPlayerAdded(player)
end
Players.PlayerAdded:Connect(onPlayerAdded)
Players.PlayerRemoving:Connect(function(player) Players.PlayerRemoving:Connect(function(player)
server.removeClient(player) server.removeClient(player)
@ -671,7 +665,7 @@ end)
``` ```
> [!NOTE] > [!NOTE]
> On the server, make sure each key corresponds to the same signal across all players. If two players subscribe to the same key, but were given different signals, Charm will output a warning. > On the server, make sure each key corresponds to the same data across all players. If two players subscribe to the same key, but were given different signals, Charm will output a warning.
To sync the client with the server's state, call `client.addSignals` with a table of writable signals (setter functions or atoms) whose keys match their server counterparts. To sync the client with the server's state, call `client.addSignals` with a table of writable signals (setter functions or atoms) whose keys match their server counterparts.
@ -701,8 +695,8 @@ A configuration table that customizes the behavior of Charm Sync on the server.
| --------------- | ------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | --------------- | ------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| interval | `0` | The frequency at which the server will send patches to the client, in seconds. A value of `0` sends updates on the next frame. Set to a negative value to disable the interval. | | interval | `0` | The frequency at which the server will send patches to the client, in seconds. A value of `0` sends updates on the next frame. Set to a negative value to disable the interval. |
| preserveHistory | `false` | Whether to preserve a full history of state changes since the last sync, at the cost of performance. This is useful if you need to replicate each individual change that occurs between sync events. | | preserveHistory | `false` | Whether to preserve a full history of state changes since the last sync, at the cost of performance. This is useful if you need to replicate each individual change that occurs between sync events. |
| fixArrays | `true` | When `true`, Charm will attempt to work around Roblox remote event limitations regarding array patches. Disable this if your networking library serializes remote arguments (Zap, ByteNet, etc.). | | fixArrays | `true` | When `true`, Charm will attempt to work around remote event limitations that cause data loss in array patches. Disable this if your networking library serializes remote arguments (Zap, ByteNet, etc.). |
| validatePatches | `true` | When `true`, and both `fixArrays` and strict mode are enabled, synced values containing unsafe sparse arrays or mixed tables will throw an error. See the [remote argument limitations](https://create.roblox.com/docs/scripting/events/remote#argument-limitations). | | validatePatches | `true` | When `true`, and both `fixArrays` and strict mode are enabled, synced values containing unsafe sparse arrays or mixed tables will emit a warning. See the [remote argument limitations](https://create.roblox.com/docs/scripting/events/remote#argument-limitations). |
--- ---
@ -713,7 +707,7 @@ The `addSignalsToClient` function subscribes a client to updates in the given si
You can pass signal getter functions, computed signals, atoms, and [reactive objects](#reactiveinitialvalue) in the `getters` table. This function can also be called multiple times on the same client to subscribe to new signals. You can pass signal getter functions, computed signals, atoms, and [reactive objects](#reactiveinitialvalue) in the `getters` table. This function can also be called multiple times on the same client to subscribe to new signals.
```luau ```luau
Players.PlayerAdded:Connect(function(player) playerReadyEvent.OnServerEvent:Connect(function(player)
server.addSignalsToClient(player, { server.addSignalsToClient(player, {
name = nameStore.getName, name = nameStore.getName,
surname = nameStore.getSurname, surname = nameStore.getSurname,
@ -722,14 +716,22 @@ Players.PlayerAdded:Connect(function(player)
end) end)
``` ```
You're also allowed to create new signals to sync to specific players, as long as the key is unique to that player: You're also allowed to create new signals to sync to specific players, as long as the key is unique to that data:
```luau ```luau
server.addSignalsToClient(player, { playerReadyEvent.OnServerEvent:Connect(function(player)
[`data-{player.UserId}`] = computed(function() server.addSignalsToClient(player, {
return getPlayerData(player.UserId) -- 🟢 Good: Player-specific data is synced with a unique key
end), [`data-{player.UserId}`] = computed(function()
}) return getPlayerData(player.UserId)
end),
-- 🔴 Bad: Syncing different data with the same keys does not work
playerData = computed(function()
return getPlayerData(player.UserId)
end),
})
end)
``` ```
--- ---
@ -754,7 +756,7 @@ server.removeSignalsFromClient(player, "surname")
The `removeClient` function unsubscribes a client from receiving all state updates from the server. You should call this function when a player leaves the game. The `removeClient` function unsubscribes a client from receiving all state updates from the server. You should call this function when a player leaves the game.
```luau ```luau
Players.PlayerAdded:Connect(function(player) playerReadyEvent.OnServerEvent:Connect(function(player)
server.addSignalsToClient(player, signals) server.addSignalsToClient(player, signals)
end) end)

View file

@ -9,19 +9,22 @@ return {
Whether the history of state changes since the client's last update Whether the history of state changes since the client's last update
should be preserved. This is useful for values that change multiple should be preserved. This is useful for values that change multiple
times per frame, where each individual change is important. Defaults times per frame, where each individual change is important. Defaults
to `false`. to `false` for performance reasons.
]] ]]
preserveHistory = false, preserveHistory = false,
--[[ --[[
When `true`, Charm will apply validation and serialize unsafe arrays When `true`, Charm will modify sparse arrays to prevent data loss from
to address remote event argument limitations. Defaults to `true`. remote event limitations. When an unsafe array is detected, numeric
indices will be converted to string keys so that the array can be sent
safely and then converted back to an array on the receiving end.
This option should be disabled if your network library uses a custom This option should be disabled if your network library uses a custom
serialization method (i.e. Zap, ByteNet) to prevent interference. serialization method (i.e. Zap, ByteNet) to prevent interference.
]] ]]
fixArrays = true, fixArrays = true,
--[[ --[[
When `true`, synced state containing unsafe sparse arrays or mixed When `true`, synced state containing unsafe sparse arrays or mixed
tables will throw an error. Only checked in strict mode and if tables will emit a warning. Only checked in strict mode and if
`fixArrays` is enabled. Defaults to `true`. `fixArrays` is enabled. Defaults to `true`.
]] ]]
validatePatches = true, validatePatches = true,

View file

@ -78,14 +78,17 @@ export const config: {
* Whether the history of state changes since the client's last update * Whether the history of state changes since the client's last update
* should be preserved. This is useful for values that change multiple * should be preserved. This is useful for values that change multiple
* times per frame, where each individual change is important. Defaults * times per frame, where each individual change is important. Defaults
* to `false`. * to `false` for performance reasons.
* *
* @server * @server
*/ */
preserveHistory: boolean; preserveHistory: boolean;
/** /**
* When `true`, Charm will apply validation and serialize unsafe arrays * When `true`, Charm will modify sparse arrays to prevent data loss from
* to address remote event argument limitations. Defaults to `true`. * remote event limitations. When an unsafe array is detected, numeric
* indices will be converted to string keys so that the array can be sent
* safely and then converted back to an array on the receiving end.
*
* This option should be disabled if your network library uses a custom * This option should be disabled if your network library uses a custom
* serialization method (i.e. Zap, ByteNet) to prevent interference. * serialization method (i.e. Zap, ByteNet) to prevent interference.
* *
@ -94,7 +97,7 @@ export const config: {
fixArrays: boolean; fixArrays: boolean;
/** /**
* When `true`, synced state containing unsafe sparse arrays or mixed * When `true`, synced state containing unsafe sparse arrays or mixed
* tables will throw an error. Only checked in strict mode and if * tables will emit a warning. Only checked in strict mode and if
* `fixArrays` is enabled. Defaults to `true`. * `fixArrays` is enabled. Defaults to `true`.
* *
* @server * @server
@ -242,7 +245,7 @@ export namespace patch {
* @param statePatch The patches to apply. * @param statePatch The patches to apply.
* @returns The new state with the patch applied. * @returns The new state with the patch applied.
*/ */
export function apply<State, FixArrays extends boolean = true>( export function applyImmutable<State, FixArrays extends boolean = true>(
currentState: State, currentState: State,
statePatch: SyncPatch<State, FixArrays>, statePatch: SyncPatch<State, FixArrays>,
): State; ): State;

View file

@ -497,7 +497,7 @@ end)
test("recursive effects should run and cleanup in order", function() test("recursive effects should run and cleanup in order", function()
local a, setA = signal(0) local a, setA = signal(0)
local order = {} local order: { string } = {}
effect(function() effect(function()
local value = a() local value = a()

View file

@ -91,7 +91,7 @@ end)
test("should handle recursive additions and removals", function() test("should handle recursive additions and removals", function()
local items, setItems = signal({ 1 }) local items, setItems = signal({ 1 })
local edits = {} local edits: { string } = {}
observe(items, function(item) observe(items, function(item)
if item < 5 then if item < 5 then
@ -114,7 +114,7 @@ end)
test("should handle self removal", function() test("should handle self removal", function()
local items, setItems = signal({ 1, 2, 3, 4 }) local items, setItems = signal({ 1, 2, 3, 4 })
local edits = {} local edits: { string } = {}
observe(items, function(item, index) observe(items, function(item, index)
setItems(function(state: { number }) setItems(function(state: { number })

View file

@ -46,7 +46,7 @@ test("should run in untracked", function()
end) end)
test("should ensure correct execution order", function() test("should ensure correct execution order", function()
local order = {} local order: { number } = {}
local n1, setN1 = signal(0) local n1, setN1 = signal(0)
local n2, setN2 = signal(0) local n2, setN2 = signal(0)
local sum = computed(function() local sum = computed(function()
@ -72,7 +72,7 @@ test("should ensure correct execution order", function()
end) end)
test("should skip redundant updates", function() test("should skip redundant updates", function()
local order = {} local order: { "even" | "odd" } = {}
local a, setA = signal(0) local a, setA = signal(0)
subscribe(function(): "even" | "odd" subscribe(function(): "even" | "odd"