test: add benchmarks, dev mode

This commit is contained in:
Richard 2024-03-02 02:41:30 -08:00
parent 3548ca5c58
commit 4586882d3c
6 changed files with 95 additions and 11 deletions

View file

@ -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,
},
}

View file

@ -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

View file

@ -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)

View file

@ -2,7 +2,7 @@ return function()
local previous = _G.__DEV__
beforeAll(function()
_G.__DEV__ = true
_G.__DEV__ = false -- prevent React errors
end)
afterAll(function()

View file

@ -1,15 +1,10 @@
local reference = require(script.Parent.utils.reference)
type AtomInternal<T> = {
--- @deprecated
setForced: (self: AtomInternal<T>, 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: State): Atom<State>
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,8 +98,10 @@ local function atom<State>(state: State): Atom<State>
function atom:readonly(): ReadonlyAtom<State>
function setter(_, _)
if _G.__DEV__ then
error(`Attempt to set read-only atom '{label}' created at: {traceback}`, 3)
end
end
return self
end

View file

@ -15,16 +15,29 @@ type Derive =
local function derive<Result>(...): ReadonlyAtom<Result>
local atoms: { Atom<unknown> } = { ... }
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<Result>(...): ReadonlyAtom<Result>
-- its derived atoms will be in use as well.
derivedAtom.owners[atom] = true
atom.listeners[listener] = true
listeners[listener] = true
end
return derivedAtom