perf(push_child): defer unparent until evaluate, keeping unchanged ordered parents

This commit is contained in:
ernisto 2026-07-07 15:38:09 -03:00
parent 19eaeb424f
commit d7df0d9850

View file

@ -1,5 +1,8 @@
local flags = require "./flags" local flags = require "./flags"
local TEMPORALLY_UNPARENTED = -1
type TEMPORALLY_UNPARENTED = number
export type SourceNode<T> = { export type SourceNode<T> = {
cache: T, cache: T,
[number]: Node<T> [number]: Node<T>
@ -15,6 +18,7 @@ export type Node<T> = {
owned: { Node<T> } | false, owned: { Node<T> } | false,
owner: Node<T> | false, owner: Node<T> | false,
pushing_parent_index: number | TEMPORALLY_UNPARENTED,
parents: { SourceNode<T> }, parents: { SourceNode<T> },
[number]: Node<T> -- children [number]: Node<T> -- children
} }
@ -66,11 +70,6 @@ local function assert_stable_scope(): Node<unknown>
return scope return scope
end end
local function push_child<T>(parent: SourceNode<any>, child: Node<any>)
table.insert(parent, child)
table.insert(child.parents, parent)
end
local function push_scope<T>(node: Node<T>) local function push_scope<T>(node: Node<T>)
local n = scopes.n + 1 local n = scopes.n + 1
scopes.n = n scopes.n = n
@ -109,7 +108,22 @@ local function find_and_swap_pop<T>(t: { T }, v: T)
t[n] = nil t[n] = nil
end end
local function push_child<T>(parent: SourceNode<any>, child: Node<any>)
local parent_index = child.pushing_parent_index
local parents = child.parents
child.pushing_parent_index = parent_index + 1
local previous_parent = parents[parent_index]
if previous_parent == parent then return end
parents[parent_index] = parent
if previous_parent then find_and_swap_pop(previous_parent, child) end
table.insert(parent, child)
end
local function unparent<T>(node: Node<T>) local function unparent<T>(node: Node<T>)
if node.pushing_parent_index == TEMPORALLY_UNPARENTED then return end
local parents = node.parents local parents = node.parents
for i, parent in parents do for i, parent in parents do
@ -146,6 +160,14 @@ end
local update_queue = { n = 0 } :: { n: number, [number]: Node<any> } local update_queue = { n = 0 } :: { n: number, [number]: Node<any> }
local function unparent_unuseds<T>(node: Node<T>)
local parents = node.parents
for i = node.pushing_parent_index+1, #parents do
find_and_swap_pop(parents[i], node)
parents[i] = nil
end
end
local function evaluate_node<T>(node: Node<T>) local function evaluate_node<T>(node: Node<T>)
if flags.strict then if flags.strict then
if table.find(scopes, node) then if table.find(scopes, node) then
@ -159,10 +181,13 @@ local function evaluate_node<T>(node: Node<T>)
flush_cleanups(node) flush_cleanups(node)
destroy_owned(node) destroy_owned(node)
node.pushing_parent_index = 1
push_scope(node) push_scope(node)
local ok, new_value = ycall(node.effect :: (T) -> T, cur_value) local ok, new_value = ycall(node.effect :: (T) -> T, cur_value)
pop_scope() pop_scope()
unparent_unuseds(node)
if not ok then if not ok then
table.clear(update_queue) table.clear(update_queue)
@ -179,10 +204,13 @@ local function evaluate_node<T>(node: Node<T>)
flush_cleanups(node) flush_cleanups(node)
destroy_owned(node) destroy_owned(node)
node.pushing_parent_index = 1
push_scope(node) push_scope(node)
local ok, new_value = pcall(node.effect :: (T) -> T, node.cache) local ok, new_value = pcall(node.effect :: (T) -> T, node.cache)
pop_scope() pop_scope()
unparent_unuseds(node)
if not ok then if not ok then
table.clear(update_queue) table.clear(update_queue)
@ -196,13 +224,15 @@ local function evaluate_node<T>(node: Node<T>)
end end
local function queue_children_for_update<T>(node: SourceNode<T>) local function queue_children_for_update<T>(node: SourceNode<T>)
local i = update_queue.n local queue_length = update_queue.n
while node[1] do for j = 1, #node do
i += 1 if node[j].pushing_parent_index == TEMPORALLY_UNPARENTED then continue end
update_queue[i] = node[1] node[j].pushing_parent_index = TEMPORALLY_UNPARENTED
unparent(node[1])
queue_length += 1
update_queue[queue_length] = node[j]
end end
update_queue.n = i update_queue.n = queue_length
end end
local function get_update_queue_length() local function get_update_queue_length()
@ -267,6 +297,7 @@ local function create_node<T>(owner: false | Node<any>, effect: false | (T) -> T
owner = owner, owner = owner,
owned = false, owned = false,
pushing_parent_index = 1,
parents = {}, parents = {},
} }