fix: allow nested captures

This commit is contained in:
Richard 2024-06-09 20:39:17 -07:00
parent c5d4ef470b
commit b9d576c876
4 changed files with 38 additions and 19 deletions

View file

@ -28,4 +28,21 @@ return function()
expect(deps[a]).to.be.ok() expect(deps[a]).to.be.ok()
expect(result).to.equal(1) expect(result).to.equal(1)
end) end)
it("allows nested captures", function()
local a = atom(1)
local b = atom(1)
local inner
local outer = store.capture(function()
inner = store.capture(function()
return b()
end)
return a()
end)
assert(outer[a], "outer did not capture a")
assert(outer[b], "outer did not capture b")
assert(inner[a] == nil, "inner captured a")
assert(inner[b], "inner did not capture b")
end)
end end

View file

@ -15,8 +15,8 @@ local function atom<T>(state: T, options: AtomOptions<T>?): Atom<T>
local function atom(...) local function atom(...)
if select("#", ...) == 0 then if select("#", ...) == 0 then
if store.capturing.current then for set in store.capturing do
store.capturing.current[atom] = true set[atom] = true
end end
return state return state

View file

@ -1,11 +1,11 @@
local types = require(script.Parent.types) local types = require(script.Parent.types)
type Atom<T> = types.Atom<T> type Atom<T> = types.Atom<T>
type Set<T> = types.Set<T>
type WeakMap<K, V> = types.WeakMap<K, V> type WeakMap<K, V> = types.WeakMap<K, V>
local listeners: WeakMap<Atom<any>, WeakMap<() -> (), unknown>> = setmetatable({}, { __mode = "k" }) local listeners: WeakMap<Atom<any>, WeakMap<() -> (), unknown>> = setmetatable({}, { __mode = "k" })
local capturing: Set<Set<Atom<any>>> = {}
local capturing: { current: { [Atom<any>]: true }? } = {} local batched: Set<() -> ()> = {}
local batched: { [() -> ()]: true } = {}
local batching = false local batching = false
--[=[ --[=[
@ -81,18 +81,16 @@ end
@param molecule The function to run. @param molecule The function to run.
@return A tuple containing the captured atoms and the result of the function. @return A tuple containing the captured atoms and the result of the function.
]=] ]=]
local function capture<T>(molecule: () -> T): ({ [Atom<any>]: true }, T) local function capture<T>(molecule: () -> T): (Set<Atom<any>>, T)
if listeners[molecule] then if listeners[molecule] then
return { [molecule] = true }, molecule() return { [molecule] = true }, molecule()
end end
local dependencies = {} local dependencies: Set<Atom<any>> = {}
local previous = capturing.current capturing[dependencies] = true
capturing.current = dependencies
local result = try(molecule, function() local result = try(molecule, function()
capturing.current = previous capturing[dependencies] = nil
end) end)
return dependencies, result return dependencies, result
@ -135,16 +133,18 @@ local function peek<T, U...>(callback: ((U...) -> T) | T, ...: U...): T
return callback return callback
end end
if not capturing.current then if not next(capturing) then
return callback(...) return callback(...)
end end
local previous = capturing.current local snapshot = table.clone(capturing)
capturing.current = nil table.clear(capturing)
local result = try(callback, function() local result = try(callback, function()
capturing.current = previous for set in snapshot do
capturing[set] = true
end
end, ...) end, ...)
return result return result
@ -157,7 +157,7 @@ end
@param listener The function to call when the atoms change. @param listener The function to call when the atoms change.
@param ref Optionally bind the lifetime of the listener to a value. @param ref Optionally bind the lifetime of the listener to a value.
]=] ]=]
local function connect(dependencies: { [Atom<any>]: true }, listener: () -> (), ref: unknown?) local function connect(dependencies: Set<Atom<any>>, listener: () -> (), ref: unknown?)
for atom in dependencies do for atom in dependencies do
listeners[atom][listener] = ref or true listeners[atom][listener] = ref or true
end end
@ -169,7 +169,7 @@ end
@param dependencies The atoms to stop listening to. @param dependencies The atoms to stop listening to.
@param listener The function to stop calling when the atoms change. @param listener The function to stop calling when the atoms change.
]=] ]=]
local function disconnect(dependencies: { [Atom<any>]: true }, listener: () -> ()) local function disconnect(dependencies: Set<Atom<any>>, listener: () -> ())
for atom in dependencies do for atom in dependencies do
listeners[atom][listener] = nil listeners[atom][listener] = nil
end end
@ -187,10 +187,10 @@ end
]=] ]=]
local function reconnect<T>( local function reconnect<T>(
molecule: Atom<T>, molecule: Atom<T>,
dependencies: { [Atom<any>]: true }, dependencies: Set<Atom<any>>,
listener: () -> (), listener: () -> (),
ref: unknown? ref: unknown?
): ({ [Atom<any>]: true }, T) ): (Set<Atom<any>>, T)
disconnect(dependencies, listener) disconnect(dependencies, listener)
local nextDependencies, state = capture(molecule) local nextDependencies, state = capture(molecule)
connect(nextDependencies, listener, ref) connect(nextDependencies, listener, ref)

View file

@ -1,3 +1,5 @@
export type Set<T> = { [T]: true }
export type WeakMap<K, V> = typeof(setmetatable({} :: { [K]: V }, { __mode = "k" })) export type WeakMap<K, V> = typeof(setmetatable({} :: { [K]: V }, { __mode = "k" }))
--[=[ --[=[