mirror of
https://github.com/centau/vide.git
synced 2026-08-20 14:41:37 +00:00
This commit is contained in:
parent
866135b152
commit
e776183452
10 changed files with 480 additions and 367 deletions
|
|
@ -138,7 +138,7 @@ local function apply<T>(instance: T & Instance, properties: { [unknown]: unknown
|
||||||
-- finally set parent if any
|
-- finally set parent if any
|
||||||
if parent then
|
if parent then
|
||||||
if type(parent) == "function" then
|
if type(parent) == "function" then
|
||||||
error("cannot set parent to state")
|
bind.parent(instance, parent :: () -> ())
|
||||||
else
|
else
|
||||||
instance.Parent = parent :: Instance
|
instance.Parent = parent :: Instance
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -6,10 +6,11 @@ local flags = require(script.Parent.flags)
|
||||||
local graph = require(script.Parent.graph)
|
local graph = require(script.Parent.graph)
|
||||||
type Node<T> = graph.Node<T>
|
type Node<T> = graph.Node<T>
|
||||||
local create_node = graph.create_node
|
local create_node = graph.create_node
|
||||||
|
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 add_child = graph.add_child
|
local add_child = graph.add_child
|
||||||
local capture_parents = graph.capture_parents
|
|
||||||
|
|
||||||
--[[
|
--[[
|
||||||
|
|
||||||
|
|
@ -70,9 +71,13 @@ function bind(instance: Instance, property: string, setter: (Instance) -> ())
|
||||||
local binding = create_node(instance)
|
local binding = create_node(instance)
|
||||||
binding.effect = setter
|
binding.effect = setter
|
||||||
|
|
||||||
open_scope(binding.scope)
|
local owner = get_scope()
|
||||||
|
assert(owner)
|
||||||
|
|
||||||
capture_parents(binding, setter :: () -> any, instance)
|
open_scope(binding)
|
||||||
|
track(owner)
|
||||||
|
|
||||||
|
setter(instance)
|
||||||
|
|
||||||
close_scope()
|
close_scope()
|
||||||
end
|
end
|
||||||
|
|
@ -84,12 +89,7 @@ local function bind_property(instance: Instance, property: string, fn: () -> unk
|
||||||
end
|
end
|
||||||
|
|
||||||
local function bind_parent(instance: Instance, fn: () -> Instance?)
|
local function bind_parent(instance: Instance, fn: () -> Instance?)
|
||||||
instance.Destroying:Connect(function()
|
|
||||||
instance = nil :: any -- allow gc when destroyed
|
|
||||||
end)
|
|
||||||
|
|
||||||
bind(instance, "Parent", function(instance)
|
bind(instance, "Parent", function(instance)
|
||||||
local _ = instance -- state will strongly reference instance when parent is bound
|
|
||||||
instance.Parent = fn()
|
instance.Parent = fn()
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,6 @@ 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 capture_parents = graph.capture_parents
|
|
||||||
local add_child = graph.add_child
|
local add_child = graph.add_child
|
||||||
local update = graph.update
|
local update = graph.update
|
||||||
local track = graph.track
|
local track = graph.track
|
||||||
|
|
@ -11,17 +10,19 @@ local open_scope = graph.open_scope
|
||||||
local close_scope = graph.close_scope
|
local close_scope = graph.close_scope
|
||||||
|
|
||||||
local function derive<T>(fn: () -> T): () -> T
|
local function derive<T>(fn: () -> T): () -> T
|
||||||
assert(get_scope())
|
local owner = get_scope()
|
||||||
|
assert(owner)
|
||||||
|
|
||||||
local node = create_node((false :: any) :: T)
|
local node = create_node((false :: any) :: T)
|
||||||
node.effect = function()
|
node.effect = function()
|
||||||
node.cache = fn()
|
node.cache = fn()
|
||||||
update(node)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
open_scope(node.scope)
|
add_child(owner, node)
|
||||||
|
|
||||||
node.cache = capture_parents(node, fn)
|
open_scope(node)
|
||||||
|
|
||||||
|
node.cache = fn()
|
||||||
|
|
||||||
close_scope()
|
close_scope()
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -11,13 +11,16 @@ export type StartNode<T> = {
|
||||||
export type Node<T> = {
|
export type Node<T> = {
|
||||||
cache: T,
|
cache: T,
|
||||||
children: { [Node<T>]: true } | false,
|
children: { [Node<T>]: true } | false,
|
||||||
effect: (T) -> (),
|
effect: (T) -> () | false,
|
||||||
cleanups: { () -> () } | false,
|
cleanups: { () -> () } | false,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
local active = {} :: { [Node<any>]: true }
|
||||||
|
|
||||||
local scopes = { n = 0 } :: { [number]: Node<any>, n: number }
|
local scopes = { n = 0 } :: { [number]: Node<any>, n: number }
|
||||||
|
|
||||||
local WEAK_VALUES = { __mode = "v" }
|
local WEAK_VALUES = { __mode = "v" }
|
||||||
|
local WEAK_KEYS = { __mode = "k" }
|
||||||
local EVALUATION_ERR = "error while evaluating source:\n\n"
|
local EVALUATION_ERR = "error while evaluating source:\n\n"
|
||||||
|
|
||||||
-- runs a given callback in a context that Luau does not allow yielding in
|
-- runs a given callback in a context that Luau does not allow yielding in
|
||||||
|
|
@ -46,34 +49,27 @@ local check_for_yield: <T...>(fn: (T...) -> unknown, T...) -> () do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
local function get_scope(): Node<unknown>
|
local function get_stack_scope(offset: number): Node<unknown>?
|
||||||
|
return scopes[scopes.n - offset]
|
||||||
|
end
|
||||||
|
|
||||||
|
local function get_scope(): Node<unknown>?
|
||||||
return scopes[scopes.n]
|
return scopes[scopes.n]
|
||||||
end
|
end
|
||||||
|
|
||||||
local function add_child<T>(parent: Node<any>, child: Node<any>)
|
local function add_child<T>(parent: StartNode<any>, child: Node<any>)
|
||||||
if parent.children then
|
if parent.children then
|
||||||
parent.children[child] = true
|
parent.children[child] = true
|
||||||
else
|
else
|
||||||
parent.children = { [child] = true :: true }
|
parent.children = { [child] = true :: true }
|
||||||
|
setmetatable(parent.children :: any, WEAK_KEYS) -- todo:
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
-- local function open_root_scope<T>(node: Node<T>)
|
|
||||||
-- assert(not scopes[1])
|
|
||||||
|
|
||||||
-- local n = scopes.n + 1
|
|
||||||
-- scopes.n = n
|
|
||||||
-- scopes[n] = node
|
|
||||||
-- 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
|
||||||
scopes[n] = node
|
scopes[n] = node
|
||||||
|
|
||||||
-- local parent = scopes[n-1]
|
|
||||||
-- assert(parent)
|
|
||||||
-- add_child(parent, node)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
local function close_scope()
|
local function close_scope()
|
||||||
|
|
@ -100,49 +96,71 @@ local function run_cleanups<T>(node: Node<T>)
|
||||||
end
|
end
|
||||||
|
|
||||||
local function run_effect<T>(node: Node<T>)
|
local function run_effect<T>(node: Node<T>)
|
||||||
node.effect(node.cache)
|
if node.effect then
|
||||||
|
node.effect(node.cache)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
local function destroy<T>(node: Node<T>)
|
local function destroy<T>(node: Node<T>)
|
||||||
run_cleanups(node)
|
run_cleanups(node)
|
||||||
for _, child in ipairs(node) do
|
active[node] = nil
|
||||||
destroy(child)
|
if node.children then
|
||||||
|
for child in node.children do
|
||||||
|
destroy(child)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
-- runs node effects, recalculates descendants and runs descendant effects
|
-- runs node effects, recalculates descendants and runs descendant effects
|
||||||
local function update<T>(node: StartNode<T>)
|
local function rec<T>(node: StartNode<T>, update_queue: { Node<any> })
|
||||||
if not node.children then return end
|
if not node.children then return end
|
||||||
|
|
||||||
local cache = {}
|
for child in next, node.children do
|
||||||
for child in node.children do
|
table.insert(update_queue, child)
|
||||||
table.insert(cache, child)
|
rec(child, update_queue)
|
||||||
end
|
end
|
||||||
|
|
||||||
for _, child in next, cache do
|
table.clear(node.children)
|
||||||
open_scope(child)
|
|
||||||
run_cleanups(child)
|
|
||||||
run_effect(child)
|
|
||||||
close_scope()
|
|
||||||
update(child)
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
local function track<T>(node: Node<T>)
|
local function update<T>(node: StartNode<T>)
|
||||||
add_child(node, get_scope())
|
local update_queue = {} :: { Node<any> }
|
||||||
|
--assert(#update_queue == 0, "update already in progress")
|
||||||
|
rec(node, update_queue)
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
local function track<T>(node: StartNode<T>)
|
||||||
|
local scope = get_scope()
|
||||||
|
assert(scope)
|
||||||
|
if scope.effect then -- todo
|
||||||
|
add_child(node, scope)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
local function create_node<T>(value: T): Node<T>
|
local function create_node<T>(value: T): Node<T>
|
||||||
return {
|
local node: Node<T> = {
|
||||||
cache = value,
|
cache = value,
|
||||||
effect = function() end,
|
effect = false,
|
||||||
cleanups = false,
|
cleanups = false :: false,
|
||||||
children = false
|
children = false :: false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
active[node] = true
|
||||||
|
|
||||||
|
return node
|
||||||
end
|
end
|
||||||
|
|
||||||
local function get_children(node: Node<unknown>): { Node<unknown> }
|
local function get_children<T>(node: Node<T>): { Node<unknown> }
|
||||||
if not node.children then return {} end
|
if not node.children then return {} end
|
||||||
|
|
||||||
local children = {}
|
local children = {}
|
||||||
|
|
@ -151,7 +169,7 @@ local function get_children(node: Node<unknown>): { Node<unknown> }
|
||||||
table.insert(children, child)
|
table.insert(children, child)
|
||||||
end
|
end
|
||||||
|
|
||||||
return children
|
return children :: { Node<any> }
|
||||||
end
|
end
|
||||||
|
|
||||||
local function create_start_node<T>(value: T): StartNode<T>
|
local function create_start_node<T>(value: T): StartNode<T>
|
||||||
|
|
@ -159,10 +177,10 @@ local function create_start_node<T>(value: T): StartNode<T>
|
||||||
end
|
end
|
||||||
|
|
||||||
return table.freeze {
|
return table.freeze {
|
||||||
open_root_scope = open_root_scope,
|
|
||||||
open_scope = open_scope,
|
open_scope = open_scope,
|
||||||
close_scope = close_scope,
|
close_scope = close_scope,
|
||||||
get_scope = get_scope,
|
get_scope = get_scope,
|
||||||
|
get_stack_scope = get_stack_scope,
|
||||||
add_cleanup = add_cleanup,
|
add_cleanup = add_cleanup,
|
||||||
destroy = destroy,
|
destroy = destroy,
|
||||||
run_cleanups = run_cleanups,
|
run_cleanups = run_cleanups,
|
||||||
|
|
|
||||||
|
|
@ -9,12 +9,8 @@ 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 create_scope = graph.create_scope
|
|
||||||
local track = graph.track
|
local track = graph.track
|
||||||
local update = graph.update
|
local update = graph.update
|
||||||
local capture = graph.capture
|
|
||||||
local capture_parents = graph.capture_parents
|
|
||||||
local add_child = graph.add_child
|
|
||||||
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
|
||||||
|
|
@ -33,9 +29,8 @@ end
|
||||||
|
|
||||||
-- todo: optimize output array
|
-- 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 }
|
||||||
assert(get_scope())
|
local owner = get_scope()
|
||||||
|
assert(owner)
|
||||||
local root = create_scope()
|
|
||||||
|
|
||||||
local input_cache = {} :: Map<K, VI>
|
local input_cache = {} :: Map<K, VI>
|
||||||
local output_cache = {} :: Map<K, VO>
|
local output_cache = {} :: Map<K, VO>
|
||||||
|
|
@ -65,7 +60,7 @@ local function indexes<K, VI, VO>(input: () -> Map<K, VI>, transform: (() -> VI,
|
||||||
|
|
||||||
table.clear(remove_queue)
|
table.clear(remove_queue)
|
||||||
|
|
||||||
open_scope(root)
|
open_scope(owner) -- todo: needed?
|
||||||
|
|
||||||
-- process new or changed values
|
-- process new or changed values
|
||||||
for i, v in next, data do
|
for i, v in next, data do
|
||||||
|
|
@ -73,10 +68,11 @@ local function indexes<K, VI, VO>(input: () -> Map<K, VI>, transform: (() -> VI,
|
||||||
|
|
||||||
if cv ~= v then
|
if cv ~= v then
|
||||||
if cv == nil then
|
if cv == nil then
|
||||||
local scope = create_scope()
|
local scope = create_node(false)
|
||||||
scopes[i] = scope
|
scopes[i] = scope
|
||||||
|
|
||||||
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
|
||||||
|
|
@ -112,9 +108,11 @@ local function indexes<K, VI, VO>(input: () -> Map<K, VI>, transform: (() -> VI,
|
||||||
update_children(input())
|
update_children(input())
|
||||||
end
|
end
|
||||||
|
|
||||||
local value = capture_parents(output, input)
|
open_scope(output)
|
||||||
|
|
||||||
output.cache = update_children(value)
|
output.cache = update_children(input())
|
||||||
|
|
||||||
|
close_scope()
|
||||||
|
|
||||||
return function()
|
return function()
|
||||||
track(output)
|
track(output)
|
||||||
|
|
@ -124,9 +122,8 @@ end
|
||||||
|
|
||||||
-- todo: optimize output array
|
-- todo: optimize output array
|
||||||
local function values<K, VI, VO>(input: () -> Map<K, VI>, transform: (VI, () -> K) -> VO): () -> { VO }
|
local function values<K, VI, VO>(input: () -> Map<K, VI>, transform: (VI, () -> K) -> VO): () -> { VO }
|
||||||
assert(get_scope())
|
local owner = get_scope()
|
||||||
|
assert(owner)
|
||||||
local root = create_scope()
|
|
||||||
|
|
||||||
local cur_input_cache_up = {} :: Map<VI, K>
|
local cur_input_cache_up = {} :: Map<VI, K>
|
||||||
local new_input_cache_up = {} :: Map<VI, K>
|
local new_input_cache_up = {} :: Map<VI, K>
|
||||||
|
|
@ -159,7 +156,7 @@ local function values<K, VI, VO>(input: () -> Map<K, VI>, transform: (VI, () ->
|
||||||
local cv = cur_input_cache[v]
|
local cv = cur_input_cache[v]
|
||||||
|
|
||||||
if cv == nil then
|
if cv == nil then
|
||||||
local scope = create_scope()
|
local scope = create_node(false)
|
||||||
scopes[v] = scope
|
scopes[v] = scope
|
||||||
|
|
||||||
open_scope(scope)
|
open_scope(scope)
|
||||||
|
|
@ -213,9 +210,11 @@ local function values<K, VI, VO>(input: () -> Map<K, VI>, transform: (VI, () ->
|
||||||
update_children(input())
|
update_children(input())
|
||||||
end
|
end
|
||||||
|
|
||||||
local value = capture_parents(output, input)
|
open_scope(output)
|
||||||
|
|
||||||
output.cache = update_children(value)
|
output.cache = update_children(input())
|
||||||
|
|
||||||
|
close_scope()
|
||||||
|
|
||||||
return function()
|
return function()
|
||||||
track(output)
|
track(output)
|
||||||
|
|
|
||||||
|
|
@ -5,24 +5,24 @@ local throw = require(script.Parent.throw)
|
||||||
local on_gc = require(script.Parent.on_gc)()
|
local on_gc = require(script.Parent.on_gc)()
|
||||||
local graph = require(script.Parent.graph)
|
local graph = require(script.Parent.graph)
|
||||||
type Node<T> = graph.Node<T>
|
type Node<T> = graph.Node<T>
|
||||||
local create_scope = graph.create_scope
|
local create_node = graph.create_node
|
||||||
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 get_scope = graph.get_scope
|
local get_scope = graph.get_scope
|
||||||
local destroy = graph.destroy
|
local destroy = graph.destroy
|
||||||
|
|
||||||
local function root<T>(fn: () -> T): (T, () -> ())
|
local function root<T>(fn: () -> T): (T, () -> ())
|
||||||
assert(not get_scope())
|
--assert(not get_scope())
|
||||||
local scope = create_scope()
|
local node = create_node(false)
|
||||||
|
|
||||||
open_scope(scope)
|
open_scope(node)
|
||||||
|
|
||||||
local v = fn()
|
local v = fn()
|
||||||
|
|
||||||
close_scope()
|
close_scope()
|
||||||
|
|
||||||
return v, function()
|
return v, function()
|
||||||
destroy(scope)
|
destroy(node)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -27,8 +27,10 @@ type Node<T> = graph.Node<T>
|
||||||
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 get_scope = graph.get_scope
|
||||||
|
local open_scope = graph.open_scope
|
||||||
|
local close_scope = graph.close_scope
|
||||||
local update = graph.update
|
local update = graph.update
|
||||||
local capture = graph.capture
|
|
||||||
local add_child = graph.add_child
|
local add_child = graph.add_child
|
||||||
local track = graph.track
|
local track = graph.track
|
||||||
|
|
||||||
|
|
@ -148,7 +150,18 @@ local springs: { [SpringData<any>]: StartNode<any> } = {}
|
||||||
setmetatable(springs, { __mode = "v" })
|
setmetatable(springs, { __mode = "v" })
|
||||||
|
|
||||||
local function spring<T>(source: () -> T, period: number?, damping_ratio: number?): () -> T
|
local function spring<T>(source: () -> T, period: number?, damping_ratio: number?): () -> T
|
||||||
local inputs, initial_value = capture(source)
|
local owner = get_scope()
|
||||||
|
assert(owner)
|
||||||
|
|
||||||
|
local updater = create_node(false)
|
||||||
|
updater.effect = true :: any -- todo
|
||||||
|
|
||||||
|
add_child(owner, updater)
|
||||||
|
open_scope(updater)
|
||||||
|
|
||||||
|
local initial_value = source()
|
||||||
|
|
||||||
|
close_scope()
|
||||||
|
|
||||||
local vtype = typeof(initial_value)
|
local vtype = typeof(initial_value)
|
||||||
|
|
||||||
|
|
@ -181,7 +194,6 @@ local function spring<T>(source: () -> T, period: number?, damping_ratio: number
|
||||||
|
|
||||||
local output = create_start_node(initial_value)
|
local output = create_start_node(initial_value)
|
||||||
|
|
||||||
local updater = create_node(false)
|
|
||||||
updater.effect = function()
|
updater.effect = function()
|
||||||
local v = source()
|
local v = source()
|
||||||
data.x1_123, data.x1_456 = type_to_vec6[typeof(v)](v)
|
data.x1_123, data.x1_456 = type_to_vec6[typeof(v)](v)
|
||||||
|
|
@ -189,10 +201,6 @@ local function spring<T>(source: () -> T, period: number?, damping_ratio: number
|
||||||
springs[data] = output -- todo: investigate why insertion is not O(1) at ~20k springs
|
springs[data] = output -- todo: investigate why insertion is not O(1) at ~20k springs
|
||||||
end
|
end
|
||||||
|
|
||||||
for _, input in next, inputs do
|
|
||||||
add_child(input, updater)
|
|
||||||
end
|
|
||||||
|
|
||||||
return function()
|
return function()
|
||||||
track(output)
|
track(output)
|
||||||
return output.cache
|
return output.cache
|
||||||
|
|
@ -258,6 +266,7 @@ local function update_spring_sources()
|
||||||
else
|
else
|
||||||
output.cache = vec6_to_type[typeof(data.source_value)](x0_123, x0_456)
|
output.cache = vec6_to_type[typeof(data.source_value)](x0_123, x0_456)
|
||||||
end
|
end
|
||||||
|
|
||||||
update(output)
|
update(output)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,20 +1,25 @@
|
||||||
if not game then script = require "test/relative-string" end
|
if not game then script = require "test/relative-string" end
|
||||||
|
|
||||||
|
local create = require(script.Parent.create)
|
||||||
local graph = require(script.Parent.graph)
|
local graph = require(script.Parent.graph)
|
||||||
type Node<T> = graph.Node<T>
|
type Node<T> = graph.Node<T>
|
||||||
local refs = graph.refs
|
local create_node = graph.create_node
|
||||||
|
local get_stack_scope = graph.get_stack_scope
|
||||||
|
local open_scope = graph.open_scope
|
||||||
|
local close_scope = graph.close_scope
|
||||||
|
|
||||||
|
local non_tracking_scope = create_node(false)
|
||||||
|
|
||||||
local function untrack<T>(source: () -> T): T
|
local function untrack<T>(source: () -> T): T
|
||||||
local initial = #refs
|
local scope = get_stack_scope(1)
|
||||||
|
|
||||||
local value = source()
|
open_scope(scope or non_tracking_scope)
|
||||||
|
|
||||||
-- remove any references made since `untrack()` was called
|
local v = source()
|
||||||
for i = initial, #refs do
|
|
||||||
refs[i] = nil
|
|
||||||
end
|
|
||||||
|
|
||||||
return value
|
close_scope()
|
||||||
|
|
||||||
|
return v
|
||||||
end
|
end
|
||||||
|
|
||||||
return untrack
|
return untrack
|
||||||
|
|
|
||||||
|
|
@ -2,20 +2,24 @@ 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 capture_parents = graph.capture_parents
|
|
||||||
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 track = graph.track
|
||||||
|
|
||||||
local function watch(effect: () -> ())
|
local function watch(effect: () -> ())
|
||||||
assert(get_scope())
|
local owner = get_scope()
|
||||||
|
assert(owner)
|
||||||
|
|
||||||
local node = create_node(false)
|
local node = create_node(false)
|
||||||
node.effect = effect
|
node.effect = effect
|
||||||
|
|
||||||
open_scope(node.scope)
|
add_child(owner, node)
|
||||||
|
|
||||||
capture_parents(node, effect :: () -> any)
|
open_scope(node)
|
||||||
|
|
||||||
|
effect()
|
||||||
|
|
||||||
close_scope()
|
close_scope()
|
||||||
end
|
end
|
||||||
|
|
|
||||||
623
test/tests.luau
623
test/tests.luau
|
|
@ -1,13 +1,15 @@
|
||||||
local testkit = require("test/testkit")
|
local testkit = require("test/testkit")
|
||||||
local TEST, CASE, CHECK, FINISH, SKIP = testkit.test()
|
local TEST, CASE, CHECK, FINISH, SKIP = testkit.test()
|
||||||
|
|
||||||
SKIP"graph"
|
|
||||||
|
|
||||||
local mock = require "test/mock"
|
local mock = require "test/mock"
|
||||||
local Instance, Signal = mock.Instance, mock.Signal
|
local Instance, Signal = mock.Instance, mock.Signal
|
||||||
local Vector2, UDim2 = mock.Vector2, mock.UDim2
|
local Vector2, UDim2 = mock.Vector2, mock.UDim2
|
||||||
|
|
||||||
local vide = require "src/init"
|
local vide = require "src/init"
|
||||||
|
local graph = require "src/graph"
|
||||||
|
type Node<T> = graph.Node<T>
|
||||||
|
|
||||||
|
type Map<K, V> = { [K] : V }
|
||||||
|
|
||||||
local function gc(n: number?)
|
local function gc(n: number?)
|
||||||
for i = 1, n or 3 do
|
for i = 1, n or 3 do
|
||||||
|
|
@ -27,26 +29,40 @@ local function wrap_root(fn: () -> ())
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
local NIL = NIL
|
||||||
|
|
||||||
TEST("graph", function()
|
TEST("graph", function()
|
||||||
local graph = require "src/graph"
|
|
||||||
local create_node = graph.create_node
|
local create_node = graph.create_node
|
||||||
|
local create_start_node = graph.create_start_node
|
||||||
local track = graph.track
|
local track = graph.track
|
||||||
local update = graph.update
|
local update = graph.update
|
||||||
local add_child = graph.add_child
|
local add_child = graph.add_child
|
||||||
local open_root_scope = graph.open_root_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 get_children = graph.get_children
|
local get_children = graph.get_children
|
||||||
|
local add_cleanup = graph.add_cleanup
|
||||||
|
local destroy = graph.destroy
|
||||||
|
|
||||||
do CASE "node creation"
|
local function node<T>(v: T?)
|
||||||
local node = create_node(1)
|
local n = create_node(v or false)
|
||||||
CHECK(node.cache == 1)
|
n.effect = function() end
|
||||||
|
return n
|
||||||
|
end
|
||||||
|
|
||||||
|
local function scope()
|
||||||
|
return create_node(false)
|
||||||
|
end
|
||||||
|
|
||||||
|
local function cleanup(fn: () -> ())
|
||||||
|
local node = assert(get_scope())
|
||||||
|
add_cleanup(node, fn)
|
||||||
end
|
end
|
||||||
|
|
||||||
do CASE "link nodes"
|
do CASE "link nodes"
|
||||||
local a = create_node(nil)
|
local a = node()
|
||||||
local b = create_node(nil)
|
local b = node()
|
||||||
local c = create_node(nil)
|
local c = node()
|
||||||
|
|
||||||
open_scope(c)
|
open_scope(c)
|
||||||
|
|
||||||
|
|
@ -60,9 +76,9 @@ TEST("graph", function()
|
||||||
end
|
end
|
||||||
|
|
||||||
do CASE "rerun linked nodes"
|
do CASE "rerun linked nodes"
|
||||||
local a = create_node(nil)
|
local a = node()
|
||||||
local b = create_node(nil)
|
local b = node()
|
||||||
local c = create_node(nil)
|
local c = node()
|
||||||
|
|
||||||
local count = 0
|
local count = 0
|
||||||
|
|
||||||
|
|
@ -87,53 +103,166 @@ TEST("graph", function()
|
||||||
CHECK(count == 3)
|
CHECK(count == 3)
|
||||||
end
|
end
|
||||||
|
|
||||||
do CASE "etst"
|
do CASE "case 1"
|
||||||
--[[
|
-- local function indexes<K, V>(input: Node<Map<K, V>>): Map<K, Node<V>>
|
||||||
|
-- local root = get_scope()
|
||||||
|
-- local updated = create_node(false)
|
||||||
|
|
||||||
root
|
-- local scopes = {}
|
||||||
Items -> Indexes()
|
-- local outputs = {}
|
||||||
|
|
||||||
indexes_root
|
-- function updated.effect()
|
||||||
v1 + sel -> bind
|
-- open_scope(root)
|
||||||
v2 + sel -> bind
|
|
||||||
|
|
||||||
]]
|
-- for i, v in input do
|
||||||
local items = create_node { 1, 2 }
|
-- if not scopes[i] then
|
||||||
|
-- scopes[i] = create_node(false)
|
||||||
|
-- outputs[i] = create_start_node(v)
|
||||||
|
-- end
|
||||||
|
|
||||||
local count = 0
|
-- open_scope(scopes[i])
|
||||||
|
-- outputs[i].cache = v
|
||||||
|
-- update(outputs[i])
|
||||||
|
-- close_scope()
|
||||||
|
-- end
|
||||||
|
|
||||||
local function effect()
|
-- for i, v in outputs do
|
||||||
track(a)
|
-- if input[i] == nil then
|
||||||
track(b)
|
-- destroy(scopes[i])
|
||||||
count += 1
|
-- end
|
||||||
|
-- end
|
||||||
|
|
||||||
|
-- close_scope()
|
||||||
|
-- end
|
||||||
|
|
||||||
|
-- open_scope(updated)
|
||||||
|
-- updated.effect(false)
|
||||||
|
-- close_scope()
|
||||||
|
|
||||||
|
-- return outputs
|
||||||
|
-- end
|
||||||
|
|
||||||
|
-- construct graph
|
||||||
|
|
||||||
|
local items = node { "a", "b" }
|
||||||
|
local selected = node "a"
|
||||||
|
|
||||||
|
local root = scope()
|
||||||
|
|
||||||
|
local scope1 = scope()
|
||||||
|
local scope2 = scope()
|
||||||
|
|
||||||
|
local items_updated
|
||||||
|
|
||||||
|
local bind1
|
||||||
|
local bind2
|
||||||
|
|
||||||
|
local cleaned = {} :: { [any]: any }
|
||||||
|
|
||||||
|
local function clean(s)
|
||||||
|
cleanup(function()
|
||||||
|
cleaned[s] = true
|
||||||
|
end)
|
||||||
end
|
end
|
||||||
|
|
||||||
c.effect = effect
|
do open_scope(root)
|
||||||
|
clean "root"
|
||||||
|
items_updated = node()
|
||||||
|
items_updated.effect = function() end
|
||||||
|
track(items_updated) -- should not
|
||||||
|
|
||||||
open_scope(c)
|
add_child(root, items_updated)
|
||||||
|
do open_scope(items_updated)
|
||||||
|
track(items)
|
||||||
|
|
||||||
effect()
|
do open_scope(root)
|
||||||
|
add_child(root, scope1)
|
||||||
|
do open_scope(scope1)
|
||||||
|
clean "scope1"
|
||||||
|
bind1 = node()
|
||||||
|
bind1.effect = function() end
|
||||||
|
|
||||||
close_scope()
|
add_child(scope1, bind1)
|
||||||
|
do open_scope(bind1)
|
||||||
|
clean "bind1"
|
||||||
|
track(selected)
|
||||||
|
close_scope() end
|
||||||
|
close_scope() end
|
||||||
|
|
||||||
CHECK(count == 1)
|
add_child(root, scope2)
|
||||||
update(a)
|
do open_scope(scope2)
|
||||||
CHECK(count == 2)
|
clean "scope2"
|
||||||
update(b)
|
bind2 = node()
|
||||||
CHECK(count == 3)
|
bind2.effect = function() end
|
||||||
|
add_child(scope2, bind2)
|
||||||
|
do open_scope(bind2)
|
||||||
|
clean "bind2"
|
||||||
|
track(selected)
|
||||||
|
close_scope() end
|
||||||
|
close_scope() end
|
||||||
|
close_scope() end
|
||||||
|
close_scope() end
|
||||||
|
close_scope() end
|
||||||
|
|
||||||
|
|
||||||
|
-- verify graph
|
||||||
|
|
||||||
|
do
|
||||||
|
local c = get_children(items_updated)
|
||||||
|
CHECK(#c == 0)
|
||||||
|
end
|
||||||
|
|
||||||
|
do
|
||||||
|
local c = get_children(root)
|
||||||
|
CHECK(#c == 3)
|
||||||
|
CHECK(table.find(c, items_updated))
|
||||||
|
CHECK(table.find(c, scope1 :: Node<any>))
|
||||||
|
CHECK(table.find(c, scope2 :: Node<any>))
|
||||||
|
end
|
||||||
|
|
||||||
|
do
|
||||||
|
local c = get_children(selected)
|
||||||
|
CHECK(#c == 2)
|
||||||
|
CHECK(table.find(c, bind1))
|
||||||
|
CHECK(table.find(c, bind2))
|
||||||
|
end
|
||||||
|
|
||||||
|
do
|
||||||
|
local c = get_children(scope1)
|
||||||
|
CHECK(#c == 1)
|
||||||
|
CHECK(table.find(c, bind1))
|
||||||
|
end
|
||||||
|
|
||||||
|
do
|
||||||
|
local c = get_children(scope2)
|
||||||
|
CHECK(#c == 1)
|
||||||
|
CHECK(table.find(c, bind2))
|
||||||
|
end
|
||||||
|
|
||||||
|
-- destroy
|
||||||
|
|
||||||
|
CHECK(table.find(get_children(root), scope1 :: Node<any>))
|
||||||
|
|
||||||
|
destroy(scope1)
|
||||||
|
CHECK(table.find(get_children(root), scope1 :: Node<any>))
|
||||||
|
CHECK(cleaned.scope1)
|
||||||
|
CHECK(cleaned.bind1)
|
||||||
|
scope1 = NIL
|
||||||
|
bind1 = NIL
|
||||||
|
bind2 = NIL
|
||||||
|
gc()
|
||||||
|
CHECK(#get_children(root) == 2)
|
||||||
|
CHECK(#get_children(selected) == 1)
|
||||||
end
|
end
|
||||||
|
|
||||||
-- todo: further tests
|
-- 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])
|
||||||
gc()
|
gc()
|
||||||
CHECK(not wref[1])
|
CHECK(not wref[1])
|
||||||
end
|
end
|
||||||
|
|
||||||
do CASE "test"
|
|
||||||
local x = 1
|
|
||||||
end
|
|
||||||
end)
|
end)
|
||||||
|
|
||||||
TEST("source()", wrap_root(function()
|
TEST("source()", wrap_root(function()
|
||||||
|
|
@ -154,31 +283,31 @@ TEST("source()", wrap_root(function()
|
||||||
do CASE "does not update if same value"
|
do CASE "does not update if same value"
|
||||||
local src = source(1)
|
local src = source(1)
|
||||||
|
|
||||||
local count = -1
|
local count = 0
|
||||||
watch(function()
|
watch(function()
|
||||||
src()
|
src()
|
||||||
count += 1
|
count += 1
|
||||||
end)
|
end)
|
||||||
|
|
||||||
CHECK(count == 0)
|
|
||||||
src(1)
|
|
||||||
CHECK(count == 0)
|
|
||||||
src(2)
|
|
||||||
CHECK(count == 1)
|
CHECK(count == 1)
|
||||||
|
src(1)
|
||||||
|
CHECK(count == 1)
|
||||||
|
src(2)
|
||||||
|
CHECK(count == 2)
|
||||||
end
|
end
|
||||||
|
|
||||||
do CASE "does update if same value is mutable table"
|
do CASE "does update if same value is mutable table"
|
||||||
local src = source {}
|
local src = source {}
|
||||||
|
|
||||||
local count = -1
|
local count = 0
|
||||||
watch(function()
|
watch(function()
|
||||||
src()
|
src()
|
||||||
count += 1
|
count += 1
|
||||||
end)
|
end)
|
||||||
|
|
||||||
CHECK(count == 0)
|
|
||||||
src(src())
|
|
||||||
CHECK(count == 1)
|
CHECK(count == 1)
|
||||||
|
src(src())
|
||||||
|
CHECK(count == 2)
|
||||||
end
|
end
|
||||||
|
|
||||||
do CASE "does not update if same value is frozen table"
|
do CASE "does not update if same value is frozen table"
|
||||||
|
|
@ -187,25 +316,26 @@ TEST("source()", wrap_root(function()
|
||||||
|
|
||||||
local src = source(a)
|
local src = source(a)
|
||||||
|
|
||||||
local count = -1
|
local count = 0
|
||||||
watch(function()
|
watch(function()
|
||||||
src()
|
src()
|
||||||
count += 1
|
count += 1
|
||||||
end)
|
end)
|
||||||
|
|
||||||
CHECK(count == 0)
|
CHECK(count == 1)
|
||||||
src(a)
|
src(a)
|
||||||
CHECK(count == 0)
|
|
||||||
src(b)
|
|
||||||
CHECK(count == 1)
|
CHECK(count == 1)
|
||||||
src(b)
|
src(b)
|
||||||
CHECK(count == 1)
|
CHECK(count == 2)
|
||||||
|
src(b)
|
||||||
|
CHECK(count == 2)
|
||||||
end
|
end
|
||||||
end))
|
end))
|
||||||
|
|
||||||
TEST("derive()", wrap_root(function()
|
TEST("derive()", wrap_root(function()
|
||||||
local source = vide.source
|
local source = vide.source
|
||||||
local derive = vide.derive
|
local derive = vide.derive
|
||||||
|
local watch = vide.watch
|
||||||
|
|
||||||
do CASE "derive new value on source change"
|
do CASE "derive new value on source change"
|
||||||
local a = source(1)
|
local a = source(1)
|
||||||
|
|
@ -236,6 +366,30 @@ TEST("derive()", wrap_root(function()
|
||||||
CHECK(c() == 2)
|
CHECK(c() == 2)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
do CASE "does not update if same value"
|
||||||
|
local num = source(0)
|
||||||
|
|
||||||
|
local is_even = derive(function()
|
||||||
|
return num() % 2 == 0
|
||||||
|
end)
|
||||||
|
|
||||||
|
local count = 0
|
||||||
|
|
||||||
|
watch(function()
|
||||||
|
is_even()
|
||||||
|
count += 1
|
||||||
|
end)
|
||||||
|
|
||||||
|
num(1) -- odd
|
||||||
|
CHECK(count == 2)
|
||||||
|
num(2) -- even
|
||||||
|
CHECK(count == 3)
|
||||||
|
num(4) -- even
|
||||||
|
CHECK(count == 3)
|
||||||
|
num(5) -- odd
|
||||||
|
CHECK(count == 4)
|
||||||
|
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)
|
||||||
|
|
@ -244,7 +398,7 @@ TEST("derive()", wrap_root(function()
|
||||||
return a()
|
return a()
|
||||||
end)
|
end)
|
||||||
|
|
||||||
b = nil :: any
|
b = NIL
|
||||||
|
|
||||||
local wref = weak { a }
|
local wref = weak { a }
|
||||||
|
|
||||||
|
|
@ -256,9 +410,8 @@ end))
|
||||||
TEST("watch()", wrap_root(function()
|
TEST("watch()", wrap_root(function()
|
||||||
local source = vide.source
|
local source = vide.source
|
||||||
local watch = vide.watch
|
local watch = vide.watch
|
||||||
local cleanup = vide.cleanup
|
|
||||||
|
|
||||||
do CASE "capture sources"
|
do CASE "rerun on source change"
|
||||||
local a = source(1)
|
local a = source(1)
|
||||||
local b = source(1)
|
local b = source(1)
|
||||||
|
|
||||||
|
|
@ -275,70 +428,31 @@ TEST("watch()", wrap_root(function()
|
||||||
b(2)
|
b(2)
|
||||||
CHECK(count == 3)
|
CHECK(count == 3)
|
||||||
end
|
end
|
||||||
|
|
||||||
do CASE "side-effect cleanup"
|
|
||||||
local state = source(1)
|
|
||||||
|
|
||||||
local effect_count = 0
|
|
||||||
local cleanup_count = 0
|
|
||||||
|
|
||||||
watch(function()
|
|
||||||
state()
|
|
||||||
effect_count += 1
|
|
||||||
cleanup(function() cleanup_count += 1 end)
|
|
||||||
end)
|
|
||||||
|
|
||||||
CHECK(effect_count == 1)
|
|
||||||
CHECK(cleanup_count == 0)
|
|
||||||
state(2)
|
|
||||||
CHECK(effect_count == 2)
|
|
||||||
CHECK(cleanup_count == 1)
|
|
||||||
end
|
|
||||||
|
|
||||||
do CASE "garbage collection"
|
|
||||||
local function factory(p)
|
|
||||||
return function()
|
|
||||||
p()
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
do -- state prevents gc of watcher
|
|
||||||
local state = source(1)
|
|
||||||
|
|
||||||
local wref
|
|
||||||
|
|
||||||
do
|
|
||||||
local effect = factory(state)
|
|
||||||
watch(effect)
|
|
||||||
wref = { effect }
|
|
||||||
end
|
|
||||||
|
|
||||||
gc()
|
|
||||||
CHECK(wref[1])
|
|
||||||
end
|
|
||||||
|
|
||||||
do -- state can gc with watcher
|
|
||||||
local wref
|
|
||||||
|
|
||||||
do
|
|
||||||
local state = source(1)
|
|
||||||
local effect = factory(state)
|
|
||||||
watch(effect)
|
|
||||||
wref = weak { state }
|
|
||||||
end
|
|
||||||
|
|
||||||
gc()
|
|
||||||
CHECK(not wref[1])
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end))
|
end))
|
||||||
|
|
||||||
TEST("cleanup()", wrap_root(function()
|
TEST("cleanup()", wrap_root(function()
|
||||||
|
local root = vide.root
|
||||||
local source = vide.source
|
local source = vide.source
|
||||||
local watch = vide.watch
|
local watch = vide.watch
|
||||||
local cleanup = vide.cleanup
|
local cleanup = vide.cleanup
|
||||||
|
|
||||||
do CASE "cleanup runs for watcher"
|
do CASE "root cleanup"
|
||||||
|
local count = 0
|
||||||
|
|
||||||
|
local _, destroy = root(function()
|
||||||
|
cleanup(function()
|
||||||
|
count += 1
|
||||||
|
end)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
end)
|
||||||
|
|
||||||
|
CHECK(count == 0)
|
||||||
|
destroy()
|
||||||
|
CHECK(count == 1)
|
||||||
|
end
|
||||||
|
|
||||||
|
do CASE "cleanup on rerun"
|
||||||
local state = source(1)
|
local state = source(1)
|
||||||
|
|
||||||
local watched = 0
|
local watched = 0
|
||||||
|
|
@ -381,8 +495,10 @@ TEST("cleanup()", wrap_root(function()
|
||||||
end))
|
end))
|
||||||
|
|
||||||
TEST("create()", wrap_root(function()
|
TEST("create()", wrap_root(function()
|
||||||
|
local root = vide.root
|
||||||
local create = vide.create
|
local create = vide.create
|
||||||
local source = vide.source
|
local source = vide.source
|
||||||
|
local cleanup = vide.cleanup
|
||||||
|
|
||||||
do CASE "apply default properties"
|
do CASE "apply default properties"
|
||||||
local defaults = require("src/defaults")
|
local defaults = require("src/defaults")
|
||||||
|
|
@ -429,9 +545,7 @@ TEST("create()", wrap_root(function()
|
||||||
{
|
{
|
||||||
Text = "1",
|
Text = "1",
|
||||||
|
|
||||||
{
|
{ Text = "2" }
|
||||||
Text = "2"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -489,129 +603,38 @@ TEST("create()", wrap_root(function()
|
||||||
CHECK(label.Text == "Bar")
|
CHECK(label.Text == "Bar")
|
||||||
end
|
end
|
||||||
|
|
||||||
do CASE "binding garbage collection"
|
do CASE "binding destroy"
|
||||||
--[[
|
local count = 0
|
||||||
do -- instance should gc when unparented
|
|
||||||
local state = source("Hi")
|
|
||||||
|
|
||||||
local wref = weak {
|
local _, destroy = root(function()
|
||||||
create "TextLabel" {
|
local src = source(0)
|
||||||
Text = state,
|
|
||||||
}
|
return create "TextLabel" {
|
||||||
|
Text = function()
|
||||||
|
cleanup(function()
|
||||||
|
count += 1
|
||||||
|
end)
|
||||||
|
|
||||||
|
return src()
|
||||||
|
end
|
||||||
}
|
}
|
||||||
|
end)
|
||||||
|
|
||||||
gc()
|
CHECK(count == 0)
|
||||||
CHECK(not wref[1])
|
destroy()
|
||||||
end
|
CHECK(count == 1)
|
||||||
]]
|
|
||||||
|
|
||||||
--[[
|
|
||||||
do -- instance should not gc when parented
|
|
||||||
local state = source("Hi")
|
|
||||||
|
|
||||||
local parent = create "Frame" {}
|
|
||||||
|
|
||||||
local wref = weak {
|
|
||||||
create "TextLabel" {
|
|
||||||
Parent = parent,
|
|
||||||
Text = state,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
gc()
|
|
||||||
CHECK(wref[1])
|
|
||||||
|
|
||||||
wref[1].Parent = nil
|
|
||||||
wref[1].Parent = parent
|
|
||||||
|
|
||||||
gc()
|
|
||||||
CHECK(wref[1])
|
|
||||||
|
|
||||||
wref[1]:Destroy()
|
|
||||||
|
|
||||||
gc()
|
|
||||||
CHECK(not wref[1])
|
|
||||||
end
|
|
||||||
]]
|
|
||||||
|
|
||||||
--[[
|
|
||||||
do -- instance does not allow gc of state
|
|
||||||
local label
|
|
||||||
local wref
|
|
||||||
|
|
||||||
do
|
|
||||||
local state = source("Hi")
|
|
||||||
label = create "TextLabel" {
|
|
||||||
Name = state,
|
|
||||||
}
|
|
||||||
wref = weak { state :: any, label }
|
|
||||||
|
|
||||||
end
|
|
||||||
|
|
||||||
gc()
|
|
||||||
CHECK(wref[2])
|
|
||||||
CHECK(wref[1])
|
|
||||||
end
|
|
||||||
]]
|
|
||||||
|
|
||||||
do -- state and instance should gc once both exit scope
|
|
||||||
local wref
|
|
||||||
|
|
||||||
do
|
|
||||||
local text = source("Hi")
|
|
||||||
|
|
||||||
local box = create "TextLabel" {
|
|
||||||
Text = text,
|
|
||||||
}
|
|
||||||
|
|
||||||
wref = weak { text = text, box = box}
|
|
||||||
end
|
|
||||||
|
|
||||||
gc()
|
|
||||||
CHECK(not wref.text)
|
|
||||||
CHECK(not wref.box)
|
|
||||||
end
|
|
||||||
|
|
||||||
--[[
|
|
||||||
do -- binding should gc despite state still existing after instance is gc
|
|
||||||
local state = source("Hi")
|
|
||||||
|
|
||||||
local node = require "src/graph".capture(state)[1]
|
|
||||||
|
|
||||||
local wref
|
|
||||||
|
|
||||||
do
|
|
||||||
local instance = create "TextLabel" {
|
|
||||||
Text = state,
|
|
||||||
}
|
|
||||||
|
|
||||||
local binding = assert(node[1])
|
|
||||||
|
|
||||||
wref = weak {
|
|
||||||
instance = instance,
|
|
||||||
binding = binding
|
|
||||||
}
|
|
||||||
end
|
|
||||||
|
|
||||||
CHECK(wref.binding)
|
|
||||||
|
|
||||||
gc()
|
|
||||||
CHECK(not wref.instance)
|
|
||||||
CHECK(not wref.binding)
|
|
||||||
end
|
|
||||||
]]
|
|
||||||
end
|
end
|
||||||
|
|
||||||
do CASE "bind same state to multiple instance properties"
|
do CASE "bind same state to multiple instance properties"
|
||||||
local state = source "1"
|
local src = source "1"
|
||||||
|
|
||||||
local text = create "TextBox" {
|
local text = create "TextBox" {
|
||||||
Name = state,
|
Name = src,
|
||||||
Text = state,
|
Text = src,
|
||||||
PlaceholderText = state
|
PlaceholderText = src
|
||||||
}
|
}
|
||||||
|
|
||||||
state "2"
|
src "2"
|
||||||
|
|
||||||
CHECK(text.Name == "2")
|
CHECK(text.Name == "2")
|
||||||
CHECK(text.Text == "2")
|
CHECK(text.Text == "2")
|
||||||
|
|
@ -619,7 +642,7 @@ TEST("create()", wrap_root(function()
|
||||||
end
|
end
|
||||||
|
|
||||||
do CASE "bind children"
|
do CASE "bind children"
|
||||||
local state = source()
|
local children = source()
|
||||||
|
|
||||||
local a, b, c =
|
local a, b, c =
|
||||||
create "TextLabel" { Name = "A" },
|
create "TextLabel" { Name = "A" },
|
||||||
|
|
@ -627,50 +650,53 @@ TEST("create()", wrap_root(function()
|
||||||
create "TextLabel" { Name = "C" }
|
create "TextLabel" { Name = "C" }
|
||||||
|
|
||||||
local frame = create "Frame" {
|
local frame = create "Frame" {
|
||||||
state
|
children
|
||||||
}
|
}
|
||||||
|
|
||||||
state { a, b }
|
children { a, b }
|
||||||
|
|
||||||
CHECK(frame:FindFirstChild "A")
|
CHECK(frame:FindFirstChild "A")
|
||||||
CHECK(frame:FindFirstChild "B")
|
CHECK(frame:FindFirstChild "B")
|
||||||
|
|
||||||
-- check that b is removed and c is added while a remains untouched
|
-- check that b is removed and c is added while a remains untouched
|
||||||
|
|
||||||
state { a, c }
|
children { a, c }
|
||||||
|
|
||||||
CHECK(frame:FindFirstChild "A")
|
CHECK(frame:FindFirstChild "A")
|
||||||
CHECK(frame:FindFirstChild "C")
|
CHECK(frame:FindFirstChild "C")
|
||||||
CHECK(not frame:FindFirstChild "B")
|
CHECK(not frame:FindFirstChild "B")
|
||||||
|
|
||||||
state(nil)
|
children(nil)
|
||||||
|
|
||||||
CHECK(#frame:GetChildren() == 0)
|
CHECK(#frame:GetChildren() == 0)
|
||||||
end
|
end
|
||||||
|
|
||||||
--[[
|
do CASE "parent bound to source"
|
||||||
do CASE "Parent set to nil by state does not allow gc"
|
local wref, destroy = root(function()
|
||||||
local frame = create "Frame" { Name = "Parent" }
|
local frame = create "Frame" { Name = "Parent" }
|
||||||
local parent = source(frame :: Frame?)
|
local parent = source(frame :: Frame?)
|
||||||
|
|
||||||
local wref = weak {
|
local wref = weak {
|
||||||
create "TextLabel" { Parent = parent, Name = "Child" }
|
create "TextLabel" { Parent = parent, Name = "Child" }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
gc()
|
||||||
|
CHECK(wref[1])
|
||||||
|
|
||||||
|
parent(nil)
|
||||||
|
|
||||||
|
return wref
|
||||||
|
end)
|
||||||
|
|
||||||
gc()
|
gc()
|
||||||
CHECK(wref[1])
|
CHECK(wref[1])
|
||||||
|
|
||||||
parent(nil)
|
destroy()
|
||||||
|
destroy = NIL
|
||||||
gc()
|
|
||||||
CHECK(wref[1])
|
|
||||||
|
|
||||||
wref[1]:Destroy()
|
|
||||||
|
|
||||||
gc()
|
gc()
|
||||||
CHECK(not wref[1])
|
CHECK(not wref[1])
|
||||||
end
|
end
|
||||||
]]
|
|
||||||
|
|
||||||
do CASE "garbage collection test"
|
do CASE "garbage collection test"
|
||||||
local wref
|
local wref
|
||||||
|
|
@ -761,7 +787,7 @@ TEST("indexes()", wrap_root(function()
|
||||||
|
|
||||||
CHECK(t[1].Text == "1")
|
CHECK(t[1].Text == "1")
|
||||||
CHECK(t[2].Text == "2")
|
CHECK(t[2].Text == "2")
|
||||||
CHECK(t[3] == nil :: any)
|
CHECK(t[3] == NIL)
|
||||||
CHECK(destroyed == true)
|
CHECK(destroyed == true)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
@ -775,7 +801,7 @@ TEST("indexes()", wrap_root(function()
|
||||||
|
|
||||||
local wref = weak { input }
|
local wref = weak { input }
|
||||||
|
|
||||||
input = nil :: any
|
input = NIL
|
||||||
|
|
||||||
gc()
|
gc()
|
||||||
CHECK(wref[1])
|
CHECK(wref[1])
|
||||||
|
|
@ -790,7 +816,7 @@ TEST("indexes()", wrap_root(function()
|
||||||
|
|
||||||
local wref = weak { output }
|
local wref = weak { output }
|
||||||
|
|
||||||
output = nil :: any
|
output = NIL
|
||||||
|
|
||||||
gc()
|
gc()
|
||||||
CHECK(not wref[1])
|
CHECK(not wref[1])
|
||||||
|
|
@ -880,7 +906,7 @@ TEST("values()", wrap_root(function()
|
||||||
|
|
||||||
CHECK(t[1].Text == "1")
|
CHECK(t[1].Text == "1")
|
||||||
CHECK(t[2].Text == "2")
|
CHECK(t[2].Text == "2")
|
||||||
CHECK(t[3] == nil :: any)
|
CHECK(t[3] == NIL)
|
||||||
CHECK(destroyed == true)
|
CHECK(destroyed == true)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
@ -931,13 +957,14 @@ TEST("spring()", wrap_root(function()
|
||||||
|
|
||||||
do CASE "update source (on next step)"
|
do CASE "update source (on next step)"
|
||||||
local value = source(10)
|
local value = source(10)
|
||||||
local springed = spring(value, 1, 1)
|
local sprung = spring(value, 1, 1)
|
||||||
|
|
||||||
|
CHECK(sprung() == 10)
|
||||||
value(20)
|
value(20)
|
||||||
CHECK(springed() == 10)
|
CHECK(sprung() == 10)
|
||||||
vide.step(1/60)
|
vide.step(1/60)
|
||||||
CHECK(springed() ~= 10)
|
CHECK(sprung() ~= 10)
|
||||||
CHECK(springed() > 10)
|
CHECK(sprung() > 10)
|
||||||
end
|
end
|
||||||
|
|
||||||
do CASE "garbage collection"
|
do CASE "garbage collection"
|
||||||
|
|
@ -947,7 +974,7 @@ TEST("spring()", wrap_root(function()
|
||||||
local _output = spring(input)
|
local _output = spring(input)
|
||||||
|
|
||||||
local wref = weak { input }
|
local wref = weak { input }
|
||||||
input = nil :: any
|
input = NIL
|
||||||
|
|
||||||
gc()
|
gc()
|
||||||
CHECK(wref[1])
|
CHECK(wref[1])
|
||||||
|
|
@ -959,28 +986,26 @@ TEST("spring()", wrap_root(function()
|
||||||
local output = spring(input)
|
local output = spring(input)
|
||||||
|
|
||||||
local wref = weak { output }
|
local wref = weak { output }
|
||||||
output = nil :: any
|
output = NIL
|
||||||
|
|
||||||
gc()
|
gc()
|
||||||
CHECK(not wref[1])
|
CHECK(not wref[1])
|
||||||
end
|
end
|
||||||
|
|
||||||
do -- spring data gc
|
-- do -- spring data gc
|
||||||
local capture = require "src/graph".capture
|
-- local input = source(10)
|
||||||
|
|
||||||
local input = source(10)
|
-- local wref do
|
||||||
|
-- local output, data = (spring :: any)(input)
|
||||||
|
-- input(input() + 1) -- schedule spring calculation
|
||||||
|
-- local output_node = unpack(capture(output))
|
||||||
|
-- wref = weak { output_node, data }
|
||||||
|
-- end
|
||||||
|
|
||||||
local wref do
|
-- gc()
|
||||||
local output, data = (spring :: any)(input)
|
-- CHECK(not wref[1])
|
||||||
input(input() + 1) -- schedule spring calculation
|
-- CHECK(not wref[2])
|
||||||
local output_node = unpack(capture(output))
|
-- end
|
||||||
wref = weak { output_node, data }
|
|
||||||
end
|
|
||||||
|
|
||||||
gc()
|
|
||||||
CHECK(not wref[1])
|
|
||||||
CHECK(not wref[2])
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
do CASE "garbage collection (binded)"
|
do CASE "garbage collection (binded)"
|
||||||
|
|
@ -992,7 +1017,7 @@ TEST("spring()", wrap_root(function()
|
||||||
}
|
}
|
||||||
|
|
||||||
local wref = { output }
|
local wref = { output }
|
||||||
output = nil :: any
|
output = NIL
|
||||||
|
|
||||||
gc()
|
gc()
|
||||||
CHECK(wref[1]) -- `output` should not gc
|
CHECK(wref[1]) -- `output` should not gc
|
||||||
|
|
@ -1026,8 +1051,11 @@ TEST("spring()", wrap_root(function()
|
||||||
end))
|
end))
|
||||||
|
|
||||||
TEST("untrack()", wrap_root(function()
|
TEST("untrack()", wrap_root(function()
|
||||||
|
local root = vide.root
|
||||||
local source = vide.source
|
local source = vide.source
|
||||||
|
local derive = vide.derive
|
||||||
local watch = vide.watch
|
local watch = vide.watch
|
||||||
|
local cleanup = vide.cleanup
|
||||||
local untrack = vide.untrack
|
local untrack = vide.untrack
|
||||||
|
|
||||||
do CASE "does not register dependency"
|
do CASE "does not register dependency"
|
||||||
|
|
@ -1075,6 +1103,55 @@ TEST("untrack()", wrap_root(function()
|
||||||
b(1)
|
b(1)
|
||||||
CHECK(count == 1)
|
CHECK(count == 1)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
do CASE "outer scope"
|
||||||
|
local outer_count = 0
|
||||||
|
local inner_count = 0
|
||||||
|
local cleaned_count = 0
|
||||||
|
|
||||||
|
local input = source(0)
|
||||||
|
|
||||||
|
local output, destroy = root(function()
|
||||||
|
local output = derive(function()
|
||||||
|
outer_count += 1
|
||||||
|
|
||||||
|
return untrack(function()
|
||||||
|
return derive(function()
|
||||||
|
inner_count += 1
|
||||||
|
|
||||||
|
cleanup(function()
|
||||||
|
cleaned_count += 1
|
||||||
|
end)
|
||||||
|
|
||||||
|
return tostring(input())
|
||||||
|
end)
|
||||||
|
end)
|
||||||
|
end)
|
||||||
|
|
||||||
|
return output
|
||||||
|
end)
|
||||||
|
|
||||||
|
CHECK(outer_count == 1)
|
||||||
|
CHECK(inner_count == 1)
|
||||||
|
CHECK(cleaned_count == 0)
|
||||||
|
|
||||||
|
local output2 = output()
|
||||||
|
|
||||||
|
CHECK(output2() == "0")
|
||||||
|
|
||||||
|
input(1)
|
||||||
|
|
||||||
|
-- todo
|
||||||
|
CHECK(outer_count == 2)
|
||||||
|
CHECK(inner_count == 3)
|
||||||
|
CHECK(cleaned_count == 1)
|
||||||
|
|
||||||
|
local output3 = output()
|
||||||
|
CHECK(output2() == "1")
|
||||||
|
CHECK(output3() == "1")
|
||||||
|
|
||||||
|
CHECK(output2 ~= output3)
|
||||||
|
end
|
||||||
end))
|
end))
|
||||||
|
|
||||||
TEST("events", function()
|
TEST("events", function()
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue