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)
|
end; assert(owner)
|
||||||
|
|
||||||
local node = create_node(initial_value, effect)
|
local node = create_node(initial_value, effect)
|
||||||
|
|
||||||
set_owner(node, owner)
|
set_owner(node, owner)
|
||||||
evaluate_node(node)
|
evaluate_node(node)
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -11,11 +11,8 @@ export type StartNode<T> = {
|
||||||
export type Node<T> = {
|
export type Node<T> = {
|
||||||
cache: T,
|
cache: T,
|
||||||
effect: ((T) -> T) | false,
|
effect: ((T) -> T) | false,
|
||||||
|
|
||||||
owner: Node<T> | false,
|
|
||||||
cleanups: { () -> () } | false,
|
cleanups: { () -> () } | false,
|
||||||
|
parents: { owner: StartNode<T>?, [number]: StartNode<T> },
|
||||||
parents: { StartNode<T> },
|
|
||||||
[number]: Node<T>
|
[number]: Node<T>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -57,7 +54,7 @@ local function add_child<T>(parent: StartNode<any>, child: Node<any>)
|
||||||
end
|
end
|
||||||
|
|
||||||
local function set_owner(node: Node<any>, owner: Node<any>)
|
local function set_owner(node: Node<any>, owner: Node<any>)
|
||||||
node.owner = owner
|
node.parents.owner = owner
|
||||||
table.insert(owner, node)
|
table.insert(owner, node)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
@ -100,20 +97,21 @@ local function remove_child<T>(parent: StartNode<T>, child: Node<T>)
|
||||||
end
|
end
|
||||||
|
|
||||||
local function unparent<T>(node: Node<T>)
|
local function unparent<T>(node: Node<T>)
|
||||||
for _, parent in node.parents do
|
local parents = node.parents
|
||||||
remove_child(parent, node)
|
|
||||||
end
|
|
||||||
|
|
||||||
table.clear(node.parents)
|
for i, parent in ipairs(parents) do
|
||||||
|
remove_child(parent, node)
|
||||||
|
parents[i] = nil
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
local function destroy<T>(node: Node<T>)
|
local function destroy<T>(node: Node<T>)
|
||||||
run_cleanups(node)
|
run_cleanups(node)
|
||||||
unparent(node)
|
unparent(node)
|
||||||
|
|
||||||
if node.owner then
|
if node.parents.owner then
|
||||||
remove_child(node.owner, node)
|
remove_child(node.parents.owner, node)
|
||||||
node.owner = false
|
node.parents.owner = nil
|
||||||
end
|
end
|
||||||
|
|
||||||
while node[1] do destroy(node[1]) 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 function create_node<T>(value: T, effect: false | (T) -> T): Node<T>
|
||||||
local node: Node<T> = {
|
local node: Node<T> = {
|
||||||
cache = value,
|
cache = value,
|
||||||
owner = false,
|
|
||||||
effect = effect,
|
effect = effect,
|
||||||
cleanups = false :: false,
|
cleanups = false :: false,
|
||||||
parents = {},
|
parents = {},
|
||||||
children = false :: false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return node
|
return node
|
||||||
|
|
@ -203,7 +199,7 @@ local function get_children<T>(node: Node<T>): { Node<unknown> }
|
||||||
end
|
end
|
||||||
|
|
||||||
local function create_start_node<T>(value: T): StartNode<T>
|
local function create_start_node<T>(value: T): StartNode<T>
|
||||||
return { cache = value, children = false }
|
return { cache = value }
|
||||||
end
|
end
|
||||||
|
|
||||||
return table.freeze {
|
return table.freeze {
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@ local effect = require(script.effect)
|
||||||
local cleanup = require(script.cleanup)
|
local cleanup = require(script.cleanup)
|
||||||
local untrack = require(script.untrack)
|
local untrack = require(script.untrack)
|
||||||
local derive = require(script.derive)
|
local derive = require(script.derive)
|
||||||
local match = require(script.match)
|
local switch = require(script.switch)
|
||||||
local indexes, values = require(script.maps)()
|
local indexes, values = require(script.maps)()
|
||||||
local spring, update_springs = require(script.spring)()
|
local spring, update_springs = require(script.spring)()
|
||||||
local action = require(script.action)()
|
local action = require(script.action)()
|
||||||
|
|
@ -50,7 +50,7 @@ local vide = {
|
||||||
source = source,
|
source = source,
|
||||||
effect = effect,
|
effect = effect,
|
||||||
derive = derive,
|
derive = derive,
|
||||||
match = match,
|
switch = switch,
|
||||||
indexes = indexes,
|
indexes = indexes,
|
||||||
values = values,
|
values = values,
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -29,9 +29,6 @@ local function check_primitives(t: {})
|
||||||
end
|
end
|
||||||
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 function indexes<K, VI, VO>(input: () -> Map<K, VI>, transform: (() -> VI, K) -> VO): () -> { VO }
|
||||||
local owner = get_scope()
|
local owner = get_scope()
|
||||||
if not owner then
|
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)
|
local scope = create_node(false, false)
|
||||||
scopes[i] = scope :: Node<any>
|
scopes[i] = scope :: Node<any>
|
||||||
|
|
||||||
|
local node = create_start_node(v)
|
||||||
|
|
||||||
set_owner(scope, subowner)
|
set_owner(scope, subowner)
|
||||||
open_scope(scope)
|
open_scope(scope)
|
||||||
|
|
||||||
local node = create_start_node(v)
|
local ok, result = pcall(transform, function()
|
||||||
input_nodes[i] = node
|
|
||||||
input_cache[i] = v
|
|
||||||
output_cache[i] = transform(function()
|
|
||||||
track(node)
|
track(node)
|
||||||
return node.cache
|
return node.cache
|
||||||
end, i)
|
end, i)
|
||||||
|
|
||||||
close_scope()
|
close_scope()
|
||||||
|
|
||||||
|
if not ok then error(result, 0) end
|
||||||
|
|
||||||
|
input_nodes[i] = node
|
||||||
|
input_cache[i] = v
|
||||||
|
output_cache[i] = result
|
||||||
else
|
else
|
||||||
input_nodes[i].cache = v
|
input_nodes[i].cache = v
|
||||||
update(input_nodes[i])
|
update(input_nodes[i])
|
||||||
|
|
@ -98,6 +100,7 @@ local function indexes<K, VI, VO>(input: () -> Map<K, VI>, transform: (() -> VI,
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- todo: handle early error
|
||||||
close_scope()
|
close_scope()
|
||||||
|
|
||||||
local output_array = table.create(#scopes)
|
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
|
return output_array
|
||||||
end
|
end
|
||||||
|
|
||||||
local output = create_node(false :: any, function()
|
local node = create_node(false :: any, function()
|
||||||
return update_children(input())
|
return update_children(input())
|
||||||
end)
|
end)
|
||||||
|
|
||||||
evaluate_node(output)
|
evaluate_node(node)
|
||||||
|
|
||||||
|
|
||||||
return function()
|
return function()
|
||||||
track(output)
|
track(node)
|
||||||
return output.cache
|
return node.cache
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
@ -162,17 +165,22 @@ local function values<K, VI, VO>(input: () -> Map<K, VI>, transform: (VI, () ->
|
||||||
local scope = create_node(false, false)
|
local scope = create_node(false, false)
|
||||||
scopes[v] = scope :: Node<any>
|
scopes[v] = scope :: Node<any>
|
||||||
|
|
||||||
|
local node = create_start_node(i)
|
||||||
|
|
||||||
set_owner(scope, subowner)
|
set_owner(scope, subowner)
|
||||||
open_scope(scope)
|
open_scope(scope)
|
||||||
|
|
||||||
local node = create_start_node(i)
|
local ok, result = pcall(transform, v, function()
|
||||||
input_nodes[v] = node
|
|
||||||
output_cache[v] = transform(v, function()
|
|
||||||
track(node)
|
track(node)
|
||||||
return node.cache
|
return node.cache
|
||||||
end)
|
end)
|
||||||
|
|
||||||
close_scope()
|
close_scope()
|
||||||
|
|
||||||
|
if not ok then error(result, 0) end
|
||||||
|
|
||||||
|
input_nodes[v] = node
|
||||||
|
output_cache[v] = result
|
||||||
else
|
else
|
||||||
if cv ~= i then
|
if cv ~= i then
|
||||||
input_nodes[v].cache = i
|
input_nodes[v].cache = i
|
||||||
|
|
@ -182,6 +190,7 @@ local function values<K, VI, VO>(input: () -> Map<K, VI>, transform: (VI, () ->
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- todo: handle early error
|
||||||
close_scope()
|
close_scope()
|
||||||
|
|
||||||
-- remove old values
|
-- remove old values
|
||||||
|
|
@ -206,15 +215,15 @@ local function values<K, VI, VO>(input: () -> Map<K, VI>, transform: (VI, () ->
|
||||||
return output_array
|
return output_array
|
||||||
end
|
end
|
||||||
|
|
||||||
local output = create_node(false :: any, function()
|
local node = create_node(false :: any, function()
|
||||||
return update_children(input())
|
return update_children(input())
|
||||||
end)
|
end)
|
||||||
|
|
||||||
evaluate_node(output)
|
evaluate_node(node)
|
||||||
|
|
||||||
return function()
|
return function()
|
||||||
track(output)
|
track(node)
|
||||||
return output.cache
|
return node.cache
|
||||||
end
|
end
|
||||||
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)
|
||||||
end)
|
end)
|
||||||
|
|
||||||
-- todo: why is this so fast?
|
|
||||||
BENCH("set derived value", function()
|
BENCH("set derived value", function()
|
||||||
local src = vide.source(1)
|
local src = vide.source(1)
|
||||||
|
|
||||||
|
|
@ -110,7 +109,7 @@ BENCH("apply 8 properties", function()
|
||||||
end
|
end
|
||||||
end)
|
end)
|
||||||
|
|
||||||
BENCH("bind source", function()
|
BENCH("bind property", function()
|
||||||
local apply = require "src/apply"
|
local apply = require "src/apply"
|
||||||
|
|
||||||
local instance = vide.create("Frame") {}
|
local instance = vide.create("Frame") {}
|
||||||
|
|
|
||||||
|
|
@ -453,22 +453,6 @@ TEST("derive()", wrap_root(function()
|
||||||
gc()
|
gc()
|
||||||
CHECK(wref[1])
|
CHECK(wref[1])
|
||||||
end
|
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))
|
end))
|
||||||
|
|
||||||
TEST("effect()", wrap_root(function()
|
TEST("effect()", wrap_root(function()
|
||||||
|
|
@ -822,6 +806,72 @@ TEST("create()", wrap_root(function()
|
||||||
end
|
end
|
||||||
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()
|
TEST("indexes()", wrap_root(function()
|
||||||
local create = vide.create
|
local create = vide.create
|
||||||
local source = vide.source
|
local source = vide.source
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue