first commit
This commit is contained in:
@@ -0,0 +1,90 @@
|
||||
# DeltaStateManager
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
## Purpose
|
||||
|
||||
`DeltaStateManager` keeps deep-cloned snapshots of previous values per state key, so that a delta encoder can diff the current value against its predecessor without worrying about caller-side mutations. It is the storage layer for `DeltaManager`; consumers usually go through `DeltaManager` rather than calling this class directly.
|
||||
|
||||
The store is bounded: an LRU eviction policy drops the oldest key once the configured capacity is reached. Default capacity is 100 keys, which is plenty for most workloads (one key per per-peer state stream). Re-storing under an existing key updates the value in place without advancing its LRU position — this matches upstream semantics so that frequently-updated keys do not unfairly evict rarely-updated ones.
|
||||
|
||||
## Features
|
||||
|
||||
- Deep-cloning on store: `Date`, all typed-array variants (`Uint8Array`, `Int8Array`, `Uint16Array`, `Int16Array`, `Uint32Array`, `Int32Array`, `Float32Array`, `Float64Array`), arrays, and plain objects are all isolated from caller mutations.
|
||||
- Insertion-order LRU eviction; `maxStates` configurable (default 100).
|
||||
- `clear()`, `clearState(key)`, `getStateCount()`, `getMaxStates()` accessors for inspection and reset.
|
||||
- Zero `Math.random` / `Date.now` calls — fully deterministic given identical inputs.
|
||||
|
||||
## Usage: Basic Example
|
||||
|
||||
```typescript
|
||||
import { DeltaStateManager } from "@suaveplan/bitpacker-delta";
|
||||
|
||||
const states = new DeltaStateManager(100);
|
||||
|
||||
states.storePreviousState("player-1", { x: 0, y: 0 });
|
||||
const previous = states.getPreviousState("player-1");
|
||||
// → { x: 0, y: 0 }
|
||||
```
|
||||
|
||||
## Usage: Advanced Example
|
||||
|
||||
```typescript
|
||||
import { DeltaStateManager } from "@suaveplan/bitpacker-delta";
|
||||
|
||||
const states = new DeltaStateManager(3);
|
||||
states.storePreviousState("a", { v: 1 });
|
||||
states.storePreviousState("b", { v: 2 });
|
||||
states.storePreviousState("c", { v: 3 });
|
||||
states.storePreviousState("d", { v: 4 }); // evicts "a"
|
||||
|
||||
states.hasPreviousState("a"); // → false (evicted)
|
||||
states.hasPreviousState("d"); // → true
|
||||
|
||||
// Mutating the original after storing does NOT touch the snapshot:
|
||||
const original = { x: 0 };
|
||||
states.storePreviousState("e", original);
|
||||
original.x = 999;
|
||||
states.getPreviousState("e"); // → { x: 0 }
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
### `new DeltaStateManager(maxStates?: number)`
|
||||
|
||||
- `maxStates` — positive integer; default `100`. Throws `RangeError` if non-integer or `< 1`.
|
||||
|
||||
### `storePreviousState(key: string, state: unknown): void`
|
||||
|
||||
Stores a deep-cloned copy of `state` under `key`. If `key` already exists, the value is updated in place; LRU position is unchanged. If the store is at capacity and this is a new key, the oldest key is evicted.
|
||||
|
||||
### `getPreviousState(key: string): unknown | undefined`
|
||||
|
||||
Returns the stored snapshot, or `undefined` if the key has never been stored or has been evicted.
|
||||
|
||||
### `hasPreviousState(key: string): boolean`
|
||||
|
||||
True if a snapshot exists for `key`.
|
||||
|
||||
### `clear(): void`
|
||||
|
||||
Drops every snapshot.
|
||||
|
||||
### `clearState(key: string): void`
|
||||
|
||||
Drops the snapshot for one key. No-op if absent.
|
||||
|
||||
### `getStateCount(): number`
|
||||
|
||||
Number of stored snapshots.
|
||||
|
||||
### `getMaxStates(): number`
|
||||
|
||||
Configured maximum capacity.
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
The deep-clone walker handles the eight typed-array variants explicitly because the generic object-clone path (`Object.hasOwn` over keys) does not preserve `Uint8Array`-vs-plain-array distinction. Maps and Sets are intentionally NOT cloned: delta-state semantics assume keys are strings and values are plain data; if a consumer needs `Map`/`Set`, they should canonicalise to a plain object first.
|
||||
|
||||
Performance: insertion is O(1) amortised; eviction is O(1) (Array `.shift()` on a small bounded array). Lookup is O(1). The `stateKeys` array tracks insertion order without using a `Map`'s native iteration (some legacy environments don't preserve insertion order — though every modern runtime does — so the explicit array is defensive).
|
||||
Reference in New Issue
Block a user