first commit
This commit is contained in:
103
docs/networking/drive/drive-mesh-protocol/src/select/form.md
Normal file
103
docs/networking/drive/drive-mesh-protocol/src/select/form.md
Normal file
@@ -0,0 +1,103 @@
|
||||
# selectForm
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
## Purpose
|
||||
|
||||
The form-selection helpers that pick the most compact `ChunkRequest` form for a given set of needed chunks. The mesh protocol defines three forms (`whole` / `specific` / `bitset`); `selectForm()` picks `specific` when the count is small and `bitset` when the count is large. The threshold (`SELECT_FORM_BITSET_THRESHOLD = 64`) matches the empirical bytes-on-wire break-even where a packed bitset becomes smaller than an array of 32-bit chunk ids. The module also ships `selectWhole()` for the unambiguous "request everything" case and `encodeBitset()` / `decodeBitset()` helpers for the bitset payload.
|
||||
|
||||
## Features
|
||||
|
||||
- Auto-picks the optimal form based on chunk count.
|
||||
- Rejects ambiguous empty-array requests with a `DMP.EMPTY_REQUEST` error so callers cannot accidentally express "nothing" as "specific with empty array."
|
||||
- Bitset encoding is zero-deps — no bitpacker dependency in this module — so the function is usable from environments that haven't loaded `@suaveplan/bitpacker-compressed` yet.
|
||||
- Final-byte padding bits are guaranteed zero per DMP-08.
|
||||
- Roundtrip property: `decodeBitset(encodeBitset(needed)) === needed.toSorted((a, b) => a - b)` for any non-empty list of non-negative integers.
|
||||
|
||||
## Usage: Basic Example
|
||||
|
||||
```ts
|
||||
import { selectForm } from "@suaveplan/drive-mesh-protocol/select";
|
||||
|
||||
const small = selectForm([0, 5, 17]);
|
||||
// → { kind: 'specific', chunkIds: [0, 5, 17] }
|
||||
|
||||
const large = selectForm(Array.from({ length: 100 }, (_, i) => i));
|
||||
// → { kind: 'bitset', bytes: <13-byte bitset> }
|
||||
```
|
||||
|
||||
## Usage: Advanced Example
|
||||
|
||||
```ts
|
||||
import {
|
||||
decodeBitset,
|
||||
encodeBitset,
|
||||
selectForm,
|
||||
selectWhole,
|
||||
SELECT_FORM_BITSET_THRESHOLD,
|
||||
} from "@suaveplan/drive-mesh-protocol/select";
|
||||
|
||||
function describePlan(needed: readonly number[]) {
|
||||
if (needed.length === 0) return selectWhole();
|
||||
return selectForm(needed);
|
||||
}
|
||||
|
||||
// Adaptive request: ask for everything on the first request, then
|
||||
// switch to selectForm() once we have a partial inventory.
|
||||
const firstRequest = describePlan([]);
|
||||
// → { kind: 'whole' }
|
||||
|
||||
const stillMissing = [200, 201, 202, 203, 204, 205];
|
||||
const followUp = describePlan(stillMissing);
|
||||
// → { kind: 'specific', chunkIds: [200, 201, 202, 203, 204, 205] }
|
||||
|
||||
// Roundtrip check
|
||||
const bitset = encodeBitset(stillMissing);
|
||||
const recovered = decodeBitset(bitset);
|
||||
console.log(recovered);
|
||||
// → [200, 201, 202, 203, 204, 205]
|
||||
|
||||
// Threshold introspection
|
||||
console.log(SELECT_FORM_BITSET_THRESHOLD); // 64
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
### `const SELECT_FORM_BITSET_THRESHOLD`
|
||||
|
||||
The literal `64`. `selectForm` returns `kind: 'specific'` when `needed.length <= 64` and `kind: 'bitset'` otherwise. Documented as a `const` so consumers can reference the threshold without hardcoding it.
|
||||
|
||||
### `function selectForm(needed): ChunkRequest`
|
||||
|
||||
- `needed: readonly number[]` — the chunk ids to request.
|
||||
- Returns `{ kind: 'specific', chunkIds }` when `needed.length <= SELECT_FORM_BITSET_THRESHOLD`.
|
||||
- Returns `{ kind: 'bitset', bytes }` otherwise.
|
||||
- Throws `DmpError(DMP.EMPTY_REQUEST)` when `needed.length === 0`.
|
||||
|
||||
### `function selectWhole(): ChunkRequest`
|
||||
|
||||
Returns the canonical `{ kind: 'whole' }` form. Use this for "request everything the holder has."
|
||||
|
||||
### `function encodeBitset(needed): Uint8Array`
|
||||
|
||||
- `needed: readonly number[]` — non-negative integers to set bits for.
|
||||
- Returns a `Uint8Array` of length `Math.ceil((max(needed) + 1) / 8)`.
|
||||
- Bit `i` is set iff `i ∈ needed`. Padding bits in the final byte are zero.
|
||||
- Throws `DmpError(DMP.BITSET_LENGTH_MISMATCH)` when any element is negative or non-integer.
|
||||
|
||||
### `function decodeBitset(bytes, bitLength?): number[]`
|
||||
|
||||
- `bytes: Uint8Array` — the packed bitset.
|
||||
- `bitLength?: number` — number of bits to consider. Defaults to `bytes.length * 8`.
|
||||
- Returns the sorted list of bit indexes that are set within `[0, bitLength)`.
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
The 64-chunk threshold is empirically chosen and documented in the design proposal ("Requirements with no source line"). At exactly 64 chunks, a 32-bit-per-id `specific` array is 256 bytes; a packed bitset covering chunks 0–63 is 8 bytes raw, ~6 bytes after `bitpacker-compressed` RLE. The cutover correctly favors `specific` when the target chunks are sparse (fewer than 64 of them, anywhere in the file) and `bitset` when they are dense (more than 64 of them).
|
||||
|
||||
The bitset format is little-endian within each byte: bit 0 of `bytes[0]` is chunk 0, bit 7 of `bytes[0]` is chunk 7, bit 0 of `bytes[1]` is chunk 8. This matches the most natural reading order for log dumps and is the de facto standard among suaveplan bit-packing helpers.
|
||||
|
||||
`encodeBitset` does NOT compress via `bitpacker-compressed` — the on-wire envelope encoding adds compression at the framing layer (`@suaveplan/bitpacker-network`). Encoding here keeps the function dependency-light so it is usable from contexts that haven't loaded the framing codec yet.
|
||||
|
||||
`decodeBitset` accepts an optional `bitLength` parameter so the caller can express "decode only the first N bits" — useful when the wire format declares `totalChunks` separately and padding bits past it must be ignored. When `bitLength` is omitted, every bit in the buffer is decoded.
|
||||
Reference in New Issue
Block a user