This commit is contained in:
Aaron Smith 2023-08-01 16:26:09 +01:00
parent bf1b1978d9
commit 5b270f0f36
12 changed files with 308 additions and 477 deletions

View file

@ -1,39 +0,0 @@
------------------------------------------------------------------------------------------
-- vide/Children.lua
------------------------------------------------------------------------------------------
if not game then
script = (require :: any) "test/wrap-require"
typeof = require "test/mock".typeof
end
local graph = require(script.Parent.graph)
local wrapped = graph.wrapped
local throw = require(script.Parent.throw)
local bind = require(script.Parent.bind)
local Types = require(script.Parent.Types)
type Children = Types.Children
local function setChildren(instance: Instance, children: Children)
if typeof(children) == "Instance" then
if children.Parent then throw(`Cannot parent instance { instance.Name }, instance already parented`) end
children.Parent = instance
elseif wrapped(children) then
bind.children(children :: any, instance )
elseif type(children) == "table" then
for _, child: Children in next, children do
setChildren(instance, child)
end
else
throw(`Cannot parent non-instance { typeof(children) }`)
end
end
local Children = {
priority = 1,
run = setChildren
} :: Types.Symbol<Children>
return Children :: unknown

View file

@ -1,16 +0,0 @@
------------------------------------------------------------------------------------------
-- vide/Created.lua
------------------------------------------------------------------------------------------
if not game then script = (require :: any) "test/wrap-require" end
local Types = require(script.Parent.Types)
local Created = {
priority = 3,
run = function(instance: Instance, callback: (Instance) -> ())
callback(instance)
end
} :: Types.Symbol<(Instance) -> ()>
return Created :: unknown

View file

@ -1,46 +0,0 @@
------------------------------------------------------------------------------------------
-- vide/Layout.lua
------------------------------------------------------------------------------------------
if not game then script = (require :: any) "test/wrap-require" end
local graph = require(script.Parent.graph)
local wrapped = graph.wrapped
local throw = require(script.Parent.throw)
local bind = require(script.Parent.bind)
local flags = require(script.Parent.flags)
local Types = require(script.Parent.Types)
local layoutProperties = {
Parent = true,
AnchorPoint = true,
LayoutOrder = true,
Position = true,
Rotation = true,
Size = true,
SizeConstraint = true,
Visible = true,
ZIndex = true
}
local function setLayout(instance: Instance, properties: { [string]: unknown })
for property, value in next, properties do
if flags.strict and not layoutProperties[property] then
throw(`{ property } is not a valid layout property`)
end
if wrapped(value) then
bind.property(value :: graph.State<unknown>, instance, property)
else
(instance :: any)[property] = value
end
end
end
local Layout = {
priority = 1,
run = setLayout
} :: Types.Symbol<{ [string]: unknown }>
return Layout :: unknown

View file

@ -1,19 +0,0 @@
--------------------------------------------------------------------------------------------------------------
-- vide/Types.lua
--------------------------------------------------------------------------------------------------------------
if not game then script = (require :: any) "test/wrap-require" end
local graph = require(script.Parent.graph)
type State<T> = graph.State<T>
type MaybeState<T> = graph.MaybeState<T>
export type Symbol<T> = {
priority: number,
run: (Instance, T) -> ()
}
export type Children = Instance | State<Children> | { Children }
return {}

View file

@ -1,24 +0,0 @@
--------------------------------------------------------------------------------------------------------------
-- vide/each.lua
--------------------------------------------------------------------------------------------------------------
if not game then script = (require :: any) "test/wrap-require" end
local graph = require(script.Parent.graph)
type State<T> = graph.State<T>
type Node<T> = graph.Node<T>
local create = graph.create
local get = graph.get
local wrapped = graph.wrapped
local link = graph.link
type Map<K, V> = { [K]: V }
local function each<K, VI, VO>(input: State<Map<K, VI>>, transform: (K, VI) -> VO, cleanup: (VO) -> ()?)
end
return (each :: any) :: ( <V>(input: number, transform: (number) -> V) -> Map<number, V> ) &
( <K, VI, VO>(input: Map<K, VI>, transform: (K, VI) -> VO) -> Map<K, VO> ) &
( <K, VI, VO>(input: State<Map<K, VI>>, transform: (K, VI) -> VO, cleanup: (VO) -> ()?)
-> State<Map<K, VO>> )

View file

