Jump to content

Arrays

From OpenBOR

OpenBOR supports indexed and string-keyed arrays. Both forms are provided by the same allocated array object, though they use different internal storage and have different performance characteristics.

Arrays are allocated in the script heap and referenced through pointers. Use free() when an array is no longer needed.

void values = array(4);

// Use the array.

free(values);

Indexed Arrays

Indexed arrays store a contiguous block of variants and use zero-based integer indexes. This provides fast, memory-efficient access to existing elements.

void colors = array(3);

set(colors, 0, "Red");
set(colors, 1, "Green");
set(colors, 2, "Blue");

The first element is index 0, and the last element is size(array_pointer) - 1.

Growing, inserting into, or deleting from an indexed array may require reallocating its storage and copying or shifting existing elements. Allocate the expected size in advance and avoid structural changes inside frequently executed gameplay code when practical.

String-Keyed Arrays

String-keyed arrays, sometimes called associative arrays, store values under named keys.

void character = array(0);

set(character, "name", "Max");
set(character, "health", 100);
set(character, "speed", 2.5);

Named entries are stored in a doubly linked list with hash acceleration for key lookup. This structure uses more memory than indexed storage, but allows named entries to be added and removed without resizing or shifting an indexed block.

Allocate string-keyed arrays with a size of 0 because named entries are stored separately from the indexed block.

Mixing Key Types

Allocating an array always creates its indexed storage first. Using a string key adds named-list storage alongside that indexed block.

Warning: Do not use the same array as both indexed and string-keyed storage. Mixed arrays produce ambiguous size results and make iteration, ownership, and structural behavior difficult to reason about.

When an array contains named entries, size() returns the number of named entries. Otherwise, it returns the number of indexed elements. It does not return a combined total.

Nested Arrays

Array pointers may be stored inside other arrays to create multidimensional or hierarchical data structures. Parent and child arrays may use different key types.

void position = array(3);

set(position, 0, 100);
set(position, 1, 200);
set(position, 2, 0);

void entity_data = array(0);

set(entity_data, "name", "Example");
set(entity_data, "position", position);

Nested arrays are independent allocations. Free() each allocated array when it is no longer needed.

free(position);
free(entity_data);

Functions

add()

add(array_pointer, key, value);

Adds a value to an array.

Indexed
Inserts the value at the specified index. The index may range from 0 through the current array size. Existing elements at that index and above shift upward by one position. Using the current size appends the value.
void values = array(2);

set(values, 0, "First");
set(values, 1, "Third");

add(values, 1, "Second");
String-keyed
Creates a named entry or replaces the value of an existing entry. This is equivalent to set() for string keys.

array()

void array_pointer = array(size);

Allocates an array with the specified number of indexed elements and returns its pointer. The size must be a non-negative integer.

Use the expected element count for an indexed array:

void indexed_array = array(10);

Use 0 for a string-keyed array:

void named_array = array(0);

delete()

delete(array_pointer, key);

Removes an entry from an array.

Indexed
Deletes the element at the specified index, shifts all higher elements down by one position, and reduces the array size.
String-keyed
Removes the named entry.

Structural deletion creates more work than clearing an existing value. When the slot or key should remain available, assign NULL() instead:

set(array_pointer, key, NULL());

For string-keyed arrays, assigning NULL() clears the stored value but retains the named entry.

get()

mixed result = get(array_pointer, key);

Returns the value stored at an integer index or string key. Returns NULL() when the requested entry does not exist.

mixed name = get(character, "name");
mixed first = get(colors, 0);

Named lookups may reposition the array's internal cursor. Avoid performing unrelated named lookups while traversing an array with the cursor functions.

isarray()

int result = isarray(pointer);

Returns 1 if the supplied pointer references an OpenBOR array. Returns 0 otherwise.

isfirst()

int result = isfirst(array_pointer);

String-keyed arrays only. Returns 1 if the internal cursor is positioned at the first named entry. Returns 0 otherwise.

This function is meaningful only when the named array contains at least one entry.

islast()

int result = islast(array_pointer);

String-keyed arrays only. Returns 1 if the internal cursor is positioned at the final named entry. Returns 0 otherwise.

This function is meaningful only when the named array contains at least one entry.

key()

char current_key = key(array_pointer);

String-keyed arrays only. Returns the string key at the current cursor position. Returns NULL() when no current entry is available.

next()

int success = next(array_pointer);

String-keyed arrays only. Attempts to move the internal cursor to the next named entry.

Returns 1 when the cursor moves successfully. Returns 0 when it is already positioned at the final entry.

previous()

int success = previous(array_pointer);

String-keyed arrays only. Attempts to move the internal cursor to the previous named entry.

Returns 1 when the cursor moves successfully. Returns 0 when it is already positioned at the first entry.

reset()

int success = reset(array_pointer);

String-keyed arrays only. Moves the internal cursor to the first named entry.

Returns 1 when an entry is available. Returns 0 when the named array is empty.

set()

set(array_pointer, key, value);

Stores a value in an array.

Indexed
Replaces the value at the specified index. If the index is outside the current allocation, the indexed block grows to include it.
String-keyed
Creates a named entry or replaces the value of an existing entry.

Prefer set() when writing to a known indexed slot. Use add() when inserting a new element and intentionally shifting later indexes.

size()

int array_size = size(array_pointer);

Returns the number of elements in an array.

Indexed
Returns the current number of indexed elements.
String-keyed
Returns the current number of named entries.

Caution: An array containing both storage forms reports the named-entry count whenever at least one named entry exists. Do not mix integer and string keys in the same array.

value()

mixed current_value = value(array_pointer);

String-keyed arrays only. Returns the value at the current cursor position. Returns NULL() when no current entry is available.

Iterating Over Named Entries

Use reset(), key(), value(), and next() to traverse a string-keyed array.

void character = array(0);

set(character, "name", "Max");
set(character, "health", 100);
set(character, "speed", 2.5);

if (reset(character)) {
    do {
        char current_key = key(character);
        mixed current_value = value(character);

        log(current_key + ": " + current_value);
    } while (next(character));
}

free(character);

Under The Hood

The following is an overview of OpenBOR's internal array and list handling.

Indexed

OpenBOR’s indexed array implementation is fairly straightforward - essentially a management layer over a contiguous C array. The first storage position tracks the array’s size, while script-visible values follow in contiguous slots. This creates a flat one-slot offset between each script index and its physical storage position. By abstracting allocation and reallocation, the layer allows arrays to grow, accept insertions and deletions, and report their current size without exposing memory management to the script.

Simplified illustration of OpenBOR’s indexed array layout and access. In actuality, the array elements contain pointers to structures that house the payload rather than the payload value itself, but the effect is still the same. Simple offset calculations and hardware friendly storage provide maximum performance.

String Keyed

String-keyed values are stored as double linked lists with a hash accelerator. Each individual list maintains its own hash index, so global variables, local variables, arrays, and other lists do not share one combined lookup table.

OpenBOR performs a string-key lookup with the following process:

  1. The supplied name is converted to a 64-bit hash using the FNV-1a algorithm.
  2. The low-order 10 bits select one of 1,024 possible buckets.
  3. OpenBOR searches the selected bucket by comparing the requested 64-bit hash with the full hash cached on each stored node.
  4. When the full hashes match, OpenBOR performs a final string comparison to confirm that the names are identical.
  5. A confirmed match returns the existing value for get() or replaces it for set().
  6. When no match exists, get() returns NULL(), while set() creates a new named node.

The bucket directory is allocated only when a list receives its first named entry. Individual buckets are also allocated only when used. Each bucket begins with room for two node pointers and doubles its capacity when necessary.

Several names may intentionally share the same bucket because only 10 bits select it. This is an ordinary bucket collision and does not require a string comparison when the complete 64-bit hashes differ. Within the same bucket, collisions are nearly impossible. FNV-1a is a non-cryptographic hash algorithm designed for fast hash-table lookup. Like every fixed-width hash, it doesn't technically guarantee that two different names will never produce the same result, but in practical terms, it may as well. The chance for any particular pair is approximately 1 in 2^64 - or 1 in 18,446,744,073,709,551,616. The final string comparison nevertheless guarantees correct lookup even if a complete-hash collision occurs.

Simplified illustration of OpenBOR’s string-key access. In actuality, the node values contain pointers to structures that house the payload rather than the payload value itself, but effect is still the same.