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 = CharmSync;
export as namespace CharmSync; export as namespace CharmSync;
@ -8,13 +8,18 @@ type Cleanup = () => void;
declare namespace CharmSync { declare namespace CharmSync {
type AtomMap = Record<string, Atom<any>>; 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> = { 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. * @param options The atoms to synchronize with the client.
* @returns A `ServerSyncer` object. * @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 * 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 * A payload that can be sent from the server to the client to synchronize
* state between the two. * state between the two.
*/ */
type SyncPayload<Molecules extends MoleculeMap> = type SyncPayload<Selectors extends SelectorMap> =
| { type: "init"; data: StateOfMap<Molecules> } | { type: "init"; data: StateOfMap<Selectors> }
| { type: "patch"; data: SyncPatch<StateOfMap<Molecules>> }; | { type: "patch"; data: SyncPatch<StateOfMap<Selectors>> };
interface ClientOptions<Atoms extends AtomMap> { interface ClientOptions<Atoms extends AtomMap> {
/** /**
@ -106,11 +111,11 @@ declare namespace CharmSync {
ignoreUnhydrated?: boolean; ignoreUnhydrated?: boolean;
} }
interface ServerOptions<Molecules extends MoleculeMap> { interface ServerOptions<Selectors extends SelectorMap> {
/** /**
* The atoms to synchronize with the client. * The atoms to synchronize with the client.
*/ */
atoms: Molecules; atoms: Selectors;
/** /**
* The interval at which to send patches to the client, in seconds. * 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 * 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; 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 * 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` * 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. * @param callback The function to call when the state changes.
* @returns A cleanup function that unsubscribes all listeners. * @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 * 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. * 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; 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. * 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) local types = require(script.types)
export type Atom<State> = types.Atom<State> export type Atom<State> = types.Atom<State>
export type Molecule<State> = types.Selector<State>
export type Selector<State> = types.Selector<State> export type Selector<State> = types.Selector<State>
export type Molecule<State> = types.Selector<State>
export type SyncPayload = types.SyncPayload export type SyncPayload = types.SyncPayload
return { return {

View file

@ -9,15 +9,15 @@ local function noop() end
cleans up the instance when the item is removed. Returns a cleanup function cleans up the instance when the item is removed. Returns a cleanup function
that unsubscribes all instances. 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. @param factory The function that tracks the lifecycle of each item.
@return A function that unsubscribes all instances. @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 connections: { [K]: () -> () } = {}
local unsubscribe = effect(function() local unsubscribe = effect(function()
local state = molecule() local state = callback()
for key, disconnect in next, connections do for key, disconnect in next, connections do
if state[key] == nil then if state[key] == nil then

View file

@ -100,8 +100,8 @@ end
Returns the result of the function without subscribing to changes. If a Returns the result of the function without subscribing to changes. If a
non-function value is provided, it is returned as is. non-function value is provided, it is returned as is.
@param molecule The atom or molecule to get the state of. @param callback The atom or selector to get the state of.
@param args Arguments to pass to the molecule. @param args Arguments to pass to the function.
@return The current state. @return The current state.
]=] ]=]
local function peek<T, U...>(callback: ((U...) -> T) | T, ...: U...): T 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 Captures all atoms that are read during the function call and returns them
along with the result of the function. Useful for tracking dependencies. 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. @return A tuple containing the captured atoms and the result of the function.
]=] ]=]
local function capture<T>(molecule: () -> T): (Set<Atom<any>>, T) local function capture<T>(callback: () -> T): (Set<Atom<any>>, T)
if listeners[molecule] then if listeners[callback] then
return { [molecule] = true }, peek(molecule) return { [callback] = true }, peek(callback)
end end
local dependencies: Set<Atom<any>> = {} local dependencies: Set<Atom<any>> = {}
@ -141,7 +141,7 @@ local function capture<T>(molecule: () -> T): (Set<Atom<any>>, T)
capturing.index += 1 capturing.index += 1
capturing.stack[capturing.index] = dependencies capturing.stack[capturing.index] = dependencies
local result = try(molecule, function() local result = try(callback, function()
capturing.stack[capturing.index] = nil capturing.stack[capturing.index] = nil
capturing.index -= 1 capturing.index -= 1
end) end)

View file

@ -3,39 +3,39 @@ local types = require(script.Parent.types)
type Selector<T> = types.Selector<T> 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 called with the current state and the previous state immediately after a
change occurs. change occurs.
@param molecule The atom or molecule to subscribe to. @param callback The atom or selector to subscribe to.
@param callback The function to call when the state changes. @param listener The function to call when the state changes.
@return A function that unsubscribes the callback. @return A function that unsubscribes the callback.
]=] ]=]
local function subscribe<T>(molecule: Selector<T>, callback: (state: T, prev: T) -> ()): () -> () local function subscribe<T>(callback: Selector<T>, listener: (state: T, prev: T) -> ()): () -> ()
local dependencies, state = store.capture(molecule) local dependencies, state = store.capture(callback)
local disconnected = false local disconnected = false
local function listener() local function handler()
local prevState = state local prevState = state
store.disconnect(dependencies, listener) store.disconnect(dependencies, handler)
dependencies, state = store.capture(molecule) dependencies, state = store.capture(callback)
if not disconnected then if not disconnected then
store.connect(dependencies, listener) store.connect(dependencies, handler)
end end
if state ~= prevState then if state ~= prevState then
callback(state, prevState) listener(state, prevState)
end end
end end
store.connect(dependencies, listener) store.connect(dependencies, handler)
return function() return function()
if not disconnected then if not disconnected then
disconnected = true disconnected = true
store.disconnect(dependencies, listener) store.disconnect(dependencies, handler)
end end
end 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. * component is re-rendered whenever the state changes.
* *
* If the `dependencies` array is provided, the subscription to the atom or * 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. * 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. * @param dependencies An array of values that the subscription depends on.
* @returns The current state. * @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) 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. component is re-rendered whenever the state changes.
If the `dependencies` array is provided, the subscription to the atom or 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. 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. @param dependencies An array of values that the subscription depends on.
@return The current state. @return The current state.
]=] ]=]
local function useAtom<State>(molecule: Charm.Molecule<State>, dependencies: { any }?): State local function useAtom<State>(callback: Charm.Selector<State>, dependencies: { any }?): State
local state, setState = React.useState(molecule) local state, setState = React.useState(callback)
React.useEffect(function() React.useEffect(function()
setState(molecule()) setState(callback())
return Charm.subscribe(molecule, setState) return Charm.subscribe(callback, setState)
end, dependencies or {}) end, dependencies or {})
return state 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. * 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. * @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. 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. @return The reactive source.
]=] ]=]
local function useAtom<State>(molecule: () -> State): State local function useAtom<State>(callback: () -> State): State
local state = Vide.source(molecule()) local state = Vide.source(callback())
local unsubscribe = Charm.subscribe(molecule, function(value) local unsubscribe = Charm.subscribe(callback, function(value)
task.spawn(state, value) task.spawn(state, value)
end) end)

View file

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

View file

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