docs: add doc comments

This commit is contained in:
Richard 2024-06-01 18:45:43 -07:00
parent df43a9be2e
commit 6a2241884a
17 changed files with 361 additions and 14 deletions

View file

@ -1,9 +1,15 @@
local peek = require(script.Parent.peek)
local store = require(script.Parent.store) local store = require(script.Parent.store)
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 AtomOptions<T> = types.AtomOptions<T> type AtomOptions<T> = types.AtomOptions<T>
--[=[
Creates a new atom with the given state.
@param state The initial state.
@param options Optional configuration.
@return A new atom.
]=]
local function atom<T>(state: T, options: AtomOptions<T>?): Atom<T> local function atom<T>(state: T, options: AtomOptions<T>?): Atom<T>
local equals = options and options.equals local equals = options and options.equals
@ -16,7 +22,7 @@ local function atom<T>(state: T, options: AtomOptions<T>?): Atom<T>
return state return state
end end
local nextState = peek(..., state) local nextState = store.peek(..., state)
if state ~= nextState and not (equals and equals(state, nextState)) then if state ~= nextState and not (equals and equals(state, nextState)) then
state = nextState state = nextState

View file

@ -5,6 +5,15 @@ type Atom<T> = types.Atom<T>
type AtomOptions<T> = types.AtomOptions<T> type AtomOptions<T> = types.AtomOptions<T>
type Molecule<T> = types.Molecule<T> type Molecule<T> = types.Molecule<T>
--[=[
Creates a read-only atom that derives its state from one or more atoms.
Used to avoid unnecessary recomputations if multiple listeners depend on
the same molecule.
@param molecule The function that produces the state.
@param options Optional configuration.
@return A new read-only atom.
]=]
local function computed<T>(molecule: Molecule<T>, options: AtomOptions<T>?): Molecule<T> local function computed<T>(molecule: Molecule<T>, options: AtomOptions<T>?): Molecule<T>
local captured, state = store.capture(molecule) local captured, state = store.capture(molecule)
local computedAtom = atom(state, options) local computedAtom = atom(state, options)

190
src/index.d.ts vendored
View file

@ -9,35 +9,138 @@ type AnyMap<K, V> =
| (K extends string | number | symbol ? { readonly [Key in K]: V } : never); | (K extends string | number | symbol ? { readonly [Key in K]: V } : never);
declare namespace Charm { declare namespace Charm {
/**
* A primitive state container that can be read from and written to. When the
* state changes, all subscribers are notified.
*
* @template State The type of the state.
* @param state The next state or a function that produces the next state.
* @returns The current state, if no arguments are provided.
*/
interface Atom<State> extends Molecule<State> { interface Atom<State> extends Molecule<State> {
/**
* @deprecated This property is not meant to be accessed directly.
*/
readonly __nominal: unique symbol; readonly __nominal: unique symbol;
/**
* @param state The next state or a function that produces the next state.
* @returns The current state, if no arguments are provided.
*/
(state: State | ((prev: State) => State)): void; (state: State | ((prev: State) => State)): void;
} }
/**
* A function that depends on one or more atoms and produces a state. Can be
* used to derive state from atoms.
*
* @template State The type of the state.
* @returns The current state.
*/
type Molecule<State> = () => State; type Molecule<State> = () => State;
interface AtomOptions<State> { interface AtomOptions<State> {
/**
* A function that determines whether the state has changed. By default,
* a strict equality check (`===`) is used.
*/
equals?: (prev: State, next: State) => boolean; equals?: (prev: State, next: State) => boolean;
} }
/**
* Creates a new atom with the given state.
*
* @template State The type of the state.
* @param state The initial state.
* @param options Optional configuration.
* @returns A new atom.
*/
function atom<State>(state: State, options?: AtomOptions<State>): Atom<State>; function atom<State>(state: State, options?: AtomOptions<State>): Atom<State>;
/**
* Creates a read-only atom that derives its state from one or more atoms.
* Used to avoid unnecessary recomputations if multiple listeners depend on
* the same molecule.
*
* @param molecule The function that produces the state.
* @param options Optional configuration.
* @returns A new read-only atom.
*/
function computed<State>(molecule: Molecule<State>, options?: AtomOptions<State>): Molecule<State>; function computed<State>(molecule: Molecule<State>, options?: AtomOptions<State>): Molecule<State>;
/**
* Subscribes to changes in the given atom or molecule. The callback is
* called with the current state and the previous state immediately after a
* change occurs.
*
* @param molecule The atom or molecule to subscribe to.
* @param callback The function to call when the state changes.
* @returns A function that unsubscribes the callback.
*/
function subscribe<State>(molecule: Molecule<State>, callback: (state: State, prev: State) => void): Cleanup; function subscribe<State>(molecule: Molecule<State>, callback: (state: State, prev: State) => void): Cleanup;
/**
* Runs the given callback immediately and whenever any atom it depends on
* changes. Returns a cleanup function that unsubscribes the callback.
*
* Note that dependencies are only evaluated once when the effect is created,
* conditional dependencies may not work as expected.
*
* @param callback The function to run.
* @returns A function that unsubscribes the callback.
*/
function effect(callback: () => void): Cleanup; function effect(callback: () => void): Cleanup;
/**
* Returns the result of the function without subscribing to changes. If a
* non-function value is provided, it is returned as is.
*
* @param molecule The atom or molecule to get the state of.
* @param args Arguments to pass to the molecule.
* @returns The current state.
*/
function peek<State, Args extends unknown[]>(molecule: State | ((...args: Args) => State), ...args: Args): State; function peek<State, Args extends unknown[]>(molecule: State | ((...args: Args) => State), ...args: Args): State;
/**
* Returns whether the given value is an atom.
*
* @param value The value to check.
* @returns `true` if the value is an atom, otherwise `false`.
*/
function isAtom(value: unknown): value is Atom<any>; function isAtom(value: unknown): value is Atom<any>;
/**
* Runs the given function and schedules listeners to be notified only once
* after the function has completed. Useful for batching multiple changes.
*
* @param callback The function to run.
*/
function batch(callback: () => void): void; function batch(callback: () => void): void;
/**
* Captures all atoms that are read during the function call and returns them
* along with the result of the function. Useful for tracking dependencies.
*
* @param molecule The function to run.
* @returns A tuple containing the captured atoms and the result of the function.
*/
function capture<State>(molecule: Molecule<State>): LuaTuple<[captured: Set<Atom<unknown>>, state: State]>; function capture<State>(molecule: Molecule<State>): LuaTuple<[captured: Set<Atom<unknown>>, state: State]>;
/**
* Notifies all subscribers of the given atom that the state has changed.
*
* @param atom The atom to notify.
*/
function notify(atom: Atom<unknown>): void; function notify(atom: Atom<unknown>): void;
/**
* Creates an instance of `factory` for each item in the atom's state, and
* cleans up the instance when the item is removed. Returns a cleanup function
* that unsubscribes all instances.
*
* @param molecule The atom or molecule to observe.
* @param factory The function that tracks the lifecycle of each item.
* @returns A function that unsubscribes all instances.
*/
function observe<Item>( function observe<Item>(
molecule: Molecule<readonly Item[]>, molecule: Molecule<readonly Item[]>,
factory: (item: Item, index: number) => Cleanup | void, factory: (item: Item, index: number) => Cleanup | void,
@ -48,6 +151,16 @@ declare namespace Charm {
factory: (item: Item, key: Key) => Cleanup | void, factory: (item: Item, key: Key) => Cleanup | void,
): Cleanup; ): Cleanup;
/**
* Maps each entry in the atom's state to a new key-value pair. If the `mapper`
* function returns `undefined`, the entry is omitted from the resulting map.
* When the atom changes, the `mapper` is called for each entry in the state
* to compute the new state.
*
* @param molecule The atom or molecule to map.
* @param mapper The function that maps each entry.
* @returns A new atom with the mapped state.
*/
function mapped<V0, K1, V1>( function mapped<V0, K1, V1>(
molecule: Molecule<readonly V0[]>, molecule: Molecule<readonly V0[]>,
mapper: (value: V0, index: number) => LuaTuple<[value: V1 | undefined, key: K1]>, mapper: (value: V0, index: number) => LuaTuple<[value: V1 | undefined, key: K1]>,
@ -65,41 +178,110 @@ declare namespace Charm {
// React // React
/**
* A hook that subscribes to changes in the given atom or molecule. The
* component is re-rendered whenever the state changes.
*
* If the `dependencies` array is provided, the subscription to the atom or
* molecule is re-created whenever the dependencies change. Otherwise, the
* subscription is created once when the component is mounted.
*
* @param molecule The atom or molecule to subscribe to.
* @param dependencies An array of values that the subscription depends on.
* @returns The current state.
*/
function useAtom<State>(molecule: Molecule<State>, dependencies?: unknown[]): State; function useAtom<State>(molecule: Molecule<State>, dependencies?: unknown[]): State;
// Sync /**
* Synchronizes state between the client and server. The server sends patches
* to the client, which applies them to its local state.
*/
const sync: { const sync: {
/**
* Creates a `ClientSyncer` object that receives patches from the server and
* applies them to the local state.
*
* @param options The atoms to synchronize with the server.
* @returns A `ClientSyncer` object.
*/
client: <T extends Record<string, Atom<any>>>(options: ClientOptions<T>) => ClientSyncer<T>; client: <T extends Record<string, Atom<any>>>(options: ClientOptions<T>) => ClientSyncer<T>;
/**
* Creates a `ServerSyncer` object that sends patches to the client and
* hydrates the client's state.
*
* @param options The atoms to synchronize with the client.
* @returns A `ServerSyncer` object.
*/
server: <T extends Record<string, Atom<any>>>(options: ServerOptions<T>) => ServerSyncer<T>; server: <T extends Record<string, Atom<any>>>(options: ServerOptions<T>) => ServerSyncer<T>;
collect: (root: Instance) => Record<string, Atom<any>>;
}; };
/**
* A special value that denotes the absence of a value. Used to represent
* undefined values in patches.
*/
interface None { interface None {
__none: "__none"; readonly __none: "__none";
} }
type SyncPatch<T> = { type SyncPatch<T> = {
readonly [P in keyof T]?: (T[P] extends object ? SyncPatch<T[P]> : T[P]) | (T[P] extends undefined ? None : never); readonly [P in keyof T]?: (T[P] extends object ? SyncPatch<T[P]> : T[P]) | (T[P] extends undefined ? None : never);
}; };
/**
* A payload that can be sent from the server to the client to synchronize
* state between the two.
*/
type SyncPayload<T> = { type: "init"; data: T } | { type: "patch"; data: SyncPatch<T> }; type SyncPayload<T> = { type: "init"; data: T } | { type: "patch"; data: SyncPatch<T> };
interface ClientOptions<T extends Record<string, Atom<any>>> { interface ClientOptions<T extends Record<string, Atom<any>>> {
/**
* The atoms to synchronize with the server.
*/
atoms: T; atoms: T;
} }
interface ServerOptions<T extends Record<string, Atom<any>>> { interface ServerOptions<T extends Record<string, Atom<any>>> {
/**
* The atoms to synchronize with the client.
*/
atoms: T; atoms: T;
/**
* The interval at which to send patches to the client, in seconds.
* Defaults to `0` (patches are sent up to once per frame). Set to a
* negative value to disable automatic syncing.
*/
interval?: number; interval?: number;
} }
interface ClientSyncer<T extends Record<string, Atom<any>>> { interface ClientSyncer<T extends Record<string, Atom<any>>> {
/**
* Applies a patch or initializes the state of the atoms with the given
* payload from the server.
*
* @param payload The patch or hydration payload to apply.
*/
sync(payload: SyncPayload<T>): void; sync(payload: SyncPayload<T>): void;
} }
interface ServerSyncer<T extends Record<string, Atom<any>>> { interface ServerSyncer<T extends Record<string, Atom<any>>> {
/**
* Sets up a subscription to each atom that schedules a patch to be sent to
* the client whenever the state changes. When a change occurs, the `callback`
* is called with the player and the payload to send.
*
* Note that the `payload` object should not be mutated. If you need to
* modify the payload, apply the changes to a copy of the object.
*
* @param callback The function to call when the state changes.
* @returns A cleanup function that unsubscribes all listeners.
*/
connect(callback: (player: Player, payload: SyncPayload<T>) => void): Cleanup; connect(callback: (player: Player, payload: SyncPayload<T>) => void): Cleanup;
/**
* Hydrates the client's state with the server's state. This should be
* called when a player joins the game and requires the server's state.
*
* @param player The player to hydrate.
*/
hydrate(player: Player): void; hydrate(player: Player): void;
} }
} }

View file

@ -5,7 +5,6 @@ local computed = require(script.computed)
local effect = require(script.effect) local effect = require(script.effect)
local mapped = require(script.mapped) local mapped = require(script.mapped)
local observe = require(script.observe) local observe = require(script.observe)
local peek = require(script.peek)
local useAtom = require(script.react.useAtom) local useAtom = require(script.react.useAtom)
local store = require(script.store) local store = require(script.store)
local subscribe = require(script.subscribe) local subscribe = require(script.subscribe)
@ -22,12 +21,12 @@ return {
effect = effect, effect = effect,
mapped = mapped, mapped = mapped,
observe = observe, observe = observe,
peek = peek,
useAtom = useAtom, useAtom = useAtom,
batch = store.batch, batch = store.batch,
capture = store.capture, capture = store.capture,
notify = store.notify,
isAtom = store.isAtom, isAtom = store.isAtom,
notify = store.notify,
peek = store.peek,
subscribe = subscribe, subscribe = subscribe,
sync = sync, sync = sync,
} }

View file

@ -1,6 +1,6 @@
local atom = require(script.Parent.atom) local atom = require(script.Parent.atom)
local effect = require(script.Parent.effect) local effect = require(script.Parent.effect)
local peek = require(script.Parent.peek) local store = require(script.Parent.store)
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 Molecule<T> = types.Molecule<T> type Molecule<T> = types.Molecule<T>
@ -10,6 +10,16 @@ type Map =
& (<K0, V0, V1>(fn: Molecule<{ [K0]: V0 }>, mapper: (V0, K0) -> V1?) -> Molecule<{ [K0]: V1 }>) & (<K0, V0, V1>(fn: Molecule<{ [K0]: V0 }>, mapper: (V0, K0) -> V1?) -> Molecule<{ [K0]: V1 }>)
& (<K0, V0, K1, V1>(fn: Molecule<{ [K0]: V0 }>, mapper: (V0, K0) -> (V1?, K1?)) -> Molecule<{ [K1]: V1 }>) & (<K0, V0, K1, V1>(fn: Molecule<{ [K0]: V0 }>, mapper: (V0, K0) -> (V1?, K1?)) -> Molecule<{ [K1]: V1 }>)
--[=[
Maps each entry in the atom's state to a new key-value pair. If the `mapper`
function returns `undefined`, the entry is omitted from the resulting map.
When the atom changes, the `mapper` is called for each entry in the state
to compute the new state.
@param molecule The atom or molecule to map.
@param mapper The function that maps each entry.
@return A new atom with the mapped state.
]=]
local function mapped<K0, V0, K1, V1>(molecule: Molecule<{ [K0]: V0 }>, mapper: (V0, K0) -> (V1?, K1?)): Molecule<{ [K1]: V1 }> local function mapped<K0, V0, K1, V1>(molecule: Molecule<{ [K0]: V0 }>, mapper: (V0, K0) -> (V1?, K1?)): Molecule<{ [K1]: V1 }>
local mappedAtom = atom({}) local mappedAtom = atom({})
local mappedAtomRef = setmetatable({ current = mappedAtom }, { __mode = "v" }) local mappedAtomRef = setmetatable({ current = mappedAtom }, { __mode = "v" })
@ -22,9 +32,10 @@ local function mapped<K0, V0, K1, V1>(molecule: Molecule<{ [K0]: V0 }>, mapper:
end end
local items = molecule() local items = molecule()
local mappedItems = table.clone(peek(mappedAtomRef.current)) local mappedItems = table.clone(store.peek(mappedAtomRef.current))
local mappedKeys = {} local mappedKeys = {}
-- TODO: Only call mapper if the item has changed.
for key, item in items do for key, item in items do
local newItem, newKey = mapper(item, key) local newItem, newKey = mapper(item, key)
if newKey == nil then if newKey == nil then

View file

@ -2,6 +2,15 @@ local effect = require(script.Parent.effect)
local types = require(script.Parent.types) local types = require(script.Parent.types)
type Molecule<T> = types.Molecule<T> type Molecule<T> = types.Molecule<T>
--[=[
Creates an instance of `factory` for each item in the atom's state, and
cleans up the instance when the item is removed. Returns a cleanup function
that unsubscribes all instances.
@param molecule The atom or molecule to observe.
@param factory The function that tracks the lifecycle of each item.
@return A function that unsubscribes all instances.
]=]
local function observe<K, V>(molecule: Molecule<{ [K]: V }>, factory: (value: V, key: K) -> () -> ()): () -> () local function observe<K, V>(molecule: Molecule<{ [K]: V }>, factory: (value: V, key: K) -> () -> ()): () -> ()
local connections: { [K]: () -> () } = {} local connections: { [K]: () -> () } = {}

View file

@ -3,6 +3,18 @@ local subscribe = require(script.Parent.Parent.subscribe)
local types = require(script.Parent.Parent.types) local types = require(script.Parent.Parent.types)
type Molecule<T> = types.Molecule<T> type Molecule<T> = types.Molecule<T>
--[=[
A hook that subscribes to changes in the given atom or molecule. The
component is re-rendered whenever the state changes.
If the `dependencies` array is provided, the subscription to the atom or
molecule is re-created whenever the dependencies change. Otherwise, the
subscription is created once when the component is mounted.
@param molecule The atom or molecule to subscribe to.
@param dependencies An array of values that the subscription depends on.
@return The current state.
]=]
local function useAtom<State>(molecule: Molecule<State>, dependencies: { any }?): State local function useAtom<State>(molecule: Molecule<State>, dependencies: { any }?): State
local React = requireReact() local React = requireReact()

View file

@ -2,13 +2,24 @@ local store = require(script.Parent.store)
local types = require(script.Parent.types) local types = require(script.Parent.types)
type Molecule<T> = types.Molecule<T> type Molecule<T> = types.Molecule<T>
--[=[
Subscribes to changes in the given atom or molecule. The callback is
called with the current state and the previous state immediately after a
change occurs.
@param molecule The atom or molecule to subscribe to.
@param callback The function to call when the state changes.
@return A function that unsubscribes the callback.
]=]
local function subscribe<T>(molecule: Molecule<T>, callback: (state: T, prev: T) -> ()): () -> () local function subscribe<T>(molecule: Molecule<T>, callback: (state: T, prev: T) -> ()): () -> ()
local captured, state = store.capture(molecule) local captured, state = store.capture(molecule)
local function listener() local function listener()
local prevState = state local prevState = state
state = molecule() state = molecule()
callback(state, prevState) if state ~= prevState then
callback(state, prevState)
end
end end
for atom in captured do for atom in captured do

View file

@ -4,13 +4,29 @@ type SyncPayload = types.SyncPayload
local patch = require(script.Parent.patch) local patch = require(script.Parent.patch)
type ClientOptions = { type ClientOptions = {
--[=[
The atoms to synchronize with the server.
]=]
atoms: { [string]: Atom<any> }, atoms: { [string]: Atom<any> },
} }
type ClientSyncer = { type ClientSyncer = {
--[=[
Applies a patch or initializes the state of the atoms with the given
payload from the server.
@param payload The patch or hydration payload to apply.
]=]
sync: (self: ClientSyncer, payload: SyncPayload) -> (), sync: (self: ClientSyncer, payload: SyncPayload) -> (),
} }
--[=[
Creates a `ClientSyncer` object that receives patches from the server and
applies them to the local state.
@param options The atoms to synchronize with the server.
@return A `ClientSyncer` object.
]=]
local function client(options: ClientOptions): ClientSyncer local function client(options: ClientOptions): ClientSyncer
local atoms = options.atoms local atoms = options.atoms
local self = {} :: ClientSyncer local self = {} :: ClientSyncer

View file

@ -1,6 +1,10 @@
local client = require(script.client) local client = require(script.client)
local server = require(script.server) local server = require(script.server)
--[=[
Synchronizes state between the client and server. The server sends patches
to the client, which applies them to its local state.
]=]
return { return {
client = client, client = client,
server = server, server = server,

View file

@ -1,5 +1,9 @@
local validate = require(script.Parent.validate) local validate = require(script.Parent.validate)
--[=[
A special value that denotes the absence of a value. Used to represent
undefined values in patches.
]=]
local NONE = { __none = "__none" } local NONE = { __none = "__none" }
local function diff(prevState: { [any]: any }, nextState: { [any]: any }) local function diff(prevState: { [any]: any }, nextState: { [any]: any })

View file

@ -8,16 +8,51 @@ local setInterval = require(script.Parent.Parent.utils.setInterval)
local patch = require(script.Parent.patch) local patch = require(script.Parent.patch)
type ServerOptions = { type ServerOptions = {
--[=[
The atoms to synchronize with the client.
]=]
atoms: { [string]: Atom<any> }, atoms: { [string]: Atom<any> },
--[=[
The interval at which to send patches to the client, in seconds.
Defaults to `0` (patches are sent up to once per frame). Set to a
negative value to disable automatic syncing.
]=]
interval: number?, interval: number?,
} }
type ServerSyncer = { type ServerSyncer = {
--[=[
Sets up a subscription to each atom that schedules a patch to be sent to
the client whenever the state changes. When a change occurs, the `callback`
is called with the player and the payload to send.
Note that the `payload` object should not be mutated. If you need to
modify the payload, apply the changes to a copy of the object.
@param callback The function to call when the state changes.
@return A cleanup function that unsubscribes all listeners.
]=]
connect: (self: ServerSyncer, callback: (player: Player, payload: SyncPayload) -> ()) -> () -> (), connect: (self: ServerSyncer, callback: (player: Player, payload: SyncPayload) -> ()) -> () -> (),
--[=[
Hydrates the client's state with the server's state. This should be
called when a player joins the game and requires the server's state.
@param player The player to hydrate.
]=]
hydrate: (self: ServerSyncer, player: Player) -> (), hydrate: (self: ServerSyncer, player: Player) -> (),
--[=[
@deprecated For internal use only.
]=]
_sendPatch: (self: ServerSyncer, player: Player) -> (), _sendPatch: (self: ServerSyncer, player: Player) -> (),
} }
--[=[
Creates a `ServerSyncer` object that sends patches to the client and
hydrates the client's state.
@param options The atoms to synchronize with the client.
@return A `ServerSyncer` object.
]=]
local function server(options: ServerOptions): ServerSyncer local function server(options: ServerOptions): ServerSyncer
local atoms = options.atoms local atoms = options.atoms
local interval = options.interval or 0 local interval = options.interval or 0
@ -42,7 +77,7 @@ local function server(options: ServerOptions): ServerSyncer
end) end)
state[key] = atom() state[key] = atom()
stateSnapshot[key] = atom() stateSnapshot[key] = state[key]
end end
local disconnect = setInterval(function() local disconnect = setInterval(function()
@ -51,7 +86,7 @@ local function server(options: ServerOptions): ServerSyncer
end end
local diffs = patch.diff(stateSnapshot, state) local diffs = patch.diff(stateSnapshot, state)
stateSnapshot = table.clone(state) stateSnapshot = state
changed = false changed = false
for _, player in Players:GetPlayers() do for _, player in Players:GetPlayers() do

View file

@ -29,6 +29,13 @@ local function isUnsafeTable(object: { [any]: any })
return false return false
end end
--[=[
Validates a value to ensure it can be synced over a remote event.
@param value The value to validate.
@param key The key of the value in the table.
@error Throws an error if the value cannot be synced.
]=]
local function validate(value: any, key: any) local function validate(value: any, key: any)
local typeOfKey = type(key) local typeOfKey = type(key)
local typeOfValue = type(value) local typeOfValue = type(value)

View file

@ -1,13 +1,38 @@
export type WeakMap<K, V> = typeof(setmetatable({} :: { [K]: V }, { __mode = "k" })) export type WeakMap<K, V> = typeof(setmetatable({} :: { [K]: V }, { __mode = "k" }))
--[=[
A primitive state container that can be read from and written to. When the
state changes, all subscribers are notified.
@param state The next state or a function that produces the next state.
@return The current state, if no arguments are provided.
]=]
export type Atom<T> = (() -> T) & (state: T | (T) -> T) -> T export type Atom<T> = (() -> T) & (state: T | (T) -> T) -> T
--[=[
A function that depends on one or more atoms and produces a state. Can be
used to derive state from atoms.
@template State The type of the state.
@return The current state.
]=]
export type Molecule<T> = () -> T export type Molecule<T> = () -> T
--[=[
Optional configuration for creating an atom.
]=]
export type AtomOptions<T> = { export type AtomOptions<T> = {
--[=[
A function that determines whether the state has changed. By default,
a strict equality check (`===`) is used.
]=]
equals: (prev: T, next: T) -> boolean, equals: (prev: T, next: T) -> boolean,
} }
--[=[
A payload that can be sent from the server to the client to synchronize
state between the two.
]=]
export type SyncPayload = { export type SyncPayload = {
type: "patch" | "init", type: "patch" | "init",
data: { [any]: any }, data: { [any]: any },

View file

@ -1,3 +1,7 @@
--[=[
Trigger a garbage collection cycle by allocating a large amount of junk memory
and then waiting for the next frame.
]=]
local function collect() local function collect()
for _ = 1, 1e4 do for _ = 1, 1e4 do
local _ = table.create(1e3) local _ = table.create(1e3)

View file

@ -1,3 +1,9 @@
--[=[
Counts the number of elements in a table.
@param object The table to count.
@return The number of elements in the table.
]=]
local function count(object: any): number local function count(object: any): number
local count = 0 local count = 0
for _ in next, object do for _ in next, object do

View file

@ -1,5 +1,12 @@
local RunService = game:GetService("RunService") local RunService = game:GetService("RunService")
--[=[
Schedules a function to be called at a fixed interval of `seconds`.
@param callback The function to call.
@param seconds The interval in seconds.
@return A function that cancels the interval.
]=]
local function setInterval(callback: () -> (), seconds: number): () -> () local function setInterval(callback: () -> (), seconds: number): () -> ()
if seconds < 0 then if seconds < 0 then
return function() end return function() end