refactor: replace all references to molecules

This commit is contained in:
Richard 2024-10-25 19:13:06 -07:00
parent 5eacec70ff
commit 7152f21129
12 changed files with 69 additions and 56 deletions

View file

@ -1,4 +1,4 @@
import { Atom, Molecule } from "@rbxts/charm";
import { Atom, Selector } from "@rbxts/charm";
export = CharmSync;
export as namespace CharmSync;
@ -8,13 +8,18 @@ type Cleanup = () => void;
declare namespace CharmSync {
type AtomMap = Record<string, Atom<any>>;
type MoleculeMap = Record<string, Molecule<any>>;
/**
* @deprecated Use `SelectorMap` instead.
*/
type MoleculeMap = Record<string, Selector<any>>;
type SelectorMap = Record<string, Selector<any>>;
/**
* Recursively infers the type of the state produced by a map of molecules.
* Infers the type of the return values produced by a map of functions.
*/
type StateOfMap<T> = {
[P in keyof T]: T[P] extends Molecule<infer State> ? State : never;
[P in keyof T]: T[P] extends Selector<infer State> ? State : never;
};
/**
@ -45,7 +50,7 @@ declare namespace CharmSync {
* @param options The atoms to synchronize with the client.
* @returns A `ServerSyncer` object.
*/
function server<Molecules extends MoleculeMap>(options: ServerOptions<Molecules>): ServerSyncer<Molecules>;
function server<Selectors extends SelectorMap>(options: ServerOptions<Selectors>): ServerSyncer<Selectors>;
/**
* Checks whether a value is `None`. If `true`, the value is scheduled to be
@ -90,9 +95,9 @@ declare namespace CharmSync {
* A payload that can be sent from the server to the client to synchronize
* state between the two.
*/
type SyncPayload<Molecules extends MoleculeMap> =
| { type: "init"; data: StateOfMap<Molecules> }
| { type: "patch"; data: SyncPatch<StateOfMap<Molecules>> };
type SyncPayload<Selectors extends SelectorMap> =
| { type: "init"; data: StateOfMap<Selectors> }
| { type: "patch"; data: SyncPatch<StateOfMap<Selectors>> };
interface ClientOptions<Atoms extends AtomMap> {
/**
@ -106,11 +111,11 @@ declare namespace CharmSync {
ignoreUnhydrated?: boolean;
}
interface ServerOptions<Molecules extends MoleculeMap> {
interface ServerOptions<Selectors extends SelectorMap> {
/**
* The atoms to synchronize with the client.
*/
atoms: Molecules;
atoms: Selectors;
/**
* 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
@ -139,7 +144,7 @@ declare namespace CharmSync {
sync(...payloads: SyncPayload<Atoms>[]): void;
}
interface ServerSyncer<Molecules extends MoleculeMap> {
interface ServerSyncer<Selectors extends SelectorMap> {
/**
* 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`
@ -151,7 +156,7 @@ declare namespace CharmSync {
* @param callback The function to call when the state changes.
* @returns A cleanup function that unsubscribes all listeners.
*/
connect(callback: (player: Player, ...payloads: SyncPayload<Molecules>[]) => void): Cleanup;
connect(callback: (player: Player, ...payloads: SyncPayload<Selectors>[]) => 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.

View file

@ -37,6 +37,14 @@ declare namespace Charm {
*/
type Selector<State> = () => State;
/**
* A function that depends on one or more atoms and produces a value. Can be
* used to derive state from atoms.
*
* @returns The current state.
*/
type Molecule<State> = Selector<State>;
/**
* Infers the type of the state produced by the given function.
*/

View file

@ -8,8 +8,8 @@ local subscribe = require(script.subscribe)
local types = require(script.types)
export type Atom<State> = types.Atom<State>
export type Molecule<State> = types.Selector<State>
export type Selector<State> = types.Selector<State>
export type Molecule<State> = types.Selector<State>
export type SyncPayload = types.SyncPayload
return {

View file

@ -9,15 +9,15 @@ local function noop() end
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 callback The atom or selector 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: Selector<{ [K]: V }>, factory: (value: V, key: K) -> (() -> ())?): () -> ()
local function observe<K, V>(callback: Selector<{ [K]: V }>, factory: (value: V, key: K) -> (() -> ())?): () -> ()
local connections: { [K]: () -> () } = {}
local unsubscribe = effect(function()
local state = molecule()
local state = callback()
for key, disconnect in next, connections do
if state[key] == nil then

View file

@ -100,8 +100,8 @@ end
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.
@param callback The atom or selector to get the state of.
@param args Arguments to pass to the function.
@return The current state.
]=]
local function peek<T, U...>(callback: ((U...) -> T) | T, ...: U...): T
@ -128,12 +128,12 @@ end
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.
@param callback The function to run.
@return A tuple containing the captured atoms and the result of the function.
]=]
local function capture<T>(molecule: () -> T): (Set<Atom<any>>, T)
if listeners[molecule] then
return { [molecule] = true }, peek(molecule)
local function capture<T>(callback: () -> T): (Set<Atom<any>>, T)
if listeners[callback] then
return { [callback] = true }, peek(callback)
end
local dependencies: Set<Atom<any>> = {}
@ -141,7 +141,7 @@ local function capture<T>(molecule: () -> T): (Set<Atom<any>>, T)
capturing.index += 1
capturing.stack[capturing.index] = dependencies
local result = try(molecule, function()
local result = try(callback, function()
capturing.stack[capturing.index] = nil
capturing.index -= 1
end)

View file

@ -3,39 +3,39 @@ local types = require(script.Parent.types)
type Selector<T> = types.Selector<T>
--[=[
Subscribes to changes in the given atom or molecule. The callback is
Subscribes to changes in the given atom or selector. 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.
@param callback The atom or selector to subscribe to.
@param listener The function to call when the state changes.
@return A function that unsubscribes the callback.
]=]
local function subscribe<T>(molecule: Selector<T>, callback: (state: T, prev: T) -> ()): () -> ()
local dependencies, state = store.capture(molecule)
local function subscribe<T>(callback: Selector<T>, listener: (state: T, prev: T) -> ()): () -> ()
local dependencies, state = store.capture(callback)
local disconnected = false
local function listener()
local function handler()
local prevState = state
store.disconnect(dependencies, listener)
dependencies, state = store.capture(molecule)
store.disconnect(dependencies, handler)
dependencies, state = store.capture(callback)
if not disconnected then
store.connect(dependencies, listener)
store.connect(dependencies, handler)
end
if state ~= prevState then
callback(state, prevState)
listener(state, prevState)
end
end
store.connect(dependencies, listener)
store.connect(dependencies, handler)
return function()
if not disconnected then
disconnected = true
store.disconnect(dependencies, listener)
store.disconnect(dependencies, handler)
end
end
end

View file

@ -1,15 +1,15 @@
import { Molecule } from "@rbxts/charm";
import { Selector } from "@rbxts/charm";
/**
* A hook that subscribes to changes in the given atom or molecule. The
* A hook that subscribes to changes in the given atom or selector. 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
* selector 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 callback The atom or selector to subscribe to.
* @param dependencies An array of values that the subscription depends on.
* @returns The current state.
*/
export function useAtom<State>(molecule: Molecule<State>, dependencies?: unknown[]): State;
export function useAtom<State>(callback: Selector<State>, dependencies?: unknown[]): State;

View file

@ -4,23 +4,23 @@ local React = if script.Parent:FindFirstChild("react")
else require(script.Parent.React)
--[=[
A hook that subscribes to changes in the given atom or molecule. The
A hook that subscribes to changes in the given atom or selector. 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
selector 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 callback The atom or selector to subscribe to.
@param dependencies An array of values that the subscription depends on.
@return The current state.
]=]
local function useAtom<State>(molecule: Charm.Molecule<State>, dependencies: { any }?): State
local state, setState = React.useState(molecule)
local function useAtom<State>(callback: Charm.Selector<State>, dependencies: { any }?): State
local state, setState = React.useState(callback)
React.useEffect(function()
setState(molecule())
return Charm.subscribe(molecule, setState)
setState(callback())
return Charm.subscribe(callback, setState)
end, dependencies or {})
return state

View file

@ -1,9 +1,9 @@
import { Molecule } from "@rbxts/charm";
import { Selector } from "@rbxts/charm";
/**
* Subscribes to the state of an atom and returns a Vide source.
*
* @param molecule The atom or molecule to subscribe to.
* @param callback The atom or selector to subscribe to.
* @returns The reactive source.
*/
export function useAtom<T>(atom: Molecule<T>): () => T;
export function useAtom<T>(callback: Selector<T>): () => T;

View file

@ -6,12 +6,12 @@ local Vide = if script.Parent:FindFirstChild("vide")
--[=[
Subscribes to the state of an atom and returns a Vide source.
@param molecule The atom or molecule to subscribe to.
@param callback The atom or selector to subscribe to.
@return The reactive source.
]=]
local function useAtom<State>(molecule: () -> State): State
local state = Vide.source(molecule())
local unsubscribe = Charm.subscribe(molecule, function(value)
local function useAtom<State>(callback: () -> State): State
local state = Vide.source(callback())
local unsubscribe = Charm.subscribe(callback, function(value)
task.spawn(state, value)
end)

View file

@ -17,7 +17,7 @@ return function()
expect(hook.result).to.equal(1)
end)
it("accepts a molecule", function()
it("accepts a selector", function()
local state = atom(0)
local hook = renderHook(function()
return useAtom(function()

View file

@ -20,7 +20,7 @@ return function()
cleanup()
end)
it("accepts a molecule", function()
it("accepts a selector", function()
local state = atom(0)
local source
local cleanup = root(function()