This commit is contained in:
Aaron Smith 2023-09-13 13:11:38 +01:00
parent d353f71619
commit 0d542c66a6
10 changed files with 76 additions and 105 deletions

View file

@ -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 = {

View file

@ -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)

View file

@ -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: () -> ()) -> ())

View file

@ -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,

View file

@ -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,

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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