mirror of
https://github.com/centau/vide.git
synced 2026-08-20 14:41:37 +00:00
This commit is contained in:
parent
dcc40b4dc1
commit
52ea2e4690
9 changed files with 147 additions and 102 deletions
|
|
@ -9,38 +9,7 @@ local create_node = graph.create_node
|
||||||
local get_scope = graph.get_scope
|
local get_scope = graph.get_scope
|
||||||
local open_scope = graph.open_scope
|
local open_scope = graph.open_scope
|
||||||
local close_scope = graph.close_scope
|
local close_scope = graph.close_scope
|
||||||
local track = graph.track
|
local set_owner = graph.set_owner
|
||||||
local add_child = graph.add_child
|
|
||||||
|
|
||||||
--[[
|
|
||||||
|
|
||||||
Roblox instances in Luau are referenced using a kind of userdata proxy,
|
|
||||||
this proxy can be garbage collected independently from the actual instance, even
|
|
||||||
if the instance is still parented. Since reactive bindings allow the garbage
|
|
||||||
collection of instances, this proxy can can garbage collected while the instance
|
|
||||||
is still parented, causing the binding to be lost and no longer update the
|
|
||||||
instance on changes.
|
|
||||||
|
|
||||||
Vide's solution to this is to hold the proxy in memory as long as the instance
|
|
||||||
is parented to the datamodel by using `GetPropertyChanged("Parent")` to add or
|
|
||||||
remove the proxy from a table whose sole purpose is to strongly reference
|
|
||||||
proxies.
|
|
||||||
|
|
||||||
todo: investigate behavior in case B is parented to A, and A has no parent or reference, and B has a binding.
|
|
||||||
|
|
||||||
]]
|
|
||||||
|
|
||||||
type Binding = {
|
|
||||||
instance: Instance,
|
|
||||||
property: string,
|
|
||||||
source: () -> unknown
|
|
||||||
}
|
|
||||||
|
|
||||||
local function binder(b: Binding)
|
|
||||||
(b.instance :: any)[b.property] = b.source()
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
-- todo: replace with throw's method
|
-- todo: replace with throw's method
|
||||||
local root do
|
local root do
|
||||||
|
|
@ -65,7 +34,7 @@ local function traceback(skips: number) -- ensures trace begins outside of any v
|
||||||
return debug.traceback(nil, s)
|
return debug.traceback(nil, s)
|
||||||
end
|
end
|
||||||
|
|
||||||
function create_binding<T>(updater: (T) -> (), binding_data: T)
|
function create_binding<T>(updater: (T) -> T, binding_data: T)
|
||||||
-- if flags.strict then
|
-- if flags.strict then
|
||||||
-- -- wrap setter in function with stack inspection for better error msgs
|
-- -- wrap setter in function with stack inspection for better error msgs
|
||||||
-- local fn = setter
|
-- local fn = setter
|
||||||
|
|
@ -84,7 +53,7 @@ function create_binding<T>(updater: (T) -> (), binding_data: T)
|
||||||
local owner = get_scope()
|
local owner = get_scope()
|
||||||
assert(owner)
|
assert(owner)
|
||||||
|
|
||||||
add_child(owner, binding)
|
set_owner(binding, owner)
|
||||||
open_scope(binding)
|
open_scope(binding)
|
||||||
|
|
||||||
updater(binding_data)
|
updater(binding_data)
|
||||||
|
|
@ -100,6 +69,7 @@ type PropertyBinding = {
|
||||||
|
|
||||||
local function update_property(p: PropertyBinding)
|
local function update_property(p: PropertyBinding)
|
||||||
(p.instance :: any)[p.property] = p.source()
|
(p.instance :: any)[p.property] = p.source()
|
||||||
|
return p
|
||||||
end
|
end
|
||||||
|
|
||||||
type ParentBinding = {
|
type ParentBinding = {
|
||||||
|
|
@ -109,6 +79,7 @@ type ParentBinding = {
|
||||||
|
|
||||||
local function update_parent(p: ParentBinding)
|
local function update_parent(p: ParentBinding)
|
||||||
p.instance.Parent = p.parent()
|
p.instance.Parent = p.parent()
|
||||||
|
return p
|
||||||
end
|
end
|
||||||
|
|
||||||
type ChildrenBinding = {
|
type ChildrenBinding = {
|
||||||
|
|
@ -145,6 +116,8 @@ local function update_children(p: ChildrenBinding)
|
||||||
|
|
||||||
table.clear(cur_children_set) -- clear cache, preserve capacity
|
table.clear(cur_children_set) -- clear cache, preserve capacity
|
||||||
p.cur_children_set, p.new_children_set = new_child_set, cur_children_set
|
p.cur_children_set, p.new_children_set = new_child_set, cur_children_set
|
||||||
|
|
||||||
|
return p
|
||||||
end
|
end
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|
|
||||||
|
|
@ -2,8 +2,7 @@ if not game then script = require "test/relative-string" end
|
||||||
|
|
||||||
local graph = require(script.Parent.graph)
|
local graph = require(script.Parent.graph)
|
||||||
local create_node = graph.create_node
|
local create_node = graph.create_node
|
||||||
local add_child = graph.add_child
|
local set_owner = graph.set_owner
|
||||||
local update = graph.update
|
|
||||||
local track = graph.track
|
local track = graph.track
|
||||||
local get_scope = graph.get_scope
|
local get_scope = graph.get_scope
|
||||||
local open_scope = graph.open_scope
|
local open_scope = graph.open_scope
|
||||||
|
|
@ -15,11 +14,10 @@ local function derive<T>(fn: () -> T): () -> T
|
||||||
|
|
||||||
local node = create_node((false :: any) :: T)
|
local node = create_node((false :: any) :: T)
|
||||||
node.effect = function()
|
node.effect = function()
|
||||||
node.cache = fn()
|
return fn()
|
||||||
end
|
end
|
||||||
|
|
||||||
add_child(owner, node)
|
set_owner(node, owner)
|
||||||
|
|
||||||
open_scope(node)
|
open_scope(node)
|
||||||
|
|
||||||
node.cache = fn()
|
node.cache = fn()
|
||||||
|
|
|
||||||
120
src/graph.luau
120
src/graph.luau
|
|
@ -10,9 +10,10 @@ export type StartNode<T> = {
|
||||||
|
|
||||||
export type Node<T> = {
|
export type Node<T> = {
|
||||||
cache: T,
|
cache: T,
|
||||||
|
owner: Node<T> | false,
|
||||||
parents: { StartNode<T> },
|
parents: { StartNode<T> },
|
||||||
children: { Node<T> } | false,
|
children: { Node<T> } | false,
|
||||||
effect: (T) -> () | false,
|
effect: ((T) -> T) | false,
|
||||||
cleanups: { () -> () } | false,
|
cleanups: { () -> () } | false,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -56,6 +57,7 @@ local function get_scope(): Node<unknown>?
|
||||||
return scopes[scopes.n]
|
return scopes[scopes.n]
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
local function add_child<T>(parent: StartNode<any>, child: Node<any>)
|
local function add_child<T>(parent: StartNode<any>, child: Node<any>)
|
||||||
if parent.children then
|
if parent.children then
|
||||||
table.insert(parent.children :: { Node<T> }, child)
|
table.insert(parent.children :: { Node<T> }, child)
|
||||||
|
|
@ -66,6 +68,15 @@ local function add_child<T>(parent: StartNode<any>, child: Node<any>)
|
||||||
table.insert(child.parents, parent)
|
table.insert(child.parents, parent)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
local function set_owner(node: Node<any>, owner: Node<any>)
|
||||||
|
node.owner = owner
|
||||||
|
if owner.children then
|
||||||
|
table.insert(owner.children :: { Node<any> }, node)
|
||||||
|
else
|
||||||
|
owner.children = { node }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
local function open_scope<T>(node: Node<T>)
|
local function open_scope<T>(node: Node<T>)
|
||||||
local n = scopes.n + 1
|
local n = scopes.n + 1
|
||||||
scopes.n = n
|
scopes.n = n
|
||||||
|
|
@ -95,20 +106,18 @@ local function run_cleanups<T>(node: Node<T>)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
local function run_effect<T>(node: Node<T>)
|
local function remove_child<T>(parent: StartNode<T>, child: Node<T>)
|
||||||
if node.effect then
|
local children = parent.children :: {}
|
||||||
node.effect(node.cache)
|
local idx = table.find(children :: {}, child)
|
||||||
end
|
|
||||||
|
local n = #children
|
||||||
|
children[idx] = children[n]
|
||||||
|
children[n] = nil
|
||||||
end
|
end
|
||||||
|
|
||||||
local function unparent<T>(node: Node<T>)
|
local function unparent<T>(node: Node<T>)
|
||||||
for _, parent in node.parents do
|
for _, parent in node.parents do
|
||||||
local children = parent.children :: {}
|
remove_child(parent, node)
|
||||||
local idx = table.find(children :: {}, node)
|
|
||||||
|
|
||||||
local n = #children
|
|
||||||
children[idx] = children[n]
|
|
||||||
children[n] = nil
|
|
||||||
end
|
end
|
||||||
|
|
||||||
table.clear(node.parents)
|
table.clear(node.parents)
|
||||||
|
|
@ -117,6 +126,8 @@ 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 remove_child(node.owner, node) end
|
||||||
|
node.owner = false
|
||||||
local children = node.children :: {}
|
local children = node.children :: {}
|
||||||
if children then
|
if children then
|
||||||
while children[1] do destroy(children[1]) end
|
while children[1] do destroy(children[1]) end
|
||||||
|
|
@ -125,36 +136,77 @@ end
|
||||||
|
|
||||||
local update_queue = {} :: { Node<any> }
|
local update_queue = {} :: { Node<any> }
|
||||||
|
|
||||||
-- runs node effects, recalculates descendants and runs descendant effects
|
-- -- runs node effects, recalculates descendants and runs descendant effects
|
||||||
local function rec<T>(node: StartNode<T>)
|
-- local function rec<T>(node: StartNode<T>)
|
||||||
if not node.children then return end
|
-- if not node.children then return end
|
||||||
|
|
||||||
local children = node.children :: {}
|
-- local children = node.children :: {}
|
||||||
|
|
||||||
while children[1] do
|
-- while children[1] do
|
||||||
table.insert(update_queue, children[1])
|
-- table.insert(update_queue, children[1])
|
||||||
rec(children[1])
|
-- rec(children[1])
|
||||||
unparent(children[1])
|
-- unparent(children[1])
|
||||||
end
|
-- end
|
||||||
|
|
||||||
table.clear(children)
|
-- table.clear(children)
|
||||||
end
|
-- end
|
||||||
|
|
||||||
|
-- local function update<T>(node: StartNode<T>)
|
||||||
|
-- --assert(#update_queue == 0, "update already in progress")
|
||||||
|
-- -- check if recursive update
|
||||||
|
-- local first = update_queue[1] == nil
|
||||||
|
|
||||||
|
-- rec(node)
|
||||||
|
|
||||||
|
-- if first then
|
||||||
|
-- for _, n in next, update_queue do
|
||||||
|
-- open_scope(n) -- todo
|
||||||
|
-- run_cleanups(n)
|
||||||
|
-- run_effect(n)
|
||||||
|
-- close_scope()
|
||||||
|
-- end
|
||||||
|
|
||||||
|
-- table.clear(update_queue)
|
||||||
|
-- end
|
||||||
|
-- end
|
||||||
|
|
||||||
local function update<T>(node: StartNode<T>)
|
local function update<T>(node: StartNode<T>)
|
||||||
--assert(#update_queue == 0, "update already in progress")
|
local children = node.children :: {}
|
||||||
-- check if recursive update
|
if not children then return end
|
||||||
local first = update_queue[1] == nil
|
|
||||||
|
|
||||||
rec(node)
|
local n0 = #update_queue
|
||||||
|
local first_update = n0 == 0
|
||||||
|
local n = n0
|
||||||
|
|
||||||
if first then
|
do
|
||||||
for _, n in next, update_queue do
|
local child = children[1]
|
||||||
open_scope(n) -- todo
|
while child do -- todo: case where child in owner context
|
||||||
run_cleanups(n)
|
unparent(child)
|
||||||
run_effect(n)
|
|
||||||
close_scope()
|
n += 1
|
||||||
|
update_queue[n] = child
|
||||||
|
|
||||||
|
child = children[1]
|
||||||
end
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
for i = n0 + 1, n do
|
||||||
|
local child = update_queue[i]
|
||||||
|
|
||||||
|
local old_value = child.cache
|
||||||
|
|
||||||
|
open_scope(child)
|
||||||
|
run_cleanups(child)
|
||||||
|
local new_value = child.effect and child.effect(old_value)
|
||||||
|
close_scope()
|
||||||
|
|
||||||
|
if old_value ~= new_value then
|
||||||
|
child.cache = new_value
|
||||||
|
update(child)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if first_update then
|
||||||
table.clear(update_queue)
|
table.clear(update_queue)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
@ -169,7 +221,8 @@ end
|
||||||
local function create_node<T>(value: T): Node<T>
|
local function create_node<T>(value: T): Node<T>
|
||||||
local node: Node<T> = {
|
local node: Node<T> = {
|
||||||
cache = value,
|
cache = value,
|
||||||
effect = false,
|
owner = false,
|
||||||
|
effect = false :: false,
|
||||||
cleanups = false :: false,
|
cleanups = false :: false,
|
||||||
parents = {},
|
parents = {},
|
||||||
children = false :: false
|
children = false :: false
|
||||||
|
|
@ -200,6 +253,7 @@ return table.freeze {
|
||||||
get_scope = get_scope,
|
get_scope = get_scope,
|
||||||
get_stack_scope = get_stack_scope,
|
get_stack_scope = get_stack_scope,
|
||||||
add_cleanup = add_cleanup,
|
add_cleanup = add_cleanup,
|
||||||
|
set_owner = set_owner,
|
||||||
destroy = destroy,
|
destroy = destroy,
|
||||||
run_cleanups = run_cleanups,
|
run_cleanups = run_cleanups,
|
||||||
track = track,
|
track = track,
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@ type Scope = graph.Scope
|
||||||
type StartNode<T> = graph.StartNode<T>
|
type StartNode<T> = graph.StartNode<T>
|
||||||
local create_node = graph.create_node
|
local create_node = graph.create_node
|
||||||
local create_start_node = graph.create_start_node
|
local create_start_node = graph.create_start_node
|
||||||
|
local set_owner = graph.set_owner
|
||||||
local track = graph.track
|
local track = graph.track
|
||||||
local update = graph.update
|
local update = graph.update
|
||||||
local get_scope = graph.get_scope
|
local get_scope = graph.get_scope
|
||||||
|
|
@ -36,7 +37,6 @@ local function indexes<K, VI, VO>(input: () -> Map<K, VI>, transform: (() -> VI,
|
||||||
local output_cache = {} :: Map<K, VO>
|
local output_cache = {} :: Map<K, VO>
|
||||||
local input_nodes = {} :: Map<K, StartNode<VI>>
|
local input_nodes = {} :: Map<K, StartNode<VI>>
|
||||||
local remove_queue = {} :: { K }
|
local remove_queue = {} :: { K }
|
||||||
local output_array = {} :: { VO }
|
|
||||||
|
|
||||||
local scopes = {} :: Map<K, Scope>
|
local scopes = {} :: Map<K, Scope>
|
||||||
|
|
||||||
|
|
@ -71,8 +71,8 @@ local function indexes<K, VI, VO>(input: () -> Map<K, VI>, transform: (() -> VI,
|
||||||
local scope = create_node(false)
|
local scope = create_node(false)
|
||||||
scopes[i] = scope
|
scopes[i] = scope
|
||||||
|
|
||||||
|
set_owner(scope, owner)
|
||||||
open_scope(scope)
|
open_scope(scope)
|
||||||
track(owner)
|
|
||||||
|
|
||||||
local node = create_start_node(v)
|
local node = create_start_node(v)
|
||||||
input_nodes[i] = node
|
input_nodes[i] = node
|
||||||
|
|
@ -93,8 +93,7 @@ local function indexes<K, VI, VO>(input: () -> Map<K, VI>, transform: (() -> VI,
|
||||||
|
|
||||||
close_scope()
|
close_scope()
|
||||||
|
|
||||||
-- output elements
|
local output_array = table.create(#scopes)
|
||||||
table.clear(output_array)
|
|
||||||
for _, v in next, output_cache do
|
for _, v in next, output_cache do
|
||||||
table.insert(output_array, v)
|
table.insert(output_array, v)
|
||||||
end
|
end
|
||||||
|
|
@ -105,7 +104,7 @@ local function indexes<K, VI, VO>(input: () -> Map<K, VI>, transform: (() -> VI,
|
||||||
|
|
||||||
local output = create_node(false :: any)
|
local output = create_node(false :: any)
|
||||||
output.effect = function()
|
output.effect = function()
|
||||||
update_children(input())
|
return update_children(input())
|
||||||
end
|
end
|
||||||
|
|
||||||
open_scope(output)
|
open_scope(output)
|
||||||
|
|
@ -130,7 +129,6 @@ local function values<K, VI, VO>(input: () -> Map<K, VI>, transform: (VI, () ->
|
||||||
|
|
||||||
local output_cache = {} :: Map<VI, VO>
|
local output_cache = {} :: Map<VI, VO>
|
||||||
local input_nodes = {} :: Map<VI, StartNode<K>>
|
local input_nodes = {} :: Map<VI, StartNode<K>>
|
||||||
local output_array = {} :: { VO }
|
|
||||||
|
|
||||||
local scopes = {} :: Map<VI, Scope>
|
local scopes = {} :: Map<VI, Scope>
|
||||||
|
|
||||||
|
|
@ -147,7 +145,7 @@ local function values<K, VI, VO>(input: () -> Map<K, VI>, transform: (VI, () ->
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
open_scope(root)
|
open_scope(owner)
|
||||||
|
|
||||||
-- process data
|
-- process data
|
||||||
for i, v in next, data do
|
for i, v in next, data do
|
||||||
|
|
@ -159,6 +157,7 @@ local function values<K, VI, VO>(input: () -> Map<K, VI>, transform: (VI, () ->
|
||||||
local scope = create_node(false)
|
local scope = create_node(false)
|
||||||
scopes[v] = scope
|
scopes[v] = scope
|
||||||
|
|
||||||
|
set_owner(scope, owner)
|
||||||
open_scope(scope)
|
open_scope(scope)
|
||||||
|
|
||||||
local node = create_start_node(i)
|
local node = create_start_node(i)
|
||||||
|
|
@ -193,13 +192,10 @@ local function values<K, VI, VO>(input: () -> Map<K, VI>, transform: (VI, () ->
|
||||||
table.clear(cur_input_cache)
|
table.clear(cur_input_cache)
|
||||||
cur_input_cache_up, new_input_cache_up = new_input_cache, cur_input_cache
|
cur_input_cache_up, new_input_cache_up = new_input_cache, cur_input_cache
|
||||||
|
|
||||||
-- output elements
|
local output_array = table.create(#scopes)
|
||||||
table.clear(output_array)
|
|
||||||
|
|
||||||
for _, v in next, output_cache do
|
for _, v in next, output_cache do
|
||||||
table.insert(output_array, v)
|
table.insert(output_array, v)
|
||||||
end
|
end
|
||||||
|
|
||||||
check_primitives(output_array)
|
check_primitives(output_array)
|
||||||
|
|
||||||
return output_array
|
return output_array
|
||||||
|
|
@ -207,7 +203,7 @@ local function values<K, VI, VO>(input: () -> Map<K, VI>, transform: (VI, () ->
|
||||||
|
|
||||||
local output = create_node(false :: any)
|
local output = create_node(false :: any)
|
||||||
output.effect = function()
|
output.effect = function()
|
||||||
update_children(input())
|
return update_children(input())
|
||||||
end
|
end
|
||||||
|
|
||||||
open_scope(output)
|
open_scope(output)
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,8 @@ local close_scope = graph.close_scope
|
||||||
local get_scope = graph.get_scope
|
local get_scope = graph.get_scope
|
||||||
local destroy = graph.destroy
|
local destroy = graph.destroy
|
||||||
|
|
||||||
|
local refs = {}
|
||||||
|
|
||||||
local function root<T>(fn: () -> T): (T, () -> ())
|
local function root<T>(fn: () -> T): (T, () -> ())
|
||||||
--assert(not get_scope())
|
--assert(not get_scope())
|
||||||
local node = create_node(false)
|
local node = create_node(false)
|
||||||
|
|
@ -21,7 +23,10 @@ local function root<T>(fn: () -> T): (T, () -> ())
|
||||||
|
|
||||||
close_scope()
|
close_scope()
|
||||||
|
|
||||||
|
refs[node] = true
|
||||||
|
|
||||||
return v, function()
|
return v, function()
|
||||||
|
refs[node] = nil
|
||||||
destroy(node)
|
destroy(node)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -31,7 +31,7 @@ local get_scope = graph.get_scope
|
||||||
local open_scope = graph.open_scope
|
local open_scope = graph.open_scope
|
||||||
local close_scope = graph.close_scope
|
local close_scope = graph.close_scope
|
||||||
local update = graph.update
|
local update = graph.update
|
||||||
local add_child = graph.add_child
|
local set_owner = graph.set_owner
|
||||||
local track = graph.track
|
local track = graph.track
|
||||||
|
|
||||||
local UPDATE_RATE = 120
|
local UPDATE_RATE = 120
|
||||||
|
|
@ -156,7 +156,7 @@ local function spring<T>(source: () -> T, period: number?, damping_ratio: number
|
||||||
local updater = create_node(false)
|
local updater = create_node(false)
|
||||||
updater.effect = true :: any -- todo
|
updater.effect = true :: any -- todo
|
||||||
|
|
||||||
add_child(owner, updater)
|
set_owner(updater, owner)
|
||||||
open_scope(updater)
|
open_scope(updater)
|
||||||
|
|
||||||
local initial_value = source()
|
local initial_value = source()
|
||||||
|
|
|
||||||
|
|
@ -5,23 +5,21 @@ local create_node = graph.create_node
|
||||||
local get_scope = graph.get_scope
|
local get_scope = graph.get_scope
|
||||||
local open_scope = graph.open_scope
|
local open_scope = graph.open_scope
|
||||||
local close_scope = graph.close_scope
|
local close_scope = graph.close_scope
|
||||||
local add_child = graph.add_child
|
local set_owner = graph.set_owner
|
||||||
local track = graph.track
|
|
||||||
|
|
||||||
local function watch(effect: () -> ())
|
local function watch<T>(effect: (T) -> T, initial_value: T)
|
||||||
local owner = get_scope()
|
local owner = get_scope()
|
||||||
assert(owner)
|
assert(owner)
|
||||||
|
|
||||||
local node = create_node(false)
|
local node = create_node(false)
|
||||||
node.effect = effect
|
node.effect = effect
|
||||||
|
|
||||||
add_child(owner, node)
|
set_owner(node, owner)
|
||||||
|
|
||||||
open_scope(node)
|
open_scope(node)
|
||||||
|
|
||||||
effect()
|
effect(initial_value)
|
||||||
|
|
||||||
close_scope()
|
close_scope()
|
||||||
end
|
end
|
||||||
|
|
||||||
return watch
|
return watch :: (<T>(effect: (T) -> T, initial_value: T) -> ()) & ((effect: () -> ()) -> ())
|
||||||
|
|
|
||||||
|
|
@ -241,7 +241,6 @@ TEST("graph", function()
|
||||||
CHECK(table.find(get_children(root), scope1 :: Node<any>))
|
CHECK(table.find(get_children(root), scope1 :: Node<any>))
|
||||||
|
|
||||||
destroy(scope1)
|
destroy(scope1)
|
||||||
CHECK(table.find(get_children(root), scope1 :: Node<any>))
|
|
||||||
CHECK(cleaned.scope1)
|
CHECK(cleaned.scope1)
|
||||||
CHECK(cleaned.bind1)
|
CHECK(cleaned.bind1)
|
||||||
scope1 = NIL
|
scope1 = NIL
|
||||||
|
|
@ -252,8 +251,6 @@ TEST("graph", function()
|
||||||
CHECK(#get_children(selected) == 1)
|
CHECK(#get_children(selected) == 1)
|
||||||
end
|
end
|
||||||
|
|
||||||
-- todo: further tests
|
|
||||||
|
|
||||||
do CASE "nodes garbage collection"
|
do CASE "nodes garbage collection"
|
||||||
local wref = weak { create_node(1) }
|
local wref = weak { create_node(1) }
|
||||||
destroy(wref[1])
|
destroy(wref[1])
|
||||||
|
|
@ -330,9 +327,11 @@ TEST("source()", wrap_root(function()
|
||||||
end))
|
end))
|
||||||
|
|
||||||
TEST("derive()", wrap_root(function()
|
TEST("derive()", wrap_root(function()
|
||||||
|
local root = vide.root
|
||||||
local source = vide.source
|
local source = vide.source
|
||||||
local derive = vide.derive
|
local derive = vide.derive
|
||||||
local watch = vide.watch
|
local watch = vide.watch
|
||||||
|
local cleanup = vide.cleanup
|
||||||
|
|
||||||
do CASE "derive new value on source change"
|
do CASE "derive new value on source change"
|
||||||
local a = source(1)
|
local a = source(1)
|
||||||
|
|
@ -367,7 +366,7 @@ TEST("derive()", wrap_root(function()
|
||||||
local num = source(0)
|
local num = source(0)
|
||||||
|
|
||||||
local is_even = derive(function()
|
local is_even = derive(function()
|
||||||
return num() % 2 == 0
|
return bit32.band(num(), 0b01) == 0
|
||||||
end)
|
end)
|
||||||
|
|
||||||
local count = 0
|
local count = 0
|
||||||
|
|
@ -417,15 +416,37 @@ TEST("derive()", wrap_root(function()
|
||||||
CHECK(count == 4)
|
CHECK(count == 4)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
do CASE "owner not disconnected"
|
||||||
|
local count = 0
|
||||||
|
local a = source(0)
|
||||||
|
|
||||||
|
local _, destroy = root(function()
|
||||||
|
|
||||||
|
local b = derive(function()
|
||||||
|
cleanup(function()
|
||||||
|
count += 1
|
||||||
|
end)
|
||||||
|
|
||||||
|
return a()
|
||||||
|
end)
|
||||||
|
end)
|
||||||
|
|
||||||
|
CHECK(count == 0)
|
||||||
|
a(1) -- b clears parents (should not clear owner)
|
||||||
|
CHECK(count == 1)
|
||||||
|
destroy()
|
||||||
|
CHECK(count == 2)
|
||||||
|
end
|
||||||
|
|
||||||
do CASE "garbage collection"
|
do CASE "garbage collection"
|
||||||
-- check that `b` does not allow gc of `a`
|
-- check that `b` does not allow gc of `a`
|
||||||
local a = source(1)
|
local a = source(1)
|
||||||
|
|
||||||
local b = derive(function()
|
local _b = derive(function()
|
||||||
return a()
|
return a()
|
||||||
end)
|
end)
|
||||||
|
|
||||||
b = NIL
|
_b = NIL
|
||||||
|
|
||||||
local wref = weak { a }
|
local wref = weak { a }
|
||||||
|
|
||||||
|
|
|
||||||
4
todo.md
4
todo.md
|
|
@ -3,8 +3,8 @@
|
||||||
- better error reporting and stack traces in strict mode
|
- better error reporting and stack traces in strict mode
|
||||||
- auto-enable of strict mode depending on compiler optimizaton level
|
- auto-enable of strict mode depending on compiler optimizaton level
|
||||||
- investigate if weak table iteration can be invalidated
|
- investigate if weak table iteration can be invalidated
|
||||||
- define behavior of `cleanup()` in `untrack()` scopes
|
- property binding optimization
|
||||||
-
|
- would no longer allow `cleanup()` usage in binding scopes
|
||||||
- solution to nested reactivity, see: SolidJS stores
|
- solution to nested reactivity, see: SolidJS stores
|
||||||
- SolidJS control flow components
|
- SolidJS control flow components
|
||||||
- equality checking of derived sources
|
- equality checking of derived sources
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue