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;.
- The generated schema name is
Pool<ItemSchemaName>. Each slot accommodates either anItemor anInt32free-list link, including alignment padding. - The pool is managed: construction initializes empty storage, and destruction destroys live items before releasing allocated storage.
Itemselects a non-Meta In-place schema and must permit default construction. For an existing prototype, supply a mutable view such as@prototype. ARefprototype 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
Poolobjects are movable, not copyable.newthrough 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
SCHEMA_NAME: static Text naming the generated schema asPool<ItemSchemaName>.POOL: static empty-Tuple marker identifying the generated Dict as aPool.Item: static NIL reference identifying the stored item schema.entrySize: static slot stride in bytes, including space and alignment for either anItemor anInt32free-list link.data: reference to the allocated entry area, or NIL when the pool owns no entry storage.dataSize: allocated entry-slot count; starts at 0 and is not reduced by erase or clear.firstFree: head key of the reusable-key list, or-1when no reusable key exists.exactAllocatedMemSize: byte-size bookkeeping for the most recent allocation; may remain nonzero after a move empties the pool.
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
getSizeandsizeboth returndataSize; neither counts live items. Count live items by walkingfirstValid/nextValiduntil the size sentinel.
size
(-- count) Returns the number of allocated entry slots.- It returns the same
dataSizevalue asgetSize, 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
Int32in0 .. size-1and identify a live item. With DEBUG enabled, an out-of-range key reportsIndex is out of range!; an in-range free key reportsPool::at: element is invalid!. - The reference points into the pool's entry storage. Growth, erasure, clearing, and destruction invalidate references as described above.
keyis anInt32in0 .. size-1. Directvalidchecks known bounds even withDEBUGdisabled; runtime range checks requireDEBUG.- A key whose validity bit is clear returns
FALSE; a live key returnsTRUE.
Remarks
- Run-time range and live-slot checks require DEBUG. Do not rely on
atoreraseto 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 + 1and skips invalid slots. - Stop at the size sentinel; passing it to
nextValidreturnssize + 1, not the sentinel again. - A key below
-1fails withIndex is out of range!.
getNextIndex
(-- key) Returns the key the next insert will use.- It returns
firstFreewhen a reusable key exists; otherwise it returnsdataSize.
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
Itemschema and support the selected operation. - When
firstFreeis 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.
- The key must be an
Int32in0 .. size-1and 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 reportsPool::erase: element is invalid!. - Erasing does not reduce
sizeor allocated memory.
- 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
Itemthrough the returned reference only for a live key in0 .. 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-1before reading or writing its link. The link is the next free key or-1; accessing a live slot's link aliases itsItemstorage. No bounds or validity check is performed.
- The byte position must be in
0 .. size/8-1before access. Keykuses bytek / 8and bitk mod 8. No bounds check is performed.
Remarks
nextFreeAtandvalidAtreturn 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
poolexplicitly; selectingmakeIterfrom a receiver does not supply that argument. The explicit argument is retained in the iterator. methodis 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
Itemreference; reference mutability follows the supplied pool view. The iterator starts atfirstValidand skips invalid entries. - On exhaustion,
methodis still called with key0and a NILItemreference; its result is followed byFALSE. 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
poolargument is a view, the iterator borrows that pool; keep it alive for the iterator's lifetime.
iter.nextreturnsrecord valid. Each record'svalueis anItemreference whose mutability follows the pool view; only records paired withTRUEidentify live entries.- At exhaustion,
itersupplies a record withkeyequal to0and a NILItemreference asvalue, followed byFALSE.
Remarks
- Iterators returned by
iter,keys, andvaluesborrow the pool, which must outlive them. They are not snapshots. Keep pool membership unchanged during a traversal: erasing a cached next key can makenextfail, 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.counton the pool or one of these iterators counts live entries.
keys
(-- iter) Returns an iterator of live keys in ascending numeric order.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
- Array: Growable array.
- HashTable: Hash-based map and set module.
- PriorityQueue: Priority queue with customizable comparison.
- algorithm: Collection interfaces, comparison helpers, iteration adapters, and view slicing utilities.