mirror of
https://github.com/littensy/charm.git
synced 2026-10-20 17:51:37 +00:00
feat: add deep freeze flag, misc refactors
This commit is contained in:
parent
708bc55de7
commit
77c98ca94a
22 changed files with 185 additions and 337 deletions
40
packages/charm/src/__tests__/core/atom.spec.luau
Normal file
40
packages/charm/src/__tests__/core/atom.spec.luau
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
local core = require("../../core")
|
||||
local suite = require("../../../../../test/suite")
|
||||
|
||||
local atom = core.atom
|
||||
local effect = core.effect
|
||||
local test = suite.test
|
||||
|
||||
test("should accept equality function", function()
|
||||
local calls = {}
|
||||
local src = atom(0, function(current, incoming)
|
||||
return current == incoming or incoming % 2 == 1
|
||||
end)
|
||||
|
||||
effect(function()
|
||||
table.insert(calls, src())
|
||||
end)
|
||||
|
||||
src(2)
|
||||
src(3)
|
||||
assert(src() == 2, "should skip odd value")
|
||||
|
||||
src(4)
|
||||
src(5)
|
||||
assert(src() == 4, "should skip odd value")
|
||||
assert(table.concat(calls) == "024", `should only call on even updates, got {table.concat(calls)}`)
|
||||
end)
|
||||
|
||||
test("should enforce immutability", function()
|
||||
local src = atom({ nested = {} })
|
||||
assert(not pcall(function()
|
||||
src().nested.a = 1
|
||||
end), "should not allow mutation of initial value")
|
||||
|
||||
src({ nested = { a = 1 } })
|
||||
assert(not pcall(function()
|
||||
src().nested.a = 2
|
||||
end), "should not allow mutation of new value")
|
||||
end)
|
||||
|
||||
return {}
|
||||
|
|
@ -1,8 +1,9 @@
|
|||
local core = require("../../core")
|
||||
local suite = require("../../../../../test/suite")
|
||||
|
||||
local computed = core.computed
|
||||
local atom = core.atom
|
||||
local computed = core.computed
|
||||
local effect = core.effect
|
||||
local test = suite.test
|
||||
|
||||
test("should correctly propagate changes through computed core", function()
|
||||
|
|
@ -80,4 +81,27 @@ test("should not update if the atom value is reverted", function()
|
|||
assert(times == 1, "computed ran when it should not")
|
||||
end)
|
||||
|
||||
test("should accept equality function", function()
|
||||
local calls = {}
|
||||
local src = atom(0)
|
||||
local c1 = computed(function()
|
||||
return src()
|
||||
end, function(current, incoming)
|
||||
return current == incoming or incoming % 2 == 1
|
||||
end)
|
||||
|
||||
effect(function()
|
||||
table.insert(calls, c1())
|
||||
end)
|
||||
|
||||
src(2)
|
||||
src(3)
|
||||
assert(c1() == 2, "should skip odd value")
|
||||
|
||||
src(4)
|
||||
src(5)
|
||||
assert(c1() == 4, "should skip odd value")
|
||||
assert(table.concat(calls) == "024", `should only call on even updates, got {table.concat(calls)}`)
|
||||
end)
|
||||
|
||||
return {}
|
||||
|
|
|
|||
|
|
@ -8,15 +8,11 @@ local effectScope = core.effectScope
|
|||
local test = suite.test
|
||||
|
||||
--- Creates a weak reference to a value.
|
||||
--- @return Function to dereference the value.
|
||||
local function weakRef<T>(value: T): () -> T?
|
||||
local ref = setmetatable({ value = value }, { __mode = "v" })
|
||||
return function()
|
||||
return ref.value
|
||||
end
|
||||
local function weakRef<T>(value: T): { value: T? }
|
||||
return setmetatable({ value = value }, { __mode = "v" }) :: any
|
||||
end
|
||||
|
||||
--- Triggers garbage collection by allocating excessive memory.
|
||||
--- Triggers garbage collection by creating a large number of tables.
|
||||
local function gc()
|
||||
for _ = 1, 1000 do
|
||||
local _ = table.create(100, 0)
|
||||
|
|
@ -24,14 +20,14 @@ local function gc()
|
|||
end
|
||||
|
||||
--- In alien-signals, a computed value unlinks from dependencies when its last
|
||||
--- parent scope is disposed. Calling a computed value outside of a scope links
|
||||
--- it to dependencies, but the dependencies hold strong references to the
|
||||
--- computed value, so it won't GC until called in a scope and disposed.
|
||||
--- parent scope is disposed. However, calling a computed value outside of a
|
||||
--- scope still links it to dependencies, which act as strong references.
|
||||
--- Computed values called in this way won't GC until unlinked from a scope.
|
||||
---
|
||||
--- This function releases a computed value by linking it to a scope and then
|
||||
--- immediately closing it, causing alien-signals to unlink the computed value
|
||||
--- and allow it to be garbage collected.
|
||||
local function release(getter: () -> ())
|
||||
--- from its dependencies and allow it to be garbage collected.
|
||||
local function unlink(getter: () -> ())
|
||||
effectScope(function()
|
||||
getter()
|
||||
end)()
|
||||
|
|
@ -39,18 +35,17 @@ end
|
|||
|
||||
test("should release computed cache", function()
|
||||
local srcRef = weakRef({})
|
||||
local src = atom(srcRef())
|
||||
|
||||
local src = atom(srcRef.value)
|
||||
local c = computed(function()
|
||||
return src()
|
||||
end)
|
||||
|
||||
c() -- cache src value
|
||||
src(nil) -- release value
|
||||
c = release(c) -- release computed
|
||||
c = unlink(c) -- release computed
|
||||
|
||||
gc()
|
||||
assert(srcRef() == nil, "should release computed cache")
|
||||
assert(srcRef.value == nil, "should release computed cache")
|
||||
end)
|
||||
|
||||
test("should not release effect for atom", function()
|
||||
|
|
@ -61,7 +56,6 @@ test("should not release effect for atom", function()
|
|||
effectCalls += 1
|
||||
src()
|
||||
end)
|
||||
|
||||
assert(effectCalls == 1, "should call effect once")
|
||||
|
||||
gc()
|
||||
|
|
|
|||
|
|
@ -1,113 +0,0 @@
|
|||
local core = require("../../core")
|
||||
local suite = require("../../../../../test/suite")
|
||||
|
||||
local computed = core.computed
|
||||
local effect = core.effect
|
||||
local setCurrentSub = core.setCurrentSub
|
||||
local atom = core.atom
|
||||
local test = suite.test
|
||||
|
||||
type ReactionOptions<T = unknown> = {
|
||||
fireImmediately: boolean?,
|
||||
equals: ((a: T, b: T?) -> boolean)?,
|
||||
onError: ((error: unknown) -> ())?,
|
||||
scheduler: ((fn: () -> ()) -> ())?,
|
||||
once: boolean?,
|
||||
}
|
||||
|
||||
local function untracked<T>(callback: () -> T): T
|
||||
local currentSub = setCurrentSub(nil)
|
||||
local success, result = pcall(callback)
|
||||
setCurrentSub(currentSub)
|
||||
if not success then
|
||||
error(result)
|
||||
end
|
||||
return result
|
||||
end
|
||||
|
||||
local function reaction<T>(
|
||||
dataFn: () -> T,
|
||||
effectFn: (newValue: T, oldValue: T?) -> (),
|
||||
options: ReactionOptions<T>?
|
||||
): () -> ()
|
||||
local options: ReactionOptions<T> = options or {}
|
||||
local scheduler: (() -> ()) -> () = options.scheduler or function(fn)
|
||||
return fn()
|
||||
end
|
||||
local equals: (T, T?) -> boolean = options.equals or function(a, b)
|
||||
return a == b
|
||||
end
|
||||
local onError = options.onError
|
||||
local once = options.once or false
|
||||
local fireImmediately = options.fireImmediately or false
|
||||
|
||||
local prevValue: T?
|
||||
local version = 0
|
||||
|
||||
local tracked = computed(function()
|
||||
local success, result = pcall(dataFn)
|
||||
if not success then
|
||||
untracked(function()
|
||||
if onError then
|
||||
onError(result)
|
||||
end
|
||||
return nil
|
||||
end)
|
||||
return prevValue
|
||||
end
|
||||
return result
|
||||
end)
|
||||
|
||||
local dispose
|
||||
dispose = effect(function()
|
||||
local current = tracked()
|
||||
if not fireImmediately and version == 0 then
|
||||
prevValue = current
|
||||
end
|
||||
version += 1
|
||||
if equals(current :: T, prevValue) then
|
||||
return
|
||||
end
|
||||
local oldValue = prevValue
|
||||
prevValue = current
|
||||
untracked(function()
|
||||
scheduler(function()
|
||||
local success = pcall(effectFn, current :: T, oldValue)
|
||||
if not success and onError then
|
||||
onError(error)
|
||||
end
|
||||
if once then
|
||||
if (fireImmediately and version > 1) or (not fireImmediately and version > 0) then
|
||||
dispose()
|
||||
end
|
||||
end
|
||||
end)
|
||||
return nil
|
||||
end)
|
||||
end)
|
||||
|
||||
return dispose
|
||||
end
|
||||
|
||||
test("#48", function()
|
||||
local source = atom(0)
|
||||
local disposeInner: () -> ()
|
||||
|
||||
reaction(function()
|
||||
return source()
|
||||
end, function(val)
|
||||
if val == 1 then
|
||||
disposeInner = reaction(function()
|
||||
return source()
|
||||
end, function() end)
|
||||
elseif val == 2 then
|
||||
disposeInner()
|
||||
end
|
||||
end)
|
||||
|
||||
source(1)
|
||||
source(2)
|
||||
source(3)
|
||||
end)
|
||||
|
||||
return {}
|
||||
|
|
@ -1,23 +0,0 @@
|
|||
local core = require("../../core")
|
||||
local suite = require("../../../../../test/suite")
|
||||
|
||||
local computed = core.computed
|
||||
local setCurrentSub = core.setCurrentSub
|
||||
local atom = core.atom
|
||||
local test = suite.test
|
||||
|
||||
test("should pause tracking", function()
|
||||
local src = atom(0)
|
||||
local c = computed(function()
|
||||
local currentSub = setCurrentSub(nil)
|
||||
local value = src()
|
||||
setCurrentSub(currentSub)
|
||||
return value
|
||||
end)
|
||||
assert(c() == 0, "should not track src")
|
||||
|
||||
src(1)
|
||||
assert(c() == 0, "should not track src")
|
||||
end)
|
||||
|
||||
return {}
|
||||
|
|
@ -98,4 +98,21 @@ test("should track inner subs", function()
|
|||
assert(table.concat(order) == "", `bad order: {table.concat(order)}`)
|
||||
end)
|
||||
|
||||
test("should skip redundant updates", function()
|
||||
local order = {}
|
||||
local a = atom(0)
|
||||
|
||||
listen(function(): "even" | "odd"
|
||||
return if a() % 2 == 0 then "even" else "odd"
|
||||
end, function(value)
|
||||
table.insert(order, value)
|
||||
end)
|
||||
|
||||
a(1)
|
||||
a(3)
|
||||
a(4)
|
||||
a(6)
|
||||
assert(table.concat(order) == "evenoddeven", `bad order: {table.concat(order)}`)
|
||||
end)
|
||||
|
||||
return {}
|
||||
|
|
|
|||
|
|
@ -1,107 +0,0 @@
|
|||
local Charm = require("../")
|
||||
local suite = require("../../../../test/suite")
|
||||
|
||||
local atom = Charm.atom
|
||||
local computed = Charm.computed
|
||||
local effect = Charm.effect
|
||||
local onCleanup = Charm.onCleanup
|
||||
local peek = Charm.peek
|
||||
local test = suite.test
|
||||
|
||||
test("should not run detached", function()
|
||||
local a = atom(1)
|
||||
local spyCalls = 0
|
||||
|
||||
effect(function()
|
||||
a()
|
||||
peek(function()
|
||||
onCleanup(function()
|
||||
spyCalls += 1
|
||||
end)
|
||||
end)
|
||||
end)
|
||||
|
||||
assert(spyCalls == 0, "should not be called yet")
|
||||
a(2)
|
||||
assert(spyCalls == 1, "should be called once")
|
||||
a(3)
|
||||
assert(spyCalls == 2, "should be called twice")
|
||||
end)
|
||||
|
||||
test("should block tracking inside effects", function()
|
||||
local a = atom(1)
|
||||
local b = atom(2)
|
||||
local spyCalls = 0
|
||||
|
||||
effect(function()
|
||||
peek(function()
|
||||
spyCalls += 1
|
||||
return a() + b()
|
||||
end)
|
||||
end)
|
||||
assert(spyCalls == 1, "should be called once")
|
||||
|
||||
a(10)
|
||||
b(20)
|
||||
assert(spyCalls == 1, "should be called once")
|
||||
end)
|
||||
|
||||
test("should block tracking even when run inside effect run inside peek", function()
|
||||
local s = atom(1)
|
||||
local spyCalls = 0
|
||||
|
||||
peek(function()
|
||||
effect(function()
|
||||
peek(function()
|
||||
spyCalls += 1
|
||||
return s()
|
||||
end)
|
||||
end)
|
||||
end)
|
||||
assert(spyCalls == 1, "should be called once")
|
||||
|
||||
s(2)
|
||||
assert(spyCalls == 1, "should be called once")
|
||||
end)
|
||||
|
||||
test("should not cause atom assignments throw", function()
|
||||
local a = atom(1)
|
||||
local aChangedTime = atom(0)
|
||||
|
||||
local dispose = effect(function()
|
||||
a()
|
||||
peek(function()
|
||||
aChangedTime(aChangedTime() + 1)
|
||||
end)
|
||||
end)
|
||||
|
||||
a(2)
|
||||
assert(aChangedTime() == 2, "aChangedTime should be 2")
|
||||
a(3)
|
||||
assert(aChangedTime() == 3, "aChangedTime should be 3")
|
||||
|
||||
dispose()
|
||||
end)
|
||||
|
||||
test("should block tracking inside computed atoms", function()
|
||||
local a = atom(1)
|
||||
local b = atom(2)
|
||||
local spyCalls = 0
|
||||
local c = computed(function()
|
||||
return peek(function()
|
||||
spyCalls += 1
|
||||
return a() + b()
|
||||
end)
|
||||
end)
|
||||
|
||||
assert(spyCalls == 0, "should not be called yet")
|
||||
assert(c() == 3, "c should be 3")
|
||||
a(10)
|
||||
c()
|
||||
b(20)
|
||||
c()
|
||||
assert(spyCalls == 1, "should be called once")
|
||||
assert(c() == 3, "c should be 3")
|
||||
end)
|
||||
|
||||
return {}
|
||||
|
|
@ -1,6 +1,9 @@
|
|||
local suite = require("../../../../test/suite")
|
||||
|
||||
suite.describe("core", function()
|
||||
suite.describe("atom", function()
|
||||
require("./core/atom.spec")
|
||||
end)
|
||||
suite.describe("computed", function()
|
||||
require("./core/computed.spec")
|
||||
end)
|
||||
|
|
@ -10,18 +13,12 @@ suite.describe("core", function()
|
|||
suite.describe("effectScope", function()
|
||||
require("./core/effectScope.spec")
|
||||
end)
|
||||
suite.describe("issue_48", function()
|
||||
require("./core/issue_48.spec")
|
||||
end)
|
||||
suite.describe("gc", function()
|
||||
require("./core/gc.spec")
|
||||
end)
|
||||
suite.describe("topology", function()
|
||||
require("./core/topology.spec")
|
||||
end)
|
||||
suite.describe("untrack", function()
|
||||
require("./core/untrack.spec")
|
||||
end)
|
||||
end)
|
||||
|
||||
suite.describe("listen", function()
|
||||
|
|
|
|||
|
|
@ -103,8 +103,6 @@ test("should track inner subs", function()
|
|||
end)
|
||||
end)
|
||||
|
||||
assert(table.concat(order) == "", "should not run immediately")
|
||||
|
||||
a(a() + 1)
|
||||
assert(table.concat(order) == "123", `bad order: {table.concat(order)}`)
|
||||
|
||||
|
|
@ -118,4 +116,21 @@ test("should track inner subs", function()
|
|||
assert(table.concat(order) == "", `bad order: {table.concat(order)}`)
|
||||
end)
|
||||
|
||||
test("should skip redundant updates", function()
|
||||
local order = {}
|
||||
local a = atom(0)
|
||||
|
||||
subscribe(function(): "even" | "odd"
|
||||
return if a() % 2 == 0 then "even" else "odd"
|
||||
end, function(value)
|
||||
table.insert(order, value)
|
||||
end)
|
||||
|
||||
a(1)
|
||||
a(3)
|
||||
a(4)
|
||||
a(6)
|
||||
assert(table.concat(order) == "oddeven", `bad order: {table.concat(order)}`)
|
||||
end)
|
||||
|
||||
return {}
|
||||
|
|
|
|||
|
|
@ -5,27 +5,28 @@ local atom = Charm.atom
|
|||
local computed = Charm.computed
|
||||
local effect = Charm.effect
|
||||
local untracked = Charm.untracked
|
||||
local onCleanup = Charm.onCleanup
|
||||
local test = suite.test
|
||||
|
||||
test("should run detached", function()
|
||||
test("should not run detached", function()
|
||||
local a = atom(1)
|
||||
local spyCalls = 0
|
||||
|
||||
effect(function()
|
||||
a()
|
||||
untracked(function()
|
||||
onCleanup(function()
|
||||
spyCalls += 1
|
||||
end, true)
|
||||
effect(function()
|
||||
return function()
|
||||
spyCalls += 1
|
||||
end
|
||||
end)
|
||||
end)
|
||||
end)
|
||||
|
||||
assert(spyCalls == 0, "should not be called")
|
||||
assert(spyCalls == 0, "should not be called yet")
|
||||
a(2)
|
||||
assert(spyCalls == 0, "rerun should not call cleanup")
|
||||
assert(spyCalls == 1, "should be called once")
|
||||
a(3)
|
||||
assert(spyCalls == 0, "rerun should not call cleanup")
|
||||
assert(spyCalls == 2, "should be called twice")
|
||||
end)
|
||||
|
||||
test("should block tracking inside effects", function()
|
||||
|
|
|
|||
|
|
@ -1,3 +1,7 @@
|
|||
-- Adapted from Alien Signals
|
||||
-- https://github.com/stackblitz/alien-signals
|
||||
-- https://github.com/Nicell/alien-signals-luau
|
||||
|
||||
local config = require("./flags")
|
||||
local system = require("./system")
|
||||
local utils = require("./utils")
|
||||
|
|
@ -8,6 +12,7 @@ export type Link = system.Link
|
|||
|
||||
local ReactiveFlags = system.ReactiveFlags
|
||||
local createReactiveSystem = system.createReactiveSystem
|
||||
local deepFreeze = utils.deepFreeze
|
||||
local noYield = utils.noYield
|
||||
|
||||
local NONE = 0b0000000
|
||||
|
|
@ -211,6 +216,9 @@ end
|
|||
local function writeAtom<T>(atom: Atom<T>, oldValue: T, newValue: T): T
|
||||
local equals = atom.equals
|
||||
if if equals then not equals(oldValue, newValue :: T) else oldValue ~= newValue then
|
||||
if config.frozen then
|
||||
deepFreeze(newValue)
|
||||
end
|
||||
atom.value = newValue
|
||||
atom.flags = bit32.bor(MUTABLE, DIRTY)
|
||||
local subs = atom.subs
|
||||
|
|
@ -323,6 +331,9 @@ local function unwatched(node: Atom | Computed | Effect | EffectScope)
|
|||
end
|
||||
|
||||
local function atom<T>(initialValue: T, equals: ((T, T) -> boolean)?): (...((T) -> T) | T) -> T
|
||||
if config.frozen then
|
||||
deepFreeze(initialValue)
|
||||
end
|
||||
return atomGetterSetter({
|
||||
previousValue = initialValue,
|
||||
value = initialValue,
|
||||
|
|
|
|||
|
|
@ -2,7 +2,13 @@ local function inlineTest(): string
|
|||
return debug.info(1, "n")
|
||||
end
|
||||
|
||||
local isO2 = inlineTest() ~= "inlineTest"
|
||||
|
||||
return {
|
||||
-- Enable strict mode in O2
|
||||
strict = inlineTest() == "inlineTest",
|
||||
--- Enforces synchronous, non-yielding behavior in atoms and effects.
|
||||
--- Enabled if the optimize level is below 2, which is the case in studio.
|
||||
strict = not isO2,
|
||||
--- Enforces immutability of atoms by deep-freezing table values. Disabled
|
||||
--- by default, but can be enabled to detect accidental mutations.
|
||||
frozen = false,
|
||||
}
|
||||
|
|
|
|||
13
packages/charm/src/index.d.ts
vendored
13
packages/charm/src/index.d.ts
vendored
|
|
@ -12,6 +12,7 @@ export type Equals<T> = (current: T, incoming: T) => boolean;
|
|||
|
||||
export const flags: {
|
||||
strict: boolean;
|
||||
frozen: boolean;
|
||||
};
|
||||
|
||||
export function atom<T>(value: T, equals?: Equals<T>): Atom<T>;
|
||||
|
|
@ -63,6 +64,14 @@ export function batched<Args extends any[], Result>(
|
|||
...args: Args
|
||||
): (...args: Args) => Result;
|
||||
|
||||
export function peek<Args extends any[], Result>(callback: (...args: Args) => Result, ...args: Args): Result;
|
||||
|
||||
export function untracked<Args extends any[], Result>(callback: (...args: Args) => Result, ...args: Args): Result;
|
||||
|
||||
/**
|
||||
* @deprecated Use `batched()` instead.
|
||||
*/
|
||||
export const batch: typeof batched;
|
||||
|
||||
/**
|
||||
* @deprecated Use `untracked()` instead.
|
||||
*/
|
||||
export const peek: typeof untracked;
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@ local flags = require("@self/flags")
|
|||
local listen = require("@self/listen")
|
||||
local mapped = require("@self/mapped")
|
||||
local observe = require("@self/observe")
|
||||
local peek = require("@self/peek")
|
||||
local subscribe = require("@self/subscribe")
|
||||
local types = require("@self/types")
|
||||
local untracked = require("@self/untracked")
|
||||
|
|
@ -12,6 +11,11 @@ local untracked = require("@self/untracked")
|
|||
export type Atom<T = any> = types.Atom<T>
|
||||
export type Selector<T = any> = types.Selector<T>
|
||||
|
||||
--- @deprecated Function `batch()` is deprecated, use `batched()` instead.
|
||||
local batch = batched
|
||||
--- @deprecated Function `peek()` is deprecated, use `untracked()` instead.
|
||||
local peek = untracked
|
||||
|
||||
return {
|
||||
core = core,
|
||||
flags = flags,
|
||||
|
|
@ -24,7 +28,9 @@ return {
|
|||
listen = listen,
|
||||
mapped = mapped,
|
||||
observe = observe,
|
||||
peek = peek,
|
||||
subscribe = subscribe,
|
||||
untracked = untracked,
|
||||
-- Temporary aliases for backwards compatibility
|
||||
batch = batch,
|
||||
peek = peek,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
local core = require("./core")
|
||||
local peek = require("./peek")
|
||||
local untracked = require("./untracked")
|
||||
|
||||
local computed = core.computed
|
||||
local effect = core.effect
|
||||
|
|
@ -19,7 +19,7 @@ local function listen<T>(selector: () -> T, listener: (state: T, previous: T?, d
|
|||
dispose = effect(function()
|
||||
local previous = current
|
||||
current = tracked()
|
||||
peek(listener, current, previous, dispose)
|
||||
untracked(listener, current, previous, dispose)
|
||||
end)
|
||||
|
||||
if disposed then
|
||||
|
|
|
|||
|
|
@ -1,18 +0,0 @@
|
|||
local core = require("./core")
|
||||
|
||||
local setCurrentTracking = core.setCurrentTracking
|
||||
|
||||
local function handlePeek(prevTracking: boolean, ok: boolean, ...)
|
||||
setCurrentTracking(prevTracking)
|
||||
if ok then
|
||||
return ...
|
||||
else
|
||||
error((...))
|
||||
end
|
||||
end
|
||||
|
||||
local function peek<Args..., Results...>(callback: (Args...) -> Results..., ...: Args...): Results...
|
||||
return handlePeek(setCurrentTracking(false), pcall(callback, ...))
|
||||
end
|
||||
|
||||
return peek
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
local core = require("./core")
|
||||
local peek = require("./peek")
|
||||
local untracked = require("./untracked")
|
||||
|
||||
local computed = core.computed
|
||||
local effect = core.effect
|
||||
|
|
@ -18,7 +18,7 @@ local function subscribe<T>(selector: () -> T, listener: (state: T, lastState: T
|
|||
if mounted then
|
||||
mounted = false
|
||||
else
|
||||
peek(listener, current, previous)
|
||||
untracked(listener, current, previous)
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
|
|
|||
|
|
@ -1,30 +1,6 @@
|
|||
--[[
|
||||
alien-signals 2.0.1 implementation adopted from
|
||||
https://github.com/Nicell/alien-signals-luau
|
||||
https://github.com/stackblitz/alien-signals
|
||||
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2024-present Johnson Chu, Nicholas Winans
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
]]
|
||||
-- Adapted from Alien Signals
|
||||
-- https://github.com/stackblitz/alien-signals
|
||||
-- https://github.com/Nicell/alien-signals-luau
|
||||
|
||||
export type ReactiveNode = {
|
||||
deps: Link?,
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
local core = require("./core")
|
||||
|
||||
local setCurrentSub = core.setCurrentSub
|
||||
local setCurrentTracking = core.setCurrentTracking
|
||||
|
||||
local function handleUntracked(prevSub: core.ReactiveNode?, ok: boolean, ...)
|
||||
setCurrentSub(prevSub)
|
||||
local function handleUntracked(prevTracking: boolean, ok: boolean, ...)
|
||||
setCurrentTracking(prevTracking)
|
||||
if ok then
|
||||
return ...
|
||||
else
|
||||
|
|
@ -12,7 +12,7 @@ local function handleUntracked(prevSub: core.ReactiveNode?, ok: boolean, ...)
|
|||
end
|
||||
|
||||
local function untracked<Args..., Results...>(callback: (Args...) -> Results..., ...: Args...): Results...
|
||||
return handleUntracked(setCurrentSub(nil), pcall(callback, ...))
|
||||
return handleUntracked(setCurrentTracking(false), pcall(callback, ...))
|
||||
end
|
||||
|
||||
return untracked
|
||||
|
|
|
|||
|
|
@ -13,6 +13,16 @@ local function noYield<Args..., Results...>(callback: (Args...) -> Results..., .
|
|||
return handleNoYield(thread, coroutine.resume(thread, ...))
|
||||
end
|
||||
|
||||
local function deepFreeze(object: any)
|
||||
if type(object) == "table" and not table.isfrozen(object) then
|
||||
table.freeze(object)
|
||||
for _, value in object do
|
||||
deepFreeze(value)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return {
|
||||
noYield = noYield :: typeof(pcall),
|
||||
deepFreeze = deepFreeze,
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue