This commit is contained in:
aaron 2023-07-30 14:35:46 +01:00
parent 1aa74310f3
commit 676bbbcc00
15 changed files with 665 additions and 873 deletions

View file

@ -1,41 +0,0 @@
------------------------------------------------------------------------------------------
-- vide/Event.lua
------------------------------------------------------------------------------------------
if not game then script = (require :: any) "test/wrap-require" end
local memoize = require(script.Parent.memoize)
local bind = require(script.Parent.bind)
local graph = require(script.Parent.graph)
type State<T> = graph.State<T>
type MaybeState<T> = graph.MaybeState<T>
local wrapped = graph.wrapped
local Types = require(script.Parent.Types)
type Listener = (unknown) -> ()
local getEventSymbol = memoize(function(name: string): Types.Symbol<MaybeState<Listener>>
return {
priority = 2,
run = function(instance: Instance, listener: MaybeState<Listener>)
local event: RBXScriptSignal<...unknown> = (instance :: any)[name]
if type(listener) == "function" then
event:Connect(listener)
elseif wrapped(listener) then
bind.event(listener :: State<Listener>, instance, event)
else
error("Attempt to connect non-function to event", 2)
end
end
}
end)
local Event = table.freeze(setmetatable({}, {__index = function(_, index: string)
return getEventSymbol(index)
end})) :: any
return Event :: { [string]: unknown }

View file

@ -4,12 +4,57 @@
if not game then script = (require :: any) "test/wrap-require" end
local applyProperties = require(script.Parent.applyProperties)
local graph = require(script.Parent.graph)
type Node<T> = graph.Node<T>
local function apply<T>(instance: T & Instance)
return function(properties: { [any]: unknown }): T
return applyProperties(instance, properties)
local throw = require(script.Parent.throw)
local bind = require(script.Parent.bind)
local function recurse(instance: Instance, properties: { [unknown]: unknown }, event_buffer)
for property, value in properties do
if type(value) == "table" then
recurse(instance, value :: {}, event_buffer)
elseif type(property) == "string" then
if type(value) == "function" then
if typeof((instance :: any)[property] == "RBXScriptSignal") then
event_buffer[property] = value
else
bind.property(instance, property, value :: () -> ())
end
else
(instance :: any)[property] = value
end
elseif type(property) == "number" then
if type(value) == "function" then
bind.children(instance, value :: () -> { Instance })
else
(value :: Instance).Parent = instance
end
end
end
end
local function apply<T>(instance: T & Instance, properties: { [unknown]: unknown }): T
local parent: unknown = properties.Parent
if parent then properties.Parent = nil end
local event_buffer: { [string]: () -> () } = {} -- connect events after setting properties
recurse(instance, properties, event_buffer)
for event, fn in next, event_buffer do
(instance :: any)[event]:Connect(fn)
end
if parent then
if type(parent) == "function" then
error("cannot set parent to state")
else
instance.Parent = parent :: Instance
end
end
return instance
end
return apply

View file

@ -1,66 +0,0 @@
------------------------------------------------------------------------------------------
-- vide/applyProperties.lua
------------------------------------------------------------------------------------------
if not game then script = (require :: any) "test/wrap-require" end
local graph = require(script.Parent.graph)
type State<T> = graph.State<T>
local wrapped = graph.wrapped
local throw = require(script.Parent.throw)
local bind = require(script.Parent.bind)
local Types = require(script.Parent.Types)
local function applyProperty(instance: Instance, property: string, value: unknown)
end
local function applyProperties<T>(instance: T & Instance, properties: { [string|Types.Symbol<unknown> ]: unknown }): T
local parent: unknown = properties.Parent
if parent then properties.Parent = nil end
local eventBuffer: { [(Instance, () -> ()) -> ()]: () -> () } = {} -- connect events after setting properties
local postCreation: (Instance) -> ()? = nil -- buffer for post-creation callback
for property, value in next, properties do
if type(property) == "string" then
if wrapped(value) then
bind.property(value :: State<unknown>, instance, property)
else
(instance :: any)[property] = value
end
elseif type(property) == "table" then
local priority = property.priority
if priority == 1 then
property.run(instance, value)
elseif priority == 2 then
eventBuffer[property.run] = value :: () -> ()
elseif priority == 3 then
assert(not postCreation)
postCreation = value :: () -> ()
else
error("invalid priority")
end
else throw(`Invalid property { tostring(property) }, expected string or symbol`) end
end
for fn, v in next, eventBuffer do
fn(instance, v)
end
if parent then
applyProperty(instance, "Parent", parent)
if wrapped(parent) then
bind.parent(parent :: State<Instance?>, instance)
else
instance.Parent = parent :: Instance
end
end
if postCreation then postCreation(instance) end
return instance
end
return applyProperties

