mirror of
https://github.com/centau/vide.git
synced 2026-08-20 14:41:37 +00:00
This commit is contained in:
parent
0d542c66a6
commit
7dead44ac5
9 changed files with 207 additions and 173 deletions
|
|
@ -17,7 +17,7 @@ local function action(callback: (Instance) -> (), priority: number?): Action
|
||||||
|
|
||||||
setmetatable(t :: any, ActionMT)
|
setmetatable(t :: any, ActionMT)
|
||||||
|
|
||||||
return t
|
return table.freeze(t)
|
||||||
end
|
end
|
||||||
|
|
||||||
return function()
|
return function()
|
||||||
|
|
|
||||||
|
|
@ -10,11 +10,16 @@ local _, is_action = require(script.Parent.action)()
|
||||||
local graph = require(script.Parent.graph)
|
local graph = require(script.Parent.graph)
|
||||||
type Node<T> = graph.Node<T>
|
type Node<T> = graph.Node<T>
|
||||||
|
|
||||||
|
type Array<V> = { V }
|
||||||
|
type Map<K, V> = { [K]: V }
|
||||||
|
|
||||||
-- buffer of event -> callback to connect after properties are set
|
-- buffer of event -> callback to connect after properties are set
|
||||||
local event_buffer: { [string]: () -> () } = {}
|
local event_buffer = {} :: Map<string, () -> ()>
|
||||||
|
|
||||||
-- buffer of priority -> callback to run after events are connected
|
-- buffer of priority -> callback to run after events are connected
|
||||||
local action_buffers = {} :: { { (Instance) -> () } }
|
local action_buffers = {} :: Map<number, Array<(Instance) -> ()>>
|
||||||
|
|
||||||
|
-- lazily create buffers on nil index
|
||||||
setmetatable(action_buffers :: any, {
|
setmetatable(action_buffers :: any, {
|
||||||
__index = function(_, i: number)
|
__index = function(_, i: number)
|
||||||
action_buffers[i] = {}
|
action_buffers[i] = {}
|
||||||
|
|
@ -22,8 +27,9 @@ setmetatable(action_buffers :: any, {
|
||||||
end
|
end
|
||||||
})
|
})
|
||||||
|
|
||||||
-- cache used in strict mode to detect duplicate property sets at same nesting levels
|
-- cache in strict mode to detect duplicate property set at same nesting level
|
||||||
local nested_debug_cache: { [number]: { [string]: true } } = {}
|
local nested_debug_cache = {} :: Map<number, Map<string, true>>
|
||||||
|
|
||||||
setmetatable(nested_debug_cache :: any, {
|
setmetatable(nested_debug_cache :: any, {
|
||||||
__index = function(_, i: number)
|
__index = function(_, i: number)
|
||||||
nested_debug_cache[i] = {}
|
nested_debug_cache[i] = {}
|
||||||
|
|
@ -31,28 +37,30 @@ setmetatable(nested_debug_cache :: any, {
|
||||||
end
|
end
|
||||||
})
|
})
|
||||||
|
|
||||||
-- a stack used in place of a recursive function to process nesting layers one at a time
|
-- use stack instead of recursive function to process nested layers one at time
|
||||||
-- enforces the behavior of deeper-nested properties taking precedence of lesser-nested ones
|
-- deeper-nested properties take precedence over shallower-nested ones
|
||||||
-- each nested table occupies two indexes, reference to table itself and the depth number
|
-- each nested layer occupies two indexes: 1. table ref 2. nested depth
|
||||||
-- e.g. props = { t1 = { t3 = {} }, t2 = {} } -> { t1, 1, t2, 1, t3, 2 }
|
-- e.g. { t1 = { t3 = {} }, t2 = {} } -> { t1, 1, t2, 1, t3, 2 }
|
||||||
local nested_stack = {} :: { {} | number }
|
local nested_stack = {} :: { {} | number }
|
||||||
|
|
||||||
-- todo: solution without manual updating of this table
|
-- todo: solution without manual updating of this table
|
||||||
-- map of datatype names to class default constructor for aggregate initialization
|
-- map of datatype names to class default constructor for aggregate init
|
||||||
local aggregates = {}
|
local aggregates = {}
|
||||||
|
|
||||||
for i, v in next, {
|
for i, v in next, {
|
||||||
Vector2 = Vector2,
|
CFrame = CFrame,
|
||||||
UDim2 = UDim2,
|
Color3 = Color3,
|
||||||
UDim = UDim,
|
UDim = UDim,
|
||||||
Rect = Rect,
|
UDim2 = UDim2,
|
||||||
Color3 = Color3
|
Vector2 = Vector2,
|
||||||
|
Vector3 = Vector3,
|
||||||
|
Rect = Rect
|
||||||
} do
|
} do
|
||||||
aggregates[i] = v.new
|
aggregates[i] = v.new
|
||||||
end
|
end
|
||||||
|
|
||||||
-- processes a potentially nested table of values to assign to an instance
|
-- processes a potentially nested table of values to assign to an instance
|
||||||
local function process_nested(instance: Instance, properties: { [unknown]: unknown })
|
local function process_props(instance: Instance, properties: Map<unknown, unknown>)
|
||||||
local strict = flags.strict
|
local strict = flags.strict
|
||||||
|
|
||||||
table.clear(nested_stack)
|
table.clear(nested_stack)
|
||||||
|
|
@ -73,27 +81,27 @@ local function process_nested(instance: Instance, properties: { [unknown]: unkno
|
||||||
if type(value) == "table" then -- attempt aggregate init
|
if type(value) == "table" then -- attempt aggregate init
|
||||||
local ctor = aggregates[typeof((instance :: any)[property])]
|
local ctor = aggregates[typeof((instance :: any)[property])]
|
||||||
if ctor == nil then
|
if ctor == nil then
|
||||||
throw(`cannot aggregate construct type {typeof(value)} for property {property}`)
|
throw(`cannot aggregate type {typeof(value)} for property {property}`)
|
||||||
end
|
end
|
||||||
(instance :: any)[property] = ctor(unpack(value :: {}))
|
(instance :: any)[property] = ctor(unpack(value :: {}))
|
||||||
elseif type(value) == "function" then
|
elseif type(value) == "function" then
|
||||||
if typeof((instance :: any)[property]) == "RBXScriptSignal" then
|
if typeof((instance :: any)[property]) == "RBXScriptSignal" then
|
||||||
event_buffer[property] = value :: () -> () -- add event to buffer
|
event_buffer[property] = value :: () -> () -- add event to buffer
|
||||||
else
|
else
|
||||||
bind.property(instance, property, value :: () -> ()) -- bind source
|
bind.property(instance, property, value :: () -> ()) -- bind property
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
(instance :: any)[property] = value -- set property
|
(instance :: any)[property] = value -- set property
|
||||||
end
|
end
|
||||||
elseif type(property) == "number" then
|
elseif type(property) == "number" then
|
||||||
if type(value) == "function" then
|
if type(value) == "function" then
|
||||||
bind.children(instance, value :: () -> { Instance }) -- bind children
|
bind.children(instance, value :: () -> Instance | Array<Instance>) -- bind children
|
||||||
elseif type(value) == "table" then
|
elseif type(value) == "table" then
|
||||||
if is_action(value) then
|
if is_action(value) then
|
||||||
table.insert(action_buffers[(value :: any).priority], (value :: any).callback :: () -> ()) -- add action to buffer
|
table.insert(action_buffers[(value :: any).priority], (value :: any).callback :: () -> ()) -- add action to buffer
|
||||||
else
|
else
|
||||||
table.insert(nested_stack, depth + 1) -- push table to stack for later processing
|
|
||||||
table.insert(nested_stack, value :: {})
|
table.insert(nested_stack, value :: {})
|
||||||
|
table.insert(nested_stack, depth + 1) -- push table to stack for later processing
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
(value :: Instance).Parent = instance -- parent child
|
(value :: Instance).Parent = instance -- parent child
|
||||||
|
|
@ -102,8 +110,8 @@ local function process_nested(instance: Instance, properties: { [unknown]: unkno
|
||||||
end
|
end
|
||||||
|
|
||||||
-- pop next nested table off stack
|
-- pop next nested table off stack
|
||||||
properties = table.remove(nested_stack) :: {}
|
|
||||||
depth = table.remove(nested_stack) :: number
|
depth = table.remove(nested_stack) :: number
|
||||||
|
properties = table.remove(nested_stack) :: {}
|
||||||
|
|
||||||
until not properties
|
until not properties
|
||||||
end
|
end
|
||||||
|
|
@ -121,14 +129,14 @@ local function apply<T>(instance: T & Instance, properties: { [unknown]: unknown
|
||||||
end
|
end
|
||||||
|
|
||||||
-- process all properties for immediate setting or buffering
|
-- process all properties for immediate setting or buffering
|
||||||
process_nested(instance, properties)
|
process_props(instance, properties)
|
||||||
|
|
||||||
-- connect buffered events
|
-- connect buffered events
|
||||||
for event, fn in next, event_buffer do
|
for event, fn in next, event_buffer do
|
||||||
(instance :: any)[event]:Connect(fn)
|
(instance :: any)[event]:Connect(fn)
|
||||||
end
|
end
|
||||||
|
|
||||||
-- run buffered actions respecting their priorities
|
-- run buffered actions
|
||||||
for _, buffer in next, action_buffers do
|
for _, buffer in next, action_buffers do
|
||||||
for _, callback in next, buffer do
|
for _, callback in next, buffer do
|
||||||
callback(instance)
|
callback(instance)
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,4 @@
|
||||||
if not game then script = require "test/relative-string" end
|
if not game then script = require "test/relative-string" end
|
||||||
local warn = game and warn or print :: never
|
|
||||||
|
|
||||||
local throw = require(script.Parent.throw)
|
local throw = require(script.Parent.throw)
|
||||||
local flags = require(script.Parent.flags)
|
local flags = require(script.Parent.flags)
|
||||||
|
|
@ -33,27 +32,38 @@ 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) -> T, binding_data: T)
|
function create_binding<T>(updater: (T) -> T, binding: T)
|
||||||
-- if flags.strict then
|
if flags.strict then
|
||||||
-- -- wrap setter in function with stack inspection for better error msgs
|
-- track bind creation trace
|
||||||
-- local fn = setter
|
local fn = updater
|
||||||
-- local bind_trace = traceback(0)
|
local bind_trace = traceback(0)
|
||||||
-- setter = function(instance)
|
updater = function(...)
|
||||||
-- local ok, err: string? = xpcall(fn, function(err: string)
|
local ok, result = xpcall(fn, function(err: string)
|
||||||
-- return err .. "\nsource updated at: " .. traceback(2)
|
return err
|
||||||
-- end, instance)
|
end, ...)
|
||||||
-- if not ok then warn(`error occured updating {property}: {err}bound at: {bind_trace}`) end
|
|
||||||
-- end
|
|
||||||
-- end
|
|
||||||
|
|
||||||
local binding = create_node(binding_data, updater)
|
if not ok then
|
||||||
|
local btype =
|
||||||
|
if (binding :: any).property then (binding :: any).property
|
||||||
|
elseif (binding :: any).parent then "Parent"
|
||||||
|
else "children"
|
||||||
|
error(`PROPERTY BINDING ERROR: Property {btype}\n{result}\nBIND CREATION TRACE:\n{bind_trace}`, 0)
|
||||||
|
end
|
||||||
|
|
||||||
|
return result
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
local owner = get_scope()
|
local owner = get_scope()
|
||||||
if not owner then throw("cannot bind property in non-reactive scope") end
|
if not owner then
|
||||||
assert(owner)
|
throw("cannot bind property in non-reactive scope")
|
||||||
|
end; assert(owner)
|
||||||
|
|
||||||
|
local node = create_node(binding, updater)
|
||||||
|
|
||||||
set_owner(binding, owner)
|
set_owner(node, owner)
|
||||||
evaluate_node(binding)
|
evaluate_node(node)
|
||||||
end
|
end
|
||||||
|
|
||||||
type PropertyBinding = {
|
type PropertyBinding = {
|
||||||
|
|
@ -81,7 +91,7 @@ type ChildrenBinding = {
|
||||||
instance: Instance,
|
instance: Instance,
|
||||||
cur_children_set: { [Instance]: true },
|
cur_children_set: { [Instance]: true },
|
||||||
new_children_set: { [Instance]: true },
|
new_children_set: { [Instance]: true },
|
||||||
children: () -> { Instance }
|
children: () -> Instance | { Instance }
|
||||||
}
|
}
|
||||||
|
|
||||||
local function update_children(p: ChildrenBinding)
|
local function update_children(p: ChildrenBinding)
|
||||||
|
|
@ -95,7 +105,7 @@ local function update_children(p: ChildrenBinding)
|
||||||
end
|
end
|
||||||
|
|
||||||
if new_children then
|
if new_children then
|
||||||
for _, child in next, new_children do
|
for _, child in next, new_children :: { Instance } do
|
||||||
new_child_set[child] = true -- record child set from this update
|
new_child_set[child] = true -- record child set from this update
|
||||||
if not cur_children_set[child] then
|
if not cur_children_set[child] then
|
||||||
child.Parent = p.instance -- if child wasn't already parented then parent it
|
child.Parent = p.instance -- if child wasn't already parented then parent it
|
||||||
|
|
|
||||||
|
|
@ -10,8 +10,9 @@ local evaluate_node = graph.evaluate_node
|
||||||
|
|
||||||
local function derive<T>(fn: () -> T): () -> T
|
local function derive<T>(fn: () -> T): () -> T
|
||||||
local owner = get_scope()
|
local owner = get_scope()
|
||||||
if not owner then throw("cannot derive in non-reactive scope") end
|
if not owner then
|
||||||
assert(owner)
|
throw("cannot derive in non-reactive scope")
|
||||||
|
end; assert(owner)
|
||||||
|
|
||||||
local node = create_node(false :: any, fn)
|
local node = create_node(false :: any, fn)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -5,26 +5,24 @@ local flags = require(script.Parent.flags)
|
||||||
|
|
||||||
export type StartNode<T> = {
|
export type StartNode<T> = {
|
||||||
cache: T,
|
cache: T,
|
||||||
children: { Node<T> } | false
|
[number]: Node<T>
|
||||||
}
|
}
|
||||||
|
|
||||||
export type Node<T> = {
|
export type Node<T> = {
|
||||||
cache: T,
|
cache: T,
|
||||||
owner: Node<T> | false,
|
|
||||||
parents: { StartNode<T> },
|
|
||||||
children: { Node<T> } | false,
|
|
||||||
effect: ((T) -> T) | false,
|
effect: ((T) -> T) | false,
|
||||||
|
|
||||||
|
owner: Node<T> | false,
|
||||||
cleanups: { () -> () } | false,
|
cleanups: { () -> () } | false,
|
||||||
|
|
||||||
|
parents: { StartNode<T> },
|
||||||
|
[number]: Node<T>
|
||||||
}
|
}
|
||||||
|
|
||||||
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_KEYS = { __mode = "k" }
|
|
||||||
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
|
||||||
local check_for_yield: <T...>(fn: (T...) -> unknown, T...) -> () do
|
local check_for_yield: <T...>(fn: (T...) -> (boolean, string?), T...) -> () do
|
||||||
local t = { __mode = "kv" }
|
local t = { __mode = "kv" }
|
||||||
setmetatable(t, t)
|
setmetatable(t, t)
|
||||||
|
|
||||||
|
|
@ -35,17 +33,13 @@ local check_for_yield: <T...>(fn: (T...) -> unknown, T...) -> () do
|
||||||
fn(unpack(args))
|
fn(unpack(args))
|
||||||
end
|
end
|
||||||
|
|
||||||
local ok, err = pcall(function()
|
local ok, err: string? = pcall(function()
|
||||||
local _ = -t
|
local _ = -t
|
||||||
end)
|
end)
|
||||||
|
|
||||||
if not ok then
|
return ok, if err == "attempt to yield across metamethod/C-call boundary"
|
||||||
if err == "attempt to yield across metamethod/C-call boundary" or err == "thread is not yieldable" then
|
or err == "thread is not yieldable" then "yield occured"
|
||||||
throw(EVALUATION_ERR .. "cannot yield when deriving node in effecter")
|
else err
|
||||||
else
|
|
||||||
throw(EVALUATION_ERR .. err)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
@ -57,24 +51,14 @@ 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
|
table.insert(parent, child)
|
||||||
table.insert(parent.children :: { Node<T> }, child)
|
|
||||||
else
|
|
||||||
parent.children = { child }
|
|
||||||
end
|
|
||||||
|
|
||||||
table.insert(child.parents, parent)
|
table.insert(child.parents, parent)
|
||||||
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.owner = owner
|
||||||
if owner.children then
|
table.insert(owner, node)
|
||||||
table.insert(owner.children :: { Node<any> }, node)
|
|
||||||
else
|
|
||||||
owner.children = { node }
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
local function open_scope<T>(node: Node<T>)
|
local function open_scope<T>(node: Node<T>)
|
||||||
|
|
@ -108,12 +92,11 @@ local function run_cleanups<T>(node: Node<T>)
|
||||||
end
|
end
|
||||||
|
|
||||||
local function remove_child<T>(parent: StartNode<T>, child: Node<T>)
|
local function remove_child<T>(parent: StartNode<T>, child: Node<T>)
|
||||||
local children = parent.children :: {}
|
local idx = table.find(parent, child)
|
||||||
local idx = table.find(children :: {}, child)
|
assert(idx, "child not found")
|
||||||
|
local n = #parent
|
||||||
local n = #children
|
parent[idx] = parent[n]
|
||||||
children[idx] = children[n]
|
parent[n] = nil
|
||||||
children[n] = nil
|
|
||||||
end
|
end
|
||||||
|
|
||||||
local function unparent<T>(node: Node<T>)
|
local function unparent<T>(node: Node<T>)
|
||||||
|
|
@ -127,12 +110,13 @@ 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
|
if node.owner then
|
||||||
local children = node.children :: {}
|
remove_child(node.owner, node)
|
||||||
if children then
|
node.owner = false
|
||||||
while children[1] do destroy(children[1]) end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
while node[1] do destroy(node[1]) end
|
||||||
end
|
end
|
||||||
|
|
||||||
local function evaluate_node<T>(node: Node<T>)
|
local function evaluate_node<T>(node: Node<T>)
|
||||||
|
|
@ -147,7 +131,7 @@ local function evaluate_node<T>(node: Node<T>)
|
||||||
close_scope()
|
close_scope()
|
||||||
|
|
||||||
if not ok then
|
if not ok then
|
||||||
throw(`side-effect error\n{new_value}`)
|
throw(`side-effect error from source update\n{new_value}`)
|
||||||
end
|
end
|
||||||
|
|
||||||
node.cache = new_value
|
node.cache = new_value
|
||||||
|
|
@ -158,8 +142,7 @@ end
|
||||||
local update_queue = {} :: { Node<any> }
|
local update_queue = {} :: { Node<any> }
|
||||||
|
|
||||||
local function update<T>(node: StartNode<T>)
|
local function update<T>(node: StartNode<T>)
|
||||||
local children = node.children :: {}
|
if not node[1] then return end
|
||||||
if not children then return end
|
|
||||||
|
|
||||||
local n0 = #update_queue
|
local n0 = #update_queue
|
||||||
local first_update = n0 == 0
|
local first_update = n0 == 0
|
||||||
|
|
@ -170,14 +153,14 @@ local function update<T>(node: StartNode<T>)
|
||||||
end
|
end
|
||||||
|
|
||||||
do
|
do
|
||||||
local child = children[1]
|
local child = node[1]
|
||||||
while child do -- todo: case where child in owner context
|
while child do -- todo: case where child in owner context
|
||||||
unparent(child)
|
unparent(child)
|
||||||
|
|
||||||
n += 1
|
n += 1
|
||||||
update_queue[n] = child
|
update_queue[n] = child
|
||||||
|
|
||||||
child = children[1]
|
child = node[1]
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
@ -216,15 +199,7 @@ local function create_node<T>(value: T, effect: false | (T) -> T): Node<T>
|
||||||
end
|
end
|
||||||
|
|
||||||
local function get_children<T>(node: Node<T>): { Node<unknown> }
|
local function get_children<T>(node: Node<T>): { Node<unknown> }
|
||||||
if not node.children then return {} end
|
return { unpack(node) } :: { Node<any> }
|
||||||
|
|
||||||
local children = {}
|
|
||||||
|
|
||||||
for _, child in node.children do
|
|
||||||
table.insert(children, child)
|
|
||||||
end
|
|
||||||
|
|
||||||
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>
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,7 @@ local update = graph.update
|
||||||
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 evaluate_node = graph.evaluate_node
|
||||||
local destroy = graph.destroy
|
local destroy = graph.destroy
|
||||||
|
|
||||||
type Map<K, V> = { [K]: V }
|
type Map<K, V> = { [K]: V }
|
||||||
|
|
@ -33,14 +34,17 @@ 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 }
|
||||||
local owner = get_scope()
|
local owner = get_scope()
|
||||||
if not owner then throw("cannot derive in non-reactive scope") end
|
if not owner then
|
||||||
assert(owner)
|
throw("cannot derive in non-reactive scope")
|
||||||
|
end; assert(owner)
|
||||||
|
|
||||||
|
local subowner = create_node(false, false)
|
||||||
|
set_owner(subowner, owner)
|
||||||
|
|
||||||
local input_cache = {} :: Map<K, VI>
|
local input_cache = {} :: Map<K, 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 scopes = {} :: Map<K, Node<unknown>>
|
local scopes = {} :: Map<K, Node<unknown>>
|
||||||
|
|
||||||
local function update_children(data)
|
local function update_children(data)
|
||||||
|
|
@ -63,7 +67,7 @@ local function indexes<K, VI, VO>(input: () -> Map<K, VI>, transform: (() -> VI,
|
||||||
|
|
||||||
table.clear(remove_queue)
|
table.clear(remove_queue)
|
||||||
|
|
||||||
open_scope(owner) -- todo: needed?
|
open_scope(subowner)
|
||||||
|
|
||||||
-- process new or changed values
|
-- process new or changed values
|
||||||
for i, v in next, data do
|
for i, v in next, data do
|
||||||
|
|
@ -71,10 +75,10 @@ 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_node(false)
|
local scope = create_node(false, false)
|
||||||
scopes[i] = scope :: Node<any>
|
scopes[i] = scope :: Node<any>
|
||||||
|
|
||||||
set_owner(scope, owner)
|
set_owner(scope, subowner)
|
||||||
open_scope(scope)
|
open_scope(scope)
|
||||||
|
|
||||||
local node = create_start_node(v)
|
local node = create_start_node(v)
|
||||||
|
|
@ -125,15 +129,17 @@ 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 }
|
||||||
local owner = get_scope()
|
local owner = get_scope()
|
||||||
if not owner then throw("cannot derive in non-reactive scope") end
|
if not owner then
|
||||||
assert(owner)
|
throw("cannot derive in non-reactive scope")
|
||||||
|
end; assert(owner)
|
||||||
|
|
||||||
|
local subowner = create_node(false, false)
|
||||||
|
set_owner(subowner, owner)
|
||||||
|
|
||||||
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>
|
||||||
|
|
||||||
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 scopes = {} :: Map<VI, Node<unknown>>
|
local scopes = {} :: Map<VI, Node<unknown>>
|
||||||
|
|
||||||
local function update_children(data: Map<K, VI>)
|
local function update_children(data: Map<K, VI>)
|
||||||
|
|
@ -149,7 +155,7 @@ local function values<K, VI, VO>(input: () -> Map<K, VI>, transform: (VI, () ->
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
open_scope(owner)
|
open_scope(subowner)
|
||||||
|
|
||||||
-- process data
|
-- process data
|
||||||
for i, v in next, data do
|
for i, v in next, data do
|
||||||
|
|
@ -158,10 +164,10 @@ 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_node(false)
|
local scope = create_node(false, false)
|
||||||
scopes[v] = scope :: Node<any>
|
scopes[v] = scope :: Node<any>
|
||||||
|
|
||||||
set_owner(scope, owner)
|
set_owner(scope, subowner)
|
||||||
open_scope(scope)
|
open_scope(scope)
|
||||||
|
|
||||||
local node = create_start_node(i)
|
local node = create_start_node(i)
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,9 @@ local vide = require "src/init"
|
||||||
|
|
||||||
local N = 2^18 -- 262144
|
local N = 2^18 -- 262144
|
||||||
|
|
||||||
BENCH("create state", function()
|
-- todo: wide and deep graph benchmarks
|
||||||
|
|
||||||
|
BENCH("create source", function()
|
||||||
local source = vide.source
|
local source = vide.source
|
||||||
|
|
||||||
local cache = table.create(N)
|
local cache = table.create(N)
|
||||||
|
|
@ -15,50 +17,50 @@ BENCH("create state", function()
|
||||||
end)
|
end)
|
||||||
|
|
||||||
BENCH("get value", function()
|
BENCH("get value", function()
|
||||||
local state = vide.source(1)
|
local src = vide.source(1)
|
||||||
|
|
||||||
for i = 1, START(N) do
|
for i = 1, START(N) do
|
||||||
state()
|
src()
|
||||||
end
|
end
|
||||||
end)
|
end)
|
||||||
|
|
||||||
BENCH("set value", function()
|
BENCH("set value", function()
|
||||||
local state = vide.source(1)
|
local src = vide.source(1)
|
||||||
|
|
||||||
for i = 1, START(N) do
|
for i = 1, START(N) do
|
||||||
state(i)
|
src(i)
|
||||||
end
|
end
|
||||||
end)
|
end)
|
||||||
|
|
||||||
BENCH("derive 1 state", function()
|
BENCH("derive 1 source", function()
|
||||||
local derive = vide.derive
|
local derive = vide.derive
|
||||||
|
|
||||||
local cache = table.create(N)
|
local cache = table.create(N)
|
||||||
local state = vide.source(1)
|
local src = vide.source(1)
|
||||||
|
|
||||||
vide.root(function()
|
vide.root(function()
|
||||||
for i = 1, START(N) do
|
for i = 1, START(N) do
|
||||||
cache[i] = derive(function()
|
cache[i] = derive(function()
|
||||||
return state()
|
return src()
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
return nil
|
return nil
|
||||||
end)
|
end)
|
||||||
end)
|
end)
|
||||||
|
|
||||||
BENCH("derive 4 states", function()
|
BENCH("derive 4 sources", function()
|
||||||
local derive = vide.derive
|
local derive = vide.derive
|
||||||
|
|
||||||
local cache = table.create(N)
|
local cache = table.create(N)
|
||||||
local state = vide.source(1)
|
local src = vide.source(1)
|
||||||
local state2 = vide.source(2)
|
local src2 = vide.source(2)
|
||||||
local state3 = vide.source(3)
|
local src3 = vide.source(3)
|
||||||
local state4 = vide.source(4)
|
local src4 = vide.source(4)
|
||||||
|
|
||||||
vide.root(function()
|
vide.root(function()
|
||||||
for i = 1, START(N) do
|
for i = 1, START(N) do
|
||||||
cache[i] = derive(function()
|
cache[i] = derive(function()
|
||||||
return state() + state2() + state3() + state4()
|
return src() + src2() + src3() + src4()
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
@ -66,14 +68,15 @@ BENCH("derive 4 states", function()
|
||||||
end)
|
end)
|
||||||
end)
|
end)
|
||||||
|
|
||||||
|
-- todo: why is this so fast?
|
||||||
BENCH("set derived value", function()
|
BENCH("set derived value", function()
|
||||||
local state = vide.source(1)
|
local src = vide.source(1)
|
||||||
|
|
||||||
vide.root(function()
|
vide.root(function()
|
||||||
local _derived = vide.derive(state)
|
local _derived = vide.derive(src)
|
||||||
|
|
||||||
for i = 1, START(N) do
|
for i = 1, START(N) do
|
||||||
state(i)
|
src(i)
|
||||||
end
|
end
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
|
@ -95,14 +98,14 @@ BENCH("apply 8 properties", function()
|
||||||
|
|
||||||
for i = 1, START(N) do
|
for i = 1, START(N) do
|
||||||
apply(instance, {
|
apply(instance, {
|
||||||
Name = i,
|
Text = i,
|
||||||
Name2 = i,
|
Text2 = i,
|
||||||
Name3 = i,
|
Text3 = i,
|
||||||
Name4 = i,
|
Text4 = i,
|
||||||
Name5 = i,
|
Text5 = i,
|
||||||
Name6 = i,
|
Text6 = i,
|
||||||
Name7 = i,
|
Text7 = i,
|
||||||
Name8 = i,
|
Text8 = i,
|
||||||
})
|
})
|
||||||
end
|
end
|
||||||
end)
|
end)
|
||||||
|
|
@ -111,12 +114,12 @@ BENCH("bind source", function()
|
||||||
local apply = require "src/apply"
|
local apply = require "src/apply"
|
||||||
|
|
||||||
local instance = vide.create("Frame") {}
|
local instance = vide.create("Frame") {}
|
||||||
local state = vide.source(1)
|
local src = vide.source(1)
|
||||||
|
|
||||||
vide.root(function()
|
vide.root(function()
|
||||||
for i = 1, START(N) do
|
for i = 1, START(N) do
|
||||||
apply(instance, {
|
apply(instance, {
|
||||||
Name = state
|
Text = src
|
||||||
})
|
})
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
@ -128,15 +131,15 @@ BENCH("update binding", function()
|
||||||
local apply = require "src/apply"
|
local apply = require "src/apply"
|
||||||
|
|
||||||
local instance = vide.create("Frame") {}
|
local instance = vide.create("Frame") {}
|
||||||
local state = vide.source(1)
|
local src = vide.source(1)
|
||||||
|
|
||||||
vide.root(function()
|
vide.root(function()
|
||||||
apply(instance, {
|
apply(instance, {
|
||||||
Name = state
|
Text = src
|
||||||
})
|
})
|
||||||
|
|
||||||
for i = 1, START(N) do
|
for i = 1, START(N) do
|
||||||
state(i)
|
src(i)
|
||||||
end
|
end
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
|
@ -145,6 +148,26 @@ end)
|
||||||
|
|
||||||
N /= 1024
|
N /= 1024
|
||||||
|
|
||||||
|
BENCH("indexes() all new", function()
|
||||||
|
local data = {}
|
||||||
|
|
||||||
|
for i = 1, N do
|
||||||
|
data[i] = i
|
||||||
|
end
|
||||||
|
|
||||||
|
local src = vide.source(data)
|
||||||
|
|
||||||
|
vide.root(function()
|
||||||
|
START(N)
|
||||||
|
|
||||||
|
local _list = vide.indexes(src, function(v, i)
|
||||||
|
return {}
|
||||||
|
end)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
end)
|
||||||
|
end)
|
||||||
|
|
||||||
BENCH("indexes() no change", function()
|
BENCH("indexes() no change", function()
|
||||||
local data = {}
|
local data = {}
|
||||||
|
|
||||||
|
|
@ -152,18 +175,16 @@ BENCH("indexes() no change", function()
|
||||||
data[i] = i
|
data[i] = i
|
||||||
end
|
end
|
||||||
|
|
||||||
local state = vide.source(data)
|
local src = vide.source(data)
|
||||||
|
|
||||||
vide.root(function()
|
vide.root(function()
|
||||||
local _list = vide.indexes(state, function(v, i)
|
local _list = vide.indexes(src, function(v, i)
|
||||||
return {}
|
return {}
|
||||||
end)
|
end)
|
||||||
|
|
||||||
--state(state()) -- fill double buffer
|
|
||||||
|
|
||||||
START(N)
|
START(N)
|
||||||
|
|
||||||
state(data)
|
src(data)
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
end)
|
end)
|
||||||
|
|
@ -176,15 +197,15 @@ BENCH("indexes() all change", function()
|
||||||
data[i] = i
|
data[i] = i
|
||||||
end
|
end
|
||||||
|
|
||||||
local state = vide.source(data)
|
local src = vide.source(data)
|
||||||
|
|
||||||
|
|
||||||
vide.root(function()
|
vide.root(function()
|
||||||
local _list = vide.indexes(state, function(v, i)
|
local _list = vide.indexes(src, function(v, i)
|
||||||
return {}
|
return {}
|
||||||
end)
|
end)
|
||||||
|
|
||||||
--state(state()) -- fill double buffer
|
--src(src()) -- fill double buffer
|
||||||
|
|
||||||
for i, v in data do
|
for i, v in data do
|
||||||
data[i] = v + 1
|
data[i] = v + 1
|
||||||
|
|
@ -192,7 +213,7 @@ BENCH("indexes() all change", function()
|
||||||
|
|
||||||
START(N)
|
START(N)
|
||||||
|
|
||||||
state(data)
|
src(data)
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
end)
|
end)
|
||||||
|
|
@ -205,10 +226,10 @@ BENCH("indexes() all remove", function()
|
||||||
data[i] = i
|
data[i] = i
|
||||||
end
|
end
|
||||||
|
|
||||||
local state = vide.source(data)
|
local src = vide.source(data)
|
||||||
|
|
||||||
vide.root(function()
|
vide.root(function()
|
||||||
local _list = vide.indexes(state, function(v, i)
|
local _list = vide.indexes(src, function(v, i)
|
||||||
return {}
|
return {}
|
||||||
end)
|
end)
|
||||||
|
|
||||||
|
|
@ -216,7 +237,27 @@ BENCH("indexes() all remove", function()
|
||||||
|
|
||||||
START(N)
|
START(N)
|
||||||
|
|
||||||
state(data)
|
src(data)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
end)
|
||||||
|
end)
|
||||||
|
|
||||||
|
BENCH("values() all new", function()
|
||||||
|
local data = {}
|
||||||
|
|
||||||
|
for i = 1, N do
|
||||||
|
data[i] = {}
|
||||||
|
end
|
||||||
|
|
||||||
|
local src = vide.source(data)
|
||||||
|
|
||||||
|
vide.root(function()
|
||||||
|
START(N)
|
||||||
|
|
||||||
|
local _list = vide.values(src, function(v, i)
|
||||||
|
return {}
|
||||||
|
end)
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
end)
|
end)
|
||||||
|
|
@ -229,18 +270,18 @@ BENCH("values() no change", function()
|
||||||
data[i] = {}
|
data[i] = {}
|
||||||
end
|
end
|
||||||
|
|
||||||
local state = vide.source(data)
|
local src = vide.source(data)
|
||||||
|
|
||||||
vide.root(function()
|
vide.root(function()
|
||||||
local _list = vide.values(state, function(v, i)
|
local _list = vide.values(src, function(v, i)
|
||||||
return {}
|
return {}
|
||||||
end)
|
end)
|
||||||
|
|
||||||
state(state()) -- fill double buffer
|
src(src()) -- fill double buffer
|
||||||
|
|
||||||
START(N)
|
START(N)
|
||||||
|
|
||||||
state(data)
|
src(data)
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
end)
|
end)
|
||||||
|
|
@ -253,14 +294,14 @@ BENCH("values() all change", function()
|
||||||
data[i] = {}
|
data[i] = {}
|
||||||
end
|
end
|
||||||
|
|
||||||
local state = vide.source(data)
|
local src = vide.source(data)
|
||||||
|
|
||||||
vide.root(function()
|
vide.root(function()
|
||||||
local _list = vide.values(state, function(v, i)
|
local _list = vide.values(src, function(v, i)
|
||||||
return {}
|
return {}
|
||||||
end)
|
end)
|
||||||
|
|
||||||
state(state()) -- fill double buffer
|
src(src()) -- fill double buffer
|
||||||
|
|
||||||
for i = 1, N do
|
for i = 1, N do
|
||||||
local r = math.random(1, #data)
|
local r = math.random(1, #data)
|
||||||
|
|
@ -269,7 +310,7 @@ BENCH("values() all change", function()
|
||||||
|
|
||||||
START(N)
|
START(N)
|
||||||
|
|
||||||
state(data)
|
src(data)
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
end)
|
end)
|
||||||
|
|
@ -282,10 +323,10 @@ BENCH("values() all remove", function()
|
||||||
data[i] = {}
|
data[i] = {}
|
||||||
end
|
end
|
||||||
|
|
||||||
local state = vide.source(data)
|
local src = vide.source(data)
|
||||||
|
|
||||||
vide.root(function()
|
vide.root(function()
|
||||||
local _list = vide.values(state, function(v, i)
|
local _list = vide.values(src, function(v, i)
|
||||||
return {}
|
return {}
|
||||||
end)
|
end)
|
||||||
|
|
||||||
|
|
@ -293,7 +334,7 @@ BENCH("values() all remove", function()
|
||||||
|
|
||||||
START(N)
|
START(N)
|
||||||
|
|
||||||
state(data)
|
src(data)
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
end)
|
end)
|
||||||
|
|
|
||||||
|
|
@ -113,6 +113,7 @@ local Instance = {} :: any do
|
||||||
local function __newindex(userdata: userdata, property: string, value: unknown)
|
local function __newindex(userdata: userdata, property: string, value: unknown)
|
||||||
local data = get_data(userdata)
|
local data = get_data(userdata)
|
||||||
if property == "Name" then
|
if property == "Name" then
|
||||||
|
if type(value) ~= "string" then error("name must be a string", 2) end
|
||||||
data.name = value :: string
|
data.name = value :: string
|
||||||
elseif property == "Parent" then
|
elseif property == "Parent" then
|
||||||
assert(value == nil or is_instance(value), "attempt to set non-instance as parent")
|
assert(value == nil or is_instance(value), "attempt to set non-instance as parent")
|
||||||
|
|
|
||||||
|
|
@ -29,15 +29,7 @@ local function wrap_root(fn: () -> ())
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
local NIL = nil
|
local NIL = nil :: any
|
||||||
|
|
||||||
-- vide.mount(function()
|
|
||||||
-- local src = vide.source(0)
|
|
||||||
|
|
||||||
-- vide.effect(function()
|
|
||||||
-- axasd += 1
|
|
||||||
-- end)
|
|
||||||
-- end)
|
|
||||||
|
|
||||||
TEST("graph", function()
|
TEST("graph", function()
|
||||||
local create_node = graph.create_node
|
local create_node = graph.create_node
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue