From 4586882d3cc0da98492d6a242c4a76e8289a87cc Mon Sep 17 00:00:00 2001 From: Richard Date: Sat, 2 Mar 2024 02:41:30 -0800 Subject: [PATCH] test: add benchmarks, dev mode --- src/__tests__/benchmarks/atom.bench.luau | 53 ++++++++++++++++++++ src/__tests__/derive.spec.luau | 10 ++++ src/__tests__/init.spec.luau | 10 ++++ src/__tests__/{sync => react}/init.spec.luau | 2 +- src/atom.luau | 15 +++--- src/derive.luau | 16 +++++- 6 files changed, 95 insertions(+), 11 deletions(-) create mode 100644 src/__tests__/benchmarks/atom.bench.luau rename src/__tests__/{sync => react}/init.spec.luau (74%) diff --git a/src/__tests__/benchmarks/atom.bench.luau b/src/__tests__/benchmarks/atom.bench.luau new file mode 100644 index 0000000..2bca275 --- /dev/null +++ b/src/__tests__/benchmarks/atom.bench.luau @@ -0,0 +1,53 @@ +-- This file is for use by Benchmarker +-- https://boatbomber.itch.io/benchmarker + +local Package = game:GetService("ReplicatedStorage").Packages.charm + +local atom = require(Package.atom) +local derive = require(Package.derive) + +return { + ParameterGenerator = function() + return atom({ count = 0 }) + end, + + Functions = { + atom = function(_, source: any) + for _ = 1, 500 do + source({ count = math.random(1, 2) }) + end + end, + + derived = function(_, source: any) + derive(source, function(state) + return { count = 2 * state.count } + end) + for _ = 1, 500 do + source({ count = math.random(1, 2) }) + end + end, + + derived_memo = function(_, source: any) + source:memo(function(previous, current) + return previous.count == current.count + end) + derive(source, function(state) + return { count = 2 * state.count } + end) + for _ = 1, 500 do + source({ count = math.random(1, 2) }) + end + end, + + derived_writable = function(_, source: any) + local derived = derive(source, function(state) + return { count = 2 * state.count } + end):writable(function(update) + source({ count = 0.5 * update.count }) + end) + for _ = 1, 500 do + derived({ count = 2 * math.random(1, 2) }) + end + end, + }, +} diff --git a/src/__tests__/derive.spec.luau b/src/__tests__/derive.spec.luau index a06db04..00b60a2 100644 --- a/src/__tests__/derive.spec.luau +++ b/src/__tests__/derive.spec.luau @@ -89,4 +89,14 @@ return function() error("None of the derived atoms were collected") end) + + it("unmounts from parent atom", function() + local source = atom(1) + local derived = derive(source, function(value) + return value + end) + expect((next(source.listeners))).to.be.ok() + derived:unmount() + expect((next(source.listeners))).to.never.be.ok() + end) end diff --git a/src/__tests__/init.spec.luau b/src/__tests__/init.spec.luau index 53f1537..c37cf45 100644 --- a/src/__tests__/init.spec.luau +++ b/src/__tests__/init.spec.luau @@ -1,6 +1,16 @@ local reference = require(script.Parent.Parent.utils.reference) return function() + local previous = _G.__DEV__ + + beforeAll(function() + _G.__DEV__ = true + end) + + afterAll(function() + _G.__DEV__ = previous + end) + afterEach(function() reference.clear() end) diff --git a/src/__tests__/sync/init.spec.luau b/src/__tests__/react/init.spec.luau similarity index 74% rename from src/__tests__/sync/init.spec.luau rename to src/__tests__/react/init.spec.luau index 57649b9..05ca21e 100644 --- a/src/__tests__/sync/init.spec.luau +++ b/src/__tests__/react/init.spec.luau @@ -2,7 +2,7 @@ return function() local previous = _G.__DEV__ beforeAll(function() - _G.__DEV__ = true + _G.__DEV__ = false -- prevent React errors end) afterAll(function() diff --git a/src/atom.luau b/src/atom.luau index 17eb224..2e76e77 100644 --- a/src/atom.luau +++ b/src/atom.luau @@ -1,15 +1,10 @@ local reference = require(script.Parent.utils.reference) type AtomInternal = { - --- @deprecated setForced: (self: AtomInternal, state: T | ((T) -> T)) -> (), - --- @deprecated listeners: { [(T) -> ()]: true }, - --- @deprecated debugTraceback: string, - --- @deprecated debugLabel: string, - --- @deprecated owners: { [any]: true }, } @@ -63,10 +58,10 @@ local function atom(state: State): Atom if setter then local result = setter(nextState, state) - if result == nil then - return - else + if result ~= nil then nextState = result + else + return end end @@ -103,7 +98,9 @@ local function atom(state: State): Atom function atom:readonly(): ReadonlyAtom function setter(_, _) - error(`Attempt to set read-only atom '{label}' created at: {traceback}`, 3) + if _G.__DEV__ then + error(`Attempt to set read-only atom '{label}' created at: {traceback}`, 3) + end end return self end diff --git a/src/derive.luau b/src/derive.luau index 6a62f41..614d2d1 100644 --- a/src/derive.luau +++ b/src/derive.luau @@ -15,16 +15,29 @@ type Derive = local function derive(...): ReadonlyAtom local atoms: { Atom } = { ... } local combiner: (...unknown) -> Result = table.remove(atoms) :: any - local size = #atoms + local size = 0 local dependencies: { unknown } = {} + local listeners: { [() -> ()]: boolean } = {} for index, atom in atoms do dependencies[index] = atom:get() + size += 1 end local derivedAtom = atom(combiner(table.unpack(dependencies, 1, size))):readonly() + local derivedAtomUnmount = derivedAtom.unmount local getDerivedAtom = reference.weak(derivedAtom) + function derivedAtom:unmount() + derivedAtomUnmount(self) + + for listener in listeners do + for _, atom in atoms do + atom.listeners[listener] = nil + end + end + end + for index, atom in atoms do local function listener(state: unknown) -- Remove the listener if the derived atom has been garbage collected, @@ -44,6 +57,7 @@ local function derive(...): ReadonlyAtom -- its derived atoms will be in use as well. derivedAtom.owners[atom] = true atom.listeners[listener] = true + listeners[listener] = true end return derivedAtom