This commit is contained in:
aaron 2023-09-13 23:57:38 +01:00
parent a425c6b8be
commit bdd725659e
8 changed files with 177 additions and 95 deletions

View file

@ -14,6 +14,7 @@ local function effect<T>(effect: (T) -> T, initial_value: T)
end; assert(owner)
local node = create_node(initial_value, effect)
set_owner(node, owner)
evaluate_node(node)
end

View file

@ -11,11 +11,8 @@ export type StartNode<T> = {
export type Node<T> = {
cache: T,
effect: ((T) -> T) | false,
owner: Node<T> | false,
cleanups: { () -> () } | false,
parents: { StartNode<T> },
parents: { owner: StartNode<T>?, [number]: StartNode<T> },
[number]: Node<T>
}
@ -57,7 +54,7 @@ local function add_child<T>(parent: StartNode<any>, child: Node<any>)
end
local function set_owner(node: Node<any>, owner: Node<any>)
node.owner = owner
node.parents.owner = owner
table.insert(owner, node)
end
@ -100,20 +97,21 @@ local function remove_child<T>(parent: StartNode<T>, child: Node<T>)
end
local function unparent<T>(node: Node<T>)
for _, parent in node.parents do
remove_child(parent, node)
end
local parents = node.parents
table.clear(node.parents)
for i, parent in ipairs(parents) do
remove_child(parent, node)
parents[i] = nil
end
end
local function destroy<T>(node: Node<T>)
run_cleanups(node)
unparent(node)
if node.owner then
remove_child(node.owner, node)
node.owner = false
if node.parents.owner then
remove_child(node.parents.owner, node)
node.parents.owner = nil
end
while node[1] do destroy(node[1]) end
@ -188,11 +186,9 @@ end
local function create_node<T>(value: T, effect: false | (T) -> T): Node<T>
local node: Node<T> = {
cache = value,
owner = false,
effect = effect,
cleanups = false :: false,
parents = {},
children = false :: false
}
return node
@ -203,7 +199,7 @@ local function get_children<T>(node: Node<T>): { Node<unknown> }
end
local function create_start_node<T>(value: T): StartNode<T>
return { cache = value, children = false }
return { cache = value }
end
return table.freeze {

View file

@ -14,7 +14,7 @@ local effect = require(script.effect)
local cleanup = require(script.cleanup)
local untrack = require(script.untrack)
local derive = require(script.derive)
local match = require(script.match)
local switch = require(script.switch)
local indexes, values = require(script.maps)()
local spring, update_springs = require(script.spring)()
local action = require(script.action)()
@ -50,7 +50,7 @@ local vide = {
source = source,
effect = effect,
derive = derive,
match = match,
switch = switch,
indexes = indexes,
values = values,

View file

@ -29,9 +29,6 @@ local function check_primitives(t: {})
end
end
-- todo: verify destruction of subscopes when owner scope is destroyed
-- todo: optimize output array
local function indexes<K, VI, VO>(input: () -> Map<K, VI>, transform: (() -> VI, K) -> VO): () -> { VO }
local owner = get_scope()
if not owner then
@ -78,18 +75,23 @@ local function indexes<K, VI, VO>(input: () -> Map<K, VI>, transform: (() -> VI,
local scope = create_node(false, false)
scopes[i] = scope :: Node<any>
local node = create_start_node(v)
set_owner(scope, subowner)
open_scope(scope)
local node = create_start_node(v)
input_nodes[i] = node
input_cache[i] = v
output_cache[i] = transform(function()
local ok, result = pcall(transform, function()
track(node)
return node.cache
end, i)
close_scope()
if not ok then error(result, 0) end
input_nodes[i] = node
input_cache[i] = v
output_cache[i] = result
else
input_nodes[i].cache = v
update(input_nodes[i])
@ -98,6 +100,7 @@ local function indexes<K, VI, VO>(input: () -> Map<K, VI>, transform: (() -> VI,
end
end
-- todo: handle early error
close_scope()
local output_array = table.create(#scopes)
@ -109,16 +112,16 @@ local function indexes<K, VI, VO>(input: () -> Map<K, VI>, transform: (() -> VI,
return output_array
end
local output = create_node(false :: any, function()
local node = create_node(false :: any, function()
return update_children(input())
end)
evaluate_node(output)
evaluate_node(node)
return function()
track(output)
return output.cache
track(node)
return node.cache
end
end
@ -162,17 +165,22 @@ local function values<K, VI, VO>(input: () -> Map<K, VI>, transform: (VI, () ->
local scope = create_node(false, false)
scopes[v] = scope :: Node<any>
local node = create_start_node(i)
set_owner(scope, subowner)
open_scope(scope)
local node = create_start_node(i)
input_nodes[v] = node
output_cache[v] = transform(v, function()
local ok, result = pcall(transform, v, function()
track(node)
return node.cache
end)
close_scope()
if not ok then error(result, 0) end
input_nodes[v] = node
output_cache[v] = result
else
if cv ~= i then
input_nodes[v].cache = i
@ -182,6 +190,7 @@ local function values<K, VI, VO>(input: () -> Map<K, VI>, transform: (VI, () ->
end
end
-- todo: handle early error
close_scope()
-- remove old values
@ -206,15 +215,15 @@ local function values<K, VI, VO>(input: () -> Map<K, VI>, transform: (VI, () ->
return output_array
end
local output = create_node(false :: any, function()
local node = create_node(false :: any, function()
return update_children(input())
end)
evaluate_node(output)
evaluate_node(node)
return function()
track(output)
return output.cache
track(node)
return node.cache
end
end

View file

@ -1,39 +0,0 @@
if not game then script = require "test/relative-string" end
local throw = require(script.Parent.throw)
local flags = require(script.Parent.flags)
local graph = require(script.Parent.graph)
type Node<T> = graph.Node<T>
type StartNode<T> = graph.StartNode<T>
local create_node = graph.create_node
local create_start_node = graph.create_start_node
local set_owner = graph.set_owner
local track = graph.track
local update = graph.update
local get_scope = graph.get_scope
local open_scope = graph.open_scope
local close_scope = graph.close_scope
local destroy = graph.destroy
type Map<K, V> = { [K]: V }
local function match<T, U>(source: () -> T, map: Map<T, () -> U?>): () -> U?
local owner = get_scope()
assert(owner)
local match_updater = create_node(nil :: U?)
function match_updater.effect()
local value = source()
open_scope(owner)
local component = map[value]()
close_scope()
return component
end
return function()
track(match_updater)
return match_updater.cache
end
end
return match

66
src/switch.luau Normal file
View file

@ -0,0 +1,66 @@
if not game then script = require "test/relative-string" end
local throw = require(script.Parent.throw)
local graph = require(script.Parent.graph)
type Node<T> = graph.Node<T>
type StartNode<T> = graph.StartNode<T>
local create_node = graph.create_node
local evaluate_node = graph.evaluate_node
local set_owner = graph.set_owner
local track = graph.track
local destroy = graph.destroy
local get_scope = graph.get_scope
local open_scope = graph.open_scope
local close_scope = graph.close_scope
type Map<K, V> = { [K]: V }
local function switch<T, U>(source: () -> T): (map: Map<T, ((() -> U)?)>) -> () -> U?
return function(map)
local owner = get_scope()
if not owner then
throw("cannot switch in non-reactive scope")
end; assert(owner)
local scope: Node<false>?
local last_component: (() -> U)?
local function update(): U?
local component = map[source()]
if component == last_component then return nil end
last_component = component
if scope then
destroy(scope :: Node<any>)
end
if component == nil then return nil end
local new_scope = create_node(false, false)
scope = new_scope :: Node<any>
set_owner(new_scope, owner)
open_scope(new_scope)
local ok, result = pcall(component)
close_scope()
if not ok then error(result, 0) end
return result
end
local node = create_node(nil, update :: () -> any)
set_owner(node, owner)
evaluate_node(node)
return function()
track(node)
return node.cache
end
end
end
return switch

View file

@ -68,7 +68,6 @@ BENCH("derive 4 sources", function()
end)
end)
-- todo: why is this so fast?
BENCH("set derived value", function()
local src = vide.source(1)
@ -110,7 +109,7 @@ BENCH("apply 8 properties", function()
end
end)
BENCH("bind source", function()
BENCH("bind property", function()
local apply = require "src/apply"
local instance = vide.create("Frame") {}

View file

@ -453,22 +453,6 @@ TEST("derive()", wrap_root(function()
gc()
CHECK(wref[1])
end
do CASE "raw derive"
local a = source(1)
local b = derive(a)
local count = 0
effect(function()
b()
count += 1
end)
CHECK(count == 1)
a(2)
CHECK(count == 2)
end
end))
TEST("effect()", wrap_root(function()
@ -822,6 +806,72 @@ TEST("create()", wrap_root(function()
end
end))
TEST("switch()", wrap_root(function()
local create = vide.create
local source = vide.source
local switch = vide.switch
local effect = vide.effect
local derive = vide.derive
local cleanup = vide.cleanup
do CASE "update on source change"
local input = source(true)
local output = switch(input) {
[true] = function() return 1 end,
[false] = function() return 0 end
}
local count = 0
effect(function() output(); count += 1 end)
CHECK(count == 1)
CHECK(output() == 1)
input(false)
CHECK(output() == 0)
CHECK(count == 2)
input(false)
CHECK(count == 2)
input(nil)
CHECK(output() == nil)
end
do CASE "scoped switch"
local input = source(true)
local owner_count = 0
local switch0_count = 0
local switch1_count = 0
cleanup(function() owner_count += 1 end)
local output = switch(input) {
[true] = function()
cleanup(function() switch1_count += 1 end)
return 1
end,
[false] = function()
cleanup(function() switch0_count += 1 end)
return 0
end
}
CHECK(output() == 1)
input(false)
CHECK(switch1_count == 1)
CHECK(switch0_count == 0)
input(true)
CHECK(switch1_count == 1)
CHECK(switch0_count == 1)
input(nil)
CHECK(switch1_count == 2)
CHECK(switch0_count == 1)
CHECK(owner_count == 0)
end
end))
TEST("indexes()", wrap_root(function()
local create = vide.create
local source = vide.source