refactor: separate code for preserving history

This commit is contained in:
Richard 2024-09-25 22:10:19 -07:00
parent 8aef95e2ae
commit e5f655482f

View file

@ -62,32 +62,29 @@ type ServerSyncer = {
local function server(options: ServerOptions): ServerSyncer
local self = {} :: ServerSyncer
local sync: (player: Player, payload: SyncPayload) -> ()
local snapshots: { { [string]: any } } = {}
local changed = false
-- Start the interval to send state patches over the network.
function self:connect(callback)
local cleanups = {}
sync = callback
local function getSnapshot()
local function createSnapshot()
local snapshot: { [string]: any } = {}
for key, atom in next, options.atoms do
snapshot[key] = atom()
end
return snapshot
end
local function pushSnapshot(key: string, current: any, previous: any)
if not options.preserveHistory then
if snapshots[2] then
snapshots[2][key] = current
else
snapshots[2] = getSnapshot()
end
return
end
if options.preserveHistory then
-- Preserve a history of every state change to allow for more granular
-- syncing with the client.
local snapshots: { { [string]: any } } = {}
local changed = false
function self:connect(callback)
local subscriptions = {}
sync = callback
local function pushSnapshot(key: string, current: any, previous: any)
local lastSnapshot = snapshots[#snapshots]
local previousSnapshot = snapshots[#snapshots - 1]
@ -103,16 +100,16 @@ local function server(options: ServerOptions): ServerSyncer
end
end
-- Populate the initial state and snapshot for each atom.
-- 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, options.atoms do
cleanups[key] = subscribe(atom, function(current, previous)
subscriptions[key] = subscribe(atom, function(current, previous)
pushSnapshot(key, current, previous)
changed = true
end)
end
snapshots[1] = getSnapshot()
snapshots[1] = createSnapshot()
local disconnect = setInterval(function()
if not changed then
@ -145,20 +142,11 @@ local function server(options: ServerOptions): ServerSyncer
return function()
disconnect()
for _, cleanup in next, cleanups do
cleanup()
end
end
end
-- Send the initial state to a player when they join the server.
function self:hydrate(player)
assert(sync, "connect() must be called before hydrate()")
sync(player, {
type = "init",
data = snapshots[#snapshots],
})
for _, unsubscribe in next, subscriptions do
unsubscribe()
end
end
end
function self:_sendPatch(player: Player)
@ -186,6 +174,80 @@ local function server(options: ServerOptions): ServerSyncer
sync(player, unpack(payloads))
end
else
-- Only send the most recent state to the client.
local previousSnapshot = createSnapshot()
local currentSnapshot = table.clone(previousSnapshot)
local changed = false
function self:connect(callback)
local subscriptions = {}
sync = callback
-- Subscribe to each atom and update the state when one changes.
for key, atom in next, options.atoms do
subscriptions[key] = subscribe(atom, function(state)
currentSnapshot[key] = state
changed = true
end)
end
local disconnect = setInterval(function()
if not changed then
return
end
local payload: SyncPayload = {
type = "patch",
data = patch.diff(previousSnapshot, currentSnapshot),
}
previousSnapshot = table.clone(currentSnapshot)
changed = false
for _, player in next, Players:GetPlayers() do
callback(player, payload)
end
end, options.interval or 0)
return function()
disconnect()
for _, unsubscribe in next, subscriptions do
unsubscribe()
end
end
end
function self:_sendPatch(player: Player)
assert(sync, "connect() must be called before _sendPatch()")
if not changed then
return
end
local payload: SyncPayload = {
type = "patch",
data = patch.diff(previousSnapshot, currentSnapshot),
}
previousSnapshot = table.clone(currentSnapshot)
changed = false
sync(player, payload)
end
end
-- Send the initial state to a player when they join the server.
function self:hydrate(player)
assert(sync, "connect() must be called before hydrate()")
sync(player, {
type = "init",
data = createSnapshot(),
})
end
return self
end