<?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>Cryptoadz on afloat.boats</title><link>https://www.afloat.boats/tags/cryptoadz/</link><description>Recent content in Cryptoadz on afloat.boats</description><generator>Hugo -- gohugo.io</generator><language>en-us</language><lastBuildDate>Sun, 28 Apr 2024 23:04:15 -0700</lastBuildDate><atom:link href="https://www.afloat.boats/tags/cryptoadz/index.xml" rel="self" type="application/rss+xml"/><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>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>