import type { SCRTIntVector, SCRTStringDictionary, TSciChart } from "../../types/TSciChart"; /** * The code stored for a row whose value is missing or empty. Reads back as `""`, never as * `undefined` - `undefined` is reserved by callers for an *unknown column*, which is a different * thing from a known column with a blank cell. */ export declare const MISSING_STRING_CODE = -1; /** * @summary Dictionary-encoded storage for ONE string column: an `SCRTIntVector` (or * `SCRTIntFifoVector`) of per-row Int32 codes, plus an `SCRTStringDictionary` holding each distinct * value once. * * @description * The store is deliberately self-contained: it owns one column's codes, its dictionary and the two * JS-side caches that make reads cheap, and knows nothing about data series, rendering or queries. * {@link BaseDataSeries} delegates to it. * * Two caches sit alongside the native storage: * - `jsDictCache` decodes each distinct value to a JS string ONCE, so a per-row read never crosses * the wasm boundary or allocates. * - `labelToCode` encodes an incoming string to its code without a native lookup. * * Both are rebuilt from the remap whenever {@link compactIfNeeded} moves codes. * * **Refcounting.** Every value that enters the column is `AddRef`'d and every value that leaves is * `Release`'d - on overwrite, on remove, and on FIFO eviction. That is what lets the dictionary * tell a live entry from a dead one, and therefore what makes {@link compactIfNeeded} correct. * Both native calls ignore out-of-range codes (including {@link MISSING_STRING_CODE}), so no call * site needs a guard. * * **FIFO.** With `fifoCapacity` set, codes live in an `SCRTIntFifoVector` and the oldest row is * evicted when the ring wraps. The wrap arithmetic belongs to the native vector: `get`/`set` take * LOGICAL indices and resolve the wrap themselves. Inserts and removes are not supported on a FIFO * column (neither is the underlying vector - it throws). * * @ignore Implementation detail of {@link BaseDataSeries}, not documented public API. The class is * exported so typings resolve, but the supported surface is the string-column methods on * `BaseDataSeries` - `isStringColumn`, `getTextAt`, `getStringValuesCopy`, `setStringColumnValues`. * Reach for this class directly and you own the refcounting and compaction contracts yourself. */ export declare class StringColumnStore { /** The column name this store holds values for. Identity only; nothing here dispatches on it. */ readonly name: string; private readonly wasmContext; /** Ring capacity, or `undefined` for an unbounded column. */ private readonly fifoCapacity; private codes; private dictionary; /** code -> value, decoded once. Index is the code; never sparse below `dictionary.Count()`. */ private jsDictCache; /** value -> code, the inverse of {@link jsDictCache}. */ private labelToCode; /** * Nesting depth of {@link suspendCompaction}. While above zero {@link compactIfNeeded} is a * no-op - see that method for why a live filter must be able to hold compaction off. */ private compactionSuspendCount; private isDeleted; /** * @param wasmContext The wasm context that owns the native codes vector and dictionary. * @param name The column name. Identity only. * @param fifoCapacity When set, codes are stored in a ring of this many rows and the oldest row * is evicted on overflow. Must be greater than zero. */ constructor(wasmContext: TSciChart, name: string, fifoCapacity?: number); /** The number of rows stored. */ count(): number; /** True when this column stores its codes in a ring buffer of a fixed capacity. */ get isFifo(): boolean; /** * Appends values, encoding each to its dictionary code. On a FIFO column this evicts (and * releases) as many oldest rows as it needs to. */ appendValues(values: string[]): void; /** * Appends `count` rows with no value. Writes {@link MISSING_STRING_CODE}; no refcount changes, * because a missing cell references no dictionary entry. */ appendEmpty(count: number): void; /** Inserts values at a row index, shifting later rows up. Not supported on a FIFO column. */ insertValues(index: number, values: string[]): void; /** Inserts `count` rows with no value at a row index. Not supported on a FIFO column. */ insertEmpty(index: number, count: number): void; /** Replaces one row's value, leaving the row count unchanged. */ setValueAt(index: number, value: string): void; /** * The decoded value at a row index. Returns `""` for a missing cell or an out-of-range index - * never `undefined`, which callers reserve for an unknown column name. */ getTextAt(index: number): string; /** * Materialises the whole column as JS strings (missing cells become `""`). A copy, for export * and serialisation - the per-row read path is {@link getTextAt}. */ getValuesCopy(): string[]; /** * A zero-copy Int32 view onto the codes. Re-fetch it after any mutation - it is invalidated by * a heap growth, and its contents change under a {@link compactIfNeeded}. * * @remarks On a FIFO column this is the ring in PHYSICAL order: pair it with * `(getCodes() as SCRTIntFifoVector).getStartIndex()`, exactly as the numeric FIFO path does. * The two orders coincide until the ring first wraps. */ getCodesView(): Int32Array; /** The decode-once code -> value cache. Heap-independent, so it survives a heap growth. */ getDictCache(): readonly string[]; /** The native codes vector. Borrowed, not transferred - this store still owns and deletes it. */ getCodes(): SCRTIntVector; /** The native dictionary. Borrowed, not transferred - this store still owns and deletes it. */ getDictionary(): SCRTStringDictionary; /** Removes one row, releasing its value. Not supported on a FIFO column. */ removeAt(index: number): void; /** Removes a run of rows, releasing each value. Not supported on a FIFO column. */ removeRange(startIndex: number, count: number): void; /** Drops every row AND every distinct value, returning the column to its constructed state. */ clear(): void; /** * Reclaims the bytes of dictionary entries no row references any more, when that is worth * doing, and rewrites this column's codes onto their new values. * * @returns true if it compacted. * * @remarks The threshold is a policy decision, not a formality - see the * COMPACT_MIN_DEAD_TO_LIVE_RATIO and COMPACT_MIN_DEAD_ENTRIES constants in this file. This is caller * driven on purpose: compaction MOVES codes, so only a caller that knows nothing is holding a * code across the call can decide when it is safe. * * That is also why {@link suspendCompaction} exists. `SCRTFilterSet` keeps a `oneOf` filter's * selection as a by-value snapshot of dictionary codes (`SCRTFilterSet.h:179`, populated at * `.cpp:140`) with no gate that re-resolves it when the dictionary changes - after a compaction * that snapshot names different strings and the filter silently selects the wrong rows. This * store cannot see filters, and must not learn about the query layer to find out, so whoever * owns a live `oneOf` filter over this column must hold compaction off for its lifetime. * Deferring reclamation while the user is looking at a filtered view is harmless. */ compactIfNeeded(): boolean; /** * Holds {@link compactIfNeeded} off until the matching {@link resumeCompaction}. Nests. * Callers that hold a dictionary code across a span - a live `oneOf` filter above all - must * bracket that span with this pair. See {@link compactIfNeeded}. */ suspendCompaction(): void; /** Releases one {@link suspendCompaction}. Never goes below zero. */ resumeCompaction(): void; /** True while at least one {@link suspendCompaction} is outstanding. */ isCompactionSuspended(): boolean; /** Frees the native codes vector and dictionary. Idempotent; the store is unusable afterwards. */ delete(): void; /** * Encodes one value to its code, adding it to the dictionary on first sight, and takes the * reference the row about to hold it owns. An empty (or absent) value is * {@link MISSING_STRING_CODE} and references nothing. */ private encode; /** Decodes one code through the JS cache. Missing / unknown codes read back as `""`. */ private decode; /** * Releases the values of the rows an append of `count` (never more than `fifoCapacity`) rows is * about to overwrite. The evicted rows are always the oldest, which are logical indices * `0..evicted`, so the ring resolves their slots itself. */ private releaseEvictedBy; /** * Writes already-encoded codes onto the ring in at most two bulk memory writes - one boundary * crossing each, rather than one per row - then tells the ring what was written. * * An EXACT MIRROR of `FIFOVectorProvider.appendArray`, and it has to be: a series' numeric and * string rings are drawn together in RAW order by sweeping mode, which pairs numeric slot *k* * with string slot *k*. Two rings can each be in the right logical order and still render every * label against the wrong point if their physical layouts differ. * * @param codes The codes to write, already trimmed to at most `fifoCapacity`. * @param appendedCount How many rows the caller appended BEFORE that trim. A batch bigger than * the ring rewrites it from physical slot 0 (nothing that was there survives), and reporting * the untrimmed count is what tells `notifyAppend` so - exactly as the numeric path does. Any * other batch is written at the logical tail. */ private appendFifoCodes; /** * Reclaims the dead entries' bytes and moves every surviving code, then rewrites this column's * codes and both caches onto the new code space. The dictionary cannot do this itself: it does * not know who references it, which is exactly why it hands back a remap for the owner to * apply. */ private compact; /** * An Int32Array over exactly the rows this column holds, so that reads and bulk rewrites of the * codes cost one boundary crossing rather than one per row. * * On a FIFO column this is the ring in PHYSICAL order. `dataPtrZero()` plus `size()` covers the * live rows in both ring states: before the first wrap they occupy `[0, size)` with a start * index of 0, and after it `size` is the capacity, so it is the whole buffer. * * Writable on purpose - `vectorToArrayViewI32`'s view is documented read-only because a caller * cannot resize through it, which is not what {@link compact} does: that is an in-place rewrite * of the same length, with no wasm allocation in between to move the heap underneath it. */ private rawCodesView; private throwIfFifo; private throwIfDeleted; }