diff --git a/src/derive.luau b/src/derive.luau index 73bbfeb..461dd13 100644 --- a/src/derive.luau +++ b/src/derive.luau @@ -11,6 +11,7 @@ local function derive(source: () -> T): () -> T return function() push_child_to_scope(node) + evaluate_node(node) return node.cache end end diff --git a/src/graph.luau b/src/graph.luau index 360c0cb..4147fb3 100644 --- a/src/graph.luau +++ b/src/graph.luau @@ -1,12 +1,16 @@ local flags = require "./flags" +local update_id = 0 export type SourceNode = { cache: T, + needs_queue_children: boolean, [number]: Node } export type Node = { cache: T, + last_eval_update_id: number, + needs_queue_children: boolean, effect: ((T) -> T) | false, cleanups: { () -> () } | false, @@ -147,6 +151,7 @@ end local update_queue = { n = 0 } :: { n: number, [number]: Node } local function evaluate_node(node: Node) + if update_id == node.last_eval_update_id then return node.needs_queue_children end if flags.strict then local initial_value = node.cache @@ -169,7 +174,10 @@ local function evaluate_node(node: Node) node.cache = new_value :: T end - return initial_value ~= node.cache + local needs_queue_children = initial_value ~= node.cache + node.needs_queue_children = needs_queue_children + node.last_eval_update_id = update_id + return needs_queue_children else local cur_value = node.cache @@ -185,18 +193,24 @@ local function evaluate_node(node: Node) update_queue.n = 0 error(`effect error:\n{new_value}\n`, 0) end - + local needs_queue_children = cur_value ~= new_value + node.needs_queue_children = needs_queue_children + node.last_eval_update_id = update_id node.cache = new_value - return cur_value ~= new_value + return needs_queue_children end end local function queue_children_for_update(node: SourceNode) + node.needs_queue_children = false local i = update_queue.n - while node[1] do + local j = 1 + + while node[j] do + if node[j].last_eval_update_id == update_id then j += 1; continue end i += 1 - update_queue[i] = node[1] - unparent(node[1]) + update_queue[i] = node[j] + unparent(node[j]) end update_queue.n = i end @@ -222,8 +236,9 @@ local function flush_update_queue(from: number) update_queue.n = from end -local function update_descendants(root: SourceNode) +local function update_descendants(root: SourceNode, is_root_update: boolean?) local n0 = update_queue.n + if is_root_update then update_id += 1 end queue_children_for_update(root) if flags.batch then return end @@ -258,6 +273,8 @@ local function create_node(owner: false | Node, effect: false | (T) -> T effect = effect, cleanups = false, + needs_queue_children = false, + last_eval_update_id = 0, context = false, owner = owner, @@ -278,7 +295,7 @@ local function create_node(owner: false | Node, effect: false | (T) -> T end local function create_source_node(value: T): SourceNode - return { cache = value } + return { cache = value, needs_queue_children = false } end local function get_children(node: Node): { Node } diff --git a/src/source.luau b/src/source.luau index d7aa53d..36c4609 100644 --- a/src/source.luau +++ b/src/source.luau @@ -21,7 +21,7 @@ local function source(initial_value: T): Source end node.cache = v - update_descendants(node) + update_descendants(node, true) return v end diff --git a/test/tests.luau b/test/tests.luau index 14409bb..d1995b7 100644 --- a/test/tests.luau +++ b/test/tests.luau @@ -98,9 +98,9 @@ TEST("graph", function() pop_scope() CHECK(count == 1) - update_descendants(a) + update_descendants(a, true) CHECK(count == 2) - update_descendants(b) + update_descendants(b, true) CHECK(count == 3) end @@ -119,7 +119,7 @@ TEST("graph", function() push_scope(c); push_child_to_scope(a); pop_scope() push_scope(d); push_child_to_scope(b); push_child_to_scope(c); pop_scope() - update_descendants(a) + update_descendants(a, true) CHECK(b_cnt == 1) CHECK(c_cnt == 1) @@ -138,7 +138,7 @@ TEST("graph", function() push_scope(c); assert(type(c.effect) == "function" and c.effect)(NIL); pop_scope() - update_descendants(a) + update_descendants(a, true) CHECK(#get_children(a) == 1) CHECK(#get_children(b) == 1) @@ -286,14 +286,14 @@ TEST("graph", function() local a, b, c, d, e, f = node(root), node(root), node(root), node(root), node(root), node(root) function b.effect(x) - update_descendants(d) + update_descendants(d, true) return not x end push_child(a, b); push_child(a, c) push_child(d, e); push_child(d, f) - update_descendants(a) + update_descendants(a, true) CHECK(true) end @@ -2517,6 +2517,29 @@ TEST("graph edge cases", wrap_root(function() local effect = vide.effect local root = vide.root + do CASE "brother requires older brother" + --[[ + A + ^ ^ + B <-- C + ]] + + local count = { b = 0, c = 0 } + local a = source(0) + local b + + local function upd_c() count.c += 1; return a()* (b and b() or 0) end + local c = derive(upd_c) + + local function upd_b() count.b += 1; return a()*2 end + b = derive(upd_b) + + a(2) + print('CAVALO', count.b, count.c) + CHECK(count.b == 2) + CHECK(count.c == 2) + end + do CASE "diamond A,B,C,D" --[[ @@ -2577,14 +2600,14 @@ TEST("graph edge cases", wrap_root(function() CHECK(count.b == 2) CHECK(count.c == 2) CHECK(count.d == 2) - CHECK(count.e == 3) -- todo: redundant re-eval + CHECK(count.e == 2) CHECK(e() == 4) a(3) CHECK(count.b == 2) CHECK(count.c == 3) CHECK(count.d == 3) - CHECK(count.e == 4) + CHECK(count.e == 3) CHECK(e() == 12) end