mirror of
https://github.com/littensy/charm.git
synced 2026-10-21 03:36:11 +00:00
refactor: improve structure and alien-signals integration
This commit is contained in:
parent
a9c60c701f
commit
95476e93ec
107 changed files with 3660 additions and 815 deletions
121
packages/charm-sync/src/__tests__/client.spec.luau
Normal file
121
packages/charm-sync/src/__tests__/client.spec.luau
Normal file
|
|
@ -0,0 +1,121 @@
|
|||
local Charm = require("../../Charm")
|
||||
local CharmSync = require("../")
|
||||
local suite = require("../../../../test/suite")
|
||||
|
||||
local atom = Charm.atom
|
||||
local subscribe = Charm.subscribe
|
||||
local client = CharmSync.client
|
||||
local test = suite.test
|
||||
|
||||
test("should sync state payload", function()
|
||||
local a = atom(1)
|
||||
local client = client({ atoms = { a = a } })
|
||||
|
||||
client:sync({
|
||||
type = "init",
|
||||
data = { a = 2 },
|
||||
})
|
||||
|
||||
assert(a() == 2, "should set a to the initial value")
|
||||
end)
|
||||
|
||||
test("should sync state patch", function()
|
||||
local a = atom({ b = 1, c = 2 })
|
||||
local previous = a()
|
||||
local client = client({ atoms = { a = a } })
|
||||
|
||||
client:sync({
|
||||
type = "init",
|
||||
data = { a = a() },
|
||||
})
|
||||
|
||||
assert(a() == previous, "should set a to the initial value")
|
||||
|
||||
client:sync({
|
||||
type = "patch",
|
||||
data = { a = { b = 3 } },
|
||||
})
|
||||
|
||||
assert(a() ~= previous, "should apply patch")
|
||||
assert(a().b == 3, "should set b to the new value")
|
||||
assert(a().c == 2, "should not change c")
|
||||
end)
|
||||
|
||||
test("should fix sparse array patches", function()
|
||||
local a = atom({ 1 })
|
||||
local client = client({ atoms = { a = a } })
|
||||
|
||||
client:sync({
|
||||
type = "init",
|
||||
data = { a = a() },
|
||||
})
|
||||
|
||||
-- This patch would be the result of a diff between { 1 } and { 1, 2 } that
|
||||
-- was JSON-serialized and deserialized. The client should be able to catch
|
||||
-- this and convert the string key back to a number.
|
||||
client:sync({
|
||||
type = "patch",
|
||||
data = { a = { ["2"] = 2 } },
|
||||
})
|
||||
|
||||
assert(a()["2" :: any] == nil, "should convert serialized arrays to use numbers")
|
||||
assert(a()[2] == 2, "should convert serialized arrays to use numbers")
|
||||
end)
|
||||
|
||||
test("should apply multiple patches", function()
|
||||
local a = atom(1)
|
||||
local client = client({ atoms = { a = a } })
|
||||
local calls = {}
|
||||
|
||||
subscribe(a, function(value)
|
||||
table.insert(calls, value)
|
||||
end)
|
||||
|
||||
client:sync({
|
||||
type = "init",
|
||||
data = { a = a() },
|
||||
})
|
||||
|
||||
client:sync({
|
||||
type = "patch",
|
||||
data = { a = 2 },
|
||||
}, {
|
||||
type = "patch",
|
||||
data = { a = 3 },
|
||||
})
|
||||
|
||||
assert(a() == 3, "should apply the last patch")
|
||||
assert(table.concat(calls) == "23", `wrong order: {table.concat(calls)}`)
|
||||
end)
|
||||
|
||||
test("should batch patches", function()
|
||||
local a = atom(0)
|
||||
local b = atom(0)
|
||||
local client = client({ atoms = { a = a, b = b } })
|
||||
local states = {}
|
||||
|
||||
subscribe(a, function()
|
||||
states.b = b()
|
||||
end)
|
||||
|
||||
subscribe(b, function()
|
||||
states.a = a()
|
||||
end)
|
||||
|
||||
client:sync({
|
||||
type = "init",
|
||||
data = { a = a(), b = b() },
|
||||
})
|
||||
|
||||
client:sync({
|
||||
type = "patch",
|
||||
data = { a = 1, b = 2 },
|
||||
})
|
||||
|
||||
assert(a() == 1, "should set a to the new value")
|
||||
assert(b() == 2, "should set b to the new value")
|
||||
assert(states.a == 1, "sub to a should run after b updates")
|
||||
assert(states.b == 2, "sub to b should run after a updates")
|
||||
end)
|
||||
|
||||
return {}
|
||||
25
packages/charm-sync/src/__tests__/flatten.spec.luau
Normal file
25
packages/charm-sync/src/__tests__/flatten.spec.luau
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
local Charm = require("../../Charm")
|
||||
local CharmSync = require("../")
|
||||
local suite = require("../../../../test/suite")
|
||||
|
||||
local atom = Charm.atom
|
||||
local flatten = CharmSync.flatten
|
||||
local test = suite.test
|
||||
|
||||
test("should flatten atoms", function()
|
||||
local input = {
|
||||
a = atom(0),
|
||||
b = { c = atom(0) },
|
||||
d = { e = { f = atom(0) } },
|
||||
}
|
||||
local result = flatten(input)
|
||||
|
||||
assert(result.a == input.a, "should flatten the first level")
|
||||
assert(result["b/c"] == input.b.c, "should flatten the second level")
|
||||
assert(result["d/e/f"] == input.d.e.f, "should flatten the third level")
|
||||
|
||||
assert(result.b == nil, "should remove the original nested tables")
|
||||
assert(result.d == nil, "should remove the original nested tables")
|
||||
end)
|
||||
|
||||
return {}
|
||||
128
packages/charm-sync/src/__tests__/patch.spec.luau
Normal file
128
packages/charm-sync/src/__tests__/patch.spec.luau
Normal file
|
|
@ -0,0 +1,128 @@
|
|||
local HttpService = game:GetService("HttpService")
|
||||
|
||||
local CharmSync = require("../")
|
||||
local suite = require("../../../../test/suite")
|
||||
|
||||
local patch = CharmSync.patch
|
||||
local test = suite.test
|
||||
|
||||
local function serdes(value: { [any]: any })
|
||||
return HttpService:JSONDecode(HttpService:JSONEncode(value))
|
||||
end
|
||||
|
||||
test("returns a patch", function()
|
||||
local prevState = { foo = "bar", baz = "qux" }
|
||||
local nextState = { foo = "bar", baz = "quux" }
|
||||
local patches = patch.diff(prevState, nextState)
|
||||
|
||||
assert(patches ~= nil, "should return a patch")
|
||||
assert(patches.baz == "quux", "should return the changed value")
|
||||
assert(patches.foo == nil, "should omit unchanged values")
|
||||
end)
|
||||
|
||||
test("applies a patch", function()
|
||||
local prevState = { foo = "bar", baz = "qux" }
|
||||
local patches = { baz = "quux", qaz = "qux" }
|
||||
local applied = patch.apply(prevState, patches)
|
||||
|
||||
assert(applied ~= nil, "should return a new state")
|
||||
assert(applied.baz == "quux", "should apply the changed value")
|
||||
assert(applied.foo == "bar", "should keep the unchanged value")
|
||||
assert(applied.qaz == "qux", "should add the new value")
|
||||
end)
|
||||
|
||||
test("allows nested patches", function()
|
||||
local prevState = { foo = { bar = "baz" } }
|
||||
local nextState = { foo = { bar = "qux" } }
|
||||
local patches = patch.diff(prevState, nextState)
|
||||
local applied = patch.apply(prevState, patches)
|
||||
|
||||
assert(applied.foo.bar == "qux", "should apply the nested patch")
|
||||
end)
|
||||
|
||||
test("preserves array density when applying", function()
|
||||
local prevState = { "a", "b", "c", "d" }
|
||||
local nextState = { ["2"] = "B", ["4"] = "D" }
|
||||
local applied = patch.apply(prevState, nextState)
|
||||
|
||||
assert(applied[1] == "a", "should keep a")
|
||||
assert(applied[2] == "B", "should apply B")
|
||||
assert(applied[3] == "c", "should keep c")
|
||||
assert(applied[4] == "D", "should apply D")
|
||||
|
||||
assert(applied["2"] == nil, "should convert serialized arrays to use numbers")
|
||||
assert(applied["4"] == nil, "should convert serialized arrays to use numbers")
|
||||
end)
|
||||
|
||||
test("preserves array density after serdes", function()
|
||||
-- This particular change results in a patch that cannot be serialized
|
||||
-- without losing information. Charm resolves this issue internally.
|
||||
local prevState = { "a", "b", "c", "d", "e" }
|
||||
local nextState = { "A", "b", "c", "D" }
|
||||
|
||||
local patches = serdes(patch.diff(prevState, nextState))
|
||||
local applied = patch.apply(prevState, patches)
|
||||
|
||||
for index in next, prevState do
|
||||
assert(applied[index] == nextState[index], `expected {applied[index]} to be {nextState[index]}`)
|
||||
end
|
||||
|
||||
for index in next, applied do
|
||||
assert(applied[index] == nextState[index], `expected {applied[index]} to be {nextState[index]}`)
|
||||
end
|
||||
end)
|
||||
|
||||
test("preserves large gaps between keys", function()
|
||||
local prevState = table.create(10000, "foo")
|
||||
local nextState = table.move(prevState, 2, 9998, 2, { [1] = "bar", [9999] = "bar" })
|
||||
|
||||
local start = os.clock()
|
||||
|
||||
local patches = serdes(patch.diff(prevState, nextState))
|
||||
local applied = patch.apply(prevState, patches)
|
||||
|
||||
assert(os.clock() - start < 10e-3, `Performance is not acceptable ({(os.clock() - start) * 1e3}ms)`)
|
||||
|
||||
for index in next, prevState do
|
||||
assert(applied[index] == nextState[index], `expected {applied[index]} to be {nextState[index]}`)
|
||||
end
|
||||
end)
|
||||
|
||||
test("cannot preserve sparse arrays", function()
|
||||
local prevState = {}
|
||||
local nextState = { [2] = "foo" }
|
||||
|
||||
local patches = serdes(patch.diff(prevState, nextState))
|
||||
local applied = patch.apply(prevState, patches)
|
||||
|
||||
-- Not enough information is available to determine whether 'patches'
|
||||
-- should keep the string keys added during remote event serialization.
|
||||
assert(applied["2"] ~= nil, "will fail to convert string keys to numbers")
|
||||
assert(applied[2] == nil, "will fail to convert string keys to numbers")
|
||||
end)
|
||||
|
||||
test("optionally fixes sparse arrays", function()
|
||||
for index in next, patch.diff({ 1, 1, 1, 1, 1 }, { 2, 1, 1, 2 }, true) do
|
||||
assert(type(index) == "string", `expected {type(index)} {index} to be string`)
|
||||
end
|
||||
|
||||
for index in next, patch.diff({ 1, 1, 1, 1, 1 }, { 2, 1, 1, 2 }, false) do
|
||||
assert(type(index) == "number", `expected {type(index)} {index} to be number`)
|
||||
end
|
||||
end)
|
||||
|
||||
test("optionally validates state", function()
|
||||
local success = pcall(function()
|
||||
patch.diff({}, { [0.1] = true }, true)
|
||||
end)
|
||||
|
||||
assert(not success, "should not allow non-integer keys")
|
||||
|
||||
success = pcall(function()
|
||||
patch.diff({}, { [0.1] = true }, false)
|
||||
end)
|
||||
|
||||
assert(success, "should skip validation if not requested")
|
||||
end)
|
||||
|
||||
return {}
|
||||
99
packages/charm-sync/src/__tests__/server.spec.luau
Normal file
99
packages/charm-sync/src/__tests__/server.spec.luau
Normal file
|
|
@ -0,0 +1,99 @@
|
|||
local Charm = require("../../Charm")
|
||||
local CharmSync = require("../")
|
||||
local suite = require("../../../../test/suite")
|
||||
|
||||
type SyncPayload = CharmSync.SyncPayload
|
||||
|
||||
local atom = Charm.atom
|
||||
local server = CharmSync.server
|
||||
local patch = CharmSync.patch
|
||||
local test = suite.test
|
||||
|
||||
local target: Player = {} :: any
|
||||
|
||||
test("sends state to new players", function()
|
||||
local a = atom(1)
|
||||
local server = server({ atoms = { a = a }, interval = -1 })
|
||||
local player: Player, payload: SyncPayload
|
||||
|
||||
local disconnect = server:connect(function(...)
|
||||
player, payload = ...
|
||||
end)
|
||||
|
||||
server:hydrate(target)
|
||||
|
||||
assert(player == target, "should send to the target player")
|
||||
assert(payload.type == "init", "should be an init payload")
|
||||
assert(payload.data.a == 1, "should send the initial state")
|
||||
|
||||
disconnect()
|
||||
end)
|
||||
|
||||
test("sends state patches", function()
|
||||
local a = atom({ b = 1, c = 2 } :: { [string]: number? })
|
||||
local server = server({ atoms = { a = a }, interval = -1 })
|
||||
local player: Player, payload: SyncPayload
|
||||
|
||||
local disconnect = server:connect(function(...)
|
||||
player, payload = ...
|
||||
end)
|
||||
|
||||
a({ b = 1, c = 5 })
|
||||
server:_sendPatch(target)
|
||||
|
||||
assert(player == target, "should send to the target player")
|
||||
assert(payload.type == "patch", "should be a patch payload")
|
||||
assert(payload.data.a.b == nil, "should not send unchanged values")
|
||||
assert(payload.data.a.c == 5, "should send changed values")
|
||||
|
||||
a({ b = 1, d = 1 })
|
||||
server:_sendPatch(target)
|
||||
|
||||
assert(payload.data.a.b == nil, "should not send unchanged values")
|
||||
assert(patch.isNone(payload.data.a.c) == true, "should send None for removed values")
|
||||
assert(payload.data.a.d == 1, "should send new values")
|
||||
|
||||
disconnect()
|
||||
end)
|
||||
|
||||
test("preserves history", function()
|
||||
local a = atom(1)
|
||||
local b = atom(1)
|
||||
local payloads: { SyncPayload }
|
||||
|
||||
local server = server({
|
||||
atoms = { a = a, b = b },
|
||||
interval = -1,
|
||||
preserveHistory = true,
|
||||
})
|
||||
|
||||
local disconnect = server:connect(function(_, ...)
|
||||
payloads = { ... }
|
||||
end)
|
||||
|
||||
a(2)
|
||||
b(2)
|
||||
server:_sendPatch(target)
|
||||
|
||||
assert(#payloads == 1, "should only have one payload")
|
||||
assert(payloads[1].type == "patch", "should be a patch payload")
|
||||
assert(payloads[1].data.a == 2, "should send the new value of a")
|
||||
assert(payloads[1].data.b == 2, "should send the new value of b")
|
||||
|
||||
a(3)
|
||||
a(4)
|
||||
b(3)
|
||||
server:_sendPatch(target)
|
||||
|
||||
assert(#payloads == 2, "should have two payloads")
|
||||
assert(payloads[1].type == "patch", "1 should be a patch payload")
|
||||
assert(payloads[1].data.a == 3, "1 should send the new value of a")
|
||||
assert(payloads[1].data.b == nil, "1 should not send unchanged values")
|
||||
assert(payloads[2].type == "patch", "2 should be a patch payload")
|
||||
assert(payloads[2].data.a == 4, "2 should send the new value of a")
|
||||
assert(payloads[2].data.b == 3, "2 should send the new value of b")
|
||||
|
||||
disconnect()
|
||||
end)
|
||||
|
||||
return {}
|
||||
24
packages/charm-sync/src/__tests__/spec.luau
Normal file
24
packages/charm-sync/src/__tests__/spec.luau
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
local suite = require("../../../../test/suite")
|
||||
|
||||
suite.describe("charm-sync", function()
|
||||
if not game then
|
||||
return suite.skip()
|
||||
end
|
||||
suite.describe("client", function()
|
||||
require("./client.spec")
|
||||
end)
|
||||
suite.describe("server", function()
|
||||
require("./server.spec")
|
||||
end)
|
||||
suite.describe("flatten", function()
|
||||
require("./flatten.spec")
|
||||
end)
|
||||
suite.describe("patch", function()
|
||||
require("./patch.spec")
|
||||
end)
|
||||
suite.describe("validate", function()
|
||||
require("./validate.spec")
|
||||
end)
|
||||
end)
|
||||
|
||||
return {}
|
||||
35
packages/charm-sync/src/__tests__/validate.spec.luau
Normal file
35
packages/charm-sync/src/__tests__/validate.spec.luau
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
local Charm = require("../../Charm")
|
||||
local CharmSync = require("../")
|
||||
local suite = require("../../../../test/suite")
|
||||
|
||||
local atom = Charm.atom
|
||||
local validate = CharmSync.validate
|
||||
local test = suite.test
|
||||
|
||||
test("validates inputs", function()
|
||||
local cases: { [string]: { value: any, expected: any } } = {
|
||||
array = { value = { 1, 2, 3 }, expected = true },
|
||||
array_sparse = { value = { 1, nil :: any, 3 }, expected = true },
|
||||
array_sparse_invalid = { value = { [1] = 0, [3] = 0 }, expected = false },
|
||||
|
||||
map_decimal_key = { value = { [1.5] = 0 }, expected = false },
|
||||
map_infinity_key = { value = { [math.huge] = true }, expected = false },
|
||||
map_mixed_key = { value = { [1] = 0, a = 0 }, expected = false },
|
||||
|
||||
nested_atom = { value = atom(0), expected = false },
|
||||
}
|
||||
|
||||
for key, case in next, cases do
|
||||
local success, result = pcall(function()
|
||||
validate(case.value, key)
|
||||
end)
|
||||
|
||||
if case.expected and not success then
|
||||
error(`Expected {key} to be valid, threw {result}`)
|
||||
elseif not case.expected and success then
|
||||
error(`Expected {key} to be invalid, but it was valid`)
|
||||
end
|
||||
end
|
||||
end)
|
||||
|
||||
return {}
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
local Charm = require(script.Parent.Parent.Charm)
|
||||
local patch = require(script.Parent.patch)
|
||||
local types = require(script.Parent.types)
|
||||
local Charm = require("../Charm")
|
||||
local patch = require("./patch")
|
||||
local types = require("./types")
|
||||
|
||||
type AtomMap = types.AtomMap
|
||||
type SyncPayload = types.SyncPayload
|
||||
|
|
@ -36,7 +36,7 @@ local function client(options: ClientOptions): ClientSyncer
|
|||
local function hydrate(state: { [string | number]: any })
|
||||
hydrated = true
|
||||
|
||||
for key, value in next, state do
|
||||
for key, value in state do
|
||||
local atom = atoms[key]
|
||||
atom(value)
|
||||
end
|
||||
|
|
@ -45,13 +45,13 @@ local function client(options: ClientOptions): ClientSyncer
|
|||
local function apply(data: { [string | number]: any })
|
||||
local target = {}
|
||||
|
||||
for key, atom in next, atoms do
|
||||
for key, atom in atoms do
|
||||
target[key] = atom()
|
||||
end
|
||||
|
||||
target = patch.apply(target, data)
|
||||
|
||||
for key, atom in next, atoms do
|
||||
for key, atom in atoms do
|
||||
atom(target[key])
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
local types = require(script.Parent.types)
|
||||
local types = require("./types")
|
||||
|
||||
type AtomMap = types.AtomMap
|
||||
|
||||
|
|
|
|||
1
packages/charm-sync/src/index.d.ts
vendored
Normal file
1
packages/charm-sync/src/index.d.ts
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
export {};
|
||||
|
|
@ -1,9 +1,9 @@
|
|||
local client = require(script.client)
|
||||
local flatten = require(script.flatten)
|
||||
local patch = require(script.patch)
|
||||
local server = require(script.server)
|
||||
local types = require(script.types)
|
||||
local validate = require(script.validate)
|
||||
local client = require("@self/client")
|
||||
local flatten = require("@self/flatten")
|
||||
local patch = require("@self/patch")
|
||||
local server = require("@self/server")
|
||||
local types = require("@self/types")
|
||||
local validate = require("@self/validate")
|
||||
|
||||
export type SyncPayload = types.SyncPayload
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,3 @@
|
|||
local RunService = game:GetService("RunService")
|
||||
|
||||
--- Schedules a function to be called at a fixed interval of `seconds`.
|
||||
---
|
||||
--- @param callback The function to call.
|
||||
|
|
@ -12,7 +10,7 @@ local function interval(callback: () -> (), seconds: number): () -> ()
|
|||
|
||||
local clock = 0
|
||||
|
||||
local connection = RunService.Heartbeat:Connect(function(delta)
|
||||
local connection = game:GetService("RunService").Heartbeat:Connect(function(delta)
|
||||
clock += delta
|
||||
if clock >= seconds then
|
||||
clock = 0
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
local validate = require(script.Parent.validate)
|
||||
|
||||
local __DEV__ = _G.__DEV__
|
||||
local Charm = require("../Charm")
|
||||
local validate = require("./validate")
|
||||
|
||||
--- A special symbol that denotes the absence of a value. Used to represent
|
||||
--- deleted values in patches.
|
||||
|
|
@ -64,7 +63,7 @@ local function diff(prevState: { [any]: any }, nextState: { [any]: any }, serial
|
|||
patches = stringifySparseArray(patches)
|
||||
end
|
||||
|
||||
if serialize and __DEV__ then
|
||||
if serialize and Charm.strict.enabled then
|
||||
for key, value in next, patches do
|
||||
validate(value, key)
|
||||
end
|
||||
|
|
|
|||
|
|
@ -1,9 +1,7 @@
|
|||
local Players = game:GetService("Players")
|
||||
|
||||
local Charm = require(script.Parent.Parent.Charm)
|
||||
local interval = require(script.Parent.interval)
|
||||
local patch = require(script.Parent.patch)
|
||||
local types = require(script.Parent.types)
|
||||
local Charm = require("../Charm")
|
||||
local interval = require("./interval")
|
||||
local patch = require("./patch")
|
||||
local types = require("./types")
|
||||
|
||||
type AtomMap = types.AtomMap
|
||||
type SyncPayload = types.SyncPayload
|
||||
|
|
@ -64,7 +62,7 @@ local function server(options: ServerOptions): ServerSyncer
|
|||
local function createSnapshot()
|
||||
local snapshot = {}
|
||||
|
||||
for key, atom in next, atoms do
|
||||
for key, atom in atoms do
|
||||
snapshot[key] = atom()
|
||||
end
|
||||
|
||||
|
|
@ -110,7 +108,7 @@ local function server(options: ServerOptions): ServerSyncer
|
|||
|
||||
-- Populate the snapshot with the initial state of each atom.
|
||||
-- Subscribe to each atom and update the state when it changes.
|
||||
for key, atom in next, atoms do
|
||||
for key, atom in atoms do
|
||||
subscriptions[key] = Charm.subscribe(atom, function(current, previous)
|
||||
pushSnapshot(key, current, previous)
|
||||
changed = true
|
||||
|
|
@ -125,7 +123,7 @@ local function server(options: ServerOptions): ServerSyncer
|
|||
local payloads: { SyncPayload } = {}
|
||||
local lastSnapshot
|
||||
|
||||
for index, snapshot in next, snapshots do
|
||||
for index, snapshot in snapshots do
|
||||
lastSnapshot = snapshot
|
||||
|
||||
if index == 1 then
|
||||
|
|
@ -141,7 +139,7 @@ local function server(options: ServerOptions): ServerSyncer
|
|||
snapshots = { lastSnapshot }
|
||||
changed = false
|
||||
|
||||
for _, player in next, Players:GetPlayers() do
|
||||
for _, player in game:GetService("Players"):GetPlayers() do
|
||||
callback(player, unpack(payloads))
|
||||
end
|
||||
end, syncRate)
|
||||
|
|
@ -149,7 +147,7 @@ local function server(options: ServerOptions): ServerSyncer
|
|||
return function()
|
||||
disconnect()
|
||||
|
||||
for _, unsubscribe in next, subscriptions do
|
||||
for _, unsubscribe in subscriptions do
|
||||
unsubscribe()
|
||||
end
|
||||
end
|
||||
|
|
@ -164,7 +162,7 @@ local function server(options: ServerOptions): ServerSyncer
|
|||
|
||||
local payloads: { SyncPayload } = {}
|
||||
|
||||
for index, snapshot in next, snapshots do
|
||||
for index, snapshot in snapshots do
|
||||
if index == 1 then
|
||||
continue
|
||||
end
|
||||
|
|
@ -194,7 +192,7 @@ local function server(options: ServerOptions): ServerSyncer
|
|||
sync = callback
|
||||
|
||||
-- Subscribe to each atom and update the state when one changes.
|
||||
for key, atom in next, atoms do
|
||||
for key, atom in atoms do
|
||||
subscriptions[key] = Charm.subscribe(atom, function(state)
|
||||
currentSnapshot[key] = state
|
||||
changed = true
|
||||
|
|
@ -214,7 +212,7 @@ local function server(options: ServerOptions): ServerSyncer
|
|||
previousSnapshot = table.clone(currentSnapshot)
|
||||
changed = false
|
||||
|
||||
for _, player in next, Players:GetPlayers() do
|
||||
for _, player in game:GetService("Players"):GetPlayers() do
|
||||
callback(player, payload)
|
||||
end
|
||||
end, syncRate)
|
||||
|
|
@ -222,7 +220,7 @@ local function server(options: ServerOptions): ServerSyncer
|
|||
return function()
|
||||
disconnect()
|
||||
|
||||
for _, unsubscribe in next, subscriptions do
|
||||
for _, unsubscribe in subscriptions do
|
||||
unsubscribe()
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
local Charm = require(script.Parent.Parent.Charm)
|
||||
local Charm = require("../Charm")
|
||||
|
||||
type Atom<T> = Charm.Atom<T>
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue