<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:itunes="http://www.itunes.com/dtds/podcast-1.0.dtd" xmlns:googleplay="http://www.google.com/schemas/play-podcasts/1.0"><channel><title><![CDATA[Bytes Beneath]]></title><description><![CDATA[Occasional deep-dives on building games from the ground up - no engines, just grit and code.]]></description><link>https://www.bytesbeneath.com</link><image><url>https://substackcdn.com/image/fetch/$s_!G6W0!,w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F410b2934-9526-41e2-b517-4180b6d92282_500x500.png</url><title>Bytes Beneath</title><link>https://www.bytesbeneath.com</link></image><generator>Substack</generator><lastBuildDate>Sun, 19 Apr 2026 03:54:17 GMT</lastBuildDate><atom:link href="https://www.bytesbeneath.com/feed" rel="self" type="application/rss+xml"/><copyright><![CDATA[Dylan Falconer]]></copyright><language><![CDATA[en]]></language><webMaster><![CDATA[falconerd@substack.com]]></webMaster><itunes:owner><itunes:email><![CDATA[falconerd@substack.com]]></itunes:email><itunes:name><![CDATA[Dylan Falconer]]></itunes:name></itunes:owner><itunes:author><![CDATA[Dylan Falconer]]></itunes:author><googleplay:owner><![CDATA[falconerd@substack.com]]></googleplay:owner><googleplay:email><![CDATA[falconerd@substack.com]]></googleplay:email><googleplay:author><![CDATA[Dylan Falconer]]></googleplay:author><itunes:block><![CDATA[Yes]]></itunes:block><item><title><![CDATA[Make Any Game You Can Imagine - Programming Lies]]></title><description><![CDATA[You have been told that the first step of creating the game of your dreams is to pick an engine. In this contained a subtle lie.]]></description><link>https://www.bytesbeneath.com/p/make-any-game-you-can-imagine-programming</link><guid isPermaLink="false">https://www.bytesbeneath.com/p/make-any-game-you-can-imagine-programming</guid><dc:creator><![CDATA[Dylan Falconer]]></dc:creator><pubDate>Mon, 23 Feb 2026 04:28:51 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/30eab7c2-a16e-4d94-a089-4ba85c4a3c94_1861x973.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>You have been told that the first step of creating the game of your dreams is to pick an engine. And from the moment you accepted that framing, your dreams have been bound, inhibited, curbed, without you even realising.</p><p>The lie was implicit. It wasn&#8217;t intentional, but it was there nonetheless: <em>You can make anything you want [with these engines]</em>.</p><p>Can you travel through your voxel world at 7,400km/h in real-time?</p><p>Can you &#8220;simply&#8221; carve a tunnel through a world built entirely from mathematical equations?</p><p>Most programmers never find out. They optimise for shipping quickly, take the common path, and never notice the walls.</p><p>These aren&#8217;t theoretical examples, these are games that exist right now, built by programmers who went a different way.</p><p><strong>Engines don&#8217;t remove the hard problems&#8212;they only bury them. If you want to grow as a programmer, you must eventually run the gauntlet.</strong></p><p>By the end of this article, you&#8217;ll see why:</p><ul><li><p>Understanding lets you go where engines can&#8217;t follow</p></li><li><p>Your tools define your possibility space</p></li><li><p>The constraint isn&#8217;t only technical, it&#8217;s creative</p></li></ul><h2><strong>Understanding Lets You Go Where Engines Can&#8217;t Follow</strong></h2><p>A Flock of Meese has been sharing his Minecraft-like game engine for the Gamecube and Dreamcast:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!LlN9!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F972d9b9d-313b-4088-ae8a-a49fda72688c_598x520.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!LlN9!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F972d9b9d-313b-4088-ae8a-a49fda72688c_598x520.png 424w, https://substackcdn.com/image/fetch/$s_!LlN9!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F972d9b9d-313b-4088-ae8a-a49fda72688c_598x520.png 848w, https://substackcdn.com/image/fetch/$s_!LlN9!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F972d9b9d-313b-4088-ae8a-a49fda72688c_598x520.png 1272w, https://substackcdn.com/image/fetch/$s_!LlN9!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F972d9b9d-313b-4088-ae8a-a49fda72688c_598x520.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!LlN9!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F972d9b9d-313b-4088-ae8a-a49fda72688c_598x520.png" width="598" height="520" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/972d9b9d-313b-4088-ae8a-a49fda72688c_598x520.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:520,&quot;width&quot;:598,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:391308,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://www.bytesbeneath.com/i/188866019?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F972d9b9d-313b-4088-ae8a-a49fda72688c_598x520.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!LlN9!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F972d9b9d-313b-4088-ae8a-a49fda72688c_598x520.png 424w, https://substackcdn.com/image/fetch/$s_!LlN9!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F972d9b9d-313b-4088-ae8a-a49fda72688c_598x520.png 848w, https://substackcdn.com/image/fetch/$s_!LlN9!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F972d9b9d-313b-4088-ae8a-a49fda72688c_598x520.png 1272w, https://substackcdn.com/image/fetch/$s_!LlN9!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F972d9b9d-313b-4088-ae8a-a49fda72688c_598x520.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>The Dreamcast is a console from 1998. It has a 200MHz CPU, 16MB of RAM, 8MB of VRAM and a 100MHz GPU.</p><p>The difference between the Dreamcast and a typical gaming PC in 2009, the year Minecraft was created is orders of magnitude.</p><p>The Dreamcast peaked at ~7 million polygons/sec. An <em>average</em> 2009 PC: 775 million/sec&#8212;110x more.</p><p>This Dreamcast example is only possible because the programmer understands the hardware <em>and the problem space</em> at a deep level.</p><p>It also means that Minecraft could have launched in 1998. There was no technical reason that it did not exist yet, only one of human ingenuity.</p><p>We must assume that right now there are game concepts waiting to be discovered, that use the constraints of our current hardware to their maximum, and <em>likely </em>won&#8217;t be discovered for decades.</p><h2><strong>Your Tools Define Your Possibility Space</strong></h2><p>Here is the PC version of the same engine. Movement at 7,400km/h with a 1KM draw distance, in real-time with no frame dips, at 60FPS:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!FQf7!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffa1e4788-6905-4647-8679-e9a76fd3b457_598x738.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!FQf7!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffa1e4788-6905-4647-8679-e9a76fd3b457_598x738.png 424w, https://substackcdn.com/image/fetch/$s_!FQf7!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffa1e4788-6905-4647-8679-e9a76fd3b457_598x738.png 848w, https://substackcdn.com/image/fetch/$s_!FQf7!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffa1e4788-6905-4647-8679-e9a76fd3b457_598x738.png 1272w, https://substackcdn.com/image/fetch/$s_!FQf7!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffa1e4788-6905-4647-8679-e9a76fd3b457_598x738.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!FQf7!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffa1e4788-6905-4647-8679-e9a76fd3b457_598x738.png" width="598" height="738" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/fa1e4788-6905-4647-8679-e9a76fd3b457_598x738.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:738,&quot;width&quot;:598,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:258640,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://www.bytesbeneath.com/i/188866019?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffa1e4788-6905-4647-8679-e9a76fd3b457_598x738.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!FQf7!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffa1e4788-6905-4647-8679-e9a76fd3b457_598x738.png 424w, https://substackcdn.com/image/fetch/$s_!FQf7!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffa1e4788-6905-4647-8679-e9a76fd3b457_598x738.png 848w, https://substackcdn.com/image/fetch/$s_!FQf7!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffa1e4788-6905-4647-8679-e9a76fd3b457_598x738.png 1272w, https://substackcdn.com/image/fetch/$s_!FQf7!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffa1e4788-6905-4647-8679-e9a76fd3b457_598x738.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>So far the developer has been quiet about his methods, but I can say for certain that you don&#8217;t get these kinds of results without deeply understanding the problem space, and the hardware.</p><p>Contrast this with the industry standard high-performance game engine: Unreal Engine 5. With typical cell sizes of 256m^3, and loading ranges around 768m radius, you can&#8217;t sustain that speed without FPS drops (called &#8220;hitches&#8221; in <a href="https://dev.epicgames.com/community/learning/tutorials/qpll/unreal-engine-level-streaming-hitching-guide">their documentation</a>).</p><p>With the engine A Flock of Meese has created, game design ideas that aren&#8217;t even possible in other engines are unlocked.</p><p>Want to create a game about an immortal cat piloting ICBMs across a vast voxel world in real-time? You can&#8217;t do that in just any engine.</p><h2><strong>The Constraint Isn&#8217;t Only Technical, It&#8217;s Creative</strong></h2><p>Mike Turitzin has been sharing his SDF-based game engine, and the results are something else:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!ySEZ!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2bfa5d02-1017-437a-8f08-ff696db24336_598x756.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!ySEZ!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2bfa5d02-1017-437a-8f08-ff696db24336_598x756.png 424w, https://substackcdn.com/image/fetch/$s_!ySEZ!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2bfa5d02-1017-437a-8f08-ff696db24336_598x756.png 848w, https://substackcdn.com/image/fetch/$s_!ySEZ!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2bfa5d02-1017-437a-8f08-ff696db24336_598x756.png 1272w, https://substackcdn.com/image/fetch/$s_!ySEZ!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2bfa5d02-1017-437a-8f08-ff696db24336_598x756.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!ySEZ!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2bfa5d02-1017-437a-8f08-ff696db24336_598x756.png" width="598" height="756" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/2bfa5d02-1017-437a-8f08-ff696db24336_598x756.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:756,&quot;width&quot;:598,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:306433,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://www.bytesbeneath.com/i/188866019?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2bfa5d02-1017-437a-8f08-ff696db24336_598x756.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!ySEZ!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2bfa5d02-1017-437a-8f08-ff696db24336_598x756.png 424w, https://substackcdn.com/image/fetch/$s_!ySEZ!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2bfa5d02-1017-437a-8f08-ff696db24336_598x756.png 848w, https://substackcdn.com/image/fetch/$s_!ySEZ!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2bfa5d02-1017-437a-8f08-ff696db24336_598x756.png 1272w, https://substackcdn.com/image/fetch/$s_!ySEZ!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2bfa5d02-1017-437a-8f08-ff696db24336_598x756.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>A gun that opens a traversable tunnel through arbitrary geometry in real time, <em>and then closes it again</em>.</p><p>Mike calls it simple, because he built the tools that made it natural to think about this type of operation.</p><p>The engine-dependent programmer never arrives at this idea because nothing in their toolchain points towards it.</p><p><strong>The limitations shape the imagination before the developer even sits down to design the game.</strong></p><h2><strong>Conclusion</strong></h2><p>The programmers in this article didn&#8217;t find a shortcut. They worked through the hard problems and came out the other side with the capability to explore spaces other programmers cannot.</p><p>They researched how programs work, the hardware, the mathematics, and created <em>their own unique possibility space</em>.</p><p>That&#8217;s the gauntlet. It&#8217;s harder. It&#8217;s slower. But what&#8217;s waiting through the fire isn&#8217;t just better code, it&#8217;s a possibility space that&#8217;s uniquely yours.</p><blockquote><p>Fire is the test of gold; adversity, of strong men,<br>  &#8212; Seneca</p></blockquote><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://www.bytesbeneath.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading Bytes Beneath! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div>]]></content:encoded></item><item><title><![CDATA[Why I Still Choose Custom Engines in 2026]]></title><description><![CDATA[A video version of this article is available on my YouTube channel:]]></description><link>https://www.bytesbeneath.com/p/why-i-still-choose-custom-engines</link><guid isPermaLink="false">https://www.bytesbeneath.com/p/why-i-still-choose-custom-engines</guid><dc:creator><![CDATA[Dylan Falconer]]></dc:creator><pubDate>Fri, 06 Feb 2026 16:06:34 GMT</pubDate><enclosure url="https://substackcdn.com/image/youtube/w_728,c_limit/dDhIjP2B5VQ" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>A video version of this article is available on my YouTube channel: </p><div id="youtube2-dDhIjP2B5VQ" class="youtube-wrap" data-attrs="{&quot;videoId&quot;:&quot;dDhIjP2B5VQ&quot;,&quot;startTime&quot;:null,&quot;endTime&quot;:null}" data-component-name="Youtube2ToDOM"><div class="youtube-inner"><iframe src="https://www.youtube-nocookie.com/embed/dDhIjP2B5VQ?rel=0&amp;autoplay=0&amp;showinfo=0&amp;enablejsapi=0" frameborder="0" loading="lazy" gesture="media" allow="autoplay; fullscreen" allowautoplay="true" allowfullscreen="true" width="728" height="409"></iframe></div></div><p>There are many reasons I build my own engines from scratch, and one that&#8217;s not what you think... It has to do with the collapse of civilisation.</p><p>But first, let&#8217;s talk about the obvious stuff that made me quit game engines.</p><h2>The Obvious</h2><p>Why do most people stick with big engines like Unity even when they&#8217;re frustrated? I speak to developers that have successful games published that *hate* working in their engine.</p><p>For them, dealing with frustration feels safer than starting over, and much safer than going engine-free. But here&#8217;s the thing: much of what they are protecting is engine-specific trivia that never transfers anywhere else anyway.</p><p>I used to think that serialisation was difficult, because it was, back when I used Unity as my main engine.</p><p>Nowadays, with my knowledge of building custom engines, it&#8217;s not only trivial, but fun to write serialisation code for my games.</p><p>One of the reasons that serialisation was such a chore back in those days was because of Unity&#8217;s  version control tool. It was rudimentary and had merge conflicts constantly that lost us hours or days of work.</p><p>And that&#8217;s not even to mention the compile times of these engines. Sitting around for tens of seconds, or sometimes minutes just to test a change is insane considering how powerful modern computers are.</p><p>When I&#8217;m writing my own game engine, 2D or 3D, if the compile time is greater than 1 second -  something&#8217;s wrong.</p><h2>Control</h2><p>Beyond the day-to-day minutiae, it&#8217;s also about control.</p><p>What happens when something breaks in a 3rd party engine? You&#8217;re stuck waiting for a patch or workaround, and praying the patch doesn&#8217;t add more bugs.</p><p>In my world? I can dive in and fix it myself immediately - and, that forces higher quality. No excuses, no finger-pointing, because I&#8217;ll just be pointing in the mirror in that case.</p><p>I take full responsibility for any low-quality outcome.</p><p>My stat distribution heavily weighted toward programming. I care about software craftsmanship. This is my way to lead by example - proving you don&#8217;t need massive tools to make something great.</p><h2>3rd Party Risks</h2><p>If you want to take game development seriously and treat it like a business, you have to manage your risk profile. Using a 3rd party engine is a non-trivial liability. If at any point the incentives of you and the company that makes your game engine don&#8217;t align, they can cause serious damage. Here are some real stories to prove it.</p><h3>Unity Runtime Fee Fiasco 2023</h3><p>Back in 2023, Unity was the go-to engine for indies. That market share has been significantly eaten into by Godot since then - and this is one reason why.</p><p>If you somehow haven&#8217;t heard of the runtime fee fiasco, let me lay it out for you.</p><p>Unity announced a pricing change - adding a $0.20 fee, after a threshold, for the developer to pay to Unity *per installation* of their game. The kicker? It applied to games that were already published *and* to every instance of an installation - even the same user installing your game again after deleting it would incur a $0.20 fee.</p><p>As you can imagine, the game developer community was very upset about this - and, rightfully so. Many studios faced potential bankruptcy due to the way their games were priced.</p><p>Your free to play game that gets millions of installs per year, but makes no money? Pretty soon, you&#8217;d owe Unity 6-figures.</p><p>After massive backlash, Unity scrapped the per-install fee entirely in 2024, but the damage was done.</p><p>Many devs had their trust broken, and left Unity for other engines, or went engine-free.</p><h3>Our Machinery License Termination 2022</h3><p>Our Machinery was a game engine being developed by two ex-Autodesk developers starting in 2021. It was a promising extensible engine written in C.</p><p>It seemed like a reliable up-and-coming alternative without the bloat of engines like Unity or Unreal.</p><p>In 2022, the company shut down and terminated all licenses overnight, demanding that all studios using the engine delete their source code and binaries *within 14 days*.</p><p>There was no path for migration, and we still aren&#8217;t sure what happened.</p><p>One Hacker News user commented<a class="footnote-anchor" data-component-name="FootnoteAnchorToDOM" id="footnote-anchor-1" href="#footnote-1" target="_self">1</a> that a friend had been building a game using Our Machinery, and had started the painful process of porting it to Godot.</p><p>Arrowhead and Fatshark, the creators of the Helldivers franchise and Warhammer: 40K Darktide, respectively had invested financially into Our Machinery<a class="footnote-anchor" data-component-name="FootnoteAnchorToDOM" id="footnote-anchor-2" href="#footnote-2" target="_self">2</a>. The engine&#8217;s failure meant that investment was wasted, though they weren&#8217;t developing games in it that we know of.</p><h3>Unity Security Vulnerability 2025</h3><p>Using new technology is generally riskier, but what about using something that&#8217;s been around for ages? Let&#8217;s take another look at a recent security vulnerability with Unity games published since 2017.</p><p>As indie game developers, we don&#8217;t want to have to understand everything about cyber security - so, we offload the responsibility to the authors of the tools we use.</p><p>But what happens when the people making the tools make a mistake? Everyone using them pays the price.</p><p>Any game made with Unity 2017.1 up until October 2025 - that&#8217;s 8 years - had to patch this vulnerability. Fortunately, Unity provided binary patch tools to apply the fix without doing full rebuilds and there is no evidence the exploit was used.</p><div><hr></div><p>These aren&#8217;t hypotheticals that may *one day* happen. They have happened and will continue to happen.</p><p>With a custom engine? You don&#8217;t have that kind of risk.</p><h2>Fun and Ease</h2><p>Programming games from scratch is made out to be this Herculean task that only a few can accomplish. Maybe that&#8217;s true, maybe not. All I know is that if your stat distribution is over-indexed into programming, or you *want it to be*, then this path is not just possible, but *can be* easier.</p><p>I enjoy &#8220;low level&#8221; programming. Bit flipping, data packing, pointer abuse, memory management, all that kind of stuff. For me, it&#8217;s easier to create a small game engine that fits my needs than to learn and fight with a big game engine.</p><p>If you don&#8217;t want to start from scratch, working in a systems language like Odin or C means you can use pretty much any library out there easily.</p><p>From something like raylib, which handles windowing, input, sound, rendering, and even some collision detection, to something more bare-bones like SDL3 or Sokol.</p><p>I have no editor overhead. What I type is what compiles. And, it takes me less than 1 second to compile my game. My game, which, by the way, has a play test available on <a href="https://store.steampowered.com/app/4310870/LANE4/">Steam</a>, and also a web build playable on <a href="https://falconerd.itch.io/lane4">itch.io</a> right now. If you are into Zachtronics style games, you&#8217;ll like LANE-4.</p><h2>The Collapse of Civilisation</h2><p>Finally, we get to the reason that may seem outlandish, but makes total sense - The Collapse of Civilisation.</p><p>As we move into this LLM-powered future, more and more developers are outsourcing not just their programming, but their critical thinking to idiot machines. Machines that are not conscious, cannot think, but can produce a simulacrum of what a human can.</p><p>The effect of offloading these skills is that less people understand how computers work at a fundamental level. This is an issue since almost all our infrastructure is built with computers of some kind - be that the tiny microcontroller in your air conditioner, or the high-powered CPU in your phone or computer.</p><p>It follows that if less people understand how to fix our infrastructure, that will create a skill-supply shortage. Increasingly, situations will arise in which nobody is available to fix the problem, and part of our civilisational infrastructure is left to degrade.</p><p>Knowledge is not automatically propagated forward, otherwise we would have a crystal clear understanding of the Ancient Egyptians, or the Mayans and Aztecs, or the Mesopotamians.</p><p>Instead, we have fragments to piece together a puzzle which can never be completely solved.</p><p>This loss of knowledge has happened in the distant past, and the very recent past. There are a couple of good talks on this, already, so I&#8217;d recommend you watch these if you are interested:</p><p>One is <a href="https://www.youtube.com/watch?v=ZSRHeXYDLko">&#8220;Preventing the Collapse of Civilization&#8221; by Jonathan Blow</a></p><p>Another is <a href="https://www.youtube.com/watch?v=bRcu-ysocX4">&#8220;1177 BC: The Year Civilization Collapsed&#8221; by Eric Cline</a></p><div><hr></div><p>Enjoyed this? I made <a href="https://programvideogames.com/?src=newsletter_">Program Video Games</a> for you.<br>Game Ideas -&gt; Working Systems using first principles.</p><div class="footnote" data-component-name="FootnoteToDOM"><a id="footnote-1" href="#footnote-anchor-1" class="footnote-number" contenteditable="false" target="_self">1</a><div class="footnote-content"><p><a href="https://news.ycombinator.com/item?id=32300785">https://news.ycombinator.com/item?id=32300785</a></p></div></div><div class="footnote" data-component-name="FootnoteToDOM"><a id="footnote-2" href="#footnote-anchor-2" class="footnote-number" contenteditable="false" target="_self">2</a><div class="footnote-content"><p><a href="https://tech.yahoo.com/gaming/articles/helldivers-2-built-obscure-swedish-232158475.html">https://tech.yahoo.com/gaming/articles/helldivers-2-built-obscure-swedish-232158475.html</a></p><p></p></div></div>]]></content:encoded></item><item><title><![CDATA[Hot Reloading in Odin + Raylib]]></title><description><![CDATA[Change Code - Instantly See Results In-Game]]></description><link>https://www.bytesbeneath.com/p/hot-reloading-in-odin-raylib</link><guid isPermaLink="false">https://www.bytesbeneath.com/p/hot-reloading-in-odin-raylib</guid><dc:creator><![CDATA[Dylan Falconer]]></dc:creator><pubDate>Tue, 07 Oct 2025 05:04:22 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!jYUu!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F54f1147d-23ed-4b86-92dc-098436b87fa8_1277x861.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>You change one line of code. Compile. Wait. Run. Test. Repeat 47 times. There goes your afternoon.</p><p><strong>In this newsletter:</strong></p><ol><li><p>Why the compile-restart destroys your productivity</p></li><li><p>How to hot reload in Odin + Raylib without losing state</p></li><li><p>The affordance that&#8217;s unlocked when you use hot reloading</p></li></ol><div><hr></div><h2>1. The Manual Feedback Loop Tax</h2><p>Every second between &#8220;I have a cool idea&#8221; and having the implementation working in your game to see if it works is friction.</p><p>Whether you&#8217;re tweaking the main character&#8217;s jump height, or modifying the colours of a background piece of art, or modifying the pitch of a sound effect, one thing is clear: You want to <em>observe the change as quickly as possible.</em></p><p>With the default setup, you tweak, compile, get back to the correct state, then you can test. This can take from seconds to minutes, depending on your game and what you&#8217;re testing. And then, you have to do it again because the first change is invariably not the last.</p><p>One of the most common reasons cited for embedding slow scripting languages into most game engines is to be able to change things while the game is running. But, what if you don&#8217;t want to, or aren&#8217;t using a scripting language?</p><h2>2. Dynamic Libraries to the Rescue</h2><p><strong>Odin can compile your code as a dynamic library (.dll/.so/.dylib) that loads at runtime</strong></p><p>The trick: split your code into two parts. The terminology is borrowed from Casey Muratori&#8217;s excellent Handmade Hero series.</p><ol><li><p><strong>The Cradle:</strong> This part of the program handles OS and platform code. It owns all the memory, and it calls into the dynamic library.</p></li><li><p><strong>The Game:</strong> A dynamic library that contains only game code. Any state that is required across reloads must live in the memory that&#8217;s passed from the Cradle.</p></li></ol><p>For this example, I have a third component: A <code>raylib_api</code> package that provides runtime procedure bindings for raylib. This keeps the build process simple - less moving parts.</p><p>The code below is to get the gist, full source code is linked at the end.</p><pre><code><code>// main.odin - The Cradle
main :: proc() {
    rl_api := raylib_api.init()
    game_api: Game_API
    game_api_reload(&amp;game_api)
    
    rl.InitWindow(800, 600, &#8220;Hot Reloading&#8221;)
    defer rl.CloseWindow()
    
    // Ask the Game how much memory it needs
    game_memory_buffer := make([]u8, game_api.game_memory_size())
    game_memory_pointer := raw_data(game_memory_buffer)
    
    game_api.game_init(game_memory_pointer, &amp;rl_api)
    
    for !rl.WindowShouldClose() {
        // Check if the DLL changed on disk
        if dll_write_time, err := os.last_write_time_by_name(DLL_PATH_FULL); err == nil {
            if dll_write_time != game_api.last_write_time {
                game_api_reload(&amp;game_api)  // Hot reload!
            }
        }
        
        game_api.game_update(game_memory_pointer, &amp;rl_api)
        
        rl.BeginDrawing()
        game_api.game_render(game_memory_pointer, &amp;rl_api)
        rl.EndDrawing()
    }
}

// game.odin - The Game

import rl "vendor:raylib"
import "raylib_api"

// Types and constants from raylib are safe to use
Vec2 :: rl.Vector2

Game_State :: struct {
&#9;positions: [dynamic]Vec2,
&#9;sizes: [dynamic]Vec2,
&#9;colors: [dynamic]rl.Color,
}

@export
game_memory_size :: proc() -&gt; int {
&#9;return size_of(Game_State)
}

@export
game_init :: proc(gs: ^Game_State, rl: ^raylib_api.Raylib_API) { // ...

// other procedures follow the same structure ...</code></code></pre><p>The Cradle is compiled as your main application - your .exe on Windows. It then loads the Game at runtime and binds the procedures to an API that can be called. Once bound, the Cradle can then call into the loaded procedures, passing in the required memory to keep the game running.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!jYUu!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F54f1147d-23ed-4b86-92dc-098436b87fa8_1277x861.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!jYUu!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F54f1147d-23ed-4b86-92dc-098436b87fa8_1277x861.png 424w, https://substackcdn.com/image/fetch/$s_!jYUu!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F54f1147d-23ed-4b86-92dc-098436b87fa8_1277x861.png 848w, https://substackcdn.com/image/fetch/$s_!jYUu!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F54f1147d-23ed-4b86-92dc-098436b87fa8_1277x861.png 1272w, https://substackcdn.com/image/fetch/$s_!jYUu!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F54f1147d-23ed-4b86-92dc-098436b87fa8_1277x861.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!jYUu!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F54f1147d-23ed-4b86-92dc-098436b87fa8_1277x861.png" width="1277" height="861" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/54f1147d-23ed-4b86-92dc-098436b87fa8_1277x861.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:861,&quot;width&quot;:1277,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:115665,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://www.bytesbeneath.com/i/175498391?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F54f1147d-23ed-4b86-92dc-098436b87fa8_1277x861.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!jYUu!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F54f1147d-23ed-4b86-92dc-098436b87fa8_1277x861.png 424w, https://substackcdn.com/image/fetch/$s_!jYUu!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F54f1147d-23ed-4b86-92dc-098436b87fa8_1277x861.png 848w, https://substackcdn.com/image/fetch/$s_!jYUu!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F54f1147d-23ed-4b86-92dc-098436b87fa8_1277x861.png 1272w, https://substackcdn.com/image/fetch/$s_!jYUu!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F54f1147d-23ed-4b86-92dc-098436b87fa8_1277x861.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Recompiling the Game will trigger a reload and rebind the procedures - changing gameplay code in real time.</p><h2>3. The Affordances Hot Reloading Unlocks</h2><p><strong>Hot reloading doesn&#8217;t just save time. It changes the possibility space.</strong></p><p>With instant feedback, you entirely remove the friction of making changes, and avoid the tendency to batch a bunch of small changes together. Bonus points if you put a build hotkey in your code editor.</p><p>You can <em>sculpt</em> the game feel in real-time, for example:</p><p><strong>Tune character feel in real time:</strong> Adjust jump heights, animation lengths, sound effects, damage numbers.</p><p><strong>Design levels live:</strong> Move platforms, resize enemies or hitboxes, adjust spawns.</p><p><strong>Iterate on juice:</strong> Modify screen shake, particle effects, camera movement - <em>until it feels great</em>.</p><p>I don&#8217;t know about you, but I&#8217;ve found myself getting distracted if I have to wait <em>more than a couple of seconds</em>. That means that every compile-restart cycle is a potential distraction point in the day.</p><p>Plus, without the cost of restarting each time, you can have the confidence to tweak anything and everything. No more hesitation and doing math in your head to calculate if you&#8217;ve changed enough lines to justify a restart.</p><p>You unlock <em>designer mode</em>. As a programmer, I find it very appealing to be able to expand into that other mode without friction and go from Change -&gt; Compile -&gt; Test to Observe -&gt; Adjust -&gt; Observe.</p><div><hr></div><h2>Key Insights</h2><ol><li><p><strong>Less Friction Means More Focus</strong> - Focus is about removing distractions. Every time you wait for a compile, your brain will start to look for other things to do - even if it&#8217;s just a couple of seconds.</p></li><li><p><strong>Dynamic Libraries let you reload code without restarting</strong> - Split into Cradle (persistent memory/platform) and Game (hot-reloadable logic).</p></li><li><p><strong>Enable Designer Mode</strong> - With hot reloading, you get to switch into designer mode and tweak -&gt; observe -&gt; tweak -&gt; observe. No break of flow getting back to the same state.</p></li></ol><div><hr></div><p><strong>P.S.</strong> Want structured guidance on building games from first principles? <a href="https://programvideogames.com/?src=newsletter_0020">Program Video Games</a> gives you the full system - Odin + Raylib, no engines, complete control.</p><p>Code: <a href="https://gist.github.com/Falconerd/35e7fa827dc572d18ef20bef527c5a6a">GitHub Gist (click here)</a></p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://www.bytesbeneath.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading Bytes Beneath! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div>]]></content:encoded></item><item><title><![CDATA[25 Lines of Code that Glue Systems Together (Events)]]></title><description><![CDATA[Used in games like Skyrim and Fallout. Drop it in your engine this afternoon.]]></description><link>https://www.bytesbeneath.com/p/25-lines-of-code-that-glue-systems</link><guid isPermaLink="false">https://www.bytesbeneath.com/p/25-lines-of-code-that-glue-systems</guid><dc:creator><![CDATA[Dylan Falconer]]></dc:creator><pubDate>Mon, 08 Sep 2025 00:18:55 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!G6W0!,w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F410b2934-9526-41e2-b517-4180b6d92282_500x500.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Hard-coding everything works, until it doesn't. Friction increases, hair gets pulled out, projects get "refactored" - another game abandoned.</p><p>That's where event systems shine. Keep your systems separate, and keep communication open.</p><p>What you'll learn:</p><ul><li><p>Events decouple systems</p></li><li><p>Every major game uses them</p></li><li><p>How to build one in 25 lines of code</p></li></ul><h2>Events Decouple Systems</h2><p>An event system is a mail service between systems. One system sends a message, others receive it and respond. The response is a procedure - a callback - that consumes the event state and produces side-effects. Without this mail service, your code risks becoming 99% spaghetti, making you want to quit programming forever.</p><p>Imagine you're playing a game, running around, picking up items, and you pick up a gemstone. You receive a notification: Quest Started - The Missing Link (1/20 collected). Without events, you'd have either every item checked on pick-up if it should start a quest. Every sword, shield and bow dropped by bandits would have a bunch of code to check whether they are quest starters. With events, you have the item system announce "Special Gem Collected" and the quest system responds - no coupling required. The quest system doesn't even have to know the item system exists.</p><pre><code><code>// Without events
on_item_pickup :: proc(item: Item) {
&#9;if item.type == .Special_Gem {
&#9;&#9;if !quest_is_active(.Collect_Special_Gems) {
&#9;&#9;&#9;quest_start(.Collect_Special_Gems)
&#9;&#9;}
&#9;&#9;
&#9;&#9;quest_progress_increment(.Collect_Special_Gems)
&#9;}
}

// With events
on_item_pickup :: proc(item: Item) {
&#9;event_publish(.Item_Collected, ItemCollectedPayload{item})
}</code></code></pre><p>At first glance, the example without events looks better. It's explicit about what's going on. The version with events is abstract. The trade-off is that now <em>any system</em> can listen for the <code>Item_Collected</code> event and respond. The UI system can show a little notification, the quest system can update/start a quest, the sound system can play the "pick up item" sound, etc.</p><p>This is why major studios rely on event systems - they scale with your systems. Let's look at how Fallout used event systems.</p><h2>Major Games Use Them</h2><blockquote><p>I've been using these [event systems] for decades in my games... designers like it, programmers like it, my guess is artists like it too</p></blockquote><ul><li><p>Timothy Cain, Lead Designer of Fallout<a class="footnote-anchor" data-component-name="FootnoteAnchorToDOM" id="footnote-anchor-1" href="#footnote-1" target="_self">1</a></p></li></ul><p>Timothy Cain has a video about event systems on his YouTube channel. He describes how in Fallout, when a player picks a lock, the Skill System publishes an event and the AI System checks if nearby guards "saw" anything. The AI System and Skill System communicate without being coupled - they don't know about each other.</p><p>Another example covered is that a follower may get angry when the player kills too many "cystypigs" (a gross looking pig in the world of Fallout). This demonstrates the Companion System and Combat System working together without being coupled.</p><h2>Simple Implementation</h2><p>A simple event system may have types setup something like this:</p><pre><code><code>EventType :: enum {
&#9;Invalid,
&#9;TextUpdate,
&#9;PositionChange,
}

EventPayloadTextUpdate :: struct {
&#9;text: string,
&#9;// some relevant data..
}

EventPayloadPositionChange :: struct {
&#9;pos: [2]f32,
}

EventPayload :: union {
&#9;EventPayloadTextUpdate,
&#9;EventPayloadPositionChange,
}

Event :: struct {
&#9;type: EventType,
&#9;payload: EventPayload,
}</code></code></pre><p>To put the types into context, you'll want some variables - stored here as globals to make things simple:</p><pre><code><code>// Event callback type
EventCallbackProc :: proc(event: Event)

event_listeners: map[EventType][dynamic]EventCallbackProc
event_queue: queue.Queue(Event)</code></code></pre><p>To push an event onto the queue, you'll want to create a wrapper so you don't pollute your call-sites with implementation details:</p><pre><code><code>event_publish :: proc(type: EventType, payload: EventPayload) {
&#9;queue.enqueue(&amp;event_queue, Event{
&#9;&#9;type = type,
&#9;&#9;payload = payload,
&#9;})
}</code></code></pre><p>To subscribe to an event, you can link a callback procedure with an event type:</p><pre><code><code>event_type_subscribe :: proc(type: EventType, callback: EventCallbackProc) {
&#9;if type not_in event_listeners {
&#9;&#9;event_listeners[type] = make([dynamic]EventCallbackProc)
&#9;}

&#9;append(&amp;event_listeners[type], callback)
}
</code></code></pre><p>In your main loop, you can add a call to something like this:</p><pre><code><code>process_events :: proc() {
&#9;for queue.len(event_queue) &gt; 0 {
&#9;&#9;event := queue.dequeue(&amp;event_queue)

&#9;&#9;if listeners, ok := event_listeners[event.type]; ok {
&#9;&#9;&#9;for callback in listeners {
&#9;&#9;&#9;&#9;callback(event)
&#9;&#9;&#9;}
&#9;&#9;}
&#9;}
}</code></code></pre><p>This is a <em>very simple</em> event system that takes events off the queue every update and has no conditions to determine whether the event is "ready".</p><p>I've included an example with timed events in the code<a class="footnote-anchor" data-component-name="FootnoteAnchorToDOM" id="footnote-anchor-2" href="#footnote-2" target="_self">2</a>. There are other types of conditions that may be useful, such as:</p><ul><li><p><strong>Priority Events:</strong> Events have a priority score, higher priority are always checked first, low priority can be checked every so often</p></li><li><p><strong>Conditional Events:</strong> Attach a callback to an event that returns a boolean. If the callback returns true - which could be a game state lookup - consume the event.</p></li><li><p><strong>Filters:</strong> Location, Specific Entities, Game Modes - basically anything can be a filter.</p></li><li><p><strong>Persistent:</strong> Events that need to be saved and loaded when the game is saved and loaded.</p></li><li><p><strong>Sequences:</strong> Events that publish events when conditions are met.<br>... and much more!</p></li></ul><h2>Key Takeaways</h2><ul><li><p>Use events to keep systems decoupled, where performance is not the priority</p></li><li><p>Events are used in major games, and written the same way or similar, for decades</p></li><li><p>Events can be extended for increased capabilities</p></li></ul><div><hr></div><p>Enjoyed this? I made <a href="https://programvideogames.com/?src=newsletter_">Program Video Games</a> for you.<br>Game Ideas -&gt; Working Systems using first principles.</p><div class="footnote" data-component-name="FootnoteToDOM"><a id="footnote-1" href="#footnote-anchor-1" class="footnote-number" contenteditable="false" target="_self">1</a><div class="footnote-content"><p><a href="https://www.youtube.com/watch?v=P935C85WIpU">Event Systems - Timothy Cain (YouTube)</a></p></div></div><div class="footnote" data-component-name="FootnoteToDOM"><a id="footnote-2" href="#footnote-anchor-2" class="footnote-number" contenteditable="false" target="_self">2</a><div class="footnote-content"><p><a href="https://github.com/Falconerd/newsletter_code/tree/master/0019_event_system">https://github.com/Falconerd/newsletter_code/tree/master/0019_event_system</a></p></div></div>]]></content:encoded></item><item><title><![CDATA[Your Game's Input System is Holding You Back]]></title><description><![CDATA[Hard coded keybindings. Frame-dependent input bugs. Players complaining they can't rebind controls.]]></description><link>https://www.bytesbeneath.com/p/your-games-input-system-is-holding</link><guid isPermaLink="false">https://www.bytesbeneath.com/p/your-games-input-system-is-holding</guid><dc:creator><![CDATA[Dylan Falconer]]></dc:creator><pubDate>Mon, 25 Aug 2025 04:49:29 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/ca76e1e2-2dd2-479e-b6c2-d4335e886dce_1024x1024.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Sound familiar?</p><p>Most developers treat input as an afterthought - just slap some <code>IsKeyPressed(&lt;hardcoded_key&gt;)</code> calls and call it a day. Input is <em>the interface for player interaction</em>. Get it wrong and it can ruin a game.</p><p><strong>Spend less than 15 minutes and less than 100 lines of code to write a proper input system that your players will thank you for</strong></p><p>After reading this article, you'll never have the excuse that it's too hard to implement key rebinding.</p><p>What you'll learn:</p><ul><li><p>How to abstract input states from raw keyboard events</p></li><li><p>Why configuration files beat hard-coded bindings every time</p></li><li><p>How to create a custom config file that auto-generates when invalid or missing</p></li></ul><h2>Input Abstraction</h2><p>Raw input is messy. A key can be up, pressed (this frame), held down or released (this frame). Most input bugs come from confusing these states.</p><blockquote><p>I use a Colemak keyboard layout with a split keyboard at home. I usually remap WASD to FRST as that's what's comfortable for my keyboard.</p><p>When I first played Cyberpunk 2077, the in-game menu did not allow rebinding the "pick up" key - it was stuck to T, which is the same as "Strafe Right" with my layout.</p><p>Many times during combat, I'd be fighting and strafing, my crosshair would go onto a corpse while I'm strafing right, and my character would do the pick-up-this-guy animation, leaving me totally vulnerable in the middle of combat.</p><p>It was infuriating. and made me quit playing the game for a while until I bothered to learn how to edit the config file manually. You don't want your players to experience that kind of thing - many of them won't come back.</p></blockquote><p>Here's the input states for keys/buttons:</p><pre><code><code>InputState :: enum {
&#9;Up,
&#9;Pressed,
&#9;Down,
&#9;Released,
}</code></code></pre><p>Something I've found useful is to map actions to key bindings. Just like the player rebinds "pick up" to T, we are creating games with certain actions.</p><p>For our example game, we'll have four actions:</p><pre><code><code>GameBinding :: enum {
&#9;Jump,
&#9;Sprint,
&#9;Left,
&#9;Right,
}</code></code></pre><p>Now we can easily separate our actions from keys by using a map (or an enum array):</p><pre><code><code>// Assuming we use raylib, this could be win32's VK_KEY type, or SDL3, or GLFW, or whatever
input_map: [GameBinding]rl.KeyboardKey
input_state: [GameBinding]InputState</code></code></pre><p>We have two mappings. One for the binding, and one for the current state.</p><p>We'll create a procedure to update the state every frame:</p><pre><code><code>input_update :: proc() {
&#9;for key, binding in input_map {
&#9;&#9;if rl.IsKeyPressed(key) {
&#9;&#9;&#9;input_state[binding] = .Pressed
&#9;&#9;} else if rl.IsKeyDown(key) {
&#9;&#9;&#9;input_state[binding] = .Down
&#9;&#9;} else if rl.IsKeyReleased(key) {
&#9;&#9;&#9;input_state[binding] = .Released
&#9; &#9;} else {
&#9;&#9;&#9;input_state[binding] = .Up
&#9; &#9;}
&#9;}
}</code></code></pre><p>Raylib makes this easy because it has the same states: Pressed, Down, Up, Released.</p><p>Something like SDL3 would be a bit different:</p><pre><code><code>event_update_sdl3 :: proc() {
&#9;event: sdl.Event
&#9;for sdl.PollEvent(&amp;event) {
&#9;&#9;#partial switch event.type {
&#9;&#9;case .KEY_DOWN:
&#9;&#9;&#9;// Check if key was previously up, etc
&#9;&#9;case .KEY_UP:
&#9;&#9;&#9;// ...
&#9;&#9;// ... other event types ...
&#9;&#9;}
&#9;}
}</code></code></pre><h2>Config Files</h2><p>Config files exist because we need a way to store the settings between launches of the game. Imagine if every time the player restarted the game they had to rebind the keys!</p><p>Let's say we want a config.ini file like this:</p><pre><code><code>[bindings]
Jump = SPACE
Sprint = LEFT_SHIFT
Left = A
Right = D</code></code></pre><p>We can create a simple default string that serves as our config file if none is present:</p><pre><code><code>config_default_fmt :: `
[bindings]
Jump = %s
Sprint = %s
Left = %s
Right = %s
`

// later

config_default := fmt.tprintf(
&#9;config_default_fmt[1:],
&#9;string_from_key(.SPACE),
&#9;string_from_key(.LEFT_SHIFT),
&#9;string_from_key(.A),
&#9;string_from_key(.D),
)</code></code></pre><p>The key values for different platforms and frameworks will probably be different. So, you may need to create a couple of conversion procedures, such as these:</p><pre><code><code>string_from_key :: proc(key: rl.KeyboardKey, scratch := context.temp_allocator) -&gt; string {
&#9;if key &gt;= .A &amp;&amp; key &lt;= .Z {
&#9;&#9;return fmt.aprintf("%c", u8(key), allocator = scratch)
&#9;}

&#9;#partial switch key {
&#9;case .SPACE: return "SPACE"
&#9;case .LEFT_SHIFT: return "LEFT_SHIFT"
&#9;// ...
&#9;}

&#9;return "NULL"
}

key_from_string :: proc(s: string) -&gt; rl.KeyboardKey {
&#9;if len(s) == 1 {
&#9;&#9;return rl.KeyboardKey(u8(s[0]))
&#9;}

&#9;switch s {
&#9;case "SPACE": return .SPACE
&#9;case "LEFT_SHIFT": return .LEFT_SHIFT
&#9;// ...
&#9;}

&#9;return .KEY_NULL
}</code></code></pre><h2>Creating and Loading the Config File</h2><p>For this example game, we'll use a simple <code>config.ini</code> file, which Odin conveniently has a parser for in <code>core:encoding/ini</code>.</p><pre><code><code>config_load_and_apply :: proc(input_map: ^[GameBinding]rl.KeyboardKey) {
    is_config_valid := false
    m: ini.Map
    
    // Load existing config file
    if os.exists(CONFIG_PATH) {
        ini_map, err, ok := ini.load_map_from_path(CONFIG_PATH, context.temp_allocator)
        if err == .None &amp;&amp; ok {
            is_config_valid = true
            m = ini_map
        }
    }

    // Rewrite default config file if invalid
    if !is_config_valid {
        // Generate defaults and save...
    }

    // Apply the config
    input_map[.Jump] = key_from_string(m["bindings"]["Jump"])
    // etc...
}</code></code></pre><p>Generating the default config may look something like this:</p><pre><code><code>config_default := fmt.tprintf(
&#9;config_default_fmt[1:], // remove the newline at the top
&#9;string_from_key(.SPACE),
&#9;string_from_key(.LEFT_SHIFT),
&#9;string_from_key(.A),
&#9;string_from_key(.D),
)
os.write_entire_file(CONFIG_PATH, transmute([]u8)config_default)

ini_map, err := ini.load_map_from_string(config_default, context.temp_allocator)
if err == .None {
&#9;m = ini_map
}</code></code></pre><p>The generated config.ini:</p><pre><code><code>[bindings]
Jump = SPACE
Sprint = LEFT_SHIFT
Left = A
Right = D</code></code></pre><p>You would need to validate the config file based on your specific game's needs. For example, if some bindings are missing, that may be an invalid file and a new one will be generated.</p><h2>The Usage</h2><p>In your gameplay code, you can now refer to the actions your game cares about, rather than arbitrary keys:</p><pre><code><code>if input_state[.Jump] == .Pressed &amp;&amp; player.is_grounded {
&#9;// Jump
}</code></code></pre><h2>Key Takeaways</h2><p>This system is simple, easy to implement, and configurable by the player.</p><p>Even if you don't have the budget to create a menu, players can change the bindings in the config file directly.</p><p>The entire thing comes to less than 100 lines of code.</p><p>It goes without saying that accessibility is another major consideration when designing input systems.</p><p>By mapping actions &lt;-&gt; keys, your gameplay code stays clear of nasty bugs due to hard-coded key bindings, and your players get to play how they want.</p><p><a href="https://github.com/Falconerd/newsletter_code/tree/master/0018_easy_key_rebinding_for_games">Full source code</a></p><div><hr></div><p>If you enjoy this newsletter and want to go deeper, with more structure, I made <a href="https://programvideogames.com/?src=newsletter_">Program Video Games</a> for you. Transform your game ideas into working systems from first principles.</p>]]></content:encoded></item><item><title><![CDATA[Programming Languages as Religion]]></title><description><![CDATA[The programming language wars are a distraction from what actually matters in programming, and in life - getting better.]]></description><link>https://www.bytesbeneath.com/p/programming-languages-as-religion</link><guid isPermaLink="false">https://www.bytesbeneath.com/p/programming-languages-as-religion</guid><dc:creator><![CDATA[Dylan Falconer]]></dc:creator><pubDate>Fri, 16 May 2025 01:04:21 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!PDZU!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1954d564-4c72-48d0-8485-84d062101fe2_1024x1024.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!PDZU!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1954d564-4c72-48d0-8485-84d062101fe2_1024x1024.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!PDZU!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1954d564-4c72-48d0-8485-84d062101fe2_1024x1024.png 424w, https://substackcdn.com/image/fetch/$s_!PDZU!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1954d564-4c72-48d0-8485-84d062101fe2_1024x1024.png 848w, https://substackcdn.com/image/fetch/$s_!PDZU!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1954d564-4c72-48d0-8485-84d062101fe2_1024x1024.png 1272w, https://substackcdn.com/image/fetch/$s_!PDZU!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1954d564-4c72-48d0-8485-84d062101fe2_1024x1024.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!PDZU!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1954d564-4c72-48d0-8485-84d062101fe2_1024x1024.png" width="1024" height="1024" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/1954d564-4c72-48d0-8485-84d062101fe2_1024x1024.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1024,&quot;width&quot;:1024,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" title="" srcset="https://substackcdn.com/image/fetch/$s_!PDZU!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1954d564-4c72-48d0-8485-84d062101fe2_1024x1024.png 424w, https://substackcdn.com/image/fetch/$s_!PDZU!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1954d564-4c72-48d0-8485-84d062101fe2_1024x1024.png 848w, https://substackcdn.com/image/fetch/$s_!PDZU!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1954d564-4c72-48d0-8485-84d062101fe2_1024x1024.png 1272w, https://substackcdn.com/image/fetch/$s_!PDZU!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1954d564-4c72-48d0-8485-84d062101fe2_1024x1024.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Have you ever worked on a project where it was clear the user experience was being sacrificed to serve the developers? Where technology choices were made not based on performance requirements, but based on language X being the cool new thing?</p><p>People think the language choice matters more than it does. The choice only matters so far as you can finish the project and deliver a good user experience. Bonus points if you can skill-up, and though this is ultimately the most important thing, prioritising it can be unwise.</p><p><strong>Your technical choices reveal more about your priorities than your skills.</strong></p><p>What's discussed below:</p><ul><li><p>How ego influences our technical decisions</p></li><li><p>The impact of our tool choices on users</p></li><li><p>Aligning technology choices with long-term growth</p></li></ul><h2>The Ego Check</h2><p>Ask yourself: "If nobody would ever see my code or know what tools I used, would I still make the same choice?"</p><p>I do all my marketing on social media. It's probably how you found this newsletter.</p><p>I've had to learn what works and what doesn't. One thing that works is being divisive - picking a side, and importantly, picking an enemy.</p><blockquote><p>"C++ sucks, use C instead"</p><p>"Rust is superior, only brainlets use C"</p></blockquote><p>It's just like picking a sports team. People start to identify with their language/toolchain choice, and get defensive when it's criticised.</p><p>People get positive social signals by cheering on their favoured programming language in the right circles.</p><p>And then, when it comes time to make a decision about a project, they have chosen their team and are obligated to stick with it.</p><p>In reality, the best thing to do is remove ego from the equation, as difficult as that can be sometimes.</p><p>I look around and see people having arguments about languages. It reminds me of this tweet:</p><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!N-ly!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F65523c43-2666-4f92-ac99-134dae218317_585x128.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!N-ly!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F65523c43-2666-4f92-ac99-134dae218317_585x128.png 424w, https://substackcdn.com/image/fetch/$s_!N-ly!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F65523c43-2666-4f92-ac99-134dae218317_585x128.png 848w, https://substackcdn.com/image/fetch/$s_!N-ly!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F65523c43-2666-4f92-ac99-134dae218317_585x128.png 1272w, https://substackcdn.com/image/fetch/$s_!N-ly!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F65523c43-2666-4f92-ac99-134dae218317_585x128.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!N-ly!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F65523c43-2666-4f92-ac99-134dae218317_585x128.png" width="585" height="128" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/65523c43-2666-4f92-ac99-134dae218317_585x128.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:128,&quot;width&quot;:585,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:21205,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://www.bytesbeneath.com/i/163675250?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F65523c43-2666-4f92-ac99-134dae218317_585x128.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!N-ly!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F65523c43-2666-4f92-ac99-134dae218317_585x128.png 424w, https://substackcdn.com/image/fetch/$s_!N-ly!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F65523c43-2666-4f92-ac99-134dae218317_585x128.png 848w, https://substackcdn.com/image/fetch/$s_!N-ly!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F65523c43-2666-4f92-ac99-134dae218317_585x128.png 1272w, https://substackcdn.com/image/fetch/$s_!N-ly!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F65523c43-2666-4f92-ac99-134dae218317_585x128.png 1456w" sizes="100vw" loading="lazy"></picture><div></div></div></a></figure></div><p>Tsoding is right on the money. I know because I was guilty of this exact thing - learning languages rather than fundamentals, thinking the language was the problem. In reality, every language I was looking at had the performance characteristics I needed - I didn&#8217;t need to change.</p><p>In 2018, I started writing a game engine in JavaScript because I thought the language was "beautiful". I fantasised about having some game engine in JS and all the JS people being impressed. I knew the performance was bad by default, but didn't really consider the implications.</p><p><em>I know game engines and games have been written in JS. The point is, the decision was ego-driven, not based in rationality.</em></p><p>I integrated pixiJS (a rendering engine), built an ECS entity system, and was getting horrible performance. I didn't, and couldn't understand why - because I didn't know programming.</p><p>My mindset today is very different. For example, recently when looking at building a digital signal processing application with a friend, I considered first the performance, second the user experience.</p><p>I decided on C++, as that's what he uses and it works fine. But, also there are important libraries: miniaudio, and imnodes. An audio processing library, and a graphical node plugin for dear imgui.two</p><p>If I let my ego make the decision... It would have been Odin, or C. It would have been no libraries, no standard library. I doubt it would have gone anywhere.</p><h2>User Impact</h2><p>That brings me to the next question: "Am I prioritising development convenience at the expense of my users' time and resources?"</p><p>Have you looked at your target market and what their median specs are? Do you know how many cores their computers have, at what speeds, and how much RAM they have?</p><p>if you are a PC game developer, have you looked at the latest Steam Hardware Survey?<a class="footnote-anchor" data-component-name="FootnoteAnchorToDOM" id="footnote-anchor-1" href="#footnote-1" target="_self">1</a></p><p>Since this is not social media, I can allow myself a bit of nuance and say that <em>there are always trade-offs</em>. You may not be able to do the best thing for performance (user experience) because it'll cost you too much time (project runway) and so you have to choose between having no product, or one that eats the battery of people's laptops.</p><p><em>You can patch it later... If it's up to you.</em></p><p>Here's an example from today. I tried the new note-taking software called Kortex, and it uses 1.4 GB of memory without me doing anything.</p><p>I'm not willing to use it right now, and that's a direct result of their choice to write bloated software. When the global average PC has 12 GB of RAM, and the OS takes up so much of that nowadays, I can't be the only potential customer that is pushed away by this.</p><h2>The Decade Test</h2><p>Finally, and most importantly, ask this: "Will mastering this approach teach me principles that will remain valuable for decades to come?"</p><p>My journey into systems programming languages started in 2019. The improvement in my skill as a programmer is astronomical. I put this down to being forced to learn fundamentals - especially about memory.</p><p>The difference is so stark that I wonder how I ever had a job in web development in the first place. The answer, I think, is that I was tech savvy and knew how to read documentation.</p><p>Rather than focusing entirely on frameworks, which are constantly changing, I found value in implementing things from scratch. Things I can tailor specifically to the project.</p><p>Now, when utilising libraries in my code, I can understand the building blocks and make better decisions, not just look at the API.</p><p>The biggest unlock for me came by learning a bit about memory allocation and how CPUs work. I can make reasonable decisions about how to approach certain problems with just a few simple techniques<a class="footnote-anchor" data-component-name="FootnoteAnchorToDOM" id="footnote-anchor-2" href="#footnote-2" target="_self">2</a>.</p><h2>Key Takeaways</h2><ul><li><p>Rather than choosing tools based on identity, instead ask yourself what's best for the project.</p></li><li><p>Every tool choice is a trade-off for you <em>and your users</em> - they are the ones you are building for, so don't forget them.</p></li><li><p>The most valuable long-term investment is principles/foundational skills. Invest in yourself to compound long term gains.</p></li></ul><div><hr></div><p>If you enjoy this newsletter and want to go deeper, with more structure, I made <a href="https://programvideogames.com/?src=newsletter_">Program Video Games</a> for you. Transform your game ideas into working systems from first principles.</p><div class="footnote" data-component-name="FootnoteToDOM"><a id="footnote-1" href="#footnote-anchor-1" class="footnote-number" contenteditable="false" target="_self">1</a><div class="footnote-content"><p><a href="https://store.steampowered.com/hwsurvey">https://store.steampowered.com/hwsurvey</a></p></div></div><div class="footnote" data-component-name="FootnoteToDOM"><a id="footnote-2" href="#footnote-anchor-2" class="footnote-number" contenteditable="false" target="_self">2</a><div class="footnote-content"><div class="embedded-post-wrap" data-attrs="{&quot;id&quot;:163173895,&quot;url&quot;:&quot;https://www.bytesbeneath.com/p/software-is-the-platform-programming&quot;,&quot;publication_id&quot;:1017732,&quot;publication_name&quot;:&quot;The Bytes Beneath&quot;,&quot;publication_logo_url&quot;:&quot;https://substackcdn.com/image/fetch/f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F40c617f2-aeef-4100-a745-4ba6dcdfaee5_500x500.png&quot;,&quot;title&quot;:&quot;Software is the Platform - Programming Lies&quot;,&quot;truncated_body_text&quot;:&quot;I feel the cursor move sluggishly as I type into VSCode on my high-end desktop computer. It drives me nuts. Not so much because of the delay, but because I know that software doesn't have to be like this.&quot;,&quot;date&quot;:&quot;2025-05-09T00:50:27.840Z&quot;,&quot;like_count&quot;:4,&quot;comment_count&quot;:2,&quot;bylines&quot;:[{&quot;id&quot;:10518950,&quot;name&quot;:&quot;Dylan Falconer&quot;,&quot;handle&quot;:&quot;falconerd&quot;,&quot;previous_name&quot;:&quot;Zvlkkvv&quot;,&quot;photo_url&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/6a39947f-6044-4b21-9df1-4a51442bcc52_1080x1080.jpeg&quot;,&quot;bio&quot;:&quot;I program games, game engines, tools, and make tutorial videos.&quot;,&quot;profile_set_up_at&quot;:&quot;2022-07-27T08:27:01.388Z&quot;,&quot;reader_installed_at&quot;:&quot;2023-05-10T10:56:16.831Z&quot;,&quot;publicationUsers&quot;:[{&quot;id&quot;:963676,&quot;user_id&quot;:10518950,&quot;publication_id&quot;:1017732,&quot;role&quot;:&quot;admin&quot;,&quot;public&quot;:true,&quot;is_primary&quot;:true,&quot;publication&quot;:{&quot;id&quot;:1017732,&quot;name&quot;:&quot;The Bytes Beneath&quot;,&quot;subdomain&quot;:&quot;falconerd&quot;,&quot;custom_domain&quot;:&quot;www.bytesbeneath.com&quot;,&quot;custom_domain_optional&quot;:false,&quot;hero_text&quot;:&quot;Weekly insights on building games from the ground up - no engines, just grit and code.&quot;,&quot;logo_url&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/40c617f2-aeef-4100-a745-4ba6dcdfaee5_500x500.png&quot;,&quot;author_id&quot;:10518950,&quot;primary_user_id&quot;:10518950,&quot;theme_var_background_pop&quot;:&quot;#0068EF&quot;,&quot;created_at&quot;:&quot;2022-07-27T08:28:09.517Z&quot;,&quot;email_from_name&quot;:&quot;Dylan Falconer&quot;,&quot;copyright&quot;:&quot;Dylan Falconer&quot;,&quot;founding_plan_name&quot;:&quot;Founding Member&quot;,&quot;community_enabled&quot;:true,&quot;invite_only&quot;:false,&quot;payments_state&quot;:&quot;paused&quot;,&quot;language&quot;:null,&quot;explicit&quot;:false,&quot;homepage_type&quot;:&quot;newspaper&quot;,&quot;is_personal_mode&quot;:false}}],&quot;is_guest&quot;:false,&quot;bestseller_tier&quot;:null}],&quot;utm_campaign&quot;:null,&quot;belowTheFold&quot;:true,&quot;type&quot;:&quot;newsletter&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="EmbeddedPostToDOM"><a class="embedded-post" native="true" href="https://www.bytesbeneath.com/p/software-is-the-platform-programming?utm_source=substack&amp;utm_campaign=post_embed&amp;utm_medium=web"><div class="embedded-post-header"><img class="embedded-post-publication-logo" src="https://substackcdn.com/image/fetch/$s_!YDTz!,w_56,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F40c617f2-aeef-4100-a745-4ba6dcdfaee5_500x500.png" loading="lazy"><span class="embedded-post-publication-name">The Bytes Beneath</span></div><div class="embedded-post-title-wrapper"><div class="embedded-post-title">Software is the Platform - Programming Lies</div></div><div class="embedded-post-body">I feel the cursor move sluggishly as I type into VSCode on my high-end desktop computer. It drives me nuts. Not so much because of the delay, but because I know that software doesn't have to be like this&#8230;</div><div class="embedded-post-cta-wrapper"><span class="embedded-post-cta">Read more</span></div><div class="embedded-post-meta">a year ago &#183; 4 likes &#183; 2 comments &#183; Dylan Falconer</div></a></div></div></div>]]></content:encoded></item><item><title><![CDATA[Software is the Platform - Programming Lies]]></title><description><![CDATA[Ignoring hardware realities makes you a digital vampire]]></description><link>https://www.bytesbeneath.com/p/software-is-the-platform-programming</link><guid isPermaLink="false">https://www.bytesbeneath.com/p/software-is-the-platform-programming</guid><dc:creator><![CDATA[Dylan Falconer]]></dc:creator><pubDate>Fri, 09 May 2025 00:50:27 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!HBDj!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2b7e4430-8d9b-439b-aae8-b2ea248fbdd4_1536x1024.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!HBDj!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2b7e4430-8d9b-439b-aae8-b2ea248fbdd4_1536x1024.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!HBDj!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2b7e4430-8d9b-439b-aae8-b2ea248fbdd4_1536x1024.png 424w, https://substackcdn.com/image/fetch/$s_!HBDj!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2b7e4430-8d9b-439b-aae8-b2ea248fbdd4_1536x1024.png 848w, https://substackcdn.com/image/fetch/$s_!HBDj!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2b7e4430-8d9b-439b-aae8-b2ea248fbdd4_1536x1024.png 1272w, https://substackcdn.com/image/fetch/$s_!HBDj!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2b7e4430-8d9b-439b-aae8-b2ea248fbdd4_1536x1024.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!HBDj!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2b7e4430-8d9b-439b-aae8-b2ea248fbdd4_1536x1024.png" width="1456" height="971" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/2b7e4430-8d9b-439b-aae8-b2ea248fbdd4_1536x1024.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:971,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:2057106,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://www.bytesbeneath.com/i/163173895?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2b7e4430-8d9b-439b-aae8-b2ea248fbdd4_1536x1024.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!HBDj!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2b7e4430-8d9b-439b-aae8-b2ea248fbdd4_1536x1024.png 424w, https://substackcdn.com/image/fetch/$s_!HBDj!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2b7e4430-8d9b-439b-aae8-b2ea248fbdd4_1536x1024.png 848w, https://substackcdn.com/image/fetch/$s_!HBDj!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2b7e4430-8d9b-439b-aae8-b2ea248fbdd4_1536x1024.png 1272w, https://substackcdn.com/image/fetch/$s_!HBDj!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2b7e4430-8d9b-439b-aae8-b2ea248fbdd4_1536x1024.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>I feel the cursor move sluggishly as I type into VSCode on my high-end desktop computer. It drives me nuts. Not so much because of the delay, but because I know that software doesn't have to be like this.</p><p>Microsoft can't even conceive that their applications should open instantly, putting the smallest option in a survey about expected load time as "less than 10 seconds"<a class="footnote-anchor" data-component-name="FootnoteAnchorToDOM" id="footnote-anchor-1" href="#footnote-1" target="_self">1</a>.</p><p>Applications that have an entire web browser under the hood plague our systems, eating up our RAM. This is a screenshot from my laptop as I type this.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!Yjhx!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F428cb30b-bf81-47b1-a5fd-c73f9ffb5873_666x593.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!Yjhx!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F428cb30b-bf81-47b1-a5fd-c73f9ffb5873_666x593.png 424w, https://substackcdn.com/image/fetch/$s_!Yjhx!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F428cb30b-bf81-47b1-a5fd-c73f9ffb5873_666x593.png 848w, https://substackcdn.com/image/fetch/$s_!Yjhx!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F428cb30b-bf81-47b1-a5fd-c73f9ffb5873_666x593.png 1272w, https://substackcdn.com/image/fetch/$s_!Yjhx!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F428cb30b-bf81-47b1-a5fd-c73f9ffb5873_666x593.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!Yjhx!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F428cb30b-bf81-47b1-a5fd-c73f9ffb5873_666x593.png" width="666" height="593" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/428cb30b-bf81-47b1-a5fd-c73f9ffb5873_666x593.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:593,&quot;width&quot;:666,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:38558,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://www.bytesbeneath.com/i/163173895?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F428cb30b-bf81-47b1-a5fd-c73f9ffb5873_666x593.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!Yjhx!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F428cb30b-bf81-47b1-a5fd-c73f9ffb5873_666x593.png 424w, https://substackcdn.com/image/fetch/$s_!Yjhx!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F428cb30b-bf81-47b1-a5fd-c73f9ffb5873_666x593.png 848w, https://substackcdn.com/image/fetch/$s_!Yjhx!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F428cb30b-bf81-47b1-a5fd-c73f9ffb5873_666x593.png 1272w, https://substackcdn.com/image/fetch/$s_!Yjhx!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F428cb30b-bf81-47b1-a5fd-c73f9ffb5873_666x593.png 1456w" sizes="100vw"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Windows uses an ungodly amount of memory these days, and these web applications on top are doing me no favours.</p><p>My laptop is intentionally low-end (Lenovo T480s). I write software to run fast on this thing and know it'll run fast on most devices.</p><p>For reference, the global average computer has 12 GB ram, and this has 8 GB.</p><p>Most people struggle with less than 16 GB these days, it's absurd.</p><p>I haven't even talked about games, as it's not the focus of this article, but the issue is huge in the game development space too. AAA developers tell their customers to "just buy better hardware".</p><p><strong>Being willfully ignorant about the hardware isn't cool. It's unprofessional.</strong></p><p>Disregarding the user's time is vampiric. You are trading your time for theirs, in a real sense.</p><p>By the end of this article, you'll have three key concepts to keep in mind that'll help you avoid being a digital vampire:</p><ul><li><p>Plain old data</p></li><li><p>Data locality</p></li><li><p>Access patterns</p></li></ul><h2>Plain Old Data</h2><p>Code is code. Data is data. Objects aren't real. It's all a psyop.</p><p>Treating instances of datum as if they are singular pieces with agency is something all too common. A <code>Message</code> class that has a <code>send(User to)</code> method is one such example.</p><p>Take these two ways to write that functionality:</p><pre><code><code>message.send(recipient)
// vs
message_send(message_data, recipient)</code></code></pre><p>The difference may seem trivial, but one frames your thinking in a deceptive way.</p><p>When you call <code>message.send</code>, the message isn't "sending itself". A function is being called, and a pointer to the message is being passed as the first argument implicitly.</p><p>The second example more accurately reflects the reality of programs: <strong>all programs are about transforming data</strong>.</p><p>When you consider programs as fractal data transformation tools, you come to sane conclusions about how to structure them.</p><p>For example, if we wanted to send 100 different messages to 100 recipients... Let's do it a way that would be common with the first version:</p><pre><code><code>messages = [100]MessageData{...}
recipients = [100]Recipient

for message, i in messages {
    message.send(recipients, recipients[i])
}</code></code></pre><p>Do you see what's wrong with this? Maybe the compiler will be smart enough to catch it in this contrived example, but compilers are not magic. They are notoriously capricious when it comes to good code generation.</p><p>The issue is that we are dispatching a function call for every message, setting up the stack for calling into the function, then sending a message, then tearing it down, repeat 100 times.</p><p>What about the other method?</p><pre><code><code>messages = [100]MessageData{...}
recipients = [100]Recipient

send_messages_batch(messages, recipients)</code></code></pre><p>Here we are passing entire arrays of data to be processed at once.</p><p>This function wasn't outlined in the contrived example description - but, here's the thing: It can't exist with the mind-state of the first style.</p><p>If a message is a singular object, then how can it have a method that sends it and 99 others to 100 recipients? It seems absurd due to the thought patterns of thinking about individuals vs collections.</p><p>Treating data as data and programs as data transformers unlocks performance by default, especially when taking the next two points into account.</p><h2>Data Locality</h2><p>Keeping data close together in memory allows the CPU to use it efficiently.</p><p>This is because all modern CPUs have caches. The CPU pulls in data it thinks your program is about to use and stores it in the cache. Then, when correct, it grabs it from the cache instead of RAM.</p><p>These are L1, L2, and sometimes L3. Each cache increases in size and thus increases in real-world distance from where the work is done.</p><p>The time required to get the data is much higher in the L3 cache, and significantly higher in RAM, than in L1.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!FgxG!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff141f511-e658-46de-95d7-1234191b4017_639x343.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!FgxG!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff141f511-e658-46de-95d7-1234191b4017_639x343.png 424w, https://substackcdn.com/image/fetch/$s_!FgxG!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff141f511-e658-46de-95d7-1234191b4017_639x343.png 848w, https://substackcdn.com/image/fetch/$s_!FgxG!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff141f511-e658-46de-95d7-1234191b4017_639x343.png 1272w, https://substackcdn.com/image/fetch/$s_!FgxG!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff141f511-e658-46de-95d7-1234191b4017_639x343.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!FgxG!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff141f511-e658-46de-95d7-1234191b4017_639x343.png" width="639" height="343" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/f141f511-e658-46de-95d7-1234191b4017_639x343.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:343,&quot;width&quot;:639,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:26783,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://www.bytesbeneath.com/i/163173895?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff141f511-e658-46de-95d7-1234191b4017_639x343.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!FgxG!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff141f511-e658-46de-95d7-1234191b4017_639x343.png 424w, https://substackcdn.com/image/fetch/$s_!FgxG!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff141f511-e658-46de-95d7-1234191b4017_639x343.png 848w, https://substackcdn.com/image/fetch/$s_!FgxG!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff141f511-e658-46de-95d7-1234191b4017_639x343.png 1272w, https://substackcdn.com/image/fetch/$s_!FgxG!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff141f511-e658-46de-95d7-1234191b4017_639x343.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p></p><p><em>Rough averages. Time based on 3.5-5 GHz CPU</em></p><p>There is a lot more to this picture. The thing to keep in mind is to try and fit the data for your transformation in the L1 or L2 where possible, and try to go out to RAM rarely.</p><p>Another thing to note, is the L1 and L2 are <em>per core</em> caches. That means you can <em>multiply the cache size you have access to</em> by learning how to use threads effectively.</p><h2>Access Patterns</h2><p>Finally, there are access patterns. This goes hand in hand with data locality.</p><p>The CPU will try to predict what data your program needs and prefetch it into the caches. If you choose an algorithm that jumps around a lot in memory, the CPU may prefetch the wrong data. This is called a cache miss, and it's a performance killer.</p><p>Since going to memory is so slow, if every iteration of an algorithm is causing a cache miss, the algorithm may take 100x longer than it could have.</p><p>Let's look at a classic example, and how to fix it without changing data structures. The Linked List.</p><pre><code><code>LinkedListNode {
    LinkedListNode *next
    int data
}

head = new LinkedListNode
head.data = 32

node1 = new LinkedListNode
node1.data = 42

linked_list_append(head, node1)

// some time later in the program

node2 = new LinkedListNode
node2.data = 42

linked_list_append(head, node2)

// etc, repeat however many times</code></code></pre><p>We have this linked list with individually allocated nodes.</p><p>Each node has no guarantee about its location in memory due to using the default allocator (<code>new</code>).</p><p>This <em>could be</em> disastrous for performance as the memory may be scattered around and each time you walk the list, you may have cache misses on every <code>next</code> lookup.</p><p>The mistake people make in the Linked List vs Arrays argument is assuming this is the only way to make a linked list.</p><p>Let's look at another way to make a linked list, but keep the memory close together.</p><pre><code><code>LinkedListNode {
    LinkedListNode *next
    int data
}

MAX_REQUIRED_NODES = 100

nodes = new [MAX_REQUIRED_NODES]LinkedListNode
nextNode = 0

alloc_node() -&gt; LinkedListNode * {
    node = &amp;nodes[nextNode]
    nextNode += 1
    return node
}

head = alloc_node()
head.data = 32

node1 = alloc_node()
node1.data = 42

linked_list_append(head, node1)

// some time later in the program

node2 = alloc_node()
node2.data = 42

linked_list_append(head, node2)

// etc, repeat however many times</code></code></pre><p>A few extra lines, some thinking about limits, and we have a linked list that will perform nearly as well as an array.</p><p>There are a bunch of different ways you could achieve something similar, here are two methods to use without having to define a maximum:</p><ul><li><p>Use indices instead of next pointers, and a backing array</p></li><li><p>Use a growable arena allocator</p></li></ul><p>By thinking about how data is stored and accessed, you unlock hundreds of times faster programs without much, or in some cases zero extra code.</p><h2>Conclusion</h2><p>Software runs on hardware. Hardware is the platform. Hardware has physical constraints and considerations. Abstracting away the hardware is not possible.</p><p>When writing code that needs to run fast, consider these simple questions:</p><ul><li><p>Am I treating data as data, or pretending objects have agency?</p></li><li><p>Is related data stored close together in memory?</p></li><li><p>Are my access patterns using close-together data?</p></li></ul><p>These questions force you to think about the reality of computers. You'll write code that performs better without huge refactoring efforts.</p><p>Understanding these basics isn't just for systems programmers or game developers. Every professional programmer has a responsibility to have at least a shallow understanding of these concepts.</p><p>Don't be the programmer who drains users' lifeblood one sluggish interaction at a time, when you could <em>easily</em> write code that respects it.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://www.bytesbeneath.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading. To receive more programming articles like this directly in your inbox, subscribe below.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><div class="footnote" data-component-name="FootnoteToDOM"><a id="footnote-1" href="#footnote-anchor-1" class="footnote-number" contenteditable="false" target="_self">1</a><div class="footnote-content"><p><a href="https://www.youtube.com/watch?v=3r_TuAb1b-0">Handmade Hero | Visual Studio Survey</a></p><div><hr></div><p>If you enjoy this newsletter and want to go deeper, with more structure, I made <a href="https://programvideogames.com/?src=newsletter_">Program Video Games</a> for you. Transform your game ideas into working systems from first principles.</p></div></div>]]></content:encoded></item><item><title><![CDATA[Why Games Love State Machines]]></title><description><![CDATA[Less bugs. Clear code paths.]]></description><link>https://www.bytesbeneath.com/p/why-games-love-state-machines</link><guid isPermaLink="false">https://www.bytesbeneath.com/p/why-games-love-state-machines</guid><dc:creator><![CDATA[Dylan Falconer]]></dc:creator><pubDate>Wed, 05 Mar 2025 04:54:44 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2c5879fe-7dd5-4431-8bb9-0024c96062ea_1013x621.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>"I start programming my Character Controller, thinking 'this time, it's gonna be neat, simple, and easy to extend'... and then it turns into spaghetti code"</p><p>Sound familiar?</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://www.bytesbeneath.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Join 900+ Indie Devs.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><p>Let's outline an example of how this may go down. To make the example simpler, we'll only focus on <code>is_jumping</code>, <code>is_dashing</code> and <code>is_falling</code>, though this problem gets worse the more boolean flags we add.</p><pre><code><code>Player_State :: struct {
  is_running: bool,
  is_jumping: bool,
  is_falling: bool,
  is_dashing: bool,
  is_grounded: bool,

  pos: Vec2,
  vel: Vec2,
}

player_update :: proc(ps: ^Player_State) {
  // Jump
  if input.jump &amp;&amp; !ps.is_jumping &amp;&amp; ps.is_grounded {
    ps.is_jumping = true
    ps.is_grounded = false
    ps.vel.y = JUMP_VEL
  }

  if input.dash &amp;&amp; !ps.is_dashing {
    ps.is_jumping = false
    ps.is_dashing = true
  }

  // Dashing keeps current vertical position
  if ps.is_dashing {
    ps.vel.y = 0
  }

  // Fall (+Y is often 'down' in 2D games)
  if ps.is_falling {
    ps.vel.y += GRAVITY
    ps.vel.y = max(ps.vel.y, TERMINAL_VEL)
  }

  // ...
}
</code></code></pre><p>Do you see the bug? Even with this small amount of code, it may be difficult to spot!</p><p>The <code>is_falling</code> flag is not set to <code>false</code> when a dash is started. Therefore, if the player starts dashing on the way up their jump, everything seems fine. However, if they start just at the apex, or on the way down, the dash will have gravity applied.</p><p>This is a small example, but you can imagine how complex these controllers get in a full game. You may have dozens of booleans.</p><p>Another thing to point out is that bugs can occur not just because a boolean is set or unset, but also the order in which they are checked.</p><h2>Finite State Machines</h2><p>Enter finite state machines (FSMs). They provide a way to define a finite number of valid states for our character.</p><p>Not all the booleans fit into the FSM. For example, we can be <code>dashing</code> but not <code>jumping</code> (these fit, and are hence <em>orthogonal states</em>). However, we may be <code>grounded</code> and <code>running</code> at the same time.</p><pre><code><code>Player_Movement_State :: enum {
  Idle,
  Running,
  Jumping,
  Falling,
  Dashing,
}

Player_State :: struct {
  movement_state: Player_Movement_State,
  is_grounded: bool,

  pos: Vec2,
  vel: Vec2,
}

player_update :: proc(ps: ^Player_State) {
  switch ps.movement_state {
  case Idle:
    if input.left || input.right {
      ps.movement_state = .Running
    }
    if input.jump {
      ps.movement_state = .Jumping
      ps.is_grounded = false
    }
  case Running:
    if input.jump {
      ps.movement_state = .Jumping
      ps.is_grounded = false
    }
  case Jumping:
    ps.vel.y = JUMP_VEL
    // Somehow determine when jump finished (up to implementation)
    if jump_apex_reached {
      ps.movement_state = .Falling
    }
  case Falling:
    ps.vel.y += GRAVITY
    ps.vel.y = max(ps.vel.y, TERMINAL_VEL)
  case Dashing:
    // Somehow determine dash finished (up to implementation)
    if dash_finished {
      if ps.is_grounded {
        ps.movement_state = .Idle
      } else {
        ps.movement_state = .Falling
      }
    }
  }
}</code></code></pre><p>Now you can see exactly what code is running depending on <em>orthogonal states</em>. Some code is duplicated, and wherever necessary, you can easily pull that out:</p><pre><code><code>player_try_jump :: proc(ps: ^Player_State) {
  if input.jump {
    ps.movement_state = .Jumping
    ps.is_grounded = false
  }
}</code></code></pre><p>It's common to want to use state machines everywhere, but I'd caution against that. I would suggest using these in places where it's clear there are orthogonal states and you <em>definitely do not want overlapping behaviours</em>.</p><p>In some cases, you may want overlapping behaviours - I'll write about that in a future newsletter.</p><p>The trade-off of FSMs is inherent in it's structure: it creates N number of code paths for you to now consider, and perhaps even more.</p><p><code>player_update -&gt; [N]Player_Movement_State -&gt; [M]player_try_xxx</code>.</p><h2>More Examples</h2><p>You've already seen the character controller example, let's briefly touch on a couple more.</p><h3>Enemy AI</h3><p>Consider an enemy in your game. You may want it to patrol randomly, or following some waypoints. Then, when the player comes into a certain range, you want the enemy to start chasing the player. If the enemy gets within striking distance, it should throw out an attack. If the player gets lost somehow (invisibility, running too fast, etc) - then the enemy may walk back to it's post and continue patrolling.</p><pre><code><code>[Patrolling]
player detected --&gt; [Chasing]</code></code></pre><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!Sv9M!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F52db173f-3781-4d9b-8a1e-160a307cc2dc_1004x435.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!Sv9M!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F52db173f-3781-4d9b-8a1e-160a307cc2dc_1004x435.png 424w, https://substackcdn.com/image/fetch/$s_!Sv9M!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F52db173f-3781-4d9b-8a1e-160a307cc2dc_1004x435.png 848w, https://substackcdn.com/image/fetch/$s_!Sv9M!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F52db173f-3781-4d9b-8a1e-160a307cc2dc_1004x435.png 1272w, https://substackcdn.com/image/fetch/$s_!Sv9M!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F52db173f-3781-4d9b-8a1e-160a307cc2dc_1004x435.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!Sv9M!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F52db173f-3781-4d9b-8a1e-160a307cc2dc_1004x435.png" width="1004" height="435" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/52db173f-3781-4d9b-8a1e-160a307cc2dc_1004x435.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:435,&quot;width&quot;:1004,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:26336,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://www.bytesbeneath.com/i/158417926?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F52db173f-3781-4d9b-8a1e-160a307cc2dc_1004x435.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!Sv9M!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F52db173f-3781-4d9b-8a1e-160a307cc2dc_1004x435.png 424w, https://substackcdn.com/image/fetch/$s_!Sv9M!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F52db173f-3781-4d9b-8a1e-160a307cc2dc_1004x435.png 848w, https://substackcdn.com/image/fetch/$s_!Sv9M!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F52db173f-3781-4d9b-8a1e-160a307cc2dc_1004x435.png 1272w, https://substackcdn.com/image/fetch/$s_!Sv9M!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F52db173f-3781-4d9b-8a1e-160a307cc2dc_1004x435.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!yC8b!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa0e79656-6a85-4173-9512-5a0bc214f69e_1017x440.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!yC8b!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa0e79656-6a85-4173-9512-5a0bc214f69e_1017x440.png 424w, https://substackcdn.com/image/fetch/$s_!yC8b!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa0e79656-6a85-4173-9512-5a0bc214f69e_1017x440.png 848w, https://substackcdn.com/image/fetch/$s_!yC8b!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa0e79656-6a85-4173-9512-5a0bc214f69e_1017x440.png 1272w, https://substackcdn.com/image/fetch/$s_!yC8b!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa0e79656-6a85-4173-9512-5a0bc214f69e_1017x440.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!yC8b!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa0e79656-6a85-4173-9512-5a0bc214f69e_1017x440.png" width="1017" height="440" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/a0e79656-6a85-4173-9512-5a0bc214f69e_1017x440.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:440,&quot;width&quot;:1017,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:58295,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://www.bytesbeneath.com/i/158417926?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa0e79656-6a85-4173-9512-5a0bc214f69e_1017x440.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!yC8b!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa0e79656-6a85-4173-9512-5a0bc214f69e_1017x440.png 424w, https://substackcdn.com/image/fetch/$s_!yC8b!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa0e79656-6a85-4173-9512-5a0bc214f69e_1017x440.png 848w, https://substackcdn.com/image/fetch/$s_!yC8b!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa0e79656-6a85-4173-9512-5a0bc214f69e_1017x440.png 1272w, https://substackcdn.com/image/fetch/$s_!yC8b!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa0e79656-6a85-4173-9512-5a0bc214f69e_1017x440.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><pre><code><code>[Chasing]
player within range --&gt; [Attacking]
player lost --&gt; [Searching]</code></code></pre><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!PIUn!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F768f4e7e-3e98-4d64-a096-155ec07c426f_1007x658.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!PIUn!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F768f4e7e-3e98-4d64-a096-155ec07c426f_1007x658.png 424w, https://substackcdn.com/image/fetch/$s_!PIUn!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F768f4e7e-3e98-4d64-a096-155ec07c426f_1007x658.png 848w, https://substackcdn.com/image/fetch/$s_!PIUn!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F768f4e7e-3e98-4d64-a096-155ec07c426f_1007x658.png 1272w, https://substackcdn.com/image/fetch/$s_!PIUn!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F768f4e7e-3e98-4d64-a096-155ec07c426f_1007x658.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!PIUn!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F768f4e7e-3e98-4d64-a096-155ec07c426f_1007x658.png" width="1007" height="658" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/768f4e7e-3e98-4d64-a096-155ec07c426f_1007x658.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:658,&quot;width&quot;:1007,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:89702,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://www.bytesbeneath.com/i/158417926?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F768f4e7e-3e98-4d64-a096-155ec07c426f_1007x658.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!PIUn!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F768f4e7e-3e98-4d64-a096-155ec07c426f_1007x658.png 424w, https://substackcdn.com/image/fetch/$s_!PIUn!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F768f4e7e-3e98-4d64-a096-155ec07c426f_1007x658.png 848w, https://substackcdn.com/image/fetch/$s_!PIUn!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F768f4e7e-3e98-4d64-a096-155ec07c426f_1007x658.png 1272w, https://substackcdn.com/image/fetch/$s_!PIUn!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F768f4e7e-3e98-4d64-a096-155ec07c426f_1007x658.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><pre><code><code>[Searching]
search timer ended --&gt; [Returning]</code></code></pre><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!dtnp!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2c5879fe-7dd5-4431-8bb9-0024c96062ea_1013x621.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!dtnp!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2c5879fe-7dd5-4431-8bb9-0024c96062ea_1013x621.png 424w, https://substackcdn.com/image/fetch/$s_!dtnp!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2c5879fe-7dd5-4431-8bb9-0024c96062ea_1013x621.png 848w, https://substackcdn.com/image/fetch/$s_!dtnp!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2c5879fe-7dd5-4431-8bb9-0024c96062ea_1013x621.png 1272w, https://substackcdn.com/image/fetch/$s_!dtnp!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2c5879fe-7dd5-4431-8bb9-0024c96062ea_1013x621.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!dtnp!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2c5879fe-7dd5-4431-8bb9-0024c96062ea_1013x621.png" width="1013" height="621" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/2c5879fe-7dd5-4431-8bb9-0024c96062ea_1013x621.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:621,&quot;width&quot;:1013,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:55600,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://www.bytesbeneath.com/i/158417926?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2c5879fe-7dd5-4431-8bb9-0024c96062ea_1013x621.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!dtnp!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2c5879fe-7dd5-4431-8bb9-0024c96062ea_1013x621.png 424w, https://substackcdn.com/image/fetch/$s_!dtnp!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2c5879fe-7dd5-4431-8bb9-0024c96062ea_1013x621.png 848w, https://substackcdn.com/image/fetch/$s_!dtnp!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2c5879fe-7dd5-4431-8bb9-0024c96062ea_1013x621.png 1272w, https://substackcdn.com/image/fetch/$s_!dtnp!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2c5879fe-7dd5-4431-8bb9-0024c96062ea_1013x621.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><pre><code><code>[Returning]
returning finished --&gt; [Patrolling]</code></code></pre><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!6el2!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F873bcbcd-0007-4a1f-abe2-bf87701e4498_1006x383.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!6el2!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F873bcbcd-0007-4a1f-abe2-bf87701e4498_1006x383.png 424w, https://substackcdn.com/image/fetch/$s_!6el2!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F873bcbcd-0007-4a1f-abe2-bf87701e4498_1006x383.png 848w, https://substackcdn.com/image/fetch/$s_!6el2!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F873bcbcd-0007-4a1f-abe2-bf87701e4498_1006x383.png 1272w, https://substackcdn.com/image/fetch/$s_!6el2!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F873bcbcd-0007-4a1f-abe2-bf87701e4498_1006x383.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!6el2!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F873bcbcd-0007-4a1f-abe2-bf87701e4498_1006x383.png" width="1006" height="383" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/873bcbcd-0007-4a1f-abe2-bf87701e4498_1006x383.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:383,&quot;width&quot;:1006,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:19015,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://www.bytesbeneath.com/i/158417926?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F873bcbcd-0007-4a1f-abe2-bf87701e4498_1006x383.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!6el2!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F873bcbcd-0007-4a1f-abe2-bf87701e4498_1006x383.png 424w, https://substackcdn.com/image/fetch/$s_!6el2!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F873bcbcd-0007-4a1f-abe2-bf87701e4498_1006x383.png 848w, https://substackcdn.com/image/fetch/$s_!6el2!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F873bcbcd-0007-4a1f-abe2-bf87701e4498_1006x383.png 1272w, https://substackcdn.com/image/fetch/$s_!6el2!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F873bcbcd-0007-4a1f-abe2-bf87701e4498_1006x383.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><h3>UI Screens</h3><p>Usually, we don't want multiple menus open at the same time. For example, when the player is loading a saved game, they don't want to look at the settings menu.</p><pre><code><code>[Main Menu]
'continue' selected --&gt; [Load Game Menu]
'settings' selected --&gt; [Settings Menu]</code></code></pre><h2>Hierarchical State Machines</h2><p>State machines can be nested into a hierarchy in multiple ways.</p><p>One way is to use the standard enum approach I showed and have a particular set of states, making up their own orthogonal set, that are only accessible via one state.</p><p>However, that can be confusing as the amount of states that are unrelated pile up.</p><p>Another approach is to simple create a separate state machine.</p><p>Imagine an old-school JRPG game.</p><pre><code><code>Primary State: [Exploration, Combat, Menu]

Exploration States: [Walking, Running, Swimming, Climbing]
Combat States: [Attacking, Blocking, Dodging, Casting]
Menu States: [Inventory, Skills, Map, Dialogue]</code></code></pre><p>With this kind of setup, the map cannot be opened during combat, for example.</p><p>You can't accidentally start climbing while in the middle of casting.</p><h3>Event Based State Changes</h3><p>So far, the only way we've seen to change states is by putting some conditions right into the active code path.</p><p>However, you may want to allow events to update states. This decouples state change from the code path and could get messy quickly, but is very powerful.</p><p>For example, imagine a scenario where an enemy is chasing the player, so it's in the <code>[Chasing]</code> state. The player is crafty, though, and throws a smoke bomb - obscuring the enemy's vision and making it lose the player.</p><pre><code><code>// Perhaps the smoke bomb has some code like this
entity_ids := gather_entities_nearby(pos, radius)

for entity_id in entity_ids {
  event_emit(entity_id, "vision_obscured")
}</code></code></pre><p>Now the enemy can react to this dynamic event without having it coded in the state machine itself:</p><pre><code><code>event_listen(entity_id, "vision_obscured", proc() {
  entity.state = .Searching
})</code></code></pre><p>This approach allows us to keep the code that handles vision and detection separate from the AI state machine itself. Any system that affects visibility may emit an event and the AI will respond in kind.</p><h2>Conclusion</h2><p>State machines keep orthogonal states in design orthogonal in the code.</p><p>An escape hatch can be added with something like an event system (for example) - but without caution, this will soon turn into hard to debug spaghetti code once again.</p><p>As usual, there&#8217;s no silver bullet - just trade-offs.</p><div><hr></div><p>If you enjoy this newsletter and want to go deeper, with more structure, I made <a href="https://programvideogames.com/?src=newsletter_2025_03_05">Program Video Games</a> for you. Transform your game ideas into working systems from first principles.</p>]]></content:encoded></item><item><title><![CDATA[Choosing the Right Graphics API]]></title><description><![CDATA[Lessons from Canvas2D to Vulkan]]></description><link>https://www.bytesbeneath.com/p/choosing-the-right-graphics-api</link><guid isPermaLink="false">https://www.bytesbeneath.com/p/choosing-the-right-graphics-api</guid><dc:creator><![CDATA[Dylan Falconer]]></dc:creator><pubDate>Wed, 26 Feb 2025 05:51:20 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!nCcM!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Faf8a8950-684c-43f8-b247-d61569287796_1872x1080.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>I've used quite a few graphics APIs. From Canvas2D on the web to my current engine in Vulkan. In this article I'll share with you my journey and provide a guide for anyone wanting to get into graphics.</p><h2>The Beginning: Canvas2D and JavaScript</h2><p>When I started making games as a hobby back in 2013, I was only familiar with JavaScript, Perl, Ruby, and PHP. I was a 23 year old web developer who had gone from high school into the industry, skipping university.</p><p>Naturally, I used web technology to make games. Enter Canvas2D.</p><p>Canvas is pretty easy to use. You create a <code>&lt;canvas&gt;</code> HTML element, use a function to get a context object, and call methods, like: <code>ctx.fillRect(130, 190, 40, 60);</code></p><p>So, I thought I'd make a game like Snake, but with the ambience of something like flOw.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!nCcM!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Faf8a8950-684c-43f8-b247-d61569287796_1872x1080.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!nCcM!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Faf8a8950-684c-43f8-b247-d61569287796_1872x1080.png 424w, https://substackcdn.com/image/fetch/$s_!nCcM!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Faf8a8950-684c-43f8-b247-d61569287796_1872x1080.png 848w, https://substackcdn.com/image/fetch/$s_!nCcM!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Faf8a8950-684c-43f8-b247-d61569287796_1872x1080.png 1272w, https://substackcdn.com/image/fetch/$s_!nCcM!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Faf8a8950-684c-43f8-b247-d61569287796_1872x1080.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!nCcM!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Faf8a8950-684c-43f8-b247-d61569287796_1872x1080.png" width="1456" height="840" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/af8a8950-684c-43f8-b247-d61569287796_1872x1080.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:840,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:607127,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://www.bytesbeneath.com/i/157938646?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Faf8a8950-684c-43f8-b247-d61569287796_1872x1080.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!nCcM!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Faf8a8950-684c-43f8-b247-d61569287796_1872x1080.png 424w, https://substackcdn.com/image/fetch/$s_!nCcM!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Faf8a8950-684c-43f8-b247-d61569287796_1872x1080.png 848w, https://substackcdn.com/image/fetch/$s_!nCcM!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Faf8a8950-684c-43f8-b247-d61569287796_1872x1080.png 1272w, https://substackcdn.com/image/fetch/$s_!nCcM!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Faf8a8950-684c-43f8-b247-d61569287796_1872x1080.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">flOw, Playstation 3</figcaption></figure></div><p>My game featured a "snake" similar to the one above, but much less beautiful. You would grow tail segments when collecting food.</p><p>From the sides of the screen, enemies would fly into view - enemies you can't kill.</p><p>There were squares, moving in a straight line across the screen (horizontally or vertically). There were circles, which curved towards the snake but not fast enough to be seeking-missiles. There were hexagons, which would move in random directions, then pause and shoot out 6 triangles (one from each side).</p><p>Naturally, the game got more difficult as time went on. As such, the screen filled up with shapes to render. The enemies, the food, and the growing "snake" all taking up valuable compute and rendering time.</p><p>JavaScript is a garbage collected language. I was having large slow-downs whenever the garbage collector kicked in.</p><p>The way to fix this was to implement pool allocators - ways to re-use memory. Such that the garbage collector didn't clean up the objects. I thought the issue was Canvas being slow - but really it was my understanding. Regardless, I decided to ditch it and move to Unity.</p><p>Recreating the game in Unity, I ran into the same issues. C# is also a garbage collected language, and maybe the way I programmed just wasn't up to snuff - I programmed in Unity as if it was JavaScript (that was an option back then, not just C#).</p><p>I decided to put the game on ice for a while. I made a different game using Canvas2D again about riding a motorbike to the right, and dodging obstacles. I sent this to a local technical college with an email and was accepted into the Diploma of Game Programming course, despite applications being closed (thanks Marek).</p><p>I'm going to skip ahead here, as there's not much to say besides that I got experience in Unity and UE3.</p><p>I'd recommend Canvas2D if you are already familiar with web programming and want to get your feet wet with a very simple API. It's missing many features, and although some game engines have been built on it, such as ImpactJS (heavily modified for the game CrossCode), I think we have better web options nowadays.</p><h2>Diving into the Deep End: OpenGL</h2><p>I finished my course, started and failed to complete a couple of games with an artist friend I met there, but something was still bugging me. I made so many prototypes in Unity, but I was always fighting with the engine. I could never figure out how to get the look or feel I wanted. And, don't get me started on ScriptableObjects... Plus, I had to implement object pooling in Unity as well to get performance under control.</p><p>In 2019 I had the thought that I didn't actually know how to program, so I decided to learn C and OpenGL. This was tough.</p><p>To learn OpenGL, I used the fantastic <a href="https://learnopengl.com">https://learnopengl.com</a> resource. I spent a week at the local university library, all day every day, working through it.</p><p>By the end, I could draw very simple things in OpenGL, but still had to work through that first second another five times over the next few years to really understand it.</p><p>I watched some videos by TheCherno. I was specifically interested in batch rendering. I didn't realise then that this concept is basically the same as a command buffer, and also conceptually similar to a linear allocator.</p><p>I was able to render basically any 2D game I could think of at full FPS using just this small amount of OpenGL (one quad batch renderer). The fact I was only interested in pixel art games at this time made a big difference.</p><h2>A Focus on Productivity: raylib</h2><p>Some time later, I decided that all this graphics programming was getting in the way of making things. I didn't know about RenderDoc, NVIDIA NSight, or how to add debug logging to OpenGL. Many days were spent looking at a blank screen or totally incomprehensible shapes and colours flashing across the window.</p><p>I chose raylib because it seemed simple and vaguely familiar due to the way rendering works. You first call <code>BeginDrawing</code> then write various draw calls <code>DrawRectangle..</code> and then <code>EndDrawing</code>. It's essentially immediate-mode graphics - similar to Canvas2D, and similar to batch rendering quads.</p><p>I worked on various small projects in raylib and enjoyed the experience immensely. It got out of the way and allowed me to develop gameplay systems rather than spending all my time debugging the renderer.</p><p>Eventually, though, I ran into a few issues:</p><ol><li><p>raylib is built with OpenGL 3.3 by default, I wanted to use compute shaders, which means I had to rebuild the program in 4.3 mode</p></li><li><p>raylib doesn't include fences - which are a CPU&lt;-&gt;GPU synchronisation mechanism. These are pretty required to know when compute shaders have finished task</p></li></ol><p>These were minor issues as by this point I was very familiar with build systems and modifying existing projects. raylib's source code is very straightforward, so I added the wrapped functions and recompiled from source.</p><p>raylib has a fantastic set of samples which you can view on the website. It supports both 2D and 3D, and is more of a framework for interactive applications due to it's other features: audio, input, image loading, and more.</p><p>raylib has been used to make commercial games, such as Cat &amp; Onion by Karl Zylinski, though the creator of raylib, Ray, does caution against using it for commercial endeavours.</p><h2>The Middle Road: Sokol and SDL3</h2><p>I tried Sokol at some point between OpenGL and raylib. It confused me as there was no concept of a "render pass" or "pipeline" in OpenGL. Of course there is <em>the graphics pipeline</em> - but in OpenGL it's fixed. My experience was: <code>vertex stage -&gt; fragment stage -&gt; done</code> or <code>compute stage -&gt; done</code>. Pretty simple.</p><blockquote><p>I didn't use Sokol recently because it didn't have compute shader support. However, as of writing this, it seems it will be added <em>soon</em>.</p></blockquote><p>Looking at Sokol now, it's very easy to understand - but that's because I've already moved onto Vulkan and therefore <em>must</em> have an idea about these concepts. Unfortunately, the jump from OpenGL to Vulkan is large.</p><p>What helped me was actually the recently released SDL3's GPU API. Plus <a href="https://wiki.libsdl.org/SDL3/CategoryGPU">a particular page of their documentation</a>. This page walks through step by step what one may do to setup a rendering pipeline using the API. This was the missing link I needed to make the jump to Vulkan.</p><p>I think the SDL3 GPU API would be sufficient for most games. The only reason I'm not using it is because I believe Vulkan will be the API I use for the next 20+ years, so I decided to invest in it.</p><h2>The Present: Vulkan</h2><p>I am whittling away at the final boss of graphics APIs - Vulkan. It's been a huge learning experience - but the SDL3 GPU API helped reduce confusion.</p><p>Besides future investment, I'm interested in creating custom worlds and custom rendering techniques that may be difficult or impossible with APIs besides OpenGL, DirectX or Vulkan.</p><p>The control of every aspect of rendering is something I believe will be invaluable.</p><p>If you are interested in seeing my progress, I actually implemented this chain of things <a href="https://www.youtube.com/@DylanFalconer/streams">on-stream</a> recently:</p><ul><li><p>C99 + DX11 Software Renderer</p></li><li><p>C99 + DX11 Hardware Renderer</p></li><li><p>Odin + OpenGL Compute Shader Marching Cubes Terrain Generation</p></li><li><p>Odin + SDL3 GPU API rasterised renderer, model loading, 3D lighting</p></li><li><p>Odin + Vulkan rasterised renderer (on par with SDL3 one)</p></li><li><p>Odin + Vulkan raymarching + rasterised renderer</p></li></ul><p>As you can see, I was working with DX11 - similar in difficulty to OpenGL.</p><p>I switched to Odin + OpenGL to have more parity with my course material. I teach game programming using Odin + raylib at <a href="https://programvideogames.com">https://programvideogames.com</a>.</p><p> The reason we use raylib is similar to what I stated above - we are focusing on gameplay and systems programming, not graphics programming.</p><p>I stayed with Odin, and eventually progressed to Vulkan - where I'm focusing my energy now.</p><p>If you are struggling with graphics programming - you are not alone. Nearly everyone I know who has started in the field has struggled. Especially if, like me, their mathematics knowledge unused since high school.</p><h2>Conclusion</h2><p>If you are just getting started in graphics programming and are more interested in the end-result (I.E. making a game), then I'd recommend raylib.</p><p>For something more advanced, I think SDL3 GPU API or Sokol are the best choices.</p><p>The final boss, Vulkan, is not to be undertaken lightly. I did 5 1000+ line hello-triangles before grasping what was going on.</p><p>I haven't mentioned wgpu or Metal - I don't have direct experience with them. I have heard good things about wgpu and I know notch is using it for his new game "Levers and Chests". I have read that wgpu is similar to Vulkan, but easier.</p><p>So, my order of learning would look like this:</p><ul><li><p>Canvas2D/P5.js</p></li><li><p>raylib</p></li><li><p>OpenGL/DX11</p></li><li><p>Sokol/SDL3 GPU/webgpu</p></li><li><p>Vulkan/DX12</p></li></ul><p>Obviously, you can skip straight to any API you want - this is just what I think makes sense in order of difficulty to learn and building on previous steps.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://www.bytesbeneath.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading The Bytes Beneath! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><p></p>]]></content:encoded></item><item><title><![CDATA[Entity Systems - Two ECS Alternatives]]></title><description><![CDATA[What exactly is an entity system and how do we build one?]]></description><link>https://www.bytesbeneath.com/p/entity-systems-two-ecs-alternatives</link><guid isPermaLink="false">https://www.bytesbeneath.com/p/entity-systems-two-ecs-alternatives</guid><dc:creator><![CDATA[Dylan Falconer]]></dc:creator><pubDate>Sat, 15 Feb 2025 02:09:45 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!G6W0!,w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F410b2934-9526-41e2-b517-4180b6d92282_500x500.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>I&#8217;m not making the case for or against ECS (Entity Component Systems) in this article.</p><p>I&#8217;ll assume you have done your research and decided against it.</p><p>So then, what is an Entity System and how do we build one?</p><p>Keeping things simple, we&#8217;ll put data required for all entities onto an <code>Entity</code> type.</p><p>This is often referred to as a &#8220;mega-struct&#8221;.</p><pre><code><code>Entity :: struct {
&#9;pos, vel:                   Vec2,
&#9;collider_size:              Vec2,
&#9;sprite_size, sprite_offset: Vec2,
&#9;health, max_health:         int,
&#9;flags:                      bit_set[Entity_Flags],
}</code></code></pre><p>We can store a bunch of boolean values inside a <code>bit_set</code>.</p><pre><code><code>Entity_Flags :: enum {
&#9;Is_Dead,
&#9;Is_Flipped,
&#9;// etc...
}</code></code></pre><p>We&#8217;ll store our entities in a simple dynamic array.</p><pre><code><code>entities: [dynamic]Entity</code></code></pre><p>Then, if you language allows it, I recommend creating a distinct type for your entity IDs.</p><pre><code><code>Entity_Id :: distinct int</code></code></pre><p>Why a distinct type?</p><p>Imagine we use a simple <code>int</code>.</p><p>Then imagine we also create another system that uses <code>int</code> as IDs.</p><pre><code><code>other_system_id := other_system_create()
entity := entity_get(other_system_id)</code></code></pre><p>Obviously that is contrived, but you can get into situations where you pass the wrong ID in because they are all <code>int</code>.</p><p>With distinct types, you&#8217;ll get a type error at compile time if you pass anything other than <code>Entity_Id</code> in.</p><p>Let&#8217;s build out the entity system procs:</p><pre><code><code>entity_get :: proc(id: Entity_Id) -&gt; ^Entity {
&#9;if int(id) &lt; len(entities) {
&#9;&#9;return &amp;entities[int(id)]
&#9;}
&#9;return nil
}

entity_create :: proc(entity: Entity) -&gt; Entity_Id {
&#9;for &amp;e, i in entities {
&#9;&#9;if .Is_Dead in e.flags {
&#9;&#9;&#9;e = entity
&#9;&#9;&#9;e.flags -= {.Is_Dead}
&#9;&#9;&#9;return Entity_Id(i)
&#9;&#9;}
&#9;}

&#9;index := len(entities)
&#9;append(&amp;entities, entity)

&#9;return Entity_Id(index)
}

entity_kill :: proc(id: Entity_Id) -&gt; bool {
&#9;if int(id) &lt; len(entities) {
&#9;&#9;entities[int(id)].flags += {.Is_Dead}
&#9;&#9;return true
&#9;}
&#9;return false
}</code></code></pre><blockquote><p>You should not keep Entity pointers across calls of <code>entity_create</code>. If the array is full and a reallocation is required, pointers will be invalidated.</p></blockquote><p>These are almost the simplest versions I have used across various projects.</p><p>There may be a problem, though, depending on your game&#8217;s design.</p><p>For example:</p><ul><li><p>Given two Entities A and B</p></li><li><p>B is following A</p></li><li><p>A gets killed</p></li><li><p>C gets created in A&#8217;s slot</p></li><li><p>B will now incorrectly follow C</p></li></ul><p>To counter this issue, we can introduce the concept of a &#8220;generation&#8221; to the entities.</p><pre><code><code>Entity :: struct {
&#9;generation: int,
&#9;// other fields remain unchanged
}</code></code></pre><p>Now when you assign an Entity to follow another, you also include the generation.</p><p>Something like this, (just one way of many)</p><pre><code><code>Follow :: struct {
&#9;id: Entity_Id,
&#9;generation: int,
}

following: map[Entity_Id]Follow</code></code></pre><p>Then in your update code, you&#8217;d make sure the generation matches:</p><pre><code><code>if follow, ok := following[id]; ok {
&#9;other := entity_get(other_id)
&#9;if other.generation == follow.generation {
&#9;&#9;// follow code goes here
&#9;} else {
&#9;&#9;delete_key(id)
&#9;}
}</code></code></pre><p>The generation is set when an entity slot is reused:</p><pre><code><code>entity_create :: proc(entity: Entity) -&gt; Entity_Id {
&#9;for &amp;e, i in entities {
&#9;&#9;if .Is_Dead in e.flags {
&#9;&#9;&#9;gen := e.generation + 1 // Save generation before overwriting
&#9;&#9;&#9;e = entity
&#9;&#9;&#9;e.flags -= {.Is_Dead}
&#9;&#9;&#9;e.generation = gen // Set to new generation
&#9;&#9;&#9;return Entity_Id(i)
&#9;&#9;}
&#9;}

&#9;index := len(entities)
&#9;append(&amp;entities, entity)

&#9;return Entity_Id(index)
}</code></code></pre><p>Here&#8217;s the rest of a very simple program I wrote that uses the first style:</p><pre><code><code>entity_update :: proc(dt: f32) {
&#9;for &amp;e in entities {
&#9;&#9;if .Is_Dead in e.flags do continue

&#9;&#9;e.pos += e.vel * dt
&#9;}
}

main :: proc() {
&#9;id := entity_create({pos = 100, vel = 200})

&#9;dt :: 1.0 / 60.0

&#9;for i in 0 ..&lt; 4 {
&#9;&#9;entity_update(dt)
&#9;&#9;fmt.println(entity_get(id).pos)
&#9;}
}</code></code></pre><p>It prints out:</p><pre><code><code>[103.333336, 103.333336]
[106.66667, 106.66667]
[110.000008, 110.000008]
[113.33334, 113.33334]</code></code></pre><p>Alright, that&#8217;s it!</p><p>Those are my two simple entity systems to get you going without having to create complex ECS queries.</p><p>If you enjoyed this post, please consider joining the mailing list. Thank you for reading!</p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://www.bytesbeneath.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe now&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://www.bytesbeneath.com/subscribe?"><span>Subscribe now</span></a></p><p></p>]]></content:encoded></item><item><title><![CDATA[C for JavaScript Developers]]></title><description><![CDATA[This newsletter is for those of you interested in taking a crack at C (or any systems language) from the perspective of a web developer.]]></description><link>https://www.bytesbeneath.com/p/c-for-javascript-developers</link><guid isPermaLink="false">https://www.bytesbeneath.com/p/c-for-javascript-developers</guid><dc:creator><![CDATA[Dylan Falconer]]></dc:creator><pubDate>Wed, 08 May 2024 21:01:35 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!BVJE!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcc799b79-fa73-4b00-acf8-3140026e394d_598x909.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Learning a systems language will:</p><ul><li><p>Deepen your understanding of programming concepts.</p></li><li><p>Help write more efficient code.</p></li></ul><p>I share a bit of my story and the insights I have gleaned from taking this path.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://www.bytesbeneath.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading The Bytes Beneath! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><p>Reading time is about 6 minutes.</p><h2>First, a little story</h2><p>I was in my mid twenties. I didn't have a proper Computer Science education. I hadn't even finished high school.</p><p>Spending half a decade in the web industry taught me surprisingly little about programming.</p><p>I was a "glue-coder". I received praise and more money for being a good glue-coder. I wasn't satisfied.</p><p>For you, the paycheque may be enough to stay a glue-coder. For me, it wasn't about the money.</p><p>So, I quit my web dev job and went to get a Diploma of Video Game Programming.</p><p>Unfortunately, we only learnt how to use Unreal Engine 3 Visual Scripting.</p><p>I went to University to learn Computer Science. In year 1, we only learnt about ethics of IT and some basic Linux stuff I already knew by that point.</p><p>I dropped out. I went back into the web industry and continued gluing.</p><p>I decided to teach myself C to gain more understanding of what was going on. That was five years ago.</p><p>Now I can see how little I knew and how little I needed to know. I could build front-end web applications that were "good enough".</p><p>If any of that resonates with you, I wrote this for you.</p><p>I hope this helps get you up that first hill.</p><h2>Manual memory management</h2><p>Have you ever tried to write some high-performance code in JavaScript?</p><p>A little game or animation using Canvas?</p><p>You may have noticed that performance sucks.</p><p>If you go deep enough, you may find yourself implementing an Object Pool.</p><p>An Object Pool is a strategy to prevent the Garbage Collector from deleting your objects.</p><p>This way, there won't be those sawtooth spike patterns in your memory usage that cause lag.</p><p>Let's say you are making a simple video game with a space ship flying through space.</p><p>Enemies appear from the top of the screen. You have a ship flying "up". I'm sure you've seen games like this.</p><p>When ships and missiles explode, they create particles. These particles vary in colour, size, lifetime, and velocity.</p><p>You find that when many enemies explode all at once the game lags.</p><p>You profile the game. The particles spawning and the Garbage Collector cleaning them up is causing lag.</p><p>"What if they didn't get cleaned up and instead we reuse them?"</p><p>The answer to that question is an Object Pool.</p><p>One field of a particle could be `isActive` or you could keep a separate free-list.</p><p>Regardless of the details, you have now implemented a memory allocator.</p><p>Perhaps memory management is not as scary as many would have you believe.</p><h2>Pointers</h2><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!BVJE!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcc799b79-fa73-4b00-acf8-3140026e394d_598x909.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!BVJE!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcc799b79-fa73-4b00-acf8-3140026e394d_598x909.png 424w, https://substackcdn.com/image/fetch/$s_!BVJE!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcc799b79-fa73-4b00-acf8-3140026e394d_598x909.png 848w, https://substackcdn.com/image/fetch/$s_!BVJE!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcc799b79-fa73-4b00-acf8-3140026e394d_598x909.png 1272w, https://substackcdn.com/image/fetch/$s_!BVJE!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcc799b79-fa73-4b00-acf8-3140026e394d_598x909.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!BVJE!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcc799b79-fa73-4b00-acf8-3140026e394d_598x909.png" width="598" height="909" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/cc799b79-fa73-4b00-acf8-3140026e394d_598x909.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:909,&quot;width&quot;:598,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:312312,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!BVJE!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcc799b79-fa73-4b00-acf8-3140026e394d_598x909.png 424w, https://substackcdn.com/image/fetch/$s_!BVJE!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcc799b79-fa73-4b00-acf8-3140026e394d_598x909.png 848w, https://substackcdn.com/image/fetch/$s_!BVJE!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcc799b79-fa73-4b00-acf8-3140026e394d_598x909.png 1272w, https://substackcdn.com/image/fetch/$s_!BVJE!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcc799b79-fa73-4b00-acf8-3140026e394d_598x909.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>You already use them in JavaScript. Sort of.</p><p>When you pass an Object or an Array to a function in JavaScript, you don't get a copy, you get a reference.</p><p>There is a difference between references and pointers.</p><p>In JavaScript, you can't pass an array to a function then read outside the memory of that array.</p><p>You can extend the array, even using negative indices, but let's not go there.</p><p>C doesn't have such bounds checking. That means by writing off the end of an array, you corrupt other memory. You can even corrupt the call-stack, causing all kinds of unpredictable behaviour.</p><p>In practice, there are <a href="https://bytesbeneath.com/p/tools-for-memory-safety-in-c-asan">tools to prevent these bugs</a>, but it's important to know.</p><p>In either case, mutating the reference will mutate the value passed in at the call-site.</p><h2>Types and Structs</h2><p>I suspect this is less of a problem nowadays. A lot of JavaScript developers use TypeScript.</p><p>Regardless, I'll cover what I would have liked to have known about C's types when getting into it.</p><p>Types are a way to tell the compiler the size of the data you are using. This doesn't come up in JavaScript, but it's very important in C and how to think about memory.</p><p>When you define a `struct` in C, it's easy to start thinking in weird abstract ways again.</p><p>Think about it like this:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!AHNw!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6e09a8e5-1d0a-41cc-a60d-307631a17eb6_1440x392.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!AHNw!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6e09a8e5-1d0a-41cc-a60d-307631a17eb6_1440x392.png 424w, https://substackcdn.com/image/fetch/$s_!AHNw!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6e09a8e5-1d0a-41cc-a60d-307631a17eb6_1440x392.png 848w, https://substackcdn.com/image/fetch/$s_!AHNw!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6e09a8e5-1d0a-41cc-a60d-307631a17eb6_1440x392.png 1272w, https://substackcdn.com/image/fetch/$s_!AHNw!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6e09a8e5-1d0a-41cc-a60d-307631a17eb6_1440x392.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!AHNw!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6e09a8e5-1d0a-41cc-a60d-307631a17eb6_1440x392.png" width="1440" height="392" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/6e09a8e5-1d0a-41cc-a60d-307631a17eb6_1440x392.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:392,&quot;width&quot;:1440,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:75055,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!AHNw!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6e09a8e5-1d0a-41cc-a60d-307631a17eb6_1440x392.png 424w, https://substackcdn.com/image/fetch/$s_!AHNw!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6e09a8e5-1d0a-41cc-a60d-307631a17eb6_1440x392.png 848w, https://substackcdn.com/image/fetch/$s_!AHNw!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6e09a8e5-1d0a-41cc-a60d-307631a17eb6_1440x392.png 1272w, https://substackcdn.com/image/fetch/$s_!AHNw!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6e09a8e5-1d0a-41cc-a60d-307631a17eb6_1440x392.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>A struct is a way to bundle values together and refer to them as one "thing".</p><p>They are not objects, though you may hear C programmers refer to them as objects. It's not the same - there are no methods and modification is not possible.</p><p>I'll refer to a struct value as an "instance".</p><p>An instance of a struct is a block of memory with enough bytes to store the fields in order. They may sometimes have padding between fields or at the end, but I'll skip over that.</p><p>In C, the size of types vary depending on platform. For most things we use 64-bit architecture and have been for over a decade.</p><p>That means `int` and `float` are 4 bytes and `char` is a single byte.</p><p>As I said earlier, types are kind of telling the compiler the size of things.</p><p>If you cast an `int` pointer to a `char` pointer then read the value, you will get only a single byte.</p><p>If the number happens to be less than 128, you'll see no problem. If it's higher, you may get a negative number instead as a single `char` represents -127 to 127.</p><h3>A working example</h3><p>Here's a working program that shows what I'm talking about, with extensive comments.</p><p>It includes a new concept called "casting". This allows you to refer to one pointer type as another type. It's very important to know this.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!-skC!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F83288066-51bd-4b68-a0e5-6dc401549efb_1440x952.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!-skC!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F83288066-51bd-4b68-a0e5-6dc401549efb_1440x952.png 424w, https://substackcdn.com/image/fetch/$s_!-skC!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F83288066-51bd-4b68-a0e5-6dc401549efb_1440x952.png 848w, https://substackcdn.com/image/fetch/$s_!-skC!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F83288066-51bd-4b68-a0e5-6dc401549efb_1440x952.png 1272w, https://substackcdn.com/image/fetch/$s_!-skC!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F83288066-51bd-4b68-a0e5-6dc401549efb_1440x952.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!-skC!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F83288066-51bd-4b68-a0e5-6dc401549efb_1440x952.png" width="1440" height="952" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/83288066-51bd-4b68-a0e5-6dc401549efb_1440x952.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:952,&quot;width&quot;:1440,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:209181,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!-skC!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F83288066-51bd-4b68-a0e5-6dc401549efb_1440x952.png 424w, https://substackcdn.com/image/fetch/$s_!-skC!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F83288066-51bd-4b68-a0e5-6dc401549efb_1440x952.png 848w, https://substackcdn.com/image/fetch/$s_!-skC!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F83288066-51bd-4b68-a0e5-6dc401549efb_1440x952.png 1272w, https://substackcdn.com/image/fetch/$s_!-skC!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F83288066-51bd-4b68-a0e5-6dc401549efb_1440x952.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Why is it useful to think of them as block of memory with byte offsets?</p><p>It keeps you thinking of the program in a practical way.</p><p>For example, let's say you have a library that needs the float values but nothing else.</p><p>The library can't understand your type, but it can understand byte offsets.</p><p>Libraries will often have built in the concept of "stride". That is, how many bytes are between each value it wants to operate on.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!pVzn!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4d50099f-441a-4745-bc4d-0f6ea2f890ef_1440x472.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!pVzn!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4d50099f-441a-4745-bc4d-0f6ea2f890ef_1440x472.png 424w, https://substackcdn.com/image/fetch/$s_!pVzn!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4d50099f-441a-4745-bc4d-0f6ea2f890ef_1440x472.png 848w, https://substackcdn.com/image/fetch/$s_!pVzn!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4d50099f-441a-4745-bc4d-0f6ea2f890ef_1440x472.png 1272w, https://substackcdn.com/image/fetch/$s_!pVzn!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4d50099f-441a-4745-bc4d-0f6ea2f890ef_1440x472.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!pVzn!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4d50099f-441a-4745-bc4d-0f6ea2f890ef_1440x472.png" width="1440" height="472" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/4d50099f-441a-4745-bc4d-0f6ea2f890ef_1440x472.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:472,&quot;width&quot;:1440,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:78391,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!pVzn!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4d50099f-441a-4745-bc4d-0f6ea2f890ef_1440x472.png 424w, https://substackcdn.com/image/fetch/$s_!pVzn!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4d50099f-441a-4745-bc4d-0f6ea2f890ef_1440x472.png 848w, https://substackcdn.com/image/fetch/$s_!pVzn!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4d50099f-441a-4745-bc4d-0f6ea2f890ef_1440x472.png 1272w, https://substackcdn.com/image/fetch/$s_!pVzn!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4d50099f-441a-4745-bc4d-0f6ea2f890ef_1440x472.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>If you pass in an array of `float` to the function, you can use a stride of 0.</p><p>Pass in an array of struct instances and then you'll need to tell the library the size of your type.</p><p>The lack of runtime type information is why thinking in sizes and offsets is important.</p><h2>Arrays are fixed size</h2><p>In C, arrays are always a fixed length.</p><p>This may sound limiting, but in practice it's usual to know the required capacity of your data.</p><p>I would recommend, if you need dynamic arrays, to use one of the thousands of libraries that exist. Or, <a href="https://bytesbeneath.com/p/dynamic-arrays-in-c">learn how to create your own</a>.</p><h2>Iterators</h2><p>There are no iterators or syntactic sugar for looping over stuff.</p><p>Since there are no methods, there's nothing like `my_array.forEach`.</p><p>You will have to get used to writing plain loops.</p><h2>Strings</h2><p>Strings are Arrays of `char` in C with a null-value to denote the end `\0`.</p><p>Personally, I don't like the way they work by default, so I use my own implementation.</p><p>I wrote it to be compatible with normal null-terminated strings for library interfaces.</p><p>As with Dynamic Arrays, I recommend using a library or <a href="https://bytesbeneath.com/p/custom-strings-in-c">learning how to create your own</a>.</p><p>Of course, you'll have to learn how to do Unicode to have proper language support. It is quite fun from what I've read - I haven't added it myself.</p><h2>The biggest change is mindset</h2><p>We can go over the language differences all day, but it won't matter much if you keep the JavaScript mindset.</p><p>I'm speaking from experience here. My mindset had to change to get better at C and programming in general.</p><p>I've never spoken to anyone about this before, but I used to think of programs in very fuzzy ways.</p><p>In my mind's eye I'd visualise graphs of objects all talking to each other.</p><p>I'd obsess about code cleanliness.</p><p>I'd comment every line with useless comments.</p><p>I went down the OOP SOLID rabbit hole and tried very hard to follow those rules.</p><p>None of that stuff made programming easier.</p><p>What actually helped was a <a href="https://www.youtube.com/watch?v=rX0ItVEVjHc">talk by Mike Acton</a> that I'm sure many people in this sphere are familiar with.</p><p>"Programs are for data transformation"</p><p>You have X. You want Y. The program (or subset) is how you convert X to Y.</p><p>All that's in a program: <em>plain old data</em> and <em>instructions</em> that operate on that data.</p><p>Adding layers of complexity should not be the norm. Yet it is - especially in JavaScript land.</p><p>You are better off only adding complexity after careful consideration of the trade-offs.</p><h2>How this mindset change helps</h2><p>These are examples of web based work I've done recently that benefited from a new mindset.</p><h3>A recent SaaS project</h3><p>Over the past little while I've been working on a web app.</p><p>In the past, I would have looked up what the hot JavaScript framework of the month is and used that.</p><p>I would have found some bleeding edge stuff to add to it that breaks the bundler... and spent days fixing build issues.</p><p>What did I do differently? I thought about what I actually need to get done and what the most practical approach was.</p><p>I made a back-end in Golang. I made a front-end in HTML, CSS, JS with htmx.</p><p>I added a websocket connection to send htmx events from the server to the client for dynamic updates.</p><p>There's more stuff around auth and whatever else that you'd have to do regardless, but the point is:</p><p>I was able to focus on the data transformations required to give the user the experience I wanted.</p><p>I chose the stack based on efficiency and requirements, not because it's flavour of the month.</p><p>I didn't have to think about React hooks or component lifetimes.</p><p>I didn't have to mess with webpack or vite - my build system is a few lines in a bash file.</p><h3>A recent web client</h3><p>I had a website client a few months ago who wanted a very simple site.</p><p>She needed a home page with some static information, a photo, and a booking form.</p><p>She wanted to be able to update some of the booking form content: </p><ul><li><p>Available days</p></li><li><p>Text and images of a few sections</p></li><li><p>Turn on or off a few fields</p></li></ul><p>And she refused to pay a monthly fee to somewhere like Jotform.</p><p>How would you have created this? Wordpress? A service like Wix or Squarespace?</p><p>Once again, I used Go and htmx with HTML, CSS, JS.</p><p>It took only a few lines of code to add this functionality.</p><p>The site is fast, has good SEO, and it's easy for the client to update it.</p><h2>Conclusion</h2><p>Learning how to think about programming in a different manner has been invaluable.</p><p>I can now consider and create programs that would have become tangled spaghetti code if my past self tried. </p><p>I'm sure a lot of that I owe to general experience. </p><p>OOP did not help with this problem.</p><h2>Notes</h2><ul><li><p>C isn't simple. I'm sure I've said it is simple in the past, but I was wrong. Unfortunately there is a lot of unexpected and unintuitive behaviour.</p></li><li><p>In case you thought I was insulting you for being a "glue-coder". I wasn't.</p></li></ul><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://www.bytesbeneath.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading The Bytes Beneath! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div>]]></content:encoded></item><item><title><![CDATA[Tools for memory safety in C: ASan]]></title><description><![CDATA[Believe it or not, they do exist]]></description><link>https://www.bytesbeneath.com/p/tools-for-memory-safety-in-c-asan</link><guid isPermaLink="false">https://www.bytesbeneath.com/p/tools-for-memory-safety-in-c-asan</guid><dc:creator><![CDATA[Dylan Falconer]]></dc:creator><pubDate>Tue, 05 Mar 2024 17:00:37 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/c213b5ba-7cf4-4b8e-b93f-0fa62cac3654_875x403.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>The current discourse around memory safety focuses on languages rather than tools.</p><p><strong>Tools already exist to catch memory errors.</strong></p><p>There are multiple sanitisers available in Clang, GCC, and MSVC: Address, Memory, Thread, Undefined Behavior.</p><p>Today I&#8217;m going to show you how and why you want to use the Address Sanitiser (ASan).</p><p>By utilising this tool you will:</p><ul><li><p>Prevent difficult to find bugs.</p></li><li><p>Create more robust software.</p></li></ul><p>Unfortunately, it seems that most programmers are not aware of these tools. I didn&#8217;t know about them for the first 4 years of programming in C.</p><h2><strong>Ignoring ASan is like driving blindfolded: reckless and bound to crash.</strong></h2><p>Using ASan while developing will reduce the likelihood of a whole host of bugs.</p><ul><li><p>Catch out of bounds access on heap, stack and globals.</p></li><li><p>Never have a use-after-free or double-free again.</p></li><li><p>Detect leaked memory.</p></li></ul><p>If you have been bitten by these problems in the past, read on.</p><h2>Catch Out-of-bounds errors</h2><p>Consider the following C program:</p><pre><code><code>#include &lt;stdio.h&gt;

int main(void) {
  int arr[10];
  for (int i = 0; i &lt;= 10; i += 1) {
    arr[i] = i;
  }
  printf("Done!\\n");
  return 0;
}
</code></code></pre><p>The mistake is obvious due to the tiny surface area of the program.</p><p>Loop limits are often calculated based on some dynamic data that makes this error harder to catch.</p><p>This program will compile and seemingly execute with no issues.</p><pre><code><code>&gt; cl /Zi oob.c
&gt; oob
Done!
</code></code></pre><p>When running it from the command line that &#8220;Done!&#8221; is printed, so all good, right?</p><p>If you compiled and ran this program, you would have noticed that it takes a second or two to exit.</p><p>Running it in a debugger shows an error when the program exits.</p><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!jir1!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fba89a322-4ec7-4c35-8916-0855d212dd3b_343x23.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!jir1!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fba89a322-4ec7-4c35-8916-0855d212dd3b_343x23.png 424w, https://substackcdn.com/image/fetch/$s_!jir1!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fba89a322-4ec7-4c35-8916-0855d212dd3b_343x23.png 848w, https://substackcdn.com/image/fetch/$s_!jir1!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fba89a322-4ec7-4c35-8916-0855d212dd3b_343x23.png 1272w, https://substackcdn.com/image/fetch/$s_!jir1!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fba89a322-4ec7-4c35-8916-0855d212dd3b_343x23.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!jir1!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fba89a322-4ec7-4c35-8916-0855d212dd3b_343x23.png" width="343" height="23" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/ba89a322-4ec7-4c35-8916-0855d212dd3b_343x23.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:23,&quot;width&quot;:343,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:1697,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!jir1!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fba89a322-4ec7-4c35-8916-0855d212dd3b_343x23.png 424w, https://substackcdn.com/image/fetch/$s_!jir1!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fba89a322-4ec7-4c35-8916-0855d212dd3b_343x23.png 848w, https://substackcdn.com/image/fetch/$s_!jir1!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fba89a322-4ec7-4c35-8916-0855d212dd3b_343x23.png 1272w, https://substackcdn.com/image/fetch/$s_!jir1!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fba89a322-4ec7-4c35-8916-0855d212dd3b_343x23.png 1456w" sizes="100vw" loading="lazy"></picture><div></div></div></a></figure></div><p>Let&#8217;s see what compiling with ASan turned on tells us when we run it.</p><pre><code><code>&gt; cl /Zi /fsanitize=address oob.c
&gt; oob</code></code></pre><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!i6cq!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fde2674bc-a012-4d32-8089-4690ddf8b4fa_1256x1226.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!i6cq!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fde2674bc-a012-4d32-8089-4690ddf8b4fa_1256x1226.png 424w, https://substackcdn.com/image/fetch/$s_!i6cq!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fde2674bc-a012-4d32-8089-4690ddf8b4fa_1256x1226.png 848w, https://substackcdn.com/image/fetch/$s_!i6cq!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fde2674bc-a012-4d32-8089-4690ddf8b4fa_1256x1226.png 1272w, https://substackcdn.com/image/fetch/$s_!i6cq!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fde2674bc-a012-4d32-8089-4690ddf8b4fa_1256x1226.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!i6cq!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fde2674bc-a012-4d32-8089-4690ddf8b4fa_1256x1226.png" width="1256" height="1226" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/de2674bc-a012-4d32-8089-4690ddf8b4fa_1256x1226.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1226,&quot;width&quot;:1256,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:111094,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!i6cq!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fde2674bc-a012-4d32-8089-4690ddf8b4fa_1256x1226.png 424w, https://substackcdn.com/image/fetch/$s_!i6cq!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fde2674bc-a012-4d32-8089-4690ddf8b4fa_1256x1226.png 848w, https://substackcdn.com/image/fetch/$s_!i6cq!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fde2674bc-a012-4d32-8089-4690ddf8b4fa_1256x1226.png 1272w, https://substackcdn.com/image/fetch/$s_!i6cq!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fde2674bc-a012-4d32-8089-4690ddf8b4fa_1256x1226.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><h2>A simple explanation of how ASan works</h2><p>Okay, that&#8217;s a pretty crazy looking message. Let&#8217;s go over what ASan is.</p><p><em>This is in no way a comprehensive explanation.</em></p><p>Usually when your program allocates memory on the stack or the heap, it&#8217;s <em>reasonably</em> close together.</p><p>ASan spreads out allocations and adds a bunch of &#8220;poison bytes&#8221; surrounding them.</p><p>When one of these poison bytes is written to or read from, ASan reports that as an error.</p><p>The &#8220;shadow bytes&#8221; seen above is like a minimap of the poisoned bytes.</p><p>It&#8217;s how ASan knows which memory is poisoned and what kind of poison is there.</p><h2>Never have a use-after-free or double-free again</h2><p>Consider this simple C program:</p><pre><code><code>#include &lt;stdlib.h&gt;
#include &lt;stdio.h&gt;

int main(void) {
    int *nums = malloc(32);
    free(nums);
    printf("%d\\n", nums[0]);
    return 0;
}</code></code></pre><p>When I compile and run this, I get 0.</p><pre><code><code>&gt; cl /Zi uaf.c
&gt; uaf
0</code></code></pre><p>There&#8217;s no guarantee that it will print the correct value, and perhaps the page that was allocated is no longer readable, crashing the program.</p><p>Compiled with ASan and I get a helpful error.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!WZV6!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd45539c3-c397-46da-aa5e-e6f1d5560fa6_1233x687.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!WZV6!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd45539c3-c397-46da-aa5e-e6f1d5560fa6_1233x687.png 424w, https://substackcdn.com/image/fetch/$s_!WZV6!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd45539c3-c397-46da-aa5e-e6f1d5560fa6_1233x687.png 848w, https://substackcdn.com/image/fetch/$s_!WZV6!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd45539c3-c397-46da-aa5e-e6f1d5560fa6_1233x687.png 1272w, https://substackcdn.com/image/fetch/$s_!WZV6!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd45539c3-c397-46da-aa5e-e6f1d5560fa6_1233x687.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!WZV6!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd45539c3-c397-46da-aa5e-e6f1d5560fa6_1233x687.png" width="1233" height="687" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/d45539c3-c397-46da-aa5e-e6f1d5560fa6_1233x687.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:687,&quot;width&quot;:1233,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:88515,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!WZV6!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd45539c3-c397-46da-aa5e-e6f1d5560fa6_1233x687.png 424w, https://substackcdn.com/image/fetch/$s_!WZV6!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd45539c3-c397-46da-aa5e-e6f1d5560fa6_1233x687.png 848w, https://substackcdn.com/image/fetch/$s_!WZV6!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd45539c3-c397-46da-aa5e-e6f1d5560fa6_1233x687.png 1272w, https://substackcdn.com/image/fetch/$s_!WZV6!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd45539c3-c397-46da-aa5e-e6f1d5560fa6_1233x687.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>The first line: heap-use-after free &#8230; followed by a location <code>uaf.c:7</code></p><p>You can dangle pointers all day long. Not that I advise that kind of rash memory allocation.</p><h2>Detect memory leaks</h2><p>For some reason this is <em>not</em> working on my Windows whether I compile with Clang or CL.</p><p>It&#8217;s disappointing, but I don&#8217;t get memory leaks in my own code anyway due to using custom memory allocators.</p><p>So, for this example I&#8217;ll be firing up the old WSL2 Ubuntu and using Clang.</p><p>Our program with a simple memory leak:</p><pre><code><code>#include &lt;stdlib.h&gt;

int main(void) {
    char *buf = malloc(512);
    return 0;
}</code></code></pre><p>Compiling and running the program:</p><pre><code><code>$ clang -g -fsanitize=address leak.c -o leak
$ ./leak</code></code></pre><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!to9W!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F59f1ac56-a940-4e6b-8ad0-6029771ce6ef_1154x179.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!to9W!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F59f1ac56-a940-4e6b-8ad0-6029771ce6ef_1154x179.png 424w, https://substackcdn.com/image/fetch/$s_!to9W!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F59f1ac56-a940-4e6b-8ad0-6029771ce6ef_1154x179.png 848w, https://substackcdn.com/image/fetch/$s_!to9W!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F59f1ac56-a940-4e6b-8ad0-6029771ce6ef_1154x179.png 1272w, https://substackcdn.com/image/fetch/$s_!to9W!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F59f1ac56-a940-4e6b-8ad0-6029771ce6ef_1154x179.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!to9W!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F59f1ac56-a940-4e6b-8ad0-6029771ce6ef_1154x179.png" width="1154" height="179" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/59f1ac56-a940-4e6b-8ad0-6029771ce6ef_1154x179.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:179,&quot;width&quot;:1154,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:17178,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!to9W!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F59f1ac56-a940-4e6b-8ad0-6029771ce6ef_1154x179.png 424w, https://substackcdn.com/image/fetch/$s_!to9W!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F59f1ac56-a940-4e6b-8ad0-6029771ce6ef_1154x179.png 848w, https://substackcdn.com/image/fetch/$s_!to9W!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F59f1ac56-a940-4e6b-8ad0-6029771ce6ef_1154x179.png 1272w, https://substackcdn.com/image/fetch/$s_!to9W!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F59f1ac56-a940-4e6b-8ad0-6029771ce6ef_1154x179.png 1456w" sizes="100vw" loading="lazy"></picture><div></div></div></a></figure></div><p>ASan successfully caught this memory leak.</p><p>Tthis kind of memory leak can be a false positive as there&#8217;s no need to free memory when closing the program normally.</p><p>Perhaps using some kind of macro to turn on clean-up code when running ASan is called for.</p><h2>Takeaways</h2><ul><li><p>ASan is simple to use. Just add the parameter to your compile command.</p></li><li><p>It will detect a whole host of memory errors so you can ship robust programs.</p></li><li><p>On my Windows machine it&#8217;s iffy with leaked memory. Test on Linux with Clang if cross-compilation is easy.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://www.bytesbeneath.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Want more tactical programming content in your inbox each week? Subscribe for free.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div></li></ul><p>Thanks for reading!</p><p>See you next week,<br>    &#8212; Dylan</p>]]></content:encoded></item><item><title><![CDATA[Why I care about programming]]></title><description><![CDATA[How-tos are pointless without a why.]]></description><link>https://www.bytesbeneath.com/p/why-i-care-about-programming</link><guid isPermaLink="false">https://www.bytesbeneath.com/p/why-i-care-about-programming</guid><dc:creator><![CDATA[Dylan Falconer]]></dc:creator><pubDate>Tue, 27 Feb 2024 17:00:57 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/f1d1a28b-1c5e-4601-a524-477201cee795_1024x1024.webp" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Hey Friend,</p><p>I&#8217;ve been showing you you a lot of the <em>how</em>, but nothing about <em>why you should care</em>.</p><p>How-tos are pointless without a why.</p><p>This week I'm going to share a bit about my journey in the hopes that you find something useful.</p><h3><strong>Early Programming</strong></h3><p>My first encounter with &#8220;programming&#8221; was selecting &#8220;view source&#8221;. I was looking at people&#8217;s GeoCities websites and trying to figure out how it all worked.</p><p>I&#8217;d save .html files to my machine, then change the code and see what happened.</p><p>I even came up with a genius idea of renaming all the tags to custom ones that &#8220;made more sense&#8221;.</p><p>My first &#8220;real&#8221; programming was making Final Fantasy fan sites with PHP. PHP allowed me to keep the header and footer consistent by using PHP Includes.</p><p>I started with JavaScript, Ruby and Perl at my first job (front-end web developer).</p><p>My initial thoughts were amazement, like I was learning some hidden knowledge.</p><h3><strong>The Journey</strong></h3><p>I went deep into (OOP) Object Oriented Programming. I used all the things like Dependency Injection, Encapsulation, Polymorphism by Inheritance. I even implemented Classes with Inheritance in JavaScript before ES6.</p><p>I was a big JavaScript fan.</p><p>Everything I made was a rats nest.</p><p>I learnt about Object Pools because the Garbage Collector was too slow. This was my first hint that something was wrong.</p><p>I found myself wondering&#8230; &#8220;Why am I fighting with the language feature that&#8217;s meant to help me?&#8221;</p><p>Deciding to learn C in 2020 was a big change. That&#8217;s when I actually started learning how to program.</p><h3><strong>Passion</strong></h3><p>There's nothing quite like this feeling... I'd bash my head against the wall for hours or days. And then when doing something unrelated... Eureka!</p><p>Sometimes it happens in sleep, sometimes on a walk, sometimes in the shower.</p><p>Anywhere it happens, I rush back to my home or pull out my laptop and start implementing.</p><p>Many people think that programming is not creative. I am not sure where they get this idea - but it&#8217;s completely false.</p><p>The truth is that much of the time I&#8217;m engaged in what you&#8217;d call &#8220;exploratory&#8221; programming.</p><p>I need to write some stuff to see whether it all fits together and solves what needs solving.</p><p>If not, I&#8217;ll scrap it or change it and start in another direction.</p><p>Being able to program has allowed me to do things that other people need to use a service for.</p><p> I can do specific things that are difficult otherwise.</p><p>For example, I once scraped the Steam top 200 for 12 months to see if I could identify any trends.</p><p>I never ended up using the data, but can you imagine doing that without programming? It would take hours each day as the data captured included things like discounts, tags, all sorts of stuff.</p><p>Nowadays we have AI that can create scripts. It's pretty useful, and getting better, but still wrong a lot of the time.</p><p>Plus, in the Steam example, I needed to figure out which CSS targets are the right ones for the data.</p><h3><strong>Lessons Learned Along the Way</strong></h3><p>People will go to great lengths to justify their beliefs.</p><p>In programming culture, this translates to dogmatic views about "unsafe" programming language features.</p><p>Some programmers demand that everyone adhere to their "safe" language.</p><p>Some demand that everyone must use their madness inducing paradigm.</p><p>I'm speaking of Rust and OOP/SOLID.</p><p>A little side-note:</p><p>I've been studying marketing recently. It seems that people don't make rational choices at all. At least not to do with buying products. It would not surprise me if all choices are irrational and justified to the self afterwards.</p><p>Back to programming.</p><p>Insurmountable seeming problems are best tackled by first breaking them down. Once problems are small enough to fit in one's head, progress by application of effort will be swift.</p><p>I try to apply this lesson learnt from programming to everything in my life.</p><h3><strong>The Bigger Picture</strong></h3><p>Programming will be important so long as we use digital technology.</p><p>I'm not convinced that advancements in LLMs will make programming obsolete.</p><p>Compilers didn't. No-code builders didn't. Visual scripting didn't.</p><p>For programming to become obsolete, we&#8217;d have to have a superior replacement. What most people are focusing on is "low skill" programming jobs.</p><p>Jobs like front-end web development <em>should </em>be on the decline already. So far, people have managed to secure job security through obfuscation and complication.</p><p>The web job collapse will come - if it hasn't already started. Look at the layoffs in 2023/2024.</p><p>That does not mean programming as a skill will go away any time soon. It means it won&#8217;t be so easy to get those $100K 2-year-experience front-end web jobs.</p><h3><strong>Why It Matters</strong></h3><p>Programming is my favourite creative outlet.</p><p>There is nothing quite like it. Think of some idea, load up my editor, write code until it compiles, observe the result of creation.</p><p>Artisanal programming is more important than ever.</p><p>Why? The industry is bursting with people using ChatGPT to write their code. They do not understand how to fix the AI's nor their mistakes.</p><p>Customers don&#8217;t care about how hard we work or what languages we use. But, they do care about their experience.</p><p>If the customer has a worse experience because we use AI and don't know how to fix stuff, that's bad for business.</p><p>Microsoft recently bragged about reducing their Teams startup time to &#8220;only&#8221; 9 seconds. Can you believe that?</p><p>The largest corporation in the world thinks that is something to be proud of. The bar is on the floor.</p><p>If you are on the fence about programming because you think it may be too difficult, consider what I just wrote... The level of incompetence to make a chat application start in 9 seconds is astounding.</p><p>If you are already a programmer - what are your reasons for caring? Do you care?</p><h3><strong>Conclusion</strong></h3><p>In summary, I care about programming for these reasons:</p><ul><li><p>It gives me a creative outlet (yes, it&#8217;s creative)</p></li><li><p>It allows me to create cool and/or useful things that other people can&#8217;t</p></li><li><p>It helped me to problem solve in other domains</p></li><li><p>It&#8217;s fun (recreational programming is a thing!)</p></li></ul><p>Thank you for your time and engagement.</p><p>If you got this far, please share this with someone that will enjoy it.</p><p>Share your own reasons for enjoying (or hating) programming in the comment section.</p><p>Until next week,<br>Dylan</p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://www.bytesbeneath.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe now&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://www.bytesbeneath.com/subscribe?"><span>Subscribe now</span></a></p>]]></content:encoded></item><item><title><![CDATA[Hot Reloading in C]]></title><description><![CDATA[How to save 260 hours of your time, every year.]]></description><link>https://www.bytesbeneath.com/p/hot-reloading-in-c</link><guid isPermaLink="false">https://www.bytesbeneath.com/p/hot-reloading-in-c</guid><dc:creator><![CDATA[Dylan Falconer]]></dc:creator><pubDate>Sat, 17 Feb 2024 18:00:48 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/59c91423-944f-4998-a145-d773e75473d9_1024x1024.webp" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h1>Hot reloading</h1><p>This week&#8217;s newsletter is about being able to recompile and reload code to see the changes in real-time - without restarting the application.</p><p>The way I learnt to develop software was using the compile-link-execute cycle. Depending on your setup, this may be more or less ergonomic.</p><p>In my setup, I used to:</p><ol><li><p>Edit and save the file.</p></li><li><p>Press Alt-M to run my build (compile &amp; link) script.</p></li><li><p>Win-2 to switch to my debugger, press F10 to start stepping through my program or F5 to just run it.</p></li><li><p>Do whatever in the program, set break points, analyse variables, etc.</p></li><li><p>Close the program.</p></li><li><p>Make changes to the code.</p></li><li><p>Start the process again.</p></li></ol><p>It&#8217;s pretty inefficient.</p><p>Now, I do this:</p><ol><li><p>Edit and save the file.</p></li><li><p>Press Alt-M to run my build script.</p></li><li><p>Alt-Tab back to the program, that was still running, and has all the state from before I made changes.</p></li><li><p>That&#8217;s it - the code has been updated in the background!</p></li></ol><h2>My Journey to Discovering Hot Reloading</h2><p>I knew about &#8220;hot module reloading&#8221; from my career as a front end web developer. It was amazing to have instant changes in real-time after updating some code.</p><p>What I didn&#8217;t know, is that this functionality has been available since the 1980s for compiled programs!</p><p>On Windows, we can load .dll (Dynamic Link Library) files. On Linux they are called .so (Shared Object) files.</p><p>These files can be loaded at run-time of a program and contain code and data, like an executable.</p><p>Join me on this adventure to figure out how to get hot reloading working in a simple Win32 C program. If you don&#8217;t use Windows, don&#8217;t fret - the code is simple and the structure will apply to Linux/MacOS just as well.</p><h2>Understanding Hot Reloading: Application Code and Cradle</h2><p>Hot reloading is a concept used in software development that allows programmers to immediately see changes made in the code without closing and re-opening the running program.</p><p>This is exactly the functionality we need to reduce the time between idea and execution.</p><p>How this can be done for our purposes is we split the code into two sections - the <em>application code</em> and the <em>cradle</em>.</p><p>Our application code is the dynamically linked code. The code we are updating. In the case of games - it&#8217;s our gameplay and perhaps a lot of the engine code, too.</p><p>Our cradle is the compiled executable that loads the application code and provides it with memory. It is important that the memory we use comes from the cradle as when the DLL is reloaded, the DLL&#8217;s memory is refreshed.</p><h2>Implementing Hot Reloading: A Step-by-Step Guide</h2><p>Now that we know what we are doing, let&#8217;s look at how to actually do it.</p><p>On Windows, there is a function called <code>LoadLibrary</code>, on Unix-like systems the equivalent is <code>dlopen</code> .</p><p>Let&#8217;s write a simple win32 layer that achieves the goal of hot reloading.</p><h3>main.c</h3><p>Our cradle will be a simple program that loops every second, reloading the DLL regardless of whether it has been changed or not.</p><p>In my experience, reloading every second is good enough <em>for a long time</em> during development.</p><pre><code><code>#include &lt;windows.h&gt;
#include &lt;stdio.h&gt;
#include &lt;string.h&gt;

#define MEMORY_SIZE 256

typedef void (*HotReloadedFunction)(char *memory, size_t *size);

int main(void) {
    HINSTANCE dll = 0;
    HotReloadedFunction fn;
    char memory[MEMORY_SIZE] = {0};

    while (1) {
        if (dll) {
            FreeLibrary(dll);
        }

        CopyFile("application.dll", "_application.dll", 0);
        dll = LoadLibrary("_application.dll");
        if (!dll) {
            printf("Failed to load DLL.\\n");
            return 1;
        }

        fn = (HotReloadedFunction)GetProcAddress(dll, "hot_reloaded_function");
        if (!fn) {
            printf("Failed to find function in DLL.\\n");
            return 1;
        }

        fn(memory, MEMORY_SIZE);

        Sleep(1000);
    }

    return 0;
}</code></code></pre><p>We are copying our application.dll file to a new _application.dll file so that the file isn&#8217;t in use when we try to compile a new one.</p><p>We provide some memory to our function - this would be where you&#8217;d store all your game state in a game, or all your application state in some other application.</p><p>We then load the copied DLL file and use <code>GetProcAddress</code>. <code>GetProcAddress </code>gets the procedure (function) address of a function. This function will de defined in the application code.</p><p>Then we call the function, passing in the memory and memory size.</p><p>Finally, we just sleep for a second. In a game this could be waiting until you hit 16.67 ms (for 60 FPS).</p><p>In most real-time cases, you probably want to use either a timer or watch the file for changes to reload the DLL.</p><h3>application.c</h3><p>Our application is dynamically loaded at run-time.</p><p>We must be careful about what kind of data we store in it.</p><p>In this case <code>whatever_string</code> is a static string that will be stored in the DLL and not changed.</p><p>However, <code>counter</code> is incremented in the <code>hot_reloaded_function</code> . Let&#8217;s see what happens.</p><pre><code><code>#include &lt;windows.h&gt;
#include &lt;string.h&gt;
#include &lt;stdio.h&gt;

char *whatever_string = "This can change at run time, even though the memory address doesn't!";
int counter = 0;

__declspec(dllexport) void hot_reloaded_function(char *memory, size_t size) {
    printf("memory size: %zu, address: %p\\n", size, memory);
    sprintf(memory, "%s", whatever_string);
    printf("%s\\n", memory);
    printf("counter: %d\\n", counter);
    counter += 1;
}

BOOL WINAPI DllMain(HMODULE inst, DWORD reason, LPVOID reserved) {
    (void)inst; (void)reason; (void)reserved;
    return 1;
}</code></code></pre><h3>build.bat</h3><pre><code><code>@echo off
cl /c /Zi /nologo application.c /Foapplication.obj
link /nologo /DLL /DEBUG /OUT:application.dll application.obj</code></code></pre><h3>build_exe.bat</h3><pre><code><code>@echo off
cl /Zi /nologo main.c</code></code></pre><h3>output</h3><p>We&#8217;ll assume we managed to change the contents of <code>whatever_string</code> between runs to &#8220;For example, here!&#8221;.</p><pre><code><code>memory size: 256, address: 00000097917DFC00
This can change at run time, even though the memory address doesn't!
counter: 0
memory size: 256, address: 00000097917DFC00
For example, here!
counter: 0</code></code></pre><ul><li><p>Memory address is unchanged - that&#8217;s good!</p></li><li><p>String is updated successfully - that&#8217;s good!</p></li><li><p>Counter is not incrementing! Why? &#8230;</p></li></ul><p>The answer is what I was alluding to earlier.</p><p>Each time the DLL is recompiled and reloaded, the memory of any variables inside the DLL are reset.</p><p>That&#8217;s why passing in memory that you <em>want</em> <em>to</em> <em>persist</em> from the cradle is so important.</p><p>Pass in memory you need to for your entire application from the cradle, then it will keep state across DLL reloads.</p><h3>The Impact of Hot Reloading on Your Programming Efficiency</h3><p>Using hot reloading allows the programmer to reduce the time between idea and execution.</p><p>You can press jump in your game, see how it feels, then change some values in the program, compile - press jump again.</p><p>It removes the (sometimes very lengthy) process of opening the program and getting back to the state you were in before.</p><p>Imagine you are testing some boss battle and you have to run back to the boss between each tweak of it&#8217;s attributes.</p><p>If closing your game, compiling, running, and getting back to the same state takes let&#8217;s say 3.6 seconds&#8230;</p><p>And, you may want to tweak things hundreds or thousands of times in one day...</p><pre><code>360 / 60 = 6 minutes
* 10 for 1000 is 1 hour</code></pre><p>That&#8217;s a saving of between 6 minutes and one hour <em>per day</em>.</p><p>What could you do with an extra 260 hours per year? Probably, <em>a lot of neat stuff</em>.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://www.bytesbeneath.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">If you found this useful and want to read more newsletters like this, <strong>subscribe today - it&#8217;s free</strong>!</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><h3>Hot Loading in the Future?</h3><p>Once I saw how easy it was to implement, I wondered why we don&#8217;t all use hot reloading all the time.</p><p>In the future, I think we may see this built in to languages and IDEs.</p><p>However, given it&#8217;s been essentially this easy for going on 40 years now... I can&#8217;t be certain of that.</p><p>I can be certain that I&#8217;ll be using it for the rest of my career, and I hope to have helped you decide to give it a try.</p><div><hr></div><p>If you enjoy this newsletter and want to go deeper, with more structure, I made <a href="https://programvideogames.com/?src=newsletter_0007">Program Video Games</a> for you. Transform your game ideas into working systems from first principles.</p>]]></content:encoded></item><item><title><![CDATA[ The Arena - Custom Memory Allocators in C]]></title><description><![CDATA[Easy, Flexible Memory Management]]></description><link>https://www.bytesbeneath.com/p/the-arena-custom-memory-allocators</link><guid isPermaLink="false">https://www.bytesbeneath.com/p/the-arena-custom-memory-allocators</guid><dc:creator><![CDATA[Dylan Falconer]]></dc:creator><pubDate>Fri, 09 Feb 2024 18:00:52 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/d2ae5ff5-69dc-4ebc-b78b-98151f553178_1024x1024.webp" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2>The Memory Management Problem</h2><p>Memory management is typically taught in ways that cause many issues. Let&#8217;s go over the 4 most common issues: Fragmentation, Allocation Speed, Lack of Control, and Lifetimes.</p><h3>Fragmentation</h3><p>Using <code>malloc</code> and <code>free</code> for individual objects inevitably cause fragmentation of the heap.</p><p>The locality of similar objects is not guaranteed when different types are allocated in an unpredictable order.</p><p>The general purpose memory allocator provided by the language (the thing behind the <code>malloc</code> call) does it&#8217;s best to make sure there are not too many gaps. But, it doesn&#8217;t know anything about the <em>usage and lifetimes</em> of your objects.</p><p>As a result, you can easily end up with bad performance by jumping around in the heap.</p><h3>Performance</h3><p>There&#8217;s a common idea that memory allocation on a per-frame basis is a quick way to slow down your application. I focus on games and real-time applications, so I&#8217;ll use &#8220;frames&#8221;, but you could replace this with any high frequency loop.</p><p>The idea is that <code>malloc</code> and <code>free</code> are either too slow or too unpredictable to be able to be used in high-frequency code paths.</p><p>The common remedy is to <code>malloc</code> and <code>free</code> before and after these code paths are executed. To ensure the program does not crash, one of two things must occur. Either a fine-tuning of the memory footprint, or an over-allocation at the outset.</p><p>If the data changes, and the code paths are not updated to fit the new data, crashes may occur.</p><h3>Lack of Control</h3><p><code>malloc</code> does not allow you to control the memory allocation strategy. You don&#8217;t know where you are getting memory from or how it&#8217;s doing it. It&#8217;s a black box. Need memory? Call <code>malloc</code> . That&#8217;s it.</p><h3>Lifetimes</h3><p>The cognitive overhead of trying to manage object lifetimes in the programmer&#8217;s head is a cause of many bugs. When the common wisdom is to dynamically allocate each instance of an object and release the memory <em>individually,</em> there is a large surface area for mistakes.</p><p>For example, when individually allocating a bunch of nodes for a tree, performing some data transformations, then walking the tree again to release each node. It could easily be the case that some of the nodes have been detached during the transformation and are no longer accessible.</p><p>You can get around this issue by pre-allocating an array of nodes and grabbing from the pool of them - then releasing the node array all together. This <em>is</em> a custom memory allocator - called a Pool Allocator.</p><p>Today, however, we&#8217;ll be talking about a more versatile allocator - the Arena.</p><h2>The Arena</h2><p>The Arena Allocator is quite simple, and extremely useful. Since using Arenas, I have rarely needed to think about memory management at all. I have written about them before, but I wasn&#8217;t satisfied with that article&#8217;s definition as it misses some important implementation details.</p><p>Arenas work by allocating a large buffer of memory up-front and then handing out pieces of the buffer. The amount of used memory is usually tracked with an offset number.</p><p>For example, the Arena is 4096 bytes (in reality they are usually much larger) and the program allocates 2 1024 byte chunks - now the offset will be 2048. The calculated available space is the offset - size. Actually, it&#8217;s a bit more complicated due to memory alignment, but for the sake of explaining the concept, this will do. We get into the nitty gritty later on.</p><h3>Performance and Lifetimes</h3><p>Let&#8217;s say we are working on a game. We know there&#8217;s a bunch of allocations that need to happen at startup. Things like copying files into buffers (shaders, images) to upload them to the GPU and then throw them away.</p><p>In the main loop, there may be things that need to be allocated <em>each frame and then thrown away, too</em> - debug strings, collision data, the possibilities are endless.</p><p>Rather than creating set pools for all of these things, or trying to guess how many we need and using arrays, we can create a single <em>Transient Arena Allocator.</em></p><pre><code><code>// Start up code:
String my_shader_code = io_read_entire_file("my_shader.glsl", &amp;transient_allocator);
// Link the shader
// We don't care about it after this, so do nothing

// Main loop:
{
    // Some entity loop or something:
    {
        Hit hit = physics_hit(e, target, &amp;transient_allocator);
        if (hit.did_hit) {
            // do something with hit
        }
    }

    // Reset the Arena each frame
    arena_free_all(transient_allocator.context);
}
</code></code></pre><p>With this transient allocator, we have chosen to clear it at the end of each frame.</p><p>The cost of freeing the memory? Two assignment operations:</p><pre><code><code>arena_free_all(Arena *a) {
    a-&gt;offset = 0;
    a-&gt;committed = 0;
}</code></code></pre><p>What about the cost of allocating all this memory on each frame?</p><p>It&#8217;s a little more expensive, but not by much. Certainly much less expensive than <code>malloc</code> . It&#8217;s basically a couple of add and compare instructions - see more in the implementation below.</p><p>The simplicity of these allocation and deallocation calls means that we don&#8217;t have to fear allocating and freeing memory during frames.</p><p>It&#8217;s so cheap that we can do it whenever we want.</p><h3>Fragmentation and Lack of Control</h3><p>Another side-effect of using an Arena is eliminating or greatly reducing fragmentation.</p><p>If we know that we need to both dynamically allocate and use together a few different types of objects - we can put them into a separate Arena.</p><p>Say we have a bunch of Hits and a bunch of Particles, but we are unsure how many we need (if any) per frame, but we <em>are sure</em> that we&#8217;ll use data from the Hit for each Particle.</p><p>We could create another Arena Allocator called <code>hit_allocator</code> that just stores Hits and Particles. Granted, this is <em>probably not necessary</em> and the <code>transient_allocator</code> would more than suffice, but for the sake of example - we&#8217;ll say we have so many Hits and Particles that we really want them to be <em>memory local.</em></p><pre><code><code>// Main loop:
{
    // Some entity loop or something:
    {
        Hit *hit = physics_hit(e, target, &amp;hit_allocator);
        if (hit-&gt;did_hit) {
&#9;    // do something with hit
            Particle *p = particle_spawn(hit.position, &amp;hit_allocator);
        }
    }

    // Reset the Arenas each frame
    arena_free_all(transient_allocator.context);
    arena_free_all(hit_allocator.context);
}</code></code></pre><p>Something you have probably noticed is that we are now thinking about <em>collections of objects</em> and their lifetimes, rather than <em>individual objects</em>. This is a big mental switch and <em>extremely powerful</em> if you are used to <code>malloc</code> and <code>free</code> -ing objects one at a time.</p><p>Since all our Hits and Particles are in the same Arena, we know they are probably somewhere pretty close in memory. It&#8217;s not 100% optimal, but <em>we don&#8217;t need a 100% optimal solution to get a 300x speed-up</em>. We just need our data to be in the L1, L2, or L3 caches and avoid going back to main memory.</p><p>Using different Arenas for objects that have the same lifetime reduces cognitive overhead, freeing up your mind for the multitude of other problems that need solving when developing complex software.</p><h3>Who Actually Uses Arenas?</h3><p>The concept of Arenas is so useful that it&#8217;s a built-in feature in new programming languages like Odin and Jai.</p><p>I first heard about them during a stream in which Casey Muratori and Jonathan Blow were speaking about game architecture. Jonathan said, in an off-handed way, &#8220;just put them in an Arena and you&#8217;re done&#8221;. The conversation moved on, but I was now very curious. <em>What is this &#8220;Arena&#8221; that they are speaking of as if every programmer just knows what it is?</em></p><p>Soon, I found a great resource by the creator of the Odin language and started messing around with my own implementations.</p><p>After a short time, I was hooked. My programs (which were all game prototypes) were <em>much</em> easier to reason about - I haven&#8217;t seen the word &#8220;segfault&#8221; show up in my terminal since.</p><h3>Implementation</h3><p>I like to wrap my Arenas in an Allocator interface so that all my functions that require memory allocation can take any type of Allocator.</p><pre><code><code>typedef struct {
    void *(*alloc)(size_t size, void *context);
    void (*free)(size_t size, void *ptr, void *context);
    void *context;
} Allocator;</code></code></pre><p>For convenience, I like to define a couple of macros:</p><pre><code>#define make(T, n, a) ((T *)((a).alloc(sizeof(T) * n, (a).context)))
#define release(s, p, a) ((a).free(s, p, (a).context))</code></pre><p>The Arena code is fully outlined below:</p><pre><code><code>#define DEFAULT_ALIGNMENT (2 * sizeof(void *))

typedef struct {
    void *base;
    size_t size;
    size_t offset;
    size_t committed;
} Arena;

#define arena_alloc_init(a) (Allocator){arena_alloc, arena_free, a}

#define is_power_of_two(x) ((x != 0) &amp;&amp; ((x &amp; (x - 1)) == 0))

uintptr_t align_forward(uintptr_t ptr, size_t alignment) {
    uintptr_t p, a, modulo;
    if (!is_power_of_two(alignment)) {
        return 0;
    }

    p = ptr;
    a = (uintptr_t)alignment;
    modulo = p &amp; (a - 1);

    if (modulo) {
        p += a - modulo;
    }

    return p;
}

void *arena_alloc_aligned(Arena *a, size_t size, size_t alignment) {
    uintptr_t curr_ptr = (uintptr_t)a-&gt;base + (uintptr_t)a-&gt;offset;
    uintptr_t offset = align_forward(curr_ptr, alignment);
    offset -= (uintptr_t)a-&gt;base;
&#9;&#9;
    if (offset + size &gt; a-&gt;size) {
        return 0;
    }

    a-&gt;committed += size;
    void *ptr = (uint8_t *)a-&gt;base + offset;
    a-&gt;offset = offset + size;

    return ptr;
}

void *arena_alloc(size_t size, void *context) {
    if (!size) {
        return 0;
    }
    return arena_alloc_aligned((Arena *)context, size, DEFAULT_ALIGNMENT);
}

// Does nothing.
void arena_free(size_t size, void *ptr, void *context) {
    (void)ptr; (void)size; (void)context;
}

void arena_free_all(void *context) {
    Arena *a = context;
    a-&gt;offset = 0;
    a-&gt;committed = 0;
}

Arena arena_init(void *buffer, size_t size) {
    return (Arena){.base = buffer, .size = size};
}</code></code></pre><p>Given this sample code below, we can inspect the memory of the program and see how our data ends up:</p><pre><code><code>size_t size = 1024 * 1024 * 64;
void *buffer = malloc(size);
Arena arena = arena_init(buffer, size);
Allocator allocator = arena_alloc_init(&amp;arena);

int *x = make(int, 420, allocator);
size_t *y = make(size_t, 23, allocator);
char *z = make(char, 69, allocator);

for (int i = 0; i &lt; 420; i += 1) x[i] = i;
for (int i = 0; i &lt; 23; i += 1)  y[i] = (size_t)i;
for (int i = 0; i &lt; 69; i += 1)  z[i] = (char)i + '!';</code></code></pre><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!9dlI!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa5f80f86-02f1-4286-91b5-5e1f83f4c4fb_1005x790.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!9dlI!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa5f80f86-02f1-4286-91b5-5e1f83f4c4fb_1005x790.png 424w, https://substackcdn.com/image/fetch/$s_!9dlI!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa5f80f86-02f1-4286-91b5-5e1f83f4c4fb_1005x790.png 848w, https://substackcdn.com/image/fetch/$s_!9dlI!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa5f80f86-02f1-4286-91b5-5e1f83f4c4fb_1005x790.png 1272w, https://substackcdn.com/image/fetch/$s_!9dlI!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa5f80f86-02f1-4286-91b5-5e1f83f4c4fb_1005x790.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!9dlI!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa5f80f86-02f1-4286-91b5-5e1f83f4c4fb_1005x790.png" width="1005" height="790" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/a5f80f86-02f1-4286-91b5-5e1f83f4c4fb_1005x790.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:790,&quot;width&quot;:1005,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:69730,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!9dlI!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa5f80f86-02f1-4286-91b5-5e1f83f4c4fb_1005x790.png 424w, https://substackcdn.com/image/fetch/$s_!9dlI!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa5f80f86-02f1-4286-91b5-5e1f83f4c4fb_1005x790.png 848w, https://substackcdn.com/image/fetch/$s_!9dlI!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa5f80f86-02f1-4286-91b5-5e1f83f4c4fb_1005x790.png 1272w, https://substackcdn.com/image/fetch/$s_!9dlI!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa5f80f86-02f1-4286-91b5-5e1f83f4c4fb_1005x790.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>As you can see, our data is nicely located in a predictable way - good for the CPU and good for us.</p><p><em>Note: I am in the process of experimenting with adding realloc to my Allocator interface. It seems like it could be a pretty handy thing to have.</em></p><p>If you decide to or already do use Arenas in your projects, I&#8217;d love to hear about it.</p><p>If you think my implementation can be improved, please leave a comment below!</p><div><hr></div><p>If you enjoy this newsletter and want to go deeper, with more structure, I made <a href="https://programvideogames.com/?src=newsletter_0006">Program Video Games</a> for you. Transform your game ideas into working systems from first principles.</p>]]></content:encoded></item><item><title><![CDATA[Custom Strings in C]]></title><description><![CDATA[Because <string.h> is horrible.]]></description><link>https://www.bytesbeneath.com/p/custom-strings-in-c</link><guid isPermaLink="false">https://www.bytesbeneath.com/p/custom-strings-in-c</guid><dc:creator><![CDATA[Dylan Falconer]]></dc:creator><pubDate>Fri, 02 Feb 2024 17:54:55 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/3e373bcf-884f-4aea-beef-44074555a670_1280x720.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Null-terminated strings are a cause of some serious errors and increased complexity. In this article, I&#8217;ll show you how to create a simple String type that will relieve these problems.</p><h2><strong>Problem: Always Checking For &#8216;\0&#8217;</strong></h2><p>I won&#8217;t go into depth on this one. You can imagine that having to check for the terminating character adds unwanted complexity to any functions dealing with strings in C.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://www.bytesbeneath.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">The Bytes Beneath is a reader-supported publication. To receive new posts and support my work, consider becoming a free or paid subscriber.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><h2><strong>Problem: Buffer Overruns</strong></h2><p>Buffer overruns are possible due to C&#8217;s direct memory access. Let&#8217;s look at a small example program:</p><pre><code><code>#include &lt;stdio.h&gt;

static struct {
    char str[10];
    int num;
} values;

int main(void) {
    values.num = 420;
    sprintf(values.str, "%s", "0123456789abcd");
    printf("%d\n", values.num);
    return 0;
}
</code></code></pre><p>I put two variables into a struct so they don&#8217;t get re-ordered by the compiler.</p><p>The integer <em>num</em> will appear at least 10 bytes from the start of the char array <em>str</em>. Due to alignment, <em>num</em> most likely appear 12 bytes after the start of <em>str</em>.</p><p>In the image below, I have placed labels above the memory. <code>a401</code> in hexadecimal is 420 in decimal:</p><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!Jnly!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F553807da-6a3b-45fc-84fb-e0f9c3afa8f7_857x57.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!Jnly!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F553807da-6a3b-45fc-84fb-e0f9c3afa8f7_857x57.png 424w, https://substackcdn.com/image/fetch/$s_!Jnly!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F553807da-6a3b-45fc-84fb-e0f9c3afa8f7_857x57.png 848w, https://substackcdn.com/image/fetch/$s_!Jnly!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F553807da-6a3b-45fc-84fb-e0f9c3afa8f7_857x57.png 1272w, https://substackcdn.com/image/fetch/$s_!Jnly!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F553807da-6a3b-45fc-84fb-e0f9c3afa8f7_857x57.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!Jnly!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F553807da-6a3b-45fc-84fb-e0f9c3afa8f7_857x57.png" width="857" height="57" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/553807da-6a3b-45fc-84fb-e0f9c3afa8f7_857x57.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:57,&quot;width&quot;:857,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" title="" srcset="https://substackcdn.com/image/fetch/$s_!Jnly!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F553807da-6a3b-45fc-84fb-e0f9c3afa8f7_857x57.png 424w, https://substackcdn.com/image/fetch/$s_!Jnly!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F553807da-6a3b-45fc-84fb-e0f9c3afa8f7_857x57.png 848w, https://substackcdn.com/image/fetch/$s_!Jnly!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F553807da-6a3b-45fc-84fb-e0f9c3afa8f7_857x57.png 1272w, https://substackcdn.com/image/fetch/$s_!Jnly!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F553807da-6a3b-45fc-84fb-e0f9c3afa8f7_857x57.png 1456w" sizes="100vw" loading="lazy"></picture><div></div></div></a></figure></div><p>When &#8220;0123456789abcd&#8221; is is placed at the position str, we get this result:</p><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!LTl3!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0e6bd592-9722-4751-92cb-a54a4bb63b0d_857x24.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!LTl3!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0e6bd592-9722-4751-92cb-a54a4bb63b0d_857x24.png 424w, https://substackcdn.com/image/fetch/$s_!LTl3!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0e6bd592-9722-4751-92cb-a54a4bb63b0d_857x24.png 848w, https://substackcdn.com/image/fetch/$s_!LTl3!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0e6bd592-9722-4751-92cb-a54a4bb63b0d_857x24.png 1272w, https://substackcdn.com/image/fetch/$s_!LTl3!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0e6bd592-9722-4751-92cb-a54a4bb63b0d_857x24.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!LTl3!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0e6bd592-9722-4751-92cb-a54a4bb63b0d_857x24.png" width="857" height="24" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/0e6bd592-9722-4751-92cb-a54a4bb63b0d_857x24.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:24,&quot;width&quot;:857,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" title="" srcset="https://substackcdn.com/image/fetch/$s_!LTl3!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0e6bd592-9722-4751-92cb-a54a4bb63b0d_857x24.png 424w, https://substackcdn.com/image/fetch/$s_!LTl3!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0e6bd592-9722-4751-92cb-a54a4bb63b0d_857x24.png 848w, https://substackcdn.com/image/fetch/$s_!LTl3!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0e6bd592-9722-4751-92cb-a54a4bb63b0d_857x24.png 1272w, https://substackcdn.com/image/fetch/$s_!LTl3!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0e6bd592-9722-4751-92cb-a54a4bb63b0d_857x24.png 1456w" sizes="100vw" loading="lazy"></picture><div></div></div></a></figure></div><p>As you can see, we have clobbered the first 2 bytes of <em>num</em>. We have inadvertently modified the value to be <code>6364</code> in hexadecimal which is 25699 in decimal.</p><p>This can happen with any pointer, though it&#8217;s a more common bug with regular strings due to the way we use textual data. In inserting into and formatting strings, we increase the likelihood of this class of bug.</p><p>This program doesn&#8217;t give any warnings, by the way. Even on the most strict warning settings (-Wall -Wextra -pedantic).</p><p>To Microsoft&#8217;s credit, they do warn the user about using sprintf. The MSVC compiler will recommend using sprintf_s. sprintf_s has the buffer length as one of the parameters. There is also snprintf, which provides the same protection - though differs in the return type.</p><p>This memory overwriting issue is not solved by just using these functions. For example, what about if we want to iterate through some data and add something from it to a string? I&#8217;ll show a brief example:</p><pre><code><code>char *my_str = malloc(10);
char *my_data = malloc(5);
sprintf(my_data, "%s", "012345");

for (int i = 0; i &lt; 100; i += 1) {
    my_str[i] = 0x77;
}</code></code></pre><p>In my testing, my_str appears in memory shortly before my_data.</p><p>As with the sprintf example, my_str is overwritten with bytes of 0x77.</p><h2><strong>Solution: The Custom String Type</strong></h2><p>The String type is defined as such:</p><pre><code><code>typedef struct {
    size_t len;
    char *data;
} String;</code></code></pre><p>In my version, we keep the null-terminator, and we also store the length. This allows us to create String functions that are compatible with:<br>- C Standard Library<br>- Any 3rd Party libraries that expect a null-terminated char array.</p><p>Before we dive into the implementation, let&#8217;s review some usage code:</p><pre><code><code>Allocator a = ...;

String s1 = String("This is my cool string.");
String s2 = String("And here is another one.");

String cs = str_concat(s1, s1, &amp;a); 

printf("'%s'\n", cs.data);
// =&gt; 'This is my cool string.And here is another one.'

String *split = str_split(s1, String(" "), &amp;a);

size_t len = array_length(split);
for (size_t i = 0; i &lt; len; i += 1) {
    printf("'%s'\n", split[i].data);
}
// =&gt; 'This'
// =&gt; 'is'
// =&gt; 'my'
// =&gt; 'cool'
// =&gt; 'string.'

String joined = str_join(split, String(","), &amp;a);
printf("'%s'\n", joined.data);
// =&gt; 'This,is,my,cool,string.'

String view = str_substring_view(s1, String("my"), &amp;a);
printf("'%s'\n", view.data);
// =&gt; 'my cool string.'
// As views don't alter or clone, the printf will not terminate correctly.</code></code></pre><p>There&#8217;s much more you can do, but we&#8217;ll leave it there for now.</p><p>As you can see, our string usage is <em>much</em> simpler than the standard C string functions.</p><p>No weird strtok loop to split a string. Easily join strings together. Great.</p><p>Below is a simple and not necessarily great (it&#8217;s a first-pass) implementation:</p><pre><code><code>typedef struct {
    size_t len;
    char *data;
} String;

#define String(x) (String){strlen(x), x}

String str_init(size_t len, Allocator *a) {
    String s = {
        .len = len,
        .data = a-&gt;alloc(len + 1, a-&gt;context),
    };
    s.data[len] = 0;
    return s;
}

String str_concat(String s1, String s2, Allocator *a) {
    size_t len = s1.len + s2.len;
    String s = str_init(len, a);
    memcpy(s.data, s1.data, s1.len);
    memcpy(&amp;s.data[s1.len], s2.data, s2.len);
    return s;
}

String str_substring(String s, size_t start, size_t end, Allocator *a) {
    String r = {0};
    if (end &lt;= s.len &amp;&amp; start &lt; end) {
        r = str_init(end - start, a);
        memcpy(r.data, &amp;s.data[start], r.len);
    }
    return r;
}

bool str_contains(String haystack, String needle) {
    bool found = false;
    for (size_t i = 0, j = 0; i &lt; haystack.len &amp;&amp; !found; i += 1) {
        while (haystack.data[i] == needle.data[j]) {
            j += 1;
            i += 1;
            if (j == needle.len) {
                found = true;
                break;
            }
        }
    }
    return found;
}

size_t str_index_of(String haystack, String needle) {
    for (size_t i = 0; i &lt; haystack.len; i += 1) {
        size_t j = 0;
        size_t start = i;
        while (haystack.data[i] == needle.data[j]) {
            j += 1;
            i += 1;
            if (j == needle.len) {
                return start;
            }
        }
    }
    return (size_t)-1;
}

// NOTE: this does not terminate the string with a 0 as that would destroy the original string.
String str_substring_view(String haystack, String needle) {
    String r = {0};
    size_t start_index = str_index_of(haystack, needle);
    if (start_index &lt; haystack.len) {
        r.data = &amp;haystack.data[start_index];
        r.len = needle.len;
    }
    return r;
}

bool str_equal(String a, String b) {
    if (a.len != b.len) {
        return false;
    }
    return memcmp(a.data, b.data, a.len) == 0;
}

String str_replace(String s, String match, String replacement, Allocator *a) {
    String r = {0};
    // TODO
    return r;
}

String str_view(String s, size_t start, size_t end) {
    if (end &lt; start || end - start &gt; s.len) {
        return (String){0};
    }
    return (String){end - start, s.data + start};
}

String str_clone(String s, Allocator *a) {
    String r = {0};
    if (s.len) {
        r.data = a-&gt;alloc(s.len, a-&gt;context);
        r.len = s.len;
        memcpy(r.data, s.data, s.len);
    }
    return r;
}

String *str_split(String s, String delimiter, Allocator *a) {
    String *arr = 0;
    size_t start = 0;
    for (size_t i = 0; i &lt; s.len; i += 1) {
        if (s.data[i] != delimiter.data[0]) {
            continue;
        }

        if (memcmp(&amp;s.data[i], delimiter.data, delimiter.len) == 0) {
            // Allocate array if we haven't yet.
            if (!arr) {
                arr = array(String, a);
            }

            // Clone the substring before the delimiter.
            size_t end = i;
            String cloned = str_substring(s, start, end, a);
            array_append(arr, cloned);
            start = end + delimiter.len;
        }
    }
    // Get the last segment.
    if (start + delimiter.len &lt; s.len) {
        String cloned = str_substring(s, start, s.len, a);
        array_append(arr, cloned);
    }
    return arr;
}

String *str_split_view(String s, String delimiter, Allocator *a) {
    String *arr = 0;
    size_t start = 0;
    for (size_t i = 0; i &lt; s.len; i += 1) {
        if (s.data[i] != delimiter.data[0]) {
            continue;
        }

        if (memcmp(&amp;s.data[i], delimiter.data, delimiter.len) == 0) {
            if (!arr) {
                arr = array(String, a);
            }

            size_t end = i;
            String view = str_view(s, start, end);
            array_append(arr, view);
            start = end + delimiter.len;
        }
    }
    if (start + delimiter.len &lt; s.len) {
        String view = str_view(s, start, s.len);
        array_append(arr, view);
    }
    return arr;
}

String str_join(String *s, String join, Allocator *a) {
    ArrayHeader *h = array_header(s);

    size_t total_length = s-&gt;len * join.len;
    for (size_t i = 0; i &lt; h-&gt;len; i += 1) {
        total_length += s[i].len;
    }

    char *mem = a-&gt;alloc(total_length + 1, a-&gt;context);
    size_t offset = 0;
    for (size_t i = 0; i &lt; h-&gt;len; i += 1) {
        memcpy(&amp;mem[offset], s[i].data, s[i].len);
        offset += s[i].len;

        if (i == h-&gt;len - 1) {
            break;
        }

        memcpy(&amp;mem[offset], join.data, join.len);
        offset += join.len;
    }

    mem[total_length] = 0;

    return (String){total_length, mem};
}</code></code></pre><div><hr></div><p><em>Hello there, dear reader. I&#8217;ll write about tools like ASan and UBSan in the future.</em></p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://www.bytesbeneath.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe now&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://www.bytesbeneath.com/subscribe?"><span>Subscribe now</span></a></p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://www.bytesbeneath.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">The Bytes Beneath is a reader-supported publication. To receive new posts and support my work, consider becoming a free or paid subscriber.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div>]]></content:encoded></item><item><title><![CDATA[Dynamic Arrays in C]]></title><description><![CDATA[The most useful data-structure, in C]]></description><link>https://www.bytesbeneath.com/p/dynamic-arrays-in-c</link><guid isPermaLink="false">https://www.bytesbeneath.com/p/dynamic-arrays-in-c</guid><dc:creator><![CDATA[Dylan Falconer]]></dc:creator><pubDate>Thu, 18 Jan 2024 10:44:56 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/b2a64665-6750-4582-ab19-ca2f2f00fff2_1285x1285.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>There is no doubt that the dynamic array is the most useful data structure. It's the go-to for most programmers until they may need something more specialised because they are fast and simple.</p><p>Some algorithms require an unknown number of values to be stored, either temporarily during the running of algorithm, or as the result. In cases like this, the caller of the algorithm must either know the upper bound and allocate an array with enough space, or use a dynamic array that can grow in size as required.</p><p>More recent languages almost universally deal with this problem for the programmer. Some implementations are more egregious than others. Here are just two examples of ones you may have seen.</p><p>JavaScript:</p><pre><code>// Declare an array
const my_array = []

// Assign a value to any index
my_array[69] = 420

// Now the array has a length of 70
// The first 69 items are empty</code></pre><p>C++:</p><pre><code>std::vector&lt;int&gt; my_array;

for (int i = 0; i &lt; 70; i += 1) {
    my_array.push_back(i);
}

// [0,1,2,3,4,5..69]</code></pre><p>As you can see, in both these examples, we did not have to specify the size of the array. In C, however, something like this is not a language feature nor part of the standard library. There is a concept called a Variable Length Array, however these are not quite the same and as of C11 are only optionally included in the spec, so they are not recommended.</p><h2>Implementation</h2><p>I'll skip the cruft and get straight to how to implement one in C. We'll use a a combination of a trick I learnt from Sean Barrett, the creator of the stb libraries, and custom memory allocators.</p><p>The trick is that we allocate space for the array plus some metadata. In our case, we'll define a header struct to store the metadata.</p><pre><code>typedef struct {
    size_t length;
    size_t capacity;
    size_t padding_or_something; // prefer 16-byte alignment
    Allocator *a;
} Array_Header;</code></pre><p>The padding field here is added so that the size of the header is 32 bytes. This is important because unaligned data causes the CPU to do more work to read from and write to the memory.</p><p>On x64, we want mostly 16-byte aligned data. There are some exceptions to this: SIMD (Single Instruction Multiple Data) types - specifically any SIMD types that are larger than 128 bits.</p><p>We won&#8217;t go into more details here, but I have confidence that if you are looking to exploit SIMD, then forward-aligning your dynamic array through either the allocator or the header definition should be no problem for you.</p><p>The Allocator type is quite simple, and will be expanded upon in further articles. For now, I'll show the structure and suggested values to get started without worrying too much.</p><pre><code>typedef struct {
    void *(*alloc)(size_t bytes, void *context);
    void *(*free)(size_t bytes, void *ptr, void *context);
    void *context;
} Allocator;</code></pre><p>A simple setup without custom allocators:</p><pre><code>void *my_alloc(size_t bytes, void *context) {
    (void)context;
    return malloc(bytes);
}

void *my_free(size_t bytes, void *ptr, void *context) {
    (void)ptr; (void)context;
    free(ptr);
}

Allocator my_allocator = {my_alloc, my_free, 0};</code></pre><p>This may all seem a bit redundant when you can just stick a malloc directly into the code we're about to write - but custom allocators are one of the most powerful concepts that a programmer can learn. As our machines are generally cache-bound, using the correct memory allocation strategy for the problem is very important. At the very least, we can simply make our code not slow, and it'll beat 99% of the stuff out there.</p><div class="pullquote"><p>"How is your program so fast?"<br>"I didn't make it slow."</p></div><p>First, I'll show you the usage code:</p><pre><code>int *my_array = array(int, my_allocator);
array_append(my_array, 69);
array_append(my_array, 420);
// ...</code></pre><p>That looks pretty simple - and we get an array type back (a pointer to a type in C) so we can pass it to any functions that expect that - nice.</p><p>We'll need some way to initialise a dynamic array. Since C doesn&#8217;t have a <em><strong>type</strong></em><strong> </strong>type, we&#8217;ll have to use a macro here.</p><pre><code>#define ARRAY_INITIAL_CAPACITY 16

#define array(T, a) array_init(sizeof(T), ARRAY_INITIAL_CAPACITY, a)

void *array_init(size_t item_size, size_t capacity, Allocator *a) {
    void *ptr = 0;
    size_t size = item_size * capacity + sizeof(Array_Header);
    Array_Header *h = a-&gt;alloc(size);

    if (h) {
        h-&gt;capacity = capacity;
        h-&gt;length = 0;
        h-&gt;a = a;
        ptr = h + 1;
    }

    return ptr;
}</code></pre><p>What we have done here is allocate enough space for our initial array size plus the metadata <em><strong>Array_Header</strong></em>. We then increment <em><strong>h</strong></em> by 1 which gives the address the array items start at.</p><p>Now we have our array, how can we add items to it? Well, we know it has space for 16 items, so we <em>could</em> just use the subscript operator directly:</p><pre><code>my_array[0] = 23;</code></pre><p>However, in this case the metadata is not updated - so the length is still 0. Let&#8217;s add a macro to append items.</p><pre><code>#define array_append(a, v) ( \
    (a) = array_ensure_capacity(a, 1, sizeof(v)), \
    (a)[array_header(a)-&gt;length] = (v), \
    &amp;(a)[array_header(a)-&gt;length++])</code></pre><p>Admittedly, this is a bit tough to follow, so let&#8217;s break it down.</p><p>The parameters <em><strong>a</strong></em> and <em><strong>v</strong></em> refer to the array and the value (not pointers), respectively.</p><p>We wrap the entire macro in parentheses with statements separated by commas. This is intentional, and a little (as far as I&#8217;ve seen) used language feature of C. The comma operator will evaluate each item separated by commas from left to right and resolve to the rightmost one. All but the result of the final expression will be discarded.</p><p>There&#8217;s a lot of parentheses in these macros because I have been burnt by not using them enough. It may be overkill, but since there&#8217;s no type checking and you can&#8217;t really step through the C in a debugger unless you do a partial compilation, I prefer to err on the side of caution.</p><p>So in our code above, we assign the array variable <em><strong>a</strong></em> to the result of <em><strong>array_ensure_capacity</strong></em> (we will fill this out shortly). Then, we assign the value using another macro we have yet to define: <em><strong>array_header</strong></em>. Using this macro we get a pointer to the metadata and then get the <em><strong>length</strong></em> field. That gives us the next available slot in the array which we assign <em><strong>v</strong></em> to.</p><p>Finally, we get a pointer to the place we just put <em><strong>v</strong></em> and postfix increment the length at the same time. So our macro does 3 things:</p><ol><li><p>Ensure there is enough space to put the value.</p></li><li><p>Set the value.</p></li><li><p>Increment the length.</p></li></ol><p>Returning a pointer to the new item is nice when you want to do something with the inserted value right away.</p><p>Ensuring the capacity is quite a long function (not a macro this time!). I&#8217;ll present it in its entirety:</p><pre><code>void *array_ensure_capacity(void *a, size_t item_count, size_t item_size) {
    Array_Header *h = array_header(a);
    size_t desired_capacity = h-&gt;length + item_count;

    if (h-&gt;capacity &lt; desired_capacity) {
        size_t new_capacity = h-&gt;capacity * 2;
        while (new_capacity &lt; desired_capacity) {
            new_capacity *= 2;
        }

        size_t new_size = sizeof(Array_Header) + new_capacity * item_size;
        Array_Header *new_h = h-&gt;a-&gt;alloc(new_size, h-&gt;a-&gt;context);

        if (new_h) {
            size_t old_size = sizeof(*h) + h-&gt;length * item_size;
            memcpy(new_h, h, old_size);

            if (h-&gt;a-&gt;free) {
                h-&gt;a-&gt;free(old_size, h, h-&gt;a-&gt;context);
            }

            new_h-&gt;capacity = new_capacity;
            h = new_h + 1;
        } else {
            h = 0;
        }
    } else { h += 1; }

    return h;
}</code></pre><p>Despite the fact that this function is named &#8220;ensure capacity&#8221;, if the memory allocation fails, we assign the array to 0 and assume this is a critical error. In the <em><strong>array_append</strong></em> macro, the next line will cause a crash when we try to write to <em><strong>0x0[array_header&#8594;length]</strong></em>.</p><p>Here are some convenience macros:</p><pre><code>#define array_header(a) ((Array_Header *)(a) - 1)
#define array_length(a) (array_header(a)-&gt;length)
#define array_capacity(a) (array_header(a)-&gt;capacity)</code></pre><p>I rarely use <em><strong>remove</strong></em>, and even even more rarely use something like <em><strong>pop_back</strong></em>, but for the sake of completion I&#8217;ll add the implementations below:</p><pre><code>#define array_remove(a, i) do { \
    Array_Header *h = array_header(a); \
    if (i == h-&gt;length - 1) { \
        h-&gt;length -= 1; \
    } else if (h-&gt;length &gt; 1) { \
        void *ptr = &amp;a[i]; \
        void *last = &amp;a[h-&gt;length - 1]; \
        h-&gt;length -= 1; \
        memcpy(ptr, last, sizeof(*a)); \
    } \
} while (0);

#define array_pop_back(a) (array_header(a)-&gt;length -= 1)</code></pre><p>That&#8217;s it - if there&#8217;s something you would like to add, please do so in the comments!</p><div><hr></div><p>2024/01/24:</p><ul><li><p>Fixed Array_Header struct definition so that it is 32-bytes. This will fix alignment issues.</p></li><li><p>Added small section on alignment.</p></li></ul><div><hr></div><p>If you enjoy this newsletter and want to go deeper, with more structure, I made <a href="https://programvideogames.com/?src=newsletter_0004">Program Video Games</a> for you. Transform your game ideas into working systems from first principles.</p>]]></content:encoded></item><item><title><![CDATA[Unity: The Hidden Costs of "Free" 3rd Party Software]]></title><description><![CDATA[The recent pricing structure change by Unity has captured the attention both within and without the video game industry.]]></description><link>https://www.bytesbeneath.com/p/unity-the-hidden-costs-of-free-3rd</link><guid isPermaLink="false">https://www.bytesbeneath.com/p/unity-the-hidden-costs-of-free-3rd</guid><dc:creator><![CDATA[Dylan Falconer]]></dc:creator><pubDate>Wed, 20 Sep 2023 14:25:11 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!G6W0!,w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F410b2934-9526-41e2-b517-4180b6d92282_500x500.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>The recent pricing structure change by Unity has captured the attention both within and without the video game industry. They are proposing a per-install fee for games both existing and published in the future - adding a retroactive tax to games that have long since been published. While many are rightfully concentrating on the specifics of Unity&#8217;s modifications - especially those with games in development or already released using the engine - I am seeing a critical risk often discussed in entrepreneurial circles: an over-reliance on a third party.</p><h2>The Passion Trap</h2><p>Indie game developers are largely driven by passion - this is a double-edged sword. Due to this passion, we fail to consider that what we are doing by creating and (hopefully) releasing a game which people will pay money for is <em>starting a business</em>.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://www.bytesbeneath.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading The Bytes Beneath! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><p>It seems as though Unity has relied on this passion and their arguably intuitive game engine to gain a very strong position in the market. They are now abusing this position to extort their user-base.</p><p>This is not the first time something like this has happened, and it won&#8217;t be the last. To understand the potential consequences of Unity's decision, let's look back at another dominant platform: Adobe's Flash.</p><h2>The Rise and Fall of Flash</h2><p>In 1998, Adobe&#8217;s Flash was the go-to choice for website builders wanting to add some flair - standard web capabilities were pretty limited back then. Entire interactive websites were made in Flash and pretty soon the Newgrounds website would give birth to the Flash games scene.</p><p>Newgrounds boasted 18 million unique visitors a month at its peak and was a major hub for Flash games and animations. By 2005, top Flash game developers could earn up to six figures in sponsorship deals for a single game.</p><p>In 2010, Steve Jobs wrote an open letter criticising Flash for multiple reasons, one of which being that it didn&#8217;t port well to mobile devices.</p><p>In 2011, Adobe pivoted to HTML5, though it would still take almost a decade before browsers started dropping support for Flash entirely.</p><p>Though it took a long time for the sun to set on Flash, there were web designers with careers built on this proprietary tool that could no longer find work. Businesses stopped using Flash, afraid that anything made in it would be unusable within a few years.</p><h2>The Real Cost of Free</h2><p>I don&#8217;t think anybody expected that this kind of pricing change would happen - but people did expect <em>something</em> to change with Unity&#8217;s pricing model. The argument that Unity may one day be unavailable and all the Unity-based game developers would be in a position of having skills in something that isn&#8217;t easily transferred seemed hyperbolic just a couple of weeks ago. That is, unless you looked at previous examples of things like this happening - such as <a href="https://news.ycombinator.com/item?id=32300785">The Machinery game engine</a> - a game engine that revoked their license to all developers using it and gave them 14 days to delete all source code (including their in-progress games).</p><p>The situations are different, and the mechanism is the same: updating the EULA to the detriment of the user-base. Whether this is legal is a matter for the courts - the message is clear: &#8220;we don&#8217;t give a damn about our users&#8221;.</p><p>Some developers have spent a decade learning the ins and outs of Unity and are now looking at migrating to another tool. Some developers with games in development are considering full rewrites in other engines like Godot.</p><p>Unfortunately, most people don&#8217;t think about these scenarios until they are forced to - the result is, unfortunately, some pretty dire circumstances.</p><h2>Everything is a Trade-off</h2><p>There is always a trade-off with every decision - by doing X, you cannot do anything that isn&#8217;t X. By learning Unity, you cannot spend that time learning how to create your own tools, or how to use other tools.</p><p>In the vast expanse of software development, the allure of pre-made solutions often blinds us to the pitfalls of becoming too comfortable. Unity's recent shock move only underscores a fundamental principle: Relying heavily on a single tool, no matter its reputation, can backfire spectacularly. The pitfalls of proprietary platforms remind us to spread our dependencies and stay adaptable in our craft.</p><p>Doing a bit of research into the plethora of free (as in liberty) software libraries and tools out there may save you and your business from ruinous circumstances.</p><p>I have a video that touches on how one may go about doing this available here: <a href="https://www.youtube.com/watch?v=WHyzOMYOhio">Unity Game Ends Themselves - A different take</a>.</p><h2>The Take-Away</h2><p>Remember, if your business (game dev is a business) is reliant on a 3rd party and you have no contingency plan then your business is at that 3rd party&#8217;s mercy. If something happens to them, or they decide not to work with you anymore, your business could be ruined overnight.</p><div><hr></div><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://www.bytesbeneath.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading The Bytes Beneath! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><p>If you enjoyed this post, please consider subscribing. Thank you for reading!</p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://www.bytesbeneath.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe now&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://www.bytesbeneath.com/subscribe?"><span>Subscribe now</span></a></p>]]></content:encoded></item><item><title><![CDATA[Reinventing the wheel is a waste of time - Programming Lies]]></title><description><![CDATA[Preface]]></description><link>https://www.bytesbeneath.com/p/reinventing-the-wheel-is-a-waste</link><guid isPermaLink="false">https://www.bytesbeneath.com/p/reinventing-the-wheel-is-a-waste</guid><dc:creator><![CDATA[Dylan Falconer]]></dc:creator><pubDate>Wed, 13 Sep 2023 14:25:18 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!CaCP!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Febde2cd4-ef0a-4089-90aa-0e322e97ec5b_1382x524.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2>Preface</h2><p>As you may or may not know, I offer a free game engine programming course on my YouTube channel: <a href="https://www.youtube.com/playlist?list=PLYokS5qr7lSsvgemrTwMSrQsdk4BRqJU6">C Game + Engine from Scratch</a>. The most common question I get in the comments is &#8220;Why not C++?&#8221; followed by &#8220;Why reinvent the wheel?&#8221; or &#8220;Why not use Unity?&#8221; etc. I have <a href="https://youtu.be/Qqx3HsTZW2E?si=F7T24W6hJ7XMnpz2">outlined my reasons</a> a <a href="https://youtu.be/5i0HpeHZgVk?si=-gfuTlHQBtNrTG_U">couple of times</a> in videos, but let&#8217;s add some more context.</p><h2>Introduction</h2><p>A myriad of confused programmers - either through poor education, lack of experience, or indoctrination into one or more of the various gratuitous acronym-based programming cults - find themselves believing misconceptions about their chosen vocation.</p><p>An ostensible pearl of wisdom proffered by thought-leaders and cult-leaders alike is the phrase &#8220;don&#8217;t reinvent the wheel&#8221;. Occasionally, one may have the inclination to investigate the bytes beneath the tortuous tower-of-abstractions - the very tower upon which so much of our modern day programs are built upon - the &#8220;tech stack&#8221;.</p><p>The curious programmer, upon further investigation, may decide to delve into the labyrinthine halls of the tower, perhaps glimpsing a peek of the vaults below. He may experiment there - probing and perhaps even modifying some of the code.</p><p>"Don't reinvent the wheel!" exclaims the indoctrinated programmer, compelled as if he was a CPU executing a stream of instructions.</p><p>Ridiculed and covered in bile, the curious programmer must be restored to their place on the assembly line of programmed automatons produced by modern &#8220;educational&#8221; institutions.</p><p>The supposition of the phrase makes clear, whether directly or by common inference, certain demonstrably false assumptions about programming.</p><ol><li><p>The wheel is perfect.</p></li><li><p>Problem X already has a wheel.</p></li></ol><h2>The Wheel Is Perfect</h2><p>The wheel, as we know it at in the present, is an incredible device that appears to be perfect at converting torque into linear motion. However, this depends on the context in which it&#8217;s applied.</p><p>For variations of context, we have variations of wheels. If the wheel was already perfect, we may still be using the solid stone wheels invented in Mesopotamia over 6000 years ago.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!CaCP!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Febde2cd4-ef0a-4089-90aa-0e322e97ec5b_1382x524.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!CaCP!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Febde2cd4-ef0a-4089-90aa-0e322e97ec5b_1382x524.png 424w, https://substackcdn.com/image/fetch/$s_!CaCP!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Febde2cd4-ef0a-4089-90aa-0e322e97ec5b_1382x524.png 848w, https://substackcdn.com/image/fetch/$s_!CaCP!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Febde2cd4-ef0a-4089-90aa-0e322e97ec5b_1382x524.png 1272w, https://substackcdn.com/image/fetch/$s_!CaCP!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Febde2cd4-ef0a-4089-90aa-0e322e97ec5b_1382x524.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!CaCP!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Febde2cd4-ef0a-4089-90aa-0e322e97ec5b_1382x524.png" width="1382" height="524" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/ebde2cd4-ef0a-4089-90aa-0e322e97ec5b_1382x524.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:524,&quot;width&quot;:1382,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;Timeline of the Wheel: History and Invention - Malevus&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="Timeline of the Wheel: History and Invention - Malevus" title="Timeline of the Wheel: History and Invention - Malevus" srcset="https://substackcdn.com/image/fetch/$s_!CaCP!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Febde2cd4-ef0a-4089-90aa-0e322e97ec5b_1382x524.png 424w, https://substackcdn.com/image/fetch/$s_!CaCP!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Febde2cd4-ef0a-4089-90aa-0e322e97ec5b_1382x524.png 848w, https://substackcdn.com/image/fetch/$s_!CaCP!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Febde2cd4-ef0a-4089-90aa-0e322e97ec5b_1382x524.png 1272w, https://substackcdn.com/image/fetch/$s_!CaCP!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Febde2cd4-ef0a-4089-90aa-0e322e97ec5b_1382x524.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">History of the Wheel. Source: malevus.com</figcaption></figure></div><p>The design of a wheel suitable for smooth asphalt is different from one designed for rugged mountain trails. If you need to go through the snow, snow chains or studded tires are required to provide extra traction. In deserts, wide wheels are used to prevent vehicles from sinking into the sand.</p><p>There&#8217;s functional aspects to consider as well. Truck wheels are larger and more robust because of the weight they carry.</p><p>I won&#8217;t belabour the point - wheels have been reinvented for different contexts for thousands of years.</p><h2>Problem X Already Has a Wheel</h2><p>Let&#8217;s talk about game engines. Unity, Unreal, and Godot are the big 3 game engines. They have advanced toolkits that make them appear to be the ideal solution for most games that you may want to make - and that&#8217;s true.</p><p>However, these game engines are certainly not the best solutions for all problems. Any game design that falls outside their general use-case will have the programmers and designers fighting the engine trying to get things to work.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!kqkg!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F55f45260-c012-4840-b7cc-1600e11c6dd1_2880x1800.jpeg" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!kqkg!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F55f45260-c012-4840-b7cc-1600e11c6dd1_2880x1800.jpeg 424w, https://substackcdn.com/image/fetch/$s_!kqkg!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F55f45260-c012-4840-b7cc-1600e11c6dd1_2880x1800.jpeg 848w, https://substackcdn.com/image/fetch/$s_!kqkg!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F55f45260-c012-4840-b7cc-1600e11c6dd1_2880x1800.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!kqkg!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F55f45260-c012-4840-b7cc-1600e11c6dd1_2880x1800.jpeg 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!kqkg!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F55f45260-c012-4840-b7cc-1600e11c6dd1_2880x1800.jpeg" width="1456" height="910" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/55f45260-c012-4840-b7cc-1600e11c6dd1_2880x1800.jpeg&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:910,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;CDN media&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="CDN media" title="CDN media" srcset="https://substackcdn.com/image/fetch/$s_!kqkg!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F55f45260-c012-4840-b7cc-1600e11c6dd1_2880x1800.jpeg 424w, https://substackcdn.com/image/fetch/$s_!kqkg!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F55f45260-c012-4840-b7cc-1600e11c6dd1_2880x1800.jpeg 848w, https://substackcdn.com/image/fetch/$s_!kqkg!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F55f45260-c012-4840-b7cc-1600e11c6dd1_2880x1800.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!kqkg!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F55f45260-c012-4840-b7cc-1600e11c6dd1_2880x1800.jpeg 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">14 FPS in a 2D game. Source: reddit</figcaption></figure></div><p>Look at Oxygen Not Included as an example. It&#8217;s one of my favourite games of all time. It has <em>horrible performance issues</em> if you play one save file for too long. The trade-off for Klei choosing Unity is that the world size has to be small to mitigate the lag - and they still can&#8217;t mitigate the lag.</p><p>For a new game developer, the big 3 may seem like the only viable options. They are popular and have extensive resources available to learn them. As I always say, this may be true for you and your games - but it&#8217;s not true for everyone.</p><p>Someone has to create the next Unity. Someone has to create something bespoke that the big 3 just cannot do well enough. That someone may be you.</p><h2>When The Big 3 Don&#8217;t Fit</h2><p>The big 3 are designed to fit the general case - they must cater to a wide variety of genres and platforms. This is fantastic if your game fits within the scopes of these engines, and this broad scope means that they are inherently generic. For some projects, overheads of these engines - in terms of memory usage, performance costs, or even learning curve - can be too much.</p><p>Moreover, when you use a pre-built engine, you're also confined to its architecture and limitations. While these engines provide flexibility, there's a ceiling to how much you can modify or extend their core. For games requiring a unique mechanic, more simulation computation - as with Oxygen Not Included - or a different approach to rendering, this can become a significant barrier.</p><h2>You Don&#8217;t Have to Build Everything</h2><p>There are many battle-tested libraries available in a swathe of languages. If you cannot find something native to the language you choose - you can probably find a C version and most common languages have C interoperability.</p><p>Rendering engines like Magnum and bgfx allow you to draw complex scenes with all the features you could want. Physics libraries like Bullet and qu3e have most of the features you could want. These are just 2 examples of 2 areas that make up a game engine - there are many.</p><p>What&#8217;s the difference between using an engine and using a bunch of libraries? You control how these systems talk to each other. If you really need to, you can  create custom solutions for the missing pieces - if nothing you find fits.</p><h2>Conclusion</h2><p>The phrase &#8220;don&#8217;t reinvent the wheel&#8221; holds merit in many scenarios. However, as I hope to have demonstrated by this article, there are many situations in which the wheel required reinvention. The context is vitally important.</p><p>I have been talking about games and game engines because that is what most interest me. However, you can extend this logic to all of programming. I don&#8217;t think we are close to finding a wheel in nearly any programming domain.</p><p>The next time someone questions the decision to build something from scratch - remember that without understanding, there can be no innovation. If we always stuck with the status quo, we&#8217;d still be riding on stone wheels.</p><div><hr></div><p>If you enjoy this newsletter and want to go deeper, with more structure, I made <a href="https://programvideogames.com/?src=newsletter_0002">Program Video Games</a> for you. Transform your game ideas into working systems from first principles.</p>]]></content:encoded></item><item><title><![CDATA[Memory management is too hard for you - Programming Lies]]></title><description><![CDATA[From the Ivory Tower, the Street seems too clumsy to juggle ideas.]]></description><link>https://www.bytesbeneath.com/p/manual-memory-management-is-too-hard</link><guid isPermaLink="false">https://www.bytesbeneath.com/p/manual-memory-management-is-too-hard</guid><dc:creator><![CDATA[Dylan Falconer]]></dc:creator><pubDate>Thu, 07 Sep 2023 13:47:47 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!Ni1-!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F38e6d267-ce78-45b2-83d0-d9a4f508ace5_945x534.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>If you hang around in the online spaces where programmers congregate for even a short time, you will hear a particular sentiment about manual memory management echoed by most. That is, &#8220;manual memory management is too difficult for mere mortals; it&#8217;s too error prone, and even the greybeards get it wrong&#8221;.</p><p>There are multi-decade projects built specifically to obscure the memory from the programmer - most commonly used programming languages use garbage collection rather than allow the programmer direct control of memory allocations. It&#8217;s understandable that the first instinct is to disallow certain actions - similar to how when there are small children in the house you would lock away the knives and cover the power points.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://www.bytesbeneath.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">The Bytes Beneath is a reader-supported publication. To receive new posts and support my work, consider becoming a free or paid subscriber.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><p>However, unlike with children, programmers are told not to grow and learn to take their own risks - they are kept wrapped in bubble wrap, unable to hurt themselves, severely limiting their movement and retarding their development.</p><h2>The Teaching Problem</h2><p>When learning about data structures such as linked lists, you were probably taught to allocate and free using malloc and free directly. Whether learning from a textbook or online, the examples commonly found are something akin to this:</p><pre><code>Node* push(LinkedList* list, int data) {
    Node* new_node = (Node*)malloc(sizeof(Node));
    new_node-&gt;data = new_data;
    new_node-&gt;next = (list-&gt;head);
    list-&gt;head = new_node;
    return new_node;
}</code></pre><p><code>malloc</code> is seen as a mystical operation that provides memory when either the stack won&#8217;t suffice or control over the memory&#8217;s lifetime is required. Depending on the path you have taken on your programming journey, this may be the extent of your understanding.</p><p>As with all things, there are trade-offs to this way of teaching. On the one hand, it's easier to teach about the data structure itself without delving into the intricacies of how malloc works or considering alternative memory allocation methods. On the other hand, linked lists are often criticised as slow and fragmented because proper memory allocation wasn't utilised from the start. Ironically, malloc/free's internal implementation uses a linked list to denote free blocks of memory.</p><h2>Carrying It Forward</h2><p>Due to the aforementioned learning process, many programmers reach for malloc early and often. This leads to bugs as the lifetimes of data are ignored or thought of in fragmented ways. Human error is a reasonable assumption when creating complex systems. As a result, manual memory management is blamed, rather than the lack of any process that would mitigate the bugs.</p><p>Enter garbage collection, smart pointers, reference counting, etc. - all designed to prevent programmers shooting themselves in the foot. And, all with overhead that means more CPU cycles used, which means more money spent and more electricity wasted.</p><h2>Garbage Collection</h2><p>The two reasons most often given for using a garbage collected language are: <strong>program safety</strong> and <strong>programmer productivity</strong>. One of these claims is false and the other is hard to quantify and also probably false.</p><h3>Program Safety</h3><p>One commonly cited program safety issue that arises with manual memory management is <em>dangling pointers</em> - they are pointers that no longer point to valid memory blocks.</p><p>There are a few ways to create a dangling pointer, but for the sake of simplicity, here is a classic <em>use after free</em> error.</p><pre><code>#include &lt;stdio.h&gt;
#include &lt;stdlib.h&gt;

int main() {
    int* ptr = (int *)malloc(sizeof(int));
    *ptr = 69;
    free(ptr); // Memory is deallocated here.
    *ptr = 420; // Use after free error: accessing memory that has been deallocated.
    printf("value: %d\n", *ptr);
    return 0;
}</code></pre><p>The thing that makes this type of bug so insidious is that it&#8217;s not a compile error - it&#8217;s undefined behaviour, and that means it&#8217;s up to the compiler implementation to decide what happens. You will only get a warning if you have the correct warning level turned on for your C compiler. In this case, using GCC 12.1, the program works as expected, though it&#8217;s not guaranteed to do so.</p><p>You may be wondering why I&#8217;m explaining dangling pointers, their insidiousness, and then still recommending manual memory management. It&#8217;s because GC languages have exactly the same problems, just inversed.</p><p>In a GC language, rather than having a dangling pointer, you can easily create a memory leak using the very tool that is meant to replace malloc and make it safe - <code>new</code>.</p><pre><code>var myThing = new MyThing();
myList.Add(myThing);</code></pre><ul><li><p>The <code>MyThing</code> instance resides in the heap memory, and it has at least two references pointing to it: the local variable <code>myThing</code> and the reference inside the list <code>myList</code>.</p></li><li><p>If the scope of <code>myThing</code> (e.g., a method or block where it's declared) ends, the local reference <code>myThing</code> will go out of scope and be removed. However, the <code>MyThing</code> instance won't be garbage collected because the <code>myList</code> still holds a reference to it.</p></li><li><p>Only when there are no more references pointing to the <code>MyThing</code> instance (for instance, if the item is removed from <code>myList</code> and no other references exist) will it be eligible for garbage collection. Until then, it remains in memory.</p></li></ul><p>You as the programmer can easily spot the potential issue in this case - it&#8217;s contrived, after all.</p><p>The point here is not that you can create other types of memory issues using GC&#8217;d languages, but that you are simply trading thinking about malloc/free for thinking about object references instead.</p><p>Let&#8217;s just show one more simple C# example to see how easy it is to make a memory leak.</p><pre><code>public class MyThing {
    public MyThing() {
        GlobalEvents.SomeEvent += HandleEvent;
    }

    private void HandleEvent(object sender, EventArgs e) {
        // Do something.
    }
}</code></pre><p>In the above code, <code>GlobalEvents.SomeEvent</code> holds a reference to each instance of <code>MyThing</code> until unsubscribed. This is, once again, the same type of cognitive burden that causes mistakes as malloc/free.</p><h3>Programmer Productivity</h3><p>Programmer productivity seems to be almost impossible to quantify properly. It&#8217;s certainly not X lines of code written in Y time, commit frequency, or test coverage. Perhaps something like &#8220;frequency of difficult problems solved with robust solutions&#8221; - but even then, how do we score different problems?</p><p>Rather than trying to quantify what makes programmers productive, we can simply compare garbage collection vs manual memory mental overhead and call-site code requirements.</p><p>As stated above, the GC promise of not having to worry about memory is not true in practice. The programmer must still consider object lifetimes and keep track of object references. If the argument is that manual memory allocation takes more lines of code, that may be true in the naive case - where each allocation has a free, but the argument falls apart once custom memory allocators are involved.</p><h2>The Solution: Memory Allocators</h2><p>Memory allocators allow programmers to group together object lifetimes and think about them much more easily than the usual malloc/free or new.</p><p>Rather than tracking individual object lifetimes in your head, you can decide which one of your memory allocators you&#8217;d like to use up-front and then not have to worry about it. This is the panacea to the fragmented way of thinking we have been discussing thus far.</p><h3>The Arena Allocator</h3><p>The arena allocator is an extremely simple and powerful tool to have in your arsenal. The way it works is memory is allocated sequentially and must be freed all at once.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!Ni1-!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F38e6d267-ce78-45b2-83d0-d9a4f508ace5_945x534.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!Ni1-!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F38e6d267-ce78-45b2-83d0-d9a4f508ace5_945x534.png 424w, https://substackcdn.com/image/fetch/$s_!Ni1-!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F38e6d267-ce78-45b2-83d0-d9a4f508ace5_945x534.png 848w, https://substackcdn.com/image/fetch/$s_!Ni1-!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F38e6d267-ce78-45b2-83d0-d9a4f508ace5_945x534.png 1272w, https://substackcdn.com/image/fetch/$s_!Ni1-!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F38e6d267-ce78-45b2-83d0-d9a4f508ace5_945x534.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!Ni1-!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F38e6d267-ce78-45b2-83d0-d9a4f508ace5_945x534.png" width="945" height="534" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/38e6d267-ce78-45b2-83d0-d9a4f508ace5_945x534.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:534,&quot;width&quot;:945,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:142035,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!Ni1-!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F38e6d267-ce78-45b2-83d0-d9a4f508ace5_945x534.png 424w, https://substackcdn.com/image/fetch/$s_!Ni1-!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F38e6d267-ce78-45b2-83d0-d9a4f508ace5_945x534.png 848w, https://substackcdn.com/image/fetch/$s_!Ni1-!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F38e6d267-ce78-45b2-83d0-d9a4f508ace5_945x534.png 1272w, https://substackcdn.com/image/fetch/$s_!Ni1-!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F38e6d267-ce78-45b2-83d0-d9a4f508ace5_945x534.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>The implementation of an arena allocator can be as simple as:</p><pre><code>size_t current_position = 0;
size_t arena_size = 4096;
u8 *arena_memory = malloc(arena_size);

u8 *arena_alloc(size_t bytes) {
    if (current_position + bytes &lt;= arena_size) {
        u8 *ptr = &amp;arena_memory[current_position];
        current_position += bytes;
        return ptr;
    }
    return 0;
}

void arena_free() {
    current_position = 0;
}</code></pre><p>This implementation will not do for any serious use-cases, but the concept remains the same.</p><p>With this one simple tool, the programmer is enabled to allocate a variety of objects with entirely different types and sizes, but the same lifetime. This is much more akin to how we actually <em>use</em> memory in the first place.</p><p>During the initialisation phase of your program, you can allocate permanent resources using your permanent arena allocator that you never have to free or worry about. Then, each update of your program, you may want to construct some strings and log them to a file - you can have an arena that gets cleared at the end of each update cycle.</p><p>The lifetimes of your objects are now dependent on the memory allocator you decide to use and the burden of keeping individual lifetimes in the programmer&#8217;s head is lifted.</p><p>Memory allocation techniques are often covered in advanced C/C++, systems, or OS programming materials, so many programmers are unfamiliar with them.</p><p>I believe that if this concept was taught much earlier in the programming journey, it would lead to better quality software and less confusion about how memory works.</p><h2>Conclusion</h2><p>The main challenges arising from manual memory management stem from how programming is taught, leading many programmers to find it difficult.</p><p>Garbage collection, though not a problem in and of itself, is lauded as the solution to memory bugs. However, it fails to live up to that reputation and obfuscates another class of memory bugs by default.</p><p>Custom memory allocators can be simple to implement, easy to understand, and have no hidden functionality.</p><p>Understanding memory management is essential to creating efficient and robust software. The hardware does not disappear when we try to abstract it away.</p><p>Knowing just a little bit more about how the hardware works can save CPU cycles, reducing costs and conserving energy.</p><p>Whether you are a novice or experienced, I recommend looking into custom memory allocators if you haven&#8217;t already - they are a game-changer.</p><p>Continual learning is part of the programming journey - embrace it. Every year I feel like I was a fool the year before.</p><p>I hope that in the future we will teach memory management the right way sooner. New systems languages like Zig and Odin have incorporated memory allocators as core features - this is a step in the right direction.</p><p>Golang recently added an arena allocator that works in tandem with its GC. I'm quite interested to see how that pans out.</p><p>If you&#8217;d like to learn more about arenas, I recommend these great resources: <a href="https://www.gingerbill.org/article/2019/02/08/memory-allocation-strategies-002/">gingerBill</a>, <a href="https://www.rfleury.com/p/untangling-lifetimes-the-arena-allocator">Ryan Fleury</a>.</p><p>I encourage you to share your experiences, thoughts, or questions below.</p><div><hr></div><p>If you enjoy this newsletter and want to go deeper, with more structure, I made <a href="https://programvideogames.com/?src=newsletter_0001">Program Video Games</a> for you. Transform your game ideas into working systems from first principles.</p>]]></content:encoded></item></channel></rss>