{"id":7891,"date":"2026-05-17T22:44:10","date_gmt":"2026-05-18T02:44:10","guid":{"rendered":"https:\/\/www.caskeys.com\/dc\/?p=7891"},"modified":"2026-05-18T09:10:59","modified_gmt":"2026-05-18T13:10:59","slug":"flattening-arrays","status":"publish","type":"post","link":"https:\/\/www.caskeys.com\/dc\/flattening-arrays\/","title":{"rendered":"Flattening Arrays"},"content":{"rendered":"\n<h2 class=\"wp-block-heading\">Introduction<\/h2>\n\n\n\n<p>This guide is written primarily for <a href=\"https:\/\/www.chronocrash.com\">OpenBOR module builders<\/a>, but the underlying principle applies anywhere you need to store a fixed or bounded number of records, each with a predictable set of fields.<\/p>\n\n\n\n<p>Flattening refers to a technique where an indexed array\u2019s keys are mathematically calculated to create a table-shaped data structure within a single array. Imagine six shadow trail entries, and each entry stores five values:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>X position<\/li>\n\n\n\n<li>Y position<\/li>\n\n\n\n<li>Z position<\/li>\n\n\n\n<li>sprite<\/li>\n\n\n\n<li>expiration time<\/li>\n<\/ul>\n\n\n\n<p>Once populated, the data looks like a table:<\/p>\n\n\n\n<figure class=\"wp-block-table is-style-stripes\"><table class=\"has-fixed-layout\"><thead><tr><th>Trail Index<\/th><th>X<\/th><th>Y<\/th><th>Z<\/th><th>Sprite<\/th><th>Time<\/th><\/tr><\/thead><tbody><tr><td>0<\/td><td>125.123684<\/td><td>50.574863<\/td><td>200.681584<\/td><td>#58438713<\/td><td>216<\/td><\/tr><tr><td>1<\/td><td>128.785681<\/td><td>50.574863<\/td><td>200.681584<\/td><td>#58438713<\/td><td>218<\/td><\/tr><tr><td>2<\/td><td>130.256365<\/td><td>50.574863<\/td><td>200.681584<\/td><td>#75825284<\/td><td>220<\/td><\/tr><tr><td>&#8230;<\/td><td>&#8230;<\/td><td>&#8230;<\/td><td>&#8230;<\/td><td>&#8230;<\/td><td>&#8230;<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<p>If we know how many rows we will store, careful offset math lets us flatten the whole table into one indexed array:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>&#91;cursor]&#91;x0]&#91;y0]&#91;z0]&#91;sprite0]&#91;time0]&#91;x1]&#91;y1]&#91;z1]&#91;sprite1]&#91;time1]...<\/code><\/pre>\n\n\n\n<p>To access a given entry, calculate its starting position, including any offset. Start offsets are not strictly necessary, but they allow you to reserve early slots for other important data, such as the ring buffer index in the shadow trail example.<\/p>\n\n\n\n<p>The leading [cursor] is not part of any trail entry. It is reserved metadata. In this example, it stores the <a href=\"https:\/\/www.caskeys.com\/dc\/ring-buffer\/\" data-type=\"post\" data-id=\"7779\">ring buffer\u2019s<\/a> current write position. Reserved slots like this are useful when the array needs to hold both record data and control data:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: cpp; title: Code:; notranslate\" title=\"Code:\">\n#define DATA_START 1 \/\/ Offset where table-shaped data starts.\n\n#define ENTRY_X 0\n#define ENTRY_Y 1\n#define ENTRY_Z 2\n#define ENTRY_SPRITE 3\n#define ENTRY_TIME 4\n\n#define ENTRY_STRIDE 5 \/\/ Number of array slots used by one record.\n\nentry_index = DATA_START + (entry_number * ENTRY_STRIDE);\n<\/pre><\/div>\n\n\n<p>Then access each field as an offset from that position:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: cpp; title: Code:; notranslate\" title=\"Code:\">\nx = get(data, entry_index + ENTRY_X);\ny = get(data, entry_index + ENTRY_Y);\nz = get(data, entry_index + ENTRY_Z);\nsprite = get(data, entry_index + ENTRY_SPRITE);\ntime = get(data, entry_index + ENTRY_TIME);\n<\/pre><\/div>\n\n\n<p>This is the same technique used in the shadow trail example above.<\/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-3.png\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"768\" src=\"https:\/\/www.caskeys.com\/dc\/wp-content\/uploads\/2026\/05\/image-3-1024x768.png\" alt=\"\" class=\"wp-image-7515\" srcset=\"https:\/\/www.caskeys.com\/dc\/wp-content\/uploads\/2026\/05\/image-3-1024x768.png 1024w, https:\/\/www.caskeys.com\/dc\/wp-content\/uploads\/2026\/05\/image-3-300x225.png 300w, https:\/\/www.caskeys.com\/dc\/wp-content\/uploads\/2026\/05\/image-3-768x576.png 768w, https:\/\/www.caskeys.com\/dc\/wp-content\/uploads\/2026\/05\/image-3.png 1448w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><\/a><\/figure>\n\n\n\n<p>The main advantage to flattening is speed and simplicity. OpenBOR supports multidimensional-style structures by storing one array pointer inside another. That works, but each nested access requires fetching a child array handle before accessing the field. A flattened array removes that extra step. The data lives in one continuous indexed block, and cleanup is simple because there is only one array to free.<\/p>\n\n\n\n<p>The tradeoff is readability. Flattened arrays require careful index math and clearly named constants. If your constants are wrong, the structure becomes difficult to debug. Use comments and named offsets generously.<\/p>\n\n\n\n<p>Flattened arrays are best for compact, fixed-size records in hot paths: animation state, trails, timers, hit records, cached coordinates, and other data touched every update cycle.<\/p>\n\n\n\n<p><\/p>\n","protected":false},"excerpt":{"rendered":"<p>Learn how to flatten table-shaped data into one indexed array for faster, cleaner state management.<\/p>\n","protected":false},"author":1,"featured_media":7903,"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":"","jetpack_publicize_message":"","jetpack_publicize_feature_enabled":false,"jetpack_social_post_already_shared":false,"jetpack_social_options":{"image_generator_settings":{"template":"highway","default_image_id":0,"font":"","enabled":false},"version":2},"jetpack_post_was_ever_published":false},"categories":[353,71],"tags":[232,289],"class_list":["post-7891","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-gaming-and-fantasy","category-technology-temerity","tag-applications-openbor","tag-coding-openbor-script"],"jetpack_publicize_connections":[],"jetpack_featured_media_url":"https:\/\/www.caskeys.com\/dc\/wp-content\/uploads\/2026\/05\/flat_array_cove_0.png","jetpack_sharing_enabled":true,"jetpack_shortlink":"https:\/\/wp.me\/p5lNM5-23h","jetpack_likes_enabled":true,"_links":{"self":[{"href":"https:\/\/www.caskeys.com\/dc\/wp-json\/wp\/v2\/posts\/7891","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=7891"}],"version-history":[{"count":4,"href":"https:\/\/www.caskeys.com\/dc\/wp-json\/wp\/v2\/posts\/7891\/revisions"}],"predecessor-version":[{"id":7899,"href":"https:\/\/www.caskeys.com\/dc\/wp-json\/wp\/v2\/posts\/7891\/revisions\/7899"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/www.caskeys.com\/dc\/wp-json\/wp\/v2\/media\/7903"}],"wp:attachment":[{"href":"https:\/\/www.caskeys.com\/dc\/wp-json\/wp\/v2\/media?parent=7891"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.caskeys.com\/dc\/wp-json\/wp\/v2\/categories?post=7891"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.caskeys.com\/dc\/wp-json\/wp\/v2\/tags?post=7891"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}