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