mirror of
https://github.com/centau/vide.git
synced 2026-08-20 14:41:37 +00:00
This commit is contained in:
parent
9021d04e1e
commit
1aa74310f3
6 changed files with 102 additions and 47 deletions
|
|
@ -39,7 +39,7 @@ local getChangeSymbol = memoize(function(name: string): Types.Symbol<MaybeState<
|
|||
end
|
||||
end)
|
||||
|
||||
state.updated = true
|
||||
state.__updated = true
|
||||
bind.event(state :: State<any>, instance, event)
|
||||
else
|
||||
error("Attempt to connect non-function to changed event", 2)
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ local function derive<T>(deriveValue: (Unwrapper) -> T, cleanup: (T) -> ()?): St
|
|||
end
|
||||
|
||||
local value: T = captureAndLink(node, deriveValue)
|
||||
node.cache = value
|
||||
rawset(node, "__cache", value)
|
||||
|
||||
return node :: State<T>
|
||||
end
|
||||
|
|
|
|||
111
src/graph.lua
111
src/graph.lua
|
|
@ -8,11 +8,11 @@ local flags = require(script.Parent.flags)
|
|||
|
||||
export type State<T> = typeof(setmetatable(
|
||||
{} :: {
|
||||
cache: T,
|
||||
updated: boolean,
|
||||
derive: (any) -> T,
|
||||
effects: { [(unknown) -> ()]: unknown } | false, -- weak values
|
||||
children: { State<T> } | false -- weak values
|
||||
__cache: T,
|
||||
__updated: boolean,
|
||||
__derive: (any) -> T,
|
||||
__effects: { [(unknown) -> ()]: unknown } | false, -- weak values
|
||||
__children: { State<T> } | false -- weak values
|
||||
}, {} :: {
|
||||
__concat: (any, any) -> any,
|
||||
__add: (any, any) -> any,
|
||||
|
|
@ -32,7 +32,7 @@ export type MaybeState<T> = State<T> | T
|
|||
export type Unwrapper = <T>(T) -> T
|
||||
|
||||
local WEAK_VALUES_RESIZABLE = { __mode = "vs" }
|
||||
local EVALUATION_ERR = "Error while evaluating state:\n\n"
|
||||
local EVALUATION_ERR = "error while evaluating state:\n\n"
|
||||
|
||||
local State = {}
|
||||
|
||||
|
|
@ -56,7 +56,7 @@ local checkForYield do
|
|||
|
||||
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 state in watcher", 3)
|
||||
error(EVALUATION_ERR .. "cannot yield when deriving state in watcher", 3)
|
||||
else
|
||||
error(EVALUATION_ERR..err, 3)
|
||||
end
|
||||
|
|
@ -65,16 +65,16 @@ local checkForYield do
|
|||
end
|
||||
|
||||
local function setEffect<T>(state: State<unknown>, fn: (T) -> (), key: T)
|
||||
if not state.effects then
|
||||
state.effects = setmetatable({ [fn] = key }, WEAK_VALUES_RESIZABLE) :: any
|
||||
if not state.__effects then
|
||||
state.__effects = setmetatable({ [fn] = key }, WEAK_VALUES_RESIZABLE) :: any
|
||||
else
|
||||
state.effects[fn :: () -> ()] = key
|
||||
state.__effects[fn :: () -> ()] = key
|
||||
end
|
||||
end
|
||||
|
||||
local function runEffects(state: State<unknown>)
|
||||
if state.effects then
|
||||
for effect, key in next, state.effects do
|
||||
if state.__effects then
|
||||
for effect, key in next, state.__effects do
|
||||
if flags.strict then effect(key) end
|
||||
effect(key)
|
||||
end
|
||||
|
|
@ -84,17 +84,17 @@ end
|
|||
-- retrieves a state's cached value
|
||||
-- recalculates value if an ancestor was updated
|
||||
local function get<T>(state: State<T>): T
|
||||
if state.updated then
|
||||
state.updated = false
|
||||
if state.__updated then
|
||||
state.__updated = false
|
||||
|
||||
if flags.strict then checkForYield(state.derive) end
|
||||
if flags.strict then checkForYield(state.__derive) end
|
||||
|
||||
local ok, result: T|string? = pcall(state.derive, unwrap); if ok then
|
||||
rawset(state :: any, "cache", result :: T)
|
||||
else error(EVALUATION_ERR .. result :: string, 2) end
|
||||
local ok, result: T|string? = pcall(state.__derive, unwrap); if ok then
|
||||
rawset(state :: any, "__cache", result :: T)
|
||||
else error(EVALUATION_ERR .. result :: string, 0) end
|
||||
end
|
||||
|
||||
return state.cache
|
||||
return rawget(state :: any, "__cache")
|
||||
end
|
||||
|
||||
-- utility function for retrieving a value from state and allowing passthrough of non-state
|
||||
|
|
@ -107,20 +107,20 @@ unwrap = function<T>(value: MaybeState<T>): T
|
|||
end
|
||||
|
||||
local function addChild(parent: State<unknown>, child: State<unknown>)
|
||||
if parent.children then
|
||||
table.insert(parent.children, child)
|
||||
if parent.__children then
|
||||
table.insert(parent.__children, child)
|
||||
else
|
||||
parent.children = setmetatable({ child }, WEAK_VALUES_RESIZABLE) :: any
|
||||
parent.__children = setmetatable({ child }, WEAK_VALUES_RESIZABLE) :: any
|
||||
end
|
||||
end
|
||||
|
||||
-- marks all state descendants for recalculation and runs effects
|
||||
local function update(state: State<unknown>)
|
||||
runEffects(state)
|
||||
if state.children then
|
||||
for _, child in state.children do
|
||||
if not child.updated then
|
||||
child.updated = true
|
||||
if state.__children then
|
||||
for _, child in state.__children do
|
||||
if not child.__updated then
|
||||
child.__updated = true
|
||||
update(child)
|
||||
end
|
||||
end
|
||||
|
|
@ -129,13 +129,13 @@ end
|
|||
|
||||
-- sets a state's cached value and updates all descendants
|
||||
local function set<T>(state: State<T>, value: T)
|
||||
state.cache = value
|
||||
state.__cache = value
|
||||
update(state)
|
||||
end
|
||||
|
||||
-- links two states as parent-child
|
||||
local function link(parent: State<unknown>, child: State<unknown>, derive: () -> unknown)
|
||||
child.derive = derive
|
||||
child.__derive = derive
|
||||
addChild(parent, child)
|
||||
end
|
||||
|
||||
|
|
@ -154,7 +154,7 @@ local function capture<T>(callback: (Unwrapper) -> T): ({ State<unknown> }, T)
|
|||
end
|
||||
end)
|
||||
|
||||
if not ok then error("Error while detecting watcher: " .. result :: string, 2) end
|
||||
if not ok then error("error while detecting watcher: " .. result :: string, 0) end
|
||||
|
||||
return states, result :: T
|
||||
end
|
||||
|
|
@ -163,7 +163,7 @@ end
|
|||
local function captureAndLink<T>(child: State<T>, callback: (Unwrapper) -> T): T
|
||||
local states, value = capture(callback)
|
||||
|
||||
child.derive = callback
|
||||
child.__derive = callback
|
||||
for _, parent: State<unknown> in next, states do
|
||||
addChild(parent, child)
|
||||
end
|
||||
|
|
@ -191,30 +191,61 @@ local function overload(op: (unknown, unknown) -> unknown): (any, any) -> any
|
|||
link(b :: State<unknown>, derived, function() return op(a, get(b :: State<unknown>)) end)
|
||||
end
|
||||
|
||||
derived.updated = true
|
||||
derived.__updated = true
|
||||
return derived
|
||||
end
|
||||
end
|
||||
|
||||
function State.__index(_, index)
|
||||
if index == "cache" then return nil end -- todo: better solution
|
||||
error("attempt to index state", 2)
|
||||
local function __unm(self: State<unknown>)
|
||||
local derived = create(nil :: any)
|
||||
|
||||
link(self, derived, function()
|
||||
return -get(self) :: number
|
||||
end)
|
||||
|
||||
derived.__updated = true
|
||||
return derived
|
||||
end
|
||||
|
||||
local function __index(self: State<unknown>, index: unknown)
|
||||
local derived = create(nil :: any)
|
||||
|
||||
link(self, derived, function()
|
||||
return (get(self) :: {})[index]
|
||||
end)
|
||||
|
||||
derived.__updated = true
|
||||
return derived
|
||||
end
|
||||
|
||||
State.__index = __index
|
||||
State.__concat = overload(function(a: any, b: any) return tostring(a) .. tostring(b) end)
|
||||
State.__add = overload(function(a: any, b: any) return a + b end)
|
||||
State.__sub = overload(function(a: any, b: any) return a - b end)
|
||||
State.__mul = overload(function(a: any, b: any) return a * b end)
|
||||
State.__div = overload(function(a: any, b: any) return a / b end)
|
||||
--State.__eq = overload(function(a: any, b: any) return a == b end)
|
||||
State.__pow = overload(function(a: any, b: any) return a ^ b end)
|
||||
State.__mod = overload(function(a: any, b: any) return a % b end)
|
||||
State.__unm = __unm
|
||||
|
||||
-- todo: what to do
|
||||
do
|
||||
local function err()
|
||||
error("cannot perform equality comparison with state", 2)
|
||||
end
|
||||
State.__eq = err
|
||||
State.__lt = err
|
||||
State.__le = err
|
||||
end
|
||||
|
||||
|
||||
function create<T>(value: T): State<T>
|
||||
return setmetatable({
|
||||
cache = value,
|
||||
updated = false,
|
||||
derive = function() return nil end :: any,
|
||||
effects = false :: false,
|
||||
children = false :: false
|
||||
__cache = value,
|
||||
__updated = false,
|
||||
__derive = function() return nil end :: any,
|
||||
__effects = false :: false,
|
||||
__children = false :: false
|
||||
}, State)
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -55,7 +55,7 @@ local function map<K, VI, VO>(input: unknown, transform: (K, VI) -> VO, cleanup:
|
|||
end
|
||||
|
||||
link(input :: State<Map<K, VI>>, output, derive)
|
||||
output.updated = true
|
||||
output.__updated = true
|
||||
|
||||
return output
|
||||
elseif type(input) == "table" then
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ local function wrap<T>(value: MaybeState<T>?): (State<T>, Setter<T>)
|
|||
|
||||
local v = if wrapped(vi) then get(vi :: State<T>) else vi :: T
|
||||
|
||||
if v ~= state.cache or force then
|
||||
if v ~= state.__cache or force then
|
||||
set(state, v)
|
||||
elseif flags.strict and type(v) == "table" then
|
||||
throw("attempt to set same table object")
|
||||
|
|
|
|||
|
|
@ -105,8 +105,8 @@ TEST("graph", function()
|
|||
set(a, get(a) + 1) -- mark `b` for recomputation again
|
||||
captureAndLink(c, function(from) return from(b) end)
|
||||
-- check that only `b` was linked
|
||||
CHECK(not rawfind(assert(a.children), c))
|
||||
CHECK(rawfind(assert(b.children), c))
|
||||
CHECK(not rawfind(assert(a.__children), c))
|
||||
CHECK(rawfind(assert(b.__children), c))
|
||||
end
|
||||
|
||||
do CASE "Nodes garbage collection"
|
||||
|
|
@ -291,6 +291,30 @@ TEST("derive()", function()
|
|||
set(2)
|
||||
CHECK(unwrap(text) == "22")
|
||||
end
|
||||
|
||||
do
|
||||
local a, seta = wrap { b = { c = 1 } }
|
||||
local b = a.b
|
||||
local c = b.c
|
||||
|
||||
CHECK(unwrap(c) == 1)
|
||||
|
||||
seta { b = { c = 2 } }
|
||||
|
||||
CHECK(unwrap(c) == 2)
|
||||
end
|
||||
|
||||
do
|
||||
local state, set = wrap { profiles = { decimal = { level = 1 }}}
|
||||
local stringified = "Level: " .. state.profiles.decimal.level
|
||||
|
||||
set(function(state)
|
||||
state.profiles.decimal.level += 1
|
||||
return state
|
||||
end, true)
|
||||
|
||||
CHECK(unwrap(stringified) == "Level: 2")
|
||||
end
|
||||
end
|
||||
|
||||
do CASE "Derive from updated"
|
||||
|
|
@ -704,7 +728,7 @@ TEST("create()", function()
|
|||
}
|
||||
|
||||
wref.instance = instance
|
||||
wref.binding = next((state :: any).effects)
|
||||
wref.binding = next((state :: any).__effects)
|
||||
end
|
||||
|
||||
CHECK(wref.binding)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue