mirror of
https://github.com/centau/vide.git
synced 2026-08-20 14:41:37 +00:00
This commit is contained in:
parent
d353f71619
commit
0d542c66a6
10 changed files with 76 additions and 105 deletions
|
|
@ -7,8 +7,7 @@ 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 get_scope = graph.get_scope
|
||||||
local open_scope = graph.open_scope
|
local evaluate_node = graph.evaluate_node
|
||||||
local close_scope = graph.close_scope
|
|
||||||
local set_owner = graph.set_owner
|
local set_owner = graph.set_owner
|
||||||
|
|
||||||
-- todo: replace with throw's method
|
-- todo: replace with throw's method
|
||||||
|
|
@ -47,19 +46,14 @@ function create_binding<T>(updater: (T) -> T, binding_data: T)
|
||||||
-- end
|
-- end
|
||||||
-- end
|
-- end
|
||||||
|
|
||||||
local binding = create_node(binding_data)
|
local binding = create_node(binding_data, updater)
|
||||||
binding.effect = updater
|
|
||||||
|
|
||||||
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 throw("cannot bind property in non-reactive scope") end
|
||||||
assert(owner)
|
assert(owner)
|
||||||
|
|
||||||
set_owner(binding, owner)
|
set_owner(binding, owner)
|
||||||
open_scope(binding)
|
evaluate_node(binding)
|
||||||
|
|
||||||
updater(binding_data)
|
|
||||||
|
|
||||||
close_scope()
|
|
||||||
end
|
end
|
||||||
|
|
||||||
type PropertyBinding = {
|
type PropertyBinding = {
|
||||||
|
|
|
||||||
|
|
@ -6,26 +6,17 @@ local create_node = graph.create_node
|
||||||
local set_owner = graph.set_owner
|
local set_owner = graph.set_owner
|
||||||
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 evaluate_node = graph.evaluate_node
|
||||||
local close_scope = graph.close_scope
|
|
||||||
|
|
||||||
|
|
||||||
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 throw("cannot derive in non-reactive scope") end
|
||||||
assert(owner)
|
assert(owner)
|
||||||
|
|
||||||
local node = create_node((false :: any) :: T)
|
local node = create_node(false :: any, fn)
|
||||||
node.effect = function()
|
|
||||||
return fn()
|
|
||||||
end
|
|
||||||
|
|
||||||
set_owner(node, owner)
|
set_owner(node, owner)
|
||||||
open_scope(node)
|
evaluate_node(node)
|
||||||
|
|
||||||
node.cache = fn()
|
|
||||||
|
|
||||||
close_scope()
|
|
||||||
|
|
||||||
return function()
|
return function()
|
||||||
track(node)
|
track(node)
|
||||||
|
|
|
||||||
|
|
@ -4,24 +4,18 @@ local throw = require(script.Parent.throw)
|
||||||
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 get_scope = graph.get_scope
|
local get_scope = graph.get_scope
|
||||||
local open_scope = graph.open_scope
|
local evaluate_node = graph.evaluate_node
|
||||||
local close_scope = graph.close_scope
|
|
||||||
local set_owner = graph.set_owner
|
local set_owner = graph.set_owner
|
||||||
|
|
||||||
local function effect<T>(effect: (T) -> T, initial_value: T)
|
local function effect<T>(effect: (T) -> T, initial_value: T)
|
||||||
local owner = get_scope()
|
local owner = get_scope()
|
||||||
if not owner then throw("cannot effect in non-reactive scope") end
|
if not owner then
|
||||||
assert(owner)
|
throw("cannot create effect in non-reactive scope")
|
||||||
|
end; assert(owner)
|
||||||
local node = create_node(initial_value)
|
|
||||||
node.effect = effect
|
|
||||||
|
|
||||||
|
local node = create_node(initial_value, effect)
|
||||||
set_owner(node, owner)
|
set_owner(node, owner)
|
||||||
open_scope(node)
|
evaluate_node(node)
|
||||||
|
|
||||||
effect(initial_value)
|
|
||||||
|
|
||||||
close_scope()
|
|
||||||
end
|
end
|
||||||
|
|
||||||
return effect :: (<T>(effect: (T) -> T, initial_value: T) -> ()) & ((effect: () -> ()) -> ())
|
return effect :: (<T>(effect: (T) -> T, initial_value: T) -> ()) & ((effect: () -> ()) -> ())
|
||||||
|
|
|
||||||
|
|
@ -100,7 +100,8 @@ end
|
||||||
local function run_cleanups<T>(node: Node<T>)
|
local function run_cleanups<T>(node: Node<T>)
|
||||||
if node.cleanups then
|
if node.cleanups then
|
||||||
for _, fn in next, node.cleanups do
|
for _, fn in next, node.cleanups do
|
||||||
fn()
|
local ok, err: string? = pcall(fn)
|
||||||
|
if not ok then throw(`cleanup error: {err}`) end
|
||||||
end
|
end
|
||||||
table.clear(node.cleanups)
|
table.clear(node.cleanups)
|
||||||
end
|
end
|
||||||
|
|
@ -134,6 +135,26 @@ local function destroy<T>(node: Node<T>)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
local function evaluate_node<T>(node: Node<T>)
|
||||||
|
local cur_value = node.cache
|
||||||
|
|
||||||
|
run_cleanups(node) -- todo: move in scope?
|
||||||
|
|
||||||
|
open_scope(node)
|
||||||
|
|
||||||
|
local ok, new_value = pcall(node.effect :: (T) -> T, cur_value)
|
||||||
|
|
||||||
|
close_scope()
|
||||||
|
|
||||||
|
if not ok then
|
||||||
|
throw(`side-effect error\n{new_value}`)
|
||||||
|
end
|
||||||
|
|
||||||
|
node.cache = new_value
|
||||||
|
|
||||||
|
return cur_value ~= new_value -- node has changed value
|
||||||
|
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>)
|
||||||
|
|
@ -144,6 +165,10 @@ local function update<T>(node: StartNode<T>)
|
||||||
local first_update = n0 == 0
|
local first_update = n0 == 0
|
||||||
local n = n0
|
local n = n0
|
||||||
|
|
||||||
|
if first_update then
|
||||||
|
table.clear(update_queue)
|
||||||
|
end
|
||||||
|
|
||||||
do
|
do
|
||||||
local child = children[1]
|
local child = children[1]
|
||||||
while child do -- todo: case where child in owner context
|
while child do -- todo: case where child in owner context
|
||||||
|
|
@ -158,16 +183,9 @@ local function update<T>(node: StartNode<T>)
|
||||||
|
|
||||||
for i = n0 + 1, n do
|
for i = n0 + 1, n do
|
||||||
local child = update_queue[i]
|
local child = update_queue[i]
|
||||||
|
if not child.effect then continue end
|
||||||
|
|
||||||
local old_value = child.cache
|
if evaluate_node(child) then
|
||||||
|
|
||||||
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)
|
update(child)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
@ -184,11 +202,11 @@ local function track<T>(node: StartNode<T>)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
local function create_node<T>(value: T): Node<T>
|
local function create_node<T>(value: T, effect: false | (T) -> T): Node<T>
|
||||||
local node: Node<T> = {
|
local node: Node<T> = {
|
||||||
cache = value,
|
cache = value,
|
||||||
owner = false,
|
owner = false,
|
||||||
effect = false :: false,
|
effect = effect,
|
||||||
cleanups = false :: false,
|
cleanups = false :: false,
|
||||||
parents = {},
|
parents = {},
|
||||||
children = false :: false
|
children = false :: false
|
||||||
|
|
@ -216,6 +234,7 @@ end
|
||||||
return table.freeze {
|
return table.freeze {
|
||||||
open_scope = open_scope,
|
open_scope = open_scope,
|
||||||
close_scope = close_scope,
|
close_scope = close_scope,
|
||||||
|
evaluate_node = evaluate_node,
|
||||||
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,
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@
|
||||||
if not game then script = require "test/relative-string" end
|
if not game then script = require "test/relative-string" end
|
||||||
|
|
||||||
local root = require(script.root)
|
local root = require(script.root)
|
||||||
|
local mount = require(script.mount)
|
||||||
local create = require(script.create)
|
local create = require(script.create)
|
||||||
local apply = require(script.apply)
|
local apply = require(script.apply)
|
||||||
local source = require(script.source)
|
local source = require(script.source)
|
||||||
|
|
@ -13,11 +14,12 @@ local effect = require(script.effect)
|
||||||
local cleanup = require(script.cleanup)
|
local cleanup = require(script.cleanup)
|
||||||
local untrack = require(script.untrack)
|
local untrack = require(script.untrack)
|
||||||
local derive = require(script.derive)
|
local derive = require(script.derive)
|
||||||
|
local match = require(script.match)
|
||||||
local indexes, values = require(script.maps)()
|
local indexes, values = require(script.maps)()
|
||||||
local spring, update_springs = require(script.spring)()
|
local spring, update_springs = require(script.spring)()
|
||||||
local action = require(script.action)()
|
local action = require(script.action)()
|
||||||
|
local changed = require(script.changed)
|
||||||
local throw = require(script.throw)
|
local throw = require(script.throw)
|
||||||
local _, sweep = require(script.on_gc)()
|
|
||||||
local flags = require(script.flags)
|
local flags = require(script.flags)
|
||||||
|
|
||||||
export type Source<T> = source.Source<T>
|
export type Source<T> = source.Source<T>
|
||||||
|
|
@ -30,13 +32,6 @@ local function step(dt: number)
|
||||||
|
|
||||||
update_springs(dt)
|
update_springs(dt)
|
||||||
|
|
||||||
if game then
|
|
||||||
debug.profileend()
|
|
||||||
debug.profilebegin("VIDE GARBAGE CLEANUP")
|
|
||||||
end
|
|
||||||
|
|
||||||
sweep()
|
|
||||||
|
|
||||||
if game then
|
if game then
|
||||||
debug.profileend()
|
debug.profileend()
|
||||||
debug.profileend()
|
debug.profileend()
|
||||||
|
|
@ -50,10 +45,12 @@ end)
|
||||||
local vide = {
|
local vide = {
|
||||||
-- core
|
-- core
|
||||||
root = root,
|
root = root,
|
||||||
|
mount = mount,
|
||||||
create = create,
|
create = create,
|
||||||
source = source,
|
source = source,
|
||||||
effect = effect,
|
effect = effect,
|
||||||
derive = derive,
|
derive = derive,
|
||||||
|
match = match,
|
||||||
indexes = indexes,
|
indexes = indexes,
|
||||||
values = values,
|
values = values,
|
||||||
|
|
||||||
|
|
@ -66,6 +63,7 @@ local vide = {
|
||||||
|
|
||||||
-- actions
|
-- actions
|
||||||
action = action,
|
action = action,
|
||||||
|
changed = changed,
|
||||||
|
|
||||||
-- flags
|
-- flags
|
||||||
strict = (nil :: any) :: boolean,
|
strict = (nil :: any) :: boolean,
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
if not game then script = require "test/relative-string" end
|
||||||
|
|
||||||
local throw = require(script.Parent.throw)
|
local throw = require(script.Parent.throw)
|
||||||
local flags = require(script.Parent.flags)
|
local flags = require(script.Parent.flags)
|
||||||
local graph = require(script.Parent.graph)
|
local graph = require(script.Parent.graph)
|
||||||
|
|
@ -15,18 +17,23 @@ local destroy = graph.destroy
|
||||||
|
|
||||||
type Map<K, V> = { [K]: V }
|
type Map<K, V> = { [K]: V }
|
||||||
|
|
||||||
local function match<T, U>(source: () -> T, map: Map<T, () -> U>): () -> U
|
local function match<T, U>(source: () -> T, map: Map<T, () -> U?>): () -> U?
|
||||||
local owner = get_scope()
|
local owner = get_scope()
|
||||||
assert(owner)
|
assert(owner)
|
||||||
|
|
||||||
local match_updater = create_node(nil)
|
local match_updater = create_node(nil :: U?)
|
||||||
function match_updater.effect()
|
function match_updater.effect()
|
||||||
local value = source()
|
local value = source()
|
||||||
open_scope(owner)
|
open_scope(owner)
|
||||||
local component = map[value]()
|
local component = map[value]()
|
||||||
close_scope()
|
close_scope()
|
||||||
output.cache = component
|
return component
|
||||||
end
|
end
|
||||||
|
|
||||||
local output = create_start_node()
|
return function()
|
||||||
|
track(match_updater)
|
||||||
|
return match_updater.cache
|
||||||
end
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return match
|
||||||
|
|
|
||||||
|
|
@ -1,40 +0,0 @@
|
||||||
if not game then script = require "test/relative-string" end
|
|
||||||
|
|
||||||
local flags = require(script.Parent.flags)
|
|
||||||
local throw = require(script.Parent.throw)
|
|
||||||
|
|
||||||
|
|
||||||
-- array of all cleanup callbacks
|
|
||||||
local cleanup_callbacks = {} :: { [number]: () -> () } -- always dense
|
|
||||||
-- weak array of all cleanup lifetimes
|
|
||||||
local cleanup_lifetime = {} :: { [number]: unknown } -- can be sparse
|
|
||||||
setmetatable(cleanup_lifetime :: any, { __mode = "v" })
|
|
||||||
|
|
||||||
local function on_gc(lifetime: unknown, callback: () -> ())
|
|
||||||
local id = #cleanup_callbacks + 1
|
|
||||||
cleanup_lifetime[id :: any] = lifetime -- todo
|
|
||||||
cleanup_callbacks[id] = callback
|
|
||||||
end
|
|
||||||
|
|
||||||
local function sweep()
|
|
||||||
for id = #cleanup_callbacks, 1, -1 do
|
|
||||||
if cleanup_lifetime[id] == nil then -- lifetime was garbage collected
|
|
||||||
local callback = cleanup_callbacks[id]
|
|
||||||
|
|
||||||
do -- swap and pop
|
|
||||||
local max_id = #cleanup_callbacks
|
|
||||||
|
|
||||||
cleanup_callbacks[id] = cleanup_callbacks[max_id]
|
|
||||||
cleanup_callbacks[max_id] = nil
|
|
||||||
|
|
||||||
cleanup_lifetime[id] = cleanup_lifetime[max_id]
|
|
||||||
cleanup_lifetime[max_id] = nil
|
|
||||||
end
|
|
||||||
|
|
||||||
local ok, err: string? = pcall(callback)
|
|
||||||
if not ok then warn(`error occured during cleanup: {err}`) end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
return function() return on_gc, sweep end
|
|
||||||
|
|
@ -1,30 +1,31 @@
|
||||||
if not game then script = require "test/relative-string" end
|
if not game then script = require "test/relative-string" end
|
||||||
|
|
||||||
local flags = require(script.Parent.flags)
|
|
||||||
local throw = require(script.Parent.throw)
|
local throw = require(script.Parent.throw)
|
||||||
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_node = graph.create_node
|
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 destroy = graph.destroy
|
local destroy = graph.destroy
|
||||||
|
|
||||||
local refs = {}
|
local refs = {}
|
||||||
|
|
||||||
local function root<T>(fn: () -> T): (T, () -> ())
|
local function root<T>(fn: () -> T): (T, () -> ())
|
||||||
local node = create_node(false)
|
local node = create_node(false, false)
|
||||||
|
|
||||||
open_scope(node)
|
open_scope(node)
|
||||||
|
|
||||||
local ok, v = pcall(fn)
|
local ok, result = pcall(fn)
|
||||||
|
|
||||||
close_scope()
|
close_scope()
|
||||||
|
|
||||||
refs[node] = true
|
if not ok then
|
||||||
|
throw(`mount error\n{result}`)
|
||||||
|
end
|
||||||
|
|
||||||
return v, function()
|
refs[node] = true -- prevent gc of root node
|
||||||
|
|
||||||
|
return result, function()
|
||||||
refs[node] = nil
|
refs[node] = nil
|
||||||
destroy(node)
|
destroy(node)
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,6 @@ if not game then script = require "test/relative-string" end
|
||||||
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_start_node = graph.create_start_node
|
local create_start_node = graph.create_start_node
|
||||||
local get_scope = graph.get_scope
|
|
||||||
local track = graph.track
|
local track = graph.track
|
||||||
local update = graph.update
|
local update = graph.update
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -31,6 +31,14 @@ end
|
||||||
|
|
||||||
local NIL = nil
|
local NIL = nil
|
||||||
|
|
||||||
|
-- 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
|
||||||
local create_start_node = graph.create_start_node
|
local create_start_node = graph.create_start_node
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue