<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Posts on afloat.boats</title><link>https://www.afloat.boats/posts/</link><description>Recent content in Posts on afloat.boats</description><generator>Hugo -- gohugo.io</generator><language>en-us</language><lastBuildDate>Fri, 12 Dec 2025 13:29:34 -0800</lastBuildDate><atom:link href="https://www.afloat.boats/posts/index.xml" rel="self" type="application/rss+xml"/><item><title>Rust for JavaScript Engineers - Hello Universe</title><link>https://www.afloat.boats/posts/rust-for-javascript-engineers-prelude/</link><pubDate>Fri, 12 Dec 2025 13:29:34 -0800</pubDate><guid>https://www.afloat.boats/posts/rust-for-javascript-engineers-prelude/</guid><description>A quick start with Rust&amp;#39;s toolchain and Cargo for JS engineers, before diving into the Connect-4 build.</description><content:encoded><![CDATA[<p>This is a prelude to Rust for JavaScript Engineers series. If you have written some amount of Rust and are familiar with cargo and Rust project structures in general, skip ahead to <a href="/posts/rust-for-javascript-engineers-pt-1">Building Connect-4</a>.</p>
<h2 id="hello-world">Hello, world</h2>
<p>Now before we get into building the Connect-4, let&rsquo;s get a &ldquo;Hello, world&rdquo; program going. This is extremely straightforward as long as you have Rust installed. Here&rsquo;s the place to start: <a href="https://rust-lang.org/tools/install/">Rust Installation</a>.</p>
<p>Once you have Rust installed, let&rsquo;s check the version to verify that the installation worked by running <code>rustc --version</code> in the terminal.</p>
<p>For me the output is <code>rustc 1.90.0 (1159e78c4 2025-09-14)</code>, the version might differ for you. But Rust&rsquo;s backwards compatibility guarantees should ensure the code runs.</p>
<p>Now let&rsquo;s create an empty folder and a file. As Rust is a compiled language, the folder is useful for co-locating the source code and the compiled artifacts.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">mkdir hello_world
</span></span><span class="line"><span class="cl"><span class="nb">cd</span> hello_world
</span></span><span class="line"><span class="cl">touch hello_world.rs
</span></span></code></pre></div><p>In the newly created file, let&rsquo;s write our first function.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-rust" data-lang="rust"><span class="line"><span class="cl"><span class="c1">// hello_world/hello_world.rs
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="c1">// 1. entry point
</span></span></span><span class="line"><span class="cl"><span class="c1">// ↓
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="k">fn</span> <span class="nf">main</span><span class="p">()</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="c1">//         2. macro
</span></span></span><span class="line"><span class="cl"><span class="c1">//         ↓
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="w">    </span><span class="fm">println!</span><span class="p">(</span><span class="s">&#34;Hello, world!&#34;</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="p">}</span><span class="w">
</span></span></span></code></pre></div><h5 id="1-main-function">1. <code>main</code> function</h5>
<p>Every executable Rust program requires an entrypoint, and in ours it&rsquo;s the <code>main</code> function. Here&rsquo;s what the Rust book has to say about it:</p>
<blockquote>
<p>These lines define a function named main. The main function is special: It is always the first code that runs in every executable Rust program.</p></blockquote>
<p>As we don&rsquo;t need the function to return anything, there&rsquo;s no explicit return type. The implicit return type in Rust is the unit type denoted by <code>()</code>, which is similar to a <code>void</code> in TypeScript.</p>
<h5 id="2-println-macro">2. <code>println!</code> macro</h5>
<p>Throughout this series, we will use a lot of macros. The syntax can be confusing as it&rsquo;s similar to a function call, however the key difference is the exclaimation point at the end. The <code>println!</code> macro conveniently hides the machinery that goes into printing a line the the console. It&rsquo;s similar to the <code>console.log</code> statement in JavaScript.</p>
<p>Rust has other types of <a href="https://doc.rust-lang.org/book/ch20-05-macros.html">macros</a>, I&rsquo;ll introduce them as we start using them later.</p>
<p>Let&rsquo;s compile and execute our program:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="c1"># hello_world/</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">rustc ./hello_world.rs
</span></span><span class="line"><span class="cl">./hello_world
</span></span></code></pre></div><p>That should print <code>Hello, world!</code> to the terminal!
Now that we&rsquo;ve written our first program, let&rsquo;s try out the build system that will be used throughout the series.</p>
<h3 id="cargo">Cargo</h3>
<p>Cargo is the default build system, and package manager for the Rust ecosystem. It sits at the same spot <code>npm</code> (the CLI tool) does in the JavaScript ecosystem.</p>
<p>Let&rsquo;s create a new project, called <code>hello_universe</code>, but this time using cargo.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">cargo new hello_universe
</span></span><span class="line"><span class="cl"><span class="nb">cd</span> ./hello_universe
</span></span></code></pre></div><p>It should create a structure that looks like:</p>
<pre tabindex="0"><code>hello_universe/
├─ src/
│  └─ main.rs                  // entry point
│
└─ Cargo.toml                  // package config and dependencies
</code></pre><p>The <code>main.rs</code> should already have a <code>main</code> function that prints <code>Hello, world!</code>.
I&rsquo;d like you to update that to print <code>Hello, universe!</code>, and then execute it by running <code>cargo run</code>.</p>
<pre tabindex="0"><code>   Compiling hello_universe v0.1.0 (/Users/tauseefk/personal/rust/scratch/hello_universe)
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.09s
     Running `target/debug/hello_universe`
Hello, world!
</code></pre><p>Cargo takes care of compiling the project, and executing it. By default the project gets compiled using a <code>dev</code> profile. The executable lives in <code>target/debug/hello_universe</code>.</p>
<p>We&rsquo;ll come back to debug/release builds and profiles later. For now this should be enough for us to jump back into writing the Connect-4 game.</p>
]]></content:encoded></item><item><title>Rust for Javascript Engineers - Connect-4 Interactivity</title><link>https://www.afloat.boats/posts/rust-for-javascript-engineers-interactivity/</link><pubDate>Tue, 23 Sep 2025 16:46:59 -0700</pubDate><guid>https://www.afloat.boats/posts/rust-for-javascript-engineers-interactivity/</guid><description>Player turns, move validation, and win detection for Connect-4 in Rust compiled to WASM.</description><content:encoded><![CDATA[<p><a href="/connectors" target="_blank" rel="noopener noreferrer">Connect Four</a> | <a href="https://github.com/tauseefk/connectors" target="_blank" rel="noopener noreferrer">Repository</a></p>
<p>In the <a href="/posts/rust-for-javascript-engineers-all-a-board">last post</a> we looked at setting up the Connect-4 game board and transfering data back and forth between the JavaScript and WASM boundary. This allowed us to render a randomized board to the screen, however for it to be a real game we need allow users to make moves.</p>
<p>In this post let&rsquo;s take a look at adding a bit of interactivity to allow players to play a full game, and at the very end calculate the winner!</p>
<h2 id="interactivity">Interactivity</h2>
<p>The first piece of interactivity we can add to our connect-4 is a way to switch between players.
Let&rsquo;s add a new enum that encodes the player variants, and then add it to our larger game state which is the <code>Board</code> struct.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-rust" data-lang="rust"><span class="line"><span class="cl"><span class="cp">#[derive(Copy, Clone)]</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="k">pub</span><span class="w"> </span><span class="k">enum</span> <span class="nc">Player</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">Red</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">Black</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="k">pub</span><span class="w"> </span><span class="k">struct</span> <span class="nc">Board</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">row_count</span>: <span class="kt">usize</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">col_count</span>: <span class="kt">usize</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="c1">// public so we can read it from the JavaScript side and display it
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="w">    </span><span class="k">pub</span><span class="w"> </span><span class="n">player_turn</span>: <span class="nc">Player</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="p">}</span><span class="w">
</span></span></span></code></pre></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-rust" data-lang="rust"><span class="line"><span class="cl"><span class="cp">#[wasm_bindgen]</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="k">impl</span><span class="w"> </span><span class="n">Board</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="c1">// 1.
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="w">    </span><span class="k">fn</span> <span class="nf">end_player_turn</span><span class="p">(</span><span class="o">&amp;</span><span class="k">mut</span><span class="w"> </span><span class="bp">self</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="bp">self</span><span class="p">.</span><span class="n">player_turn</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="k">match</span><span class="w"> </span><span class="bp">self</span><span class="p">.</span><span class="n">player_turn</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="n">Player</span>::<span class="n">Red</span><span class="w"> </span><span class="o">=&gt;</span><span class="w"> </span><span class="n">Player</span>::<span class="n">Black</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="n">Player</span>::<span class="n">Black</span><span class="w"> </span><span class="o">=&gt;</span><span class="w"> </span><span class="n">Player</span>::<span class="n">Red</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="c1">// 2.
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="w">    </span><span class="k">pub</span><span class="w"> </span><span class="k">fn</span> <span class="nf">select_col</span><span class="p">(</span><span class="o">&amp;</span><span class="k">mut</span><span class="w"> </span><span class="bp">self</span><span class="p">,</span><span class="w"> </span><span class="n">col_idx</span>: <span class="kt">u8</span><span class="p">)</span><span class="w"> </span>-&gt; <span class="kt">bool</span> <span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="c1">// board update logic will go here
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="bp">self</span><span class="p">.</span><span class="n">end_player_turn</span><span class="p">();</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="p">}</span><span class="w">
</span></span></span></code></pre></div><h5 id="1-end_player_turn">1. end_player_turn</h5>
<p>The <code>end_player_turn</code> function just flips the player turn state on the overall game state between red and black variants.</p>
<h5 id="2-select_col">2. select_col</h5>
<p>The <code>select_col</code> function is where majority of the game logic will go. Once all the required state has been updated, it&rsquo;ll call <code>end_player_turn</code> and the other player can make their move.</p>
<h3 id="javascript-to-match">JavaScript to match</h3>
<p>Now let&rsquo;s update our JavaScript to accomodate for these changes, by first adding another div that would display the player turn, updating the rendering logic, and adding an event handler.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-javascript" data-lang="javascript"><span class="line"><span class="cl"><span class="kr">import</span> <span class="nx">init</span><span class="p">,</span> <span class="p">{</span> <span class="nx">Board</span><span class="p">,</span> <span class="nx">TileType</span> <span class="p">}</span> <span class="nx">from</span> <span class="s2">&#34;./connectors/connectors.js&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="kr">const</span> <span class="nx">ROW_COUNT</span> <span class="o">=</span> <span class="mi">6</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="kr">const</span> <span class="nx">COL_COUNT</span> <span class="o">=</span> <span class="mi">7</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kr">const</span> <span class="nx">DISPLAY_TURN_MAP</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="p">[</span><span class="nx">Player</span><span class="p">.</span><span class="nx">Red</span><span class="p">]</span><span class="o">:</span> <span class="s2">&#34;R&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="p">[</span><span class="nx">Player</span><span class="p">.</span><span class="nx">Black</span><span class="p">]</span><span class="o">:</span> <span class="s2">&#34;B&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"><span class="p">};</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1">// this duplication is necessary
</span></span></span><span class="line"><span class="cl"><span class="c1">// just so it&#39;s easy to render as a grid
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="kr">const</span> <span class="nx">DISPLAY_TILE_MAP</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="p">[</span><span class="nx">TileType</span><span class="p">.</span><span class="nx">Empty</span><span class="p">]</span><span class="o">:</span> <span class="s2">&#34;_&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="p">[</span><span class="nx">TileType</span><span class="p">.</span><span class="nx">Red</span><span class="p">]</span><span class="o">:</span> <span class="s2">&#34;R&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="p">[</span><span class="nx">TileType</span><span class="p">.</span><span class="nx">Black</span><span class="p">]</span><span class="o">:</span> <span class="s2">&#34;B&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"><span class="p">};</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kr">const</span> <span class="nx">playerTurnEl</span> <span class="o">=</span> <span class="nb">document</span><span class="p">.</span><span class="nx">getElementById</span><span class="p">(</span><span class="s2">&#34;player-turn&#34;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="kr">const</span> <span class="nx">gameBoardEl</span> <span class="o">=</span> <span class="nb">document</span><span class="p">.</span><span class="nx">getElementById</span><span class="p">(</span><span class="s2">&#34;game-board&#34;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nx">init</span><span class="p">().</span><span class="nx">then</span><span class="p">(()</span> <span class="p">=&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="kr">const</span> <span class="nx">board</span> <span class="o">=</span> <span class="nx">Board</span><span class="p">.</span><span class="k">new</span><span class="p">(</span><span class="nx">ROW_COUNT</span><span class="p">,</span> <span class="nx">COL_COUNT</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="c1">// 1. Render
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>  <span class="kr">const</span> <span class="nx">render</span> <span class="o">=</span> <span class="p">()</span> <span class="p">=&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nx">playerTurnEl</span><span class="p">.</span><span class="nx">innerText</span> <span class="o">=</span> <span class="nx">DISPLAY_TURN_MAP</span><span class="p">[</span><span class="nx">board</span><span class="p">.</span><span class="nx">player_turn</span><span class="p">];</span>
</span></span><span class="line"><span class="cl">    <span class="kr">const</span> <span class="nx">tiles</span> <span class="o">=</span> <span class="nb">Array</span><span class="p">.</span><span class="nx">from</span><span class="p">(</span><span class="nx">board</span><span class="p">.</span><span class="nx">get_board</span><span class="p">).</span><span class="nx">map</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">      <span class="p">(</span><span class="nx">tileEnumValue</span><span class="p">)</span> <span class="p">=&gt;</span> <span class="nx">DISPLAY_TILE_MAP</span><span class="p">[</span><span class="nx">tileEnumValue</span><span class="p">],</span>
</span></span><span class="line"><span class="cl">    <span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="kr">const</span> <span class="nx">fragment</span> <span class="o">=</span> <span class="nb">document</span><span class="p">.</span><span class="nx">createDocumentFragment</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="nx">tiles</span><span class="p">.</span><span class="nx">forEach</span><span class="p">((</span><span class="nx">tile</span><span class="p">,</span> <span class="nx">idx</span><span class="p">)</span> <span class="p">=&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="kr">const</span> <span class="nx">span</span> <span class="o">=</span> <span class="nb">document</span><span class="p">.</span><span class="nx">createElement</span><span class="p">(</span><span class="s2">&#34;span&#34;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">      <span class="nx">span</span><span class="p">.</span><span class="nx">textContent</span> <span class="o">=</span> <span class="nx">tile</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">      <span class="nx">span</span><span class="p">.</span><span class="nx">dataset</span><span class="p">.</span><span class="nx">colIdx</span> <span class="o">=</span> <span class="nb">Math</span><span class="p">.</span><span class="nx">floor</span><span class="p">(</span><span class="nx">idx</span> <span class="o">%</span> <span class="nx">COL_COUNT</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">      <span class="nx">fragment</span><span class="p">.</span><span class="nx">appendChild</span><span class="p">(</span><span class="nx">span</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="p">});</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="nx">gameBoardEl</span><span class="p">.</span><span class="nx">innerHTML</span> <span class="o">=</span> <span class="s2">&#34;&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="nx">gameBoardEl</span><span class="p">.</span><span class="nx">appendChild</span><span class="p">(</span><span class="nx">fragment</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">  <span class="p">};</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="c1">// 2.
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>  <span class="nx">gameBoardEl</span><span class="p">.</span><span class="nx">addEventListener</span><span class="p">(</span><span class="s2">&#34;click&#34;</span><span class="p">,</span> <span class="p">(</span><span class="nx">event</span><span class="p">)</span> <span class="p">=&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="kr">const</span> <span class="nx">colIdx</span> <span class="o">=</span> <span class="nb">parseInt</span><span class="p">(</span><span class="nx">event</span><span class="p">.</span><span class="nx">target</span><span class="p">.</span><span class="nx">dataset</span><span class="p">.</span><span class="nx">colIdx</span><span class="p">,</span> <span class="mi">10</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="nx">board</span><span class="p">.</span><span class="nx">select_col</span><span class="p">(</span><span class="nx">colIdx</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="nx">render</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">  <span class="p">});</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="c1">// initial render
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>  <span class="nx">render</span><span class="p">();</span>
</span></span><span class="line"><span class="cl"><span class="p">});</span>
</span></span></code></pre></div><p>That&rsquo;s a wall of code, however most of it is just a small refactor to ensure that we can render after each user event and not just once when the page loads.</p>
<h5 id="1-render-refactor">1. Render refactor</h5>
<p>We extract the rendering logic into it&rsquo;s own function so that after each user interaction we can update the entire screen to display the updated state.</p>
<h5 id="2-adding-event-listeners">2. Adding event listeners</h5>
<p>The event handler is applied to the entire grid, it extract the column index from the data attribute and calls the <code>select_col</code> function that we added to the rust code earlier, and finally calls render to update the screen.</p>
<p>If you rebuild and launch the app now you&rsquo;ll see that clicking anywhere on the grid updates the Player turn back and forth between <code>R</code> and <code>B</code>.</p>
<p>Here&rsquo;s the full <a href="https://github.com/tauseefk/connectors/commit/ede59739f99adaf739061ae151608e09b165ccdf">commit</a>.</p>
<h2 id="making-moves">Making Moves</h2>
<p>So far we&rsquo;ve been using random tiles, now let&rsquo;s replace those with empty tiles.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-rust" data-lang="rust"><span class="line"><span class="cl"><span class="cp">#[wasm_bindgen]</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="k">pub</span><span class="w"> </span><span class="k">struct</span> <span class="nc">Board</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">row_count</span>: <span class="kt">usize</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">col_count</span>: <span class="kt">usize</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">pub</span><span class="w"> </span><span class="n">player_turn</span>: <span class="nc">Player</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="c1">// 1. private
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="w">    </span><span class="n">board</span>: <span class="nb">Vec</span><span class="o">&lt;</span><span class="n">TileType</span><span class="o">&gt;</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="cp">#[wasm_bindgen]</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="k">impl</span><span class="w"> </span><span class="n">Board</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">pub</span><span class="w"> </span><span class="k">fn</span> <span class="nf">new</span><span class="p">(</span><span class="n">row_count</span>: <span class="kt">usize</span><span class="p">,</span><span class="w"> </span><span class="n">col_count</span>: <span class="kt">usize</span><span class="p">)</span><span class="w"> </span>-&gt; <span class="nc">Self</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="c1">// initialize the entire board to empty values
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="w">        </span><span class="c1">//
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="w">        </span><span class="c1">//                         2. initialize to Empty
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="w">        </span><span class="c1">//                         ↓
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="w">        </span><span class="kd">let</span><span class="w"> </span><span class="n">board</span>: <span class="nb">Vec</span><span class="o">&lt;</span><span class="n">TileType</span><span class="o">&gt;</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="fm">vec!</span><span class="p">[</span><span class="n">TileType</span>::<span class="n">Empty</span><span class="p">;</span><span class="w"> </span><span class="n">row_count</span><span class="w"> </span><span class="o">*</span><span class="w"> </span><span class="n">col_count</span><span class="p">];</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="bp">Self</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="n">row_count</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="n">col_count</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="n">board</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="n">player_turn</span>: <span class="nc">Player</span>::<span class="n">Red</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="cp">#[wasm_bindgen(getter)]</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">pub</span><span class="w"> </span><span class="k">fn</span> <span class="nf">get_board</span><span class="p">(</span><span class="o">&amp;</span><span class="bp">self</span><span class="p">)</span><span class="w"> </span>-&gt; <span class="nc">js_sys</span>::<span class="n">Uint8Array</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="kd">let</span><span class="w"> </span><span class="n">board</span>: <span class="nb">Vec</span><span class="o">&lt;</span><span class="kt">u8</span><span class="o">&gt;</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="bp">self</span><span class="p">.</span><span class="n">board</span><span class="p">.</span><span class="n">iter</span><span class="p">().</span><span class="n">map</span><span class="p">(</span><span class="o">|</span><span class="n">tile</span><span class="o">|</span><span class="w"> </span><span class="n">tile</span><span class="p">.</span><span class="n">into</span><span class="p">()).</span><span class="n">collect</span><span class="p">();</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">js_sys</span>::<span class="n">Uint8Array</span>::<span class="n">from</span><span class="p">(</span><span class="o">&amp;</span><span class="n">board</span><span class="p">[</span><span class="o">..</span><span class="p">])</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="p">}</span><span class="w">
</span></span></span></code></pre></div><h5 id="1-private-vec-field">1. private <code>Vec</code> field</h5>
<p>Only simple types are directly exposed across the Wasm/JS boundary, so vector fields cannot be public. have to be converted into typed arrays.</p>
<h5 id="2-initializing-vec-to-empty">2. Initializing <code>Vec</code> to Empty</h5>
<p>Here we initialize entire board to <code>TileType::Empty</code> using the <code>vec!</code> macro instead of a randomized board.</p>
<p>Now let&rsquo;s update the <code>select_col</code> function to update the state based on user&rsquo;s selected column.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-rust" data-lang="rust"><span class="line"><span class="cl"><span class="k">impl</span><span class="w"> </span><span class="n">Board</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="c1">// 1. bounds check
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="w">    </span><span class="k">fn</span> <span class="nf">is_in_bounds</span><span class="p">(</span><span class="o">&amp;</span><span class="bp">self</span><span class="p">,</span><span class="w"> </span><span class="n">col_idx</span>: <span class="kt">u8</span><span class="p">)</span><span class="w"> </span>-&gt; <span class="kt">bool</span> <span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="p">(</span><span class="n">col_idx</span><span class="w"> </span><span class="k">as</span><span class="w"> </span><span class="kt">usize</span><span class="p">)</span><span class="w"> </span><span class="o">&lt;</span><span class="w"> </span><span class="bp">self</span><span class="p">.</span><span class="n">col_count</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">pub</span><span class="w"> </span><span class="k">fn</span> <span class="nf">select_col</span><span class="p">(</span><span class="o">&amp;</span><span class="k">mut</span><span class="w"> </span><span class="bp">self</span><span class="p">,</span><span class="w"> </span><span class="n">col_idx</span>: <span class="kt">u8</span><span class="p">)</span><span class="w"> </span>-&gt; <span class="kt">bool</span> <span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="k">if</span><span class="w"> </span><span class="o">!</span><span class="bp">self</span><span class="p">.</span><span class="n">is_in_bounds</span><span class="p">(</span><span class="n">col_idx</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="k">return</span><span class="w"> </span><span class="kc">false</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="c1">/////////////////////////////////////
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="w">        </span><span class="c1">///////// Finding Empty Row /////////
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="w">        </span><span class="c1">/////////////////////////////////////
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="c1">// 2. chunking
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="w">        </span><span class="kd">let</span><span class="w"> </span><span class="n">rows</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="bp">self</span><span class="p">.</span><span class="n">board</span><span class="p">.</span><span class="n">chunks</span><span class="p">(</span><span class="bp">self</span><span class="p">.</span><span class="n">col_count</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="c1">// 3. reversing rows
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="w">        </span><span class="kd">let</span><span class="w"> </span><span class="n">rows_reversed</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">rows</span><span class="p">.</span><span class="n">rev</span><span class="p">();</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="kd">let</span><span class="w"> </span><span class="k">mut</span><span class="w"> </span><span class="n">empty_row_idx</span>: <span class="nb">Option</span><span class="o">&lt;</span><span class="kt">usize</span><span class="o">&gt;</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nb">None</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="k">for</span><span class="w"> </span><span class="p">(</span><span class="n">idx</span><span class="p">,</span><span class="w"> </span><span class="n">row</span><span class="p">)</span><span class="w"> </span><span class="k">in</span><span class="w"> </span><span class="n">rows_reversed</span><span class="p">.</span><span class="n">enumerate</span><span class="p">()</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="kd">let</span><span class="w"> </span><span class="n">tile_in_row</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">row</span><span class="p">.</span><span class="n">get</span><span class="p">(</span><span class="n">col_idx</span><span class="w"> </span><span class="k">as</span><span class="w"> </span><span class="kt">usize</span><span class="p">).</span><span class="n">expect</span><span class="p">(</span><span class="s">&#34;This cannot happen&#34;</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="k">if</span><span class="w"> </span><span class="n">tile_in_row</span><span class="p">.</span><span class="n">is_empty</span><span class="p">()</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                </span><span class="c1">// as the rows are reversed we need to subtract from row_count
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="w">                </span><span class="n">empty_row_idx</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nb">Some</span><span class="p">(</span><span class="bp">self</span><span class="p">.</span><span class="n">row_count</span><span class="w"> </span><span class="o">-</span><span class="w"> </span><span class="n">idx</span><span class="w"> </span><span class="o">-</span><span class="w"> </span><span class="mi">1</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                </span><span class="k">break</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="k">match</span><span class="w"> </span><span class="n">empty_row_idx</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="nb">Some</span><span class="p">(</span><span class="n">empty_row_idx</span><span class="p">)</span><span class="w"> </span><span class="o">=&gt;</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                </span><span class="kd">let</span><span class="w"> </span><span class="n">idx_to_update</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="bp">self</span><span class="p">.</span><span class="n">col_count</span><span class="w"> </span><span class="o">*</span><span class="w"> </span><span class="n">empty_row_idx</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="n">col_idx</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                </span><span class="c1">// 4. updating the board
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="w">                </span><span class="bp">self</span><span class="p">.</span><span class="n">board</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="bp">self</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                    </span><span class="p">.</span><span class="n">board</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                    </span><span class="p">.</span><span class="n">iter</span><span class="p">()</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                    </span><span class="p">.</span><span class="n">enumerate</span><span class="p">()</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                    </span><span class="p">.</span><span class="n">map</span><span class="p">(</span><span class="o">|</span><span class="p">(</span><span class="n">idx</span><span class="p">,</span><span class="w"> </span><span class="n">tile</span><span class="p">)</span><span class="o">|</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                        </span><span class="k">if</span><span class="w"> </span><span class="n">idx</span><span class="w"> </span><span class="o">==</span><span class="w"> </span><span class="n">idx_to_update</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                            </span><span class="k">return</span><span class="w"> </span><span class="k">match</span><span class="w"> </span><span class="bp">self</span><span class="p">.</span><span class="n">player_turn</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                                </span><span class="n">Player</span>::<span class="n">Red</span><span class="w"> </span><span class="o">=&gt;</span><span class="w"> </span><span class="n">TileType</span>::<span class="n">Red</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                                </span><span class="n">Player</span>::<span class="n">Black</span><span class="w"> </span><span class="o">=&gt;</span><span class="w"> </span><span class="n">TileType</span>::<span class="n">Black</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                            </span><span class="p">};</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                        </span><span class="p">}</span><span class="w"> </span><span class="k">else</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                            </span><span class="k">return</span><span class="w"> </span><span class="o">*</span><span class="n">tile</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                        </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                    </span><span class="p">})</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                    </span><span class="p">.</span><span class="n">collect</span><span class="p">();</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                </span><span class="bp">self</span><span class="p">.</span><span class="n">end_player_turn</span><span class="p">();</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                </span><span class="kc">true</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="c1">// if no empty row found for `col_idx` return false
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="w">            </span><span class="nb">None</span><span class="w"> </span><span class="o">=&gt;</span><span class="w"> </span><span class="kc">false</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="p">}</span><span class="w">
</span></span></span></code></pre></div><h5 id="1-bounds-checking">1. Bounds Checking</h5>
<p>As we&rsquo;re storing the entire grid as a single array, checking if the selected index is valid is trivial.</p>
<h5 id="2-chunking">2. Chunking</h5>
<p>Rust <code>Vec</code>s support non overlapping chunks out of the box.</p>
<h5 id="3-reversing-the-rows">3. Reversing the rows</h5>
<p>As Connect-4 fills bottom up, we have to find the last row that&rsquo;s empty to replace the tile.</p>
<p>There&rsquo;s another way to combine the chunking and reversing via <code>self.boards.rchunks</code> which gives us an iterator over already reversed chunks but I wanted to break this down for clarity.</p>
<h5 id="4-updating-the-board">4. Updating the board</h5>
<p>Once an empty row is found we can map over the board and replace it after updating the appropriate tile. If we go through all the rows without finding an empty row, we can just return false to signify that no update was made.</p>
<p>That should be it for interactivity!</p>
<h2 id="winning-move">Winning Move</h2>
<p>As this requires changes in a lot of places I&rsquo;ll only go over the crucial bits that use Rust features we haven&rsquo;t explored before. The full diff can be found <a href="https://github.com/tauseefk/connectors/commit/5165777aae96af7a97693a8be1ed1eb4092c5f44">here</a>.
Currently the game never ends even if one of the player connects 4 of their tiles. The win condition requires a player to connect four of their tiles either horizontally, vertically, or diagonally. Let&rsquo;s look at the first one.</p>
<p>As the state only changes when a player makes a move we can narrow things down to a player, a row, and a column.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-rust" data-lang="rust"><span class="line"><span class="cl"><span class="c1">//                    1. PartialEq
</span></span></span><span class="line"><span class="cl"><span class="c1">//                    ↓
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="cp">#[derive(Copy, Clone, PartialEq)]</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="k">pub</span><span class="w"> </span><span class="k">enum</span> <span class="nc">Player</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">Red</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">Black</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="cp">#[wasm_bindgen]</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="k">pub</span><span class="w"> </span><span class="k">struct</span> <span class="nc">Board</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="c1">// 2.
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="w">    </span><span class="k">pub</span><span class="w"> </span><span class="n">winner</span>: <span class="nb">Option</span><span class="o">&lt;</span><span class="n">Player</span><span class="o">&gt;</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="k">impl</span><span class="w"> </span><span class="n">Board</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">fn</span> <span class="nf">is_game_over</span><span class="p">(</span><span class="o">&amp;</span><span class="bp">self</span><span class="p">,</span><span class="w"> </span><span class="n">row_idx</span>: <span class="kt">usize</span><span class="p">,</span><span class="w"> </span><span class="n">col_idx</span>: <span class="kt">usize</span><span class="p">)</span><span class="w"> </span>-&gt; <span class="kt">bool</span> <span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="bp">self</span><span class="p">.</span><span class="n">has_4_consecutive_tiles_in_row</span><span class="p">(</span><span class="bp">self</span><span class="p">.</span><span class="n">player_turn</span><span class="p">,</span><span class="w"> </span><span class="n">row_idx</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="o">||</span><span class="w"> </span><span class="bp">self</span><span class="p">.</span><span class="n">has_4_consecutive_tiles_in_col</span><span class="p">(</span><span class="bp">self</span><span class="p">.</span><span class="n">player_turn</span><span class="p">,</span><span class="w"> </span><span class="n">col_idx</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">fn</span> <span class="nf">has_4_consecutive_tiles_in_row</span><span class="p">(</span><span class="o">&amp;</span><span class="bp">self</span><span class="p">,</span><span class="w"> </span><span class="n">player</span>: <span class="nc">Player</span><span class="p">,</span><span class="w"> </span><span class="n">row_idx</span>: <span class="kt">usize</span><span class="p">)</span><span class="w"> </span>-&gt; <span class="kt">bool</span> <span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="c1">// cell idx for the start of row
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="w">        </span><span class="kd">let</span><span class="w"> </span><span class="n">row_start_idx</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="bp">self</span><span class="p">.</span><span class="n">idx_from_row_col</span><span class="p">(</span><span class="n">row_idx</span><span class="p">,</span><span class="w"> </span><span class="mi">0</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="c1">// cell idx for the end of row
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="w">        </span><span class="kd">let</span><span class="w"> </span><span class="n">row_end_idx</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">row_start_idx</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="bp">self</span><span class="p">.</span><span class="n">col_count</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="c1">// get a slice of tiles that belong to the row with `row_idx`
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="w">        </span><span class="kd">let</span><span class="w"> </span><span class="n">row_tiles</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="o">&amp;</span><span class="bp">self</span><span class="p">.</span><span class="n">board</span><span class="p">[</span><span class="n">row_start_idx</span><span class="o">..</span><span class="n">row_end_idx</span><span class="p">];</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="kd">let</span><span class="w"> </span><span class="k">mut</span><span class="w"> </span><span class="n">consecutive_count</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="mi">0</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="k">for</span><span class="w"> </span><span class="n">tile</span><span class="w"> </span><span class="k">in</span><span class="w"> </span><span class="n">row_tiles</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="c1">// 3.
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="w">            </span><span class="k">if</span><span class="w"> </span><span class="n">tile</span><span class="p">.</span><span class="n">does_belong_to</span><span class="p">(</span><span class="n">player</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                </span><span class="n">consecutive_count</span><span class="w"> </span><span class="o">+=</span><span class="w"> </span><span class="mi">1</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                </span><span class="k">if</span><span class="w"> </span><span class="n">consecutive_count</span><span class="w"> </span><span class="o">&gt;=</span><span class="w"> </span><span class="mi">4</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                    </span><span class="k">return</span><span class="w"> </span><span class="kc">true</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="p">}</span><span class="w"> </span><span class="k">else</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                </span><span class="n">consecutive_count</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="mi">0</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="kc">false</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="p">}</span><span class="w">
</span></span></span></code></pre></div><p>Similarly we can check if four tiles in a column belong to the expected player, I won&rsquo;t write the code here but it&rsquo;s part of the commit.</p>
<p>There are several ways of encoding the winning player but as we don&rsquo;t advance the player turn after the winning move, it&rsquo;s just simpler to combine the <code>player_turn</code> and <code>is_game_over</code>.</p>
<h5 id="1-partialeq-trait">1. <code>PartialEq</code> trait</h5>
<p>The partial eq trait is necessary to perform equality checks on enum variants.
The derive macro conveniently implements the trait for us here as the <code>Player</code> enum variants are trivial.</p>
<h5 id="2-option-type">2. <code>Option</code> type</h5>
<p>Rust has no null values, the idiomatic way to encode the absence of a value is to use an Option of the appropriate type. In this case <code>Option&lt;Player&gt;</code> can either store <code>Some(Player::Red)</code> or <code>Some(Player::Black)</code> or <code>None</code>. We initialize the winner to <code>None</code> when the game starts and replace it with the appropriate player that makes the winning move.</p>
<p>On the JavaScript side <code>None</code> shows up as <code>undefined</code>.</p>
<h5 id="3-does_belong_to">3. <code>does_belong_to</code></h5>
<p>As we have more <code>TileType</code> variants than <code>Player</code> variants, we need a way to map whether a tile belongs to a player.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-rust" data-lang="rust"><span class="line"><span class="cl"><span class="k">impl</span><span class="w"> </span><span class="n">TileType</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">pub</span><span class="w"> </span><span class="k">fn</span> <span class="nf">does_belong_to</span><span class="p">(</span><span class="o">&amp;</span><span class="bp">self</span><span class="p">,</span><span class="w"> </span><span class="n">player</span>: <span class="nc">Player</span><span class="p">)</span><span class="w"> </span>-&gt; <span class="kt">bool</span> <span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="k">match</span><span class="w"> </span><span class="bp">self</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="n">TileType</span>::<span class="n">Empty</span><span class="w"> </span><span class="o">=&gt;</span><span class="w"> </span><span class="kc">false</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="n">TileType</span>::<span class="n">Red</span><span class="w"> </span><span class="o">=&gt;</span><span class="w"> </span><span class="n">player</span><span class="w"> </span><span class="o">==</span><span class="w"> </span><span class="n">Player</span>::<span class="n">Red</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="n">TileType</span>::<span class="n">Black</span><span class="w"> </span><span class="o">=&gt;</span><span class="w"> </span><span class="n">player</span><span class="w"> </span><span class="o">==</span><span class="w"> </span><span class="n">Player</span>::<span class="n">Black</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="p">}</span><span class="w">
</span></span></span></code></pre></div><p>The JavaScript changes are trivial (updating text and disabling click events after game ends) and I&rsquo;ll skip those here.</p>
<p>This concludes all of the gameplay for Connect-4, we&rsquo;ve exposed the smallest amount of API required to play a full game. There&rsquo;s no clean-up required as refreshing the page frees the memory allocated in WASM and reallocates the memory when the game restarts.</p>
]]></content:encoded></item><item><title>Rust for JavaScript Engineers - All a Board</title><link>https://www.afloat.boats/posts/rust-for-javascript-engineers-all-a-board/</link><pubDate>Sat, 23 Aug 2025 16:50:09 -0700</pubDate><guid>https://www.afloat.boats/posts/rust-for-javascript-engineers-all-a-board/</guid><description>Calling JS from Rust, building the Connect-4 board as a Vec, and rendering it as HTML across the WASM boundary.</description><content:encoded><![CDATA[<p><a href="/connectors" target="_blank" rel="noopener noreferrer">Connect Four</a> | <a href="https://github.com/tauseefk/connectors" target="_blank" rel="noopener noreferrer">Repository</a></p>
<p>In the <a href="/posts/rust-for-javascript-engineers-pt-1">last post</a> we looked at setting up the codebase to build Rust crates, and then importing them into a JavaScript project.</p>
<p>By the end of the post we&rsquo;d gone over a simple enum based design for each position of the Connect 4 board. In this post, I&rsquo;m going to expand on how we can take that simple data structure, build out the entire grid, and then render it using HTML elements.</p>
<p>But first, a bit of house-keeping.
We&rsquo;d looked at calling Rust functions from JavaScript, but not the other way around. So let&rsquo;s dive into that.</p>
<h2 id="calling-home">Calling Home</h2>
<p>Let&rsquo;s expose a JavaScript function (Math.random), on the global object as that&rsquo;s how it is made available to WASM. Everything else remains the same.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-javascript" data-lang="javascript"><span class="line"><span class="cl"><span class="c1">// only new line
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="nb">window</span><span class="p">.</span><span class="nx">mathRandom</span> <span class="o">=</span> <span class="nb">Math</span><span class="p">.</span><span class="nx">random</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1">// existing code
</span></span></span></code></pre></div><p>Right now if we try to call: <code>mathRandom</code> directly in Rust, the compiler won&rsquo;t let us build.</p>
<p>Let&rsquo;s repurpose our <code>say_hello</code> function with this:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-rust" data-lang="rust"><span class="line"><span class="cl"><span class="cp">#[wasm_bindgen]</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="k">pub</span><span class="w"> </span><span class="k">fn</span> <span class="nf">say_hello</span><span class="p">()</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kd">let</span><span class="w"> </span><span class="n">random_float</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">mathRandom</span><span class="p">();</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">web_sys</span>::<span class="n">console</span>::<span class="n">log_1</span><span class="p">(</span><span class="o">&amp;</span><span class="fm">format!</span><span class="p">(</span><span class="s">&#34;Random number: </span><span class="si">{}</span><span class="s">&#34;</span><span class="p">,</span><span class="w"> </span><span class="n">random_float</span><span class="p">).</span><span class="n">into</span><span class="p">());</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="p">}</span><span class="w">
</span></span></span></code></pre></div><p>On re-building <code>connecto.rs</code>, it throws the following error.</p>
<pre tabindex="0"><code>error[E0425]: cannot find function `mathRandom` in this scope
  --&gt; connectors/src/lib.rs:22:24
   |
22 |     let random_float = mathRandom();
   |                        ^^^^^^^^^^ not found in this scope
</code></pre><p>The problem here is that even though the function is attached to the <code>window</code> object, Rust has no way of knowing it exists.
Now let&rsquo;s write the equivalent of a &ldquo;trust me bro&rdquo; to placate the Rust compiler.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-rust" data-lang="rust"><span class="line"><span class="cl"><span class="c1">// wasm bindgen ensures that the glue code for this function exists
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="cp">#[wasm_bindgen]</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="c1">// 1.
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="k">extern</span><span class="w"> </span><span class="s">&#34;C&#34;</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="c1">// 2.
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="w">    </span><span class="cp">#[wasm_bindgen(js_name = mathRandom)]</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="c1">// 3.
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="w">    </span><span class="k">fn</span> <span class="nf">math_random</span><span class="p">()</span><span class="w"> </span>-&gt; <span class="kt">f32</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="p">}</span><span class="w">
</span></span></span></code></pre></div><h5 id="1">1.</h5>
<p>The extern block is used as an FFI (Foreign Function Interface) to allow Rust code to call foreign code. This generally means that the safety guarantees of the Rust compiler can&rsquo;t save you anymore, and you better be really sure about what this function does.</p>
<h5 id="2">2.</h5>
<p>The second thing worth highlighting is the <code>js_name = mathRandom</code>. This is a way to translate names between JavaScript and Rust, so we can maintain appropriate style-guides on either end of the FFI.</p>
<h5 id="3">3.</h5>
<p>In our Rust code the function will be available as <code>math_random</code> and as the javascript function <code>Math.random</code> returns a floating point value between <code>0-1</code> the return type of f32 will suffice. Let&rsquo;s put it to use.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-rust" data-lang="rust"><span class="line"><span class="cl"><span class="cp">#[wasm_bindgen]</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="k">pub</span><span class="w"> </span><span class="k">fn</span> <span class="nf">say_hello</span><span class="p">()</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">web_sys</span>::<span class="n">console</span>::<span class="n">log_1</span><span class="p">(</span><span class="o">&amp;</span><span class="fm">format!</span><span class="p">(</span><span class="s">&#34;Random number: </span><span class="si">{}</span><span class="s">&#34;</span><span class="p">,</span><span class="w"> </span><span class="n">math_random</span><span class="p">()).</span><span class="n">into</span><span class="p">());</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="p">}</span><span class="w">
</span></span></span></code></pre></div><p>Once re-compiled the app should log:</p>
<p><code>Random number: 0.7674366</code></p>
<p>The actual random number would vary.</p>
<p>As calling JavaScript&rsquo;s standard library function is a common thing people do, the <code>js_sys</code> crate conveniently provides us handles to all of them.
Let&rsquo;s swap our hand-rolled version of <code>Math.random</code> and just use the one provided by the crate.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-rust" data-lang="rust"><span class="line"><span class="cl"><span class="cp">#[wasm_bindgen]</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="k">pub</span><span class="w"> </span><span class="k">fn</span> <span class="nf">say_hello</span><span class="p">()</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">web_sys</span>::<span class="n">console</span>::<span class="n">log_1</span><span class="p">(</span><span class="o">&amp;</span><span class="fm">format!</span><span class="p">(</span><span class="s">&#34;Random number: </span><span class="si">{}</span><span class="s">&#34;</span><span class="p">,</span><span class="w"> </span><span class="n">js_sys</span>::<span class="n">Math</span>::<span class="n">random</span><span class="p">()).</span><span class="n">into</span><span class="p">());</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="p">}</span><span class="w">
</span></span></span></code></pre></div><p>That works as expected: <code>Random number: 0.583796439883168</code></p>
<h3 id="printing-the-grid">Printing the grid</h3>
<p>For rendering the grid it would be ideal to only print 1 character per enum variant so all the positions align. Let&rsquo;s update the Display trait implementation.</p>
<p>If you rememeber from the last time, the Display trait is implemented so when we try to print each of the <code>TileType</code> variants the actual string will be displayed.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-rust" data-lang="rust"><span class="line"><span class="cl"><span class="k">impl</span><span class="w"> </span><span class="n">fmt</span>::<span class="n">Display</span><span class="w"> </span><span class="k">for</span><span class="w"> </span><span class="n">TileType</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">fn</span> <span class="nf">fmt</span><span class="p">(</span><span class="o">&amp;</span><span class="bp">self</span><span class="p">,</span><span class="w"> </span><span class="n">f</span>: <span class="kp">&amp;</span><span class="nc">mut</span><span class="w"> </span><span class="n">core</span>::<span class="n">fmt</span>::<span class="n">Formatter</span><span class="o">&lt;</span><span class="nb">&#39;_</span><span class="o">&gt;</span><span class="p">)</span><span class="w"> </span>-&gt; <span class="nc">core</span>::<span class="n">fmt</span>::<span class="nb">Result</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="kd">let</span><span class="w"> </span><span class="n">tile_repr</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="k">match</span><span class="w"> </span><span class="bp">self</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="n">TileType</span>::<span class="n">Red</span><span class="w"> </span><span class="o">=&gt;</span><span class="w"> </span><span class="s">&#34;R&#34;</span><span class="p">,</span><span class="w"> </span><span class="c1">// formerly: Red
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="w">            </span><span class="n">TileType</span>::<span class="n">Black</span><span class="w"> </span><span class="o">=&gt;</span><span class="w"> </span><span class="s">&#34;B&#34;</span><span class="p">,</span><span class="w"> </span><span class="c1">// formerly: Black
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="w">            </span><span class="n">TileType</span>::<span class="n">Empty</span><span class="w"> </span><span class="o">=&gt;</span><span class="w"> </span><span class="s">&#34;_&#34;</span><span class="p">,</span><span class="w"> </span><span class="c1">// formerly: Empty
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="w">        </span><span class="p">};</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="fm">write!</span><span class="p">(</span><span class="n">f</span><span class="p">,</span><span class="w"> </span><span class="s">&#34;{tile_repr}&#34;</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="p">}</span><span class="w">
</span></span></span></code></pre></div><p>Let&rsquo;s try printing the enum variant.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-rust" data-lang="rust"><span class="line"><span class="cl"><span class="kd">let</span><span class="w"> </span><span class="n">tile_1</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">TileType</span>::<span class="n">Red</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="n">web_sys</span>::<span class="n">console</span>::<span class="n">log_1</span><span class="p">(</span><span class="o">&amp;</span><span class="fm">format!</span><span class="p">(</span><span class="s">&#34;Tile 1 belongs to: </span><span class="si">{}</span><span class="s">&#34;</span><span class="p">,</span><span class="w"> </span><span class="n">tile_1</span><span class="p">).</span><span class="n">into</span><span class="p">());</span><span class="w">
</span></span></span></code></pre></div><p>This now prints: <code>Tile 1 belongs to: R</code></p>
<p>And with that we&rsquo;re ready to print the entire connect 4 board. As printing an empty board is not fun, let&rsquo;s generate a randomized board using the <code>js_sys::Math::random()</code> function.</p>
<h2 id="game-board">Game Board</h2>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-rust" data-lang="rust"><span class="line"><span class="cl"><span class="c1">//  all usages of `const`s are replaced by the value at compilation
</span></span></span><span class="line"><span class="cl"><span class="c1">//  ↓
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="k">const</span><span class="w"> </span><span class="no">ROWS_COUNT</span>: <span class="kt">usize</span> <span class="o">=</span><span class="w"> </span><span class="mi">6</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="k">const</span><span class="w"> </span><span class="no">COLS_COUNT</span>: <span class="kt">usize</span> <span class="o">=</span><span class="w"> </span><span class="mi">7</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="sd">/// This returns a Vec&lt;TileType&gt; of the given length
</span></span></span><span class="line"><span class="cl"><span class="sd">///
</span></span></span><span class="line"><span class="cl"><span class="sd"></span><span class="k">fn</span> <span class="nf">get_random_variants</span><span class="p">(</span><span class="n">n</span>: <span class="kt">usize</span><span class="p">)</span><span class="w"> </span>-&gt; <span class="nb">Vec</span><span class="o">&lt;</span><span class="n">TileType</span><span class="o">&gt;</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kd">let</span><span class="w"> </span><span class="k">mut</span><span class="w"> </span><span class="n">values</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nb">Vec</span>::<span class="n">with_capacity</span><span class="p">(</span><span class="n">n</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">for</span><span class="w"> </span><span class="n">_</span><span class="w"> </span><span class="k">in</span><span class="w"> </span><span class="mi">0</span><span class="o">..</span><span class="n">n</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="c1">//                                                 for 3 enum variants
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="w">        </span><span class="c1">//                                                 ↓
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="w">        </span><span class="kd">let</span><span class="w"> </span><span class="n">random_variant_idx</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">(</span><span class="n">js_sys</span>::<span class="n">Math</span>::<span class="n">random</span><span class="p">()</span><span class="w"> </span><span class="o">*</span><span class="w"> </span><span class="mf">3.0</span><span class="p">).</span><span class="n">floor</span><span class="p">()</span><span class="w"> </span><span class="k">as</span><span class="w"> </span><span class="kt">u8</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="kd">let</span><span class="w"> </span><span class="n">variant</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="k">match</span><span class="w"> </span><span class="n">random_variant_idx</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="mi">0</span><span class="w"> </span><span class="o">=&gt;</span><span class="w"> </span><span class="n">TileType</span>::<span class="n">Empty</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="mi">1</span><span class="w"> </span><span class="o">=&gt;</span><span class="w"> </span><span class="n">TileType</span>::<span class="n">Red</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="mi">2</span><span class="w"> </span><span class="o">=&gt;</span><span class="w"> </span><span class="n">TileType</span>::<span class="n">Black</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="n">_</span><span class="w"> </span><span class="o">=&gt;</span><span class="w"> </span><span class="fm">panic!</span><span class="p">(</span><span class="s">&#34;Unexpected value encountered&#34;</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="p">};</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">values</span><span class="p">.</span><span class="n">push</span><span class="p">(</span><span class="n">variant</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="c1">// anything not followed by a semi-colon is returned
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="w">    </span><span class="n">values</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="p">}</span><span class="w">
</span></span></span></code></pre></div><p>This should be fairly similar to a JavaScript function that tries to populate an array with enum values.
The <code>as</code> keyword is used to cast one type of number into another, and we want to go from a floating point number to a whole number.
In this case as we&rsquo;re never going to have more than 3 variants, there&rsquo;s no need to store anything larger than an unsigned 8-bit int (max value: 255).</p>
<p>Let&rsquo;s print this sucker.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-rust" data-lang="rust"><span class="line"><span class="cl"><span class="cp">#[wasm_bindgen]</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="k">pub</span><span class="w"> </span><span class="k">fn</span> <span class="nf">say_hello</span><span class="p">()</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kd">let</span><span class="w"> </span><span class="n">board</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">get_random_variants</span><span class="p">(</span><span class="no">ROWS_COUNT</span><span class="w"> </span><span class="o">*</span><span class="w"> </span><span class="no">COLS_COUNT</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="c1">// first create a string
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="w">    </span><span class="kd">let</span><span class="w"> </span><span class="k">mut</span><span class="w"> </span><span class="n">output</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nb">String</span>::<span class="n">new</span><span class="p">();</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="c1">//           without enumerate we won&#39;t have access to the index
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="w">    </span><span class="c1">//           │
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="w">    </span><span class="c1">//           │                      index
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="w">    </span><span class="c1">//           ↓                      ↓
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="w">    </span><span class="n">board</span><span class="p">.</span><span class="n">iter</span><span class="p">().</span><span class="n">enumerate</span><span class="p">().</span><span class="n">for_each</span><span class="p">(</span><span class="o">|</span><span class="p">(</span><span class="n">i</span><span class="p">,</span><span class="w"> </span><span class="n">tile</span><span class="p">)</span><span class="o">|</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="k">if</span><span class="w"> </span><span class="n">i</span><span class="w"> </span><span class="o">&gt;</span><span class="w"> </span><span class="mi">0</span><span class="w"> </span><span class="o">&amp;&amp;</span><span class="w"> </span><span class="n">i</span><span class="w"> </span><span class="o">%</span><span class="w"> </span><span class="no">COLS_COUNT</span><span class="w"> </span><span class="o">==</span><span class="w"> </span><span class="mi">0</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="n">output</span><span class="p">.</span><span class="n">push</span><span class="p">(</span><span class="sc">&#39;\n&#39;</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">output</span><span class="p">.</span><span class="n">push_str</span><span class="p">(</span><span class="o">&amp;</span><span class="fm">format!</span><span class="p">(</span><span class="s">&#34;</span><span class="si">{}</span><span class="s"> &#34;</span><span class="p">,</span><span class="w"> </span><span class="n">tile</span><span class="p">));</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">});</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">output</span><span class="p">.</span><span class="n">push</span><span class="p">(</span><span class="sc">&#39;\n&#39;</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">web_sys</span>::<span class="n">console</span>::<span class="n">log_1</span><span class="p">(</span><span class="o">&amp;</span><span class="n">output</span><span class="p">.</span><span class="n">into</span><span class="p">());</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="p">}</span><span class="w">
</span></span></span></code></pre></div><p>This should print a nice randomly populated grid of <code>R | B | _</code>.</p>
<p>Connect-4 boards cannot assume arbitrary random states due to the constraint of each tile dropping to the lowest available position. However we&rsquo;ll come back to that after we look at sending this data over to the JavaScript side.</p>
<h3 id="returning-the-grid">Returning the grid</h3>
<p>Let&rsquo;s expose our <code>TileType</code> enum to the JavaScript side by adding a <code>#[wasm_bindgen]</code> macro at it&rsquo;s top.</p>
<p>So far we&rsquo;ve only exposed a function and an enum to the JavaScript side, however another data type <code>struct</code> is more suitable for returning custom data structures.
Let&rsquo;s start by creating a new struct that can be later reused to implement the gameplay.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-rust" data-lang="rust"><span class="line"><span class="cl"><span class="cp">#[wasm_bindgen]</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="k">pub</span><span class="w"> </span><span class="k">struct</span> <span class="nc">Board</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">row_count</span>: <span class="kt">usize</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">col_count</span>: <span class="kt">usize</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="p">}</span><span class="w">
</span></span></span></code></pre></div><p>Nothing new here, the <code>wasm_bindgen</code> macro will create the JavaScript glue code for this struct. <code>usize</code> just means unsigned int corresponding to the size of memory addresses for your particular machine. For most modern machines this translates to <code>u64</code>.</p>
<p>Now let&rsquo;s expose a constructor:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-rust" data-lang="rust"><span class="line"><span class="cl"><span class="cp">#[wasm_bindgen]</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="k">impl</span><span class="w"> </span><span class="n">Board</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">pub</span><span class="w"> </span><span class="k">fn</span> <span class="nf">new</span><span class="p">(</span><span class="n">row_count</span>: <span class="kt">usize</span><span class="p">,</span><span class="w"> </span><span class="n">col_count</span>: <span class="kt">usize</span><span class="p">)</span><span class="w"> </span>-&gt; <span class="nc">Self</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="bp">Self</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="n">row_count</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="n">col_count</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="p">}</span><span class="w">
</span></span></span></code></pre></div><p>The glue code for struct results in a JavaScript class. All JavaScript classes (other than pure static ones) need to be instantiated. <code>new</code> constructor ends up becoming a <code>static</code> method on the corresponding <code>Board</code> class in the JavaScript package.
We can instantiate it like this: <code>const board = Board.new(6, 7)</code>.</p>
<p>Let&rsquo;s add another method that can return the randomized board we generated earlier:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-rust" data-lang="rust"><span class="line"><span class="cl"><span class="w">    </span><span class="c1">//                     1. Uint8Array
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="w">    </span><span class="c1">//                     ↓
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="k">pub</span><span class="w"> </span><span class="k">fn</span> <span class="nf">get_board</span><span class="p">(</span><span class="o">&amp;</span><span class="bp">self</span><span class="p">)</span><span class="w"> </span>-&gt; <span class="nc">js_sys</span>::<span class="n">Uint8Array</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kd">let</span><span class="w"> </span><span class="n">board</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">get_random_variants</span><span class="p">(</span><span class="bp">self</span><span class="p">.</span><span class="n">row_count</span><span class="w"> </span><span class="o">*</span><span class="w"> </span><span class="bp">self</span><span class="p">.</span><span class="n">col_count</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="c1">// js_sys crate includes utility functions to convert `slices` to typed arrays
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="w">    </span><span class="c1">//
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="w">    </span><span class="c1">//                       2. slice
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="w">    </span><span class="c1">//                       ↓
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="w">    </span><span class="n">js_sys</span>::<span class="n">Uint8Array</span>::<span class="n">from</span><span class="p">(</span><span class="o">&amp;</span><span class="n">board</span><span class="p">[</span><span class="o">..</span><span class="p">])</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="p">}</span><span class="w">
</span></span></span></code></pre></div><h5 id="1-1">1.</h5>
<p>The best way to return array like data from Rust to JavaScript is <code>TypedArray</code>s, and in this case it ends up being a <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Uint8Array">Uint8Array</a>. As each board position can only take one of three enum variants, 8-bits are more than sufficient.</p>
<h5 id="2-1">2.</h5>
<p>Slices are one of three array like data types available in Rust. The details are outside the scope of this tutorial and the Rust book provides a much better explanation than I&rsquo;d be able to <a href="https://doc.rust-lang.org/book/ch04-03-slices.html">here</a>.</p>
<p>Now let&rsquo;s get rid of our <code>say_hello</code> function as we won&rsquo;t been needing that anymore, and print the data received on the JavaScript side.</p>
<p>This is the entirely of the script now:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-javascript" data-lang="javascript"><span class="line"><span class="cl"><span class="kr">import</span> <span class="nx">init</span><span class="p">,</span> <span class="p">{</span> <span class="nx">Board</span><span class="p">,</span> <span class="nx">TileType</span> <span class="p">}</span> <span class="nx">from</span> <span class="s2">&#34;./connectors/connectors.js&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="kr">const</span> <span class="nx">ROW_COUNT</span> <span class="o">=</span> <span class="mi">6</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="kr">const</span> <span class="nx">COL_COUNT</span> <span class="o">=</span> <span class="mi">7</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="c1">// this duplication is necessary
</span></span></span><span class="line"><span class="cl"><span class="c1">// just so it&#39;s easy to render as a grid
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="kr">const</span> <span class="nx">DISPLAY_TILE_MAP</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="p">[</span><span class="nx">TileType</span><span class="p">.</span><span class="nx">Empty</span><span class="p">]</span><span class="o">:</span> <span class="s2">&#34;_&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="p">[</span><span class="nx">TileType</span><span class="p">.</span><span class="nx">Red</span><span class="p">]</span><span class="o">:</span> <span class="s2">&#34;R&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="p">[</span><span class="nx">TileType</span><span class="p">.</span><span class="nx">Black</span><span class="p">]</span><span class="o">:</span> <span class="s2">&#34;B&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"><span class="p">};</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nx">init</span><span class="p">().</span><span class="nx">then</span><span class="p">(()</span> <span class="p">=&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="kr">const</span> <span class="nx">board</span> <span class="o">=</span> <span class="nx">Board</span><span class="p">.</span><span class="k">new</span><span class="p">(</span><span class="nx">ROW_COUNT</span><span class="p">,</span> <span class="nx">COL_COUNT</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">  <span class="kr">const</span> <span class="nx">tiles</span> <span class="o">=</span> <span class="nb">Array</span><span class="p">.</span><span class="nx">from</span><span class="p">(</span><span class="nx">board</span><span class="p">.</span><span class="nx">get_board</span><span class="p">()).</span><span class="nx">map</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="p">(</span><span class="nx">tileEnumValue</span><span class="p">)</span> <span class="p">=&gt;</span> <span class="nx">DISPLAY_TILE_MAP</span><span class="p">[</span><span class="nx">tileEnumValue</span><span class="p">],</span>
</span></span><span class="line"><span class="cl">  <span class="p">);</span>
</span></span><span class="line"><span class="cl">  <span class="kd">let</span> <span class="nx">boardGrid</span> <span class="o">=</span> <span class="s2">&#34;&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="nx">tiles</span><span class="p">.</span><span class="nx">forEach</span><span class="p">((</span><span class="nx">tile</span><span class="p">,</span> <span class="nx">idx</span><span class="p">)</span> <span class="p">=&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="c1">// if next tile starts a new row, add a newline
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>    <span class="nx">boardGrid</span> <span class="o">=</span> <span class="p">(</span><span class="nx">idx</span> <span class="o">+</span> <span class="mi">1</span><span class="p">)</span> <span class="o">%</span> <span class="nx">COL_COUNT</span> <span class="o">===</span> <span class="mi">0</span>
</span></span><span class="line"><span class="cl">      <span class="o">?</span> <span class="sb">`</span><span class="si">${</span><span class="nx">boardGrid</span><span class="si">}</span><span class="sb"> </span><span class="si">${</span><span class="nx">tile</span><span class="si">}</span><span class="sb"> \n`</span>
</span></span><span class="line"><span class="cl">      <span class="o">:</span> <span class="sb">`</span><span class="si">${</span><span class="nx">boardGrid</span><span class="si">}</span><span class="sb"> </span><span class="si">${</span><span class="nx">tile</span><span class="si">}</span><span class="sb">`</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="p">});</span>
</span></span><span class="line"><span class="cl">  <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nx">boardGrid</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="p">});</span>
</span></span></code></pre></div><p>This should print an entire 7x6 grid of randomized Tiles.</p>
<h3 id="rendering-board-as-html">Rendering Board as HTML</h3>
<p>This part should be familiar. As we have all the data on the JavaScript side, we can use it to render the <code>Board</code> as HTML instead of logging it to the console.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-javascript" data-lang="javascript"><span class="line"><span class="cl"><span class="c1">// ...
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="kr">const</span> <span class="nx">fragment</span> <span class="o">=</span> <span class="nb">document</span><span class="p">.</span><span class="nx">createDocumentFragment</span><span class="p">();</span>
</span></span><span class="line"><span class="cl"><span class="c1">// create a span per tile and display the TileType
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="nx">tiles</span><span class="p">.</span><span class="nx">forEach</span><span class="p">((</span><span class="nx">tile</span><span class="p">,</span> <span class="nx">idx</span><span class="p">)</span> <span class="p">=&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="kr">const</span> <span class="nx">span</span> <span class="o">=</span> <span class="nb">document</span><span class="p">.</span><span class="nx">createElement</span><span class="p">(</span><span class="s2">&#34;span&#34;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">  <span class="nx">span</span><span class="p">.</span><span class="nx">textContent</span> <span class="o">=</span> <span class="nx">tile</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="nx">fragment</span><span class="p">.</span><span class="nx">appendChild</span><span class="p">(</span><span class="nx">span</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="p">});</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kr">const</span> <span class="nx">gameBoard</span> <span class="o">=</span> <span class="nb">document</span><span class="p">.</span><span class="nx">getElementById</span><span class="p">(</span><span class="s2">&#34;game-board&#34;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="nx">gameBoard</span><span class="p">.</span><span class="nx">innerHTML</span> <span class="o">=</span> <span class="s2">&#34;&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="nx">gameBoard</span><span class="p">.</span><span class="nx">appendChild</span><span class="p">(</span><span class="nx">fragment</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1">// ...
</span></span></span></code></pre></div><p>We won&rsquo;t go into the CSS changes, but feel free to get creative here.
The full project at this stage is <a href="https://github.com/tauseefk/connectors/tree/rendering-html-grid">here</a>.</p>
<p>We&rsquo;re getting close to gameplay now. In the next post we&rsquo;ll look into adding player turns to the Board struct, and some methods that allow players to make moves.</p>
]]></content:encoded></item><item><title>Man vs Vibes</title><link>https://www.afloat.boats/posts/man-vs-vibes/</link><pubDate>Fri, 22 Aug 2025 11:55:13 -0700</pubDate><guid>https://www.afloat.boats/posts/man-vs-vibes/</guid><description>Got nerd sniped into missing a workout, and improving the performace of rendering 500k rectangles by 10x.</description><content:encoded><![CDATA[<p>Yesterday I got nerd sniped into missing a workout.</p>
<figure>
    <video
        class='video-shortcode max-w-full'
        preload=''
        controls
    >
        <source src='/rectangles.webm' type='video/webm' />
    </video>
</figure>

<p><a href="/frankenpenguin/?rectangles=200000" target="_blank" rel="noopener noreferrer">Frankenpenguin</a> | <a href="https://github.com/tauseefk/frankenpenguin" target="_blank" rel="noopener noreferrer">Repository</a></p>
<p>[2:30 PM] I discovered a blog post about vibe coded performance benchmarks for drawing a large number of rectangles on the HTML canvas.
It was an interesting read and I was nodding along until I saw that all the Typescript benchmarks were better than the Rust ones. That can&rsquo;t be right?</p>
<p>[3:00 PM] My hunch was that most of the time was spent crossing the boundary between Rust and Javascript. This is something I&rsquo;ve been burned by in the past.</p>
<p>[3:30 PM] I decoupled the work scheduling from the sync compute and rendering [1].
Deleted a few lines of code that were creating the render loop and recursively calling it from the <code>request_animation_frame</code>. And then moved the scheduling to Javascript.</p>
<h5 id="before">BEFORE</h5>
<pre tabindex="0"><code>// Rust updates the rectangle positions
//      renders to canvas
//      updates the fps counter
//      recursively schedules next render loop

┌─────────────────┐                       ┌───────────────────────────────────┐
│  Javascript     │                       │  Rust                             │
│                 │                       │                                   │
│                 │                       │  ┌─────────────────────────────┐  │
│                 │                       │  │ start                       │  │
│                 │                       │  │                             │  │
│                 │                       │  │ ┌──────────────────────┐    │  │
│                 │                       │  │ │ start_animation_loop │    │  │
│                 │                       │  │ │                      │◀┐  │  │
│                 │  wasm_bindgen(start)  │  │ └──────┬───────────────┘ │  │  │
│                 │  ──────────────────▶  │  │        │                 │  │  │
│                 │                       │  │        ▼                 │  │  │
│                 │                       │  │ ┌──────────────────────┐ │  │  │
│                 │                       │  │ │ render               │ │  │  │
│                 │                       │  │ │                      │ │  │  │
│                 │                       │  │ └──────┬───────────────┘ │  │  │
│                 │                       │  │        │                 │  │  │
│                 │                       │  │        └─────────────────┘  │  │
│                 │                       │  └─────────────────────────────┘  │
└─────────────────┘                       └───────────────────────────────────┘
</code></pre><h5 id="after">AFTER</h5>
<pre tabindex="0"><code>// JavaScript calls the `frankenpenguin.tick()`

// Rust       updates the rectangle positions
//            renders to canvas

// JavaScript updates the fps counter
//            recursively schedules the ticks

┌────────────────────────────────────┐                   ┌────────────────────┐
│  JavaScript                        │                   │ Rust               │
│                                    │                   │                    │
│  ┌────────────────────────────┐    │                   │                    │
│  │ render                     │◀─┐ │                   │                    │
│  │                            │  │ │                   │                    │
│  │ ┌───────────────────────┐  │  │ │                   │ ┌──────────────┐   │
│  │ │                       │  │  │ │                   │ │ tick         │   │
│  │ │                       │  │  │ │                   │ │              │   │
│  │ │                       │  │  │ │                   │ └──┬───────────┘   │
│  │ │                       │  │  │ │                   │    ▼               │
│  │ │ frankenPenguin.tick() │────────────────────────▶  │ ┌──────────────┐   │
│  │ │                       │  │  │ │                   │ │ render       │   │
│  │ │ requestAnimationFrame │─────┘ │                   │ │              │   │
│  │ │                       │  │    │                   │ └──────────────┘   │
│  │ └───────────────────────┘  │    │                   │                    │
│  └────────────────────────────┘    │                   │                    │
└────────────────────────────────────┘                   └────────────────────┘
</code></pre><p>[3:45 PM] I run the benchmarks. Hmmmm, it&rsquo;s 2x faster, not bad. I was mistakenly comparing the updated Rust + WebGL implementation to the TS + WebGL version.</p>
<p>[4:00 PM] The train is unusually late.</p>
<p>[4:38 PM] I get to the gym, but the person turns me away as I&rsquo;m too late [2].</p>
<p>[5:21 PM] I push the repository to GitHub.</p>
<p>[This morning] I realize that I was comparing the wrong benchmarks and the diff resulted in a 10x bump in perf.</p>
<p>Aside 1:
To schedule async work in the browser you can pass a callback to the <code>requestAnimationFrame</code> API.
As Rust doesn&rsquo;t ship with a runtime, scheduling work requires using the closest one avaiable. In the browser it ends up being&hellip; the browser. So you have to use a crate that would provide the bindings to the <code>requestAnimationFrame</code> API.</p>
<p>Aside 2:
I ended with playing Table Tennis afterwards so it&rsquo;s all good.</p>
]]></content:encoded></item><item><title>Rust for JavaScript Engineers - Building Connect-4</title><link>https://www.afloat.boats/posts/rust-for-javascript-engineers-pt-1/</link><pubDate>Tue, 19 Aug 2025 16:11:10 -0701</pubDate><guid>https://www.afloat.boats/posts/rust-for-javascript-engineers-pt-1/</guid><description>Project structure, wasm-bindgen, enums, and traits — first steps toward building Connect-4 with Rust and WASM.</description><content:encoded><![CDATA[<p><a href="/connectors" target="_blank" rel="noopener noreferrer">Connect Four</a> | <a href="https://github.com/tauseefk/connectors" target="_blank" rel="noopener noreferrer">Repository</a></p>
<p>[Edit] After receiving some generous feedback from my friend <a href="https://kempf.dev/">Tim</a>, I&rsquo;ve made some changes to clarify confusing topics, and added another post that should make it easier for first time Rust users to get started <a href="/posts/rust-for-javascript-engineers-prelude">here</a>. If You&rsquo;re already familiar with Rust project structures, and have written some amount of Rust, please continue reading.</p>
<hr>
<p>When I first wanted to learn Rust in 2017, I had no idea where to start. I had written some <code>C</code> starting back in 7th grade, however I wasn&rsquo;t particularly good at it. The only language I was competent at was JavaScript, and there weren&rsquo;t a lot of resources that bridged the gap between JavaScript and Rust.</p>
<p>My hope with this series is that it&rsquo;ll allow people familiar with JavaScript to incrementally adopt Rust.</p>
<p>There are a lot of similarities between the ecosystems surrounding these languages. As all the Rust code is compiled to WASM, it can open up a path for adoption into existing JavaScript project workflows, hopefully lowering the barrier of entry.</p>
<p>There are also a lot of differences between these languages, some of which might be out of the scope of this series. I&rsquo;ll add links to more information when encountering jargon that might not make the most sense to somebody unfamiliar with the Rust ecosystem just yet.</p>
<p>In this series I go over building a small browser based Connect-4 game, which uses Rust compiled to WebAssembly for the core gameplay mechanics and state management.
This is very much a follow along, as that&rsquo;s what I find most effective for information retention.
<a href="https://github.com/tauseefk/connectors">Here is the accompanying repository</a>, that I&rsquo;ll use for reference. It can also serve as checkpoints in case the readers find their own implementations diverge.</p>
<h3 id="directory-structure">Directory Structure</h3>
<pre tabindex="0"><code>connectors/
├─ connecto.rs/                // 1. workspace
│  ├─ build.sh                 // 2. WASM build script
│  └─ connectors/
│     └─ src/lib.rs            // 3. Rust crate&#39;s (connectors) entry point
└─ www/                        // 4. client-side code
   ├─ connectors/              //    WASM package
   └─ src/                     //    JS entrypoint
</code></pre><p>This is the general directory structure I use for building applications with Rust and JavaScript. I&rsquo;ve found that this works best for building the rust crates <a href="https://doc.rust-lang.org/book/ch07-01-packages-and-crates.html">[1]</a> into packages that can be directly consumed by the JavaScript projects.</p>
<h5 id="1">1.</h5>
<p>Here is the definition of Cargo workspaces from <a href="https://doc.rust-lang.org/book/ch14-03-cargo-workspaces.html">the Rust Book</a>.</p>
<blockquote>
<p>&ldquo;&hellip;you might find that the library crate continues to get bigger and you want to split your package further into multiple library crates. Cargo offers a feature called workspaces that can help manage multiple related packages that are developed in tandem.&rdquo;</p></blockquote>
<p>It&rsquo;s similar in nature to a <code>yarn</code> workspace where different npm packages can share project dependencies.</p>
<h5 id="2">2.</h5>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="c1"># connecto.rs/build.sh</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1">#!/bin/bash</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nb">set</span> -euo pipefail
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nv">TARGET</span><span class="o">=</span>web
</span></span><span class="line"><span class="cl"><span class="nv">OUTDIR</span><span class="o">=</span>../../www/connectors
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># The only interesting part</span>
</span></span><span class="line"><span class="cl">wasm-pack build connectors --target <span class="nv">$TARGET</span> --release --out-dir <span class="nv">$OUTDIR</span>
</span></span></code></pre></div><p>It basically means that we&rsquo;re going to compile the <code>connectors</code> crate for <code>web</code> target into the JavaScript project&rsquo;s directory so it can be imported.</p>
<h5 id="3">3.</h5>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-rust" data-lang="rust"><span class="line"><span class="cl"><span class="c1">// connecto.rs/connectors/src/lib.rs
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="c1">// use statements are similar to an `import` statement
</span></span></span><span class="line"><span class="cl"><span class="c1">// here we&#39;re importing the `wasm_bindgen` macro from `wasm_bindgen` crate&#39;s `prelude` module
</span></span></span><span class="line"><span class="cl"><span class="c1">// &lt;crate&gt;::&lt;module&gt;::&lt;macro&gt;
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="k">use</span><span class="w"> </span><span class="n">wasm_bindgen</span>::<span class="n">prelude</span>::<span class="n">wasm_bindgen</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="c1">// wasm_bindgen takes care of creating the glue code we need to call the `say_hello` function from JavaScript
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="cp">#[wasm_bindgen]</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="k">pub</span><span class="w"> </span><span class="k">fn</span> <span class="nf">say_hello</span><span class="p">()</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="c1">// web_sys crate encapsulates a bunch of useful JavaScript functions for ease of use
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="w">  </span><span class="c1">//
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="w">  </span><span class="c1">//                      reference       into
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="w">  </span><span class="c1">//                      ↓               ↓
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="w">  </span><span class="n">web_sys</span>::<span class="n">console</span>::<span class="n">log_1</span><span class="p">(</span><span class="o">&amp;</span><span class="s">&#34;Hello, WASM!&#34;</span><span class="p">.</span><span class="n">into</span><span class="p">());</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="p">}</span><span class="w">
</span></span></span></code></pre></div><p>In JavaScript all non-primitives types are passed by reference, but in Rust you have to specify whether something is a reference or a value. For more details on this topic, check out this section of <a href="https://doc.rust-lang.org/book/ch04-02-references-and-borrowing.html">the Rust Book</a>.
Will come back to the <code>into</code> call at a later point, but here&rsquo;s what <a href="https://doc.rust-lang.org/rust-by-example/conversion/from_into.html">Rust by Example</a> has to say.</p>
<h5 id="4">4.</h5>
<p>The client side code will be the most familiar to JavaScript engineers, it&rsquo;s a single JavaScript file that starts an <code>http</code> server using <code>node</code> and serves the <code>index.html</code>.</p>
<p>Let&rsquo;s look at the javascript code:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-javascript" data-lang="javascript"><span class="line"><span class="cl"><span class="c1">// www/index.js
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>
</span></span><span class="line"><span class="cl"><span class="kr">import</span> <span class="nx">init</span><span class="p">,</span> <span class="p">{</span> <span class="nx">say_hello</span> <span class="p">}</span> <span class="nx">from</span> <span class="s2">&#34;./connectors/connectors.js&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1">// WASM modules compiled for the web require initialization
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="nx">init</span><span class="p">().</span><span class="nx">then</span><span class="p">(()</span> <span class="p">=&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="c1">// calling Rust function we declared earlier
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>  <span class="nx">say_hello</span><span class="p">();</span>
</span></span><span class="line"><span class="cl"><span class="p">});</span>
</span></span></code></pre></div><h2 id="running-the-code">Running the code</h2>
<p>Now that the project structure is somewhat out of the way, let&rsquo;s trying running the code.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="c1"># building the engine</span>
</span></span><span class="line"><span class="cl"><span class="nb">cd</span> connecto.rs
</span></span><span class="line"><span class="cl">chmod +x build.sh
</span></span><span class="line"><span class="cl">./build.sh
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># starting the node server</span>
</span></span><span class="line"><span class="cl"><span class="nb">cd</span> www
</span></span><span class="line"><span class="cl">node server.mjs
</span></span></code></pre></div><p>If you&rsquo;ve been following along so far, open up <code>http://localhost:8000</code> in your favorite browser, &ldquo;Hello, WASM!&rdquo; should be logged in the console.</p>
<h2 id="connect-four">Connect Four</h2>
<p>At this point we&rsquo;ve (hopefully) established a good baseline understanding of different parts of the codebase, we can start working towards the connect four game.</p>
<p>Let&rsquo;s look at the crate changes first.</p>
<h5 id="enginers">engine.rs</h5>
<p>This is the module which will hold all the data structures relevant to the gameplay.
The most important part of connect-4 are the states of each position.
All three configurations &ldquo;empty&rdquo;, &ldquo;red&rdquo;, and &ldquo;black&rdquo; can be stored as variants of an <code>enum</code>. And as we need to import this enum into the entrypoint <code>lib.rs</code>, I&rsquo;m declaring it using a <code>pub</code> keyword. This is necessary as data in Rust is private by default.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-rust" data-lang="rust"><span class="line"><span class="cl"><span class="c1">// connecto.rs/connectors/src/engine.rs
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="c1">// declaring the enum public allows us to access it outside of the `engine.rs` file
</span></span></span><span class="line"><span class="cl"><span class="c1">// variants for a public enum are also public
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="w">   </span><span class="k">pub</span><span class="w"> </span><span class="k">enum</span> <span class="nc">TileType</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">       </span><span class="n">Empty</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">       </span><span class="n">Red</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">       </span><span class="n">Black</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">   </span><span class="p">}</span><span class="w">
</span></span></span></code></pre></div><h5 id="librs">lib.rs</h5>
<p>Using <code>engine.rs</code> in the entrypoint <code>lib.rs</code>.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-rust" data-lang="rust"><span class="line"><span class="cl"><span class="c1">// connecto.rs/connectors/src/lib.rs
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="c1">// this is similar to importing a local module in JavaScript
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="k">mod</span> <span class="nn">engine</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="c1">// this is a bit of controversial indirection, that isn&#39;t necessary but allows all other local modules to keep their imports organized.
</span></span></span><span class="line"><span class="cl"><span class="c1">// Instead of each module having to keep track of it&#39;s own crate and local imports, they can just say `use crate::prelude::*` and call it a day.
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="k">mod</span> <span class="nn">prelude</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="o">..</span><span class="p">.</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">pub</span><span class="w"> </span><span class="k">use</span><span class="w"> </span><span class="k">crate</span>::<span class="n">engine</span>::<span class="o">*</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="c1">// this makes everything imported inside the `prelude` module available to the `lib.rs`
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="k">use</span><span class="w"> </span><span class="n">prelude</span>::<span class="o">*</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="o">---</span><span class="w">
</span></span></span></code></pre></div><p>Now to the significant bit. I&rsquo;ve updated the <code>say_hello</code> function to declare a few variables with the <code>let</code> keyword and assigned enum variants.
The <code>let</code> keyword is different from JavaScript in that it by default doesn&rsquo;t not allow re-assignment to those variables.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-rust" data-lang="rust"><span class="line"><span class="cl"><span class="c1">// connecto.rs/connectors/src/lib.rs
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="cp">#[wasm_bindgen]</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="k">pub</span><span class="w"> </span><span class="k">fn</span> <span class="nf">say_hello</span><span class="p">()</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kd">let</span><span class="w"> </span><span class="n">tile_1</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">TileType</span>::<span class="n">Red</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kd">let</span><span class="w"> </span><span class="n">tile_2</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">TileType</span>::<span class="n">Black</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kd">let</span><span class="w"> </span><span class="n">tile_3</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">TileType</span>::<span class="n">Empty</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">web_sys</span>::<span class="n">console</span>::<span class="n">log_1</span><span class="p">(</span><span class="o">&amp;</span><span class="fm">format!</span><span class="p">(</span><span class="s">&#34;Tile 1 belongs to: </span><span class="si">{}</span><span class="s">&#34;</span><span class="p">,</span><span class="w"> </span><span class="n">tile_1</span><span class="p">).</span><span class="n">into</span><span class="p">());</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">web_sys</span>::<span class="n">console</span>::<span class="n">log_1</span><span class="p">(</span><span class="o">&amp;</span><span class="fm">format!</span><span class="p">(</span><span class="s">&#34;Tile 2 belongs to: </span><span class="si">{}</span><span class="s">&#34;</span><span class="p">,</span><span class="w"> </span><span class="n">tile_2</span><span class="p">).</span><span class="n">into</span><span class="p">());</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">web_sys</span>::<span class="n">console</span>::<span class="n">log_1</span><span class="p">(</span><span class="o">&amp;</span><span class="fm">format!</span><span class="p">(</span><span class="s">&#34;Tile 3 belongs to: </span><span class="si">{}</span><span class="s">&#34;</span><span class="p">,</span><span class="w"> </span><span class="n">tile_3</span><span class="p">).</span><span class="n">into</span><span class="p">());</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="p">}</span><span class="w">
</span></span></span></code></pre></div><p>What happens on compiling the crate via the <code>build.sh</code>?</p>
<p>I get a few errors, including the following:</p>
<pre tabindex="0"><code>error[E0277]: `engine::TileType` doesn&#39;t implement `std::fmt::Display`
  --&gt; connectors/src/lib.rs:19:63
   |
19 |     web_sys::console::log_1(&amp;format!(&#34;Tile 1 belongs to: {}&#34;, tile_1).into());
   |                                                               ^^^^^^ `engine::TileType` cannot be formatted with the default formatter
   |
   = help: the trait `std::fmt::Display` is not implemented for `engine::TileType`
   = note: in format strings you may be able to use `{:?}` (or {:#?} for pretty-print) instead
   = note: this error originates in the macro `$crate::__export::format_args` which comes from the expansion of the macro `format` (in Nightly builds, run with -Z macro-backtrace for more info)
</code></pre><p>This is the one I really care about:</p>
<pre tabindex="0"><code>the trait `std::fmt::Display` is not implemented for `engine::TileType`
</code></pre><p>Let&rsquo;s go ahead and implement the trait for TileType.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-rust" data-lang="rust"><span class="line"><span class="cl"><span class="c1">// connecto.rs/connectors/src/lib.rs
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="k">impl</span><span class="w"> </span><span class="n">fmt</span>::<span class="n">Display</span><span class="w"> </span><span class="k">for</span><span class="w"> </span><span class="n">TileType</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">fn</span> <span class="nf">fmt</span><span class="p">(</span><span class="o">&amp;</span><span class="bp">self</span><span class="p">,</span><span class="w"> </span><span class="n">f</span>: <span class="kp">&amp;</span><span class="nc">mut</span><span class="w"> </span><span class="n">core</span>::<span class="n">fmt</span>::<span class="n">Formatter</span><span class="o">&lt;</span><span class="nb">&#39;_</span><span class="o">&gt;</span><span class="p">)</span><span class="w"> </span>-&gt; <span class="nc">core</span>::<span class="n">fmt</span>::<span class="nb">Result</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="c1">// Rust&#39;s match statements are something between a ternary and a switch statement in JavaScript
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="w">        </span><span class="c1">// But as every statement in Rust is an expression (i.e. returns a value), we can assign the values to `tile_repr`
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="w">        </span><span class="c1">// And then write the value to the format &#34;stream&#34;
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="w">        </span><span class="kd">let</span><span class="w"> </span><span class="n">tile_repr</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="k">match</span><span class="w"> </span><span class="bp">self</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="n">TileType</span>::<span class="n">Empty</span><span class="w"> </span><span class="o">=&gt;</span><span class="w"> </span><span class="s">&#34;Empty&#34;</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="n">TileType</span>::<span class="n">Red</span><span class="w"> </span><span class="o">=&gt;</span><span class="w"> </span><span class="s">&#34;Player Red&#34;</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="n">TileType</span>::<span class="n">Black</span><span class="w"> </span><span class="o">=&gt;</span><span class="w"> </span><span class="s">&#34;Player Black&#34;</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="p">};</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="fm">write!</span><span class="p">(</span><span class="n">f</span><span class="p">,</span><span class="w"> </span><span class="s">&#34;{tile_repr}&#34;</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="p">}</span><span class="w">
</span></span></span></code></pre></div><p>Compiles without a hitch!
And the browser console now logs:</p>
<pre tabindex="0"><code>Tile 1 belongs to: Player Red
Tile 2 belongs to: Player Black
Tile 3 belongs to: Empty
</code></pre><p><a href="https://doc.rust-lang.org/book/ch10-02-traits.html">Traits</a> are Rust&rsquo;s way of defining shared behavior. They are more akin to composition via object extension than classical inheritence.</p>
<p>The trait <code>fmt::Display</code> requires that the function <code>fn fmt</code> be implemented, which gives our enum <code>TileType</code> the ability to define how each of its variants are to be formatted when using the <code>format!</code> macro.</p>
<p>Let&rsquo;s try one more thing, and implement a method on our enum directly called <code>is_empty</code>.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-rust" data-lang="rust"><span class="line"><span class="cl"><span class="c1">// connecto.rs/connectors/src/lib.rs
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="k">impl</span><span class="w"> </span><span class="n">TileType</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="c1">//               the borrowed self here is used for a regular method
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="w">    </span><span class="c1">//               ↓
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="w">    </span><span class="k">pub</span><span class="w"> </span><span class="k">fn</span> <span class="nf">is_empty</span><span class="p">(</span><span class="o">&amp;</span><span class="bp">self</span><span class="p">)</span><span class="w"> </span>-&gt; <span class="kt">bool</span> <span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="c1">// if the match expression is only used to return bool values, you can just use the shorthand macro
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="w">        </span><span class="fm">matches!</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span><span class="w"> </span><span class="n">TileType</span>::<span class="n">Empty</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="p">}</span><span class="w">
</span></span></span></code></pre></div><p>Enums in Rust can also have methods.
This allows us to check if a particular <code>TileType</code> variant is empty, like the following:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-rust" data-lang="rust"><span class="line"><span class="cl"><span class="c1">// connecto.rs/connectors/src/lib.rs
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="cp">#[wasm_bindgen]</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="k">pub</span><span class="w"> </span><span class="k">fn</span> <span class="nf">say_hello</span><span class="p">()</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="c1">// existing code
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">web_sys</span>::<span class="n">console</span>::<span class="n">log_1</span><span class="p">(</span><span class="o">&amp;</span><span class="fm">format!</span><span class="p">(</span><span class="s">&#34;Is Tile 3 empty: </span><span class="si">{}</span><span class="s">&#34;</span><span class="p">,</span><span class="w"> </span><span class="n">tile_3</span><span class="p">.</span><span class="n">is_empty</span><span class="p">()).</span><span class="n">into</span><span class="p">());</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="p">}</span><span class="w">
</span></span></span></code></pre></div><p>After re-compiling, the console should now log four statements:</p>
<pre tabindex="0"><code>Tile 1 belongs to: Player Red
Tile 2 belongs to: Player Black
Tile 3 belongs to: Empty
is Tile 3 empty: true
</code></pre><p>Remember that this method is associated to the concrete instance of the enum, so we&rsquo;d call the method on the variant instead of the enum itself.</p>
<p>Here&rsquo;s the complete <a href="https://github.com/tauseefk/connectors/tree/data-as-enums">diff</a>.</p>
<p>In the <a href="/posts/rust-for-javascript-engineers-all-a-board">next post</a> we&rsquo;re going to look into rendering the Connect-4 board as HTML.</p>
]]></content:encoded></item><item><title>Crossing the WASM</title><link>https://www.afloat.boats/posts/crossing-the-wasm/</link><pubDate>Sun, 27 Apr 2025 19:28:31 -0700</pubDate><guid>https://www.afloat.boats/posts/crossing-the-wasm/</guid><description>Architecture of Flashlight, a browser roguelike with vanilla JS, Rust/WASM, recursive shadowcasting, and BFS pathfinding.</description><content:encoded><![CDATA[<figure>
    <video
        class='video-shortcode'
        preload=''
        controls
    >
        <source src='/flashlight.webm' type='video/webm' />
    </video>
</figure>

<p><a href="/flashlight" target="_blank" rel="noopener noreferrer">Flashlight</a> | <a href="/path" target="_blank" rel="noopener noreferrer">Perf comparison</a></p>
<p>I recently talked to a group of engineers about integrating WASM into Javascript projects. I&rsquo;ve integrated some Rust compiled to WASM into my projects a few times for various reasons ranging from performance to FOMO. I wanted to write down the architecture of a tiny game I wrote recently (with vanilla JS, HTML, and WASM), to demonstrate how to integrate very small parts of Rust using WASM into Javascript/TS projects. And eventually let it consume your entire life.</p>
<p>I&rsquo;ve been interested in roguelikes since I first saw Gridbugs&rsquo; <a href="https://gridbugs.itch.io/rain-forest">Rain Forest</a> and started reading the blog posts. The rabbit hole led me down recursive shadowcasting and the work Roguelike Celebration conference speakers have been creating. I wanted to make something for the web without an engine or external libraries, just for shits and giggles.</p>
<p>This post highlights some aspects of the architecture that I think are most crucial to recreating something like <a href="https://www.afloat.boats/flashlight/">Flashlight</a> without excessive handholding.</p>
<p>The architecture has undergone several small refactors but the following constraints have always guided the changes:</p>
<ul>
<li>run in the browser</li>
<li>fast</li>
<li>LGTM on mobile</li>
<li>no libraries</li>
<li>avoid exploits</li>
</ul>
<h2 id="current-architecture">Current Architecture</h2>
<pre tabindex="0"><code>                                          ┌────────────┐
                                          │            │
                                   ┌─────▶│Shadowcaster│
                                   │      │            │
                                   │      └────────────┘
┌─────────┐      ┌──────────┐      │      visibility
│         │      │          │      │
│   Rex   ├─────▶│Flashlight│──────┤
│         │      │          │      │
└─────────┘      └──────────┘      │      pathfinding
Javascript        Rust             │      ┌────────────┐
                  entrypoint       │      │            │
                                   └─────▶│ Pathfinder │
                                          │            │
                                          └────────────┘
</code></pre><h3 id="rex">Rex</h3>
<p>Usually when I write a new web app I just use React as it&rsquo;s straightforward and I can focus on the app logic. This time I didn&rsquo;t want to bloat the bundle size for something this simple.</p>
<p><a href="https://github.com/tauseefk/rex">Rex</a> is an incredibly thin library for wrangling observable streams. All of the event handling, data manipulation, and visual side effects are handled by it.</p>
<p><strong>It can do a handful of things</strong>:</p>
<ol>
<li>
<p>create a stream</p>
<p>1.1. from an event</p>
<p>1.2. from a timer</p>
</li>
<li>
<p>apply filtering over a stream</p>
</li>
<li>
<p>map over a stream</p>
</li>
<li>
<p>merge two streams</p>
</li>
<li>
<p>cause side effects via stream subscriptions</p>
</li>
</ol>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-typescript" data-lang="typescript"><span class="line"><span class="cl"><span class="c1">// Emit a tuple of values every 500 millisecond.
</span></span></span><span class="line"><span class="cl"><span class="c1">// Log 5 times and then unsubscribe.
</span></span></span><span class="line"><span class="cl"><span class="c1">//
</span></span></span><span class="line"><span class="cl"><span class="c1">// - `Stream.fromInterval(500)` creates a stream that emits a value every 500 milliseconds.
</span></span></span><span class="line"><span class="cl"><span class="c1">// - `Stream.fromInterval(1000)` creates a stream that emits a value every 1 second.
</span></span></span><span class="line"><span class="cl"><span class="c1">// - withLatestFrom combines both the Streams.
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="kr">const</span> <span class="nx">halfSecStream$</span> <span class="o">=</span> <span class="nx">Stream</span><span class="p">.</span><span class="nx">fromInterval</span><span class="p">(</span><span class="mi">500</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="kr">const</span> <span class="nx">mixedTimerStream$</span> <span class="o">=</span> <span class="nx">Stream</span><span class="p">.</span><span class="nx">fromInterval</span><span class="p">(</span><span class="mi">1000</span><span class="p">).</span><span class="nx">withLatestFrom</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">  <span class="nx">halfSecStream$</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nx">mixedTimerStream$</span><span class="p">.</span><span class="nx">subscribe</span><span class="p">({</span>
</span></span><span class="line"><span class="cl">  <span class="nx">next</span><span class="o">:</span> <span class="p">([</span><span class="nx">halfSecTick</span><span class="p">,</span> <span class="nx">oneSecTick</span><span class="p">])</span> <span class="o">=&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nx">halfSecTick</span><span class="p">,</span> <span class="nx">oneSecTick</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="c1">// unsubscribe after logging 5 times
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>    <span class="k">if</span> <span class="p">(</span><span class="nx">halfSecTick</span> <span class="o">&gt;=</span> <span class="mi">5</span><span class="p">)</span> <span class="nx">mixedTimerStream$</span><span class="p">.</span><span class="nx">unsubscribe</span><span class="o">?</span><span class="p">.();</span>
</span></span><span class="line"><span class="cl">  <span class="p">},</span>
</span></span><span class="line"><span class="cl">  <span class="nx">complete</span><span class="o">:</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="s2">&#34;stream concluded&#34;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">  <span class="p">},</span>
</span></span><span class="line"><span class="cl"><span class="p">});</span>
</span></span></code></pre></div><p>These are sufficient to build a tiny game like Flashlight.</p>
<h3 id="entrypoint---flashlight-crate">Entrypoint - Flashlight (crate)</h3>
<p>This is the entrypoint into the gameplay logic all of which is compiled to WASM and runs in the browser. The game uses recursive shadowcasting to calculate the visible tiles.
Initially I wrote <code>shadowcaster</code> to use on a game I wrote with Bevy (a Rust game engine), so instead of re-implementing shadowcasting in Javascript I build the game around it and used WASM to run it on the browser.
Later on in the post you&rsquo;ll see that running the core game logic in WASM provides a gameplay benefit that aligns with the core mechanic.</p>
<p>Flashlight takes care of the following:</p>
<ul>
<li>process character movement</li>
<li>process enemy movement</li>
<li>get clipped map state from camera</li>
<li>update map state</li>
<li>enforce gameplay rules like walkable tiles, consumables, combat etc</li>
</ul>
<h4 id="shadowcaster">Shadowcaster</h4>
<p>Flashlight calls into <a href="https://crates.io/crates/shadowcaster">Shadowcaster</a> to calculate the tiles visible to the character at any given time. I won&rsquo;t go much into the details of the implementation other than that it was implemented without looking at existing implementations and was pretty incorrect for a long time. Here are some references that I&rsquo;ve used while building it.</p>
<ul>
<li><a href="https://www.gridbugs.org/visible-area-detection-recursive-shadowcast/">Visible Area Detection with Recursive Shadowcast - Gridbugs</a></li>
<li><a href="https://www.adammil.net/blog/v125_Roguelike_Vision_Algorithms.html">Roguelike Vision Algorithms - Adam Milazzo</a></li>
</ul>
<p>The <code>compute_visible_tiles</code> function does the heavy lifting of finding the visible tiles and returning their positions, and Flashlight then decides how to best use that to update map state.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-rust" data-lang="rust"><span class="line"><span class="cl"><span class="c1">// Rust
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="k">pub</span><span class="w"> </span><span class="k">enum</span> <span class="nc">TileType</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">Transparent</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">Opaque</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="k">pub</span><span class="w"> </span><span class="k">struct</span> <span class="nc">TileGrid</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">pub</span><span class="w"> </span><span class="n">tiles</span>: <span class="nb">Vec</span><span class="o">&lt;</span><span class="n">TileType</span><span class="o">&gt;</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="o">..</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="k">fn</span> <span class="nf">compute_visible_tiles</span><span class="p">(</span><span class="o">&amp;</span><span class="k">mut</span><span class="w"> </span><span class="bp">self</span><span class="p">,</span><span class="w"> </span><span class="n">world</span>: <span class="kp">&amp;</span><span class="nc">TileGrid</span><span class="p">)</span><span class="w"> </span>-&gt; <span class="nc">HashMap</span><span class="o">&lt;</span><span class="n">IVec2</span><span class="p">,</span><span class="w"> </span><span class="kt">i32</span><span class="o">&gt;</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="o">..</span><span class="p">.</span><span class="w"> </span><span class="p">}</span><span class="w">
</span></span></span></code></pre></div><h4 id="pathfinder">Pathfinder</h4>
<p>This is just a naive breadth-first search implementation that allows the enemy to find the character. I wanted to keep pathfinding in a separate create as I&rsquo;ve been using it on other projects that don&rsquo;t share the core gameplay of Flashlight.
It merely takes a vector of glyphs, grid width, a starting glyph and an ending glyph, and returns the shortest set of moves between those glyphs.
The rules engine then determines what to do with all this data.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-rust" data-lang="rust"><span class="line"><span class="cl"><span class="c1">// Rust
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="k">fn</span> <span class="nf">find_path</span><span class="p">(</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">starting_map</span>: <span class="kp">&amp;</span><span class="nb">Vec</span><span class="o">&lt;</span><span class="n">Glyph</span><span class="o">&gt;</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">width</span>: <span class="kt">u8</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">from_glyph</span>: <span class="nc">Glyph</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">to_glyph</span>: <span class="nc">Glyph</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="p">)</span><span class="w"> </span>-&gt; <span class="nb">Vec</span><span class="o">&lt;</span><span class="n">Move</span><span class="o">&gt;</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="o">..</span><span class="p">.</span><span class="w"> </span><span class="p">}</span><span class="w">
</span></span></span></code></pre></div><h3 id="flashlight-crate-contd">Flashlight (crate) contd.</h3>
<p>Once the crate has all the information it updates the state and converts it to a JS <code>UInt8Array</code> to communicate the updated map state back to the JS for rendering.
As the only way to communicate between the JS context and the WASM boundary is via character move commands and a map state in the form of a <code>UInt8Array</code> there&rsquo;s little room for leaking extra information.
This ensures that the player can&rsquo;t just inspect element on the hidden glyphs to find the locations of the enemy and the health pack.
Only returning a single array of glyphs that represent each cell on the map has it&rsquo;s drawbacks. The state can only represent full moves, and no sub move data can be represented using the map state. Eg. animation direction of the character when it bumps into an obstacle is kept firmly in the JS land. Visual properties of how each cell is rendered have to be mapped from a single UInt8 (glyph).
On one hand it&rsquo;s very limiting but it also means that the frontend can be completely independent. I can write it using canvas, a native gui library, as a terminal UI etc.</p>
<p>This brings me back to Rex and how the glyphs are rendered to the screen (it&rsquo;s just HTML divs).</p>
<h3 id="ui-and-ephemeral-state-management">UI and Ephemeral State Management</h3>
<p>Rendering the UI elements and the game is handled via JS functions all of which are orchestrated using <a href="https://github.com/tauseefk/rex">Rex</a>.</p>
<p>Any state that doesn&rsquo;t impact the core gameplay stays firmly in the JS land.
The state of the UI is stored in a class which keeps track of things like character animation states, in-game dialog, dialog boxes, button states, etc.
All of this isn&rsquo;t part of the core gameplay logic (inside flashlight crate) as it can either be derived from the map state or it&rsquo;s too tightly coupled to the visual representation.
Particle effect states like position and intensity of rain, damage numbers over the player character and the monster, etc are part of the closure state of their update functions. This data doesn&rsquo;t have gameplay consequences and that&rsquo;s why colocating it inside the core engine has no benefits.</p>
<p>I want to focus on a small section of the game that triggers a state update and renders the map to demonstrate the end to end flow.
At the start of the game the user is expected to turn the flashlight on by tapping the button three consecutive times.</p>
<p><strong>Local state update</strong></p>
<p>This is an ever so slightly simplified version of the code that connects events to state updates. The code comments point to the capabilities of Rex mentioned earlier.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-typescript" data-lang="typescript"><span class="line"><span class="cl"><span class="c1">// Typescript
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>
</span></span><span class="line"><span class="cl"><span class="c1">// 1.1. create a stream from an event
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="kr">const</span> <span class="nx">keyDownInput$</span> <span class="o">=</span> <span class="nx">Stream</span><span class="p">.</span><span class="nx">fromEvent</span><span class="p">(</span><span class="s2">&#34;keydown&#34;</span><span class="p">,</span> <span class="nb">document</span><span class="p">.</span><span class="nx">body</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1">// 2. apply filtering to a stream
</span></span></span><span class="line"><span class="cl"><span class="c1">// filtered stream only contains instances of `f` keydown events
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="kr">const</span> <span class="nx">fKeyInput$</span> <span class="o">=</span> <span class="nx">keyUpInput$</span><span class="p">.</span><span class="nx">filter</span><span class="p">((</span><span class="nx">e</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="nx">keyIsF</span><span class="p">(</span><span class="nx">e</span><span class="p">));</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kr">const</span> <span class="nx">flashlightButtonClick$</span> <span class="o">=</span> <span class="nx">Stream</span><span class="p">.</span><span class="nx">fromEvent</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">  <span class="s2">&#34;click&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nb">document</span><span class="p">.</span><span class="nx">getElementById</span><span class="p">(</span><span class="s2">&#34;flashlight&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl"><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nx">flashlightButtonClick$</span>
</span></span><span class="line"><span class="cl">  <span class="c1">// 4. merge two streams
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>  <span class="p">.</span><span class="nx">withLatestFrom</span><span class="p">(</span><span class="nx">fKeyInput$</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="c1">// only continue if the flashlight is turned off
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>  <span class="p">.</span><span class="nx">filter</span><span class="p">(()</span> <span class="o">=&gt;</span> <span class="o">!</span><span class="nx">playerState</span><span class="p">.</span><span class="nx">isFlashlightOn</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="c1">// 3. map over a stream
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>  <span class="p">.</span><span class="nx">map</span><span class="p">(([</span><span class="nx">e1</span><span class="p">,</span> <span class="nx">e2</span><span class="p">])</span> <span class="o">=&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nx">e1</span><span class="o">?</span><span class="p">.</span><span class="nx">preventDefault</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">    <span class="nx">e2</span><span class="o">?</span><span class="p">.</span><span class="nx">preventDefault</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1">// update state
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>    <span class="k">return</span> <span class="p">{</span> <span class="nx">value</span>: <span class="kt">playerState.isFlashlightOn</span> <span class="p">};</span>
</span></span><span class="line"><span class="cl">  <span class="p">})</span>
</span></span><span class="line"><span class="cl">  <span class="p">.</span><span class="nx">subscribe</span><span class="p">({</span>
</span></span><span class="line"><span class="cl">    <span class="nx">next</span><span class="o">:</span> <span class="p">({</span> <span class="nx">value</span> <span class="p">})</span> <span class="o">=&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="c1">// 5. cause side effects
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>      <span class="nx">toggleFlashlight</span><span class="p">({</span> <span class="nx">value</span> <span class="p">});</span>
</span></span><span class="line"><span class="cl">    <span class="p">},</span>
</span></span><span class="line"><span class="cl">    <span class="nx">complete</span><span class="o">:</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="p">{},</span>
</span></span><span class="line"><span class="cl">  <span class="p">});</span>
</span></span></code></pre></div><p><strong>Rendering</strong></p>
<p>This is a version of the render function, simplified for illustration.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-typescript" data-lang="typescript"><span class="line"><span class="cl"><span class="c1">// Typescript
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>
</span></span><span class="line"><span class="cl"><span class="kr">export</span> <span class="kr">const</span> <span class="nx">render</span> <span class="o">=</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="c1">// this computes the visibility of each time from the player&#39;s POV
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>  <span class="k">if</span> <span class="p">(</span><span class="nx">playerState</span><span class="p">.</span><span class="nx">isFlashlightOn</span><span class="p">)</span> <span class="nx">engine</span><span class="p">.</span><span class="nx">compute_visibility</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="c1">// UInt8Array converted to an array of glyph indices
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>  <span class="kr">const</span> <span class="nx">mapState</span> <span class="o">=</span> <span class="nb">Array</span><span class="p">.</span><span class="kr">from</span><span class="p">(</span><span class="nx">engine</span><span class="p">.</span><span class="nx">get_map_state</span><span class="p">());</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="c1">// render target
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>  <span class="kr">const</span> <span class="nx">mapGridContainer</span> <span class="o">=</span> <span class="nb">document</span><span class="p">.</span><span class="nx">getElementById</span><span class="p">(</span><span class="s2">&#34;grid-container&#34;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="c1">// render each tile as a div
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>  <span class="nx">arr</span><span class="p">.</span><span class="nx">forEach</span><span class="p">((</span><span class="nx">glyphIdx</span><span class="p">,</span> <span class="nx">cellIdx</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="kr">const</span> <span class="nx">cell</span> <span class="o">=</span> <span class="nb">document</span><span class="p">.</span><span class="nx">createElement</span><span class="p">(</span><span class="s2">&#34;div&#34;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="kr">const</span> <span class="nx">cellVisibility</span> <span class="o">=</span> <span class="nx">visibility</span><span class="p">[</span><span class="nx">cellIdx</span><span class="p">];</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1">// set luminance value based on calculated visibility for the tile
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>    <span class="nx">cell</span><span class="p">.</span><span class="nx">style</span><span class="p">.</span><span class="nx">backgroundColor</span> <span class="o">=</span> <span class="sb">`hsla(44, 96, </span><span class="si">${</span><span class="nx">cellVisibility</span><span class="si">}</span><span class="sb">, 100)`</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1">// [&#39;+&#39;, &#39; &#39;, &#39;♣&#39;, &#39; &#39;, &#39;.&#39;, &#39;@&#39;, &#39;G&#39;, &#39;g&#39;]
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>    <span class="nx">cell</span><span class="p">.</span><span class="nx">textContent</span> <span class="o">=</span> <span class="nx">GLYPHS</span><span class="p">[</span><span class="nx">glyphIdx</span><span class="p">];</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="nx">mapGridContainer</span><span class="p">.</span><span class="nx">appendChild</span><span class="p">(</span><span class="nx">cell</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">  <span class="p">});</span>
</span></span><span class="line"><span class="cl"><span class="p">};</span>
</span></span></code></pre></div><p>Once the filter is satisfied the state for flashlight (in-game item) is toggled which results in the <code>compute_visibility</code> function getting invoked in the next render loop.
Flashlight uses the <code>shadowcaster</code> crate to get a hashmap of tiles that are visible.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-rust" data-lang="rust"><span class="line"><span class="cl"><span class="c1">// Rust
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="k">impl</span><span class="w"> </span><span class="n">Flashlight</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="o">..</span><span class="p">.</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">pub</span><span class="w"> </span><span class="k">fn</span> <span class="nf">compute_visibility</span><span class="p">(</span><span class="o">&amp;</span><span class="k">mut</span><span class="w"> </span><span class="bp">self</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="c1">// shadowcaster only cares if a tile is opaque or transparent
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="w">        </span><span class="c1">// so I map each Glyph to the appropriate tile type
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="w">        </span><span class="kd">let</span><span class="w"> </span><span class="n">tiles</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="bp">self</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="p">.</span><span class="n">map_state</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="p">.</span><span class="n">iter</span><span class="p">()</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="p">.</span><span class="n">map</span><span class="p">(</span><span class="o">|</span><span class="n">glyph</span><span class="o">|</span><span class="w"> </span><span class="k">match</span><span class="w"> </span><span class="n">glyph</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                </span><span class="o">|</span><span class="w"> </span><span class="n">Glyph</span>::<span class="n">Monster</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                </span><span class="o">|</span><span class="w"> </span><span class="n">Glyph</span>::<span class="n">Player</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                </span><span class="o">|</span><span class="w"> </span><span class="n">Glyph</span>::<span class="n">Floor</span><span class="w"> </span><span class="o">=&gt;</span><span class="w"> </span><span class="n">TileType</span>::<span class="n">Transparent</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                </span><span class="n">Glyph</span>::<span class="n">Target</span><span class="w"> </span><span class="o">|</span><span class="w"> </span><span class="n">Glyph</span>::<span class="n">Tree</span><span class="w"> </span><span class="o">=&gt;</span><span class="w"> </span><span class="n">TileType</span>::<span class="n">Opaque</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="p">})</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="p">.</span><span class="n">collect</span><span class="p">();</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="kd">let</span><span class="w"> </span><span class="k">mut</span><span class="w"> </span><span class="n">visibility</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">Visibility</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="o">..</span><span class="p">.</span><span class="w"> </span><span class="p">};</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">visibility</span><span class="p">.</span><span class="n">observer</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">IVec2</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="c1">// set the player position as the observer
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="w">        </span><span class="p">};</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="kd">let</span><span class="w"> </span><span class="n">visible_tiles_hashmap</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">visibility</span><span class="p">.</span><span class="n">compute_visible_tiles</span><span class="p">(</span><span class="o">&amp;</span><span class="n">TileGrid</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="n">tiles</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="o">..</span><span class="p">.</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="p">});</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="c1">// store the visibility on the main struct
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="w">        </span><span class="bp">self</span><span class="p">.</span><span class="n">visibility_state</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">visible_tiles_hashmap</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="p">}</span><span class="w">
</span></span></span></code></pre></div>]]></content:encoded></item><item><title>Component Coloring</title><link>https://www.afloat.boats/posts/component-coloring/</link><pubDate>Wed, 22 Jan 2025 20:07:42 -0800</pubDate><guid>https://www.afloat.boats/posts/component-coloring/</guid><description>NextJS 15 made sync APIs async to force component coloring for SSR boundaries. Here&amp;#39;s why.</description><content:encoded><![CDATA[<p>Full disclosure, I hate frameworks of frameworks. How does a thing become so large that it needs other large things to prop it up?
Especially if the scaffolding is opaque, and you can&rsquo;t fathom why it does what it does.
People complain about Webpack, but at least you could reason about the pipeline end-to-end.</p>
<p>I&rsquo;ve written UI apps in React since before the first major version was released. Things like a rich text editor, a photo-editor, some games etc.
Back in my day, things had lifecycle methods.. And we injected an instance of V8 inside Laravel to SSR before it was mainstream.</p>
<p>I like React, it&rsquo;s been straightforward to use. There are faster/lighter/easier things out there, some more interesting <a href="https://github.com/cyclejs/cyclejs">like this</a>. I don&rsquo;t really want to talk about them.</p>
<p>I despise NextJS, not for it&rsquo;s own faults although I can talk about those too, but for the bullshit surrounding it. From Guillermo&rsquo;s annoying tweets to the npm package name grab,
to two bilboards on the same street corner
<img loading="lazy" src="/double_vercel.png" alt="double-vercel"  />


to Youtube videos titled &ldquo;Cool FramerJS animations with NextJS&rdquo;.
Bro it&rsquo;s an SSR framework why do you need to&hellip; ugh.</p>
<p>Anyway, I recently discovered that NextJS 15 forces some sync APIs like <code>useParams</code> to be <code>async</code>. It reminded me of a discourse from a few years ago about <a href="https://journal.stuffwithstuff.com/2015/02/01/what-color-is-your-function/">function coloring</a>.</p>
<p>TLDR; NextJS needs to know which components to skip when pre-rendering, <code>async</code> is how they do it. GGPO.</p>
<h2 id="what-the-hell-is-component-coloring">What the hell is component coloring?</h2>
<p>If you’re familiar with <code>async/await</code> you’d remember that to use the output of an awaited function you have two choices:</p>
<ul>
<li>make the caller <code>async</code></li>
<li>or slap a <code>.then(callback)</code></li>
</ul>
<p>The second option isn&rsquo;t really &ldquo;using the awaited value&rdquo; as things are out of the caller&rsquo;s hands now and the awaited value can only be used in the callback.</p>
<p>The first option is what the cool kids are doing. And this &ldquo;making caller async&rdquo; can potentially go all the way to your root component. Good luck.</p>
<p>But what does this have to do with server components?
I&rsquo;m glad you asked.</p>
<p>NextJS pre-renders components. Which means it&rsquo;ll render some components before a user even asks for them. Eg: some static stuff that doesn&rsquo;t depend on user behavior. However evaluating the contents of an entire component to determine whether it can be statically rendered is costly.</p>
<p>Remember the stupid annotations &ldquo;<code>use client</code>&rdquo;? The thing that everyone slaps at the top of every component so they can keep writing React code the way they already liked. That&rsquo;s how React sorts your client/isomorphic components into neat little buckets.</p>
<p>However that&rsquo;s not good enough, you need even more fine grained buckets to make sure you&rsquo;re not wasting compute evaluating the contents of a component that&rsquo;s dependent on indeterminate data.</p>
<p>In a recent update NextJS decided to make a few APIs asynchronous that actually are not.
Think of it as an <code>async</code> IIFE, basically <code>Promise.resolve(value)</code>.</p>
<p>What in the world?</p>
<p>The reason for doing this is to force the developers to somehow color their components, which makes it easier for NextJS to determine where to draw the pre-rendering boundary.</p>
<p>An example of this is the <code>useParams</code> API.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-typescript" data-lang="typescript"><span class="line"><span class="cl"><span class="c1">// app/greeting/page.jsx
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="kr">const</span> <span class="nx">BlueComponent</span> <span class="o">=</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="k">return</span> <span class="p">&lt;</span><span class="nt">div</span><span class="p">&gt;</span><span class="nx">Hello</span><span class="o">!</span><span class="p">&lt;/</span><span class="nt">div</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1">// NextJS 14
</span></span></span><span class="line"><span class="cl"><span class="c1">// app/greeting/[:username]/page.jsx
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="kr">const</span> <span class="nx">AnotherBlueComponent</span> <span class="o">=</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="kr">const</span> <span class="p">{</span> <span class="nx">username</span> <span class="p">}</span> <span class="o">=</span> <span class="nx">useParams</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="nx">username</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="p">&lt;</span><span class="nt">BlueComponent</span> <span class="p">/&gt;</span>
</span></span><span class="line"><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="k">return</span> <span class="p">&lt;</span><span class="nt">div</span><span class="p">&gt;</span><span class="nx">Hello</span><span class="o">!</span> <span class="p">{</span><span class="nx">username</span><span class="p">}&lt;/</span><span class="nt">div</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>As the parameters come from a request, NextJS would have to see into the future to pre-render this. But as brilliant as Vercel is at marketing, they can’t really pre-render something that&rsquo;s dynamic.
Why?
Coz it could literally be anything, like social security numbers, or the phone number of your high school crush.</p>
<p>So to be able to efficiently draw a boundary around the work that NextJS needs to do around SSR they want you to declare which components are dependent on these indeterminate parameters.</p>
<p>But how can a framework be sure that developers will do the right categorization?
You could either do something like introduce a new keyword that developers would have to use to define which components our dynamically rendered like &ldquo;server pre-render pls&rdquo; and &ldquo;server skip&rdquo; or you could use something that already exists in the language.
JavaScript already has a keyword that could be helpful, but a framework can&rsquo;t still reliably ensure that a user will declare their components asynchronous. So by making a handful crucial APIs asynchronous NextJS can effectively force the developers to color their components.
If a component is blue, i.e. synchronous NextJS can pre-render it but if a component is green, i.e. asynchronous NextJS can reliably skip without leaving perf gains on the table.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-typescript" data-lang="typescript"><span class="line"><span class="cl"><span class="c1">// app/greeting/page.jsx
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="kr">const</span> <span class="nx">BlueComponent</span> <span class="o">=</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="k">return</span> <span class="p">&lt;</span><span class="nt">div</span><span class="p">&gt;</span><span class="nx">Hello</span><span class="o">!</span><span class="p">&lt;/</span><span class="nt">div</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1">// NextJS 15
</span></span></span><span class="line"><span class="cl"><span class="c1">// app/greeting/[:username]/page.jsx
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="kr">const</span> <span class="nx">GreenComponent</span> <span class="o">=</span> <span class="kr">async</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="kr">const</span> <span class="p">{</span> <span class="nx">username</span> <span class="p">}</span> <span class="o">=</span> <span class="k">await</span> <span class="nx">useParams</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">  <span class="k">return</span> <span class="p">&lt;</span><span class="nt">div</span><span class="p">&gt;</span><span class="nx">Hello</span><span class="o">!</span> <span class="p">{</span><span class="nx">username</span><span class="p">}&lt;/</span><span class="nt">div</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>Theo will tell you that these greens (components) are good for you.
I will tell you that using existing language feature to improve a framework is good but selling your soul to SSR for the &ldquo;perceived&rdquo; speed improvement is not worth it. You&rsquo;re only writing a client-side Framer animation for a marketing website anyway!</p>
<p>If you love NextJS and really disagree with everything I mention, LMK in the comments below.</p>
]]></content:encoded></item><item><title>Render, dude</title><link>https://www.afloat.boats/posts/renderdude/</link><pubDate>Thu, 07 Nov 2024 21:34:42 -0800</pubDate><guid>https://www.afloat.boats/posts/renderdude/</guid><description>Writing a minimal renderer for a photo editor using TypeScript and WebGPU.</description><content:encoded><![CDATA[<p><a href="/posts/skittles" target="_blank" rel="noopener noreferrer">Previous post</a> | <a href="/dots" target="_blank" rel="noopener noreferrer">Photo-editor</a></p>
<p>In the last post I built the UX component, but so far it doesn&rsquo;t do anything other than being highly entertaining for cats.</p>
<p>In this one I&rsquo;m gonna write a render pipeline and by the end it still won&rsquo;t makes sense how to connect the *cough* Dots.</p>
<h2 id="how-old-is-this-browser">How old is this browser?</h2>
<p>In a past life I worked on an extremely cool photo editor that had a ton of features and 100k MAUs.
It was built using WebGL, and it works on most browsers and most phones. So naturally I wanted to build this one on WebGPU which is available on less than 50% of the browsers that matter.</p>
<p>I use Firefox, so to build this thing I downloaded Firefox Nightly. Safari works too, but I had to enable the WebGPU feature flag.</p>
<h2 id="renderer">Render..er</h2>
<p>As I&rsquo;m quite an overachiever I decideed that the API had to be right from the beginning, and I couldn&rsquo;t just throw everything inside a <code>useEffect</code>.</p>
<p>So I wrote a class that takes care of initialization, tear down, and rendering. WebGPU initialization is async, and Javascript doesn&rsquo;t have async constructors so I did the right thing and routed the initialization through a static function. Do the right thing.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-typescript" data-lang="typescript"><span class="line"><span class="cl"><span class="kr">export</span> <span class="kr">class</span> <span class="nx">RenderDude</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="nx">gpuConfiguration</span>: <span class="kt">GPUConfiguration</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="c1">// making the constructor private ensures that RenderDude can&#39;t be called with `new`
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>  <span class="kr">private</span> <span class="kr">constructor</span><span class="p">(</span><span class="nx">gpuConfiguration</span>: <span class="kt">GPUConfiguration</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="k">this</span><span class="p">.</span><span class="nx">gpuConfiguration</span> <span class="o">=</span> <span class="nx">gpuConfiguration</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="c1">// this is the only API that gets called from external code to initialize the GPU and return an instance of RenderDude
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>  <span class="kr">static</span> <span class="kr">async</span> <span class="nx">init() {</span>
</span></span><span class="line"><span class="cl">    <span class="kr">const</span> <span class="nx">gpuConfig</span> <span class="o">=</span> <span class="k">await</span> <span class="nx">RenderDude</span><span class="p">.</span><span class="nx">initializeGPU</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="k">new</span> <span class="nx">RenderDude</span><span class="p">(</span><span class="nx">gpuConfig</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="c1">// this does the heavy lifting of getting the WebGPU context and initializing the pipeline
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>  <span class="kr">private</span> <span class="kr">static</span> <span class="kr">async</span> <span class="nx">initializeGPU() {</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="nx">navigator</span><span class="p">.</span><span class="nx">gpu</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="k">throw</span> <span class="k">new</span> <span class="nb">Error</span><span class="p">(</span><span class="s2">&#34;Your browser doesn&#39;t support GPUs&#34;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="kr">const</span> <span class="nx">canvas</span> <span class="o">=</span> <span class="nb">document</span><span class="p">.</span><span class="nx">getElementById</span><span class="p">(</span><span class="s2">&#34;canvas-root&#34;</span><span class="p">)</span> <span class="kr">as</span> <span class="nx">HTMLCanvasElement</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="kr">const</span> <span class="nx">adapter</span> <span class="o">=</span> <span class="k">await</span> <span class="nx">navigator</span><span class="p">.</span><span class="nx">gpu</span><span class="p">.</span><span class="nx">requestAdapter</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">    <span class="kr">const</span> <span class="nx">device</span> <span class="o">=</span> <span class="k">await</span> <span class="nx">adapter</span><span class="o">?</span><span class="p">.</span><span class="nx">requestDevice</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">    <span class="kr">const</span> <span class="nx">context</span> <span class="o">=</span> <span class="nx">canvas</span><span class="p">.</span><span class="nx">getContext</span><span class="p">(</span><span class="s2">&#34;webgpu&#34;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1">// ... pipeline init omitted for later
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>    <span class="k">return</span> <span class="p">{</span> <span class="nx">device</span><span class="p">,</span> <span class="nx">context</span><span class="p">,</span> <span class="nx">pipeline</span> <span class="p">};</span>
</span></span><span class="line"><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><h2 id="loading-an-image">Loading an image</h2>
<h3 id="vertex-shader">Vertex Shader</h3>
<p>It&rsquo;s easier to render a triangle, but that&rsquo;s unrelated to photo editors so I just skipped that part.
The simplest vertex shader I could write for the editor is one that loads an image.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-typescript" data-lang="typescript"><span class="line"><span class="cl"><span class="kr">const</span> <span class="nx">vertexShaderOutputStructName</span> <span class="o">=</span> <span class="s2">&#34;VertexShaderOutput&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="kr">const</span> <span class="nx">vertexShaderOutputStruct</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="nx">name</span>: <span class="kt">vertexShaderOutputStructName</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nx">struct</span><span class="o">:</span> <span class="sb">`
</span></span></span><span class="line"><span class="cl"><span class="sb">struct </span><span class="si">${</span><span class="nx">vertexShaderOutputStructName</span><span class="si">}</span><span class="sb"> {
</span></span></span><span class="line"><span class="cl"><span class="sb">  @builtin(position) position: vec4&lt;f32&gt;,
</span></span></span><span class="line"><span class="cl"><span class="sb">  @location(0) texcoord: vec2&lt;f32&gt;,
</span></span></span><span class="line"><span class="cl"><span class="sb">};`</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"><span class="p">};</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kr">const</span> <span class="nx">vertex</span> <span class="o">=</span> <span class="sb">`
</span></span></span><span class="line"><span class="cl"><span class="sb"></span><span class="si">${</span><span class="nx">vertexShaderOutputStruct</span><span class="p">.</span><span class="nx">struct</span><span class="si">}</span><span class="sb">
</span></span></span><span class="line"><span class="cl"><span class="sb">
</span></span></span><span class="line"><span class="cl"><span class="sb">@vertex
</span></span></span><span class="line"><span class="cl"><span class="sb">fn main(@builtin(vertex_index) vertexIndex: u32) -&gt; </span><span class="si">${</span><span class="nx">vertexShaderOutputStruct</span><span class="p">.</span><span class="nx">name</span><span class="si">}</span><span class="sb"> {
</span></span></span><span class="line"><span class="cl"><span class="sb">
</span></span></span><span class="line"><span class="cl"><span class="sb">  // I&#39;m using triangle lists, so have to pass 2 sets of 3 vertices to render a rectangle
</span></span></span><span class="line"><span class="cl"><span class="sb">  // texture coordinates go 0.0 -&gt; 1.0, across and down
</span></span></span><span class="line"><span class="cl"><span class="sb">  var pos = array&lt;vec2&lt;f32&gt;, 6&gt;(
</span></span></span><span class="line"><span class="cl"><span class="sb">    vec2f( 0.0,  0.0),  // center
</span></span></span><span class="line"><span class="cl"><span class="sb">    vec2f( 1.0,  0.0),  // right, center
</span></span></span><span class="line"><span class="cl"><span class="sb">    vec2f( 0.0,  1.0),  // center, top
</span></span></span><span class="line"><span class="cl"><span class="sb">    vec2f( 0.0,  1.0),  // center, top
</span></span></span><span class="line"><span class="cl"><span class="sb">    vec2f( 1.0,  0.0),  // right, center
</span></span></span><span class="line"><span class="cl"><span class="sb">    vec2f( 1.0,  1.0),  // right, top
</span></span></span><span class="line"><span class="cl"><span class="sb">    );
</span></span></span><span class="line"><span class="cl"><span class="sb">
</span></span></span><span class="line"><span class="cl"><span class="sb">  var vsOutput: VertexShaderOutput;
</span></span></span><span class="line"><span class="cl"><span class="sb">  let xy = pos[vertexIndex];
</span></span></span><span class="line"><span class="cl"><span class="sb">
</span></span></span><span class="line"><span class="cl"><span class="sb">  // scale the quarter screen, so it renders on the entire canvas
</span></span></span><span class="line"><span class="cl"><span class="sb">  vsOutput.position = vec4&lt;f32&gt;(xy * 2.0 - 1.0, 0.0, 1.0);
</span></span></span><span class="line"><span class="cl"><span class="sb">  vsOutput.texcoord = xy;
</span></span></span><span class="line"><span class="cl"><span class="sb">  return vsOutput;
</span></span></span><span class="line"><span class="cl"><span class="sb">}
</span></span></span><span class="line"><span class="cl"><span class="sb">`</span><span class="p">;</span>
</span></span></code></pre></div><h3 id="fragment-shader">Fragment Shader</h3>
<p>This is even simpler as all I did here was to return the color sampled from the texture, using the texture coordinates.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-typescript" data-lang="typescript"><span class="line"><span class="cl"><span class="kr">const</span> <span class="nx">fragment</span> <span class="o">=</span> <span class="sb">`
</span></span></span><span class="line"><span class="cl"><span class="sb"></span><span class="si">${</span><span class="nx">vertexShaderOutputStruct</span><span class="p">.</span><span class="nx">struct</span><span class="si">}</span><span class="sb">
</span></span></span><span class="line"><span class="cl"><span class="sb">
</span></span></span><span class="line"><span class="cl"><span class="sb">@group(0) @binding(1) var imageSampler: sampler;
</span></span></span><span class="line"><span class="cl"><span class="sb">@group(0) @binding(2) var imageTexture: texture_2d&lt;f32&gt;;
</span></span></span><span class="line"><span class="cl"><span class="sb">
</span></span></span><span class="line"><span class="cl"><span class="sb">@fragment
</span></span></span><span class="line"><span class="cl"><span class="sb">fn main(fsInput: </span><span class="si">${</span><span class="nx">vertexShaderOutputStruct</span><span class="p">.</span><span class="nx">name</span><span class="si">}</span><span class="sb">) -&gt; @location(0) vec4&lt;f32&gt; {
</span></span></span><span class="line"><span class="cl"><span class="sb">  let sampledColor = textureSample(imageTexture, imageSampler, fsInput.texcoord);
</span></span></span><span class="line"><span class="cl"><span class="sb">
</span></span></span><span class="line"><span class="cl"><span class="sb">  return sampledColor;
</span></span></span><span class="line"><span class="cl"><span class="sb">}
</span></span></span><span class="line"><span class="cl"><span class="sb">`</span><span class="p">;</span>
</span></span></code></pre></div>]]></content:encoded></item><item><title>Monochrome Skittles</title><link>https://www.afloat.boats/posts/skittles/</link><pubDate>Mon, 21 Oct 2024 21:32:15 -0700</pubDate><guid>https://www.afloat.boats/posts/skittles/</guid><description>Reverse engineering Apple&amp;#39;s 2D style edit slider for the web, with some React and a whole bunch of math.</description><content:encoded><![CDATA[<p><a href="/dots" target="_blank" rel="noopener noreferrer">Photo-editor</a> | <a href="/skittles" target="_blank" rel="noopener noreferrer">Debugger</a></p>
<p>There hasn&rsquo;t been a lot of advancement in the ease of use of photo editing tools, and the barrier remains fairly high.
Generative AI doesn&rsquo;t do as much for photo editing as it does for full blown photo generation.
So while the new iPhone and iOS launches have all been about Apple Intelligence, the feature that I&rsquo;ve loved is the new style edit tool in the Photos app. Style edit is the much needed improvement in editing UX. It&rsquo;s akin to Prometheus making fire accessible to mere mortals.. well mortals who own an iPhone 16.
It’s a 2-dimensional slider that combines something they call a palette, and a general 3-channel curves tool.
After testing a few styles I’ve concluded that what they call palette is just a look-up-table (LUT). Something colorists have used for decades to create a consistent visual language through color scheme. It’s an incredibly fast way to alter the look and feel of an image, but I digress.</p>
<h3 id="why-do-i-care-so-much">Why do I care so much?</h3>
<p>When Douglas Engelbart first introduced the mouse in &lsquo;68 it opened UX possibilities that are still the primary mode of interacting with a computer. Multi-touch screens ushered in a new era of user experiences that were not possible before, eg. doomscrolling.
Now I can’t claim that the 2D slider is going to change our world, but for lazy photographers like myself it might be the best compromise between unedited photos and a cohesive visual language.</p>
<p>What&rsquo;s annoying though is that Style editor is paywalled behind the latest version of the iPhone, and it only works on the photos taken with the iPhone 16. Bummer, I wonder if I can just write a web version.</p>
<p>This is what it looks like:</p>
<figure>
    <video
        class='video-shortcode'
        preload=''
        controls
    >
        <source src='/dots-editor.webm' type='video/webm' />
    </video>
</figure>

<h2 id="lets-bring-it-to-the-web-shall-we">Let&rsquo;s bring it to the web, shall we?</h2>
<p>As I like to have fun and not burning out on side-projects, I decided to build the toy first, that is the 2D slider component.</p>
<h4 id="rendering-the-dots">Rendering the dots</h4>
<p>I start off by creating a dot grid. CSS grid made this trivial once I decided on the number of rows and columns.
Once the cells are in place, I render a circle in the middle of each cell.</p>
<p>I wanted to make the size of each dot react to the distance from the pointer. And also ensure that the effect dissipates quickly leaving any dots outside of a certain distance un-affected.
As I want the strength of the effect to decrease sharply I can make it proportional to the inverse of some power of the distance. Square should work, right?</p>
<h4 id="calculating-distance-of-dots">Calculating distance of dots</h4>
<p>To calculate the distance I need the position of the cell’s center where the dot is rendered.
Once the cell is rendered and its layout calculated, I use <code>getBoundingClientRect</code> to get its position and size. Position of the center is just:
<code>x = (rect.left + rect.width / 2)</code>
<code>y = (rect.top + rect.height / 2)</code></p>
<p>To validate I can render the actual value of the square distance inside each cell.</p>
<p><code>squareDist = (dot.x - touchPosition.x)^2 + (dot.y - touchPosition.y)^2</code></p>
<p>Currently this gives me actual pixel distance, but what I really want is a relative distance grounded in the bounds of the container, in a range <code>[0, 1]</code>.
For instance if I move the pointer to a corner of the container, the diagonally opposite corner should be at a distance of 1.</p>
<p>The normalized square distance then becomes: <code>squareDist / (diagonal length)^2</code>
This ensures that the values we get are always under 1.</p>
<h4 id="frame-of-reference">Frame of reference</h4>
<p>The diagonal length comes from the container.
To get the bounds of the container I use <code>getBoundingClientRect</code>. It provides the left, top, height, and width of the component.
From the touch events I already have the position of the cursor, so I can determine the cursor position relative to the container.
<code>x = touchPosition.x - rect.left</code>
<code>y = touchPosition.y - rect.top</code>
<code>normalized(x) = (touchPosition.x - rect.left) / rect.width</code>
<code>normalized(y) = (touchPosition.y - rect.top) / rect.height</code>
Then clamping the value to (0, 1) would give me the position inside the container.</p>
<h4 id="dot-sizes">Dot sizes</h4>
<p>Now that I have the normalized distance, I can use that to bump the size of each dot based on an arbitrary constant multiple: <code>SCALE_MULTIPLE * (1 - normalizedSquareDist)</code>
But before that I want to debug the normalized distance values.
A fairly intuitive way to do this is to use <code>hsl</code> color values. And I replaced the dots with actual <code>clampedScale</code> values with two precision points.</p>
<p>CSS makes the first part trivial: <code>hsl(${(1 - normalizedSquareDist) * 360}, 100%, 60%)</code>. As hue values range between <code>[0, 360]</code>, and <code>normalizedSquareDist</code> ranges between <code>[0, 1]</code>, hence <code>(1 - normalizedSquareDist) * 360</code> becomes <code>[360, 0]</code> and gives me the whole rainbow.</p>
<figure>
    <video
        class='video-shortcode'
        preload=''
        controls
    >
        <source src='/skittles-1.webm' type='video/webm' />
    </video>
</figure>

<p>Check out the interactive version <a href="https://tauseefk.github.io/skittles/?variant=1" target="_blank" rel="noopener noreferrer">here</a>.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-typescript" data-lang="typescript"><span class="line"><span class="cl"><span class="kr">const</span> <span class="nx">normalizedSquareDist</span> <span class="o">=</span> <span class="nx">clamp</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">  <span class="nx">squareDistFromTouchPoint</span> <span class="o">/</span> <span class="nx">squareDiagonalLen</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="mi">0</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="mi">1</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kr">const</span> <span class="nx">clampedScale</span> <span class="o">=</span> <span class="mi">1</span> <span class="o">-</span> <span class="nx">normalizedSquareDist</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1">// ...
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>
</span></span><span class="line"><span class="cl"><span class="p">&lt;</span><span class="nt">span</span>
</span></span><span class="line"><span class="cl">  <span class="na">style</span><span class="o">=</span><span class="p">{{</span>
</span></span><span class="line"><span class="cl">    <span class="nx">color</span><span class="o">:</span> <span class="sb">`hsl(</span><span class="si">${</span><span class="p">(</span><span class="mi">1</span> <span class="o">-</span> <span class="nx">normalizedSquareDist</span><span class="p">)</span> <span class="o">*</span> <span class="mi">360</span><span class="si">}</span><span class="sb">, 100%, 60%)`</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="p">}}</span>
</span></span><span class="line"><span class="cl"><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">  <span class="p">{</span><span class="nx">clampedScale</span><span class="p">.</span><span class="nx">toFixed</span><span class="p">(</span><span class="mi">2</span><span class="p">)}</span>
</span></span><span class="line"><span class="cl"><span class="p">&lt;/</span><span class="nt">span</span><span class="p">&gt;;</span>
</span></span></code></pre></div><h4 id="interesting-results">Interesting results</h4>
<p>Now if I add scale multiple on top of the square distance and scale the text content, I get an interesting result.
The numbers are almost unreadable closer to the cursor, and it affects almost the entire grid.</p>
<figure>
    <video
        class='video-shortcode'
        preload=''
        controls
    >
        <source src='/skittles-2.webm' type='video/webm' />
    </video>
</figure>

<p><a href="https://tauseefk.github.io/skittles/?variant=2" target="_blank" rel="noopener noreferrer">Interactive</a></p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-typescript" data-lang="typescript"><span class="line"><span class="cl"><span class="c1">// normalized
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="kr">const</span> <span class="nx">normalizedSquareDist</span> <span class="o">=</span> <span class="nx">clamp</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">  <span class="nx">squareDistFromTouchPoint</span> <span class="o">/</span> <span class="nx">squareDiagonalLen</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="mi">0</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="mi">1</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kr">const</span> <span class="nx">clampedScale</span> <span class="o">=</span> <span class="nx">clamp</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">  <span class="p">(</span><span class="mi">1</span> <span class="o">-</span> <span class="nx">normalizedSquareDist</span><span class="p">)</span> <span class="o">*</span> <span class="nx">SCALE_MULTIPLE</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nx">SCALE_MIN</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nb">Number</span><span class="p">.</span><span class="nx">POSITIVE_INFINITY</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1">// ...
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>
</span></span><span class="line"><span class="cl"><span class="p">&lt;</span><span class="nt">span</span>
</span></span><span class="line"><span class="cl">  <span class="na">style</span><span class="o">=</span><span class="p">{{</span>
</span></span><span class="line"><span class="cl">    <span class="c1">// ...
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>    <span class="nx">transform</span><span class="o">:</span> <span class="sb">`scale(</span><span class="si">${</span><span class="nx">clampedScale</span><span class="si">}</span><span class="sb">)`</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="p">}}</span>
</span></span><span class="line"><span class="cl"><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">  <span class="p">{</span><span class="nx">clampedScale</span><span class="p">.</span><span class="nx">toFixed</span><span class="p">(</span><span class="mi">2</span><span class="p">)}</span>
</span></span><span class="line"><span class="cl"><span class="p">&lt;/</span><span class="nt">span</span><span class="p">&gt;;</span>
</span></span></code></pre></div><h4 id="scale-multiple-with-faster-attenuation">Scale multiple with faster attenuation</h4>
<p>To reduce the radius of the scaling, I can make the initial fraction smaller i.e. <code>1 - normalizedSquareDist * &lt;some-multiple&gt;</code>.
This causes the scaling to drop sharply creating a blob quite similar to what I initially set out to make.</p>
<figure>
    <video
        class='video-shortcode'
        preload=''
        controls
    >
        <source src='/skittles-3.webm' type='video/webm' />
    </video>
</figure>

<p><a href="https://tauseefk.github.io/skittles/?variant=3" target="_blank" rel="noopener noreferrer">Interactive</a></p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-typescript" data-lang="typescript"><span class="line"><span class="cl"><span class="c1">// normalized
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="kr">const</span> <span class="nx">normalizedSquareDist</span> <span class="o">=</span> <span class="nx">clamp</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">  <span class="nx">squareDistFromTouchPoint</span> <span class="o">/</span> <span class="nx">squareDiagonalLen</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="mi">0</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="mi">1</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kr">const</span> <span class="nx">clampedScale</span> <span class="o">=</span> <span class="nx">clamp</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">  <span class="p">(</span><span class="mi">1</span> <span class="o">-</span> <span class="nx">normalizedSquareDist</span> <span class="o">*</span> <span class="mi">5</span><span class="p">)</span> <span class="o">*</span> <span class="nx">SCALE_MULTIPLE</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nx">SCALE_MIN</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nb">Number</span><span class="p">.</span><span class="nx">POSITIVE_INFINITY</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1">// ...
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>
</span></span><span class="line"><span class="cl"><span class="p">&lt;</span><span class="nt">span</span>
</span></span><span class="line"><span class="cl">  <span class="na">style</span><span class="o">=</span><span class="p">{{</span>
</span></span><span class="line"><span class="cl">    <span class="c1">// ...
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>    <span class="nx">transform</span><span class="o">:</span> <span class="sb">`scale(</span><span class="si">${</span><span class="nx">clampedScale</span><span class="si">}</span><span class="sb">)`</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="c1">// ...
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>  <span class="p">}}</span>
</span></span><span class="line"><span class="cl"><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">  <span class="p">{</span><span class="nx">clampedScale</span><span class="p">.</span><span class="nx">toFixed</span><span class="p">(</span><span class="mi">2</span><span class="p">)}</span>
</span></span><span class="line"><span class="cl"><span class="p">&lt;/</span><span class="nt">span</span><span class="p">&gt;;</span>
</span></span></code></pre></div><h4 id="skittles">Skittles</h4>
<p>Once I had the attenuation effect working, I wanted to turn the &ldquo;debugger&rdquo; off and go back to rendering the dots.
This is my favorite version, I wonder what the cat thinks.</p>
<figure>
    <video
        class='video-shortcode'
        preload=''
        controls
    >
        <source src='/skittles-4.webm' type='video/webm' />
    </video>
</figure>

<p><a href="https://tauseefk.github.io/skittles/?variant=4" target="_blank" rel="noopener noreferrer">Interactive</a></p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-typescript" data-lang="typescript"><span class="line"><span class="cl"><span class="kr">const</span> <span class="nx">clampedScale</span> <span class="o">=</span> <span class="nx">clamp</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">  <span class="p">(</span><span class="mi">1</span> <span class="o">-</span> <span class="nx">normalizedSquareDist</span> <span class="o">*</span> <span class="nx">ATTENUATION</span><span class="p">)</span> <span class="o">*</span> <span class="nx">SCALE_MULTIPLE</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nx">SCALE_MIN</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nb">Number</span><span class="p">.</span><span class="nx">POSITIVE_INFINITY</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="p">&lt;</span><span class="nt">span</span>
</span></span><span class="line"><span class="cl">  <span class="na">style</span><span class="o">=</span><span class="p">{{</span>
</span></span><span class="line"><span class="cl">    <span class="c1">// ..
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>    <span class="nx">transform</span><span class="o">:</span> <span class="sb">`scale(</span><span class="si">${</span><span class="nx">clampedScale</span><span class="si">}</span><span class="sb">)`</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="p">}}</span>
</span></span><span class="line"><span class="cl">  <span class="na">className</span><span class="o">=</span><span class="p">{</span><span class="s2">&#34;pointer-events-none dark:bg-white bg-black rounded-full w-4 h-4&#34;</span><span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">/&gt;;</span>
</span></span></code></pre></div><h4 id="monochrome-just-like-steve-wanted">Monochrome, just like Steve wanted</h4>
<p>I simplified the effect a little bit so I don&rsquo;t get distracted by my own UI. This is supposed to be a photo editor remember!
To add just a little bit of visual interest I applied similar but simpler math to the opacity.</p>
<figure>
    <video
        class='video-shortcode'
        preload=''
        controls
    >
        <source src='/skittles-5.webm' type='video/webm' />
    </video>
</figure>

<p><a href="https://tauseefk.github.io/skittles/?variant=5" target="_blank" rel="noopener noreferrer">Interactive</a></p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-typescript" data-lang="typescript"><span class="line"><span class="cl"><span class="kr">const</span> <span class="nx">opacity</span> <span class="o">=</span> <span class="nx">isMouseDown</span>
</span></span><span class="line"><span class="cl">  <span class="o">?</span> <span class="nx">clamp</span><span class="p">(</span><span class="mi">1</span> <span class="o">-</span> <span class="nx">normalizedSquareDist</span><span class="p">,</span> <span class="nx">OPACITY_MIN</span><span class="p">,</span> <span class="nx">OPACITY_MAX</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="o">:</span> <span class="nx">OPACITY_MIN</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kr">const</span> <span class="nx">clampedScale</span> <span class="o">=</span> <span class="nx">clamp</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">  <span class="p">(</span><span class="mi">1</span> <span class="o">-</span> <span class="nx">normalizedSquareDist</span> <span class="o">*</span> <span class="nx">ATTENUATION</span><span class="p">)</span> <span class="o">*</span> <span class="nx">SCALE_MULTIPLE</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nx">SCALE_MIN</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nb">Number</span><span class="p">.</span><span class="nx">POSITIVE_INFINITY</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="p">&lt;</span><span class="nt">span</span>
</span></span><span class="line"><span class="cl">  <span class="na">style</span><span class="o">=</span><span class="p">{{</span>
</span></span><span class="line"><span class="cl">    <span class="nx">opacity</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nx">transform</span><span class="o">:</span> <span class="sb">`scale(</span><span class="si">${</span><span class="nx">clampedScale</span><span class="si">}</span><span class="sb">)`</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="p">}}</span>
</span></span><span class="line"><span class="cl">  <span class="na">className</span><span class="o">=</span><span class="p">{</span><span class="s2">&#34;rounded-full w-4 h-4&#34;</span><span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">/&gt;;</span>
</span></span></code></pre></div><h4 id="whats-next">What&rsquo;s next?</h4>
<p>This is only the beginning, I haven&rsquo;t built a renderer yet.
I&rsquo;ll write about that next time as it&rsquo;s too late and I wanna go back to reading my book.</p>
]]></content:encoded></item><item><title>No Sharp Corners</title><link>https://www.afloat.boats/posts/no-sharp-corners/</link><pubDate>Sun, 25 Aug 2024 11:58:05 -0700</pubDate><guid>https://www.afloat.boats/posts/no-sharp-corners/</guid><description>An algorithm for rounding convex corners and making grids look posh AF.</description><content:encoded><![CDATA[<p><a href="/pancakes" target="_blank" rel="noopener noreferrer">Pancakes</a></p>
<p>I recently wrote an algorithm to make rounded rectangles for a grid with “holes”.
The goal was to ensure that all the convex corners of the grid sections were rounded.
I looked at existing implementations in softwares that I use daily, the most used are code editors.
One of my favorite text editors Zed, renders text selections with beautifully rounded convex and concave corners.</p>
<p>I couldn’t find a way to add external rounded corners without adding extra blocks at the beginning and end of each row, so decided to focus on internal corners, which led me to a somewhat elegant solution.
The algorithm requires that I evaluate for each cell it’s neighboring cells, starting with the one to its left, and going clockwise at 45 degree increments.
Here are the directions: <code>[⇐, ⇖, ⇑, ⇗, ⇒, ⇘, ⇓, ⇙]</code>
For each corner I had to evaluate the three adjacent cells that it touches. Evaluating each corner cell clockwise staring from left-top makes it awkward as the last corner (left-bottom) requires that I look at the first direction (left) again.
This would require array index shenanigans and I wanted to avoid that for the sake of obsession. However, if I duplicate the first direction at the end, it simplifies the code.
The final directions array: <code>[⇐, ⇖, ⇑, ⇗, ⇒, ⇘, ⇓, ⇙, ⇐]</code>
Now I can implement a sliding window of width three on top of this array which can account for all the corners.</p>
<h4 id="who-drew-the-short-stick">Who drew the short stick?</h4>
<p>A corner that touches three empty cells is rounded.
Vice-versa an empty cell that touches three filled cells, is rounded.
As I&rsquo;m only concerned with the external rounding, I ended up ignoring the empty cell case.</p>
<p>Let’s look at an example:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-markdown" data-lang="markdown"><span class="line"><span class="cl">       ┌───┬───┬───┬───┐
</span></span><span class="line"><span class="cl">       │   │   │   │   │▐▌
</span></span><span class="line"><span class="cl">       ├───┼───┼───┼───┤▐▌
</span></span><span class="line"><span class="cl">       │   │ x │ x │   │▐▌
</span></span><span class="line"><span class="cl">       ├───┼───┼───┼───┤▐▌
</span></span><span class="line"><span class="cl">       │   │ x │ x │   │▐▌
</span></span><span class="line"><span class="cl">       └───┴───┴───┴───┘▐▌
</span></span><span class="line"><span class="cl">        ▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▘
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">       ┌───┬───┬───┬───┐
</span></span><span class="line"><span class="cl">       │   │   │   │   │▐▌
</span></span><span class="line"><span class="cl">       ├── ╭───┬───╮ ──┤▐▌
</span></span><span class="line"><span class="cl">       │   │ x │ x │   │▐▌
</span></span><span class="line"><span class="cl">       ├── ├───┼───┤ ──┤▐▌
</span></span><span class="line"><span class="cl">       │   │ x │ x │   │▐▌
</span></span><span class="line"><span class="cl">       └── ╰───┴───╯ ──┘▐▌
</span></span><span class="line"><span class="cl">        ▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▘
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        // Expected outcome
</span></span></code></pre></div><p>Evaluating the cell marked with <code>*</code>, <code>x</code>s are filled, and <code>_</code>s are empty.</p>
<p>By looking at the rectangle I can deduce that <code>*</code> should have the left top corner rounded and nothing else.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-markdown" data-lang="markdown"><span class="line"><span class="cl">       ┌───┬───┬───┬───┐
</span></span><span class="line"><span class="cl">       │ <span class="ge">_ │ _</span> │ <span class="ge">_ │ _</span> │▐▌
</span></span><span class="line"><span class="cl">       ├── ╭───┬───┼───┤▐▌
</span></span><span class="line"><span class="cl">       │ <span class="ge">_ │ * │ x │ _</span> │▐▌
</span></span><span class="line"><span class="cl">       ├── ├───┼───┼───┤▐▌
</span></span><span class="line"><span class="cl">       │ <span class="ge">_ │ x │ x │ _</span> │▐▌
</span></span><span class="line"><span class="cl">       └───┴───┴───┴───┘▐▌
</span></span><span class="line"><span class="cl">        ▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▘
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        // Expected outcome
</span></span></code></pre></div><p>Dry running the algorithm for the top left corner, I start by checking the cell to the left, then the one to the left and above, and then finally the one above.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-markdown" data-lang="markdown"><span class="line"><span class="cl">       ┌───┬───┬───┬───┐
</span></span><span class="line"><span class="cl">       │ 2 │ 3 │ <span class="ge">_ │ _</span> │▐▌
</span></span><span class="line"><span class="cl">       ├───┼───┼───┼───┤▐▌
</span></span><span class="line"><span class="cl">       │ 1 │ * │ x │ _ │▐▌
</span></span><span class="line"><span class="cl">       ├───┼───┼───┼───┤▐▌
</span></span><span class="line"><span class="cl">       │ <span class="ge">_ │ x │ x │ _</span> │▐▌
</span></span><span class="line"><span class="cl">       └───┴───┴───┴───┘▐▌
</span></span><span class="line"><span class="cl">        ▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▘
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        // Expected outcome
</span></span></code></pre></div><p>As I was evaluating a filled cell, and all the neighboring cells for the left top corner are empty, I can mark the corner as <code>rounded</code>.</p>
<p>Let’s look at a more complicated example:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-markdown" data-lang="markdown"><span class="line"><span class="cl">       ┌───┬───┬───┬───┐
</span></span><span class="line"><span class="cl">       │ x │ <span class="ge">_ │ _</span> │ _ │▐▌
</span></span><span class="line"><span class="cl">       ├───┼───┼───┼───┤▐▌
</span></span><span class="line"><span class="cl">       │ <span class="ge">_ │ x │ x │ _</span> │▐▌
</span></span><span class="line"><span class="cl">       ├───┼───┼───┼───┤▐▌
</span></span><span class="line"><span class="cl">       │ <span class="ge">_ │ x │ x │ _</span> │▐▌
</span></span><span class="line"><span class="cl">       └───┴───┴───┴───┘▐▌
</span></span><span class="line"><span class="cl">        ▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▘
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        // Expected outcome
</span></span></code></pre></div><p>For ths particular case, all the corners except the corner that’s common between the large and the small rectangle should be rounded. The clockwise sweeping algorithm is a generalized solution and works fine for this case.</p>
<p>Expected Outcome:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-markdown" data-lang="markdown"><span class="line"><span class="cl">       ╭───╮ ──┬───┬───┐
</span></span><span class="line"><span class="cl">       │ x │   │   │   │▐▌
</span></span><span class="line"><span class="cl">       ╰───┼───┬───╮ ──┤▐▌
</span></span><span class="line"><span class="cl">       │   │ x │ x │   │▐▌
</span></span><span class="line"><span class="cl">       ├── ├───┼───┤ ──┤▐▌
</span></span><span class="line"><span class="cl">       │   │ x │ x │   │▐▌
</span></span><span class="line"><span class="cl">       └── ╰───┴───╯ ──┘▐▌
</span></span><span class="line"><span class="cl">        ▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▘
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        // Expected outcome
</span></span></code></pre></div><p>Let&rsquo;s look at the cell marked with <code>*</code>, starting with the top left corner:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-markdown" data-lang="markdown"><span class="line"><span class="cl">     2   3
</span></span><span class="line"><span class="cl">      →┌───┬───┬───┬───┐
</span></span><span class="line"><span class="cl">     1 │ * │ <span class="ge">_ │ _</span> │ _ │▐▌
</span></span><span class="line"><span class="cl">       ├───┼───┼───┼───┤▐▌
</span></span><span class="line"><span class="cl">       │ <span class="ge">_ │ x │ x │ _</span> │▐▌
</span></span><span class="line"><span class="cl">       ├───┼───┼───┼───┤▐▌
</span></span><span class="line"><span class="cl">       │ <span class="ge">_ │ x │ x │ _</span> │▐▌
</span></span><span class="line"><span class="cl">       └───┴───┴───┴───┘▐▌
</span></span><span class="line"><span class="cl">        ▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▘
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        // Expected outcome
</span></span></code></pre></div><p>As the three relevant adjacent cells are out of bounds, I can mark the corner rounded.</p>
<p>Let’s look at the top right corner:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-markdown" data-lang="markdown"><span class="line"><span class="cl">           ↓
</span></span><span class="line"><span class="cl">       ╭───┬───┬───┬───┐
</span></span><span class="line"><span class="cl">       │ * │ <span class="ge">_ │ _</span> │ _ │▐▌
</span></span><span class="line"><span class="cl">       ├───┼───┼───┼───┤▐▌
</span></span><span class="line"><span class="cl">       │ <span class="ge">_ │ x │ x │ _</span> │▐▌
</span></span><span class="line"><span class="cl">       ├───┼───┼───┼───┤▐▌
</span></span><span class="line"><span class="cl">       │ <span class="ge">_ │ x │ x │ _</span> │▐▌
</span></span><span class="line"><span class="cl">       └───┴───┴───┴───┘▐▌
</span></span><span class="line"><span class="cl">        ▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▘
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        // Expected outcome
</span></span></code></pre></div><p>The first two adjacent cells are out-of-bounds and hence considered empty.
The last one is actually empty, I can mark that corner rounded.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-markdown" data-lang="markdown"><span class="line"><span class="cl">         1 ↓ 2
</span></span><span class="line"><span class="cl">       ╭───┬───┬───┬───┐
</span></span><span class="line"><span class="cl">       │ * │ <span class="ge">_ │ _</span> │ _ │▐▌
</span></span><span class="line"><span class="cl">       ├───┼───┼───┼───┤▐▌
</span></span><span class="line"><span class="cl">       │ <span class="ge">_ │ x │ x │ _</span> │▐▌
</span></span><span class="line"><span class="cl">       ├───┼───┼───┼───┤▐▌
</span></span><span class="line"><span class="cl">       │ <span class="ge">_ │ x │ x │ _</span> │▐▌
</span></span><span class="line"><span class="cl">       └───┴───┴───┴───┘▐▌
</span></span><span class="line"><span class="cl">        ▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▘
</span></span><span class="line"><span class="cl">        // 1 and 2 are out-of-bounds
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">           ↓
</span></span><span class="line"><span class="cl">       ╭───┬───┬───┬───┐
</span></span><span class="line"><span class="cl">       │ * │ 3 │ <span class="ge">_ │ _</span> │▐▌
</span></span><span class="line"><span class="cl">       ├───┼───┼───┼───┤▐▌
</span></span><span class="line"><span class="cl">       │ <span class="ge">_ │ x │ x │ _</span> │▐▌
</span></span><span class="line"><span class="cl">       ├───┼───┼───┼───┤▐▌
</span></span><span class="line"><span class="cl">       │ <span class="ge">_ │ x │ x │ _</span> │▐▌
</span></span><span class="line"><span class="cl">       └───┴───┴───┴───┘▐▌
</span></span><span class="line"><span class="cl">        ▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▘
</span></span><span class="line"><span class="cl">        // 3 is empty
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">           ↓
</span></span><span class="line"><span class="cl">       ╭───╮ ──┬───┬───┐
</span></span><span class="line"><span class="cl">       │ * │ 3 │ <span class="ge">_ │ _</span> │▐▌
</span></span><span class="line"><span class="cl">       ├───┼───┼───┼───┤▐▌
</span></span><span class="line"><span class="cl">       │ <span class="ge">_ │ x │ x │ _</span> │▐▌
</span></span><span class="line"><span class="cl">       ├───┼───┼───┼───┤▐▌
</span></span><span class="line"><span class="cl">       │ <span class="ge">_ │ x │ x │ _</span> │▐▌
</span></span><span class="line"><span class="cl">       └───┴───┴───┴───┘▐▌
</span></span><span class="line"><span class="cl">        ▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▘
</span></span><span class="line"><span class="cl">        // rounded corner
</span></span></code></pre></div><p>Let&rsquo;s look at the bottom right corner.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-markdown" data-lang="markdown"><span class="line"><span class="cl">       ╭───╮ ──┬───┬───┐
</span></span><span class="line"><span class="cl">       │ * │ 1 │ <span class="ge">_ │ _</span> │▐▌
</span></span><span class="line"><span class="cl">       ├ → ┼───┼───┼───┤▐▌
</span></span><span class="line"><span class="cl">       │ 3 │ 2 │ x │ _ │▐▌
</span></span><span class="line"><span class="cl">       ├───┼───┼───┼───┤▐▌
</span></span><span class="line"><span class="cl">       │ <span class="ge">_ │ x │ x │ _</span> │▐▌
</span></span><span class="line"><span class="cl">       └───┴───┴───┴───┘▐▌
</span></span><span class="line"><span class="cl">        ▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▘
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        // Expected outcome
</span></span></code></pre></div><p>As the cells marked 1 and 3 are empty but cell 2 is not, this corner can’t be rounded.</p>
<p>Last corner:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-markdown" data-lang="markdown"><span class="line"><span class="cl">       ╭───╮ ──┬───┬───┐
</span></span><span class="line"><span class="cl">     3 │ * │ <span class="ge">_ │ _</span> │ _ │▐▌
</span></span><span class="line"><span class="cl">      →├───┼───┼───┼───┤▐▌
</span></span><span class="line"><span class="cl">     2 │ 1 │ x │ x │ _ │▐▌
</span></span><span class="line"><span class="cl">       ├───┼───┼───┼───┤▐▌
</span></span><span class="line"><span class="cl">       │ <span class="ge">_ │ x │ x │ _</span> │▐▌
</span></span><span class="line"><span class="cl">       └───┴───┴───┴───┘▐▌
</span></span><span class="line"><span class="cl">        ▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▘
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        // Expected outcome
</span></span></code></pre></div><p>As all the relevant cells are either empty or out of bounds, this corner can be marked as rounded similar to the right top corner.</p>
<p>Outcome:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-markdown" data-lang="markdown"><span class="line"><span class="cl">       ╭───╮ ──┬───┬───┐
</span></span><span class="line"><span class="cl">       │ * │ <span class="ge">_ │ _</span> │ _ │▐▌
</span></span><span class="line"><span class="cl">       ╰───┼───┼───┼───┤▐▌
</span></span><span class="line"><span class="cl">       │ 1 │ x │ x │ _ │▐▌
</span></span><span class="line"><span class="cl">       ├───┼───┼───┼───┤▐▌
</span></span><span class="line"><span class="cl">       │ <span class="ge">_ │ x │ x │ _</span> │▐▌
</span></span><span class="line"><span class="cl">       └───┴───┴───┴───┘▐▌
</span></span><span class="line"><span class="cl">        ▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▘
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        // Expected outcome
</span></span></code></pre></div><h4 id="in-practice">In practice</h4>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-Typescript" data-lang="Typescript"><span class="line"><span class="cl"><span class="kr">const</span> <span class="nx">DIRECTION_DELTAS</span> <span class="o">=</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">  <span class="p">[</span><span class="o">-</span><span class="mi">1</span><span class="p">,</span> <span class="mi">0</span><span class="p">],</span> <span class="c1">//  ⇐
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>  <span class="p">[</span><span class="o">-</span><span class="mi">1</span><span class="p">,</span> <span class="o">-</span><span class="mi">1</span><span class="p">],</span> <span class="c1">// ⇖
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>  <span class="p">[</span><span class="mi">0</span><span class="p">,</span> <span class="o">-</span><span class="mi">1</span><span class="p">],</span> <span class="c1">//  ⇑
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>  <span class="p">[</span><span class="mi">1</span><span class="p">,</span> <span class="o">-</span><span class="mi">1</span><span class="p">],</span> <span class="c1">//  ⇗
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>  <span class="p">[</span><span class="mi">1</span><span class="p">,</span> <span class="mi">0</span><span class="p">],</span> <span class="c1">//   ⇒
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>  <span class="p">[</span><span class="mi">1</span><span class="p">,</span> <span class="mi">1</span><span class="p">],</span> <span class="c1">//   ⇘
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>  <span class="p">[</span><span class="mi">0</span><span class="p">,</span> <span class="mi">1</span><span class="p">],</span> <span class="c1">//   ⇓
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>  <span class="p">[</span><span class="o">-</span><span class="mi">1</span><span class="p">,</span> <span class="mi">1</span><span class="p">],</span> <span class="c1">//  ⇙
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>  <span class="p">[</span><span class="o">-</span><span class="mi">1</span><span class="p">,</span> <span class="mi">0</span><span class="p">],</span> <span class="c1">//  ⇐
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="p">]</span> <span class="kr">as</span> <span class="kr">const</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="cm">/**
</span></span></span><span class="line"><span class="cl"><span class="cm"> * Grid can be represented as a string: &#34;x____xx__xx_&#34;
</span></span></span><span class="line"><span class="cl"><span class="cm"> * and width.
</span></span></span><span class="line"><span class="cl"><span class="cm"> * or formatted as
</span></span></span><span class="line"><span class="cl"><span class="cm"> * `
</span></span></span><span class="line"><span class="cl"><span class="cm"> * x___
</span></span></span><span class="line"><span class="cl"><span class="cm"> * _xx_
</span></span></span><span class="line"><span class="cl"><span class="cm"> * _xx_
</span></span></span><span class="line"><span class="cl"><span class="cm"> * &#39;
</span></span></span><span class="line"><span class="cl"><span class="cm"> */</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kr">const</span> <span class="nx">isEmpty</span> <span class="o">=</span> <span class="p">(</span><span class="nx">char</span>: <span class="kt">string</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="nx">char</span> <span class="o">===</span> <span class="s2">&#34;_&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kr">const</span> <span class="nx">getCornerRounding</span> <span class="o">=</span> <span class="p">(</span>
</span></span><span class="line"><span class="cl">  <span class="nx">str</span>: <span class="kt">string</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nx">width</span>: <span class="kt">number</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nx">currIdx</span>: <span class="kt">Vec2</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span><span class="o">:</span> <span class="kr">readonly</span> <span class="p">[</span><span class="kr">boolean</span><span class="p">,</span> <span class="kr">boolean</span><span class="p">,</span> <span class="kr">boolean</span><span class="p">,</span> <span class="kr">boolean</span><span class="p">]</span> <span class="o">=&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="kr">const</span> <span class="nx">isCurrentEmpty</span> <span class="o">=</span> <span class="nx">isEmpty</span><span class="p">(</span><span class="nx">str</span><span class="p">[</span><span class="nx">currIdx</span><span class="p">]);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="c1">// get indices of the cells to evaluate
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>  <span class="kr">const</span> <span class="nx">surroundingIndices</span> <span class="o">=</span> <span class="nx">DIRECTION_DELTAS</span><span class="p">.</span><span class="nx">map</span><span class="p">(([</span><span class="nx">dX</span><span class="p">,</span> <span class="nx">dY</span><span class="p">])</span> <span class="o">=&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="c1">// ...
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>  <span class="p">});</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="kr">const</span> <span class="nx">isNeighborEmpty</span> <span class="o">=</span> <span class="p">(</span><span class="nx">directionIdx</span>: <span class="kt">number</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="kr">const</span> <span class="nx">neighboringIdx</span> <span class="o">=</span> <span class="nx">surroundingIndices</span><span class="p">[</span><span class="nx">directionIdx</span><span class="p">];</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="nx">isEmpty</span><span class="p">(</span><span class="nx">str</span><span class="p">[</span><span class="nx">neighboringIdx</span><span class="p">]);</span>
</span></span><span class="line"><span class="cl">  <span class="p">};</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="kr">const</span> <span class="nx">directionsForTopLeft</span> <span class="o">=</span> <span class="p">[</span><span class="mi">0</span><span class="p">,</span> <span class="mi">1</span><span class="p">,</span> <span class="mi">2</span><span class="p">];</span>
</span></span><span class="line"><span class="cl">  <span class="kr">const</span> <span class="nx">directionsForTopRight</span> <span class="o">=</span> <span class="p">[</span><span class="mi">2</span><span class="p">,</span> <span class="mi">3</span><span class="p">,</span> <span class="mi">4</span><span class="p">];</span>
</span></span><span class="line"><span class="cl">  <span class="kr">const</span> <span class="nx">directionsForBottomRight</span> <span class="o">=</span> <span class="p">[</span><span class="mi">4</span><span class="p">,</span> <span class="mi">5</span><span class="p">,</span> <span class="mi">6</span><span class="p">];</span>
</span></span><span class="line"><span class="cl">  <span class="kr">const</span> <span class="nx">directionsForBottomLeft</span> <span class="o">=</span> <span class="p">[</span><span class="mi">6</span><span class="p">,</span> <span class="mi">7</span><span class="p">,</span> <span class="mi">8</span><span class="p">];</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="k">return</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">    <span class="nx">directionsForTopLeft</span>
</span></span><span class="line"><span class="cl">      <span class="p">.</span><span class="nx">map</span><span class="p">(</span><span class="nx">isNeighborEmpty</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">      <span class="c1">// check if the neighbors are different from the current cell
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>      <span class="p">.</span><span class="nx">every</span><span class="p">(</span><span class="nx">isEmpty</span> <span class="o">!==</span> <span class="nx">isCurrentCellEmpty</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">    <span class="c1">// ...
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>  <span class="p">]</span> <span class="kr">as</span> <span class="kr">const</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">};</span>
</span></span></code></pre></div><h3 id="q-can-this-use-a-compute-shader-to-find-the-solution-much-faster">Q: Can this use a compute shader to find the solution much faster?</h3>
<p>I think so.</p>
]]></content:encoded></item><item><title>Cirque Du Spritesheet</title><link>https://www.afloat.boats/posts/cirque-du-spritesheet/</link><pubDate>Sun, 28 Apr 2024 23:04:15 -0700</pubDate><guid>https://www.afloat.boats/posts/cirque-du-spritesheet/</guid><description>Creating motion streaks in a Bevy game using a ring buffer.</description><content:encoded><![CDATA[<p><a href="/toad" target="_blank" rel="noopener noreferrer">Photo-editor</a> | <a href="https://github.com/tauseefk/rasengan" target="_blank" rel="noopener noreferrer">Repository</a></p>
<p>There isn’t much of a connection between Cirque Du Soleil and circular buffers. However, when I look at an animated gif of circular buffers the motion of the read and write heads reminds me of the wall of death. The write head’s ever advancing march over values that were yet to be read, but might never see I/O.</p>
<p>An ongoing <a href="https://tauseefk.github.io/toad/">video game project</a> of mine is where I experiment with Rust’s features to expand my understanding of the language. When I initially wrote <a href="https://github.com/tauseefk/rasengan">Rasengan</a>, a minimal circular buffer implementation, I had no plans on integrating it into the project. However, recently I wanted to implement a sort of blurred motion streak behind the video game character and <code>Rasengan</code> popped into my brain.</p>
<p>There are several ways to implement a motion streak, and some of them don’t need instantiating new entities.
I’m going to look for ways to do this as a camera effect in Bevy eventually, but it was quite tempting to use <code>Rasengan</code>.</p>
<p>You see, a simple array can be used to store all the information I needed to achieve this. When the character starts moving, at regular intervals its positions can be pushed into an array. At some other regular interval, the values from the array can be read back to render the streak (I use the same sprite, but with lower opacity and color adjustment).
As the game runs at 60 fps under normal circumstances, this array can get incredibly large quickly.
For instance, if you hold down the right arrow key, you would end up with 5MBs of positions data in the array in an hour.</p>
<p>That doesn’t sound so bad considering the memory that ships with modern computers. For instance the computer I’m using to write this blog post has 18 gigs of RAM.
With that said, the video game binary is only 15 Mbs itself, and one hour of gameplay generating a third of that seems awfully wasteful.</p>
<p>As the motion streak is quite short, I only needed to store a handful of previous positions. An array of length 10 can store all the data I was going to need, but continuously writing to the array would end up with an ever increasing size which would require a lot of re-allocations. Once a position has been read, it would never be used again.
If only I could re-use the array of length 10.</p>
<p>One solution is to use a queue, I would push a new position into the queue on player move and pop one when rendering the streak.
In Rust, a queue would require a <code>RefCell</code> to create the self referential data structure needed to implement a queue, details of which are out of the scope of this post.</p>
<p>I wanted to allocate all the data on the stack without pointers which could be achieved by using an array. However the frequent (60 frames a second) re-sizing of the array seemed like an unnecessary overhead.</p>
<p>If I could just wrap around a fixed size array and overwrite older values when writing the data, I could achieve similar outcome.</p>
<h2 id="enter---rasengan">Enter - Rasengan</h2>
<p>Yeah I&rsquo;m aware that it&rsquo;s a corny reference, fortunately that&rsquo;s the last you&rsquo;d have to think about references.</p>
<p>I first wrote Rasengan as a side project to explore const generics and circular buffers in general. My understanding of arrays in Rust was also fuzzy. There are quite a few different data structures that represent contiguous memory: array, slice, and vector.
Vectors are allowed to grow and shrink as needed, but I wanted a constant sized chunk.
Slices are a view into an existing array.
Which leaves us the array: <code>[T; usize]</code>.</p>
<p>Usually circular buffers prevent new writes until previous data has been read. Rasengan implements circular buffer with overwrite, which means that once the buffer is completely full (unread data is at capacity), it starts to overwrite the least recently updated values.
Which works perfectly for rendering the motion streak as lost frames aren’t a big deal.</p>
<p>Here’s a box diagram detailing how the wraparound overwrite works.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-markdown" data-lang="markdown"><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    W       R
</span></span><span class="line"><span class="cl">    │       │
</span></span><span class="line"><span class="cl">┌───▼───┬───▼───┬───────┬───────┬─ ─ ─ ─ ─ ─ ─ ─
</span></span><span class="line"><span class="cl">│       │       │       │       │       │        ▐▌
</span></span><span class="line"><span class="cl">│       │       │       │       │       │        ▐▌
</span></span><span class="line"><span class="cl">└───▲───┴───────┴───────┴───────┴─ ─ ─ ─ ─ ─ ─ ─ ▐▌
</span></span><span class="line"><span class="cl"> ▀▀▀│▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀│▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▘
</span></span><span class="line"><span class="cl">    └─── Capacity = 4 ──────┘
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">// buffer has no unread values
</span></span><span class="line"><span class="cl">read() -&gt; panic(&#34;Nothing to read here&#34;)
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    W       R
</span></span><span class="line"><span class="cl">    │       │
</span></span><span class="line"><span class="cl">┌───▼───┬───▼───┬───────┬───────┬─ ─ ─ ─ ─ ─ ─ ─
</span></span><span class="line"><span class="cl">│       │       │       │       │       │        ▐▌
</span></span><span class="line"><span class="cl">│       │   5   │       │       │       │        ▐▌
</span></span><span class="line"><span class="cl">└───▲───┴───────┴───────┴───────┴─ ─ ─ ─ ─ ─ ─ ─ ▐▌
</span></span><span class="line"><span class="cl"> ▀▀▀│▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀│▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▘
</span></span><span class="line"><span class="cl">    └───────────────────────┘
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">inc(W); write(5); // increment W before writing
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    w       R                       W
</span></span><span class="line"><span class="cl">    │       │                       │
</span></span><span class="line"><span class="cl">┌───▼───┬───▼───┬───────┬───────┬─ ─▼─ ─ ─ ─ ─ ─
</span></span><span class="line"><span class="cl">│       │       │       │       │       │        ▐▌
</span></span><span class="line"><span class="cl">│   8   │   5   │   2   │   4   │       │        ▐▌
</span></span><span class="line"><span class="cl">└───▲───┴───────┴───────┴───────┴─ ─ ─ ─ ─ ─ ─ ─ ▐▌
</span></span><span class="line"><span class="cl"> ▀▀▀│▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀│▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▘
</span></span><span class="line"><span class="cl">    └───────────────────────┘
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">inc(W); write(2); inc(W); write(4); inc(W); write(8);
</span></span><span class="line"><span class="cl">w = W % capacity // write with wrap-around
</span></span></code></pre></div><h2 id="trail-rendering-system">Trail Rendering System</h2>
<p>I use the Bevy game engine and its ECS model. Explaining how ECS works is out of the scope of this post, but I&rsquo;m happy to talk you out of OOP if you&rsquo;re considering it.</p>
<p>Bevy allows instantiating resources that need to be accessed by systems which is exactly what I needed for storing the positions data. I initialize a <code>Rasengan</code> instance at game launch, and then read and write to it in the trail systems.
Here is the entirely of the trail system.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-rust" data-lang="rust"><span class="line"><span class="cl"><span class="k">pub</span><span class="w"> </span><span class="k">fn</span> <span class="nf">trail_position_write_system</span><span class="p">(</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="c1">// ECS models usually depend on queries to access different entities
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="w">  </span><span class="n">player_transform_query</span>: <span class="nc">Query</span><span class="o">&lt;&amp;</span><span class="n">Transform</span><span class="p">,</span><span class="w"> </span><span class="n">With</span><span class="o">&lt;</span><span class="n">Player</span><span class="o">&gt;&gt;</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="c1">// this is the data that I&#39;m writing to and reading from
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="w">  </span><span class="k">mut</span><span class="w"> </span><span class="n">trail_position</span>: <span class="nc">ResMut</span><span class="o">&lt;</span><span class="n">Rasengan</span><span class="o">&lt;</span><span class="n">Vec3</span><span class="p">,</span><span class="w"> </span><span class="no">TRAIL_LENGTH</span><span class="o">&gt;&gt;</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="k">if</span><span class="w"> </span><span class="n">player_transform_query</span><span class="p">.</span><span class="n">get_single</span><span class="p">().</span><span class="n">is_err</span><span class="p">()</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">return</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="kd">let</span><span class="w"> </span><span class="n">player_transform</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">player_transform_query</span><span class="p">.</span><span class="n">single</span><span class="p">();</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="n">trail_position</span><span class="p">.</span><span class="n">write</span><span class="p">(</span><span class="n">player_transform</span><span class="p">.</span><span class="n">translation</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="k">pub</span><span class="w"> </span><span class="k">fn</span> <span class="nf">trail_position_read_system</span><span class="p">(</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="k">mut</span><span class="w"> </span><span class="n">player_trail_transform_query</span>: <span class="nc">Query</span><span class="o">&lt;&amp;</span><span class="k">mut</span><span class="w"> </span><span class="n">Transform</span><span class="p">,</span><span class="w"> </span><span class="n">With</span><span class="o">&lt;</span><span class="n">PlayerTrail</span><span class="o">&gt;&gt;</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="k">mut</span><span class="w"> </span><span class="n">trail_position</span>: <span class="nc">ResMut</span><span class="o">&lt;</span><span class="n">Rasengan</span><span class="o">&lt;</span><span class="n">Vec3</span><span class="p">,</span><span class="w"> </span><span class="no">TRAIL_LENGTH</span><span class="o">&gt;&gt;</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="k">if</span><span class="w"> </span><span class="n">player_trail_transform_query</span><span class="p">.</span><span class="n">get_single</span><span class="p">().</span><span class="n">is_err</span><span class="p">()</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">return</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="kd">let</span><span class="w"> </span><span class="k">mut</span><span class="w"> </span><span class="n">player_transform</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">player_trail_transform_query</span><span class="p">.</span><span class="n">single_mut</span><span class="p">();</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="k">if</span><span class="w"> </span><span class="kd">let</span><span class="w"> </span><span class="nb">Some</span><span class="p">(</span><span class="n">trail_position</span><span class="p">)</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">trail_position</span><span class="p">.</span><span class="n">read</span><span class="p">()</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">player_transform</span><span class="p">.</span><span class="n">translation</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">trail_position</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="p">}</span><span class="w">
</span></span></span></code></pre></div><p>There’s a minor problem with it though, the system is writing values to the positions data each frame, even when the position doesn&rsquo;t change. Although <code>Rasengan</code> is not allocating more memory, this still feels unnecessary.</p>
<h2 id="extending-rasengan-with-write_unique">Extending <code>Rasengan</code> with <code>write_unique</code></h2>
<p>I decided to update the api to write only when the system attempted to write a new value. This would prevent unnecessary writes when the game character wasn’t moving.</p>
<p>The <code>Rasengan::write_unique</code> is quite simple, it leverages <code>Rasengan::write</code> but first compares the value at the write head.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-rust" data-lang="rust"><span class="line"><span class="cl"><span class="k">impl</span><span class="o">&lt;</span><span class="n">T</span>: <span class="nb">Copy</span><span class="p">,</span><span class="w"> </span><span class="k">const</span><span class="w"> </span><span class="n">N</span>: <span class="kt">usize</span><span class="o">&gt;</span><span class="w"> </span><span class="n">Rasengan</span><span class="o">&lt;</span><span class="n">T</span><span class="p">,</span><span class="w"> </span><span class="n">N</span><span class="o">&gt;</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="o">..</span><span class="p">.</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="k">pub</span><span class="w"> </span><span class="k">fn</span> <span class="nf">write_unique</span><span class="p">(</span><span class="o">&amp;</span><span class="k">mut</span><span class="w"> </span><span class="bp">self</span><span class="p">,</span><span class="w"> </span><span class="n">data</span>: <span class="nc">T</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">if</span><span class="w"> </span><span class="bp">self</span><span class="p">.</span><span class="n">write_ptr</span><span class="w"> </span><span class="o">==</span><span class="w"> </span><span class="mi">0</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="bp">self</span><span class="p">.</span><span class="n">write</span><span class="p">(</span><span class="n">data</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="k">return</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kd">let</span><span class="w"> </span><span class="n">idx</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="bp">self</span><span class="p">.</span><span class="n">write_ptr</span><span class="w"> </span><span class="o">%</span><span class="w"> </span><span class="bp">self</span><span class="p">.</span><span class="n">buf</span><span class="p">.</span><span class="n">len</span><span class="p">();</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kd">let</span><span class="w"> </span><span class="n">last_written</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="bp">self</span><span class="p">.</span><span class="n">buf</span><span class="p">[</span><span class="n">idx</span><span class="p">];</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">if</span><span class="w"> </span><span class="kd">let</span><span class="w"> </span><span class="nb">Some</span><span class="p">(</span><span class="n">last_written</span><span class="p">)</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">last_written</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="k">if</span><span class="w"> </span><span class="n">data</span><span class="w"> </span><span class="o">!=</span><span class="w"> </span><span class="n">last_written</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="bp">self</span><span class="p">.</span><span class="n">write</span><span class="p">(</span><span class="n">data</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="p">}</span><span class="w">
</span></span></span></code></pre></div><p>There’s another hiccup though. This doesn’t compile.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">error<span class="o">[</span>E0369<span class="o">]</span>: binary operation <span class="sb">`</span>!<span class="o">=</span><span class="sb">`</span> cannot be applied to <span class="nb">type</span> <span class="sb">`</span>T<span class="sb">`</span>
</span></span><span class="line"><span class="cl">   --&gt; src/rasengan.rs:131:21
</span></span><span class="line"><span class="cl">    <span class="p">|</span>
</span></span><span class="line"><span class="cl"><span class="m">131</span> <span class="p">|</span>             <span class="k">if</span> data !<span class="o">=</span> last_written <span class="o">{</span>
</span></span><span class="line"><span class="cl">    <span class="p">|</span>                ---- ^^ ------------ T
</span></span><span class="line"><span class="cl">    <span class="p">|</span>                <span class="p">|</span>
</span></span><span class="line"><span class="cl">    <span class="p">|</span>                T
</span></span></code></pre></div><p>The problem here is that <code>Rasengan</code>’s implementation doesn’t have sufficient trait bounds that ensure the concrete type passed to <code>write_unique</code> implements <code>core::cmp::PartialEq</code>.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-rust" data-lang="rust"><span class="line"><span class="cl"><span class="k">impl</span><span class="o">&lt;</span><span class="n">T</span>: <span class="nb">Copy</span> <span class="o">+</span><span class="w"> </span><span class="nb">PartialEq</span><span class="p">,</span><span class="w"> </span><span class="k">const</span><span class="w"> </span><span class="n">N</span>: <span class="kt">usize</span><span class="o">&gt;</span><span class="w"> </span><span class="n">Rasengan</span><span class="o">&lt;</span><span class="n">T</span><span class="p">,</span><span class="w"> </span><span class="n">N</span><span class="o">&gt;</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="k">pub</span><span class="w"> </span><span class="k">fn</span> <span class="nf">write_unique</span><span class="p">(</span><span class="o">&amp;</span><span class="k">mut</span><span class="w"> </span><span class="bp">self</span><span class="p">,</span><span class="w"> </span><span class="n">data</span>: <span class="nc">T</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">if</span><span class="w"> </span><span class="bp">self</span><span class="p">.</span><span class="n">write_ptr</span><span class="w"> </span><span class="o">==</span><span class="w"> </span><span class="mi">0</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="bp">self</span><span class="p">.</span><span class="n">write</span><span class="p">(</span><span class="n">data</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="k">return</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kd">let</span><span class="w"> </span><span class="n">idx</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="bp">self</span><span class="p">.</span><span class="n">write_ptr</span><span class="w"> </span><span class="o">%</span><span class="w"> </span><span class="bp">self</span><span class="p">.</span><span class="n">buf</span><span class="p">.</span><span class="n">len</span><span class="p">();</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kd">let</span><span class="w"> </span><span class="n">last_written</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="bp">self</span><span class="p">.</span><span class="n">buf</span><span class="p">[</span><span class="n">idx</span><span class="p">];</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">if</span><span class="w"> </span><span class="kd">let</span><span class="w"> </span><span class="nb">Some</span><span class="p">(</span><span class="n">last_written</span><span class="p">)</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">last_written</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="k">if</span><span class="w"> </span><span class="n">data</span><span class="w"> </span><span class="o">!=</span><span class="w"> </span><span class="n">last_written</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="bp">self</span><span class="p">.</span><span class="n">write</span><span class="p">(</span><span class="n">data</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="p">}</span><span class="w">
</span></span></span></code></pre></div><p>I added a second implementation with the <code>PartialEq</code> trait bound for <code>Rasengan</code> and everything was back to normal in the pond.</p>
<p>The reason for adding a separate implementation with an added <code>PartialEq</code> bound instead of adding <code>write_unique</code> to the existing implementation was so that I can still use <code>Rasengan</code> normally without requiring types to implement <code>PartialEq</code> when <code>write_unique</code> is not necessary.</p>
]]></content:encoded></item><item><title>Streams of AGI</title><link>https://www.afloat.boats/posts/streams-of-agi/</link><pubDate>Sun, 21 May 2023 12:28:13 -0700</pubDate><guid>https://www.afloat.boats/posts/streams-of-agi/</guid><description>DIY streaming for OpenAI&amp;#39;s chat API using ReadableStreams, event parsing, and a custom React hook.</description><content:encoded><![CDATA[<p>OpenAI&rsquo;s SDK currently doesn&rsquo;t support streaming for models <code>GPT-3.5-Turbo</code> or <code>GPT-4</code>.</p>
<p>Yes, very sad, anyway. I decided to DIY this shit.</p>
<h3 id="backend">Backend</h3>
<p>On Node you can use the <code>fetch</code> api and get a <code>ReadableStream</code> of bytes as a response.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-Typescript" data-lang="Typescript"><span class="line"><span class="cl"><span class="kr">const</span> <span class="nx">openAIReadableTextStream</span> <span class="o">=</span> <span class="kr">async</span> <span class="p">(</span><span class="nx">path</span>: <span class="kt">string</span><span class="p">,</span> <span class="nx">body</span>: <span class="kt">any</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="kr">const</span> <span class="nx">response</span> <span class="o">=</span> <span class="k">await</span> <span class="nx">fetch</span><span class="p">(</span><span class="sb">`https://api.openai.com/v1</span><span class="si">${</span><span class="nx">path</span><span class="si">}</span><span class="sb">`</span><span class="p">,</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="nx">method</span><span class="o">:</span> <span class="s1">&#39;POST&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="nx">headers</span><span class="o">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="s1">&#39;Content-Type&#39;</span><span class="o">:</span> <span class="s1">&#39;application/json&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="nx">Authorization</span><span class="o">:</span> <span class="sb">`Bearer </span><span class="si">${</span><span class="nx">process</span><span class="p">.</span><span class="nx">env</span><span class="p">.</span><span class="nx">OPENAI_API_KEY</span><span class="si">}</span><span class="sb">`</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="p">},</span>
</span></span><span class="line"><span class="cl">        <span class="nx">body</span>: <span class="kt">JSON.stringify</span><span class="p">({</span>
</span></span><span class="line"><span class="cl">            <span class="p">...</span><span class="nx">body</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="nx">stream</span>: <span class="kt">true</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="p">}),</span>
</span></span><span class="line"><span class="cl">    <span class="p">});</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="nx">response</span><span class="p">.</span><span class="nx">body</span><span class="p">)</span> <span class="k">throw</span> <span class="k">new</span> <span class="nb">Error</span><span class="p">(</span><span class="s1">&#39;No response body.&#39;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="nx">response</span><span class="p">.</span><span class="nx">body</span><span class="p">.</span><span class="nx">pipeThrough</span><span class="p">(</span><span class="k">new</span> <span class="nx">TextDecoderStream</span><span class="p">());</span>
</span></span><span class="line"><span class="cl"><span class="p">};</span>
</span></span></code></pre></div><p>Here we use the <code>fetch</code> api to make a call to the OpenAI server and get a <code>ReadableStream&lt;UInt8Array&gt;</code> in response. It needs to be decoded into plaintext so we do that with <code>pipeThrough</code>.
The OpenAI streaming endpoints return the response as an <a href="https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events#event_stream_format">event stream</a>.</p>
<p>The next step is to parse the event stream, and connect it to a Response stream.
For parsing the event stream we can use <a href="https://www.npmjs.com/package/eventsource-parser">eventsource-parser</a>.</p>
<p>Installation:
<code>npm i eventsource-parser --save</code></p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-Typescript" data-lang="Typescript"><span class="line"><span class="cl"><span class="kr">export</span> <span class="kr">const</span> <span class="nx">getStreamingChatCompletion</span> <span class="o">=</span> <span class="kr">async</span> <span class="p">({</span>
</span></span><span class="line"><span class="cl">    <span class="nx">messages</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nx">writeStream</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span><span class="o">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nx">messages</span>: <span class="kt">ChatCompletionMessage</span><span class="p">[];</span>
</span></span><span class="line"><span class="cl">    <span class="nx">writeStream</span>: <span class="kt">Response</span><span class="p">&lt;</span><span class="nt">any</span><span class="p">&gt;;</span>
</span></span><span class="line"><span class="cl"><span class="p">})</span> <span class="o">=&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="kd">function</span> <span class="nx">onParse</span><span class="p">(</span><span class="nx">event</span>: <span class="kt">ParseEvent</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="k">if</span> <span class="p">(</span><span class="nx">event</span><span class="p">.</span><span class="kr">type</span> <span class="o">===</span> <span class="s1">&#39;event&#39;</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="k">if</span> <span class="p">(</span><span class="nx">event</span><span class="p">.</span><span class="nx">data</span> <span class="o">!==</span> <span class="s1">&#39;[DONE]&#39;</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">              <span class="nx">writeStream</span><span class="p">.</span><span class="nx">write</span><span class="p">(</span><span class="nx">JSON</span><span class="p">.</span><span class="nx">parse</span><span class="p">(</span><span class="nx">event</span><span class="p">.</span><span class="nx">data</span><span class="p">).</span><span class="nx">choices</span><span class="p">[</span><span class="mi">0</span><span class="p">].</span><span class="nx">delta</span><span class="o">?</span><span class="p">.</span><span class="nx">content</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">            <span class="p">}</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="k">try</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="kr">const</span> <span class="nx">response</span> <span class="o">=</span> <span class="k">await</span> <span class="nx">openAIReadableTextStream</span><span class="p">(</span><span class="s1">&#39;/chat/completions&#39;</span><span class="p">,</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="nx">model</span><span class="o">:</span> <span class="s1">&#39;gpt-4&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="nx">messages</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="p">});</span>
</span></span><span class="line"><span class="cl">        <span class="kr">const</span> <span class="nx">parser</span> <span class="o">=</span> <span class="nx">createParser</span><span class="p">(</span><span class="nx">onParse</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="c1">// @ts-expect-error Node 16+ supports async iterables
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>        <span class="k">for</span> <span class="k">await</span> <span class="p">(</span><span class="kr">const</span> <span class="nx">value</span> <span class="k">of</span> <span class="nx">response</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="nx">parser</span><span class="p">.</span><span class="nx">feed</span><span class="p">(</span><span class="nx">value</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="cl">        <span class="nx">writeStream</span><span class="p">.</span><span class="nx">end</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span> <span class="k">catch</span> <span class="p">(</span><span class="nx">error</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="nx">console</span><span class="p">.</span><span class="nx">error</span><span class="p">(</span><span class="nx">error</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="s1">&#39;Failed to get streaming completion.&#39;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">};</span>
</span></span></code></pre></div><p>We take the individual events and parse the data, which then gets written to the response stream.
Once the end of the event stream is reached, we can <code>end</code> the response stream.</p>
<p>Now we can hook up the <code>express</code> endpoint with the chat completion stream.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-Typescript" data-lang="Typescript"><span class="line"><span class="cl"><span class="nx">app</span><span class="p">.</span><span class="kr">get</span><span class="p">(</span><span class="s1">&#39;/chatCompletion&#39;</span><span class="p">,</span> <span class="kr">async</span> <span class="p">(</span><span class="nx">req</span><span class="p">,</span> <span class="nx">res</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="kr">const</span> <span class="nx">headers</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="s1">&#39;Content-Type&#39;</span><span class="o">:</span> <span class="s1">&#39;text/event-stream&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="nx">Connection</span><span class="o">:</span> <span class="s1">&#39;keep-alive&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="s1">&#39;Cache-Control&#39;</span><span class="o">:</span> <span class="s1">&#39;no-cache&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="p">};</span>
</span></span><span class="line"><span class="cl">    <span class="nx">res</span><span class="p">.</span><span class="nx">writeHead</span><span class="p">(</span><span class="mi">200</span><span class="p">,</span> <span class="nx">headers</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">await</span> <span class="nx">getStreamingChatCompletion</span><span class="p">({</span>
</span></span><span class="line"><span class="cl">        <span class="c1">// this is where the messages list goes
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>        <span class="nx">messages</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="nx">writeStream</span>: <span class="kt">res</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="p">});</span>
</span></span><span class="line"><span class="cl"><span class="p">});</span>
</span></span></code></pre></div><p>Here we can see that the response stream is just a response object we get access to inside an <code>express</code> endpoint callback.</p>
<h3 id="frontend">Frontend</h3>
<p>This is the developer experience I was looking for:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-Typescript" data-lang="Typescript"><span class="line"><span class="cl"><span class="kr">const</span> <span class="nx">Component</span>: <span class="kt">FC</span> <span class="o">=</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="kr">const</span> <span class="p">[</span><span class="nx">streamingData</span><span class="p">,</span> <span class="nx">triggerQuery</span><span class="p">]</span> <span class="o">=</span> <span class="nx">useStreamingQuery</span><span class="p">(</span><span class="s1">&#39;/chatCompletion&#39;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="p">&lt;</span><span class="nt">div</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">            <span class="p">{</span><span class="nx">streamingData</span><span class="p">}</span>
</span></span><span class="line"><span class="cl">            <span class="p">&lt;</span><span class="nt">button</span> <span class="na">onClick</span><span class="o">=</span><span class="p">{</span><span class="nx">triggerQuery</span><span class="p">}</span> <span class="p">/&gt;</span>
</span></span><span class="line"><span class="cl">        <span class="p">&lt;/</span><span class="nt">div</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="p">};</span>
</span></span></code></pre></div><p>I wrote a few hooks that abstract away all of the <code>ReadableStream</code> synchronization logic, and some nice-to-have data fetching wrappers.</p>
<h5 id="usestreamingquery-hook">useStreamingQuery Hook</h5>
<p>This is one of the wrappers that get exposed from <code>readable-hook</code>.</p>
<p><em>Internally it uses <code>useReadable</code>, which takes a stream producer (the <code>fetch</code> API in case of the <code>useStreamingQuery</code> hook), and returns a query trigger and the streamed data.</em></p>
<p>Installation:
<code>npm i readable-hook --save</code></p>
<p>This is a simplified version of the hook. Check out <a href="https://www.npmjs.com/package/readable-hook">readable-hook</a> for more details.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-Typescript" data-lang="Typescript"><span class="line"><span class="cl"><span class="kr">const</span> <span class="nx">useStreamingQuery</span> <span class="o">=</span> <span class="p">(</span><span class="nx">path</span>: <span class="kt">string</span><span class="p">)</span><span class="o">:</span> <span class="p">[</span><span class="kt">string</span><span class="p">,</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="k">void</span><span class="p">]</span> <span class="o">=&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="kr">const</span> <span class="p">[</span><span class="nx">data</span><span class="p">,</span> <span class="nx">setData</span><span class="p">]</span> <span class="o">=</span> <span class="nx">useState</span><span class="p">(</span><span class="s1">&#39;&#39;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="kr">const</span> <span class="nx">queryStream</span> <span class="o">=</span> <span class="nx">useCallback</span><span class="p">(</span><span class="kr">async</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="kr">const</span> <span class="nx">response</span> <span class="o">=</span> <span class="k">await</span> <span class="nx">fetch</span><span class="p">(</span><span class="sb">`</span><span class="si">${</span><span class="nx">BASE_URL</span><span class="si">}${</span><span class="nx">path</span><span class="si">}</span><span class="sb">`</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="nx">response</span><span class="p">.</span><span class="nx">body</span><span class="p">)</span> <span class="k">throw</span> <span class="k">new</span> <span class="nb">Error</span><span class="p">(</span><span class="s1">&#39;No response body found.&#39;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="kr">const</span> <span class="nx">reader</span> <span class="o">=</span> <span class="nx">response</span><span class="p">.</span><span class="nx">getReader</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="kr">async</span> <span class="kd">function</span> <span class="nx">syncWithTextStream() {</span>
</span></span><span class="line"><span class="cl">            <span class="kr">const</span> <span class="p">{</span> <span class="nx">value</span><span class="p">,</span> <span class="nx">done</span> <span class="p">}</span> <span class="o">=</span> <span class="k">await</span> <span class="nx">reader</span><span class="p">.</span><span class="nx">read</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">            <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="nx">done</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">                <span class="nx">setData</span><span class="p">(</span><span class="nx">value</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">                <span class="nx">requestAnimationFrame</span><span class="p">(()</span> <span class="o">=&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">                    <span class="nx">syncWithTextStream</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">                <span class="p">});</span>
</span></span><span class="line"><span class="cl">            <span class="p">}</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="nx">syncWithTextStream</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">    <span class="p">},</span> <span class="p">[</span><span class="nx">path</span><span class="p">]);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="p">[</span><span class="nx">data</span><span class="p">,</span> <span class="nx">queryStream</span><span class="p">];</span>
</span></span><span class="line"><span class="cl"><span class="p">};</span>
</span></span></code></pre></div><p>We setup intermediate state (on line 2), and the query function that handles fetching data from the stream periodically (on line 3). Both are then returned from the hook (on line 22). Even though the internals of the hook are fairly straightforward, the hook makes it much easier to re-use streaming data in other parts of the app.</p>
<p>Once the hook is initialized, we can read the values from <code>streamingData</code>, and update the UI.
The hook takes care of all the heavy-lifting.</p>
<p>After all this hard work, we can finally have streaming response from the OpenAI chat completion apis.</p>
]]></content:encoded></item><item><title>Calendar Tetris: Representation Matters</title><link>https://www.afloat.boats/posts/calendar-tetris-pt-2/</link><pubDate>Sun, 12 Feb 2023 00:49:25 -0800</pubDate><guid>https://www.afloat.boats/posts/calendar-tetris-pt-2/</guid><description>Using Petgraph&amp;#39;s directed graph to model overlapping calendar events instead of self-referential types in Rust.</description><content:encoded><![CDATA[<p><a href="https://tauseefk.github.io/cal-ender/">Live</a> | <a href="https://github.com/tauseefk/cal-ender">Repository</a></p>
<h3 id="too-many-events-all-at-once">Too many events all at once</h3>
<p>Let&rsquo;s assume that we want to stack two calendar blocks, <code>block_1</code> starts at 12:30 AM and ends at 02:00 AM, and <code>block_2</code> starts at 01:00 AM and ends at 01:30 AM. To simplify things however, let&rsquo;s just use their start and end times as minutes i.e. an event that starts at 12:30 AM would just be starting at minute 30.</p>
<p>To display the blocks we&rsquo;re going to use their start time as a top offset. Assuming that the day starts at minute 0, a block that starts at minute 30 will have a 30 px offset from the top. Keeping the page height in sync with the minutes in the day will make the offset math convenient.</p>
<p><img loading="lazy" src="/block_30_120.png" alt="block_30_120"  />

</p>
<p><img loading="lazy" src="/block_30_120_block_60_90.png" alt="block_30_120_block_60_90"  />

</p>
<h3 id="the-recursion-trap">The recursion trap</h3>
<p>Self-referential data types are more intuitive for modeling relationships between calendar nodes.</p>
<p>All calendar events can be represented by a handful of properties:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-Typescript" data-lang="Typescript"><span class="line"><span class="cl"><span class="c1">// using typescript to write pseudocode
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="kr">interface</span> <span class="nx">CalendarBlock</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="nx">id</span>: <span class="kt">number</span>
</span></span><span class="line"><span class="cl">  <span class="nx">startMinute</span>: <span class="kt">number</span>
</span></span><span class="line"><span class="cl">  <span class="nx">endMinute</span>: <span class="kt">number</span>
</span></span><span class="line"><span class="cl">  <span class="nx">blockType</span><span class="o">:</span> <span class="s1">&#39;busy&#39;</span> <span class="o">|</span> <span class="s1">&#39;available&#39;</span>
</span></span><span class="line"><span class="cl">  <span class="nx">children</span>: <span class="kt">CalendarBlock</span><span class="p">[]</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>Forward traversal can be achieved elegantly by:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-Typescript" data-lang="Typescript"><span class="line"><span class="cl"><span class="nx">currentBlock</span> <span class="o">=</span> <span class="nx">currentBlock</span><span class="p">.</span><span class="nx">children</span><span class="p">[</span><span class="mi">0</span><span class="p">];</span>
</span></span></code></pre></div><p>However, the Rust compiler needs to know the size of objects at compile time. As it&rsquo;s impossible to determine the size of self-referential data types, we have to use <code>RefCell</code>s to model the <code>CalendarBlock</code>s.</p>
<p>Rust implementation of self-referential CalendarBlock:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-Rust" data-lang="Rust"><span class="line"><span class="cl"><span class="k">enum</span> <span class="nc">CalendarBlockType</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="n">Busy</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="n">Available</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="k">struct</span> <span class="nc">CalendarBlock</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="n">id</span>: <span class="kt">u32</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="n">startMinute</span>: <span class="kt">u32</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="n">endMinute</span>: <span class="kt">u32</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="n">blockType</span>: <span class="nc">BlockType</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="n">children</span>: <span class="nb">Vec</span><span class="o">&lt;</span><span class="n">RefCell</span><span class="o">&lt;</span><span class="n">CalendarBlock</span><span class="o">&gt;&gt;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="p">}</span><span class="w">
</span></span></span></code></pre></div><p>I didn&rsquo;t want to do that as it seemed &ldquo;un-idiomatic&rdquo;, and decided to use a separate adjacency list using Petgraph to store the calendar block tree instead.</p>
<h3 id="adjacency-list">Adjacency List</h3>
<p>Petgraph has a clean API and we&rsquo;re going to use a directed graph to represent the calendar block tree.</p>
<p>So now the we can get rid of the <code>RefCell</code>.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-Rust" data-lang="Rust"><span class="line"><span class="cl"><span class="k">struct</span> <span class="nc">CalendarBlock</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="n">startMinute</span>: <span class="kt">u32</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="n">endMinute</span>: <span class="kt">u32</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="n">blockType</span>: <span class="nc">BlockType</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="k">type</span> <span class="nc">Weight</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="kt">usize</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="k">pub</span><span class="w"> </span><span class="k">struct</span> <span class="nc">CalendarBlockTree</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="n">root_idx</span>: <span class="nc">NodeIndex</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="n">adjacency_list</span>: <span class="nc">Graph</span><span class="o">&lt;</span><span class="kt">u32</span><span class="p">,</span><span class="w"> </span><span class="n">Weight</span><span class="o">&gt;</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="k">fn</span> <span class="nf">main</span><span class="p">()</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="kd">let</span><span class="w"> </span><span class="n">calendar_block_1</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">CalendarBlock</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">id</span>: <span class="mi">1</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">startMinute</span>: <span class="mi">30</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">endMinute</span>: <span class="mi">120</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">blockType</span>: <span class="nc">CalendarBlockType</span>::<span class="n">Available</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="p">};</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="kd">let</span><span class="w"> </span><span class="n">calendar_block_2</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">CalendarBlock</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">id</span>: <span class="mi">2</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">startMinute</span>: <span class="mi">60</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">endMinute</span>: <span class="mi">90</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">blockType</span>: <span class="nc">CalendarBlockType</span>::<span class="n">Available</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="p">};</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="kd">let</span><span class="w"> </span><span class="n">adjacency_list</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">Petgraph</span>::<span class="n">Graph</span>::<span class="n">new</span><span class="p">();</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="kd">let</span><span class="w"> </span><span class="n">node_1_idx</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">adjacency_list</span><span class="p">.</span><span class="n">add_node</span><span class="p">(</span><span class="n">calendar_block_1</span><span class="p">.</span><span class="n">id</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="kd">let</span><span class="w"> </span><span class="n">node_2_idx</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">adjacency_list</span><span class="p">.</span><span class="n">add_node</span><span class="p">(</span><span class="n">calendar_block_2</span><span class="p">.</span><span class="n">id</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="c1">// add_edge takes the source, destination, and weight as params
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="w">	</span><span class="n">adjacency_list</span><span class="p">.</span><span class="n">add_edge</span><span class="p">(</span><span class="n">node_1_idx</span><span class="p">,</span><span class="w"> </span><span class="n">node_2_idx</span><span class="p">,</span><span class="w"> </span><span class="mi">1</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="c1">// now that the nodes and edges are in place we can instantiate our CalendarBlockTree
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="w">	</span><span class="kd">let</span><span class="w"> </span><span class="n">calendar_block_tree</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">CalendarBlockTree</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	  </span><span class="n">root_idx</span>: <span class="nc">node_1_idx</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	  </span><span class="n">adjacency_list</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="p">};</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="p">}</span><span class="w">
</span></span></span></code></pre></div><h3 id="traversal">Traversal</h3>
<p>Petgraph&rsquo;s <code>edges_directed</code> API returns an iterator over <code>EdgeReference</code>. <code>EdgeReference</code> has methods to retrieve the <code>source</code>, <code>target</code>, and <code>weight</code> of a particular edge. We can use <code>edges_directed</code> to find edges in both directions forward and backward (used later).</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-Rust" data-lang="Rust"><span class="line"><span class="cl"><span class="kd">let</span><span class="w"> </span><span class="n">forward_neighbors_list</span>: <span class="nb">Vec</span><span class="o">&lt;</span><span class="n">petgraph</span>::<span class="n">graph</span>::<span class="n">EdgeReference</span><span class="o">&lt;</span><span class="kt">usize</span><span class="o">&gt;&gt;</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="bp">self</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="p">.</span><span class="n">adjacency</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="p">.</span><span class="n">edges_directed</span><span class="p">(</span><span class="n">destination</span><span class="p">,</span><span class="w"> </span><span class="n">petgraph</span>::<span class="n">Direction</span>::<span class="n">Outgoing</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="p">.</span><span class="n">collect</span><span class="p">();</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="c1">// unwrap is a story we&#39;re not going to tell today
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="kd">let</span><span class="w"> </span><span class="n">node_index</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">forward_neighbors</span><span class="p">.</span><span class="n">first</span><span class="p">().</span><span class="n">unwrap</span><span class="p">();</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="kd">let</span><span class="w"> </span><span class="n">calendar_block_2_from_adjacency_list</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">adjacency_list</span><span class="p">[</span><span class="n">node_idx</span><span class="p">];</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="n">assert_eq</span><span class="p">(</span><span class="n">calendar_block_2</span><span class="p">.</span><span class="n">id</span><span class="p">,</span><span class="w"> </span><span class="n">calendar_block_2_from_adjacency_list</span><span class="p">.</span><span class="n">id</span><span class="p">);</span><span class="w">
</span></span></span></code></pre></div><p>Forward traversal is useful for calculating stack position, and backward traversal is useful for calculating the sub-tree depth (also called height) of each node.</p>
<p>Our first algorithm does not require the sub-tree depth so we&rsquo;ll start there.</p>
<h3 id="stacking-blocks">Stacking blocks</h3>
<p>When populating the tree I had to make sure that the calendar blocks are added in the right place. For simplicity I populate the tree every time a block is added/moved. As the blocks get sorted before the tree is populated we can create the correct hierarchy using the following rules:</p>
<ul>
<li>compare the new block with existing blocks starting from the root</li>
<li>if the events overlap the event that starts later will be deeper in the tree (as we start with a sorted list this will always be the new block)</li>
<li>if the events do not overlap we check the next event in the list of neighbors for an overlap
<ul>
<li>if no overlaps found the event gets added at the end (sorting helps here as well)</li>
</ul>
</li>
</ul>
<p>To determine whether a calendar block overlaps with another I added a method to the <code>CalendarBlock</code> struct:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-Rust" data-lang="Rust"><span class="line"><span class="cl"><span class="k">impl</span><span class="w"> </span><span class="n">CalendarBlock</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="k">pub</span><span class="w"> </span><span class="k">fn</span> <span class="nf">does_overlap</span><span class="p">(</span><span class="o">&amp;</span><span class="bp">self</span><span class="p">,</span><span class="w"> </span><span class="n">block</span>: <span class="nc">CalendarBlock</span><span class="p">)</span><span class="w"> </span>-&gt; <span class="nb">Option</span><span class="o">&lt;</span><span class="n">CalendarBlockOverlap</span><span class="o">&gt;</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">if</span><span class="w"> </span><span class="bp">self</span><span class="p">.</span><span class="n">start_minute</span><span class="w"> </span><span class="o">&gt;=</span><span class="w"> </span><span class="n">block</span><span class="p">.</span><span class="n">end_minute</span><span class="w"> </span><span class="o">||</span><span class="w"> </span><span class="bp">self</span><span class="p">.</span><span class="n">end_minute</span><span class="w"> </span><span class="o">&lt;</span><span class="w"> </span><span class="n">block</span><span class="p">.</span><span class="n">start_minute</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="k">return</span><span class="w"> </span><span class="nb">None</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">if</span><span class="w"> </span><span class="bp">self</span><span class="p">.</span><span class="n">start_minute</span><span class="w"> </span><span class="o">&gt;</span><span class="w"> </span><span class="n">block</span><span class="p">.</span><span class="n">start_minute</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="o">||</span><span class="w"> </span><span class="p">(</span><span class="bp">self</span><span class="p">.</span><span class="n">start_minute</span><span class="w"> </span><span class="o">==</span><span class="w"> </span><span class="n">block</span><span class="p">.</span><span class="n">start_minute</span><span class="w"> </span><span class="o">&amp;&amp;</span><span class="w"> </span><span class="bp">self</span><span class="p">.</span><span class="n">end_minute</span><span class="w"> </span><span class="o">&lt;=</span><span class="w"> </span><span class="n">block</span><span class="p">.</span><span class="n">end_minute</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="k">return</span><span class="w"> </span><span class="nb">Some</span><span class="p">(</span><span class="n">CalendarBlockOverlap</span>::<span class="n">GetsSwallowed</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nb">Some</span><span class="p">(</span><span class="n">CalendarBlockOverlap</span>::<span class="n">Swallows</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="p">}</span><span class="w">
</span></span></span></code></pre></div><h3 id="calendar-tree-with-stack-position">Calendar tree with stack position</h3>
<p>This section is a WIP.</p>
]]></content:encoded></item><item><title>Calendar Tetris: Intro</title><link>https://www.afloat.boats/posts/calendar-tetris-pt-1/</link><pubDate>Fri, 10 Feb 2023 10:02:22 -0800</pubDate><guid>https://www.afloat.boats/posts/calendar-tetris-pt-1/</guid><description>How Apple, Google, and Outlook stack overlapping calendar events differently, and what sane stacking looks like.</description><content:encoded><![CDATA[<p><a href="https://tauseefk.github.io/cal-ender/">Live</a> | <a href="https://github.com/tauseefk/cal-ender">Repository</a></p>
<h3 id="motivation">Motivation</h3>
<p>Earlier last year I was looking at various calendar applications by the standard providers (Apple, Google, Outlook etc) and realized that they handle event stacking differently.</p>
<p>This is what they look like:</p>
<h3 id="apple">Apple</h3>
<p><img loading="lazy" src="/apple_5_block.png" alt="Apple Calendar"  />


Apple takes the easy way out with reduced opacity to show overlaps. I can&rsquo;t deduce how the ordering works here, as it&rsquo;s quite mind boggling.</p>
<h3 id="outlook">Outlook</h3>
<p><img loading="lazy" src="/outlook_5_block.png" alt="Outlook Calendar"  />


This one is interesting, they seem to be using a tree to represent their calendar stacks. If you look at the sequence of events <code>1-&gt;5-&gt;2-&gt;3</code> they&rsquo;re all of equal width. But the event #4 takes up rest of the column space, which hints at the fact that #4 has the information about whether there are more events downstream from it.</p>
<h3 id="google">Google</h3>
<p><img loading="lazy" src="/google_5_block.png" alt="Google Calendar"  />


This should look familiar to the one by Outlook. Either G made some improvements on the Outlook algorithm to reduce wasted space, or Outlook imitated G&rsquo;s algorithm poorly. There is a chance of simultaneous independent discovery, but let&rsquo;s not delude ourselves.</p>
<h3 id="why-would-you-need-this">Why would you need this?</h3>
<p>You probably don&rsquo;t.
Nobody in their right mind would have four levels of overlapping calendar events. Even if they do they probably don&rsquo;t need them stacked neatly, so yeah this is over-engineered. In fact there&rsquo;s a lot more nuance to the UX decisions that make the whole app, however we want to focus on the tree not the forest.</p>
<h3 id="what-is-sane-stacking">What is sane stacking?</h3>
<ol>
<li>every conflicting event is visible and directly interactable</li>
<li>minimize wasted space</li>
</ol>
<h4 id="solving-for-1">Solving for #1</h4>
<p>We can go the route of Outlook and ensure no overlap. In a previous section I mentioned the sequence <code>1-&gt;5-&gt;2-&gt;3</code>, which can be displayed with no overlap if we know the following two things:</p>
<ul>
<li>width of the event block</li>
<li>left offset</li>
</ul>
<p>As long as we know how many events exist in a sequence, we can divide the total column width with that number and find our block width.
Left offset requires that we know the event&rsquo;s position in the sequence e.g. event block 2 has position 3, so the left offset would be (3 - 1)x the width.</p>
<h4 id="solving-for-2">Solving for #2</h4>
<p>This is the tricky part. Choosing no overlap can lead to very cramped day columns. On the other hand almost complete overlap like in the case of Apple, makes it a nightmare to interact with overlapped events.
We&rsquo;re going to choose an overlap that makes sure the event label is visible, and interactions are available. Another thing that helps is the label font size, I&rsquo;ve found the sweet spot to be whatever that can fit between your calendar app&rsquo;s minimum time increments e.g. if the app allows default times in 15 minute increments, and each minute is 1px, 12px font size would work.
Google goes to great lengths to aviod label overlaps by restricting overlapping event block widths and offsets even further if the difference between event start times is too little.</p>
<p><a href="/posts/calendar-tetris-pt-2/">Next in series &gt;</a></p>
]]></content:encoded></item><item><title>Dead Simple Spritesheet Animation</title><link>https://www.afloat.boats/posts/dead-simple-spritesheet-animation/</link><pubDate>Sun, 04 Dec 2022 22:57:16 -0800</pubDate><guid>https://www.afloat.boats/posts/dead-simple-spritesheet-animation/</guid><description>A spritesheet animation state machine in Rust with page-based looping and variant transitions for 2D games.</description><content:encoded><![CDATA[<p><a href="https://tauseefk.github.io/toad/">Live</a> | <a href="https://github.com/tauseefk/animation_transition">Repository</a></p>
<h3 id="motivation">Motivation</h3>
<p>I’ve been using <a href="https://bevyengine.org/">Bevy</a> for a video game project and it’s been a delightful experience. As the project has grown, I’ve turned into a level designer, animator, illustrator, along with being a programmer.</p>
<p>Sometimes I hit walls, and rabbit holes around those walls are too compelling to pass up. One such rabbit hole was building an animation state machine that could create different looping animations from a single sprite sheet. The only reason for using a single sprite-sheet is that being the dev and the illustrator is very time consuming and I wanted my workflow to remain as simple as possible. But as I&rsquo;ve built a few different prototypes the single spritesheet model has proven to be quite useful.</p>
<p><img loading="lazy" src="/toad_animation_frames.png" alt="Sprite sheet"  />

</p>
<p><img loading="lazy" src="https://user-images.githubusercontent.com/11029896/196866248-5c28ed96-4f96-433b-a1b6-3319c2991a95.gif" alt="Animated"  />

</p>
<p>I created an <code>animation-transition</code> crate that abstracts most of the details away and I&rsquo;ve used it on multiple prototypes so I think that it is fairly straight forward to use in 2D games.
The following is a sort of how-to guide to create an animation state machine from scratch.</p>
<h2 id="animation-pages">Animation Pages</h2>
<p>I first create an enum with variants with self explanatory names. For the above example three variants suffice: Idle, Rising, and Falling.
The enum variants are then mapped to animation pages.
Pages are a way to encode the information needed to build looping animations. They are made up of two parts: an offset, and a page size.
The offset stores the index of the first sprite in a single animation loop, and page size stores the number of sprites in the same animation.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-markdown" data-lang="markdown"><span class="line"><span class="cl">                                                                       
</span></span><span class="line"><span class="cl">                    DECLARING THE ENUM                                 
</span></span><span class="line"><span class="cl">                                                                       
</span></span><span class="line"><span class="cl">                                                                       
</span></span><span class="line"><span class="cl">       ╔═══════╦═══════╦═══════╦═══════╦═══════╦═══════╗               
</span></span><span class="line"><span class="cl">       ║       ║       ║       ║       ║       ║       ║▐▌             
</span></span><span class="line"><span class="cl">       ║   0   ║   1   ║   2   ║   3   ║   4   ║   5   ║▐▌             
</span></span><span class="line"><span class="cl">       ╚═══════╩═══════╩═══════╩═══════╩═══════╩═══════╝▐▌             
</span></span><span class="line"><span class="cl">        ▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▘             
</span></span><span class="line"><span class="cl">                           [usize; 6]                                  
</span></span><span class="line"><span class="cl">                                                                       
</span></span><span class="line"><span class="cl">                                                                       
</span></span><span class="line"><span class="cl">     pub enum Variant {                pub trait AnimationLoop {       
</span></span><span class="line"><span class="cl">         Idle,           implements                                    
</span></span><span class="line"><span class="cl">         Rising, ━━━━━━━━━━━━━━━━━━━━━▶    fn page() -&gt; (usize, usize);
</span></span><span class="line"><span class="cl">         Falling,                                                      
</span></span><span class="line"><span class="cl">     }                                 }                               
</span></span><span class="line"><span class="cl">                                                                       
</span></span><span class="line"><span class="cl">                                                                       
</span></span><span class="line"><span class="cl">                    MAPPING TO idx/offset                              
</span></span><span class="line"><span class="cl">                                                                       
</span></span><span class="line"><span class="cl">       ╔═══════╦═══════╦═══════╦═══════╦═══════╦═══════╗               
</span></span><span class="line"><span class="cl">       ║       ║       ║       ║       ║       ║       ║▐▌             
</span></span><span class="line"><span class="cl">       ║   0   ║   1   ║   2   ║   3   ║   4   ║   5   ║▐▌             
</span></span><span class="line"><span class="cl">       ╚═══════╩═══════╩═══════╩═══════╩═══════╩═══════╝▐▌             
</span></span><span class="line"><span class="cl">        ▀▀▀▀▀▀▀▀▀▀▀│▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀│▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▘             
</span></span><span class="line"><span class="cl">                   └───────┬───────┘                                   
</span></span><span class="line"><span class="cl">                           ╵                                           
</span></span><span class="line"><span class="cl">              Idle::pages(); // (offset: 1, size: 3)                   
</span></span><span class="line"><span class="cl">                                                                       
</span></span><span class="line"><span class="cl">                                                                       
</span></span><span class="line"><span class="cl">                                                                       
</span></span><span class="line"><span class="cl">       ╔═══════╦═══════╦═══════╦═══════╦═══════╦═══════╗               
</span></span><span class="line"><span class="cl">       ║       ║       ║       ║       ║       ║       ║▐▌             
</span></span><span class="line"><span class="cl">       ║   0   ║   1   ║   2   ║   3   ║   4   ║   5   ║▐▌             
</span></span><span class="line"><span class="cl">       ╚═══════╩═══════╩═══════╩═══════╩═══════╩═══════╝▐▌             
</span></span><span class="line"><span class="cl">        ▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀│▀▀▀▀▀▀▀│▀▀▀▀▀▀▀▀▀▀▀▀▀▘             
</span></span><span class="line"><span class="cl">                                   └───┬───┘                           
</span></span><span class="line"><span class="cl">                                       ╵                               
</span></span><span class="line"><span class="cl">              Rising::pages(); // (offset: 3, size: 2)                 
</span></span><span class="line"><span class="cl">                                                                       
</span></span><span class="line"><span class="cl">                                                                       
</span></span></code></pre></div><h3 id="implementation">Implementation</h3>
<p>Let&rsquo;s take a look at some code.</p>
<h4 id="animation-variants-enum">Animation variants enum</h4>
<p>The enum needs to implement <code>PartialEq</code> so that the variants can be compared to each other when making decisions during animation state transitions.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-Rust" data-lang="Rust"><span class="line"><span class="cl"><span class="cp">#[derive(Clone, PartialEq)]</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="k">pub</span><span class="w"> </span><span class="k">enum</span> <span class="nc">PlayerAnimationVariant</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="n">Idle</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="n">Rising</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="n">Falling</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="p">}</span><span class="w">
</span></span></span></code></pre></div><h4 id="creating-an-animationloop-trait">Creating an <code>AnimationLoop</code> trait</h4>
<p>The trait is straightforward, defines a function that would return the offset and size of the animation page.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-Rust" data-lang="Rust"><span class="line"><span class="cl"><span class="k">pub</span><span class="w"> </span><span class="k">trait</span><span class="w"> </span><span class="n">AnimationLoop</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="k">fn</span> <span class="nf">page</span><span class="p">(</span><span class="o">&amp;</span><span class="bp">self</span><span class="p">)</span><span class="w"> </span>-&gt; <span class="p">(</span><span class="kt">usize</span><span class="p">,</span><span class="w"> </span><span class="kt">usize</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="p">}</span><span class="w">
</span></span></span></code></pre></div><h4 id="implementing-the-animationloop-trait-for-the-enum">Implementing the <code>AnimationLoop</code> trait for the enum</h4>
<p>This is also straightforward, I match the enum variant to the appropriate animation page tuple i.e. frame offset and page size.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-Rust" data-lang="Rust"><span class="line"><span class="cl"><span class="k">impl</span><span class="w"> </span><span class="n">AnimationLoop</span><span class="w"> </span><span class="k">for</span><span class="w"> </span><span class="n">PlayerAnimationVariant</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="k">fn</span> <span class="nf">page</span><span class="p">(</span><span class="o">&amp;</span><span class="bp">self</span><span class="p">)</span><span class="w"> </span>-&gt; <span class="p">(</span><span class="kt">usize</span><span class="p">,</span><span class="w"> </span><span class="kt">usize</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">match</span><span class="w"> </span><span class="bp">self</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="c1">// (idx_offset, loop_size) describe the animation loop
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="w">      </span><span class="n">PlayerAnimationVariant</span>::<span class="n">Idle</span><span class="w"> </span><span class="o">=&gt;</span><span class="w"> </span><span class="p">(</span><span class="mi">0</span><span class="p">,</span><span class="w"> </span><span class="mi">3</span><span class="p">),</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="n">PlayerAnimationVariant</span>::<span class="n">Rising</span><span class="w"> </span><span class="o">=&gt;</span><span class="w"> </span><span class="p">(</span><span class="mi">2</span><span class="p">,</span><span class="w"> </span><span class="mi">2</span><span class="p">),</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="n">PlayerAnimationVariant</span>::<span class="n">Falling</span><span class="w"> </span><span class="o">=&gt;</span><span class="w"> </span><span class="p">(</span><span class="mi">4</span><span class="p">,</span><span class="w"> </span><span class="mi">4</span><span class="p">),</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="p">}</span><span class="w">
</span></span></span></code></pre></div><hr>
<h2 id="state-transitions">State Transitions</h2>
<p>Playing a single looping animation can be accomplished by flipping through a contiguous array of indices, that are then used to fetch the appropriate sprite from a sprite atlas.
Moving between different animations can be accomplished by simply updating the animation variant.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-markdown" data-lang="markdown"><span class="line"><span class="cl">                                                                       
</span></span><span class="line"><span class="cl">                                                                       
</span></span><span class="line"><span class="cl">                   FLIPPING THROUGH SPRITES                            
</span></span><span class="line"><span class="cl">                                                                       
</span></span><span class="line"><span class="cl">                                                                       
</span></span><span class="line"><span class="cl">                                   ┌───────┐                           
</span></span><span class="line"><span class="cl">                                   │       │                           
</span></span><span class="line"><span class="cl">       ╔═══════╦═══════╦═══════╦═══════╦═══▼═══╦═══════╗               
</span></span><span class="line"><span class="cl">       ║       ║       ║       ║       ║       ║       ║▐▌             
</span></span><span class="line"><span class="cl">       ║   0   ║   1   ║   2   ║   3   ║   4   ║   5   ║▐▌             
</span></span><span class="line"><span class="cl">       ╚═══════╩═══════╩═══════╩═══════╩═══════╩═══════╝▐▌             
</span></span><span class="line"><span class="cl">        ▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▘             
</span></span><span class="line"><span class="cl">                                                                       
</span></span><span class="line"><span class="cl">              animation_state.variant; // Variant::Rising              
</span></span><span class="line"><span class="cl">              animation_state.idx; // 3                                
</span></span><span class="line"><span class="cl">              animation_state.wrapping_next_idx(); //  4               
</span></span><span class="line"><span class="cl">                                                                       
</span></span><span class="line"><span class="cl">                                                                       
</span></span><span class="line"><span class="cl">                       VARIANT TRANSITION                              
</span></span><span class="line"><span class="cl">                                                                       
</span></span><span class="line"><span class="cl">                                                                       
</span></span><span class="line"><span class="cl">                                       ┏━━━━━━━━━━━┓                   
</span></span><span class="line"><span class="cl">                                       ┃           ┃                   
</span></span><span class="line"><span class="cl">                                   ┌───────┐       ┃                   
</span></span><span class="line"><span class="cl">       ╔═══════╦═══════╦═══════╦═══════╦═══════╦═══▼═══╗               
</span></span><span class="line"><span class="cl">       ║       ║       ║       ║       ║       ║       ║▐▌             
</span></span><span class="line"><span class="cl">       ║   0   ║   1   ║   2   ║   3   ║   4   ║   5   ║▐▌             
</span></span><span class="line"><span class="cl">       ╚═══════╩═══════╩═══════╩═══════╩═══════╩═══════╝▐▌             
</span></span><span class="line"><span class="cl">        ▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▘             
</span></span><span class="line"><span class="cl">                                                                       
</span></span><span class="line"><span class="cl">              animation_state.variant;              // Variant::Rising 
</span></span><span class="line"><span class="cl">              animation_state.transition_variant(); // ()              
</span></span><span class="line"><span class="cl">              animation_state.variant;              // Variant::Falling
</span></span><span class="line"><span class="cl">              animation_state.idx;                  // 5               
</span></span><span class="line"><span class="cl">                                                                       
</span></span><span class="line"><span class="cl">                                                                       
</span></span></code></pre></div><hr>
<h2 id="implementation-1">Implementation</h2>
<p>I encapsulate the information needed for the two actions (looping animation, and variant transition) in a struct called <code>PlayerAnimationState</code>.</p>
<h4 id="animation-state-manager">Animation state manager</h4>
<p>The struct stores the animation variant to play, and the current index of the frame.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-Rust" data-lang="Rust"><span class="line"><span class="cl"><span class="k">pub</span><span class="w"> </span><span class="k">struct</span> <span class="nc">PlayerAnimationState</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="k">pub</span><span class="w"> </span><span class="n">variant</span>: <span class="nc">PlayerAnimationVariant</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="k">pub</span><span class="w"> </span><span class="n">idx</span>: <span class="kt">usize</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="p">}</span><span class="w">
</span></span></span></code></pre></div><h4 id="implementing-transition-functions">Implementing transition functions</h4>
<p><code>wrapping_next_idx</code> increments the current index and wraps around at the page boundary.
It makes looping animations trivial to implement.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-Rust" data-lang="Rust"><span class="line"><span class="cl"><span class="k">impl</span><span class="w"> </span><span class="n">PlayerAnimationState</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="k">fn</span> <span class="nf">wrapping_next_idx</span><span class="p">(</span><span class="o">&amp;</span><span class="k">mut</span><span class="w"> </span><span class="bp">self</span><span class="p">)</span><span class="w"> </span>-&gt; <span class="kt">usize</span> <span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kd">let</span><span class="w"> </span><span class="n">current_idx</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="bp">self</span><span class="p">.</span><span class="n">idx</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kd">let</span><span class="w"> </span><span class="p">(</span><span class="n">offset</span><span class="p">,</span><span class="w"> </span><span class="n">size</span><span class="p">)</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="bp">self</span><span class="p">.</span><span class="n">variant</span><span class="p">.</span><span class="n">page</span><span class="p">();</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="bp">self</span><span class="p">.</span><span class="n">idx</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">offset</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="p">(</span><span class="n">current_idx</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="mi">1</span><span class="p">)</span><span class="w"> </span><span class="o">%</span><span class="w"> </span><span class="n">size</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="bp">self</span><span class="p">.</span><span class="n">idx</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="p">}</span><span class="w">
</span></span></span></code></pre></div><p><code>transition_variant</code> updates the animation state manager so that I can play a different animation based on in-game events.
This one accepts an argument of type <code>PlayerAnimationVariant</code>, so different in-game events can trigger appropriate animations in response. For example when I press spacebar on Toad, the animation state is first updated to <code>Rising</code>, once the toad reaches it&rsquo;s maximum jump height the animation is updated to <code>Falling</code>, and finally on touching the ground it can be transitioned back to <code>Idle</code>.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-Rust" data-lang="Rust"><span class="line"><span class="cl"><span class="k">impl</span><span class="w"> </span><span class="n">PlayerAnimationState</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="o">..</span><span class="p">.</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="k">fn</span> <span class="nf">transition_variant</span><span class="p">(</span><span class="o">&amp;</span><span class="k">mut</span><span class="w"> </span><span class="bp">self</span><span class="p">,</span><span class="w"> </span><span class="n">to</span>: <span class="nc">PlayerAnimationVariant</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kd">let</span><span class="w"> </span><span class="p">(</span><span class="n">offset</span><span class="p">,</span><span class="w"> </span><span class="n">_</span><span class="p">)</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">to</span><span class="p">.</span><span class="n">page</span><span class="p">();</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="bp">self</span><span class="p">.</span><span class="n">variant</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">to</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="bp">self</span><span class="p">.</span><span class="n">idx</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">offset</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="p">}</span><span class="w">
</span></span></span></code></pre></div><hr>
<p>All of this can be somewhat tedious, and repetitive if you&rsquo;re working on multiple games. I created a Rust crate that simplifies the process by providing:</p>
<ul>
<li>the traits <code>AnimationLoop</code> and <code>AnimationTransition&lt;T: AnimationLoop&gt;</code></li>
<li>a handy proc macro <code>AnimationTransitionMacro</code> that implements the necessary index/variant manipulation functions for the animation state manager</li>
</ul>
]]></content:encoded></item></channel></rss>