mirror of
https://github.com/centau/vide.git
synced 2026-08-20 14:41:37 +00:00
This commit is contained in:
parent
a425c6b8be
commit
bdd725659e
8 changed files with 177 additions and 95 deletions
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
66
src/switch.luau
Normal 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
|
||||
|
|
@ -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") {}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue