fix: refine reactive proxy logic

This commit is contained in:
littensy 2025-11-28 01:18:57 -08:00
parent b3a76face9
commit b826401bf3
6 changed files with 145 additions and 102 deletions

View file

@ -102,6 +102,22 @@ test("should replicate partial state patches (reactive)", function()
assert(payloads[1].data.a.x == nil, "Expected patch payload to not contain value 'a.x'")
end)
test("should handle nested proxies", function()
local client = {}
local src = Charm.reactive({ (Charm.reactive({})) })
local payloads: {}? = nil
server.addSignalsToClient(client, { src = src })
server.connect(function(_, newPayloads)
payloads = newPayloads
end)
server.flush()
payloads = nil
server.flush()
assert(payloads == nil, "Expected no payloads to be sent for unchanged nested proxy")
end)
test("should init new clients", function()
local clientA = {}
local clientB = {}

View file

@ -26,7 +26,7 @@ local function addSignals(signals: { [string]: Setter | {} })
if type(signal) == "function" then
trackedSignals[key] = signal
else
trackedReactives[key] = getmetatable(signal :: ReactiveProxy).set
trackedReactives[key] = ((signal :: any) :: ReactiveProxy).__set
end
end
end

View file

@ -33,7 +33,7 @@ local globalUpdates: { StateUpdate } = {}
local clients: { [any]: ClientKeys } = {}
local connection: RBXScriptConnection?
local syncCallback: (client: any, payloads: { SyncPayload }) -> ()?
local syncCallback: ((client: any, payloads: { SyncPayload }) -> ())?
local function trackSignal<T>(key: string, getter: () -> T, identifier: unknown)
if trackedStates[key] then
@ -47,8 +47,7 @@ local function trackSignal<T>(key: string, getter: () -> T, identifier: unknown)
local trackedState: TrackedState<T>
local initialValue = getter()
local rawInitialValue = Charm.toRaw(initialValue)
local initialIsReactive = initialValue ~= rawInitialValue
local snapshot = if initialIsReactive then utils.deepCopy(rawInitialValue) else initialValue
local snapshot = if initialValue ~= rawInitialValue then utils.deepCopy(rawInitialValue) else initialValue
local dispose = Charm.effect(function()
local newValue = getter()
@ -62,12 +61,12 @@ local function trackSignal<T>(key: string, getter: () -> T, identifier: unknown)
-- When preserving history, we need to keep track of previous values
-- so we can create granular patches for each change.
if config.preserveHistory then
local oldSnapshot = snapshot
local prevSnapshot = snapshot
snapshot = if isReactive then utils.deepCopy(rawValue) else newValue
table.insert(globalUpdates, {
key = key,
newValue = snapshot,
oldValue = oldSnapshot,
oldValue = prevSnapshot,
})
end
@ -79,7 +78,7 @@ local function trackSignal<T>(key: string, getter: () -> T, identifier: unknown)
identifier = identifier,
currentValue = rawInitialValue,
lastSyncedValue = snapshot,
isReactive = initialIsReactive,
isReactive = initialValue ~= rawInitialValue,
dispose = dispose,
}

View file

@ -49,12 +49,13 @@ local function signalToAtom<T>(getter: () -> T, setter: (value: ((T) -> T) | T)
end
--[[
Returns a deep copy of the given table.
Returns a deep copy of the given table. Skips tables with metatables to
prevent copying reactive proxies.
]]
local function deepCopy<T>(object: T): T
local copy: any = table.clone(object :: any)
for key, value in copy do
if type(value) == "table" then
if type(value) == "table" and getmetatable(value) == nil then
copy[key] = deepCopy(value)
end
end