@ -19,13 +19,13 @@ local refs = {} :: { Node<unknown> }
local WEAK_VALUES_RESIZABLE = { __mode = "vs" }
local EVALUATION_ERR = "error while evaluating node:\n\n"
setmetatable(refs, WEAK_VALUES_RESIZABLE)
setmetatable(refs :: any, WEAK_VALUES_RESIZABLE)
local check_for_yield do
local t = { __mode = "kv" }
setmetatable(t, t)
check_for_yield = function<T..., U...>(fn: (T...) -> (), ...: U...)
check_for_yield = function<T..., U...>(fn: (T...) -> (), ...: any)
local args = { ... }
t.__unm = function()
fn(unpack(args))
@ -96,13 +96,19 @@ local function link<T>(parent: Node<unknown>, child: Node<T>, derive: () -> T)
end
-- detect what nodes were referenced in the given callback and returns them in an array
local function capture<T, U>(fn: (U) -> T, arg: U): ({ Node<unknown> }, T)
local function capture<T, U>(fn: (U?) -> T, arg: U?): ({ Node<unknown> }, T)
if flags.strict then check_for_yield(fn, arg) end
table.clear(refs)
reff = true
local ok: boolean, result: T|string = pcall(fn, arg)
local ok: boolean, result: T|string
if arg == nil then
ok, result = pcall(fn)
else
ok, result = pcall(fn, arg)
end
reff = false
@ -123,13 +129,19 @@ local function capture_and_link<T>(child: Node<T>, fn: () -> T): T
return value :: T
end
local function create<T>(value: T): Node<T>
return {
local function create<T>(value: T): (Node<T>, () -> T)
local node = {
cache = value,
derive = function() return nil :: any end,
effects = setmetatable({}, WEAK_VALUES_RESIZABLE) :: any,
children = false :: false
}
local function get_value()
return get(node)
end
return node, get_value
end
return table.freeze {
@ -139,5 +151,5 @@ return table.freeze {
link = link,
capture = capture,
capture_and_link = capture_and_link,
create = create,
create = create :: (<T>(value: T) -> (Node<T>, () -> T)) & (<T>() -> (Node<T>, () -> T)),
}

View file

@ -5,72 +5,66 @@
if not game then script = (require :: any) "test/wrap-require" end
local graph = require(script.Parent.graph)
type State<T> = graph.State<T>
type MaybeState<T> = graph.MaybeState<T>
type Node<T> = graph.Node<T>
local create = graph.create
local get = graph.get
local wrapped = graph.wrapped
local set = graph.set
local capture = graph.capture
local link = graph.link
local capture_and_link = graph.capture_and_link
type Map<K, V> = { [K]: V }
local function map<K, VI, VO>(input: unknown, transform: (K, VI) -> VO, cleanup: (VO) -> ()?): (MaybeState<Map<K, VO>>)
if type(input) == "number" then
local output: Map<K, VO> = table.create(input) :: {}
local function map<K, VI, VO>(input: () -> Map<K, VI>, transform: (() -> VI, K) -> VO): () -> Map<K, VO>
local input_cache = {} :: Map<K, VI>
local output_cache = {} :: Map<K, VO>
local input_nodes = {} :: Map<K, Node<VI>>
local remove_queue = {} :: { K }
for i = 1, input do
local v = transform(i :: any, nil :: any)
output[i :: any] = v
end
return output
elseif wrapped(input) then
local lastInput: Map<K, VI> = {}
local lastOutput: Map<K, VO> = {}
local output = create(lastOutput)
local function derive()
local newInput = get(input :: State<Map<K, VI>>)
if type(newInput) ~= "table" then error("Attempt to run map on a non-table state", 0) end
local lastInputClone = table.clone(lastInput)
for k, vi in next, newInput do
if vi ~= lastInputClone[k] then
local vo = transform(k, vi)
if cleanup and lastOutput[k] then cleanup(lastOutput[k]) end
lastInput[k] = vi
lastOutput[k] = vo
end
lastInputClone[k] = nil
local function recompute(data)
-- queue removed values
for k in next, input_cache do
if data[k] == nil then
table.insert(remove_queue, k)
end
end
for k, v in next, lastInputClone do
lastInput[k] = nil
if cleanup and lastOutput[k] then cleanup(lastOutput[k]) end
lastOutput[k] = nil
-- remove queued values
for _, k in next, remove_queue do
input_cache[k] = nil
output_cache[k] = nil
input_nodes[k] = nil
end
-- process new or changed values
for k, v in next, data do
if input_cache[k] == nil then
local node, get_value = create(v)
input_nodes[k] = node
output_cache[k] = transform(get_value, k)
elseif input_cache[k] ~= v then
set(input_nodes[k], v)
end
return table.clone(lastOutput)
input_cache[k] = v
end
link(input :: State<Map<K, VI>>, output, derive)
output.__updated = true
return output_cache
end
return output
elseif type(input) == "table" then
local output: Map<K, VO> = table.create(#input :: any) :: {}
local function derive()
return recompute(input())
end
for k, vi in input :: Map<K, VI> do
local vo = transform(k, vi)
output[k] = vo
end
local output, get_output = create(output_cache)
return output
else error(string.format("Invalid type arg #1, expected number or table or state (got %s)", tostring(input)), 2) end
local nodes, value = capture(input)
for _, node in next, nodes do
link(node, output, derive)
end
output.cache = recompute(value)
return get_output
end
return (map :: any) :: ( <V>(input: number, transform: (number) -> V) -> Map<number, V> ) &
( <K, VI, VO>(input: Map<K, VI>, transform: (K, VI) -> VO) -> Map<K, VO> ) &
( <K, VI, VO>(input: State<Map<K, VI>>, transform: (K, VI) -> VO, cleanup: (VO) -> ()?)
-> State<Map<K, VO>> )
return map

View file

@ -11,15 +11,13 @@ local get = graph.get
local set = graph.set
type State<T> = (() -> T) & ((T) -> T)
type Source<T> = (() -> T) & ((T) -> T)
local function source<T>(value: T | () -> T): State<T>
if type(value) == "function" then value = value() end
local node = create(value :: T)
local function source<T>(value: T): Source<T>
local node, get_value = create(value :: T)
return function(...): T
if select("#", ...) == 0 then return get(node) end
if select("#", ...) == 0 then return get_value() end
local v = ... :: T
if node.cache == v and type(v) ~= "table" then return v end
@ -29,4 +27,4 @@ local function source<T>(value: T | () -> T): State<T>
end
end
return source
return source :: (<T>(value: T) -> Source<T>) & (<T>() -> Source<T>)

View file

@ -10,7 +10,7 @@ local capture = graph.capture
local function watch(effect: () -> ()): () -> ()
-- todo: call cleanup on initial debug call when strict
local nodes, cleanup = capture(effect :: () -> (() -> ()?))
local nodes, cleanup = capture(effect :: () -> (() -> ()?), nil)
nodes = table.clone(nodes)