From e03082c941a0bf17f7170b39857b8ce7632ac2f4 Mon Sep 17 00:00:00 2001 From: aaron <83140718+centau@users.noreply.github.com> Date: Sun, 6 Aug 2023 01:10:01 +0100 Subject: [PATCH] --- src/init.luau | 5 +- src/maps.luau | 136 ++++++++++++++++++++++++++++++++++++++++++++++++ test/tests.luau | 66 +++++++++++++++++++++-- 3 files changed, 200 insertions(+), 7 deletions(-) create mode 100644 src/maps.luau diff --git a/src/init.luau b/src/init.luau index 1e4e4d9..1bf027b 100644 --- a/src/init.luau +++ b/src/init.luau @@ -10,7 +10,7 @@ local source = require(script.source) local watch = require(script.watch) local cleanup, clean_garbage = require(script.cleanup)() local derive = require(script.derive) -local map = require(script.map) +local indexes, values = require(script.maps)() local spring, update_springs = require(script.spring)() local action = require(script.action)() @@ -23,7 +23,8 @@ local vide = { watch = watch, cleanup = cleanup, derive = derive, - map = map, + indexes = indexes, + values = values, -- animations spring = spring, diff --git a/src/maps.luau b/src/maps.luau new file mode 100644 index 0000000..2917166 --- /dev/null +++ b/src/maps.luau @@ -0,0 +1,136 @@ +if not game then script = (require :: any) "test/wrap-require" end + +local graph = require(script.Parent.graph) +type Node = graph.Node +local create = graph.create +local set = graph.set +local capture = graph.capture +local link = graph.link + +type Map = { [K]: V } + +-- todo: optimize, double buffering? + +local function indexes(input: () -> Map, transform: (() -> VI, K) -> VO): () -> { VO } + local input_cache = {} :: Map + local output_cache = {} :: Map + local input_nodes = {} :: Map> + local remove_queue = {} :: { K } + + local function recompute(data) + -- queue removed values + for k in next, input_cache do + if data[k] == nil then + table.insert(remove_queue, k) + end + end + + -- remove queued values + for _, k in next, remove_queue do + input_cache[k] = nil + output_cache[k] = nil + input_nodes[k] = nil + end + + -- process new or changed values + for k, v in next, data do + if input_cache[k] == nil then + local node, get_value = create(v) + input_nodes[k] = node + output_cache[k] = transform(get_value, k) + elseif input_cache[k] ~= v then + set(input_nodes[k], v) + end + input_cache[k] = v + end + + local output = {} + + for _, v in next, output_cache do + table.insert(output, v) + end + + return output + end + + local function derive() + return recompute(input()) + end + + local output, output_get = create(nil :: any) + + local nodes, value = capture(input) + + for _, node in next, nodes do + link(node, output, derive) + end + + output.cache = recompute(value) + + return output_get +end + +local function values(input: () -> Map, transform: (VI, () -> K) -> VO): () -> { VO } + local input_cache = {} :: Map + local output_cache = {} :: Map + local input_nodes = {} :: Map> + local remove_queue = {} :: { VI } + + local function recompute(data: Map) + local inverted_data = {} + + -- process new or changed values + for i, v in next, data do + if input_cache[v] == nil then + local node, get_value = create(i) + input_nodes[v] = node + input_cache[v] = i + output_cache[v] = transform(v, get_value) + elseif input_cache[v] ~= i then + set(input_nodes[v], i) + end + + inverted_data[v] = i + end + + -- queue removed values + for v, k in next, input_cache do + if inverted_data[v] == nil then + table.insert(remove_queue, v) + end + end + + -- remove queued values + for _, k in next, remove_queue do + input_cache[k] = nil + output_cache[k] = nil + input_nodes[k] = nil + end + + local output = {} + + for _, v in next, output_cache do + table.insert(output, v) + end + + return output + end + + local function derive() + return recompute(input()) + end + + local output, output_get = create(nil :: any) + + local nodes, value = capture(input) + + for _, node in next, nodes do + link(node, output, derive) + end + + output.cache = recompute(value) + + return output_get +end + +return function() return indexes, values end diff --git a/test/tests.luau b/test/tests.luau index d49e85c..8b0c6b0 100644 --- a/test/tests.luau +++ b/test/tests.luau @@ -874,14 +874,16 @@ TEST("create()", function() end end) -TEST("map()", function() +-- todo: more comprehensive tests for maps + +TEST("indexes()", function() local source = vide.source - local map = vide.map + local indexes = vide.indexes do CASE "Use state" local input = source { 1, 2, 3 } - local output = map(input, function(v, k) + local output = indexes(input, function(v, k) return tostring(v()) end) @@ -895,7 +897,7 @@ TEST("map()", function() local runcount = table.create(3, 0) - local output = map(input, function(v, i) + local output = indexes(input, function(v, i) runcount[i] += 1 return v end) @@ -914,7 +916,7 @@ TEST("map()", function() do CASE "Removal reflected" local input = source { 1, 2, 3 } - local output = map(input, function(v, i) + local output = indexes(input, function(v, i) return v end) @@ -1011,6 +1013,60 @@ TEST("map()", function() end]] end) +TEST("values()", function() + local source = vide.source + local values = vide.values + + do CASE "Use state" + local input = source { 1, 2, 3 } + + local output = values(input, function(v, k) + return tostring(v) + end) + + CHECK("" .. input()[1] == output()[1]) + CHECK("" .. input()[2] == output()[2]) + CHECK("" .. input()[3] == output()[3]) + end + + do CASE "Cache result" + local input = source { 1, 2, 3 } + + local runcount = table.create(3, 0) + + local output = values(input, function(v, i) + runcount[v] += 1 + return i + end) + + input { 1, 3, 2 } + + CHECK(output()[1]() == 1) + CHECK(output()[2]() == 3) + CHECK(output()[3]() == 2) + + CHECK(runcount[1] == 1) + CHECK(runcount[2] == 1) + CHECK(runcount[3] == 1) + end + + do CASE "Removal reflected" + local input = source { 1, 2, 3 } + + local output = values(input, function(v, i) + return v + end) + + input { 1, 2 } + + local t = output() + + CHECK(t[1] == 1) + CHECK(t[2] == 2) + CHECK(t[3] == nil) + end +end) + TEST("spring()", function() local create = vide.create local source = vide.source