Skip to content

Instantly share code, notes, and snippets.

@Mark-Marks
Last active February 13, 2026 21:08
Show Gist options
  • Select an option

  • Save Mark-Marks/4404a813a6f80b6b087d9507509af59a to your computer and use it in GitHub Desktop.

Select an option

Save Mark-Marks/4404a813a6f80b6b087d9507509af59a to your computer and use it in GitHub Desktop.
Better performance for immutable updates in Lyra
diff --git a/src/Session.luau b/src/Session.luau
index 256e1d1..1c6661e 100644
--- a/src/Session.luau
+++ b/src/Session.luau
@@ -61,8 +61,8 @@ local noYield = require(script.Parent.noYield)
.isSaved (self: Session<T>) -> boolean
-- Internal state mutation
- .setData (self: Session<T>, data: T) -> ()
- .mutateKey (self: Session<T>, newData: T) -> ()
+ .setData (self: Session<T>, data: T, immutableUpdate: boolean?) -> ()
+ .mutateKey (self: Session<T>, newData: T, immutableUpdate: boolean?) -> ()
-- Autosave management
.startAutosaving (self: Session<T>) -> ()
@@ -81,8 +81,8 @@ type SessionImpl<T> = {
orphanFile: (self: Session<T>, file: Types.File) -> (),
writeRecord: (self: Session<T>, txInfo: Types.TxInfo) -> Promise.TPromise<any>,
isSaved: (self: Session<T>) -> boolean,
- setData: (self: Session<T>, data: T) -> (),
- mutateKey: (self: Session<T>, newData: T) -> (),
+ setData: (self: Session<T>, data: T, immutableUpdate: boolean?) -> (),
+ mutateKey: (self: Session<T>, newData: T, immutableUpdate: boolean?) -> (),
startAutosaving: (self: Session<T>) -> (),
stopAutosaving: (self: Session<T>) -> (),
unload: (self: Session<T>) -> Promise.Promise,
@@ -778,13 +778,19 @@ end
@within Session
@private
@param data any -- The new data value.
+ @param immutableUpdate boolean? -- Whether this originates from an immutable data update. This triggers a faster freeze function.
]=]
-function Session:setData(data: any): ()
+function Session:setData(data: any, immutableUpdate: boolean?): ()
-- Generate a unique ID for this mutation and add it to the changeSet.
local mutationId = HttpService:GenerateGUID(false)
self.changeSet[mutationId] = true
- -- Freeze and update the data reference.
- Tables.freezeDeep(data)
+ -- Make sure the data is frozen and update the data reference.
+ if immutableUpdate == true then
+ -- POTENTIALLY UNSAFE: See function doc comment
+ Tables.ensureFrozen(data)
+ else
+ Tables.freezeDeep(data)
+ end
self.data = data
end
@@ -795,12 +801,13 @@ end
@within Session
@private
@param newData any -- The new data value.
+ @param immutableUpdate boolean? -- Whether this originates from an immutable data update. This triggers a faster freeze function.
]=]
-function Session:mutateKey(newData: any): ()
+function Session:mutateKey(newData: any, immutableUpdate: boolean?): ()
local oldData = self.data -- Store reference to previous data
-- Update internal data (sets .data and marks changeSet)
- self:setData(newData)
+ self:setData(newData, immutableUpdate)
-- Trigger change callbacks asynchronously
for _, callback in self.ctx.changedCallbacks do
@@ -1008,12 +1015,22 @@ local function updateInternal(
end
-- Ensure the new data is frozen
- Tables.freezeDeep(nextData :: any)
+ if immutable then
+ -- POTENTIALLY UNSAFE: See function doc comment
+ Tables.ensureFrozen(nextData :: any)
+ else
+ Tables.freezeDeep(nextData :: any)
+ end
-- Check if the data actually changed
- if Tables.equalsDeep(nextData :: any, currentData :: any) then
- logger:log("trace", "transform resulted in no data change, resolving true")
- return resolve(true) -- Considered successful, but data remains same
+ -- We probably don't want to use Tables.equalsDeep if we're not in an immutable context
+ -- POTENTIALLY UNSAFE: See function doc comment for Tables.immutableEqualsDeep
+ if
+ (immutable and Tables.immutableEqualsDeep(nextData :: any, currentData :: any))
+ or (immutable == false and Tables.equalsDeep(nextData :: any, currentData :: any))
+ then
+ logger:log("trace", "transform resulted in no data change, resolving true")
+ return resolve(true) -- Considered successful, but data remains same
end
if immutable == false then
@@ -1021,7 +1038,7 @@ local function updateInternal(
end
-- Data changed and is valid, apply the mutation
- self:mutateKey(nextData)
+ self:mutateKey(nextData, immutable)
logger:log("trace", "update applied successfully")
diff --git a/src/Tables.luau b/src/Tables.luau
index e32c053..ae5fe4c 100644
--- a/src/Tables.luau
+++ b/src/Tables.luau
@@ -98,6 +98,31 @@ local function equalsDeep(a: { [any]: any }, b: { [any]: any }): boolean
return true
end
+-- POTENTIALLY UNSAFE: If the original, unmodified data isn't ensured to be completely frozen, non-frozen tables within frozen tables won't be able to be detected as changed, be it user intention or error.
+local function immutableEqualsDeep(a: { [any]: any }, b: { [any]: any }): boolean
+ if typeof(a) ~= "table" or typeof(b) ~= "table" then
+ return a == b
+ end
+
+ if table.isfrozen(a) or table.isfrozen(b) then
+ return a == b
+ end
+
+ for key, value in a do
+ if not equalsDeep(value, b[key]) then
+ return false
+ end
+ end
+
+ for key, value in b do
+ if not equalsDeep(value, a[key]) then
+ return false
+ end
+ end
+
+ return true
+end
+
local function freezeDeep<T>(t: T): ()
if typeof(t) ~= "table" then
return
@@ -114,6 +139,24 @@ local function freezeDeep<T>(t: T): ()
end
end
+--- POTENTIALLY UNSAFE: Assume that if a table is frozen, all tables underneath it are also frozen.
+--- This improves performance for immutable updates at the cost of potentially not freezing some tables, be it user intention or error.
+local function ensureFrozen<T>(t: T): ()
+ if typeof(t) ~= "table" then
+ return
+ end
+
+ if table.isfrozen(t) == true then
+ return
+ end
+
+ table.freeze(t)
+
+ for _, value in t :: any do
+ ensureFrozen(value)
+ end
+end
+
-- Performs a deep reconciliation of a target table with a source table,
-- applying Copy-On-Write (COW) semantics for the target.
-- The function assumes 'target' and 'source' are non-nil tables.
@@ -223,7 +266,9 @@ return {
mergeDeep = mergeDeep,
mergeShallow = mergeShallow,
equalsDeep = equalsDeep,
+ immutableEqualsDeep = immutableEqualsDeep,
freezeDeep = freezeDeep,
+ ensureFrozen = ensureFrozen,
map = map,
reconcileDeep = reconcileDeep,
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment