mirror of
https://github.com/centau/vide.git
synced 2026-08-20 14:41:37 +00:00
There seemed to be confusion over reactive-scoping (likely due to incomplete docs), so I've added more explicit error messages when deriving in non-reactive scopes to warn the user to use `root()` or `mount()`.
126 lines
3.7 KiB
Lua
126 lines
3.7 KiB
Lua
if not game then script = require "test/relative-string" end
|
|
|
|
local trace = require(script.Parent.trace)
|
|
local flags = require(script.Parent.flags)
|
|
local graph = require(script.Parent.graph)
|
|
type Node<T> = graph.Node<T>
|
|
local create_node = graph.create_node
|
|
local get_owning_scope = graph.get_owning_scope
|
|
local evaluate_node = graph.evaluate_node
|
|
local set_owner = graph.set_owner
|
|
|
|
function create_binding<T>(updater: (T) -> T, binding: T)
|
|
if flags.strict then
|
|
-- track bind creation trace
|
|
local fn = updater
|
|
local bind_trace = debug.traceback(nil, trace()-1)
|
|
updater = function(...)
|
|
local ok, result = xpcall(fn, function(err: string)
|
|
return err
|
|
end, ...)
|
|
|
|
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_owning_scope()
|
|
|
|
local node = create_node(binding, updater)
|
|
|
|
set_owner(node, owner)
|
|
evaluate_node(node)
|
|
end
|
|
|
|
type PropertyBinding = {
|
|
instance: Instance,
|
|
property: string,
|
|
source: () -> unknown
|
|
}
|
|
|
|
local function update_property(p: PropertyBinding)
|
|
(p.instance :: any)[p.property] = p.source()
|
|
return p
|
|
end
|
|
|
|
type ParentBinding = {
|
|
instance: Instance,
|
|
parent: () -> Instance
|
|
}
|
|
|
|
local function update_parent(p: ParentBinding)
|
|
p.instance.Parent = p.parent()
|
|
return p
|
|
end
|
|
|
|
type ChildrenBinding = {
|
|
instance: Instance,
|
|
cur_children_set: { [Instance]: true },
|
|
new_children_set: { [Instance]: true },
|
|
children: () -> Instance | { Instance }
|
|
}
|
|
|
|
local function update_children(p: ChildrenBinding)
|
|
local cur_children_set: { [Instance]: true } = p.cur_children_set -- cache of all children parented before update
|
|
local new_child_set: { [Instance]: true } = p.new_children_set -- cache of all children parented after update
|
|
|
|
local new_children = p.children() -- all (and only) children that should be parented after this update
|
|
|
|
if type(new_children) ~= "table" then
|
|
new_children = { new_children }
|
|
end
|
|
|
|
if new_children then
|
|
for _, child in next, new_children :: { Instance } do
|
|
new_child_set[child] = true -- record child set from this update
|
|
if not cur_children_set[child] then
|
|
child.Parent = p.instance -- if child wasn't already parented then parent it
|
|
else
|
|
cur_children_set[child] = nil -- remove child from cache if it was already in cache
|
|
end
|
|
end
|
|
end
|
|
|
|
for child in next, cur_children_set do
|
|
child.Parent = nil -- unparent all children that weren't in the new children set
|
|
end
|
|
|
|
table.clear(cur_children_set) -- clear cache, preserve capacity
|
|
p.cur_children_set, p.new_children_set = new_child_set, cur_children_set
|
|
|
|
return p
|
|
end
|
|
|
|
return {
|
|
property = function(instance, property, source)
|
|
return create_binding(update_property, {
|
|
instance = instance,
|
|
property = property,
|
|
source = source
|
|
})
|
|
end,
|
|
|
|
parent = function(instance, parent)
|
|
return create_binding(update_parent, {
|
|
instance = instance,
|
|
parent = parent
|
|
})
|
|
end,
|
|
|
|
children = function(instance, children)
|
|
return create_binding(update_children, {
|
|
instance = instance,
|
|
cur_children_set = {},
|
|
new_children_set = {},
|
|
children = children
|
|
})
|
|
end
|
|
}
|