From ec998ccbc8ddd229a94039c76268de7286843580 Mon Sep 17 00:00:00 2001 From: Aaron Smith <83140718+centau@users.noreply.github.com> Date: Fri, 27 Oct 2023 16:47:49 +0100 Subject: [PATCH] Implement batched updates No docs yet, more testing needed. --- src/batch.luau | 23 +++++++++++++++++++++++ src/flags.luau | 2 +- src/graph.luau | 23 +++++++++++++++++++++++ src/init.luau | 4 +++- test/benchmark.luau | 38 ++++++++++++++++++++++++++++++-------- test/tests.luau | 43 +++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 123 insertions(+), 10 deletions(-) create mode 100644 src/batch.luau diff --git a/src/batch.luau b/src/batch.luau new file mode 100644 index 0000000..3c08127 --- /dev/null +++ b/src/batch.luau @@ -0,0 +1,23 @@ +if not game then script = require "test/relative-string" end + +local flags = require(script.Parent.flags) +local throw = require(script.Parent.throw) +local graph = require(script.Parent.graph) + +local function batch(setter: () -> ()) + local already_batching = flags.batch + + flags.batch = true + + local ok, err: string? = pcall(setter) + + flags.batch = false + + if not ok then throw(`error occured while batching updates: {err}`) end + + if not already_batching then -- todo: flush anyways? + graph.flush_update_queue() + end +end + +return batch diff --git a/src/flags.luau b/src/flags.luau index 1b9f80e..cc2d2f8 100644 --- a/src/flags.luau +++ b/src/flags.luau @@ -4,4 +4,4 @@ end local is_O2 = inline_test() ~= "inline_test" -return { strict = not is_O2 } +return { strict = not is_O2, batch = false } diff --git a/src/graph.luau b/src/graph.luau index d316e78..e32f3d0 100644 --- a/src/graph.luau +++ b/src/graph.luau @@ -193,10 +193,32 @@ local function queue_children(node: StartNode) update_queue.n = i end +local function flush_update_queue() + -- todo: test with recursive batch sets + local n0 = 0 + + local i = n0 + 1 + while i <= update_queue.n do + local node = update_queue[i] + --assert(node.effect) + + if evaluate_node(node) then + queue_children(node) + end + + update_queue[i] = false :: any + i += 1 + end + + update_queue.n = n0 +end + local function update(root: StartNode) local n0 = update_queue.n queue_children(root) + if flags.batch then return end + local i = n0 + 1 while i <= update_queue.n do local node = update_queue[i] @@ -257,5 +279,6 @@ return table.freeze { create_node = create_node, create_start_node = create_start_node, get_children = get_children, + flush_update_queue = flush_update_queue, scopes = scopes } diff --git a/src/init.luau b/src/init.luau index 687f417..cfc49d0 100644 --- a/src/init.luau +++ b/src/init.luau @@ -11,9 +11,10 @@ local create = require(script.create) local apply = require(script.apply) local source = require(script.source) local effect = require(script.effect) +local derive = require(script.derive) local cleanup = require(script.cleanup) local untrack = require(script.untrack) -local derive = require(script.derive) +local batch = require(script.batch) local switch = require(script.switch) local show = require(script.show) local indexes, values = require(script.maps)() @@ -59,6 +60,7 @@ local vide = { -- util cleanup = cleanup, untrack = untrack, + batch = batch, read = function(value: T | () -> T): T return if type(value) == "function" then value() else value end, diff --git a/test/benchmark.luau b/test/benchmark.luau index aac9071..bee8c68 100644 --- a/test/benchmark.luau +++ b/test/benchmark.luau @@ -6,6 +6,7 @@ local source = vide.source local derive = vide.derive local indexes = vide.indexes local values = vide.values +local batch = vide.batch local cleanup = vide.cleanup local create = vide.create @@ -132,30 +133,51 @@ ROOT_BENCH("update 1->1->1->1...1000 graph", function() end end) --- todo: crashes at 1k --- todo: repeat with batching -ROOT_BENCH("update 1000->1 graph", function() +-- todo: why does it hang at 1k? it didn't before +ROOT_BENCH("update 500->1 graph", function() local srcs = {} - for i = 1, 800 do + for i = 1, 500 do srcs[i] = source(0) end derive(function() - for i = 1, 800 do + for i = 1, 500 do srcs[i]() end return false end) for i = 1, START(1) do - for idx = 1, 800 do + for idx = 1, 500 do srcs[idx](i) end end end) --- todo: optimize, repeat with batching -ROOT_BENCH("update 1000x 1->1 common extern. graph", function() +ROOT_BENCH("update 1000->1 graph (batched)", function() + local srcs = {} + for i = 1, 1000 do + srcs[i] = source(0) + end + + derive(function() + for i = 1, 1000 do + srcs[i]() + end + return false + end) + + for i = 1, START(1) do + batch(function() + for idx = 1, 1000 do + srcs[idx](i) + end + end) + end +end) + +-- todo: optimize this case +ROOT_BENCH("update 1000 1->1 common extern. graph", function() local ext = source(-1) local srcs = {} diff --git a/test/tests.luau b/test/tests.luau index 5c51e5f..16fa50d 100644 --- a/test/tests.luau +++ b/test/tests.luau @@ -1800,6 +1800,49 @@ TEST("changed()", wrap_root(function() end end)) +TEST("batch()", wrap_root(function() + local source = vide.source + local derive = vide.derive + local batch = vide.batch + + do CASE "child evaluation halted" + local a = source(0) + + local count = { b = 0, b2 = 0, c = 0 } + + local b = derive(function() + count.b += 1 + return a() + 1 + end) + + local b2 = derive(function() + count.b2 += 1 + return a() + 2 + end) + + local c = derive(function() + count.c += 1 + return b() + b2() + end) + + batch(function() + a(1) + CHECK(count.b == 1) + CHECK(count.b2 == 1) + CHECK(count.c == 1) + end) + + CHECK(count.b == 2) + CHECK(count.b2 == 2) + CHECK(count.c == 2) + + CHECK(b() == 2) + CHECK(c() == 5) + end + + -- todo: test batch call in recursive set +end)) + TEST("read()", wrap_root(function() local source = vide.source local effect = vide.effect