{"id":7412,"date":"2026-05-05T20:43:49","date_gmt":"2026-05-06T00:43:49","guid":{"rendered":"https:\/\/www.caskeys.com\/dc\/?p=7412"},"modified":"2026-05-30T10:34:07","modified_gmt":"2026-05-30T14:34:07","slug":"using-openbor-arrays","status":"publish","type":"post","link":"https:\/\/www.caskeys.com\/dc\/using-openbor-arrays\/","title":{"rendered":"Using OpenBOR Arrays"},"content":{"rendered":"\n<p class=\"wp-block-paragraph\"><\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Introduction<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">OpenBOR Script includes array support, but despite the C-like syntax, OpenBOR arrays have their own behavior, strengths, and quirks. They are not scoped data structures in the usual sense, but instead behave much more like heap-allocated objects you access through handles. This gives OpenBOR arrays tremendous flexibility, but also means they require active care and cleanup.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">To make things more interesting, OpenBOR arrays are really two storage systems sharing the same API. One is a true indexed array straight out of C &#8211; close to the metal and built for raw performance at the cost of flexibility. The other is a doubly linked list with hash acceleration &#8211; not as fast as an indexed array, but more human-friendly to use and open to unstructured data. Which one you use depends entirely on the type of key you pass to functions like <code>get()<\/code>, <code>set()<\/code>, <code>delete()<\/code>, and related helpers.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Which one is best depends on your needs in the moment. After reading this guide, you should have a stronger understanding of which one to employ and when.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Defining Terms<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Before getting into specifics, it helps to understand what arrays and lists are in general programming terms. OpenBOR supports both concepts, but exposes them through the same \u201carray\u201d interface. We will use code samples suited to OpenBOR, but the basic principles explained here are mostly universal.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Array<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">An array is a container that stores multiple values under one name. Instead of creating separate variables for every related value, you store them together and access each one by position.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">For example, instead of this:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: cpp; title: Code:; notranslate\" title=\"Code:\">\nvoid item_0 = &quot;apple&quot;;\nvoid item_1 = &quot;banana&quot;;\nvoid item_2 = &quot;cherry&quot;;\n<\/pre><\/div>\n\n\n<p class=\"wp-block-paragraph\">you could use an array:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: cpp; title: Code:; notranslate\" title=\"Code:\">\nvoid items = array(3);\n\nset(items, 0, &quot;apple&quot;);\nset(items, 1, &quot;banana&quot;);\nset(items, 2, &quot;cherry&quot;);\n<\/pre><\/div>\n\n\n<p class=\"wp-block-paragraph\">Then you can retrieve a value by its index:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: cpp; title: Code:; notranslate\" title=\"Code:\">\nvoid item = get(items, 1); \/\/ banana\n<\/pre><\/div>\n\n\n<p class=\"wp-block-paragraph\">The number used to access the value is called an <strong>index<\/strong>. Most programming languages, including OpenBOR Script, use <strong>zero-based indexing<\/strong>, which means the first element is index <code>0<\/code>, the second is index <code>1<\/code>, and so on. <\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><a href=\"https:\/\/www.caskeys.com\/dc\/wp-content\/uploads\/2026\/05\/image.png\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"768\" src=\"https:\/\/www.caskeys.com\/dc\/wp-content\/uploads\/2026\/05\/image-1024x768.png\" alt=\"\" class=\"wp-image-7465\" srcset=\"https:\/\/www.caskeys.com\/dc\/wp-content\/uploads\/2026\/05\/image-1024x768.png 1024w, https:\/\/www.caskeys.com\/dc\/wp-content\/uploads\/2026\/05\/image-300x225.png 300w, https:\/\/www.caskeys.com\/dc\/wp-content\/uploads\/2026\/05\/image-768x576.png 768w, https:\/\/www.caskeys.com\/dc\/wp-content\/uploads\/2026\/05\/image.png 1448w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><\/a><\/figure>\n\n\n\n<h4 class=\"wp-block-heading\">High Performance, Poor Flexibility<\/h4>\n\n\n\n<p class=\"wp-block-paragraph\">Traditional arrays are a lot like drag racers &#8211; they offer maximum performance at the cost of flexibility. Array elements occupy a single contiguous block of memory, which allows hardware-level acceleration <sup data-fn=\"fad44a63-c3a3-4dd1-830e-9e3bb3bc8c80\" class=\"fn\"><a href=\"#fad44a63-c3a3-4dd1-830e-9e3bb3bc8c80\" id=\"fad44a63-c3a3-4dd1-830e-9e3bb3bc8c80-link\">1<\/a><\/sup> and direct access by index. Once the engine knows the starting location of the array and the index you asked for, it can calculate exactly where that element lives.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">This means arrays are not just fast, but consistently fast. It doesn\u2019t matter how many elements are in an array or which element you are looking for. Finding that element always takes the same amount of time. We call this <em>constant time complexity<\/em>, or O(1).<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: cpp; title: Code:; notranslate\" title=\"Code:\">\nvoid baby_bear = array(100);\nvoid mama_bear = array(10000);\nvoid poppa_bear = array(100000);\n\n\/* Same access time for each. *\/\nset(baby_bear, 99, &quot;apple&quot;);\nset(mama_bear, 9999, &quot;banana&quot;);\nset(poppa_bear, 99999, &quot;cherry&quot;);\n\n\/* Still same access time. *\/\nvoid some_value_a = get(baby_bear, 99);\nvoid some_value_b = get(mama_bear, 9999);\nvoid some_value_c = get(poppa_bear, 99999);\n<\/pre><\/div>\n\n\n<p class=\"wp-block-paragraph\">The tradeoff is that arrays do not grow or shrink freely. Adding or removing elements requires creating a new array of the correct size and copying the old contents over. That is why arrays are extremely fast when their size and layout are known ahead of time, but less convenient when the structure needs to change constantly. <\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Think of looking for a set of books about one subject. An array is like having every book on the same shelf, lined up in order. Once you know the shelf and the book number, you can go straight to it. Separate variables are more like having those books scattered across the library &#8211; easier to shelve, much harder to find.<\/p>\n\n\n\n<h4 class=\"wp-block-heading\">OpenBOR Arrays<\/h4>\n\n\n\n<p class=\"wp-block-paragraph\">OpenBOR\u2019s array implementation is very similar to C-style arrays, but with a management layer that allows growth, inserts, and size reflection. These features are useful, but they can also be a double-edged sword. We\u2019ll get to the details further below, including how they work and when you should or shouldn\u2019t use them.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The important takeaway for now is that OpenBOR indexed arrays behave like traditional single-dimension C-style arrays from the script writer\u2019s perspective. They share the same advantages of O(1) access speed and efficient indexed storage.<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><a href=\"https:\/\/www.caskeys.com\/dc\/wp-content\/uploads\/2026\/05\/image-2.png\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"768\" src=\"https:\/\/www.caskeys.com\/dc\/wp-content\/uploads\/2026\/05\/image-2-1024x768.png\" alt=\"OpenBOR indexed layout.\" class=\"wp-image-7482\" srcset=\"https:\/\/www.caskeys.com\/dc\/wp-content\/uploads\/2026\/05\/image-2-1024x768.png 1024w, https:\/\/www.caskeys.com\/dc\/wp-content\/uploads\/2026\/05\/image-2-300x225.png 300w, https:\/\/www.caskeys.com\/dc\/wp-content\/uploads\/2026\/05\/image-2-768x576.png 768w, https:\/\/www.caskeys.com\/dc\/wp-content\/uploads\/2026\/05\/image-2.png 1448w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><\/a><figcaption class=\"wp-element-caption\">Simplified illustration of OpenBOR\u2019s 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 acceleration provide maximum performance.<\/figcaption><\/figure>\n\n\n\n<h3 class=\"wp-block-heading\">Lists<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Lists are another kind of container, but they are built differently from arrays. Instead of storing every value in one contiguous block of memory, a list stores values in individual structures, called nodes. Each node contains the value, plus one or more pointers that tell the engine where to find the next node.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Doubly linked lists go one step further. Each node points both forward and backward, so the engine can move to the next node or the previous node.<\/p>\n\n\n\n<h4 class=\"wp-block-heading\">High Flexibility, Lower Performance<\/h4>\n\n\n\n<p class=\"wp-block-paragraph\">Lists are a lot like a train. Each car is separate, but linked to the cars before and after it. Adding or removing a car does not require rebuilding the entire train. You just connect or disconnect the nearby cars.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">That flexibility is the main advantage. Lists can grow naturally, and inserting or removing entries is usually less disruptive than resizing an array. Lists are also able to use non-integer keys, and they don\u2019t require identifiers to be numeric, contiguous, or sequential. Again think of a train. It doesn&#8217;t matter where the grain car or box car is, nor what their names are, as long as the links are present.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The tradeoff is lookup speed and memory overhead. Because each node is a separate object, values are scattered around memory instead of packed together. To find a specific entry, the engine may have to walk through nodes until it finds a match. Each node also needs extra memory for its links. <\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Unlike arrays, lists are affected by their size. Because finding a node requires walking the list, the more nodes you have to walk through, the more time is needed. This is called <em>linear time complexity<\/em>, or O(n), where n is the number of nodes checked before reaching the target.<\/p>\n\n\n\n<h4 class=\"wp-block-heading\">OpenBOR Lists<\/h4>\n\n\n\n<p class=\"wp-block-paragraph\">OpenBOR uses a doubly linked list structure when you access an array with a string key. From the script side, it still looks like an array:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: cpp; title: Code:; notranslate\" title=\"Code:\">\nset(my_array, &quot;health&quot;, 100);\nset(my_array, &quot;sprite_path&quot;, &quot;data\/chars\/player\/icon.png&quot;);\n<\/pre><\/div>\n\n\n<p class=\"wp-block-paragraph\">Under the hood, OpenBOR creates a doubly linked list of named nodes instead of writing into the indexed storage block. Each named entry lives in the list, and the list keeps a cursor so functions like <code>reset()<\/code>, <code>next()<\/code>, <code>previous()<\/code>, <code>key()<\/code>, and <code>value()<\/code> can walk through the entries.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">To reduce the performance cost of list lookup, OpenBOR\u2019s implementation also adds hash acceleration. The string key is converted into a small numeric hash, and that hash selects one of 256 possible buckets. Instead of checking every named node, OpenBOR only checks the nodes in the matching bucket.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">This is much better than a full list scan. The engine still has to process the string key, find the bucket, and compare entries inside that bucket until it finds the exact key, but in most use cases, the number of string comparisons needed will be minimal.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The important takeaway is that string-keyed OpenBOR arrays are essentially turbocharged doubly linked lists. They are still not on par with indexed arrays for raw speed, but they are reasonably fast, flexible, readable, and useful for data that does not fit neatly into fixed numeric slots.<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><a href=\"https:\/\/www.caskeys.com\/dc\/wp-content\/uploads\/2026\/05\/image-1.png\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"768\" src=\"https:\/\/www.caskeys.com\/dc\/wp-content\/uploads\/2026\/05\/image-1-1024x768.png\" alt=\"\" class=\"wp-image-7481\" srcset=\"https:\/\/www.caskeys.com\/dc\/wp-content\/uploads\/2026\/05\/image-1-1024x768.png 1024w, https:\/\/www.caskeys.com\/dc\/wp-content\/uploads\/2026\/05\/image-1-300x225.png 300w, https:\/\/www.caskeys.com\/dc\/wp-content\/uploads\/2026\/05\/image-1-768x576.png 768w, https:\/\/www.caskeys.com\/dc\/wp-content\/uploads\/2026\/05\/image-1.png 1448w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><\/a><figcaption class=\"wp-element-caption\">Simplified illustration of OpenBOR\u2019s 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. Hash acceleration gives reasonable performance, and the double linked list provides maximum flexibility.<\/figcaption><\/figure>\n\n\n\n<h2 class=\"wp-block-heading\">Why Use an Array?<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Arrays and lists give you organizational and performance advantages when working with groups of related values. Instead of managing a pile of separate variables, you can keep related data together under one handle and access each value by key or index.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">In the case of indexed arrays, you also get extra speed from hardware acceleration &#8211; an important consideration when working in hot paths.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Example:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: cpp; title: Code:; notranslate\" title=\"Code:\">\n#define PLAYER_DATA_HEALTH 0\n#define PLAYER_DATA_MP     1\n#define PLAYER_DATA_SCORE  2\n\n\/* Define an array with three elements. *\/\nvoid player_data = array(3);\n\n\/* Populate elements. *\/\nset(player_data, PLAYER_DATA_HEALTH, 100);\nset(player_data, PLAYER_DATA_MP, 50);\nset(player_data, PLAYER_DATA_SCORE, 0);\n\n\/* Get values from elements. *\/\nint health = get(player_data, PLAYER_DATA_HEALTH);\nint mp     = get(player_data, PLAYER_DATA_MP);\nint score  = get(player_data, PLAYER_DATA_SCORE);\n<\/pre><\/div>\n\n\n<p class=\"wp-block-paragraph\">This gives you one organized structure instead of three unrelated variables. Note use of named constants for indexes. This isn&#8217;t required, but it is best practice.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Arrays are especially useful when you need to iterate or scan a collection of data, and even more so when you need to handle multidimensional structures (like a grid). <\/p>\n\n\n\n<figure class=\"wp-block-image size-full is-style-default\"><a href=\"https:\/\/www.caskeys.com\/dc\/wp-content\/uploads\/2026\/04\/bor-0008.png\"><img loading=\"lazy\" decoding=\"async\" width=\"480\" height=\"272\" src=\"https:\/\/www.caskeys.com\/dc\/wp-content\/uploads\/2026\/04\/bor-0008.png\" alt=\"Pocket Dimensional Clash select screen.\" class=\"wp-image-7418\" srcset=\"https:\/\/www.caskeys.com\/dc\/wp-content\/uploads\/2026\/04\/bor-0008.png 480w, https:\/\/www.caskeys.com\/dc\/wp-content\/uploads\/2026\/04\/bor-0008-300x170.png 300w\" sizes=\"auto, (max-width: 480px) 100vw, 480px\" \/><\/a><figcaption class=\"wp-element-caption\">Pocket Dimensional Clash select screen. The grid style character selection and the visible grid on screen are handled by multidimensional arrays arranged into a table of columns (parent array), and rows (child array). The row elements in turn each point to locations in a separate model system that contain the data needed to draw icons and display info.<\/figcaption><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">As a practical example, consider a shadow trail (after-images) script. While active, you&#8217;ll want to store n sets of locations along with current sprite in use as the entity moves and animates. Then you play them back to the screen to create a trail of after-images.<\/p>\n\n\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter size-full\"><a href=\"https:\/\/www.caskeys.com\/dc\/wp-content\/uploads\/2026\/05\/shadow_trail_0.png\"><img loading=\"lazy\" decoding=\"async\" width=\"641\" height=\"332\" src=\"https:\/\/www.caskeys.com\/dc\/wp-content\/uploads\/2026\/05\/shadow_trail_0.png\" alt=\"\" class=\"wp-image-7798\" srcset=\"https:\/\/www.caskeys.com\/dc\/wp-content\/uploads\/2026\/05\/shadow_trail_0.png 641w, https:\/\/www.caskeys.com\/dc\/wp-content\/uploads\/2026\/05\/shadow_trail_0-300x155.png 300w\" sizes=\"auto, (max-width: 641px) 100vw, 641px\" \/><\/a><figcaption class=\"wp-element-caption\">Shadow trail after-images<\/figcaption><\/figure>\n<\/div>\n\n\n<p class=\"wp-block-paragraph\">In its simplest form, that means at least two functions resident in hot paths. One running on each entity you want shadow trails enabled to record data. Then another in the global update run against each entity present to get the recorded data for drawing after-image sprites on screen.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Let&#8217;s assume you want six shadow trail after-images. You could do something like this (don&#8217;t worry if it looks like we&#8217;re jumping to the deep end, the following is just a bit of illustration to demonstrate the point):<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: cpp; title: Code:; notranslate\" title=\"Code:\">\nvoid shadowtrail_store_bad(void acting_entity) {\n    \n    \/* Guards *\/\n    if(!acting_entity \n        || typeof(acting_entity) != openborconstant(&quot;VT_PTR&quot;)) {\n        shutdown(1, &quot;\\n\\n ERROR: shadowtrail_store_bad(&quot; + acting_entity + &quot;) - Missing or invalid parameter.\\n&quot;);\n    }\n    \n    \/* We&#039;ll need elapsed time + sprite duration.*\/\n    float expire_time = openborvariant(&quot;elapsed_time&quot;) + 100;\n\n    \/* Get entity properties *\/\n\n    void current_sprite = getentityproperty(acting_entity, &quot;sprite&quot;);\n    float x_pos =   getentityproperty(acting_entity, &quot;x&quot;);\n    float y_pos =   getentityproperty(acting_entity, &quot;y&quot;);\n    float z_pos =   getentityproperty(acting_entity, &quot;z&quot;);\n    \n    \n    \/* Get ring buffer index, initialize to 0 if necessary *\/\n\n    int key_index = getentityvar(acting_entity, &quot;shadowtrail_key_index&quot;);\n\n    if(key_index == NULL()) {\n        key_index = 0;\n    }\n\n    key_index = key_index % TRAIL_COUNT;\n \n    \/* Store sprite and position in entity vars with generated string keys. *\/\n\n    setentityvar(acting_entity, &quot;trail_x_&quot;      + key_index, x_pos);\n    setentityvar(acting_entity, &quot;trail_y_&quot;      + key_index, y_pos);\n    setentityvar(acting_entity, &quot;trail_z_&quot;      + key_index, z_pos);\n    setentityvar(acting_entity, &quot;trail_sprite_&quot; + key_index, current_sprite);\n    setentityvar(acting_entity, &quot;trail_time_&quot;   + key_index, expire_time);\n    \n    \/* Increment the ring buffer index for the next trail frame. *\/\n\n    key_index++;\n    setentityvar(acting_entity, &quot;shadowtrail_key_index&quot;, key_index);\n}\n<\/pre><\/div>\n\n\n<p class=\"wp-block-paragraph\">Then in a global update, we loop over entities and draw any active shadow trails like this:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: cpp; title: Code:; notranslate\" title=\"Code:\">\nvoid shadowtrail_draw_bad(void acting_entity) {\n    \n    \/* Guards *\/\n    if(!acting_entity \n        || typeof(acting_entity) != openborconstant(&quot;VT_PTR&quot;)) {\n        shutdown(1, &quot;\\n\\n ERROR: shadowtrail_draw_bad(&quot; + acting_entity + &quot;) - Missing or invalid parameter.\\n&quot;);\n    }\n\n    \/* Draw each trail sprite from the ring buffer with appropriate offsets. *\/\n    int i = 0;\n    void trail_sprite = NULL();\n    float trail_x = 0.0;\n    float trail_y = 0.0;\n    float trail_z = 0.0;\n    int expire_time = 0;\n    int sort_id = 0;\n    int elapsed_time = openborvariant(&quot;elapsed_time&quot;);\n    \n    \/* Scan indexes for valid trail segments *\/\n    for(i = 0; i &lt; TRAIL_COUNT; i++) {        \n\n        \/* \n        * Get the trail sprite for this segment. \n        * Make sure it&#039;s a valid pointer before \n        * trying to draw. If it&#039;s not valid, this \n        * segment is either uninitialized or cleared, \n        * so we skip it.\n        *\/\n\n        trail_sprite = getentityvar(acting_entity, &quot;trail_sprite_&quot; + i);\n        \n        if(!trail_sprite || typeof(trail_sprite) != openborconstant(&quot;VT_PTR&quot;)) {\n            continue;\n        }\n\n        \/* \n        * Check time. If expired or not set, clear the trail \n        * segment and skip to next iteration.\n        *\/\n\n        expire_time = getentityvar(acting_entity, &quot;trail_time_&quot; + i);\n\n        if(typeof(expire_time) != openborconstant(&quot;VT_INTEGER&quot;) \n        || expire_time &lt;= elapsed_time) {\n        \n            setentityvar(acting_entity, &quot;trail_time_&quot; + i, NULL());\n            setentityvar(acting_entity, &quot;trail_sprite_&quot; + i, NULL());\n            setentityvar(acting_entity, &quot;trail_x_&quot; + i, NULL());\n            setentityvar(acting_entity, &quot;trail_y_&quot; + i, NULL());\n            setentityvar(acting_entity, &quot;trail_z_&quot; + i, NULL());\n            continue;\n        }\n        \n        \/*\n        * Retrieve the position and whatever other data \n        * we need for this trail segment.\n        *\/\n\n        trail_x = getentityvar(acting_entity, &quot;trail_x_&quot; + i);\n        trail_y = getentityvar(acting_entity, &quot;trail_y_&quot; + i);\n        trail_z = getentityvar(acting_entity, &quot;trail_z_&quot; + i);\n        \n        \/*\n        * Older trails go further back, all go one step\n        * behind the current entity z to prevent overlap.\n        *\/\n\n        sort_id = i - 1;\n\n        \/*\n        * Draw the trail sprite at its position.\n        *\/\n\n        drawsprite(trail_sprite, trail_x, trail_y, trail_z, sort_id);        \n    }\n}\n<\/pre><\/div>\n\n\n<p class=\"wp-block-paragraph\">Doesn&#8217;t look too bad, right? It\u2019s simple, straightforward, and works. We assemble some variable names, store data, and then iterate back over them to draw the sprites on screen that make up our shadow trail of afterimages. In actuality however, this common method is an anti-pattern with some severe issues. Chiefly, the live name assembly.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Every time the functions run, we have to assemble variable names like <code>\"trail_x_\" + i<\/code> and <code>\"trail_sprite_\" + i<\/code> piecemeal, and each one incurs an expensive multi-step process.<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li>Convert numeric values into strings.<\/li>\n\n\n\n<li>Concatenate strings to a single larger string.<\/li>\n\n\n\n<li>Hash the finished string.<\/li>\n\n\n\n<li>Use the hash to locate the named variable entry.<\/li>\n<\/ol>\n\n\n\n<p class=\"wp-block-paragraph\">That&#8217;s a lot of extra work running in the hot paths.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Not only that, but because each variable is assembled separately, the shadow trail drawing function has to constantly check every afterimage slot for every entity whether or not it uses shadow trails, all while performing those expensive string operations to do it. We could add an &#8220;active&#8221; flag, but that&#8217;s just one more bespoke string to deal with. Finally, the example here is quite minimal. Real shadow trails will need a lot more properties for visual effect elements, palettes, etc., each one adding more and more to the complexity and runtime load.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">The Better Way<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Instead of all the extra overhead, why not use functionality designed specifically for these sorts of use cases? Let&#8217;s try again with an array. We&#8217;ll store one array handle on the entity, then keep all trail data in the array&#8217;s elements using a technique called flattening, with named constants to identify the elements (again, we&#8217;ll go over all of these methods in detail later, this is just an example):<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: cpp; title: Code:; notranslate\" title=\"Code:\">\n#define SHADOWTRAIL_VARKEY_DATA       &quot;shadowtrail_data&quot;\n#define SHADOW_TRAIL_DURATION        100 \/\/ Duration for each trail segment in centiseconds.\n\n#define SHADOWTRAIL_COUNT             6 \/\/ Number of trail segments to store and draw (ring buffer size).\n\n#define SHADOWTRAIL_DATA_CURSOR       0 \/\/ Ring buffer cursor\/index.\n#define SHADOWTRAIL_DATA_START        1 \/\/ Offset in the data array where trail segment entries begin.\n\n\/*\n* Element offsets from SHADOWTRAIL_DATA_START.\n*\/\n#define SHADOWTRAIL_ENTRY_X          0  \/\/ X position of the stored trail segment.\n#define SHADOWTRAIL_ENTRY_Y          1  \/\/ Y position of the stored trail segment.\n#define SHADOWTRAIL_ENTRY_Z          2  \/\/ Z position of the stored trail segment.\n#define SHADOWTRAIL_ENTRY_SPRITE     3  \/\/ Sprite of the stored trail segment.\n#define SHADOWTRAIL_ENTRY_TIME       4  \/\/ Expiration time of the stored trail segment.\n#define SHADOWTRAIL_ENTRY_SIZE       5  \/\/ Size of each entry in the data array.\n\nvoid shadowtrail_store_good(void acting_entity) {\n    \n    \/* Guards *\/\n    if(!acting_entity \n        || typeof(acting_entity) != openborconstant(&quot;VT_PTR&quot;)) {\n        shutdown(1, &quot;\\n\\n ERROR: shadowtrail_store_good(&quot; + acting_entity + &quot;) - Missing or invalid parameter.\\n&quot;);\n    }\n\n    \/*\n    * Retrieve the trail data array from the entity.\n    * If it doesn&#039;t exist yet, initialize it with the \n    * appropriate size and structure, then store it back \n    * in the entity variable.\n    *\/\n    void trail_data = getentityvar(acting_entity, SHADOWTRAIL_VARKEY_DATA);\n\n    if(!trail_data || typeof(trail_data) != openborconstant(&quot;VT_PTR&quot;)) {\n\n        \/*\n        * No trail data array found for this entity, so we \n        * need to initialize it.\n        *\/\n\n        \/* \n        * Calculate the total size of the trail data array \n        * based on the number of segments and entry size. \n        * Full explanation below in the code comments where \n        * we store the data.\n        *\/\n        int list_size = SHADOWTRAIL_DATA_START + (SHADOWTRAIL_COUNT * SHADOWTRAIL_ENTRY_SIZE);\n\n        \/*\n        * Allocate the trail data array with the calculated \n        * size and store its pointer in the entity variable.\n        *\/\n        trail_data = array(list_size);\n        set(trail_data, SHADOWTRAIL_DATA_CURSOR, 0);\n        setentityvar(acting_entity, SHADOWTRAIL_VARKEY_DATA, trail_data);\n    }\n\n    \/* We&#039;ll need elapsed time + duration. *\/\n    int expire_time = openborvariant(&quot;elapsed_time&quot;) + SHADOW_TRAIL_DURATION;\n\n    \/* Get current sprite and position of the entity. *\/\n    void current_sprite = getentityproperty(acting_entity, &quot;sprite&quot;);\n    float x_pos = getentityproperty(acting_entity, &quot;x&quot;);\n    float y_pos = getentityproperty(acting_entity, &quot;y&quot;);\n    float z_pos = getentityproperty(acting_entity, &quot;z&quot;);\n    int entry_index = 0;\n\n    \/* Get ring buffer index, initialize to 0 if necessary. *\/\n    int cursor = get(trail_data, SHADOWTRAIL_DATA_CURSOR);\n\n    if(typeof(cursor) != openborconstant(&quot;VT_INTEGER&quot;)) {\n        cursor = 0;\n    }\n\n    cursor = cursor % SHADOWTRAIL_COUNT;\n\n    \/* \n    * Flatten the grid data into a singular array\n    * through index offset math.\n    *\/\n    entry_index = SHADOWTRAIL_DATA_START + (cursor * SHADOWTRAIL_ENTRY_SIZE);\n\n    set(trail_data, entry_index + SHADOWTRAIL_ENTRY_X, x_pos);\n    set(trail_data, entry_index + SHADOWTRAIL_ENTRY_Y, y_pos);\n    set(trail_data, entry_index + SHADOWTRAIL_ENTRY_Z, z_pos);\n    set(trail_data, entry_index + SHADOWTRAIL_ENTRY_SPRITE, current_sprite);\n    set(trail_data, entry_index + SHADOWTRAIL_ENTRY_TIME, expire_time); \/\/ Store expiration time\n\n    \/* Increment the cursor for the next trail segment. *\/\n    cursor++;\n    set(trail_data, SHADOWTRAIL_DATA_CURSOR, cursor);\n}\n<\/pre><\/div>\n\n\n<p class=\"wp-block-paragraph\">Then draw with this function.<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: cpp; title: Code:; notranslate\" title=\"Code:\">\nvoid shadowtrail_draw_good(void acting_entity) {\n    \n    \/* Guards *\/\n    if(!acting_entity || typeof(acting_entity) != openborconstant(&quot;VT_PTR&quot;)) {\n        shutdown(1, &quot;\\n\\n ERROR: shadowtrail_draw_good(&quot; + acting_entity + &quot;) - Missing or invalid parameter.\\n&quot;);\n    }\n\n    \/*\n    * Retrieve the trail data array from the entity.\n    * If it doesn&#039;t exist or is invalid, we can&#039;t draw \n    * any trails, so we just exit.\n    *\/\n    void trail_data = getentityvar(acting_entity, SHADOWTRAIL_VARKEY_DATA);\n\n    if(!trail_data || typeof(trail_data) != openborconstant(&quot;VT_PTR&quot;)) {\n        return;\n    }\n\n    int i = 0;  \/\/ Main loop index for iterating through trail segments.\n    int j = 0;  \/\/ Inner loop index for clearing expired trail segments when we find them.\n    int entry_index = 0;\n    void trail_sprite = NULL();\n    float trail_x = 0.0;\n    float trail_y = 0.0;\n    float trail_z = 0.0;\n    int sort_id = 0;\n    int expire_time = 0;\n    int elapsed_time = openborvariant(&quot;elapsed_time&quot;);\n    \n    \/* Scan through all possible trail segments in the ring buffer. *\/\n    for(i = 0; i &lt; SHADOWTRAIL_COUNT; i++) {\n\n        \/* See store function for index calculation. *\/\n        entry_index = SHADOWTRAIL_DATA_START + (i * SHADOWTRAIL_ENTRY_SIZE);\n\n        \/* \n        * Get the trail sprite for this segment. \n        * Make sure it&#039;s a valid pointer before \n        * trying to draw. If it&#039;s not valid, this \n        * segment is either uninitialized or cleared, \n        * so we skip it.\n        *\/\n       \n        trail_sprite = get(trail_data, entry_index + SHADOWTRAIL_ENTRY_SPRITE);\n\n        if(!trail_sprite || typeof(trail_sprite) != openborconstant(&quot;VT_PTR&quot;)) {\n            continue;\n        }\n\n        \/* \n        * Check if the trail segment has expired. \n        * If it has, we clear the data for this \n        * segment and skip to next iteration. \n        *\/\n\n        expire_time = get(trail_data, entry_index + SHADOWTRAIL_ENTRY_TIME);\n        \n        if(typeof(expire_time) != openborconstant(&quot;VT_INTEGER&quot;) \n            || expire_time &lt;= elapsed_time) {\n\n            \/* Clear the trail segment data for this entry. *\/\n            for(j = 0; j &lt; SHADOWTRAIL_ENTRY_SIZE; j++) {\n                set(trail_data, entry_index + j, NULL());\n            }\n\n            continue;\n        }\n\n        \/*\n        * Retrieve the position and whatever other data \n        * we need for this trail segment.\n        *\/\n\n        trail_x = get(trail_data, entry_index + SHADOWTRAIL_ENTRY_X);\n        trail_y = get(trail_data, entry_index + SHADOWTRAIL_ENTRY_Y);\n        trail_z = get(trail_data, entry_index + SHADOWTRAIL_ENTRY_Z);\n\n        \/*\n        * Older trails go further back, all go one step\n        * behind the current entity z to prevent overlap.\n        *\/\n        \n        sort_id = i - 1;\n\n        \/*\n        * Draw the trail sprite at its position.\n        *\/\n\n        drawsprite(trail_sprite, trail_x, trail_y, trail_z, sort_id);        \n    }\n}\n<\/pre><\/div>\n\n\n<p class=\"wp-block-paragraph\">At first glance these functions look a lot more complex, and they do require freeing an array when the entity is removed (more on that later), but that\u2019s only because the code is explicit. This pattern will execute many times faster, consume less memory, and also be simpler to maintain.<\/p>\n\n\n\n<h4 class=\"wp-block-heading\"><strong>Why It&#8217;s Better<\/strong><\/h4>\n\n\n\n<p class=\"wp-block-paragraph\">Using an array gives us several advantages before we even consider the array&#8217;s own performance.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">To start, we\u2019re polling just one string-keyed entity variable to find the trail data array, and there&#8217;s no concatenation at all:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: cpp; title: Code:; notranslate\" title=\"Code:\">\n#define SHADOWTRAIL_VARKEY_DATA &quot;shadowtrail_data&quot;\n\nvoid trail_data = getentityvar(acting_entity, SHADOWTRAIL_VARKEY_DATA);\n<\/pre><\/div>\n\n\n<p class=\"wp-block-paragraph\">After that, all repeated work happens inside one indexed array using integer offsets. Instead of inventing a new variable name for every trail value, the script calculates where each record begins. Because we are working with integers and not strings, these calculations are already faster by an order of magnitude. Then we are able to minimize that work even more by splitting its logic into smaller parts and only repeating the necessary components to get each element&#8217;s index:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: cpp; title: Code:; notranslate\" title=\"Code:\">\nentry_index = SHADOWTRAIL_DATA_START + (i * SHADOWTRAIL_ENTRY_SIZE);\n\ntrail_sprite = get(trail_data, entry_index + SHADOWTRAIL_ENTRY_SPRITE);\ntrail_x = get(trail_data, entry_index + SHADOWTRAIL_ENTRY_X);\ntrail_y = get(trail_data, entry_index + SHADOWTRAIL_ENTRY_Y);\ntrail_z = get(trail_data, entry_index + SHADOWTRAIL_ENTRY_Z);\n<\/pre><\/div>\n\n\n<p class=\"wp-block-paragraph\">This keeps related data together, avoids repeated string concatenation, avoids scattering values across many entity variables, and gives the engine direct numeric indexes to work with. <\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Now consider the array itself. Since we used indexed keys, OpenBOR can store the data in a traditional C array-like container. So not only have we eliminated extra overhead, we enabled hardware-level acceleration to boot.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Lastly, the array pattern makes maintenance and expansion easier. When it&#8217;s time to add delays, alpha, scale, palette, or drawmethod data to each trail segment, you add another field constant and increase <code>SHADOWTRAIL_ENTRY_SIZE<\/code>. The layout stays centralized instead of spreading more generated variable names through your code.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Walk-through<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">No matter which type of array you use, or where you use it, there are only four basic things you can do to an array and its individual elements during the array\u2019s lifecycle. <\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li>Create<\/li>\n\n\n\n<li>Read<\/li>\n\n\n\n<li>Update<\/li>\n\n\n\n<li>Delete<\/li>\n<\/ol>\n\n\n\n<p class=\"wp-block-paragraph\">Anyone with a database background will recognize that immediately as CRUD. That&#8217;s it, that&#8217;s all. Just those four actions. Everything above and below is just mixing and matching CRUD. Not so scary right? <\/p>\n\n\n\n<p class=\"wp-block-paragraph\">All we really need to do is decide when and how we run those actions. Let&#8217;s walk through the steps for each type of array, in <strong>(C)reate<\/strong>, <strong>(U)pdate<\/strong>, <strong>(R)ead<\/strong>, <strong>(D)elete<\/strong> order.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Indexed<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Indexed arrays, or simply arrays, rely on a few core functions: <\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><code>add()<\/code> &#8211; Insert an element.<\/li>\n\n\n\n<li><code>array()<\/code> &#8211; Create an array.<\/li>\n\n\n\n<li><code>delete()<\/code> &#8211; Delete an element.<\/li>\n\n\n\n<li><code>free()<\/code> &#8211; Delete an array.<\/li>\n\n\n\n<li><code>get()<\/code> &#8211; Read an element value.<\/li>\n\n\n\n<li><code>set()<\/code> &#8211; Write to an element value. <\/li>\n\n\n\n<li><code>size()<\/code> &#8211; Get current number of elements in the array.<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">That makes them simple on the surface, but you should still plan around the lifecycle carefully to get the most out of them. <\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Remember the earlier mention of OpenBOR\u2019s double-edged sword? This is where it matters. If you set an out-of-bounds index, or use <code>add()<\/code>, OpenBOR resizes the array for you. That sounds convenient, and it is, but real arrays cannot resize in place. Under the hood, the engine allocates a new array of the required size, copies the old data into it, and smooths the process over through the API.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">That is useful when building or adjusting a structure, but it is not something you want happening in a hot path.<\/p>\n\n\n\n<h4 class=\"wp-block-heading\">CRUD Steps<\/h4>\n\n\n\n<h5 class=\"wp-block-heading\">1. Create<\/h5>\n\n\n\n<p class=\"wp-block-paragraph\">If you don\u2019t create an array, you don\u2019t have an array. To establish one, use the following function:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: cpp; title: Code:; notranslate\" title=\"Code:\">\nvoid array_pointer = array(int size);\n<\/pre><\/div>\n\n\n<p class=\"wp-block-paragraph\">OpenBOR will create a new array and return its pointer. <strong>Don&#8217;t lose the pointer!<\/strong> You will need it for all subsequent steps. <\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Size is the initial number of elements the array starts with, and by extension, the block of memory allocated for storage. This is an important factor to consider, and it is a big part of using arrays to optimize data structures. Best practice is to figure out exactly how many elements you need and allocate that amount before use. <\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Consider the shadow trail example above used to demonstrate iteration. We want six after-images, and each needs its own location, timing, etc., plus a cursor slot to serve as our ring buffer index (more on ring buffers later). We\u2019re using an indexing technique called <em>flattening<\/em> to keep everything in one array (more on that later too), so it&#8217;s going to look something like this.<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>1 cursor slot<\/li>\n\n\n\n<li>6 X values<\/li>\n\n\n\n<li>6 Y values<\/li>\n\n\n\n<li>6 Z values<\/li>\n\n\n\n<li>6 sprite values<\/li>\n\n\n\n<li>6 expire time values<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">That means we need a total of 31 elements for our array, which we can allocate one time and then access quickly:<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">31 = 1 ring buffer cursor + (5 values per trail entry * 6 shadow slots)<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">If you do not allocate sufficient space and later attempt to populate an index that doesn\u2019t exist, OpenBOR will resize the array. On the other hand, you don\u2019t want to allocate some arbitrary number either, because that wastes both memory and CPU time. Plan carefully &#8211; there is usually a static number to find if your design is correct. If the array absolutely must grow dynamically during live gameplay, you may need to consider a string-keyed array instead.<\/p>\n\n\n\n<h5 class=\"wp-block-heading\">2. Update<\/h5>\n\n\n\n<p class=\"wp-block-paragraph\">You wanted to put things in your array, right? Newly allocated arrays begin with all elements set to <code>NULL()<\/code>. To initialize a value or modify an existing value, use the <code>set()<\/code> function.<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: cpp; title: Code:; notranslate\" title=\"Code:\">\nset(array_pointer, index, value);\n<\/pre><\/div>\n\n\n<p class=\"wp-block-paragraph\">This instantly overwrites the value at the target index, simple as that. However, if the index does not exist, OpenBOR will resize the array to accommodate it, and then write to that index. That\u2019s where you need to be careful.<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: cpp; title: Code:; notranslate\" title=\"Code:\">\n\/\/ set() replaces the value at an index.\n\nvoid items = array(3);\n\n\/*\nAfter:\n\n0 = NULL()\n1 = NULL()\n2 = NULL()\n*\/\n\nset(items, 0, &quot;apple&quot;);\nset(items, 1, &quot;banana&quot;);\nset(items, 2, &quot;cherry&quot;);\n\n\/* \nAfter:\n\n0 = &quot;apple&quot;\n1 = &quot;banana&quot;\n2 = &quot;cherry&quot;\n*\/\n\nset(items, 1, &quot;orange&quot;);\n\n\/* \nAfter:\n\n0 = &quot;apple&quot;\n1 = &quot;orange&quot;\n2 = &quot;cherry&quot;\n*\/\n\n\/\/ set() will resize the array to fit an out-of-bounds index.\nset(items, 6, &quot;mango&quot;);\n\n\/* \nAfter: \n\n0 = &quot;apple&quot;\n1 = &quot;orange&quot;\n2 = &quot;cherry&quot;\n3 = NULL()\n4 = NULL()\n5 = NULL()\n6 = &quot;mango&quot;\n*\/\n<\/pre><\/div>\n\n\n<p class=\"wp-block-paragraph\">You may also clear a value back to <code>NULL()<\/code>.<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: cpp; title: Code:; notranslate\" title=\"Code:\">\nset(array_pointer, index, NULL());\n<\/pre><\/div>\n\n\n<p class=\"wp-block-paragraph\">This doesn&#8217;t remove the element, but does clear its value.<\/p>\n\n\n\n<h6 class=\"wp-block-heading\">Inserts<\/h6>\n\n\n\n<p class=\"wp-block-paragraph\">OpenBOR supports inserting values into arrays with the <code>add()<\/code> function.<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: cpp; title: Code:; notranslate\" title=\"Code:\">\nadd(array_pointer, index, value);\n<\/pre><\/div>\n\n\n<p class=\"wp-block-paragraph\">This differs from <code>set()<\/code> in that it does not overwrite the target index. Instead, it creates a new element at that index and shifts all existing elements from that index upward by one. The <code>add()<\/code> function will also accept an index one greater than the last existing index, in which case it appends an element to the end of the array.<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: cpp; title: Code:; notranslate\" title=\"Code:\">\n\/\/ add() inserts a new value at an index.\n\n\/*\nBefore:\n\n0 = &quot;apple&quot;\n1 = &quot;banana&quot;\n2 = &quot;cherry&quot;\n*\/\n\nadd(items, 1, &quot;orange&quot;);\n\n\/*\nAfter:\n\n0 = &quot;apple&quot;\n1 = &quot;orange&quot;\n2 = &quot;banana&quot;\n3 = &quot;cherry&quot;\n*\/\n\n\/\/ Add will accept the array size (one past the last valid index) and append.\nint array_size = size(items);\nadd(items, array_size, &quot;mango&quot;);\n\n\/*\nAfter:\n\n0 = &quot;apple&quot;\n1 = &quot;orange&quot;\n2 = &quot;banana&quot;\n3 = &quot;cherry&quot;\n4 = &quot;mango&quot;\n*\/\n<\/pre><\/div>\n\n\n<p class=\"wp-block-paragraph\">Aside from inserts, <code>add()<\/code> can be useful when building or reordering an ordered list. When you want to append to an array, <code>add()<\/code> may also be safer than set(), because <code>add()<\/code> only accepts indexes from <code>0<\/code> through <code>size(array_pointer)<\/code>. That means it can append one element at the end, but it will not accidentally create a huge gap of <code>NULL()<\/code> elements the way <code>set()<\/code> can. You should still pay attention to your indexes of course. <\/p>\n\n\n\n<p class=\"wp-block-paragraph\">In all cases, <code>add()<\/code> forces a resize, so avoid it in hot paths unless you specifically need insertion behavior.<\/p>\n\n\n\n<h5 class=\"wp-block-heading\">3. Read<\/h5>\n\n\n\n<p class=\"wp-block-paragraph\">There is only one way to read values from an indexed array &#8211; the <code>get()<\/code> function.<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: cpp; title: Code:; notranslate\" title=\"Code:\">\nvoid result = get(array_pointer, index);\n<\/pre><\/div>\n\n\n<p class=\"wp-block-paragraph\">You can safely use <code>get()<\/code> on an out-of-bounds index. It will simply return NULL(). That makes <code>get()<\/code> useful for checks, but don\u2019t confuse \u201csafe\u201d with \u201cfree.\u201d If you are repeatedly reading nothing in a hot path, this likely points to an anti-pattern. You should still keep your indexes valid and try to avoid unnecessary lookups.<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: cpp; title: Code:; notranslate\" title=\"Code:\">\n\/\/ The get() function returns the value at the requested index.\n\nvoid items = array(3);\n\nset(items, 0, &quot;apple&quot;);\nset(items, 1, &quot;banana&quot;);\nset(items, 2, &quot;cherry&quot;);\n\nvoid fruit = get(items, 1);\n\n\/*\nResult:\n\nfruit = &quot;banana&quot;\n*\/\n\n\/\/ If you request an index that does not exist, get() returns NULL().\nvoid missing_fruit = get(items, 9);\n\n\/*\nResult:\n\nmissing_fruit = NULL()\n*\/\n<\/pre><\/div>\n\n\n<h5 class=\"wp-block-heading\">4. Delete<\/h5>\n\n\n\n<p class=\"wp-block-paragraph\">If you want to delete an array, see <a href=\"#Cleanup\" data-type=\"internal\" data-id=\"#Cleanup\">Cleanup<\/a>.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">To remove an element, you can use the <code>delete()<\/code> function. <\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: cpp; title: Code:; notranslate\" title=\"Code:\">\ndelete(array_pointer, index); \n<\/pre><\/div>\n\n\n<p class=\"wp-block-paragraph\">The <code>delete()<\/code> function accepts an in-bounds index, removes that element, and shifts any subsequent indexes down by one. As with add(), this is generally an action to avoid in hot paths. In most cases, it is more prudent to clear the value with set(array_pointer, index, NULL()) and then free the entire array when it is no longer needed.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">String-Keyed \/ Lists<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">String-keyed arrays, or simply lists, rely on the same basic CRUD functions as indexed arrays:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><code>add()<\/code> &#8211; Create or update a named element.<\/li>\n\n\n\n<li><code>array()<\/code> &#8211; Create the container.<\/li>\n\n\n\n<li><code>delete()<\/code> &#8211; Delete a named element.<\/li>\n\n\n\n<li><code>free()<\/code> &#8211; Delete the entire container. See Cleanup.<\/li>\n\n\n\n<li><code>get()<\/code> &#8211; Read a named element value.<\/li>\n\n\n\n<li><code>set()<\/code> &#8211; Write to a named element value.<\/li>\n\n\n\n<li><code>size()<\/code> &#8211; Get the number of elements in the list.<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">In addition, lists have a set of cursor-based functions to aid with iteration. We will cover these in greater detail after the basics.<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><code>reset()<\/code> &#8211; Move the cursor to the first named element.<\/li>\n\n\n\n<li><code>next()<\/code> &#8211; Move the cursor to the next named element.<\/li>\n\n\n\n<li><code>previous()<\/code> &#8211; Move the cursor to the previous named element.<\/li>\n\n\n\n<li><code>key()<\/code> &#8211; Read the current cursor key.<\/li>\n\n\n\n<li><code>value()<\/code> &#8211; Read the current cursor value.<\/li>\n\n\n\n<li><code>isfirst()<\/code> &#8211; Check whether the cursor is at the first element.<\/li>\n\n\n\n<li><code>islast()<\/code> &#8211; Check whether the cursor is at the last element.<\/li>\n<\/ul>\n\n\n\n<h4 class=\"wp-block-heading\">CRUD Steps<\/h4>\n\n\n\n<h5 class=\"wp-block-heading\">1. Create<\/h5>\n\n\n\n<p class=\"wp-block-paragraph\">To create a string-keyed array, use <code>array(0)<\/code>.<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: cpp; title: Code:; notranslate\" title=\"Code:\">\nvoid list_pointer = array(0);\n<\/pre><\/div>\n\n\n<p class=\"wp-block-paragraph\">The 0 matters. String-keyed entries do not use the indexed storage block. They are created as named nodes when you populate the list. Allocating a larger size only creates indexed storage you do not plan to use. <strong>Always allocate 0 for string-keyed arrays.<\/strong><\/p>\n\n\n\n<h5 class=\"wp-block-heading\">2. Update<\/h5>\n\n\n\n<p class=\"wp-block-paragraph\">Updating a list (string keyed array) is simple. Use either <code>add()<\/code> or <code>set()<\/code> and pass a string for the key. Both will behave identically, though <code>set()<\/code> is recommended for consistency across code,  <\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: cpp; title: Code:; notranslate\" title=\"Code:\">\n\/\/ set() replaces the value assigned to a key.\nvoid items = array(0);\n\n\/*\nThe array starts empty. When the first string key is assigned,\nOpenBOR initializes the list structure internally.\n*\/\n\nset(items, &quot;fruit_a&quot;, &quot;apple&quot;);\nset(items, &quot;fruit_b&quot;, &quot;banana&quot;);\nset(items, &quot;fruit_c&quot;, &quot;cherry&quot;);\n\nvoid fruit = get(items, &quot;fruit_b&quot;);\n\n\/*\nResult:\n\nfruit = &quot;banana&quot;\n*\/\n<\/pre><\/div>\n\n\n<p class=\"wp-block-paragraph\">Depending on the array state, OpenBOR takes one of the following actions:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>No existing list &#8211; Initialize the list and create a node with key =&gt; value.<\/li>\n\n\n\n<li>Existing list, key not found &#8211; Create a node with key =&gt; value at end of list.<\/li>\n\n\n\n<li>Existing list, key found &#8211; Update node key with new value.<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">Because string keyed arrays use a doubly linked list, there is no true positional insert in the indexed-array sense. Either an existing node is updated, or a new node is added to the end of the list. <\/p>\n\n\n\n<h5 class=\"wp-block-heading\">3. Read<\/h5>\n\n\n\n<p class=\"wp-block-paragraph\">Lists offer two ways to read data: direct access and cursor access. <\/p>\n\n\n\n<h6 class=\"wp-block-heading\">Direct<\/h6>\n\n\n\n<p class=\"wp-block-paragraph\">Direct access works just like indexed arrays. Use <code>get()<\/code>, and pass your key.<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: cpp; title: Code:; notranslate\" title=\"Code:\">\nvoid fruit = get(items, &quot;fruit_b&quot;);\n\n\/*\nResult:\n\nfruit = &quot;banana&quot;\n*\/\n<\/pre><\/div>\n\n\n<h6 class=\"wp-block-heading\">Cursor<\/h6>\n\n\n\n<p class=\"wp-block-paragraph\">OpenBOR also keeps an internal cursor for string keyed arrays. The cursor points to one node in the list and is primarily intended for iteration.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Cursor functions operate from the cursor\u2019s current position. Use <code>reset()<\/code> to move the cursor to the beginning of the list, then <code>next()<\/code> and <code>previous()<\/code> to move through the collection. At any cursor position, <code>key()<\/code> returns the current key, and <code>value()<\/code> returns the current value.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Calling <code>get()<\/code> or <code>set()<\/code> with a string key also moves the cursor directly to the matching node. This can be useful, but it also means direct reads can change the cursor position during iteration.<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: cpp; title: Code:; notranslate\" title=\"Code:\">\n\/*\nMove the cursor directly to fruit_b. Calling get() with a string key\nlocates that node and leaves the cursor there.\n*\/\nvoid fruit = get(items, &quot;fruit_b&quot;);\n\n\/*\nRead from the current cursor position.\n*\/\nvoid fruit_key = key(items);\nvoid fruit_value = value(items);\n\n\/*\nResults:\n\nfruit_key = &quot;fruit_b&quot;\nfruit_value = &quot;banana&quot;\n*\/\n\n\/*\nIterate through the list.\n*\/\n\nint at_next = 0;\n\n\/* Reset the cursor to the beginning of the collection. *\/\nreset(items);\n\ndo\n{\n    \/* Get key and value from the current item. *\/\n    void item_key = key(items);\n    void item_value = value(items);\n\n    \/*\n    Work with item_key and item_value here.\n    *\/\n\n    \/*\n    Advance cursor to the next item in the collection.\n    Returns 1 if there is a next item, 0 otherwise.\n    *\/\n    at_next = next(items);\n\n} while(at_next);\n<\/pre><\/div>\n\n\n<h5 class=\"wp-block-heading\">4. Delete<\/h5>\n\n\n\n<p class=\"wp-block-paragraph\">It is generally inadvisable to delete list nodes during normal script flow, as this may cause unstable or disjointed behavior. You may use <code>delete()<\/code> with a string key to remove a node from normal iteration order. However, like <code>set()<\/code> and <code>get()<\/code>, <code>delete()<\/code> locates the keyed node first, which moves the cursor to that node.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Afterward, the cursor may still report the deleted key and its last value until you reposition or reset it. In some cases, the node may also remain accessible through <code>get()<\/code>. Treat this as stale internal state, not valid data.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">For this reason, it is usually better to set the node\u2019s value to <code>NULL()<\/code> instead of deleting it. This leaves the node in place, avoids restructuring the list, and lets your script test for an empty value explicitly. Keep in mind that the node will still exist in iteration order, so cursor loops should check for <code>NULL()<\/code> and skip empty entries when needed.<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: cpp; title: Code:; notranslate\" title=\"Code:\">\n\/*\nPreferred: clear the value without removing the node.\n*\/\nset(items, &quot;fruit_b&quot;, NULL());\n\n\/*\nCheck the node later.\n*\/\nif(typeof(get(items, &quot;fruit_b&quot;)) == openborconstant(&quot;VT_EMPTY&quot;))\n{\n    \/*\n    Treat fruit_b as empty.\n    *\/\n}\n<\/pre><\/div>\n\n\n<p class=\"wp-block-paragraph\">If you do choose to delete a node, first set its value to <code>NULL()<\/code>, then call <code>delete()<\/code>. Afterward, make sure to <code>reset()<\/code> the cursor before continuing cursor-based reads. Be especially cautious of performing a delete inside of loops.<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: cpp; title: Code:; notranslate\" title=\"Code:\">\n\/*\nUse delete() only when you need to remove the node from iteration order.\nClear the value first so stale cursor or lookup state cannot retain\nthe previous value.\n*\/\nset(items, &quot;fruit_b&quot;, NULL());\ndelete(items, &quot;fruit_b&quot;);\n\n\/*\ndelete() changes cursor state. Reset before iterating again.\n*\/\nreset(items);\n<\/pre><\/div>\n\n\n<h2 class=\"wp-block-heading\">Cleanup<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Always put your toys away when you are done playing with them! Arrays are global objects and persist even when the function or object that created them is destroyed. This makes arrays powerful for structures and state tracking, but it also means you need to be mindful of cleanup.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">In addition to wasting memory, orphaned arrays make OpenBOR complain in the log on shutdown to warn you about possible memory leaks. You don\u2019t want your projects looking unprofessional, right? You certainly don\u2019t want memory leaks either!<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The first step in cleanup is maintaining access to the array pointer. Remember, we said don\u2019t lose it! If you create an array inside a function and only store its pointer in one of that function\u2019s local variables, then the moment the function completes, your array is orphaned. The pointer is gone, but the array remains. It will hang around like Oliver Twist until the engine finds it on shutdown and whines over the mess.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">So to reiterate &#8211; <strong>don\u2019t lose the pointer!<\/strong><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The next step is cleaning up. To remove an array from memory, use the <code>free()<\/code> function. <\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: cpp; title: Code:; notranslate\" title=\"Code:\">\nfree(array_pointer);\n<\/pre><\/div>\n\n\n<p class=\"wp-block-paragraph\">This destroys the array and releases its memory. Don\u2019t forget to clear any variables that were storing the array pointer.<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: cpp; title: Code:; notranslate\" title=\"Code:\">\n\/\/ The free() function destroys the array and releases its memory.\n\nvoid items = array(3);\n\nset(items, 0, &quot;apple&quot;);\nset(items, 1, &quot;banana&quot;);\nset(items, 2, &quot;cherry&quot;);\n\n\/\/ Free the array.\nfree(items);\n\n\/\/ Clear the variable that stored the array pointer.\nitems = NULL();\n\n\/*\nResult:\n\nitems no longer points to the destroyed array.\nFuture checks can safely detect that the pointer is empty.\n*\/\n<\/pre><\/div>\n\n\n<p class=\"wp-block-paragraph\">In the case of a multidimensional nested array (see below), you will need to free the children before freeing the parent. <\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: cpp; title: Code:; notranslate\" title=\"Code:\">\n\/*\n* Free each child array first. In this example, each indexed\n* slot of parent_array is expected to contain another array.\n*\/\nfor(index = 0; index &lt; size(parent_array); index++) {\n\n    \/* \n    * Get child array and verify it is\n    * a pointer before attempting to free it.\n    * \n    * In 4.x we can use isrray() to check if \n    * the slot contains an array, but in 3.x \n    * we must check if it is a pointer.\n    *\/\n    child_array = get(parent_array, index);\n\n    if(typeof(child_array) == openborconstant(&quot;VT_PTR&quot;)){\n        \n        \/* Free the child array. *\/\n        free(child_array);\n\n        \/*\n        Clear the parent slot so it no longer contains\n        a pointer to freed memory.\n        *\/\n        set(parent_array, index, NULL());\n    }\n}\n<\/pre><\/div>\n\n\n<p class=\"wp-block-paragraph\">Watch out for dangling pointers. This is a common and unwanted state in coding where an object is destroyed, but a pointer to it remains. At best, the pointer no longer points to anything useful. Worse, the engine might later reuse that memory space for something unrelated. If your script tries to use the stale pointer, the result can be corrupted behavior, an engine error, or a segmentation fault. If a true segmentation fault happens, there\u2019s no coming back. The OS will instantly terminate your application.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">OpenBOR attempts to avoid the segmentation fault by throwing an error first and shutting itself down, but that still leaves you staring at the desktop with only a vague log trace to work from. Here&#8217;s an example of how it happens:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: cpp; title: Code:; notranslate\" title=\"Code:\">\n\/\/ WARNING: Do not use a pointer after free().\n\nvoid items = array(3);\n\nset(items, 0, &quot;apple&quot;);\nset(items, 1, &quot;banana&quot;);\nset(items, 2, &quot;cherry&quot;);\n\nfree(items);\n\n\/*\nDanger - Dangling pointer\n\nThe items variable still contains the old pointer address, but that address\nno longer belongs to a valid array.\n*\/\n\n\/\/ Downstream mistake:\nvoid fruit = get(items, 1);\n\n\/*\nResult:\n\nERROR - The script is trying to read from a destroyed array pointer.\nOpenBOR will kick you to the desktop to avoid a segmentation fault crash\nfrom the operating system.\n*\/\n<\/pre><\/div>\n\n\n<p class=\"wp-block-paragraph\">The solution? Lose the pointer. \ud83d\ude42<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: cpp; title: Code:; notranslate\" title=\"Code:\">\n\/\/ Clear the variable that stored the array pointer after freeing.\nitems = NULL();\n\n\/\/ Any time you use the pointer, verify it.\n\nvoid fruit = NULL();\n\nif(items &amp;&amp; typeof(items) == openborconstant(&quot;VT_PTR&quot;)) {\n\n    \/*\n    * Runs only when pointer is not NULL. Works in\n    * conjunction with setting pointer NULL after free.\n    *\/\n\n    fruit = get(items, 1);\n}\n<\/pre><\/div>\n\n\n<h2 class=\"wp-block-heading\">Advanced<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">You can do a lot more with arrays than allocate, set, and get values. The following techniques are not required for basic use, but they are where arrays start becoming powerful tools for larger systems.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Multidimensional <\/h3>\n\n\n\n<h4 class=\"wp-block-heading\">Flattened<\/h4>\n\n\n\n<p class=\"wp-block-paragraph\">Flattening is a technique used to house multidimensional, table-shaped data sets in one physical array.<\/p>\n\n\n\n<figure class=\"wp-block-embed is-type-wp-embed is-provider-dc-current wp-block-embed-dc-current\"><div class=\"wp-block-embed__wrapper\">\n<blockquote class=\"wp-embedded-content\" data-secret=\"lZj6wH6Ym4\"><a href=\"https:\/\/www.caskeys.com\/dc\/flattening-arrays\/\">Flattening Arrays<\/a><\/blockquote><iframe loading=\"lazy\" class=\"wp-embedded-content\" sandbox=\"allow-scripts\" security=\"restricted\" style=\"position: absolute; visibility: hidden;\" title=\"\u201cFlattening Arrays\u201d \u2014 DC Current\" src=\"https:\/\/www.caskeys.com\/dc\/flattening-arrays\/embed\/#?secret=6EUhtQYqi2#?secret=lZj6wH6Ym4\" data-secret=\"lZj6wH6Ym4\" width=\"600\" height=\"338\" frameborder=\"0\" marginwidth=\"0\" marginheight=\"0\" scrolling=\"no\"><\/iframe>\n<\/div><\/figure>\n\n\n\n<h4 class=\"wp-block-heading\">Nested<\/h4>\n\n\n\n<p class=\"wp-block-paragraph\">OpenBOR supports nesting arrays, meaning storing one array\u2019s pointer as a value inside another array. This creates a parent -&gt; child relationship, where the parent array acts like a table of handles to child arrays. For example:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: cpp; title: Code:; notranslate\" title=\"Code:\">\nvoid grid = array(3);\n\nvoid row_0 = array(4);\nvoid row_1 = array(4);\nvoid row_2 = array(4);\n\nset(grid, 0, row_0);\nset(grid, 1, row_1);\nset(grid, 2, row_2);\n\nset(row_1, 2, &quot;selected&quot;);\n<\/pre><\/div>\n\n\n<p class=\"wp-block-paragraph\">This gives you a structure that behaves like:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: cpp; title: Code:; notranslate\" title=\"Code:\">\ngrid&#x5B;1]&#x5B;2] = &quot;selected&quot;\n<\/pre><\/div>\n\n\n<p class=\"wp-block-paragraph\">In OpenBOR syntax, you access it in two steps:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: cpp; title: Code:; notranslate\" title=\"Code:\">\nvoid row = get(grid, 1);\nvoid value = get(row, 2);\n<\/pre><\/div>\n\n\n<p class=\"wp-block-paragraph\">This is useful for grids, tables, menus, per-animation data, and any case where one index naturally leads to another. Child arrays may themselves host child arrays, and virtually any combination is possible, including nesting string-keyed and indexed arrays. One common example is a string-keyed parent acting as the gateway to an indexed child structure.<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><a href=\"https:\/\/www.caskeys.com\/dc\/wp-content\/uploads\/2026\/05\/image-5.png\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"768\" src=\"https:\/\/www.caskeys.com\/dc\/wp-content\/uploads\/2026\/05\/image-5-1024x768.png\" alt=\"\" class=\"wp-image-7518\" srcset=\"https:\/\/www.caskeys.com\/dc\/wp-content\/uploads\/2026\/05\/image-5-1024x768.png 1024w, https:\/\/www.caskeys.com\/dc\/wp-content\/uploads\/2026\/05\/image-5-300x225.png 300w, https:\/\/www.caskeys.com\/dc\/wp-content\/uploads\/2026\/05\/image-5-768x576.png 768w, https:\/\/www.caskeys.com\/dc\/wp-content\/uploads\/2026\/05\/image-5.png 1448w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><\/a><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">You pay a nominal cost for each \u201cjump\u201d from parent to child, but it is still just another array lookup. There is no practical engine-level limit to how far you can nest, but each new dimension multiplies the complexity, and it is easy to get overwhelmed. For simpler table shaped data sets, a flattened indexed array may be faster and simpler to clean up.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Also remember ownership. If a parent array stores child array pointers, free the child arrays before freeing the parent. Once the parent is gone, you may lose the handles you need to clean up the children.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Ring Buffer<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Ring buffers are circular queues used to handle inputs, logs, helper entities, shadow trails, and other repetitive data.<\/p>\n\n\n\n<figure class=\"wp-block-embed is-type-wp-embed is-provider-dc-current wp-block-embed-dc-current\"><div class=\"wp-block-embed__wrapper\">\n<blockquote class=\"wp-embedded-content\" data-secret=\"dvGkQsAYzV\"><a href=\"https:\/\/www.caskeys.com\/dc\/ring-buffer\/\">Ring Buffer<\/a><\/blockquote><iframe loading=\"lazy\" class=\"wp-embedded-content\" sandbox=\"allow-scripts\" security=\"restricted\" style=\"position: absolute; visibility: hidden;\" title=\"\u201cRing Buffer\u201d \u2014 DC Current\" src=\"https:\/\/www.caskeys.com\/dc\/ring-buffer\/embed\/#?secret=jCGsE07PXL#?secret=dvGkQsAYzV\" data-secret=\"dvGkQsAYzV\" width=\"600\" height=\"338\" frameborder=\"0\" marginwidth=\"0\" marginheight=\"0\" scrolling=\"no\"><\/iframe>\n<\/div><\/figure>\n\n\n\n<h2 class=\"wp-block-heading\">Decisions<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Still not sure what to do? here&#8217;s a quick reference chart that might help.<\/p>\n\n\n\n<figure class=\"wp-block-table is-style-stripes\"><table class=\"has-fixed-layout\"><thead><tr><th>Need<\/th><th>Prefer<\/th><\/tr><\/thead><tbody><tr><td>Fixed-size, hot-path data<\/td><td>Indexed array<\/td><\/tr><tr><td>Named, flexible data<\/td><td>String-keyed array<\/td><\/tr><tr><td>2D grid with known dimensions<\/td><td>Flattened indexed array<\/td><\/tr><tr><td>Hierarchical records<\/td><td>Nested arrays<\/td><\/tr><tr><td>Append\/insert during setup<\/td><td><code>add()<\/code> acceptable<\/td><\/tr><tr><td>Frequent mutation during gameplay<\/td><td>Avoid resizing\/shifting<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">Still not sure? Visit the support community and ask us. We&#8217;ll be glad to help.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><a href=\"https:\/\/www.chronocrash.com\">https:\/\/www.chronocrash.com<\/a><\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Reference<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">The following is a list of OpenBOR&#8217;s array and support functions.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">add<\/h3>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: cpp; title: Code:; notranslate\" title=\"Code:\">\nvoid key = 0; \/\/ Int or string.\nvoid value = 0; \/\/ Any type.\n\nadd(void array_pointer, key, value);\n<\/pre><\/div>\n\n\n<ul class=\"wp-block-list\">\n<li><strong>Indexed &#8211;<\/strong> Inserts value at <code>key<\/code>. Key must be from <code>0<\/code> through <code>size(array_pointer)<\/code>. Resizes the array by one and shifts all indexes from <code>key<\/code> upward.<\/li>\n\n\n\n<li><strong>String-Keyed &#8211;<\/strong> Creates a named entry for <code>key<\/code> with <code>value<\/code>, or updates <code>value<\/code> if <code>key<\/code> already exists. Same behavior as <code>set()<\/code>.<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">Avoid using indexed <code>add()<\/code> during active gameplay unless you really need insertion behavior. It resizes and shifts elements.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">array<\/h3>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: cpp; title: Code:; notranslate\" title=\"Code:\">\nvoid array_pointer = array(int size);\n<\/pre><\/div>\n\n\n<p class=\"wp-block-paragraph\">Allocates an array with <code>int size<\/code> initial indexed elements and returns the array pointer.<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>Indexed &#8211;<\/strong> Allocate the number of elements you expect to use.<\/li>\n\n\n\n<li><strong>String-Keyed &#8211;<\/strong> Use <code>array(0)<\/code>, because string-keyed entries are stored separately from the indexed block.<\/li>\n<\/ul>\n\n\n\n<h3 class=\"wp-block-heading\">delete<\/h3>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: cpp; title: Code:; notranslate\" title=\"Code:\">\nvoid key = 0; \/\/ Int or string.\n\ndelete(void array_pointer, key);\n<\/pre><\/div>\n\n\n<ul class=\"wp-block-list\">\n<li><strong>Indexed &#8211;<\/strong> Deletes the target element, resizes the array, and shifts all higher indexes down.<\/li>\n\n\n\n<li><strong>String-Keyed &#8211;<\/strong> Removes the named entry for <code>key<\/code> from iteration order.<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">Avoid using <code>delete()<\/code> during active gameplay. For indexed arrays, <code>delete()<\/code> causes resizing and shifting of the entire array. For string-keyed arrays, it may leave stale state where the cursor is located at former node and still returns value, while and <code>get()<\/code> likewise returns the previous value.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">free<\/h3>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: cpp; title: Code:; notranslate\" title=\"Code:\">\nfree(void object_pointer);\n<\/pre><\/div>\n\n\n<p class=\"wp-block-paragraph\">Not an exclusive array function. Use to remove an array from memory. <\/p>\n\n\n\n<p class=\"has-luminous-vivid-orange-color has-text-color has-link-color wp-elements-f1e0c8836776098cb1e50d2bbd1cdd2a wp-block-paragraph\">Caution: As this is a general freeing function, make sure you pass it a valid array pointer. If you pass it a pointer to some other object, <code>free()<\/code> will destroy that object instead. Also, <code>free()<\/code> only frees the target array &#8211; not any sub arrays or objects the target array referenced. Make sure you <code>free()<\/code> those first if you want to remove them.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">get<\/h3>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: cpp; title: Code:; notranslate\" title=\"Code:\">\nvoid key = 0; \/\/ Int or string.\n \nvoid result = get(void array_pointer, key); \/\/ Any type.\n<\/pre><\/div>\n\n\n<p class=\"wp-block-paragraph\">Returns value at <code>key<\/code>, or <code>NULL()<\/code> if the entry does not exist.<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>Indexed<\/strong> &#8211; Retrieves by integer index. <\/li>\n\n\n\n<li><strong>String-Keyed<\/strong> &#8211; Retrieves by string key.<\/li>\n<\/ul>\n\n\n\n<h3 class=\"wp-block-heading\">isarray <\/h3>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: cpp; title: Code:; notranslate\" title=\"Code:\">\nint is_array = isarray(void object_pointer); \n<\/pre><\/div>\n\n\n<p class=\"wp-block-paragraph\">Version 4.0+ only. Returns <code>1<\/code> if pointer is an array. <code>0<\/code> otherwise.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">isfirst<\/h3>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: cpp; title: Code:; notranslate\" title=\"Code:\">\nint first = isfirst(void array_pointer);\n<\/pre><\/div>\n\n\n<p class=\"wp-block-paragraph\">String-keyed only. Returns <code>1<\/code> if the internal cursor is at first node. <code>0<\/code> otherwise.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">islast<\/h3>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: cpp; title: Code:; notranslate\" title=\"Code:\">\nint last = islast(void array_pointer);\n<\/pre><\/div>\n\n\n<p class=\"wp-block-paragraph\">String-Keyed only.<strong> <\/strong>Returns <code>1<\/code> if the internal cursor is at last node. <code>0<\/code> otherwise.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">key<\/h3>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: plain; title: Code:; notranslate\" title=\"Code:\">\nvoid string_key = key(void array_pointer);\n<\/pre><\/div>\n\n\n<p class=\"wp-block-paragraph\">String-Keyed only. Returns the string key at current cursor position.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">next<\/h3>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: cpp; title: Code:; notranslate\" title=\"Code:\">\nint success = next(void array_pointer);\n<\/pre><\/div>\n\n\n<p class=\"wp-block-paragraph\">String-Keyed only. Attempts to move cursor to next node in order. Returns <code>1<\/code> on success, <code>0<\/code> if already at last node.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">previous<\/h3>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: cpp; title: Code:; notranslate\" title=\"Code:\">\nint success = previous(void array_pointer);\n<\/pre><\/div>\n\n\n<p class=\"wp-block-paragraph\">String-keyed only. Attempts to move cursor to previous node in order. Returns <code>1<\/code> on success, <code>0<\/code> if already at first node.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">reset<\/h3>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: cpp; title: Code:; notranslate\" title=\"Code:\">\nint success = reset(void array_pointer);\n<\/pre><\/div>\n\n\n<p class=\"wp-block-paragraph\">String-Keyed only. Attempts to move cursor to first node in order. Returns <code>1<\/code> on success, <code>0<\/code> if array has no nodes.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">set<\/h3>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: cpp; title: Code:; notranslate\" title=\"Code:\">\nvoid key = 0; \/\/ Int or string.\nvoid value = 0; \/\/ Any type.\n\nset(void array_pointer, key, value);\n<\/pre><\/div>\n\n\n<ul class=\"wp-block-list\">\n<li><strong>Indexed &#8211;<\/strong> Sets target <code>key<\/code> to value. If <code>key<\/code> does not exist, resizes array to accommodate <code>key<\/code> before setting value.<\/li>\n\n\n\n<li><strong>String-Keyed &#8211;<\/strong> Creates a named entry for <code>key<\/code> with <code>value<\/code>, or updates <code>value<\/code> if <code>key<\/code> already exists. Same behavior as <code>add()<\/code>.<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">For indexed arrays, prefer <code>set()<\/code> over <code>add()<\/code> when you already know the slot you want to write.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">size<\/h3>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: cpp; title: Code:; notranslate\" title=\"Code:\">\nint array_size = size(void array_pointer);\n<\/pre><\/div>\n\n\n<ul class=\"wp-block-list\">\n<li><strong>Indexed &#8211;<\/strong> Returns current number of elements in the array.<\/li>\n\n\n\n<li><strong>String-Keyed &#8211;<\/strong> Returns number of named entries.<\/li>\n<\/ul>\n\n\n\n<p class=\"has-luminous-vivid-orange-color has-text-color has-link-color wp-elements-464ab3f2aaf809f97cc04eaab35b3bbe wp-block-paragraph\">Caution: If you mistakenly allocate &gt;0 entries to an array you intend for string keys, <code>size()<\/code> will return that allocated number of elements until you add or set a string-keyed node. Always allocate 0 for string-keyed arrays, and never mix key types.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">value<\/h3>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: cpp; title: Code:; notranslate\" title=\"Code:\">\nvoid array_value = value(void array_pointer); \/\/ Any type\n<\/pre><\/div>\n\n\n<p class=\"wp-block-paragraph\">String-Keyed only.<strong> <\/strong>Returns value at current cursor position.<\/p>\n\n\n<ol class=\"wp-block-footnotes\"><li id=\"fad44a63-c3a3-4dd1-830e-9e3bb3bc8c80\"><strong>Hardware acceleration<\/strong> &#8211; In this case, hardware acceleration does not literally mean routing work to some dedicated chip or special pathway on the machine. What it does mean is that a contiguous memory block allows both cache locality and direct CPU access to indexed data without pointer chasing. Not hardware accelerated by strict definition, but effectively the same thing in practice. <a href=\"#fad44a63-c3a3-4dd1-830e-9e3bb3bc8c80-link\" aria-label=\"Jump to footnote reference 1\">\u21a9\ufe0e<\/a><\/li><\/ol>","protected":false},"excerpt":{"rendered":"<p>OpenBOR natively supports arrays and double linked lists. Learn how they work and how to use them.<\/p>\n","protected":false},"author":1,"featured_media":7905,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_jetpack_newsletter_access":"","_jetpack_dont_email_post_to_subs":false,"_jetpack_newsletter_tier_id":0,"_jetpack_memberships_contains_paywalled_content":false,"_jetpack_memberships_contains_paid_content":false,"footnotes":"[{\"content\":\"<strong>Hardware acceleration<\/strong> - In this case, hardware acceleration does not literally mean routing work to some dedicated chip or special pathway on the machine. What it does mean is that a contiguous memory block allows both cache locality and direct CPU access to indexed data without pointer chasing. Not hardware accelerated by strict definition, but effectively the same thing in practice.\",\"id\":\"fad44a63-c3a3-4dd1-830e-9e3bb3bc8c80\"}]","jetpack_publicize_message":"","jetpack_publicize_feature_enabled":true,"jetpack_social_post_already_shared":true,"jetpack_social_options":{"image_generator_settings":{"template":"highway","default_image_id":0,"font":"","enabled":false},"version":2},"jetpack_post_was_ever_published":true},"categories":[353,71],"tags":[],"class_list":["post-7412","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-gaming-and-fantasy","category-technology-temerity"],"jetpack_publicize_connections":[],"jetpack_featured_media_url":"https:\/\/www.caskeys.com\/dc\/wp-content\/uploads\/2026\/05\/array_cover_0.png","jetpack_sharing_enabled":true,"jetpack_shortlink":"https:\/\/wp.me\/p5lNM5-1Vy","jetpack_likes_enabled":true,"_links":{"self":[{"href":"https:\/\/www.caskeys.com\/dc\/wp-json\/wp\/v2\/posts\/7412","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.caskeys.com\/dc\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.caskeys.com\/dc\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.caskeys.com\/dc\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.caskeys.com\/dc\/wp-json\/wp\/v2\/comments?post=7412"}],"version-history":[{"count":263,"href":"https:\/\/www.caskeys.com\/dc\/wp-json\/wp\/v2\/posts\/7412\/revisions"}],"predecessor-version":[{"id":8206,"href":"https:\/\/www.caskeys.com\/dc\/wp-json\/wp\/v2\/posts\/7412\/revisions\/8206"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/www.caskeys.com\/dc\/wp-json\/wp\/v2\/media\/7905"}],"wp:attachment":[{"href":"https:\/\/www.caskeys.com\/dc\/wp-json\/wp\/v2\/media?parent=7412"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.caskeys.com\/dc\/wp-json\/wp\/v2\/categories?post=7412"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.caskeys.com\/dc\/wp-json\/wp\/v2\/tags?post=7412"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}