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
|
||||||
end)
|
end)
|
||||||
|
|
||||||
state.updated = true
|
state.__updated = true
|
||||||
bind.event(state :: State<any>, instance, event)
|
bind.event(state :: State<any>, instance, event)
|
||||||
else
|
else
|
||||||
error("Attempt to connect non-function to changed event", 2)
|
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
|
end
|
||||||
|
|
||||||
local value: T = captureAndLink(node, deriveValue)
|
local value: T = captureAndLink(node, deriveValue)
|
||||||
node.cache = value
|
rawset(node, "__cache", value)
|
||||||
|
|
||||||
return node :: State<T>
|
return node :: State<T>
|
||||||
end
|
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(
|
export type State<T> = typeof(setmetatable(
|
||||||
{} :: {
|
{} :: {
|
||||||
cache: T,
|
__cache: T,
|
||||||
updated: boolean,
|
__updated: boolean,
|
||||||
derive: (any) -> T,
|
__derive: (any) -> T,
|
||||||
effects: { [(unknown) -> ()]: unknown } | false, -- weak values
|
__effects: { [(unknown) -> ()]: unknown } | false, -- weak values
|
||||||
children: { State<T> } | false -- weak values
|
__children: { State<T> } | false -- weak values
|
||||||
}, {} :: {
|
}, {} :: {
|
||||||
__concat: (any, any) -> any,
|
__concat: (any, any) -> any,
|
||||||
__add: (any, any) -> any,
|
__add: (any, any) -> any,
|
||||||
|
|
@ -32,7 +32,7 @@ export type MaybeState<T> = State<T> | T
|
||||||
export type Unwrapper = <T>(T) -> T
|
export type Unwrapper = <T>(T) -> T
|
||||||
|
|
||||||
local WEAK_VALUES_RESIZABLE = { __mode = "vs" }
|
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 = {}
|
local State = {}
|
||||||
|
|
||||||
|
|
@ -56,7 +56,7 @@ local checkForYield do
|
||||||
|
|
||||||
if not ok then
|
if not ok then
|
||||||
if err == "attempt to yield across metamethod/C-call boundary" or err == "thread is not yieldable" 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
|
else
|
||||||
error(EVALUATION_ERR..err, 3)
|
error(EVALUATION_ERR..err, 3)
|
||||||
end
|
end
|
||||||
|
|
@ -65,16 +65,16 @@ local checkForYield do
|
||||||
end
|
end
|
||||||
|
|
||||||
local function setEffect<T>(state: State<unknown>, fn: (T) -> (), key: T)
|
local function setEffect<T>(state: State<unknown>, fn: (T) -> (), key: T)
|
||||||
if not state.effects then
|
if not state.__effects then
|
||||||
state.effects = setmetatable({ [fn] = key }, WEAK_VALUES_RESIZABLE) :: any
|
state.__effects = setmetatable({ [fn] = key }, WEAK_VALUES_RESIZABLE) :: any
|
||||||
else
|
else
|
||||||
state.effects[fn :: () -> ()] = key
|
state.__effects[fn :: () -> ()] = key
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
local function runEffects(state: State<unknown>)
|
local function runEffects(state: State<unknown>)
|
||||||
if state.effects then
|
if state.__effects then
|
||||||
for effect, key in next, state.effects do
|
for effect, key in next, state.__effects do
|
||||||
if flags.strict then effect(key) end
|
if flags.strict then effect(key) end
|
||||||
effect(key)
|
effect(key)
|
||||||
end
|
end
|
||||||
|
|
@ -84,17 +84,17 @@ end
|
||||||
-- retrieves a state's cached value
|
-- retrieves a state's cached value
|
||||||
-- recalculates value if an ancestor was updated
|
-- recalculates value if an ancestor was updated
|
||||||
local function get<T>(state: State<T>): T
|
local function get<T>(state: State<T>): T
|
||||||
if state.updated then
|
if state.__updated then
|
||||||
state.updated = false
|
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
|
local ok, result: T|string? = pcall(state.__derive, unwrap); if ok then
|
||||||
rawset(state :: any, "cache", result :: T)
|
rawset(state :: any, "__cache", result :: T)
|
||||||
else error(EVALUATION_ERR .. result :: string, 2) end
|
else error(EVALUATION_ERR .. result :: string, 0) end
|
||||||
end
|
end
|
||||||
|
|
||||||
return state.cache
|
return rawget(state :: any, "__cache")
|
||||||
end
|
end
|
||||||
|
|
||||||
-- utility function for retrieving a value from state and allowing passthrough of non-state
|
-- 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
|
end
|
||||||
|
|
||||||
local function addChild(parent: State<unknown>, child: State<unknown>)
|
local function addChild(parent: State<unknown>, child: State<unknown>)
|
||||||
if parent.children then
|
if parent.__children then
|
||||||
table.insert(parent.children, child)
|
table.insert(parent.__children, child)
|
||||||
else
|
else
|
||||||
parent.children = setmetatable({ child }, WEAK_VALUES_RESIZABLE) :: any
|
parent.__children = setmetatable({ child }, WEAK_VALUES_RESIZABLE) :: any
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
-- marks all state descendants for recalculation and runs effects
|
-- marks all state descendants for recalculation and runs effects
|
||||||
local function update(state: State<unknown>)
|
local function update(state: State<unknown>)
|
||||||
runEffects(state)
|
runEffects(state)
|
||||||
if state.children then
|
if state.__children then
|
||||||
for _, child in state.children do
|
for _, child in state.__children do
|
||||||
if not child.updated then
|
if not child.__updated then
|
||||||
child.updated = true
|
child.__updated = true
|
||||||
update(child)
|
update(child)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
@ -129,13 +129,13 @@ end
|
||||||
|
|
||||||
-- sets a state's cached value and updates all descendants
|
-- sets a state's cached value and updates all descendants
|
||||||
local function set<T>(state: State<T>, value: T)
|
local function set<T>(state: State<T>, value: T)
|
||||||
state.cache = value
|
state.__cache = value
|
||||||
update(state)
|
update(state)
|
||||||
end
|
end
|
||||||
|
|
||||||
-- links two states as parent-child
|
-- links two states as parent-child
|
||||||
local function link(parent: State<unknown>, child: State<unknown>, derive: () -> unknown)
|
local function link(parent: State<unknown>, child: State<unknown>, derive: () -> unknown)
|
||||||
child.derive = derive
|
child.__derive = derive
|
||||||
addChild(parent, child)
|
addChild(parent, child)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
@ -154,7 +154,7 @@ local function capture<T>(callback: (Unwrapper) -> T): ({ State<unknown> }, T)
|
||||||
end
|
end
|
||||||
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
|
return states, result :: T
|
||||||
end
|
end
|
||||||
|
|
@ -163,7 +163,7 @@ end
|
||||||
local function captureAndLink<T>(child: State<T>, callback: (Unwrapper) -> T): T
|
local function captureAndLink<T>(child: State<T>, callback: (Unwrapper) -> T): T
|
||||||
local states, value = capture(callback)
|
local states, value = capture(callback)
|
||||||
|
|
||||||
child.derive = callback
|
child.__derive = callback
|
||||||
for _, parent: State<unknown> in next, states do
|
for _, parent: State<unknown> in next, states do
|
||||||
addChild(parent, child)
|
addChild(parent, child)
|
||||||
end
|
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)
|
link(b :: State<unknown>, derived, function() return op(a, get(b :: State<unknown>)) end)
|
||||||
end
|
end
|
||||||
|
|
||||||
derived.updated = true
|
derived.__updated = true
|
||||||
return derived
|
return derived
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
function State.__index(_, index)
|
local function __unm(self: State<unknown>)
|
||||||
if index == "cache" then return nil end -- todo: better solution
|
local derived = create(nil :: any)
|
||||||
error("attempt to index state", 2)
|
|
||||||
|
link(self, derived, function()
|
||||||
|
return -get(self) :: number
|
||||||
|
end)
|
||||||
|
|
||||||
|
derived.__updated = true
|
||||||
|
return derived
|
||||||
end
|
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.__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.__add = overload(function(a: any, b: any) return a + b end)
|
||||||
State.__sub = 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.__mul = overload(function(a: any, b: any) return a * b end)
|
||||||
State.__div = 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>
|
function create<T>(value: T): State<T>
|
||||||
return setmetatable({
|
return setmetatable({
|
||||||
cache = value,
|
__cache = value,
|
||||||
updated = false,
|
__updated = false,
|
||||||
derive = function() return nil end :: any,
|
__derive = function() return nil end :: any,
|
||||||
effects = false :: false,
|
__effects = false :: false,
|
||||||
children = false :: false
|
__children = false :: false
|
||||||
}, State)
|
}, State)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -55,7 +55,7 @@ local function map<K, VI, VO>(input: unknown, transform: (K, VI) -> VO, cleanup:
|
||||||
end
|
end
|
||||||
|
|
||||||
link(input :: State<Map<K, VI>>, output, derive)
|
link(input :: State<Map<K, VI>>, output, derive)
|
||||||
output.updated = true
|
output.__updated = true
|
||||||
|
|
||||||
return output
|
return output
|
||||||
elseif type(input) == "table" then
|
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
|
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)
|
set(state, v)
|
||||||
elseif flags.strict and type(v) == "table" then
|
elseif flags.strict and type(v) == "table" then
|
||||||
throw("attempt to set same table object")
|
throw("attempt to set same table object")
|
||||||
|
|
|
||||||
|
|
@ -105,8 +105,8 @@ TEST("graph", function()
|
||||||
set(a, get(a) + 1) -- mark `b` for recomputation again
|
set(a, get(a) + 1) -- mark `b` for recomputation again
|
||||||
captureAndLink(c, function(from) return from(b) end)
|
captureAndLink(c, function(from) return from(b) end)
|
||||||
-- check that only `b` was linked
|
-- check that only `b` was linked
|
||||||
CHECK(not rawfind(assert(a.children), c))
|
CHECK(not rawfind(assert(a.__children), c))
|
||||||
CHECK(rawfind(assert(b.children), c))
|
CHECK(rawfind(assert(b.__children), c))
|
||||||
end
|
end
|
||||||
|
|
||||||
do CASE "Nodes garbage collection"
|
do CASE "Nodes garbage collection"
|
||||||
|
|
@ -291,6 +291,30 @@ TEST("derive()", function()
|
||||||
set(2)
|
set(2)
|
||||||
CHECK(unwrap(text) == "22")
|
CHECK(unwrap(text) == "22")
|
||||||
end
|
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
|
end
|
||||||
|
|
||||||
do CASE "Derive from updated"
|
do CASE "Derive from updated"
|
||||||
|
|
@ -704,7 +728,7 @@ TEST("create()", function()
|
||||||
}
|
}
|
||||||
|
|
||||||
wref.instance = instance
|
wref.instance = instance
|
||||||
wref.binding = next((state :: any).effects)
|
wref.binding = next((state :: any).__effects)
|
||||||
end
|
end
|
||||||
|
|
||||||
CHECK(wref.binding)
|
CHECK(wref.binding)
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue