diff --git a/src/indexes.luau b/src/indexes.luau index d13d1c9..aa533f4 100644 --- a/src/indexes.luau +++ b/src/indexes.luau @@ -13,24 +13,26 @@ local function indexes( component: (Source, K, Source) -> (Obj, number?) ): Source> local update_count = 0 - local caches = {} :: Map (), - present: (boolean?) -> boolean, + local scopes = {} :: Map (), + object: Obj, value: V?, value_source: (V?) -> V, - object: Obj, - delay: number, - timeout: { cancel: boolean }?, count: number, + delay: number, + present: (boolean?) -> boolean, + timeout: { cancel: boolean }?, }> local output = source({} :: Array) local function update_output() - local array = table.create(#caches) - for _, cache in caches do - table.insert(array, cache.object) + local objects = table.create(4) + + for _, scope in scopes do + table.insert(objects, scope.object) end - output(array) + + output(objects) end effect(function() @@ -41,11 +43,11 @@ local function indexes( local children_need_update = false -- set to true if a scope is created or destroyed - -- process data + -- create or update scopes for i, v in data do - local cache = caches[i] + local scope = scopes[i] - if cache == nil then -- create new scope and create component + if scope == nil then -- create new scope and create component local value_source = source(v) local present = source(false) @@ -60,46 +62,47 @@ local function indexes( children_need_update = true - caches[i] = { - count = count, + scopes[i] = { + destroy = destroy, + object = object, value = v, - destroy_scope = destroy, value_source = value_source, - present = present, + count = count, delay = delay or 0, - object = object + present = present, + timeout = nil, } - else -- update source - cache.count = count + else -- update scope + scope.count = count - if cache.value ~= v then - if cache.timeout then - cache.timeout.cancel = true - cache.timeout = nil - cache.present(true) + if scope.value ~= v then + if scope.timeout then -- index is in input table again; cancel destruction + scope.timeout.cancel = true + scope.timeout = nil + scope.present(true) end - cache.value = v - cache.value_source(v) + scope.value = v + scope.value_source(v) end end end - -- remove old indexes - for i, cache in caches do - if cache.count < count then -- if count is not latest then value is no longer in the input table - cache.present(false) + -- destroy scopes + for i, scope in scopes do + if scope.count < count then -- if count is not latest then index is no longer in the input table + scope.present(false) - if cache.delay == 0 then - cache.destroy_scope() - caches[i] = nil + if scope.delay == 0 then + scope.destroy() + scopes[i] = nil children_need_update = true else - cache.value = nil - if cache.timeout == nil then - cache.timeout = timeout(cache.delay, function() -- todo: avoid redundant updates (e.g. indexes() input is cleared) - cache.destroy_scope() - caches[i] = nil + scope.value = nil -- set to nil for the `scope.value ~= v` check + if scope.timeout == nil then + scope.timeout = timeout(scope.delay, function() -- todo: possible redundant updates + scope.destroy() + scopes[i] = nil update_output() end) end diff --git a/src/switch.luau b/src/switch.luau index 38a7586..ee1af28 100644 --- a/src/switch.luau +++ b/src/switch.luau @@ -6,26 +6,26 @@ local timeout = require "./timeout" () type Array = { T } type Map = { [K]: V } type Source = () -> T -type Component = (Source) -> T +type Component = (Source) -> (T, number?) local function switch_map( input: Source, map: Map> ): Source> - local output = source(nil :: nil | Obj | Array) - - local caches = {} :: Map (), - present: (boolean?) -> boolean, + local scopes = {} :: Map (), object: Obj, delay: number, + present: (boolean?) -> boolean, timeout: { cancel: boolean }? }> + local output = source(nil :: nil | Obj | Array) local function update_output() local objects = {} - for _, cache in caches do - table.insert(objects, cache.object) + + for _, scope in scopes do + table.insert(objects, scope.object) end output( @@ -38,33 +38,36 @@ local function switch_map( effect(function() local key: K? = input() - for k, cache in caches do + -- destroy (or queue destroy) all scopes not associated with the input key + for k, scope in scopes do if k == key then continue end - cache.present(false) - if cache.delay == 0 then - cache.destroy_scope() - caches[k] = nil + scope.present(false) + + if scope.delay == 0 then + scope.destroy() + scopes[k] = nil else - if cache.timeout == nil then - cache.timeout = timeout(cache.delay, function() - cache.destroy_scope() - caches[k] = nil + if scope.timeout == nil then + scope.timeout = timeout(scope.delay, function() + scope.destroy() + scopes[k] = nil update_output() end) end end end + -- create new scope or abort destruction of existing scope if key exists if key ~= nil then - local cache = caches[key] + local scope = scopes[key] - if cache then - cache.present(true) + if scope then + scope.present(true) - if cache.timeout then - cache.timeout.cancel = true - cache.timeout = nil + if scope.timeout then + scope.timeout.cancel = true + scope.timeout = nil end else local component = map[key] @@ -85,11 +88,11 @@ local function switch_map( present(true) - caches[key] = { - destroy_scope = destroy, - present = present, + scopes[key] = { + destroy = destroy, object = object, delay = delay or 0, + present = present, timeout = nil } end diff --git a/src/values.luau b/src/values.luau index b9cd7a8..c4a3563 100644 --- a/src/values.luau +++ b/src/values.luau @@ -13,24 +13,26 @@ local function values( component: (V, Source, Source) -> (Obj, number?) ): Source> local update_count = 0 - local caches = {} :: Map (), - present: (boolean?) -> boolean, + local scopes = {} :: Map (), + object: Obj, index: K?, index_source: (K?) -> K, - object: Obj, - delay: number, - timeout: { cancel: boolean }?, count: number, + delay: number, + present: (boolean?) -> boolean, + timeout: { cancel: boolean }?, }> local output = source({} :: Array) local function update_output() - local array = table.create(16) - for _, cache in caches do - table.insert(array, cache.object) + local objects = table.create(4) + + for _, scope in scopes do + table.insert(objects, scope.object) end - output(array) + + output(objects) end effect(function() @@ -41,7 +43,7 @@ local function values( local children_need_update = false -- set to true if a scope is created or destroyed - if flags.strict then + if flags.strict then -- check for duplicate values local map = {} for _, v in data do if map[v] then @@ -51,11 +53,11 @@ local function values( end end - -- process data + -- create or update scopes for i, v in data do - local cache = caches[v] + local scope = scopes[v] - if cache == nil then -- create new scope and create component + if scope == nil then -- create new scope and create component local index_source = source(i) local present = source(false) @@ -70,46 +72,47 @@ local function values( children_need_update = true - caches[v] = { - count = count, + scopes[v] = { + destroy = destroy, + object = object, index = i, - destroy_scope = destroy, index_source = index_source, - present = present, + count = count, delay = delay or 0, - object = object + present = present, + timeout = nil, } - else -- update source - cache.count = count + else -- update scope + scope.count = count - if cache.index ~= i then - if cache.timeout then - cache.timeout.cancel = true - cache.timeout = nil - cache.present(true) + if scope.index ~= i then + if scope.timeout then -- value is in input table again; cancel destruction + scope.timeout.cancel = true + scope.timeout = nil + scope.present(true) end - cache.index = i - cache.index_source(i) + scope.index = i + scope.index_source(i) end end end - -- remove old values - for v, cache in caches do - if cache.count < count then -- if count is not latest then value is no longer in the input table - cache.present(false) + -- destroy scopes + for v, scope in scopes do + if scope.count < count then -- if count is not latest then value is no longer in the input table + scope.present(false) - if cache.delay == 0 then - cache.destroy_scope() - caches[v] = nil + if scope.delay == 0 then + scope.destroy() + scopes[v] = nil children_need_update = true else - cache.index = nil - if cache.timeout == nil then - cache.timeout = timeout(cache.delay, function() -- todo: avoid redundant updates (e.g. values() input is cleared) - cache.destroy_scope() - caches[v] = nil + scope.index = nil -- set to nil for the `scope.index ~= i` check + if scope.timeout == nil then + scope.timeout = timeout(scope.delay, function() -- todo: possible redundant updates + scope.destroy() + scopes[v] = nil update_output() end) end diff --git a/test/tests.luau b/test/tests.luau index 1df61e9..9a2c73f 100644 --- a/test/tests.luau +++ b/test/tests.luau @@ -2004,6 +2004,26 @@ TEST("values()", wrap_root(function() CHECK(cleaned_counts[3] == nil) end end + + do CASE "delayed destruction deferred" + local input = source {} + local output = values(input, function() + return {}, 1 + end) + + local count = 0 + effect(function() output(); count += 1 end) + + input { 1, 2, 3 } + CHECK(count == 2) + + input {} + CHECK(count == 2) + + step(1 + 0.01) + --CHECK(count == 3) + CHECK(count == 5) + end end)) TEST("spring()", wrap_root(function()