mirror of
https://github.com/littensy/charm.git
synced 2026-10-20 17:51:37 +00:00
refactor: separate code for preserving history
This commit is contained in:
parent
8aef95e2ae
commit
e5f655482f
1 changed files with 148 additions and 86 deletions
|
|
@ -62,69 +62,103 @@ 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 snapshot: { [string]: any } = {}
|
||||||
local cleanups = {}
|
|
||||||
sync = callback
|
|
||||||
|
|
||||||
local function getSnapshot()
|
|
||||||
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
|
|
||||||
|
|
||||||
local lastSnapshot = snapshots[#snapshots]
|
|
||||||
local previousSnapshot = snapshots[#snapshots - 1]
|
|
||||||
|
|
||||||
-- Optimize snapshots by updating the most recent snapshot if the
|
|
||||||
-- previous and current values are the same, since this allows us
|
|
||||||
-- to group multiple changes into a single snapshot.
|
|
||||||
if previousSnapshot and previousSnapshot[key] == previous and lastSnapshot[key] == previous then
|
|
||||||
lastSnapshot[key] = current
|
|
||||||
else
|
|
||||||
local nextSnapshot = table.clone(lastSnapshot)
|
|
||||||
nextSnapshot[key] = current
|
|
||||||
table.insert(snapshots, nextSnapshot)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Populate the initial state and snapshot for each atom.
|
|
||||||
-- 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)
|
snapshot[key] = atom()
|
||||||
pushSnapshot(key, current, previous)
|
|
||||||
changed = true
|
|
||||||
end)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
snapshots[1] = getSnapshot()
|
return snapshot
|
||||||
|
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]
|
||||||
|
|
||||||
|
-- Optimize snapshots by updating the most recent snapshot if the
|
||||||
|
-- previous and current values are the same, since this allows us
|
||||||
|
-- to group multiple changes into a single snapshot.
|
||||||
|
if previousSnapshot and previousSnapshot[key] == previous and lastSnapshot[key] == previous then
|
||||||
|
lastSnapshot[key] = current
|
||||||
|
else
|
||||||
|
local nextSnapshot = table.clone(lastSnapshot)
|
||||||
|
nextSnapshot[key] = current
|
||||||
|
table.insert(snapshots, nextSnapshot)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- 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
|
||||||
|
subscriptions[key] = subscribe(atom, function(current, previous)
|
||||||
|
pushSnapshot(key, current, previous)
|
||||||
|
changed = true
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
|
snapshots[1] = createSnapshot()
|
||||||
|
|
||||||
|
local disconnect = setInterval(function()
|
||||||
|
if not changed then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
local payloads: { SyncPayload } = {}
|
||||||
|
local lastSnapshot
|
||||||
|
|
||||||
|
for index, snapshot in next, snapshots do
|
||||||
|
lastSnapshot = snapshot
|
||||||
|
|
||||||
|
if index == 1 then
|
||||||
|
continue
|
||||||
|
end
|
||||||
|
|
||||||
|
table.insert(payloads, {
|
||||||
|
type = "patch",
|
||||||
|
data = patch.diff(snapshots[index - 1], snapshot),
|
||||||
|
})
|
||||||
|
end
|
||||||
|
|
||||||
|
snapshots = { lastSnapshot }
|
||||||
|
changed = false
|
||||||
|
|
||||||
|
for _, player in next, Players:GetPlayers() do
|
||||||
|
callback(player, unpack(payloads))
|
||||||
|
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()")
|
||||||
|
|
||||||
local disconnect = setInterval(function()
|
|
||||||
if not changed then
|
if not changed then
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
local payloads: { SyncPayload } = {}
|
local payloads: { SyncPayload } = {}
|
||||||
local lastSnapshot
|
|
||||||
|
|
||||||
for index, snapshot in next, snapshots do
|
for index, snapshot in next, snapshots do
|
||||||
lastSnapshot = snapshot
|
|
||||||
|
|
||||||
if index == 1 then
|
if index == 1 then
|
||||||
continue
|
continue
|
||||||
end
|
end
|
||||||
|
|
@ -135,19 +169,73 @@ local function server(options: ServerOptions): ServerSyncer
|
||||||
})
|
})
|
||||||
end
|
end
|
||||||
|
|
||||||
snapshots = { lastSnapshot }
|
snapshots = { snapshots[#snapshots] }
|
||||||
changed = false
|
changed = false
|
||||||
|
|
||||||
for _, player in next, Players:GetPlayers() do
|
sync(player, unpack(payloads))
|
||||||
callback(player, unpack(payloads))
|
end
|
||||||
end
|
else
|
||||||
end, options.interval or 0)
|
-- Only send the most recent state to the client.
|
||||||
|
local previousSnapshot = createSnapshot()
|
||||||
|
local currentSnapshot = table.clone(previousSnapshot)
|
||||||
|
local changed = false
|
||||||
|
|
||||||
return function()
|
function self:connect(callback)
|
||||||
disconnect()
|
local subscriptions = {}
|
||||||
for _, cleanup in next, cleanups do
|
|
||||||
cleanup()
|
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
|
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
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
@ -157,36 +245,10 @@ local function server(options: ServerOptions): ServerSyncer
|
||||||
|
|
||||||
sync(player, {
|
sync(player, {
|
||||||
type = "init",
|
type = "init",
|
||||||
data = snapshots[#snapshots],
|
data = createSnapshot(),
|
||||||
})
|
})
|
||||||
end
|
end
|
||||||
|
|
||||||
function self:_sendPatch(player: Player)
|
|
||||||
assert(sync, "connect() must be called before _sendPatch()")
|
|
||||||
|
|
||||||
if not changed then
|
|
||||||
return
|
|
||||||
end
|
|
||||||
|
|
||||||
local payloads: { SyncPayload } = {}
|
|
||||||
|
|
||||||
for index, snapshot in next, snapshots do
|
|
||||||
if index == 1 then
|
|
||||||
continue
|
|
||||||
end
|
|
||||||
|
|
||||||
table.insert(payloads, {
|
|
||||||
type = "patch",
|
|
||||||
data = patch.diff(snapshots[index - 1], snapshot),
|
|
||||||
})
|
|
||||||
end
|
|
||||||
|
|
||||||
snapshots = { snapshots[#snapshots] }
|
|
||||||
changed = false
|
|
||||||
|
|
||||||
sync(player, unpack(payloads))
|
|
||||||
end
|
|
||||||
|
|
||||||
return self
|
return self
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue