Reusable-key storage for values of one schema with direct lookup by numeric key and live-entry iteration.


Use

After "Pool" use, place an item-schema object before Pool, for example p: Int32 Pool;.

Pool

(Item -- pool) Creates an empty pool using the schema of Item.
  • The generated schema name is Pool<ItemSchemaName>. Each slot accommodates either an Item or an Int32 free-list link, including alignment padding.
  • The pool is managed: construction initializes empty storage, and destruction destroys live items before releasing allocated storage.
  • Item selects a non-Meta In-place schema and must permit default construction. For an existing prototype, supply a mutable view such as @prototype. A Ref prototype selects its referenced schema, not a stored-reference item schema.
  • Direct Text and Code items are unsupported; wrap them in a non-Meta Struct when needed.

Remarks

  • Pool objects are movable, not copyable. new through a mutable pool view transfers the contents and leaves the source empty.

Pool schema

The generated Dict contains static schema markers, mutable storage state, and the public methods below.

Fields

INIT initializes the data reference to NIL, dataSize 0, and firstFree -1.


Key lifecycle and live-entry iteration

Keys are zero-based Int32 values. Allocation size is separate from the number of live entries. The first insertion reserves eight slots; when no free slot remains, insertion reallocates to dataSize plus one quarter, rounded up to a multiple of eight, and references into the entry storage are invalidated by growth.

Growth invalidates all item references, but existing live keys continue to identify their items. Erasing a key invalidates references to that item only; erasing other keys does not move it. Clearing or destroying the owning pool invalidates its remaining item references. A reused key identifies a new item; valid does not distinguish generations of that key.

getSize

(-- count) Returns the number of allocated entry slots.
  • It returns dataSize, including free and never-used slots.

Remarks

  • getSize and size both return dataSize; neither counts live items. Count live items by walking firstValid/nextValid until the size sentinel.

size

(-- count) Returns the number of allocated entry slots.
  • It returns the same dataSize value as getSize, not the live-item count.

at

(key -- ref) Returns a reference to the live Item at key. Use a mutable pool view to obtain a mutable reference.
  • The key must be an Int32 in 0 .. size-1 and identify a live item. With DEBUG enabled, an out-of-range key reports Index is out of range!; an in-range free key reports Pool::at: element is invalid!.
  • The reference points into the pool's entry storage. Growth, erasure, clearing, and destruction invalidate references as described above.

valid

(key -- valid) Reports whether a key currently refers to a live Item.
  • key is an Int32 in 0 .. size-1. Direct valid checks known bounds even with DEBUG disabled; runtime range checks require DEBUG.
  • A key whose validity bit is clear returns FALSE; a live key returns TRUE.

Remarks

  • Run-time range and live-slot checks require DEBUG. Do not rely on at or erase to validate keys when DEBUG is disabled; even a known out-of-range key can bypass their assertion. Invalid input remains outside the contract and can corrupt the pool. Never erase a key twice without reinserting it.

firstValid

(-- key) Returns the lowest live key, or the allocation size when no live key exists.
  • It scans upward from zero and skips invalid slots.

nextValid

(key -- nextKey) For an Int32 key satisfying -1 <= key < size, returns the lowest live key greater than key, or size when no such key exists. Use -1 to search from the beginning.
  • It starts at key + 1 and skips invalid slots.
  • Stop at the size sentinel; passing it to nextValid returns size + 1, not the sentinel again.
  • A key below -1 fails with Index is out of range!.

getNextIndex

(-- key) Returns the key the next insert will use.
  • It returns firstFree when a reusable key exists; otherwise it returns dataSize.

insert

(item -- key) Inserts one Item into a free or newly allocated slot and returns its Int32 key.
  • Initializes the selected slot, then copies an immutable item view or moves a mutable one into it. The source must have the Item schema and support the selected operation.
  • When firstFree is nonnegative, insertion pops that free-list key and reuses it before allocating a new key.
  • Growth relocates existing slot storage byte-for-byte, without invoking item initialization, assignment, or destruction. Items must tolerate this relocation; references into pool storage are not adjusted.

Remarks

Known issue: when insert grows this pool before consuming an item reference into it, the reference is invalidated; the inserted item is read from released storage and holds an undefined value. Do not pass an item reference into this pool when insertion may grow it. Copy or move the item to a separate object before calling insert; 0 p.at new @p.insert is the workaround.

erase

(key --) Destroys the live Item at key and returns the key to the reusable-key list.
  • The key must be an Int32 in 0 .. size-1 and identify a live item.
  • The validity bit is cleared and the erased key becomes the new firstFree, so successive erases are reused in last-erased-first order.
  • With DEBUG enabled, an out-of-range key reports Index is out of range!; an in-range free key reports Pool::erase: element is invalid!.
  • Erasing does not reduce size or allocated memory.

clear

(--) Erases every live Item without releasing the pool's allocated storage.
  • Destroys live items in ascending key order. The cleared keys are reused in descending order before keys that were already free.

Low-level storage helpers

These methods expose pool addresses and validity storage. The caller must satisfy the explicit bounds, liveness, and slot-state conditions below; the helpers do not validate them.

getAddressByIndex

(key -- address) Returns the entry base address plus key times entrySize, as a Natx value. No bounds or validity check is performed.

getTailAddressByIndex

(position -- address) Returns the entry base address plus size times entrySize plus position, as a Natx value. position is a byte offset into the validity bitmap, not an item key; no bounds check is performed.

elementAt

(key -- ref) Returns an Item reference for the slot at key, without checking its validity. Reference mutability follows the pool access path.
  • No bounds, liveness, or initialization check is performed. Access an Item through the returned reference only for a live key in 0 .. size-1.