View file

@ -11,9 +11,10 @@ end
local graph = require(script.Parent.graph)
type State<T> = graph.State<T>
type Node<T> = graph.Node<T>
local get = graph.get
local setEffect = graph.setEffect
local set_effect = graph.set_effect
local capture = graph.capture
local throw = require(script.Parent.throw)
local flags = require(script.Parent.flags)
@ -22,15 +23,18 @@ local hold: { Instance? } = {}
local weak: { Instance? } = setmetatable({}, { __mode = "v" }) :: any
local bindcount = 0
local srcs do
local src1 = debug.info(1, "s")
local srctrunc = string.sub(src1, 1, #src1-4)
srcs = {
src1,
srctrunc .. "applyProperties",
srctrunc .. "apply",
srctrunc .. "create",
srctrunc .. "apply"
}
end
@ -43,18 +47,22 @@ local function traceback() -- ensures trace begins outside of any vide library f
return debug.traceback("", s)
end
function setup(state: State<any>, instance: Instance, updateInstance: (Instance) -> ())
function setup(instance: Instance, deriver: () -> unknown, setter: (Instance) -> ())
if flags.strict then
local fn = updateInstance
local fn = setter
local trace = traceback()
updateInstance = function(instance)
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
end
end
updateInstance(instance)
setEffect(state, updateInstance, instance)
local nodes = table.clone((capture(setter :: (Instance) -> unknown, instance)))
for _, node in next, nodes do
set_effect(node, setter, instance)
end
bindcount += 1
local key = bindcount
@ -62,7 +70,7 @@ function setup(state: State<any>, instance: Instance, updateInstance: (Instance)
weak[key] = instance
local function ref()
local _ = state -- prevent gc of state while instance exists
local _ = nodes -- prevent gc of state while instance exists
local instance = weak[key] :: Instance
hold[key] = instance.Parent and instance or nil -- prevent gc of instance while parented
end
@ -71,28 +79,28 @@ function setup(state: State<any>, instance: Instance, updateInstance: (Instance)
instance:GetPropertyChangedSignal("Parent"):Connect(ref)
end
local function bindProperty(state: State<unknown>, instance_STRONG: Instance, property: string)
setup(state, instance_STRONG, function(instance)
(instance :: any)[property] = get(state)
local function bind_property(instance: Instance, property: string, fn: () -> unknown)
setup(instance, fn, function(instance_weak: any)
instance_weak[property] = fn()
end)
end
local function bindParent(state: State<Instance?>, instance_STRONG)
instance_STRONG.Destroying:Connect(function()
instance_STRONG = nil :: any -- allow gc when destroyed
local function bind_parent(instance: Instance, fn: () -> Instance?)
instance.Destroying:Connect(function()
instance= nil :: any -- allow gc when destroyed
end)
setup(state, instance_STRONG, function(instance)
local _ = instance_STRONG -- state will strongly reference instance when parent is bound
instance.Parent = get(state)
setup(instance, fn, function(instance)
local _ = instance -- state will strongly reference instance when parent is bound
instance.Parent = fn()
end)
end
local function bindChildren(state: State<{ Instance }?>, parent_STRONG: Instance)
local function bind_children(parent: Instance, fn: () -> { Instance })
local currentChildrenSet: { [Instance]: true } = {} -- cache of all children parented before update
local newChildrenSet: { [Instance]: true } = {} -- cache of all children parented after update
setup(state, parent_STRONG, function(parent)
local newChildren = get(state) -- all (and only) children that should be parented after this update
setup(parent, fn, function(parent_weak)
local newChildren = fn() -- all (and only) children that should be parented after this update
if newChildren and type(newChildren) ~= "table" then
throw(`Cannot parent instance of type { type(newChildren) } `)
end
@ -101,7 +109,7 @@ local function bindChildren(state: State<{ Instance }?>, parent_STRONG: Instance
for _, child in next, newChildren do
newChildrenSet[child] = true -- record child set from this update
if not currentChildrenSet[child] then
child.Parent = parent -- if child wasn't already parented then parent it
child.Parent = parent_weak -- if child wasn't already parented then parent it
else
currentChildrenSet[child] = nil -- remove child from cache if it was already in cache
end
@ -117,23 +125,8 @@ local function bindChildren(state: State<{ Instance }?>, parent_STRONG: Instance
end)
end
local function bindEvent(state: State<() -> ()?>, instance_STRONG: Instance, event: RBXScriptSignal)
local current: RBXScriptConnection? = nil
setup(state, instance_STRONG, function(instance)
if current then
current:Disconnect()
current = nil
end
local listener = get(state)
if listener then
current = event:Connect(listener)
end
end)
end
return {
property = bindProperty,
parent = bindParent,
children = bindChildren,
event = bindEvent
property = bind_property,
parent = bind_parent,
children = bind_children,
}

View file

@ -9,7 +9,7 @@ end
local throw = require(script.Parent.throw)
local defaults = require(script.Parent.defaults)
local applyProperties = require(script.Parent.applyProperties)
local apply = require(script.Parent.apply)
local memoize = require(script.Parent.memoize)
local function createInstance(className: string)
@ -24,7 +24,7 @@ local function createInstance(className: string)
end
return function(properties: { [any]: unknown }): Instance
return applyProperties(instance:Clone(), properties)
return apply(instance:Clone(), properties)
end
end; createInstance = memoize(createInstance)
@ -32,7 +32,7 @@ local function cloneInstance(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
return applyProperties(clone, properties)
return apply(clone, properties)
end
end

View file

@ -5,28 +5,28 @@
if not game then script = (require :: any) "test/wrap-require" end
local graph = require(script.Parent.graph)
type State<T> = graph.State<T>
type Unwrapper = graph.Unwrapper
local create = graph.create
local captureAndLink = graph.captureAndLink
local get = graph.get
local capture_and_link = graph.capture_and_link
local function derive<T>(deriveValue: (Unwrapper) -> T, cleanup: (T) -> ()?): State<T>
local function derive<T>(fn: () -> T, cleanup: (T) -> ()?): () -> T
local node = create((nil :: any) :: T)
if cleanup then
local fn = deriveValue
local f = fn
local last: T? = nil
deriveValue = function(from)
fn = function()
if last ~= nil then cleanup(last) end
last = fn(from)
last = f()
return last :: T
end
end
local value: T = captureAndLink(node, deriveValue)
rawset(node, "__cache", value)
node.cache = capture_and_link(node, fn)
return node :: State<T>
return function()
return get(node)
end
end
return derive

View file

@ -6,49 +6,29 @@ if not game then script = (require :: any) "test/wrap-require" end
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
}, {} :: {
__concat: (any, any) -> any,
__add: (any, any) -> any,
__sub: (any, any) -> any,
__mul: (any, any) -> any,
__div: (any, any) -> any,
--__pow
--__mod
--__unm
--__eq: (unknown, unknown) -> State<boolean>
--__lt
--__le
}
))
export type Node<T> = {
cache: T,
derive: () -> T,
effects: { [(unknown) -> ()]: unknown }, -- weak values
children: { Node<T> } | false -- weak values
}
export type MaybeState<T> = State<T> | T
export type Unwrapper = <T>(T) -> T
local reff = false
local refs = {} :: { Node<unknown> }
local WEAK_VALUES_RESIZABLE = { __mode = "vs" }
local EVALUATION_ERR = "error while evaluating state:\n\n"
local EVALUATION_ERR = "error while evaluating node:\n\n"
local State = {}
setmetatable(refs, WEAK_VALUES_RESIZABLE)
local function wrapped(value: any): boolean
return getmetatable(value) == State
end
local unwrap: <T>(T) -> T;
local checkForYield do
local check_for_yield do
local t = { __mode = "kv" }
setmetatable(t, t)
checkForYield = function(fn: (Unwrapper) -> ())
check_for_yield = function<T..., U...>(fn: (T...) -> (), ...: U...)
local args = { ... }
t.__unm = function()
fn(unwrap)
fn(unpack(args))
end
local ok, err = pcall(function()
return -t :: any
@ -56,7 +36,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 node in watcher", 3)
else
error(EVALUATION_ERR..err, 3)
end
@ -64,199 +44,100 @@ local checkForYield do
end
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
else
state.__effects[fn :: () -> ()] = key
end
local function set_effect<T>(node: Node<unknown>, fn: (T) -> (), key: T)
node.effects[fn :: () -> ()] = key
end
local function runEffects(state: State<unknown>)
if state.__effects then
for effect, key in next, state.__effects do
if flags.strict then effect(key) end
effect(key)
end
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
end
-- retrieves a state's cached value
-- retrieves a node'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 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, 0) end
end
return rawget(state :: any, "__cache")
local function get<T>(node: Node<T>): T
if reff then table.insert(refs, node) end
return node.cache
end
-- utility function for retrieving a value from state and allowing passthrough of non-state
unwrap = function<T>(value: MaybeState<T>): T
if wrapped(value) then
return get(value :: State<T>)
local function set_child(parent: Node<unknown>, child: Node<unknown>)
if parent.children then
table.insert(parent.children, child)
else
return value :: T
parent.children = { child }
setmetatable(parent.children :: any, WEAK_VALUES_RESIZABLE)
end
end
local function addChild(parent: State<unknown>, child: State<unknown>)
if parent.__children then
table.insert(parent.__children, child)
else
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
update(child)
end
-- runs node effects, recalculates descendants and runs descendant effects
local function update(node: Node<unknown>)
run_effects(node)
if node.children then
for _, child in node.children do
if flags.strict then check_for_yield(child.derive) end
child.cache = child.derive()
update(child)
end
end
end
-- sets a state's cached value and updates all descendants
local function set<T>(state: State<T>, value: T)
state.__cache = value
update(state)
-- sets a node's cached value and updates all descendants
local function set<T>(node: Node<T>, value: T)
node.cache = value
update(node)
end
-- links two states as parent-child
local function link(parent: State<unknown>, child: State<unknown>, derive: () -> unknown)
child.__derive = derive
addChild(parent, child)
-- links two nodes as parent-child with a function to compute a new value for child
local function link<T>(parent: Node<unknown>, child: Node<T>, derive: () -> T)
child.derive = derive
set_child(parent, child)
end
-- detect what states were referenced in the given callback and returns them in an array
local function capture<T>(callback: (Unwrapper) -> T): ({ State<unknown> }, T)
if flags.strict then checkForYield(callback) 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)
if flags.strict then check_for_yield(fn, arg) end
local states = table.create(2)
table.clear(refs)
reff = true
local ok: boolean, result: T|string = pcall(callback, function<T>(value: MaybeState<T>): T
if wrapped(value) then
table.insert(states, value :: State<T>)
return get(value :: State<T>)
else
return value :: T
end
end)
local ok: boolean, result: T|string = pcall(fn, arg)
reff = false
if not ok then error("error while detecting watcher: " .. result :: string, 0) end
return states, result :: T
return refs, result :: T
end
-- captures and links any detected states
local function captureAndLink<T>(child: State<T>, callback: (Unwrapper) -> T): T
local states, value = capture(callback)
-- captures and links any detected nodes
local function capture_and_link<T>(child: Node<T>, fn: () -> T): T
local nodes, value = capture(fn, nil)
child.__derive = callback
for _, parent: State<unknown> in next, states do
addChild(parent, child)
child.derive = fn
for _, parent: Node<unknown> in next, nodes do
set_child(parent, child)
end
return value :: T
end
local create: <T>(value: T) -> State<T>
-- factory function for creating operator overloads for shorthands to derive state
local function overload(op: (unknown, unknown) -> unknown): (any, any) -> any
return function(a: MaybeState<unknown>, b: MaybeState<unknown>): State<unknown>
local derived: State<unknown> = create(nil :: any)
local aIsState = wrapped(a)
local bIsState = wrapped(b)
if aIsState and bIsState then
local function derive() return op(get(a :: State<unknown>), get(b :: State<unknown>)) end
link(a :: State<unknown>, derived, derive)
link(b :: State<unknown>, derived, derive)
elseif aIsState then
link(a :: State<unknown>, derived, function() return op(get(a :: State<unknown>), b) end)
else--if bIsState then
link(b :: State<unknown>, derived, function() return op(a, get(b :: State<unknown>)) end)
end
derived.__updated = true
return derived
end
end
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.__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
}, State)
local function create<T>(value: T): Node<T>
return {
cache = value,
derive = function() return nil :: any end,
effects = setmetatable({}, WEAK_VALUES_RESIZABLE) :: any,
children = false :: false
}
end
return table.freeze {
setEffect = setEffect,
set_effect = set_effect,
get = get,
set = set,
unwrap = unwrap,
link = link,
capture = capture,
captureAndLink = captureAndLink,
wrapped = wrapped,
capture_and_link = capture_and_link,
create = create,
}

View file

@ -6,20 +6,13 @@
if not game then script = (require :: any) "test/wrap-require" end
local create = require(script.create)
local apply = require(script.apply)
local wrap = require(script.wrap)
local watch = require(script.watch)
local derive = require(script.derive)
local map = require(script.map)
local unwrap = require(script.unwrap)
local wrapped = require(script.wrapped)
local Layout = require(script.Layout)
local Children = require(script.Children)
local Event = require(script.Event)
local Changed = require(script.Change)
local Created = require(script.Created)
-- local Changed = require(script.Change)
-- local Created = require(script.Created)
local spring, updateSprings = require(script.spring)()
@ -32,25 +25,20 @@ type Setter<T> = ( (new: T, force: true?) -> T ) & ( (update: (old: T) -> T, for
local vide = {
-- core
create = create,
apply = apply,
wrap = (wrap :: any) :: <T>(value: T?) -> (T, Setter<T>),
derive = (derive :: any) :: <T>(deriver: (from: <U>(U) -> U) -> T, cleanup: (T) -> ()?) -> T,
map = (map :: any) :: ( <V>(input: number, transform: (number) -> V, cleanup: (V) -> ()?) -> Map<number, V> ) & ( <K, VI, VO>(input: Map<K, VI>, transform: (K, VI) -> VO, cleanup: (VO) -> ()?) -> Map<K, VO> ),
watch = watch :: ((Unwrapper) -> ()) -> () -> (),
-- util
unwrap = unwrap,
wrapped = wrapped,
wrap = wrap,
derive = derive,
map = map,
watch = watch,
-- animations
spring = (spring :: any) :: <T>(input: T, period: number, damping: number?) -> T,
spring = spring,
-- symbols
Event = Event,
Changed = Changed,
Layout = Layout,
Children = Children,
Created = Created,
-- Event = Event,
-- Changed = Changed,
-- Layout = Layout,
-- Children = Children,
-- Created = Created,
-- flags
strict = (nil :: any) :: boolean,

View file

@ -25,13 +25,9 @@ if not game then script = (require :: any) "test/wrap-require" end
local throw = require(script.Parent.throw)
local graph = require(script.Parent.graph)
type State<T> = graph.State<T>
type MaybeState<T> = graph.MaybeState<T>
local create = graph.create
local get = graph.get
local set = graph.set
local unwrap = graph.unwrap
local wrapped = graph.wrapped
type Animatable = number | CFrame | Color3 | UDim | UDim2 | Vector2 | Vector3

View file

@ -1,9 +0,0 @@
--------------------------------------------------------------------------------------------------------------
-- vide/unwrap.lua
--------------------------------------------------------------------------------------------------------------
if not game then script = (require :: any) "test/wrap-require" end
local graph = require(script.Parent.graph)
return graph.unwrap

View file

@ -5,27 +5,27 @@
if not game then script = (require :: any) "test/wrap-require" end
local graph = require(script.Parent.graph)
type State<T> = graph.State<T>
type Unwrapper = graph.Unwrapper
local setEffect = graph.setEffect
local set_effect = graph.set_effect
local capture = graph.capture
local unwrap = graph.unwrap
local function watch(effect: (Unwrapper) -> ()): () -> ()
local states, cleanup = capture(effect :: () -> (() -> ()?))
local function watch(effect: () -> ()): () -> ()
-- todo: call cleanup on initial debug call when strict
local nodes, cleanup = capture(effect :: () -> (() -> ()?))
nodes = table.clone(nodes)
local function fn()
if cleanup then cleanup(); cleanup = nil end
cleanup = effect(unwrap)
cleanup = effect()
end
for _, state in next, states do
setEffect(state, fn, true)
for _, node in next, nodes do
set_effect(node, fn, true)
end
local function unwatch()
for _, state in next, states do
setEffect(state, fn, nil)
for _, node in next, nodes do
set_effect(node, fn, nil)
end
if cleanup then cleanup(); cleanup = nil end
end

View file

@ -2,41 +2,31 @@
-- vide/wrap.lua
------------------------------------------------------------------------------------------
if not game then script = (require :: any) "test/wrap-require" end
if not game then script = require "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 set = graph.set
local wrapped = graph.wrapped
local throw = require(script.Parent.throw)
local flags = require(script.Parent.flags)
type Setter<T> = (value: MaybeState<T> | (MaybeState<T>) -> MaybeState<T>, force: boolean?) -> T
type State<T> = (() -> T) & ((T) -> T)
local function wrap<T>(value: MaybeState<T>?): (State<T>, Setter<T>)
local state = create(if wrapped(value) then get(value :: State<T>) else value :: T)
local function wrap<T>(value: T | () -> T): State<T>
if type(value) == "function" then value = value() end
local function setter(vi: MaybeState<T> | (MaybeState<T>) -> MaybeState<T>, force: boolean?): T
if type(vi) == "function" then
vi = vi(get(state))
end
local node = create(value :: T)
local v = if wrapped(vi) then get(vi :: State<T>) else vi :: T
return function(...): T
if select("#", ...) == 0 then return get(node) end
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")
end
local v = ... :: T
if node.cache == v and type(v) ~= "table" then return v end
set(node, v)
return v
end
return state, setter
end
return wrap

View file

@ -1,9 +0,0 @@
------------------------------------------------------------------------------------------
-- vide/wrapped.lua
------------------------------------------------------------------------------------------
if not game then script = (require :: any) "test/wrap-require" end
local graph = require(script.Parent.graph)
return graph.wrapped