mirror of
https://github.com/littensy/charm.git
synced 2026-10-20 17:51:37 +00:00
fix: check for nil diffs
This commit is contained in:
parent
9f66803f04
commit
366fc668a2
3 changed files with 67 additions and 27 deletions
|
|
@ -210,6 +210,26 @@ test("should serialize state", function()
|
||||||
assert(patch.isNone(payloads[1].data.a["5"]), "Expected index 5 to be serialized as string key")
|
assert(patch.isNone(payloads[1].data.a["5"]), "Expected index 5 to be serialized as string key")
|
||||||
end)
|
end)
|
||||||
|
|
||||||
|
test("should not send unchanged state", function()
|
||||||
|
local client = {}
|
||||||
|
local a = Charm.atom(0)
|
||||||
|
local payloads
|
||||||
|
|
||||||
|
server.addSignalsToClient(client, { a = a })
|
||||||
|
server.connect(function(_, p)
|
||||||
|
payloads = p
|
||||||
|
end)
|
||||||
|
|
||||||
|
server.flush()
|
||||||
|
assert(payloads[1].type == "init", "Expected first payload to be init")
|
||||||
|
assert(payloads[1].data.a == 0, "Expected init payload to contain atom 'a'")
|
||||||
|
|
||||||
|
payloads = {}
|
||||||
|
a(0)
|
||||||
|
server.flush()
|
||||||
|
assert(#payloads == 0, "Expected no payloads to be sent for unchanged state")
|
||||||
|
end)
|
||||||
|
|
||||||
describe("historical", function()
|
describe("historical", function()
|
||||||
beforeEach(function()
|
beforeEach(function()
|
||||||
config.preserveHistory = true
|
config.preserveHistory = true
|
||||||
|
|
@ -344,6 +364,26 @@ describe("historical", function()
|
||||||
assert(payloads[4].type == "patch", "Expected fourth payload to be patch")
|
assert(payloads[4].type == "patch", "Expected fourth payload to be patch")
|
||||||
assert(payloads[4].data.b == 2, "Expected patch payload to contain atom 'b'")
|
assert(payloads[4].data.b == 2, "Expected patch payload to contain atom 'b'")
|
||||||
end)
|
end)
|
||||||
|
|
||||||
|
test("should not send unchanged state", function()
|
||||||
|
local client = {}
|
||||||
|
local a = Charm.atom(0)
|
||||||
|
local payloads
|
||||||
|
|
||||||
|
server.addSignalsToClient(client, { a = a })
|
||||||
|
server.connect(function(_, p)
|
||||||
|
payloads = p
|
||||||
|
end)
|
||||||
|
|
||||||
|
server.flush()
|
||||||
|
assert(payloads[1].type == "init", "Expected first payload to be init")
|
||||||
|
assert(payloads[1].data.a == 0, "Expected init payload to contain atom 'a'")
|
||||||
|
|
||||||
|
payloads = {}
|
||||||
|
a(0)
|
||||||
|
server.flush()
|
||||||
|
assert(#payloads == 0, "Expected no payloads to be sent for unchanged state")
|
||||||
|
end)
|
||||||
end)
|
end)
|
||||||
|
|
||||||
return {}
|
return {}
|
||||||
|
|
|
||||||
|
|
@ -75,24 +75,24 @@ local function trackReactive<T>(key: string, proxy: ReactiveProxy<T>)
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
local meta = getmetatable(proxy)
|
local api = getmetatable(proxy)
|
||||||
local snapshot = utils.deepCopy(meta.value)
|
local snapshot = utils.deepCopy(api.value)
|
||||||
local firstRun = true
|
local firstRun = true
|
||||||
|
|
||||||
local trackedState = {
|
local trackedState = {
|
||||||
target = proxy,
|
target = proxy,
|
||||||
currentValue = meta.value,
|
currentValue = api.value,
|
||||||
lastSyncedValue = snapshot,
|
lastSyncedValue = snapshot,
|
||||||
isReactive = true,
|
isReactive = true,
|
||||||
}
|
}
|
||||||
|
|
||||||
trackedState.dispose = Charm.effect(function()
|
trackedState.dispose = Charm.effect(function()
|
||||||
meta.track()
|
api.track()
|
||||||
if not config.preserveHistory or firstRun then
|
if not config.preserveHistory or firstRun then
|
||||||
firstRun = false
|
firstRun = false
|
||||||
else
|
else
|
||||||
local oldSnapshot = snapshot
|
local oldSnapshot = snapshot
|
||||||
snapshot = utils.deepCopy(meta.value)
|
snapshot = utils.deepCopy(api.value)
|
||||||
table.insert(globalUpdates, {
|
table.insert(globalUpdates, {
|
||||||
key = key,
|
key = key,
|
||||||
newValue = snapshot,
|
newValue = snapshot,
|
||||||
|
|
@ -132,8 +132,8 @@ local function diffGlobalUpdateBuffer(): { any }
|
||||||
local patchCount = #globalUpdates
|
local patchCount = #globalUpdates
|
||||||
local patches = table.create(patchCount)
|
local patches = table.create(patchCount)
|
||||||
|
|
||||||
for index, update in globalUpdates do
|
for _, update in globalUpdates do
|
||||||
patches[index] = patch.diff(update.oldValue, update.newValue)
|
table.insert(patches, patch.diff(update.oldValue, update.newValue))
|
||||||
end
|
end
|
||||||
|
|
||||||
return patches
|
return patches
|
||||||
|
|
@ -176,7 +176,7 @@ local function selectFromGlobalPatchList(client: any, globalPatches: { any }): {
|
||||||
|
|
||||||
-- If a pending signal was never updated, we still need to send its initial
|
-- If a pending signal was never updated, we still need to send its initial
|
||||||
-- state. This can happen if a signal was registered after its last change.
|
-- state. This can happen if a signal was registered after its last change.
|
||||||
local initData: { [string]: any } = {}
|
local initData = {}
|
||||||
|
|
||||||
for key, status in clientKeys do
|
for key, status in clientKeys do
|
||||||
local trackedState = trackedStates[key]
|
local trackedState = trackedStates[key]
|
||||||
|
|
@ -194,7 +194,7 @@ local function selectFromGlobalPatchList(client: any, globalPatches: { any }): {
|
||||||
return payloads
|
return payloads
|
||||||
end
|
end
|
||||||
|
|
||||||
local function diffGlobalState(): any
|
local function diffGlobalState(): { [string]: any }?
|
||||||
local currentGlobalState: { [string]: any } = {}
|
local currentGlobalState: { [string]: any } = {}
|
||||||
local previousGlobalState: { [string]: any } = {}
|
local previousGlobalState: { [string]: any } = {}
|
||||||
|
|
||||||
|
|
@ -211,7 +211,7 @@ local function diffGlobalState(): any
|
||||||
return patch.diff(previousGlobalState, currentGlobalState)
|
return patch.diff(previousGlobalState, currentGlobalState)
|
||||||
end
|
end
|
||||||
|
|
||||||
local function selectFromGlobalPatch(client: any, globalPatch: any): { SyncPayload }?
|
local function selectFromGlobalPatch(client: any, globalPatch: { [string]: any }?): { SyncPayload }?
|
||||||
local clientKeys = clients[client]
|
local clientKeys = clients[client]
|
||||||
|
|
||||||
if not clientKeys then
|
if not clientKeys then
|
||||||
|
|
@ -227,7 +227,7 @@ local function selectFromGlobalPatch(client: any, globalPatch: any): { SyncPaylo
|
||||||
for key, status in clientKeys do
|
for key, status in clientKeys do
|
||||||
local trackedState = trackedStates[key]
|
local trackedState = trackedStates[key]
|
||||||
|
|
||||||
if status == LISTENING_FOR_CHANGES then
|
if status == LISTENING_FOR_CHANGES and globalPatch then
|
||||||
patchData[key] = globalPatch[key]
|
patchData[key] = globalPatch[key]
|
||||||
elseif status == PENDING_INITIAL_STATE then
|
elseif status == PENDING_INITIAL_STATE then
|
||||||
initData[key] = trackedState.currentValue
|
initData[key] = trackedState.currentValue
|
||||||
|
|
@ -323,11 +323,11 @@ local function flush()
|
||||||
end
|
end
|
||||||
|
|
||||||
local granular = config.preserveHistory
|
local granular = config.preserveHistory
|
||||||
local globalPatch = if granular then diffGlobalUpdateBuffer() else diffGlobalState()
|
local globalPatch: { [any]: any }? = if granular then diffGlobalUpdateBuffer() else diffGlobalState()
|
||||||
|
|
||||||
for client in clients do
|
for client in clients do
|
||||||
local payloads = if granular
|
local payloads = if granular
|
||||||
then selectFromGlobalPatchList(client, globalPatch)
|
then selectFromGlobalPatchList(client, globalPatch :: { any })
|
||||||
else selectFromGlobalPatch(client, globalPatch)
|
else selectFromGlobalPatch(client, globalPatch)
|
||||||
|
|
||||||
if payloads and payloads[1] then
|
if payloads and payloads[1] then
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ export type Link = system.Link
|
||||||
|
|
||||||
export type Atom<T = any> = (...((T) -> T) | T) -> T
|
export type Atom<T = any> = (...((T) -> T) | T) -> T
|
||||||
|
|
||||||
type ReactiveProxyMeta<T = {}> = {
|
type ReactiveProxyApi<T = {}> = {
|
||||||
value: T,
|
value: T,
|
||||||
track: () -> (),
|
track: () -> (),
|
||||||
set: (T | (T) -> ()?) -> (),
|
set: (T | (T) -> ()?) -> (),
|
||||||
|
|
@ -17,7 +17,7 @@ type ReactiveProxyProps<T = {}> = {
|
||||||
__signals: { [unknown]: () -> unknown },
|
__signals: { [unknown]: () -> unknown },
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ReactiveProxy<T = {}> = T & typeof(setmetatable({} :: ReactiveProxyProps<T>, {} :: ReactiveProxyMeta<T>))
|
export type ReactiveProxy<T = {}> = T & typeof(setmetatable({} :: ReactiveProxyProps<T>, {} :: ReactiveProxyApi<T>))
|
||||||
|
|
||||||
type EffectScopeNode = ReactiveNode & {
|
type EffectScopeNode = ReactiveNode & {
|
||||||
cleanups: { () -> () },
|
cleanups: { () -> () },
|
||||||
|
|
@ -855,7 +855,7 @@ end
|
||||||
]]
|
]]
|
||||||
local function reactive<T>(initialValue: T & {}): (T, (update: (T) -> () | T?) -> ())
|
local function reactive<T>(initialValue: T & {}): (T, (update: (T) -> () | T?) -> ())
|
||||||
local track = signal(nil)
|
local track = signal(nil)
|
||||||
local proxyMeta = { value = initialValue, track = track }
|
local proxyApi = { value = initialValue, track = track }
|
||||||
local proxyMap = setmetatable({} :: { [{}]: any }, { __mode = "v" })
|
local proxyMap = setmetatable({} :: { [{}]: any }, { __mode = "v" })
|
||||||
|
|
||||||
local function trackProperty(cache: { [any]: () -> any }, target: {}, key: any)
|
local function trackProperty(cache: { [any]: () -> any }, target: {}, key: any)
|
||||||
|
|
@ -876,11 +876,11 @@ local function reactive<T>(initialValue: T & {}): (T, (update: (T) -> () | T?) -
|
||||||
proxyMap[target] = setmetatable({
|
proxyMap[target] = setmetatable({
|
||||||
__target = target,
|
__target = target,
|
||||||
__signals = setmetatable({}, { __mode = "v" }),
|
__signals = setmetatable({}, { __mode = "v" }),
|
||||||
}, proxyMeta)
|
}, proxyApi)
|
||||||
return proxyMap[target]
|
return proxyMap[target]
|
||||||
end
|
end
|
||||||
|
|
||||||
function proxyMeta.__index(self: ReactiveProxy, key: any): any
|
function proxyApi.__index(self: ReactiveProxy, key: any): any
|
||||||
if activeSub then
|
if activeSub then
|
||||||
trackProperty(self.__signals, self.__target, key)
|
trackProperty(self.__signals, self.__target, key)
|
||||||
end
|
end
|
||||||
|
|
@ -888,8 +888,8 @@ local function reactive<T>(initialValue: T & {}): (T, (update: (T) -> () | T?) -
|
||||||
return if type(value) == "table" then createProxy(value) else value
|
return if type(value) == "table" then createProxy(value) else value
|
||||||
end
|
end
|
||||||
|
|
||||||
function proxyMeta.__newindex(self: ReactiveProxy, key: any, value: any)
|
function proxyApi.__newindex(self: ReactiveProxy, key: any, value: any)
|
||||||
if type(value) == "table" and getmetatable(value) == proxyMeta then
|
if type(value) == "table" and getmetatable(value) == proxyApi then
|
||||||
value = value.__target
|
value = value.__target
|
||||||
end
|
end
|
||||||
if self.__target[key] ~= value then
|
if self.__target[key] ~= value then
|
||||||
|
|
@ -898,7 +898,7 @@ local function reactive<T>(initialValue: T & {}): (T, (update: (T) -> () | T?) -
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
function proxyMeta.__iter(self: ReactiveProxy)
|
function proxyApi.__iter(self: ReactiveProxy)
|
||||||
track()
|
track()
|
||||||
local function iterator(target: {}, index: any)
|
local function iterator(target: {}, index: any)
|
||||||
local key, value = next(target, index)
|
local key, value = next(target, index)
|
||||||
|
|
@ -907,12 +907,12 @@ local function reactive<T>(initialValue: T & {}): (T, (update: (T) -> () | T?) -
|
||||||
return iterator, self.__target
|
return iterator, self.__target
|
||||||
end
|
end
|
||||||
|
|
||||||
function proxyMeta.__len(self: ReactiveProxy)
|
function proxyApi.__len(self: ReactiveProxy)
|
||||||
track()
|
track()
|
||||||
return #self.__target
|
return #self.__target
|
||||||
end
|
end
|
||||||
|
|
||||||
function proxyMeta.set(update: ((T) -> ()) | T?)
|
function proxyApi.set(update: ((T) -> ()) | T?)
|
||||||
if type(update) == "function" then
|
if type(update) == "function" then
|
||||||
update(initialValue)
|
update(initialValue)
|
||||||
elseif type(update) == "table" then
|
elseif type(update) == "table" then
|
||||||
|
|
@ -924,7 +924,7 @@ local function reactive<T>(initialValue: T & {}): (T, (update: (T) -> () | T?) -
|
||||||
trigger(track)
|
trigger(track)
|
||||||
end
|
end
|
||||||
|
|
||||||
return createProxy(initialValue), proxyMeta.set
|
return createProxy(initialValue), proxyApi.set
|
||||||
end
|
end
|
||||||
|
|
||||||
--[[
|
--[[
|
||||||
|
|
@ -938,10 +938,10 @@ end
|
||||||
local function toRaw<T>(value: T): T
|
local function toRaw<T>(value: T): T
|
||||||
if type(value) == "table" and getmetatable(value) ~= nil then
|
if type(value) == "table" and getmetatable(value) ~= nil then
|
||||||
local proxy: ReactiveProxy = value
|
local proxy: ReactiveProxy = value
|
||||||
local meta = getmetatable(proxy)
|
local api = getmetatable(proxy)
|
||||||
if meta.value == proxy.__target then
|
if api.value == proxy.__target then
|
||||||
-- Track the root proxy since nested proxies are already tracked
|
-- Track the root proxy since nested proxies are already tracked
|
||||||
meta.track()
|
api.track()
|
||||||
end
|
end
|
||||||
return proxy.__target :: any
|
return proxy.__target :: any
|
||||||
else
|
else
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue