diff --git a/src/graph.luau b/src/graph.luau index 58b10ff..578216f 100644 --- a/src/graph.luau +++ b/src/graph.luau @@ -5,18 +5,17 @@ local flags = require(script.Parent.flags) export type StartNode = { cache: T, - children: { [Node]: true } | false + children: { Node } | false } export type Node = { cache: T, - children: { [Node]: true } | false, + parents: { StartNode }, + children: { Node } | false, effect: (T) -> () | false, cleanups: { () -> () } | false, } -local active = {} :: { [Node]: true } - local scopes = { n = 0 } :: { [number]: Node, n: number } local WEAK_VALUES = { __mode = "v" } @@ -59,11 +58,12 @@ end local function add_child(parent: StartNode, child: Node) if parent.children then - parent.children[child] = true + table.insert(parent.children :: { Node }, child) else - parent.children = { [child] = true :: true } - setmetatable(parent.children :: any, WEAK_KEYS) -- todo: + parent.children = { child } end + + table.insert(child.parents, parent) end local function open_scope(node: Node) @@ -101,13 +101,25 @@ local function run_effect(node: Node) end end +local function unparent(node: Node) + for _, parent in node.parents do + local children = parent.children :: {} + local idx = table.find(children :: {}, node) + + local n = #children + children[idx] = children[n] + children[n] = nil + end + + table.clear(node.parents) +end + local function destroy(node: Node) run_cleanups(node) - active[node] = nil - if node.children then - for child in node.children do - destroy(child) - end + unparent(node) + local children = node.children :: {} + if children then + while children[1] do destroy(children[1]) end end end @@ -117,12 +129,15 @@ local update_queue = {} :: { Node } local function rec(node: StartNode) if not node.children then return end - for child in next, node.children do - table.insert(update_queue, child) - rec(child) + local children = node.children :: {} + + while children[1] do + table.insert(update_queue, children[1]) + rec(children[1]) + unparent(children[1]) end - table.clear(node.children) + table.clear(children) end local function update(node: StartNode) @@ -156,11 +171,10 @@ local function create_node(value: T): Node cache = value, effect = false, cleanups = false :: false, + parents = {}, children = false :: false } - active[node] = true - return node end @@ -169,7 +183,7 @@ local function get_children(node: Node): { Node } local children = {} - for child in node.children do + for _, child in node.children do table.insert(children, child) end diff --git a/test/tests.luau b/test/tests.luau index 313b1be..fe38ff0 100644 --- a/test/tests.luau +++ b/test/tests.luau @@ -103,7 +103,7 @@ TEST("graph", function() CHECK(count == 3) end - do CASE "diamond problem" + do CASE "diamond graph" local a, b, c, d = node(), node(), node(), node() local b_cnt, c_cnt, d_cnt = 0, 0, 0 @@ -119,7 +119,7 @@ TEST("graph", function() CHECK(b_cnt == 1) CHECK(c_cnt == 1) - CHECK(d_cnt == 2) -- confirm current behavior + CHECK(d_cnt == 1) end do CASE "duplicate child on rerun" @@ -387,6 +387,36 @@ TEST("derive()", wrap_root(function() CHECK(count == 4) end + do CASE "conditional derive" + local a = source(false) + local b = source(false) + + local c = derive(function() + return + if a() then "a" + elseif b() then "b" + else "never" + end) + + local count = 0 + + watch(function() count += 1 end) + + b(true) + CHECK(c() == "b") + CHECK(count == 2) + a(true) + CHECK(c() == "a") + CHECK(count == 3) + b(false) + CHECK(count == 3) + b(true) + CHECK(count == 3) + a(false) + CHECK(c() == "b") + CHECK(count == 4) + end + do CASE "garbage collection" -- check that `b` does not allow gc of `a` local a = source(1)