From 0b4dd2f53bbbea0a7143c6143be594708bd2dcb1 Mon Sep 17 00:00:00 2001 From: Richard Date: Wed, 25 Sep 2024 22:10:52 -0700 Subject: [PATCH] fix(sync): fill sparse arrays with `null` --- src/sync/patch.luau | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/src/sync/patch.luau b/src/sync/patch.luau index c59b166..388e115 100644 --- a/src/sync/patch.luau +++ b/src/sync/patch.luau @@ -10,6 +10,35 @@ local function isNone(value: any): boolean return type(value) == "table" and value.__none == "__none" end +--[=[ + Some sparse arrays lose outlying values when serialized to JSON. For + example, `[1, 2, nil, nil, 5]` becomes `[1, 2]` in some cases. This loss of + data can cause hard-to-debug issues down the line. + + This function coerces the input object into being interpreted as a dense + array, so that the aforementioned `[1, 2]` result would instead be + `[1, 2, nil, nil, 5]`. +]=] +local function coerceSparseArray(object: any) + -- The object must be a table with a numeric key + if type(object) ~= "table" or type((next(object))) ~= "number" then + return object + end + + local maxn = table.maxn(object :: any) + + -- If the table is already dense, return it as-is + if maxn == #object then + return object + end + + local result = table.create(maxn, 0) + + table.move(object, 1, maxn, 1, result) + + return result +end + local function diff(prevState: { [any]: any }, nextState: { [any]: any }) local patches = table.clone(nextState) @@ -25,6 +54,9 @@ local function diff(prevState: { [any]: any }, nextState: { [any]: any }) end end + -- Coerce the patches into being interpreted as dense arrays. + patches = coerceSparseArray(patches) + if _G.__DEV__ then for key, value in next, prevState do validate(value, key)