mirror of
https://github.com/centau/vide.git
synced 2026-08-20 14:41:37 +00:00
Improve error reporting
This commit is contained in:
parent
14f8d38a35
commit
7bae2517cd
7 changed files with 50 additions and 119 deletions
|
|
@ -1,35 +1,12 @@
|
||||||
if not game then script = require "test/relative-string" end
|
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)
|
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 assert_stable_scope = graph.assert_stable_scope
|
local assert_stable_scope = graph.assert_stable_scope
|
||||||
local evaluate_node = graph.evaluate_node
|
local evaluate_node = graph.evaluate_node
|
||||||
|
|
||||||
function create_binding<T>(updater: (T) -> T, binding: T)
|
function create_implicit_effect<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
|
|
||||||
|
|
||||||
evaluate_node(create_node(assert_stable_scope(), updater, binding))
|
evaluate_node(create_node(assert_stable_scope(), updater, binding))
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
@ -39,7 +16,7 @@ type PropertyBinding = {
|
||||||
source: () -> unknown
|
source: () -> unknown
|
||||||
}
|
}
|
||||||
|
|
||||||
local function update_property(p: PropertyBinding)
|
local function update_property_effect(p: PropertyBinding)
|
||||||
(p.instance :: any)[p.property] = p.source()
|
(p.instance :: any)[p.property] = p.source()
|
||||||
return p
|
return p
|
||||||
end
|
end
|
||||||
|
|
@ -49,7 +26,7 @@ type ParentBinding = {
|
||||||
parent: () -> Instance
|
parent: () -> Instance
|
||||||
}
|
}
|
||||||
|
|
||||||
local function update_parent(p: ParentBinding)
|
local function update_parent_effect(p: ParentBinding)
|
||||||
p.instance.Parent = p.parent()
|
p.instance.Parent = p.parent()
|
||||||
return p
|
return p
|
||||||
end
|
end
|
||||||
|
|
@ -61,7 +38,7 @@ type ChildrenBinding = {
|
||||||
children: () -> Instance | { Instance }
|
children: () -> Instance | { Instance }
|
||||||
}
|
}
|
||||||
|
|
||||||
local function update_children(p: ChildrenBinding)
|
local function update_children_effect(p: ChildrenBinding)
|
||||||
local cur_children_set: { [Instance]: true } = p.cur_children_set -- cache of all children parented before update
|
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_child_set: { [Instance]: true } = p.new_children_set -- cache of all children parented after update
|
||||||
|
|
||||||
|
|
@ -94,7 +71,7 @@ end
|
||||||
|
|
||||||
return {
|
return {
|
||||||
property = function(instance, property, source)
|
property = function(instance, property, source)
|
||||||
return create_binding(update_property, {
|
return create_implicit_effect(update_property_effect, {
|
||||||
instance = instance,
|
instance = instance,
|
||||||
property = property,
|
property = property,
|
||||||
source = source
|
source = source
|
||||||
|
|
@ -102,14 +79,14 @@ return {
|
||||||
end,
|
end,
|
||||||
|
|
||||||
parent = function(instance, parent)
|
parent = function(instance, parent)
|
||||||
return create_binding(update_parent, {
|
return create_implicit_effect(update_parent_effect, {
|
||||||
instance = instance,
|
instance = instance,
|
||||||
parent = parent
|
parent = parent
|
||||||
})
|
})
|
||||||
end,
|
end,
|
||||||
|
|
||||||
children = function(instance, children)
|
children = function(instance, children)
|
||||||
return create_binding(update_children, {
|
return create_implicit_effect(update_children_effect, {
|
||||||
instance = instance,
|
instance = instance,
|
||||||
cur_children_set = {},
|
cur_children_set = {},
|
||||||
new_children_set = {},
|
new_children_set = {},
|
||||||
|
|
|
||||||
|
|
@ -23,13 +23,14 @@ export type Node<T> = {
|
||||||
local scopes = { n = 0 } :: { [number]: Node<any>, n: number } -- scopes stack
|
local scopes = { n = 0 } :: { [number]: Node<any>, n: number } -- scopes stack
|
||||||
|
|
||||||
local function ycall<T, U>(fn: (T) -> U, arg: T): (boolean, string|U)
|
local function ycall<T, U>(fn: (T) -> U, arg: T): (boolean, string|U)
|
||||||
local thread = coroutine.create(pcall)
|
local thread = coroutine.create(xpcall)
|
||||||
local resume_ok, run_ok, result = coroutine.resume(thread, fn, arg)
|
local function efn(err: string) return debug.traceback(err, 3) end
|
||||||
|
local resume_ok, run_ok, result = coroutine.resume(thread, fn, efn, arg)
|
||||||
|
|
||||||
assert(resume_ok)
|
assert(resume_ok)
|
||||||
|
|
||||||
if coroutine.status(thread) ~= "dead" then
|
if coroutine.status(thread) ~= "dead" then
|
||||||
return false, "attempt to yield in reactive scope"
|
return false, debug.traceback(thread, "attempt to yield in reactive scope")
|
||||||
end
|
end
|
||||||
|
|
||||||
return run_ok, result
|
return run_ok, result
|
||||||
|
|
@ -129,41 +130,47 @@ end
|
||||||
local update_queue = { n = 0 } :: { n: number, [number]: Node<any> }
|
local update_queue = { n = 0 } :: { n: number, [number]: Node<any> }
|
||||||
|
|
||||||
local function evaluate_node<T>(node: Node<T>)
|
local function evaluate_node<T>(node: Node<T>)
|
||||||
local cur_value = node.cache
|
|
||||||
|
|
||||||
if flags.strict then
|
if flags.strict then
|
||||||
|
local ok, cur_value, new_value
|
||||||
|
for i = 1, 2 do
|
||||||
|
cur_value = node.cache
|
||||||
|
|
||||||
|
flush_cleanups(node)
|
||||||
|
destroy_owned(node)
|
||||||
|
|
||||||
|
push_scope(node)
|
||||||
|
ok, new_value = ycall(node.effect :: (T) -> T, cur_value)
|
||||||
|
pop_scope()
|
||||||
|
|
||||||
|
if not ok then
|
||||||
|
table.clear(update_queue)
|
||||||
|
update_queue.n = 0
|
||||||
|
throw(`effect stacktrace:\n{new_value :: string}`)
|
||||||
|
end
|
||||||
|
|
||||||
|
node.cache = new_value :: T
|
||||||
|
end
|
||||||
|
|
||||||
|
return cur_value ~= new_value
|
||||||
|
else
|
||||||
|
local cur_value = node.cache
|
||||||
|
|
||||||
flush_cleanups(node)
|
flush_cleanups(node)
|
||||||
destroy_owned(node)
|
destroy_owned(node)
|
||||||
|
|
||||||
push_scope(node)
|
push_scope(node)
|
||||||
|
local ok, new_value = pcall(node.effect :: (T) -> T, node.cache)
|
||||||
local ok, new_value = ycall(node.effect :: (T) -> T, cur_value)
|
|
||||||
|
|
||||||
pop_scope()
|
pop_scope()
|
||||||
|
|
||||||
if not ok then throw(new_value :: string) end
|
|
||||||
|
|
||||||
node.cache = new_value :: T
|
if not ok then
|
||||||
|
table.clear(update_queue)
|
||||||
|
update_queue.n = 0
|
||||||
|
throw(`effect stacktrace:\n{new_value}\n`)
|
||||||
|
end
|
||||||
|
|
||||||
|
node.cache = new_value
|
||||||
|
return cur_value ~= new_value
|
||||||
end
|
end
|
||||||
|
|
||||||
flush_cleanups(node)
|
|
||||||
destroy_owned(node)
|
|
||||||
|
|
||||||
push_scope(node)
|
|
||||||
|
|
||||||
local ok, new_value = pcall(node.effect :: (T) -> T, node.cache)
|
|
||||||
|
|
||||||
pop_scope()
|
|
||||||
|
|
||||||
if not ok then
|
|
||||||
table.clear(update_queue)
|
|
||||||
update_queue.n = 0
|
|
||||||
throw(`side-effect error from source update\n{new_value}`)
|
|
||||||
end
|
|
||||||
|
|
||||||
node.cache = new_value
|
|
||||||
|
|
||||||
return cur_value ~= new_value
|
|
||||||
end
|
end
|
||||||
|
|
||||||
local function queue_children_for_update<T>(node: SourceNode<T>)
|
local function queue_children_for_update<T>(node: SourceNode<T>)
|
||||||
|
|
|
||||||
|
|
@ -29,7 +29,7 @@ local function root<T...>(fn: (destroy: () -> ()) -> T...): T...
|
||||||
|
|
||||||
if not result[1] then
|
if not result[1] then
|
||||||
refs[node] = nil
|
refs[node] = nil
|
||||||
throw(`mount error\n{result[2]}`)
|
throw(`error while running root():\n\n{result[2]}`)
|
||||||
end
|
end
|
||||||
|
|
||||||
return unpack(result :: any, 2)
|
return unpack(result :: any, 2)
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,7 @@
|
||||||
if not game then script = require "test/relative-string" end
|
if not game then script = require "test/relative-string" end
|
||||||
|
|
||||||
local trace = require(script.Parent.trace)
|
local function VIDE_ASSERT(msg): any
|
||||||
|
error(msg, 0)
|
||||||
local function throw(msg): any
|
|
||||||
error(msg, trace() - 1)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
return throw
|
return VIDE_ASSERT
|
||||||
|
|
|
||||||
|
|
@ -1,29 +0,0 @@
|
||||||
-- returns path to file as an array with each directory
|
|
||||||
-- accounts for Roblox and Luau contexts
|
|
||||||
local function get_path(s)
|
|
||||||
if string.sub(s, #s - 4, #s) == ".luau" then
|
|
||||||
s = string.sub(s, 1, #s - 5)
|
|
||||||
end
|
|
||||||
|
|
||||||
return string.split(s, string.match(s, "%w+/") and "/" or ".")
|
|
||||||
end
|
|
||||||
|
|
||||||
-- get directory of vide root
|
|
||||||
local root do
|
|
||||||
local path = get_path(debug.info(1, "s"))
|
|
||||||
root = path[#path - 1]
|
|
||||||
end
|
|
||||||
|
|
||||||
-- finds the first stack depth outside of any vide library function
|
|
||||||
return function(): number
|
|
||||||
local stack = 1
|
|
||||||
|
|
||||||
local path = get_path(debug.info(stack, "s"))
|
|
||||||
|
|
||||||
while path[#path] == root or path[#path - 1] == root do
|
|
||||||
stack += 1
|
|
||||||
path = get_path(debug.info(stack, "s"))
|
|
||||||
end
|
|
||||||
|
|
||||||
return stack
|
|
||||||
end
|
|
||||||
|
|
@ -26,7 +26,6 @@ end
|
||||||
|
|
||||||
local N = 2^18 -- 262144
|
local N = 2^18 -- 262144
|
||||||
|
|
||||||
|
|
||||||
TITLE "sources"
|
TITLE "sources"
|
||||||
|
|
||||||
BENCH("create source", function()
|
BENCH("create source", function()
|
||||||
|
|
@ -449,27 +448,6 @@ end)
|
||||||
|
|
||||||
N *= 1024
|
N *= 1024
|
||||||
|
|
||||||
TITLE "cleanup"
|
|
||||||
|
|
||||||
ROOT_BENCH("register new cleanup", function()
|
|
||||||
local cleanup = cleanup
|
|
||||||
|
|
||||||
local cleaner = function() end
|
|
||||||
|
|
||||||
local callers = {}
|
|
||||||
|
|
||||||
for i = 1, N do
|
|
||||||
callers[i] = function(fn, v)
|
|
||||||
fn(v)
|
|
||||||
return i -- return unique upvalue to ensure unique closure
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
for i = 1, START(N) do
|
|
||||||
callers[i](cleanup, cleaner)
|
|
||||||
end
|
|
||||||
end)
|
|
||||||
|
|
||||||
TITLE "aggregate"
|
TITLE "aggregate"
|
||||||
|
|
||||||
do
|
do
|
||||||
|
|
|
||||||
|
|
@ -218,8 +218,8 @@ TEST("graph", function()
|
||||||
do
|
do
|
||||||
local c = get_children(selected)
|
local c = get_children(selected)
|
||||||
CHECK(#c == 2)
|
CHECK(#c == 2)
|
||||||
CHECK(table.find(c, bind1))
|
CHECK(table.find(c, bind1 :: any))
|
||||||
CHECK(table.find(c, bind2))
|
CHECK(table.find(c, bind2 :: any))
|
||||||
end
|
end
|
||||||
|
|
||||||
do
|
do
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue