mirror of
https://github.com/littensy/charm.git
synced 2026-10-21 11:44:08 +00:00
refactor: add docs, remove some modules
This commit is contained in:
parent
027c5bf8f8
commit
7a63993293
19 changed files with 558 additions and 180 deletions
|
|
@ -6,6 +6,10 @@ type SyncPayload = types.SyncPayload
|
|||
|
||||
local atomRegister: { [string]: Charm.Atom } = {}
|
||||
|
||||
--- Registers atoms to be kept in sync with the server. Atoms with matching
|
||||
--- keys on the server will be synced to these atoms.
|
||||
---
|
||||
--- @param atoms An object mapping string keys to atoms to be synced.
|
||||
local function syncAtoms(atoms: { [string]: Charm.Atom })
|
||||
for key, atom in atoms do
|
||||
if atomRegister[key] and atomRegister[key] ~= atom then
|
||||
|
|
@ -17,6 +21,11 @@ local function syncAtoms(atoms: { [string]: Charm.Atom })
|
|||
end
|
||||
end
|
||||
|
||||
--- Unregisters one or more atoms from being kept in sync with the server.
|
||||
--- The atoms will retain their current values, but will no longer receive
|
||||
--- updates from the server.
|
||||
---
|
||||
--- @param keys The keys of the atoms to stop syncing.
|
||||
local function unsyncAtoms(...: string)
|
||||
for index = 1, select("#", ...) do
|
||||
local key = select(index, ...)
|
||||
|
|
@ -24,6 +33,9 @@ local function unsyncAtoms(...: string)
|
|||
end
|
||||
end
|
||||
|
||||
--- Unregisters all atoms from being kept in sync with the server. The
|
||||
--- atoms will retain their current values, but will no longer receive
|
||||
--- updates from the server.
|
||||
local function reset()
|
||||
table.clear(atomRegister)
|
||||
end
|
||||
|
|
@ -51,6 +63,11 @@ local function applyPatchPayload(data: { [string | number]: any })
|
|||
end
|
||||
end
|
||||
|
||||
--- Applies one or more payloads received from the server to the synced
|
||||
--- atoms. Payloads are state patches or initial state updates sent by the
|
||||
--- server, and are usually received via a remote event.
|
||||
---
|
||||
--- @param payloads The payloads to apply.
|
||||
local function applyPayloads(payloads: { SyncPayload })
|
||||
for _, payload in payloads do
|
||||
if payload.type == "init" then
|
||||
|
|
|
|||
|
|
@ -2,6 +2,11 @@ type NestedAtomMap = {
|
|||
[string]: NestedAtomMap | () -> any,
|
||||
}
|
||||
|
||||
--- Flattens a nested atom map into a single object with slash-separated keys.
|
||||
--- Useful for recursively collecting atoms returned by modules.
|
||||
---
|
||||
--- @param atoms The nested atom map to flatten.
|
||||
--- @return A flattened atom map.
|
||||
local function flatten(atoms: NestedAtomMap): { [string]: () -> any }
|
||||
local result: { [string]: () -> any } = {}
|
||||
|
||||
|
|
|
|||
128
packages/charm-sync/src/index.d.ts
vendored
128
packages/charm-sync/src/index.d.ts
vendored
|
|
@ -26,9 +26,6 @@ type DataTypes = {
|
|||
[P in keyof CheckableTypes as P extends keyof CheckablePrimitives ? never : P]: CheckableTypes[P];
|
||||
};
|
||||
|
||||
/**
|
||||
* A type that should not be made partial in patches.
|
||||
*/
|
||||
type DataType = DataTypes[keyof DataTypes];
|
||||
|
||||
/**
|
||||
|
|
@ -63,14 +60,8 @@ export type SyncPayload<Selectors extends SelectorMap = SelectorMap, Serialize e
|
|||
| { type: "patch"; data: SyncPatch<StateOfMap<Selectors>, Serialize> };
|
||||
|
||||
/**
|
||||
* Checks whether a value is `None`. If `true`, the value is scheduled to be
|
||||
* removed from the state when the patch is applied.
|
||||
*
|
||||
* @param value The value to check.
|
||||
* @returns `true` if the value is `None`, otherwise `false`.
|
||||
* Global configuration options that affect the behavior of Charm Sync.
|
||||
*/
|
||||
export function isNone(value: unknown): value is None;
|
||||
|
||||
export const config: {
|
||||
/**
|
||||
* The interval at which to send patches to the client, in seconds.
|
||||
|
|
@ -108,49 +99,142 @@ export const config: {
|
|||
validatePatches: boolean;
|
||||
};
|
||||
|
||||
/**
|
||||
* @client
|
||||
*/
|
||||
export namespace client {
|
||||
/**
|
||||
* Registers atoms to be kept in sync with the server. Atoms with matching
|
||||
* keys on the server will be synced to these atoms.
|
||||
*
|
||||
* @param atoms An object mapping string keys to atoms to be synced.
|
||||
*/
|
||||
export function syncAtoms(atoms: { [key: string]: Atom<any> }): void;
|
||||
|
||||
/**
|
||||
* Unregisters one or more atoms from being kept in sync with the server.
|
||||
* The atoms will retain their current values, but will no longer receive
|
||||
* updates from the server.
|
||||
*
|
||||
* @param keys The keys of the atoms to stop syncing.
|
||||
*/
|
||||
export function unsyncAtoms(...keys: string[]): void;
|
||||
|
||||
/**
|
||||
* Unregisters all atoms from being kept in sync with the server. The
|
||||
* atoms will retain their current values, but will no longer receive
|
||||
* updates from the server.
|
||||
*/
|
||||
export function reset(): void;
|
||||
|
||||
/**
|
||||
* Applies one or more payloads received from the server to the synced
|
||||
* atoms. Payloads are state patches or initial state updates sent by the
|
||||
* server, and are usually received via a remote event.
|
||||
*
|
||||
* @param payloads The payloads to apply.
|
||||
*/
|
||||
export function applyPayloads<Atoms extends { [key: string]: Atom<any> }, Serialize extends boolean = true>(
|
||||
payloads: SyncPayload<Atoms, Serialize>[],
|
||||
): void;
|
||||
}
|
||||
|
||||
/**
|
||||
* @server
|
||||
*/
|
||||
export namespace server {
|
||||
/**
|
||||
* Registers atoms to be kept in sync with a specific client. Should be
|
||||
* called when a player joins the game to start syncing atoms to them. Call
|
||||
* `unsyncAtomsFromClient` or `unsyncClient` to stop syncing.
|
||||
*
|
||||
* Each atom should be assigned a unique key, allowing the server to re-use
|
||||
* patches between multiple clients.
|
||||
*
|
||||
* @param client The player to sync atoms to.
|
||||
* @param atoms An object mapping string keys to atoms to be synced.
|
||||
*/
|
||||
export function syncAtomsToClient(client: Player, atoms: { [key: string]: Atom<any> }): void;
|
||||
|
||||
/**
|
||||
* Unregisters one or more atoms from being kept in sync with a specific
|
||||
* client.
|
||||
*
|
||||
* @param client The player to stop syncing atoms to.
|
||||
* @param keys The keys of the atoms to stop syncing.
|
||||
*/
|
||||
export function unsyncAtomsFromClient(client: Player, ...keys: string[]): void;
|
||||
|
||||
/**
|
||||
* Unregisters all atoms from being kept in sync with a specific client.
|
||||
* Should be called when a player leaves the game.
|
||||
*
|
||||
* @param client The player to stop syncing atoms to.
|
||||
*/
|
||||
export function unsyncClient(client: Player): void;
|
||||
|
||||
/**
|
||||
* Starts automatically syncing registered atoms to clients. Patches will
|
||||
* be sent to clients whenever the atoms are updated.
|
||||
*
|
||||
* @param onSync The function to call when patches can be sent to a client.
|
||||
* @returns `stopSync()`
|
||||
*/
|
||||
export function startSync<Atoms extends { [key: string]: Atom<any> }, Serialize extends boolean = true>(
|
||||
onSync: (client: Player, payloads: SyncPayload<Atoms, Serialize>[]) => void,
|
||||
): Cleanup;
|
||||
|
||||
/**
|
||||
* Stops automatically syncing registered atoms to clients. Patches will
|
||||
* no longer be sent to clients until `startSync` is called again.
|
||||
*/
|
||||
export function stopSync(): void;
|
||||
|
||||
/**
|
||||
* Immediately sends pending updates and initial states to all clients.
|
||||
* Normally, updates are sent at the interval specified in `config.interval`.
|
||||
* Calling this function forces all pending updates to be sent immediately.
|
||||
*/
|
||||
export function flush(): void;
|
||||
|
||||
/**
|
||||
* Unregisters all atoms from being kept in sync with all clients, and
|
||||
* stops automatic syncing.
|
||||
*/
|
||||
export function reset(): void;
|
||||
}
|
||||
|
||||
export namespace patch {
|
||||
export function isNone(value: unknown): value is None;
|
||||
|
||||
/**
|
||||
* Converts `nil` values to the special `None` symbol. This is useful when
|
||||
* preparing data for serialization, since `nil` can represent both the absence
|
||||
* of a value and the removal of a value in patches.
|
||||
*/
|
||||
export function nilToNone<T>(value: T | undefined): T | None;
|
||||
|
||||
/**
|
||||
* Compares two states and returns a patch that can be applied to the
|
||||
* current state to produce the next state.
|
||||
*
|
||||
* @param currentState The current state.
|
||||
* @param nextState The next state.
|
||||
* @returns A patch that can be applied to the current state.
|
||||
*/
|
||||
export function diff<State, Serialize extends boolean = true>(
|
||||
currentState: State,
|
||||
nextState: State,
|
||||
): SyncPatch<State, Serialize>;
|
||||
|
||||
/**
|
||||
* Applies a patch to a state and returns the resulting value.
|
||||
*
|
||||
* @param currentState The current state.
|
||||
* @param statePatch The patches to apply.
|
||||
* @returns The new state with the patch applied.
|
||||
*/
|
||||
export function apply<State, Serialize extends boolean = true>(
|
||||
state: State,
|
||||
patch: SyncPatch<State, Serialize>,
|
||||
currentState: State,
|
||||
statePatch: SyncPatch<State, Serialize>,
|
||||
): State;
|
||||
}
|
||||
|
||||
|
|
@ -170,7 +254,10 @@ type UnionToIntersection<U> = (U extends any ? (k: U) => void : never) extends (
|
|||
|
||||
type IntersectValues<T> = UnionToIntersection<{ readonly [K in keyof T]: T[K] }[keyof T]>;
|
||||
|
||||
type FlattenNestedAtoms<Atoms extends NestedAtomMap, Path extends Key | undefined = undefined> = IntersectValues<{
|
||||
export type FlattenNestedAtoms<
|
||||
Atoms extends NestedAtomMap,
|
||||
Path extends Key | undefined = undefined,
|
||||
> = IntersectValues<{
|
||||
readonly [Name in keyof Atoms as AppendPath<Path, Name>]: Atoms[Name] extends Atom<any>
|
||||
? { readonly [K in AppendPath<Path, Name>]: Atoms[Name] }
|
||||
: Atoms[Name] extends NestedAtomMap
|
||||
|
|
@ -183,6 +270,15 @@ type FlattenNestedAtoms<Atoms extends NestedAtomMap, Path extends Key | undefine
|
|||
* Useful for recursively collecting atoms returned by modules.
|
||||
*
|
||||
* @param atoms The nested atom map to flatten.
|
||||
* @return A flattened atom map.
|
||||
* @returns A flattened atom map.
|
||||
*/
|
||||
export function flatten<Atoms extends NestedAtomMap>(atoms: Atoms): FlattenNestedAtoms<Atoms>;
|
||||
|
||||
/**
|
||||
* Checks whether a value is `None`. If `true`, the value is scheduled to be
|
||||
* removed from the state when the patch is applied.
|
||||
*
|
||||
* @param value The value to check.
|
||||
* @returns `true` if the value is `None`, otherwise `false`.
|
||||
*/
|
||||
export function isNone(value: unknown): value is None;
|
||||
|
|
|
|||
|
|
@ -11,7 +11,11 @@ export type None = types.None
|
|||
--- deleted values in patches.
|
||||
local None: None = { __none = "__none" }
|
||||
|
||||
--- Checks if a value is the special `None` symbol.
|
||||
--- Checks whether a value is `None`. If `true`, the value is scheduled to be
|
||||
--- removed from the state when the patch is applied.
|
||||
---
|
||||
--- @param value The value to check.
|
||||
--- @return `true` if the value is `None`, otherwise `false`.
|
||||
local function isNone(value: any): boolean
|
||||
return type(value) == "table" and value.__none == "__none"
|
||||
end
|
||||
|
|
@ -48,30 +52,30 @@ local function stringifySparseArray(object: { any }): { [string | number]: any }
|
|||
return dictionary
|
||||
end
|
||||
|
||||
--- Compares two states and returns a patch that can be applied to the previous
|
||||
--- Compares two states and returns a patch that can be applied to the current
|
||||
--- state to produce the next state.
|
||||
---
|
||||
--- @param prevState The previous state.
|
||||
--- @param currentState The current state.
|
||||
--- @param nextState The next state.
|
||||
--- @returns A patch that can be applied to the previous state.
|
||||
local function diff(prevState: any, nextState: any): any
|
||||
if prevState == nextState then
|
||||
--- @return A patch that can be applied to the current state.
|
||||
local function diff(currentState: any, nextState: any): any
|
||||
if currentState == nextState then
|
||||
return nil
|
||||
elseif nextState == nil then
|
||||
return None
|
||||
elseif type(prevState) ~= "table" or type(nextState) ~= "table" then
|
||||
elseif type(currentState) ~= "table" or type(nextState) ~= "table" then
|
||||
return nextState
|
||||
end
|
||||
|
||||
local patches: { [any]: any } = table.clone(nextState)
|
||||
|
||||
for key, previous in prevState do
|
||||
for key, previous in currentState do
|
||||
patches[key] = diff(previous, nextState[key])
|
||||
end
|
||||
|
||||
if config.autoSerialize then
|
||||
-- Coerce sparse array patches into dictionaries
|
||||
if prevState[1] ~= nil or nextState[1] ~= nil then
|
||||
if currentState[1] ~= nil or nextState[1] ~= nil then
|
||||
patches = stringifySparseArray(patches)
|
||||
end
|
||||
|
||||
|
|
@ -85,20 +89,20 @@ local function diff(prevState: any, nextState: any): any
|
|||
return patches
|
||||
end
|
||||
|
||||
--- Applies a patch to a state and returns the next state.
|
||||
--- Applies a patch to a state and returns the resulting value.
|
||||
---
|
||||
--- @param prevState The current state.
|
||||
--- @param currentState The current state.
|
||||
--- @param statePatch The patches to apply.
|
||||
--- @returns The next state.
|
||||
local function apply(prevState: any, statePatch: any): any
|
||||
--- @return The new state with the patch applied.
|
||||
local function apply(currentState: any, statePatch: any): any
|
||||
if isNone(statePatch) then
|
||||
return nil
|
||||
elseif type(prevState) ~= "table" or type(statePatch) ~= "table" then
|
||||
elseif type(currentState) ~= "table" or type(statePatch) ~= "table" then
|
||||
return statePatch
|
||||
end
|
||||
|
||||
local nextState = table.clone(prevState)
|
||||
local stateIsArray = prevState[1] ~= nil
|
||||
local nextState = table.clone(currentState)
|
||||
local stateIsArray = currentState[1] ~= nil
|
||||
|
||||
for key, value in statePatch do
|
||||
-- Diff-checking an array produces a sparse array, which will not be
|
||||
|
|
|
|||
|
|
@ -20,7 +20,9 @@ local LISTENING_FOR_CHANGES: SyncStatus = 1
|
|||
local atomRegister: { [string]: RegisteredAtom } = {}
|
||||
local clientRegister: { [any]: { [string]: SyncStatus } } = {}
|
||||
local globalUpdates: { { key: string, value: any, previousValue: any } } = {}
|
||||
|
||||
local connection: RBXScriptConnection?
|
||||
local onSyncCallback: (client: any, payloads: { SyncPayload }) -> ()?
|
||||
|
||||
local function registerAtom<T>(key: string, atom: () -> T)
|
||||
if atomRegister[key] then
|
||||
|
|
@ -196,6 +198,15 @@ local function createPayloadsForClient(client: any, patchData: any): { SyncPaylo
|
|||
return payloads
|
||||
end
|
||||
|
||||
--- Registers atoms to be kept in sync with a specific client. Should be
|
||||
--- called when a player joins the game to start syncing atoms to them. Call
|
||||
--- `unsyncAtomsFromClient` or `unsyncClient` to stop syncing.
|
||||
---
|
||||
--- Each atom should be assigned a unique key, allowing the server to re-use
|
||||
--- patches between multiple clients.
|
||||
---
|
||||
--- @param client The player to sync atoms to.
|
||||
--- @param atoms An object mapping string keys to atoms to be synced.
|
||||
local function syncAtomsToClient(client: any, atoms: { [string]: () -> any })
|
||||
if not clientRegister[client] then
|
||||
clientRegister[client] = {}
|
||||
|
|
@ -214,10 +225,19 @@ local function syncAtomsToClient(client: any, atoms: { [string]: () -> any })
|
|||
end
|
||||
end
|
||||
|
||||
--- Unregisters all atoms from being kept in sync with a specific client.
|
||||
--- Should be called when a player leaves the game.
|
||||
---
|
||||
--- @param client The player to stop syncing atoms to.
|
||||
local function unsyncClient(client: any)
|
||||
clientRegister[client] = nil
|
||||
end
|
||||
|
||||
--- Unregisters one or more atoms from being kept in sync with a specific
|
||||
--- client.
|
||||
---
|
||||
--- @param client The player to stop syncing atoms to.
|
||||
--- @param keys The keys of the atoms to stop syncing.
|
||||
local function unsyncAtomsFromClient(client: any, ...: string)
|
||||
local syncedAtoms = clientRegister[client]
|
||||
|
||||
|
|
@ -235,11 +255,14 @@ local function unsyncAtomsFromClient(client: any, ...: string)
|
|||
end
|
||||
end
|
||||
|
||||
local function onSyncCallback(_client: any, _payloads: { SyncPayload })
|
||||
warn("[Charm] Override the onSync function by passing a custom function to startSync")
|
||||
end
|
||||
|
||||
--- Immediately sends pending updates and initial states to all clients.
|
||||
--- Normally, updates are sent at the interval specified in `config.interval`.
|
||||
--- Calling this function forces all pending updates to be sent immediately.
|
||||
local function flush()
|
||||
if not onSyncCallback then
|
||||
return
|
||||
end
|
||||
|
||||
local patches = if config.preserveHistory then createHistoricalPatches() else createPatch()
|
||||
|
||||
-- While payload objects are technically mutable as they are unique to
|
||||
|
|
@ -266,6 +289,8 @@ local function flush()
|
|||
table.clear(globalUpdates)
|
||||
end
|
||||
|
||||
--- Stops automatically syncing registered atoms to clients. Patches will
|
||||
--- no longer be sent to clients until `startSync` is called again.
|
||||
local function stopSync()
|
||||
if connection then
|
||||
connection:Disconnect()
|
||||
|
|
@ -273,6 +298,8 @@ local function stopSync()
|
|||
end
|
||||
end
|
||||
|
||||
--- Unregisters all atoms from being kept in sync with all clients, and
|
||||
--- stops automatic syncing.
|
||||
local function reset()
|
||||
table.clear(atomRegister)
|
||||
table.clear(clientRegister)
|
||||
|
|
@ -280,6 +307,11 @@ local function reset()
|
|||
stopSync()
|
||||
end
|
||||
|
||||
--- Starts automatically syncing registered atoms to clients. Patches will
|
||||
--- be sent to clients whenever the atoms are updated.
|
||||
---
|
||||
--- @param onSync The function to call when patches can be sent to a client.
|
||||
--- @return `stopSync()`
|
||||
local function startSync(onSync: (client: any, payloads: { SyncPayload }) -> ()): () -> ()
|
||||
if connection then
|
||||
connection:Disconnect()
|
||||
|
|
|
|||
|
|
@ -14,4 +14,4 @@ export type SyncPayload =
|
|||
--- differentiate between a value that was set to `nil` and an unchanged value.
|
||||
export type None = { __none: "__none" }
|
||||
|
||||
return nil
|
||||
return {}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue