This commit is contained in:
Aaron Smith 2023-08-10 13:18:16 +01:00
parent 2050c2585f
commit cc10c80a90
10 changed files with 174 additions and 220 deletions

View file

@ -43,7 +43,7 @@ function setup(instance: Instance, setter: (Instance) -> ())
local trace = traceback()
setter = function(instance)
local ok, err: string? = pcall(fn, instance)
if not ok then warn(`error occured updating state binding:\n{err}\nset from:{trace}`) end
if not ok then warn(`error occured updating property:\n{err}\nset from:{trace}`) end
end
end
@ -68,8 +68,6 @@ function setup(instance: Instance, setter: (Instance) -> ())
instance:GetPropertyChangedSignal("Parent"):Connect(ref)
end
-- todo: move `fn` as arg?
local function bind_property(instance: Instance, property: string, fn: () -> unknown)
setup(instance, function(instance_weak: any)
instance_weak[property] = fn()
@ -80,13 +78,13 @@ local function bind_parent(instance: Instance, fn: () -> Instance?)
instance.Destroying:Connect(function()
instance= nil :: any -- allow gc when destroyed
end)
setup(instance, function(instance)
local _ = instance -- state will strongly reference instance when parent is bound
instance.Parent = fn()
end)
end
-- todo: could optimize, see: maps.luau values()
local function bind_children(parent: Instance, fn: () -> { Instance })
local current_child_set: { [Instance]: true } = {} -- cache of all children parented before update
local new_child_set: { [Instance]: true } = {} -- cache of all children parented after update

View file

@ -12,7 +12,7 @@ local defaults = require(script.Parent.defaults)
local apply = require(script.Parent.apply)
local memoize = require(script.Parent.memoize)
local function createInstance(className: string)
local function create_instance(className: string)
local success, instance: Instance = pcall(Instance.new, className :: any)
if success == false then throw(`invalid class name, could not create instance of class { className }`) end
@ -26,9 +26,9 @@ local function createInstance(className: string)
return function(properties: { [any]: unknown }): Instance
return apply(instance:Clone(), properties)
end
end; createInstance = memoize(createInstance)
end; create_instance = memoize(create_instance)
local function cloneInstance(instance: Instance)
local function clone_instance(instance: Instance)
return function(properties: { [any]: unknown }): Instance
local clone = instance:Clone()
if not clone then error("Attempt to clone a non-archivable instance", 3) end
@ -38,9 +38,9 @@ end
local function create(classNameOrInstance: string|Instance)
if type(classNameOrInstance) == "string" then
return createInstance(classNameOrInstance)
return create_instance(classNameOrInstance)
elseif typeof(classNameOrInstance) == "Instance" then
return cloneInstance(classNameOrInstance)
return clone_instance(classNameOrInstance)
else
error("Bad argument #1, expected string or instance, got "..typeof(classNameOrInstance), 2)
end

View file

@ -1,5 +1,6 @@
if not game then script = require "test/wrap-require" end
local throw = require(script.Parent.throw)
local flags = require(script.Parent.flags)
export type Node<T> = {
@ -13,28 +14,30 @@ local reff = false
local refs = {} :: { Node<unknown> }
local WEAK_VALUES_RESIZABLE = { __mode = "vs" }
local EVALUATION_ERR = "error while evaluating node:\n\n"
local EVALUATION_ERR = "error while evaluating source:\n\n"
setmetatable(refs :: any, WEAK_VALUES_RESIZABLE)
local check_for_yield do
local check_for_yield: <T...>(fn: (T...) -> unknown, T...) -> () do
local t = { __mode = "kv" }
setmetatable(t, t)
check_for_yield = function<T..., U...>(fn: (T...) -> (), ...: any)
check_for_yield = function(fn, ...: any)
local args = { ... }
t.__unm = function()
t.__unm = function(_)
fn(unpack(args))
end
local ok, err = pcall(function()
return -t :: any
local _ = -t
end)
if not ok then
if err == "attempt to yield across metamethod/C-call boundary" or err == "thread is not yieldable" then
error(EVALUATION_ERR .. "cannot yield when deriving node in watcher", 3)
throw(EVALUATION_ERR .. "cannot yield when deriving node in watcher")
else
error(EVALUATION_ERR..err, 3)
throw(EVALUATION_ERR .. err)
end
end
end
@ -45,10 +48,16 @@ local function set_effect<T>(node: Node<unknown>, fn: (T) -> (), key: T)
end
local function run_effects(node: Node<unknown>)
for effect, key in next, node.effects do
if flags.strict then effect(key) end
effect(key)
end
if flags.strict then
for effect, key in next, node.effects do
effect(key)
effect(key)
end
else
for effect, key in next, node.effects do
effect(key)
end
end
end
-- retrieves a node's cached value
@ -71,8 +80,10 @@ end
local function update(node: Node<unknown>)
run_effects(node)
if node.children then
local strict = flags.strict
for _, child in node.children do
if flags.strict then check_for_yield(child.derive) end
if strict then check_for_yield(child.derive) end
child.cache = child.derive()
update(child)
end
@ -108,7 +119,7 @@ local function capture<T, U>(fn: (U?) -> T, arg: U?): ({ Node<unknown> }, T)
reff = false
if not ok then error("error while detecting watcher: " .. result :: string, 0) end
if not ok then throw(EVALUATION_ERR .. result :: string) end
return refs, result :: T
end

View file

@ -14,6 +14,7 @@ local indexes, values = require(script.maps)()
local spring, update_springs = require(script.spring)()
local action = require(script.action)()
local throw = require(script.throw)
local flags = require(script.flags)
local vide = {
@ -49,15 +50,20 @@ local vide = {
}
setmetatable(vide :: any, {
__index = function(_, index: unknown)
error(string.format("\"%s\" is not a valid member of vide", tostring(index)), 2)
__index = function(_, index: unknown): ()
if index == "strict" then
return flags.strict
else
throw(`{tostring(index)} is not a valid member of vide`)
end
end,
__newindex = function(_, index: unknown, value: unknown)
if index == "strict" then
flags.strict = if type(value) == "boolean" then value else error("strict must be a boolean", 2)
if value ~= true then throw "strict mode can only be set to true" end
flags.strict = true
else
error(string.format("\"%s\" is not a valid member of vide", tostring(index)), 2)
throw(`{tostring(index)} is not a valid member of vide`)
end
end
})

View file

@ -1,5 +1,7 @@
if not game then script = require "test/wrap-require" end
local throw = require(script.Parent.throw)
local flags = require(script.Parent.flags)
local graph = require(script.Parent.graph)
type Node<T> = graph.Node<T>
local create = graph.create
@ -9,6 +11,15 @@ local link = graph.link
type Map<K, V> = { [K]: V }
local function check_primitives(t: {})
if not flags.strict then return end
for _, v in next, t do
if type(v) == "table" or type(v) == "userdata" then continue end
throw("table source map cannot return primitives")
end
end
-- todo: optimize output array
local function indexes<K, VI, VO>(input: () -> Map<K, VI>, transform: (() -> VI, K) -> VO): () -> { VO }
local input_cache = {} :: Map<K, VI>
@ -54,7 +65,8 @@ local function indexes<K, VI, VO>(input: () -> Map<K, VI>, transform: (() -> VI,
for _, v in next, output_cache do
table.insert(output_array, v)
end
check_primitives(output_array)
return output_array
end
@ -86,6 +98,16 @@ local function values<K, VI, VO>(input: () -> Map<K, VI>, transform: (VI, () ->
local function recompute(data: Map<K, VI>)
local cur_input_cache, new_input_cache = cur_input_cache_up, new_input_cache_up
if flags.strict then
local cache = {}
for _, v in next, data do
if cache[v] ~= nil then
throw "duplicate table value detected"
end
cache[v] = true
end
end
-- process data
for i, v in next, data do
@ -136,6 +158,7 @@ local function values<K, VI, VO>(input: () -> Map<K, VI>, transform: (VI, () ->
for _, node in next, nodes do
link(node, output, derive)
end
check_primitives(output_array)
output.cache = recompute(value)

View file

@ -157,7 +157,7 @@ local function update_springs(dt: number)
typeof(initial_position),
target_type
))
throw(`Cannot tween type { typeof(initial_position) } and { target_type }`)
throw(`cannot tween type { typeof(initial_position) } and { target_type }`)
continue
end
@ -165,14 +165,13 @@ local function update_springs(dt: number)
if lerp == nil then
springs[data] = nil
throw(`Cannot animate type { target_type }`)
throw(`cannot animate type { target_type }`)
continue
end
local new_time = data.duration + dt
local new_alpha = solve(data.period, data.damping_ratio, data.initial_velocity, new_time)
local new_velocity = -(new_alpha - data.alpha)/dt
local acceleration = (new_velocity - data.velocity)/dt
data.velocity = new_velocity
@ -181,7 +180,8 @@ local function update_springs(dt: number)
local value = lerp(initial_position, target_position, new_alpha)
if math.abs(acceleration) < 0.01 then
if math.abs(acceleration) < (0.01 / data.period) then
-- close enough to target, remove and set value to target
table.insert(remove_queue, data)
set(output, target_position)
else