Disallow creation of nested tracking scopes

This commit is contained in:
aaron 2023-09-25 11:44:24 +01:00
parent c0c2166edf
commit 38342b3805
6 changed files with 256 additions and 109 deletions

View file

@ -10,7 +10,7 @@ export type StartNode<T> = {
export type Node<T> = {
cache: T,
effect: ((T) -> T) | false,
effect: ((T) -> T) | "owner" | "untracked",
cleanups: { () -> () } | false,
parents: { owner: StartNode<T>?, [number]: StartNode<T> },
[number]: Node<T>
@ -50,8 +50,8 @@ local function get_owning_scope(): Node<unknown>
if not scope then
local caller_name = debug.info(2, "n")
return throw(`cannot use {caller_name}() in non-reactive scope, must be used within a root() or mount() callback`)
elseif scope.effect then
throw("owning scope is not stable; are you trying to derive a new source from within a side-effect?")
elseif scope.effect ~= "owner" then
throw("reactive scope is not an owning scope; new effects cannot be created in side-effects")
end
return scope
end
@ -117,8 +117,6 @@ local function destroy<T>(node: Node<T>)
run_cleanups(node)
unparent(node)
node.effect = false
if node.parents.owner then
remove_child(node.parents.owner, node)
node.parents.owner = nil
@ -168,24 +166,13 @@ local function update_from<T>(node: StartNode<T>, n0: number)
-- unparent all children and queue for eval
do
local i = 1
local child = node[i]
local child = node[1]
while child do
--assert(child.parents.owner)
unparent(child)
n += 1
update_queue[n] = child
local next_child = node[i]
-- children who have this parent as an owner will not be unparented
-- if such a child is encountered then skip it
if next_child == child then
i += 1
next_child = node[i]
end
child = next_child
child = node[1]
end
end
@ -194,7 +181,7 @@ local function update_from<T>(node: StartNode<T>, n0: number)
-- evaluate all queued children
for i = n0 + 1, n do
local child = update_queue[i]
if not child.effect then continue end
assert(type(child.effect) == "function")
if evaluate_node(child) then
update_from(child, n)
@ -212,12 +199,12 @@ end
local function track<T>(node: StartNode<T>)
local scope = get_scope()
if scope and scope.effect then -- do not track nodes with no effect
if scope and type(scope.effect) == "function" then -- do not track nodes with no effect
add_child(node, scope)
end
end
local function create_node<T>(value: T, effect: false | (T) -> T): Node<T>
local function create_node<T>(value: T, effect: "owner" | (T) -> T): Node<T>
return {
cache = value,
effect = effect,

View file

@ -30,7 +30,7 @@ end
local function indexes<K, VI, VO>(input: () -> Map<K, VI>, transform: (() -> VI, K) -> VO): () -> { VO }
local owner = get_owning_scope()
local subowner = create_node(false, false)
local subowner = create_node(false, "owner")
set_owner(subowner, owner)
local input_cache = {} :: Map<K, VI>
@ -67,7 +67,7 @@ local function indexes<K, VI, VO>(input: () -> Map<K, VI>, transform: (() -> VI,
if cv ~= v then
if cv == nil then -- create new scope and run transform
local scope = create_node(false, false)
local scope = create_node(false, "owner")
scopes[i] = scope :: Node<any>
local node = create_start_node(v)
@ -112,6 +112,7 @@ local function indexes<K, VI, VO>(input: () -> Map<K, VI>, transform: (() -> VI,
local node = create_node(false :: any, function()
return update_children(input())
end)
set_owner(node, owner)
evaluate_node(node)
@ -124,7 +125,7 @@ end
local function values<K, VI, VO>(input: () -> Map<K, VI>, transform: (VI, () -> K) -> VO): () -> { VO }
local owner = get_owning_scope()
local subowner = create_node(false, false)
local subowner = create_node(false, "owner")
set_owner(subowner, owner)
local cur_input_cache_up = {} :: Map<VI, K>
@ -155,7 +156,7 @@ local function values<K, VI, VO>(input: () -> Map<K, VI>, transform: (VI, () ->
local cv = cur_input_cache[v]
if cv == nil then -- create new scope and run transform
local scope = create_node(false, false)
local scope = create_node(false, "owner")
scopes[v] = scope :: Node<any>
local node = create_start_node(i)
@ -214,6 +215,7 @@ local function values<K, VI, VO>(input: () -> Map<K, VI>, transform: (VI, () ->
local node = create_node(false :: any, function()
return update_children(input())
end)
set_owner(node, owner)
evaluate_node(node)

View file

@ -11,7 +11,7 @@ local destroy = graph.destroy
local refs = {}
local function root<T...>(fn: (destroy: () -> ()) -> T...): T...
local node = create_node(false, false)
local node = create_node(false, "owner")
refs[node] = true -- prevent gc of root node

View file

@ -38,7 +38,7 @@ local function switch<T, U>(source: () -> T): (map: Map<T, ((() -> U)?)>) -> ()
throw("map must map a value to a function")
end
local new_scope = create_node(false, false)
local new_scope = create_node(false, "owner")
last_scope = new_scope :: Node<any>
set_owner(new_scope, owner)

View file

@ -13,7 +13,7 @@ local function untrack<T>(source: () -> T): T
-- sources are only tracked if the node in scope has an effect
local effect = scope.effect
scope.effect = false
scope.effect = "untracked"
local ok, result = pcall(source)