diff --git a/src/bind.luau b/src/bind.luau index 914ba45..c0328d9 100644 --- a/src/bind.luau +++ b/src/bind.luau @@ -7,8 +7,7 @@ local graph = require(script.Parent.graph) type Node = graph.Node 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(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 = { diff --git a/src/derive.luau b/src/derive.luau index 62a7096..d5bbb7f 100644 --- a/src/derive.luau +++ b/src/derive.luau @@ -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(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) diff --git a/src/effect.luau b/src/effect.luau index 12488c4..303683f 100644 --- a/src/effect.luau +++ b/src/effect.luau @@ -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(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 :: ((effect: (T) -> T, initial_value: T) -> ()) & ((effect: () -> ()) -> ()) diff --git a/src/graph.luau b/src/graph.luau index 6f63dd7..2b939e6 100644 --- a/src/graph.luau +++ b/src/graph.luau @@ -100,7 +100,8 @@ end local function run_cleanups(node: Node) 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(node: Node) end end +local function evaluate_node(node: Node) + 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 } local function update(node: StartNode) @@ -144,6 +165,10 @@ local function update(node: StartNode) 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(node: StartNode) 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(node: StartNode) end end -local function create_node(value: T): Node +local function create_node(value: T, effect: false | (T) -> T): Node local node: Node = { 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, diff --git a/src/init.luau b/src/init.luau index bcacd64..8c2d88c 100644 --- a/src/init.luau +++ b/src/init.luau @@ -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 = source.Source @@ -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, diff --git a/src/match.luau b/src/match.luau index 75ae42d..0cb4a52 100644 --- a/src/match.luau +++ b/src/match.luau @@ -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 } -local function match(source: () -> T, map: Map U>): () -> U +local function match(source: () -> T, map: Map 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 diff --git a/src/on_gc.luau b/src/on_gc.luau deleted file mode 100644 index 7a5f722..0000000 --- a/src/on_gc.luau +++ /dev/null @@ -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 diff --git a/src/root.luau b/src/root.luau index 4e5921d..1ed5db8 100644 --- a/src/root.luau +++ b/src/root.luau @@ -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 = graph.Node 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(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 diff --git a/src/source.luau b/src/source.luau index a482f11..2e960a2 100644 --- a/src/source.luau +++ b/src/source.luau @@ -3,7 +3,6 @@ if not game then script = require "test/relative-string" end local graph = require(script.Parent.graph) type Node = graph.Node local create_start_node = graph.create_start_node -local get_scope = graph.get_scope local track = graph.track local update = graph.update diff --git a/test/tests.luau b/test/tests.luau index a47b139..b8e0394 100644 --- a/test/tests.luau +++ b/test/tests.luau @@ -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