nextFreeAt

(key -- ref) Returns a mutable Int32 reference to the free-list link stored in the slot at key.
  • The key must select a free slot in 0 .. size-1 before reading or writing its link. The link is the next free key or -1; accessing a live slot's link aliases its Item storage. No bounds or validity check is performed.

validAt

(position -- ref) Returns a mutable Nat8 reference to the validity bitmap byte at position.
  • The byte position must be in 0 .. size/8-1 before access. Key k uses byte k / 8 and bit k mod 8. No bounds check is performed.

Remarks

  • nextFreeAt and validAt return mutable references even through an immutable pool view. Writes must preserve the free-list and validity-bitmap invariants.

Iterator next result schemas

makeIter

(pool method -- iter) Builds an iterator over live pool keys using method to shape each key/item result.
  • Supply pool explicitly; selecting makeIter from a receiver does not supply that argument. The explicit argument is retained in the iterator.
  • method is stored as a static field and has stack effect (key itemRef -- result). It must consume both inputs and return one item. Block methods run through a call boundary, not inline.
  • The method receives the current key followed by the Item reference; reference mutability follows the supplied pool view. The iterator starts at firstValid and skips invalid entries.
  • On exhaustion, method is still called with key 0 and a NIL Item reference; its result is followed by FALSE. It must handle that input without dereferencing NIL and may be called again on later exhausted calls. When validity is unknown, successful and exhausted callback results must have matching schemas.

Remarks

  • When the explicit pool argument is a view, the iterator borrows that pool; keep it alive for the iterator's lifetime.

iter

(-- iter) Returns an iterator of live {key value} records in ascending key order.
  • iter.next returns record valid. Each record's value is an Item reference whose mutability follows the pool view; only records paired with TRUE identify live entries.
  • At exhaustion, iter supplies a record with key equal to 0 and a NIL Item reference as value, followed by FALSE.

Remarks

  • Iterators returned by iter, keys, and values borrow the pool, which must outlive them. They are not snapshots. Keep pool membership unchanged during a traversal: erasing a cached next key can make next fail, and insertion can change whether an exhausted iterator has more results. Updating an item's value does not change membership.
  • These iterators do not expose size. algorithm.count on the pool or one of these iterators counts live entries.

keys

(-- iter) Returns an iterator of live keys in ascending numeric order.
  • keys.next returns key valid. Keys that are free when the iterator advances are skipped; only a key paired with TRUE identifies a live entry.
  • At exhaustion, keys supplies 0, followed by FALSE.

values

(-- iter) Returns an iterator of live Item references in ascending key order.
  • values.next returns ref valid. Each successful item is an Item reference whose mutability follows the pool view; only items paired with TRUE identify live entries.
  • At exhaustion, values supplies a NIL Item reference, followed by FALSE.

Example

"Pool"    use
"control" use

{} () {} [
  p: Int32 Pool;
  @p.iter.next printStack swap drop drop
  @p.keys.next printStack swap drop drop
  @p.values.next printStack swap drop drop
] "main" exportFunction

Expected Output During Compilation

{
  key: 0;
  value: Int32 NIL;
}
FALSE
0
FALSE
Int32 NIL
FALSE

Examples

Runtime example: insert, erase, and next insertion key

"Pool"    use
"String"  use
"control" use

{} Int32 {} [
  p: Int32 Pool;
  k0: 5 @p.insert;
  k1: 6 @p.insert;
  ("k0=" k0 LF
    "k1=" k1 LF
    "value0=" k0 @p.at new LF) printList
  k0 @p.erase
  ("next=" p.getNextIndex LF) printList
  0
] "main" exportFunction

Expected Output

k0=0
k1=1
value0=5
next=0

Runtime example: iter, keys, values, and allocation size

"Pool"    use
"String"  use
"control" use

{} Int32 {} [
  p: Int32 Pool;
  10 @p.insert drop
  20 @p.insert drop
  0 @p.erase
  30 @p.insert drop

  pairs: @p.iter;
  pair0: ok0: @pairs.next;;
  pair1: ok1: @pairs.next;;
  pair2: ok2: @pairs.next;;

  keys: @p.keys;
  key0: hasKey0: @keys.next;;
  key1: hasKey1: @keys.next;;

  values: @p.values;
  value0: hasValue0: @values.next;;
  value1: hasValue1: @values.next;;

  ("iter0Key=" pair0.key LF
    "iter0Value=" pair0.value new LF
    "iter1Key=" pair1.key LF
    "iter1Value=" pair1.value new LF
    "iterDone=" ok2 LF
    "key0=" key0 LF
    "key1=" key1 LF
    "value0=" value0 new LF
    "value1=" value1 new LF
    "size=" @p.size LF) printList
  0
] "main" exportFunction

Expected Output

iter0Key=0
iter0Value=30
iter1Key=1
iter1Value=20
iterDone=FALSE
key0=0
key1=1
value0=30
value1=20
size=8

Runtime example: valid, firstValid, and nextValid

"Pool"    use
"String"  use
"control" use

{} Int32 {} [
  p: Int32 Pool;
  10 @p.insert drop
  20 @p.insert drop
  30 @p.insert drop
  1 @p.erase
  ("valid0=" 0 @p.valid LF
    "valid1=" 1 @p.valid LF
    "first=" p.firstValid LF
    "next0=" 0 @p.nextValid LF
    "next2=" 2 @p.nextValid LF) printList
  0
] "main" exportFunction

Expected Output

valid0=TRUE
valid1=FALSE
first=0
next0=2
next2=8

See also