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>
|
||||
local create_node = graph.create_node
|
||||
local get_scope = graph.get_scope
|
||||
local open_scope = graph.open_scope
|
||||
local close_scope = graph.close_scope
|
||||
local evaluate_node = graph.evaluate_node
|
||||
local set_owner = graph.set_owner
|
||||
|
||||
-- todo: replace with throw's method
|
||||
|
|
@ -47,19 +46,14 @@ function create_binding<T>(updater: (T) -> T, binding_data: T)
|
|||
-- end
|
||||
-- end
|
||||
|
||||
local binding = create_node(binding_data)
|
||||
binding.effect = updater
|
||||
local binding = create_node(binding_data, updater)
|
||||
|
||||
local owner = get_scope()
|
||||
if not owner then throw("cannot bind property in non-reactive scope") end
|
||||
assert(owner)
|
||||
|
||||
set_owner(binding, owner)
|
||||
open_scope(binding)
|
||||
|
||||
updater(binding_data)
|
||||
|
||||
close_scope()
|
||||
evaluate_node(binding)
|
||||
end
|
||||
|
||||
type PropertyBinding = {
|
||||
|
|
|
|||
|
|
@ -6,26 +6,17 @@ local create_node = graph.create_node
|
|||
local set_owner = graph.set_owner
|
||||
local track = graph.track
|
||||
local get_scope = graph.get_scope
|
||||
local open_scope = graph.open_scope
|
||||
local close_scope = graph.close_scope
|
||||
|
||||
local evaluate_node = graph.evaluate_node
|
||||
|
||||
local function derive<T>(fn: () -> T): () -> T
|
||||
local owner = get_scope()
|
||||
if not owner then throw("cannot derive in non-reactive scope") end
|
||||
assert(owner)
|
||||
|
||||
local node = create_node((false :: any) :: T)
|
||||
node.effect = function()
|
||||
return fn()
|
||||
end
|
||||
local node = create_node(false :: any, fn)
|
||||
|
||||
set_owner(node, owner)
|
||||
open_scope(node)
|
||||
|
||||
node.cache = fn()
|
||||
|
||||
close_scope()
|
||||
evaluate_node(node)
|
||||
|
||||
return function()
|
||||
track(node)
|
||||
|
|
|
|||
|
|
@ -4,24 +4,18 @@ local throw = require(script.Parent.throw)
|
|||
local graph = require(script.Parent.graph)
|
||||
local create_node = graph.create_node
|
||||
local get_scope = graph.get_scope
|
||||
local open_scope = graph.open_scope
|
||||
local close_scope = graph.close_scope
|
||||
local evaluate_node = graph.evaluate_node
|
||||
local set_owner = graph.set_owner
|
||||
|
||||
local function effect<T>(effect: (T) -> T, initial_value: T)
|
||||
local owner = get_scope()
|
||||
if not owner then throw("cannot effect in non-reactive scope") end
|
||||
assert(owner)
|
||||
|
||||
local node = create_node(initial_value)
|
||||
node.effect = effect
|
||||
if not owner then
|
||||
throw("cannot create effect in non-reactive scope")
|
||||
end; assert(owner)
|
||||
|
||||
local node = create_node(initial_value, effect)
|
||||
set_owner(node, owner)
|
||||
open_scope(node)
|
||||
|
||||
effect(initial_value)
|
||||
|
||||
close_scope()
|
||||
evaluate_node(node)
|
||||
end
|
||||
|
||||
return effect :: (<T>(effect: (T) -> T, initial_value: T) -> ()) & ((effect: () -> ()) -> ())
|
||||
|
|
|
|||
|
|
@ -100,7 +100,8 @@ end
|
|||
local function run_cleanups<T>(node: Node<T>)
|
||||
if node.cleanups then
|
||||
for _, fn in next, node.cleanups do
|
||||
fn()
|
||||
local ok, err: string? = pcall(fn)
|
||||
if not ok then throw(`cleanup error: {err}`) end
|
||||
end
|
||||
table.clear(node.cleanups)
|
||||
end
|
||||
|
|
@ -134,6 +135,26 @@ local function destroy<T>(node: Node<T>)
|
|||
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 function update<T>(node: StartNode<T>)
|
||||
|
|
@ -144,6 +165,10 @@ local function update<T>(node: StartNode<T>)
|
|||
local first_update = n0 == 0
|
||||
local n = n0
|
||||
|
||||
if first_update then
|
||||
table.clear(update_queue)
|
||||
end
|
||||
|
||||
do
|
||||
local child = children[1]
|
||||
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
|
||||
local child = update_queue[i]
|
||||
if not child.effect then continue end
|
||||
|
||||
local old_value = child.cache
|
||||
|
||||
open_scope(child)
|
||||
run_cleanups(child)
|
||||
local new_value = child.effect and child.effect(old_value)
|
||||
close_scope()
|
||||
|
||||
if old_value ~= new_value then
|
||||
child.cache = new_value
|
||||
if evaluate_node(child) then
|
||||
update(child)
|
||||
end
|
||||
end
|
||||
|
|
@ -184,11 +202,11 @@ local function track<T>(node: StartNode<T>)
|
|||
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> = {
|
||||
cache = value,
|
||||
owner = false,
|
||||
effect = false :: false,
|
||||
effect = effect,
|
||||
cleanups = false :: false,
|
||||
parents = {},
|
||||
children = false :: false
|
||||
|
|
@ -216,6 +234,7 @@ end
|
|||
return table.freeze {
|
||||
open_scope = open_scope,
|
||||
close_scope = close_scope,
|
||||
evaluate_node = evaluate_node,
|
||||
get_scope = get_scope,
|
||||
get_stack_scope = get_stack_scope,
|
||||
add_cleanup = add_cleanup,
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@
|
|||
if not game then script = require "test/relative-string" end
|
||||
|
||||
local root = require(script.root)
|
||||
local mount = require(script.mount)
|
||||
local create = require(script.create)
|
||||
local apply = require(script.apply)
|
||||
local source = require(script.source)
|
||||
|
|
@ -13,11 +14,12 @@ local effect = require(script.effect)
|
|||
local cleanup = require(script.cleanup)
|
||||
local untrack = require(script.untrack)
|
||||
local derive = require(script.derive)
|
||||
local match = require(script.match)
|
||||
local indexes, values = require(script.maps)()
|
||||
local spring, update_springs = require(script.spring)()
|
||||
local action = require(script.action)()
|
||||
local changed = require(script.changed)
|
||||
local throw = require(script.throw)
|
||||
local _, sweep = require(script.on_gc)()
|
||||
local flags = require(script.flags)
|
||||
|
||||
export type Source<T> = source.Source<T>
|
||||
|
|
@ -30,13 +32,6 @@ local function step(dt: number)
|
|||
|
||||
update_springs(dt)
|
||||
|
||||
if game then
|
||||
debug.profileend()
|
||||
debug.profilebegin("VIDE GARBAGE CLEANUP")
|
||||
end
|
||||
|
||||
sweep()
|
||||
|
||||
if game then
|
||||
debug.profileend()
|
||||
debug.profileend()
|
||||
|
|
@ -50,10 +45,12 @@ end)
|
|||
local vide = {
|
||||
-- core
|
||||
root = root,
|
||||
mount = mount,
|
||||
create = create,
|
||||
source = source,
|
||||
effect = effect,
|
||||
derive = derive,
|
||||
match = match,
|
||||
indexes = indexes,
|
||||
values = values,
|
||||
|
||||
|
|
@ -66,6 +63,7 @@ local vide = {
|
|||
|
||||
-- actions
|
||||
action = action,
|
||||
changed = changed,
|
||||
|
||||
-- flags
|
||||
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 flags = require(script.Parent.flags)
|
||||
local graph = require(script.Parent.graph)
|
||||
|
|
@ -15,18 +17,23 @@ local destroy = graph.destroy
|
|||
|
||||
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()
|
||||
assert(owner)
|
||||
|
||||
local match_updater = create_node(nil)
|
||||
local match_updater = create_node(nil :: U?)
|
||||
function match_updater.effect()
|
||||
local value = source()
|
||||
open_scope(owner)
|
||||
local component = map[value]()
|
||||
close_scope()
|
||||
output.cache = component
|
||||
return component
|
||||
end
|
||||
|
||||
local output = create_start_node()
|
||||
return function()
|
||||
track(match_updater)
|
||||
return match_updater.cache
|
||||
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
|
||||
|
||||
local flags = require(script.Parent.flags)
|
||||
local throw = require(script.Parent.throw)
|
||||
local on_gc = require(script.Parent.on_gc)()
|
||||
local graph = require(script.Parent.graph)
|
||||
type Node<T> = graph.Node<T>
|
||||
local create_node = graph.create_node
|
||||
local open_scope = graph.open_scope
|
||||
local close_scope = graph.close_scope
|
||||
local get_scope = graph.get_scope
|
||||
local destroy = graph.destroy
|
||||
|
||||
local refs = {}
|
||||
|
||||
local function root<T>(fn: () -> T): (T, () -> ())
|
||||
local node = create_node(false)
|
||||
local node = create_node(false, false)
|
||||
|
||||
open_scope(node)
|
||||
|
||||
local ok, v = pcall(fn)
|
||||
local ok, result = pcall(fn)
|
||||
|
||||
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
|
||||
destroy(node)
|
||||
end
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@ if not game then script = require "test/relative-string" end
|
|||
local graph = require(script.Parent.graph)
|
||||
type Node<T> = graph.Node<T>
|
||||
local create_start_node = graph.create_start_node
|
||||
local get_scope = graph.get_scope
|
||||
local track = graph.track
|
||||
local update = graph.update
|
||||
|
||||
|
|
|
|||
|
|
@ -31,6 +31,14 @@ end
|
|||
|
||||
local NIL = nil
|
||||
|
||||
-- vide.mount(function()
|
||||
-- local src = vide.source(0)
|
||||
|
||||
-- vide.effect(function()
|
||||
-- axasd += 1
|
||||
-- end)
|
||||
-- end)
|
||||
|
||||
TEST("graph", function()
|
||||
local create_node = graph.create_node
|
||||
local create_start_node = graph.create_start_node
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue