nupp.util
Small types that belong to no larger facility.
local record Damage
amount: integer
end
const set = new nupp.util.Bitset(128)
set:set(7)
const damage = nupp.util.newPool(Damage, 64)
damage:release(damage:acquire())
const store = nupp.util.newStore()
const theme: nupp.util.Key<string> = nupp.util.newKey("app.theme")
store:set(theme, "dark")
print(nupp.util.uuid4())Each member is one type and the few functions that hand it out, reached by the type's name rather than by a module of its own. Bitset is a set of bit positions, Store is a bag of values indexed by typed Keys, and Pool leases cleared record instances from a free list. uuid4 and uuid7 generate identifiers, and fnv1a64 hashes bytes. try and tryWith run a function protected and answer (result, reason), which is the layout the or return suffix reads and pcall's own is not.
Unlike nupp.mem, which keeps every member a module of its own, these are re-exported here. Checked source still selects members independently, so using a bitset does not retain UUID support. uuid4 and uuid7 are the only members that resolve a host provider.
Module contents
Types
| Type | Kind | Description |
|---|---|---|
Bitset | record | A growable set of bit positions counting from 0. |
Key | record | A typed identity for one value in a Store. |
Pool | record | A pool of instances stamped with one record declaration. |
Store | interface | An independent typed bag indexed by keys. |
Functions
| Function | Kind | Description |
|---|---|---|
fnv1a64 | function | Hashes bytes with FNV-1a over 64 bits, as sixteen hexadecimal digits. |
newKey | function | Registers a new key, optionally under a name. |
newPool | function | Creates a pool of instances of one record type. |
newStore | function | Creates an empty store. |
try | function | Runs a function under pcall, answering (result, raised) rather than (ok, ...), which is the layout or return reads. |
tryWith | function | The same for xpcall, with the handler after the function: it runs before the stack unwinds and its result is the reason. |
uuid4 | function | Generates a random version 4 UUID, hyphenated and lowercase. |
uuid7 | function | Generates a time-ordered version 7 UUID, hyphenated and lowercase. |
Values
| Value | Kind | Description |
|---|---|---|
WORD_BITS | variable | The bits one word of a Bitset holds. |
Types#
Bitsetrecord#
record Bitset ...A growable set of bit positions counting from 0.
Members
| Name | Kind | Description |
|---|---|---|
words | field | Zero-based words, lowest bit first. |
capacity | field | Words allocated. |
used | field | An upper bound on the words that may hold a set bit: every word at or above it is zero. |
population | field | The last resolved population, meaningful only when stale is false. |
stale | field | Whether population needs recomputing. |
constructor | constructor | Creates an empty set. |
reserve | method | Grows storage so at least bits bits fit. |
set | method | Adds a bit, growing when it is past the end. |
clear | method | Removes a bit. |
get | method | Whether a bit is set. |
setRange | method | Adds every bit in the inclusive range, one word-mask write per word rather than one operation per bit. |
count | method | How many bits are set. |
isEmpty | method | Whether nothing is set. |
clearAll | method | Removes every bit, keeping capacity so a set reused each frame stops allocating once it has reached its peak. |
setOnly | method | Removes every bit, then adds exactly one. |
wordCount | method | An upper bound on the words that may hold a set bit. |
wordAt | method | One stored word. |
nextSetBit | method | The lowest set position at or after from. |
positionsInto | method | Writes every set position at or after from into target, lowest first. |
containsAll | method | Whether every bit set in other is also set here. |
overlaps | method | Whether at least one bit is set in both. |
disjoint | method | Whether the two share no set bit. |
copyFrom | method | Replaces these bits with a copy of other's. |
orWith | method | Adds every bit set in other. |
andWith | method | Keeps only the bits also set in other. |
andNotWith | method | Removes every bit set in other. |
xorWith | method | Keeps only the bits set in exactly one of the two. |
wordsfield#
words: uint32[?]Zero-based words, lowest bit first. Every word from used up to capacity is zero, which is the invariant every word loop relies on.
usedfield#
used: integerAn upper bound on the words that may hold a set bit: every word at or above it is zero. Deliberately not the exact high-water mark, because keeping that exact costs a backwards scan on every clear and every intersection.
populationfield#
population: integerThe last resolved population, meaningful only when stale is false.
stalefield#
stale: booleanWhether population needs recomputing. Set algebra marks it rather than paying two popcounts per word to keep a number most callers never read.
constructorconstructor#
constructor: function constructor(self, capacityBits: integer?)Creates an empty set.
Arguments
| Name | Type | Description |
|---|---|---|
self | any | the set being initialized |
capacityBits | integer? | how many bits to allocate initially; setting a later position grows the set |
Raises
| Type | Condition |
|---|---|
string | when capacityBits is more than 2^31 |
reservemethod#
reserve: function reserve(self, bits: integer): nilGrows storage so at least bits bits fit. Only grows, and never returns storage. A growth at least doubles, so repeated small growths stay amortised.
Arguments
| Name | Type | Description |
|---|---|---|
self | any | |
bits | integer | how many bits must fit |
Returns
| Type | Description |
|---|---|
nil |
Raises
| Type | Condition |
|---|---|
string | when bits is NaN or more than 2^31 |
setmethod#
set: function set(self, index: integer): nilAdds a bit, growing when it is past the end.
Arguments
| Name | Type | Description |
|---|---|---|
self | any | |
index | integer | the bit position, counting from 0 |
Returns
| Type | Description |
|---|---|
nil |
Raises
| Type | Condition |
|---|---|
string | when index is negative, past 2^31 - 1, or not an integer |
clearmethod#
clear: function clear(self, index: integer): nilRemoves a bit. Never allocates, and never narrows the used-word bound, so it stays constant-time whichever bit it was.
Arguments
| Name | Type | Description |
|---|---|---|
self | any | |
index | integer | the bit position, counting from 0. One past the end, a negative one included, does nothing. |
Returns
| Type | Description |
|---|---|
nil |
getmethod#
get: function get(self, index: integer): booleanWhether a bit is set.
Arguments
| Name | Type | Description |
|---|---|---|
self | any | |
index | integer | the bit position, counting from 0 |
Returns
| Type | Description |
|---|---|
boolean | false for any position past the end, so no bound check is needed at the call site |
setRangemethod#
setRange: function setRange(self, low: integer, high: integer): nilAdds every bit in the inclusive range, one word-mask write per word rather than one operation per bit.
Arguments
| Name | Type | Description |
|---|---|---|
self | any | |
low | integer | the first position added, counting from 0 |
high | integer | the last position added. Below |
Returns
| Type | Description |
|---|---|
nil |
Raises
| Type | Condition |
|---|---|
string | when low is negative, when high is past 2^31 - 1, or when either is not an integer |
countmethod#
count: function count(self): integerHow many bits are set. Resolves a pending recount, so a caller that reads it every frame pays for the set algebra it skipped; one that never reads it never pays.
Arguments
| Name | Type | Description |
|---|---|---|
self | any |
Returns
| Type | Description |
|---|---|
integer | the number of set bits |
isEmptymethod#
isEmpty: function isEmpty(self): booleanWhether nothing is set. Says nothing about capacity.
Arguments
| Name | Type | Description |
|---|---|---|
self | any |
Returns
| Type | Description |
|---|---|
boolean | true when no bit is set |
clearAllmethod#
clearAll: function clearAll(self): nilRemoves every bit, keeping capacity so a set reused each frame stops allocating once it has reached its peak.
Arguments
| Name | Type | Description |
|---|---|---|
self | any |
Returns
| Type | Description |
|---|---|
nil |
setOnlymethod#
setOnly: function setOnly(self, index: integer): nilRemoves every bit, then adds exactly one.
Arguments
| Name | Type | Description |
|---|---|---|
self | any | |
index | integer | the one position left set, counting from 0 |
Returns
| Type | Description |
|---|---|
nil |
Raises
| Type | Condition |
|---|---|
string | when index is negative, past 2^31 - 1, or not an integer; the set is left unchanged |
wordCountmethod#
wordCount: function wordCount(self): integerAn upper bound on the words that may hold a set bit. Every word at or above it is zero, and it is not narrowed by clear or by intersection, so it may exceed the exact high-water mark.
Arguments
| Name | Type | Description |
|---|---|---|
self | any |
Returns
| Type | Description |
|---|---|
integer | the number of words a word-at-a-time walk must visit |
wordAtmethod#
wordAt: function wordAt(self, index: integer): integerOne stored word. Word w holds positions w * WORD_BITS through w * WORD_BITS + WORD_BITS - 1, lowest position first.
Arguments
| Name | Type | Description |
|---|---|---|
self | any | |
index | integer | a word index counting from 0, bounded by |
Returns
| Type | Description |
|---|---|
integer | the word as a signed value, so one with its top bit set reads negative, and 0 for any index outside the bound |
nextSetBitmethod#
nextSetBit: function nextSetBit(self, from: integer): integerThe lowest set position at or after from.
Stateless, so nested walks do not interfere and a mutation between calls cannot invalidate a walk in progress.
Arguments
| Name | Type | Description |
|---|---|---|
self | any | |
from | integer | the lowest position that may be returned, counting from 0 |
Returns
| Type | Description |
|---|---|
integer | that position, or -1 when there is none |
positionsIntomethod#
positionsInto: function positionsInto(self, target: int32[?], capacity: integer, from: integer): integer, integerWrites every set position at or after from into target, lowest first.
One call rather than one per position. A walk pays a call and a word read for every position it returns, which for a few thousand positions is most of what it costs; this reads each word once and clears the position it just reported out of a register.
target is a pointer and a count because that is what a native kernel takes. Filling it here rather than allocating keeps a per-frame extraction allocation-free, and leaves the boundary where a native one would sit.
Arguments
| Name | Type | Description |
|---|---|---|
self | any | |
target | int32[?] | where positions are written, indexed from 0 |
capacity | integer | how many positions |
from | integer | the lowest position that may be written, counting from 0 |
Returns
| Type | Description |
|---|---|
integer | how many positions were written, and the position to resume from when |
integer |
Raises
| Type | Condition |
|---|---|
string | when capacity is negative |
containsAllmethod#
containsAll: function containsAll(self, other: Bitset): booleanWhether every bit set in other is also set here.
Arguments
| Name | Type | Description |
|---|---|---|
self | any | |
other | Bitset | read and not modified |
Returns
| Type | Description |
|---|---|
boolean | true when |
overlapsmethod#
overlaps: function overlaps(self, other: Bitset): booleanWhether at least one bit is set in both. Early-exiting, and deliberately not an intersection followed by a count.
Arguments
| Name | Type | Description |
|---|---|---|
self | any | |
other | Bitset | read and not modified, as is the receiver |
Returns
| Type | Description |
|---|---|
boolean | true when the two share a set bit |
disjointmethod#
disjoint: function disjoint(self, other: Bitset): booleanWhether the two share no set bit.
Arguments
| Name | Type | Description |
|---|---|---|
self | any | |
other | Bitset | read and not modified, as is the receiver |
Returns
| Type | Description |
|---|---|
boolean | the negation of |
copyFrommethod#
copyFrom: function copyFrom(self, other: Bitset): nilReplaces these bits with a copy of other's. The two are independent afterwards, and this keeps its own capacity when that is the larger.
Arguments
| Name | Type | Description |
|---|---|---|
self | any | |
other | Bitset | read and not modified |
Returns
| Type | Description |
|---|---|
nil |
orWithmethod#
orWith: function orWith(self, other: Bitset): nilAdds every bit set in other.
One bitwise operation per word: the population is marked for recount rather than tracked, and there is no per-word test for whether the word changed, because that branch costs more than the write it saves.
Arguments
| Name | Type | Description |
|---|---|---|
self | any | |
other | Bitset | read and not modified |
Returns
| Type | Description |
|---|---|
nil |
andWithmethod#
andWith: function andWith(self, other: Bitset): nilKeeps only the bits also set in other.
Arguments
| Name | Type | Description |
|---|---|---|
self | any | |
other | Bitset | read and not modified. Words beyond the ones it uses are cleared, so an empty |
Returns
| Type | Description |
|---|---|
nil |
Keyrecord#
record Key<T> ...A typed identity for one value in a Store.
Type parameters
| Name | Description |
|---|---|
T |
Members
| Name | Kind | Description |
|---|---|---|
Step | type | The step of nupp.util.Key.registered. |
id | field | |
name | field | |
find | method | Finds a registered key by name. |
registered | method | Walks every registered name, ascending by id. |
Steptype#
type Step = function(any, integer): (integer, string)The step of nupp.util.Key.registered.
Terminating on nil is the generic for's business, so the surface type promises what the loop delivers rather than what the last call returns.
findmethod#
Finds a registered key by name.
The registry knows the name's id and nothing about its type, so the result is unknown and the caller casts to the Key<T> it claims.
Arguments
| Name | Type | Description |
|---|---|---|
name | string | the registered name |
Returns
| Type | Description |
|---|---|
unknown | the key, or nil when the name is not registered |
registeredmethod#
registered: function registered(): function(any, integer): (integer, string), any, integerWalks every registered name, ascending by id.
The walk allocates nothing: the step is one shared function and the control variable is the id it last reported. The registry itself is never handed out, so nothing the loop is given can be used to reach or change it. An anonymous key registers no name and is not walked.
Returns
| Type | Description |
|---|---|
function(any, integer): (integer, string) | the step the loop calls |
any | nothing to carry, since the ids are the whole state |
integer | the id to resume after, 0 to start |
Poolrecord#
A pool of instances stamped with one record declaration.
Type parameters
| Name | Description |
|---|---|
T |
Members
| Name | Kind | Description |
|---|---|---|
witness | field | |
capacity | field | How many released instances the pool retains for reuse. |
freeList | field | |
freeCount | field | |
liveCount | field | |
liveValues | field | |
acquire | method | Hands out an instance, from the free list or freshly stamped. |
release | method | Clears an instance and returns it to the free list. |
reserve | method | Stamps free instances until at least count are waiting. |
live | method | How many instances are acquired and not yet released. |
free | method | How many instances are waiting on the free list. |
reset | method | Drops the free list. |
acquiremethod#
Hands out an instance, from the free list or freshly stamped.
Arguments
| Name | Type | Description |
|---|---|---|
exclusive self | Pool<T> |
Returns
| Type | Description |
|---|---|
T |
releasemethod#
Clears an instance and returns it to the free list.
Arguments
| Name | Type | Description |
|---|---|---|
exclusive self | Pool<T> | |
value | T |
Returns
| Type | Description |
|---|---|
nil |
Raises
| Type | Condition |
|---|---|
string | when nothing is live, or value was not stamped by the pool's declaration |
reservemethod#
reserve: function(exclusive self: Pool<T>, count: integer): nilStamps free instances until at least count are waiting.
Arguments
| Name | Type | Description |
|---|---|---|
exclusive self | Pool<T> | |
count | integer |
Returns
| Type | Description |
|---|---|
nil |
Raises
| Type | Condition |
|---|---|
string | when count is negative |
livemethod#
live: function(self: Pool<T>): integerHow many instances are acquired and not yet released.
Arguments
| Name | Type | Description |
|---|---|---|
self | Pool<T> |
Returns
| Type | Description |
|---|---|
integer |
Storeinterface#
interface Store ...An independent typed bag indexed by keys.
Members
| Name | Kind | Description |
|---|---|---|
Step | type | The step of nupp.util.Store.entries. |
get | method | Answers the value held under a key. |
set | method | Stores a non-nil value under a key. |
remove | method | Removes the value held under a key. |
clear | method | Removes every value without touching key registration. |
entries | method | Walks every occupied key ascending by id, without its value. |
Steptype#
type Step = function(Store, integer): (integer, string?, string)The step of nupp.util.Store.entries.
Terminating on nil is the generic for's business, so the surface type promises what the loop delivers rather than what the last call returns.
getmethod#
Answers the value held under a key.
Arguments
| Name | Type | Description |
|---|---|---|
self | Store | |
key | Key<T> |
Returns
| Type | Description |
|---|---|
T? |
setmethod#
Stores a non-nil value under a key.
Arguments
| Name | Type | Description |
|---|---|---|
self | Store | |
key | Key<T> | |
value | T |
Returns
| Type | Description |
|---|---|
nil |
Raises
| Type | Condition |
|---|---|
string | when value is nil; use |
removemethod#
Removes the value held under a key.
Arguments
| Name | Type | Description |
|---|---|---|
self | Store | |
key | Key<T> |
Returns
| Type | Description |
|---|---|
nil |
clearmethod#
clear: function(self: Store): nilRemoves every value without touching key registration.
Arguments
| Name | Type | Description |
|---|---|---|
self | Store |
Returns
| Type | Description |
|---|---|
nil |
entriesmethod#
entries: function(
borrows self: Store
): (function(Store, integer): (integer, string?, string), Store borrows (self), integer)Walks every occupied key ascending by id, without its value.
Arguments
| Name | Type | Description |
|---|---|---|
borrows self | Store |
Returns
| Type | Description |
|---|---|
function(Store, integer): (integer, string?, string) | |
Store borrows (self) | |
integer |
Functions#
fnv1a64function#
Hashes bytes with FNV-1a over 64 bits, as sixteen hexadecimal digits.
Arguments
| Name | Type | Description |
|---|---|---|
borrows value | string | ByteView | the bytes to hash |
Returns
| Type | Description |
|---|---|
string | the hash, as sixteen lowercase hexadecimal digits |
newKeyfunction#
Registers a new key, optionally under a name.
Type parameters
| Name | Description |
|---|---|
T |
Arguments
| Name | Type | Description |
|---|---|---|
name | string? | the opaque name to register, or nil for an anonymous key |
Returns
| Type | Description |
|---|---|
Key<T> | the key |
Raises
| Type | Condition |
|---|---|
string | when the name is empty or already registered, before an id is used |
newPoolfunction#
Creates a pool of instances of one record type.
Type parameters
| Name | Description |
|---|---|
T |
Arguments
| Name | Type | Description |
|---|---|---|
witness | Type<T> | the record declaration, whose metatable stamps every instance |
capacity | integer? | how many released instances to retain, 32 when omitted |
Returns
| Type | Description |
|---|---|
pool.Pool<T> | the pool, warmed to capacity |
Raises
| Type | Condition |
|---|---|
string | when capacity is negative |
newStorefunction#
function newStore(): StoreCreates an empty store.
Returns
| Type | Description |
|---|---|
Store | the store |
tryfunction#
function try<R, A...>(f: function(A...): R, ...: A...): R?, unknownRuns a function under pcall, answering (result, raised) rather than (ok, ...), which is the layout or return reads.
Type parameters
| Name | Description |
|---|---|
R | |
A |
Arguments
| Name | Type | Description |
|---|---|---|
f | function(A...): R | the function to run protected |
... | A... | its arguments |
Returns
| Type | Description |
|---|---|
R? | its result, or nil when it raised |
unknown | the raised value, or nil when it did not |
tryWithfunction#
function tryWith<R, E, A...>(f: function(A...): R, handler: function(any): E, ...: A...): R?, E?The same for xpcall, with the handler after the function: it runs before the stack unwinds and its result is the reason.
Type parameters
| Name | Description |
|---|---|
R | |
E | |
A |
Arguments
| Name | Type | Description |
|---|---|---|
f | function(A...): R | the function to run protected |
handler | function(any): E | what to make of a raised value |
... | A... | the function's arguments |
Returns
| Type | Description |
|---|---|
R? | its result, or nil when it raised |
E? | what |
uuid4function#
function uuid4(): stringGenerates a random version 4 UUID, hyphenated and lowercase.
Returns
| Type | Description |
|---|---|
string | the UUID, hyphenated and lowercase |
Raises
| Type | Condition |
|---|---|
string | when the provider fails or returns a malformed UUID |
uuid7function#
function uuid7(): stringGenerates a time-ordered version 7 UUID, hyphenated and lowercase.
Ordered to the millisecond only: two made in the same millisecond carry random bits after the timestamp, so they compare in either order. It uses no RFC 9562 monotonic counter.
Returns
| Type | Description |
|---|---|
string | the UUID, hyphenated and lowercase |
Raises
| Type | Condition |
|---|---|
string | when the provider fails or returns a malformed UUID |
Values#
WORD_BITSvariable#
const WORD_BITSThe bits one word of a Bitset holds.