<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="3.10.0">Jekyll</generator><link href="/feed.xml" rel="self" type="application/atom+xml" /><link href="/" rel="alternate" type="text/html" /><updated>2025-11-12T01:51:38+00:00</updated><id>/feed.xml</id><title type="html">Init 1</title><subtitle>Unix, CS, system programming.</subtitle><entry><title type="html">Getting started with Embedded Systems</title><link href="/posts/stm32-getting-started/" rel="alternate" type="text/html" title="Getting started with Embedded Systems" /><published>2025-10-07T00:00:00+00:00</published><updated>2025-10-07T00:00:00+00:00</updated><id>/posts/stm32-getting-started</id><content type="html" xml:base="/posts/stm32-getting-started/"><![CDATA[<p>Being a backend engineer by day, mostly involved in web development, I tend to
explore different areas and domains at night. For many years I’ve been
attracted by low level systems engineering, and invariably found myself
periodically coming back to it; specifically C for the better part.</p>

<p>Curiously, as the time passes, I found myself wanting to dive deeper, going
closer and closer to the hardware, dipping my toes with networked applications
such as MQTT brokers, virtual machines, programming languages design, CPU
simulations and recently even game engine development. Something about removing
the magic, the burden of endless external dependencies that permeates the
higher level stacks, and being able to govern the behavior of the system to a
fine-grained detail, makes it all the more addicting. I’m far from good at it,
but nonetheless, I feel like I learn something new and exciting each and every
time I explore the depth of computer science and engineering at the foundation
levels.</p>

<p>The natural progression of this process led me to try my hands on bare metal,
developing on something without any real abstraction, not even an OS doing the
heavy work, only addresses, a bus to provide the clock to registers and
peripherals. Ideally, working my way up from there to an RTOS (Real Time
Operating System e.g. <a href="https://www.freertos.org">FreeRTOS</a>) or something but
only after having clear how things work at the deepest level.</p>

<p>So what is an embedded system? Apparently the community is not really aligned
on the proper definition of it (so strange, usually everyone is so in sync
in this field..), but I like the definition provided by Elecia White in her
<code class="language-plaintext highlighter-rouge">Making Embedded Systems</code> book:</p>

<p><em>An embedded system is a computerized system that is purpose-built for
its application.</em></p>

<p>As the vast majority of this blog contents, this will largely be a learning
experience, gradually documenting the progress and discoveries along the
journey; so by definition, probably full of imprecisions and mistakes.</p>

<h2 id="getting-started-with-mcus">Getting started with MCUs</h2>

<p>There is a plethora of embedded systems, literally everywhere around us, from
the coffee machine, to the car pneumatics; the category I decided to focus on
pertains real-time systems (large chunk if the sector). I considered a couple
of microcontroller boards to begin with and read extensive literature on the
topic (including the aforementioned excellent <code class="language-plaintext highlighter-rouge">Making Embedded Systems</code> by
Elecia White, still in progress but so far, one of the best engineering books I
ever read).</p>

<p>There are a huge number of boards to start playing around with LEDs,
peripherals and other beginner-friendly projects, my shortlist reduced to:</p>

<ul>
  <li><code class="language-plaintext highlighter-rouge">Arduino</code></li>
  <li><code class="language-plaintext highlighter-rouge">ESP32</code></li>
  <li><code class="language-plaintext highlighter-rouge">STM32</code></li>
</ul>

<p>Eventually my choice fell on the last one, <code class="language-plaintext highlighter-rouge">STM32</code>, among those, without taking
anything from the other two, seems to be the most industry adopted and “real
world” board, comprised with extensive documentation and plenty of resources to
build a wide range of systems. I started on it by following an excellent
<a href="https://www.udemy.com/share/101XK43@QQJZUOvpUeB3jP2aBRXSCXpKEEiZwxo387JWk8jBSYngQN-2P5zYSLs6NNVifTy-/">Udemy
course</a>
as introductory material, quickly adapting the workflow and dev environment
to better fit my liking.</p>

<p>The ST Microelectronics website provides an enormous amount of resources, docs,
data-sheets and manuals, including a full development environment and an IDE
(based on Eclipse, so not really my jam, but pretty powerful handy if you don’t
know where to start).</p>

<h2 id="dev-setup">Dev setup</h2>

<p>Most guides and tutorials suggest to begin with the ST Microelectronics
provided <code class="language-plaintext highlighter-rouge">STM32CubeIDE</code>, there are although, other ways to build and upload the
software to the board. I opted for <code class="language-plaintext highlighter-rouge">PlatformIO IDE</code>, which provides the entire
tool-chain to start developing from the more common editors, primarily targeting
<code class="language-plaintext highlighter-rouge">VSCode</code> but supporting also the likes of <code class="language-plaintext highlighter-rouge">Emacs</code> and <code class="language-plaintext highlighter-rouge">Vim</code>.</p>

<h2 id="blinking-a-led">Blinking a LED</h2>

<p>The mandatory hello world of the embedded systems world, on bare-metal, a
synchronous infinite loop activating a LED at every clock cycle with a small
delay to allow seeing it blinking rather than just being perpetually on.</p>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// 1. Where is the LED connected?</span>
<span class="c1">//</span>
<span class="c1">// &gt;&gt;&gt; From Nucleo user guide:</span>
<span class="c1">//     User LD2: the green LED is a user LED connected to ARDUINO® signal D13 corresponding</span>
<span class="c1">//               to STM32 I/O PA5 (pin 21) or PB13 (pin 34) depending on the STM32 target</span>
<span class="c1">//</span>
<span class="c1">// PA5 or PB13, 90% of the time is PA5</span>
<span class="c1">//</span>
<span class="c1">// Port: A</span>
<span class="c1">// Pin: 5</span>

<span class="cp">#include</span> <span class="cpf">&lt;stdint.h&gt;</span><span class="cp">
</span>
<span class="c1">// 2. From the datasheet, locate the address space for peripherals</span>

<span class="cp">#define PERIPH_BASE       (0x40000000UL)
#define AHB1PERIPH_OFFSET (0x00020000UL)  // AHB1 is the bus, provide access to the clock to the peripheral
#define AHB1PERIPH_BASE   (PERIPH_BASE + AHB1PERIPH_OFFSET)
#define GPIOA_OFFSET      (0x0000UL)
</span>
<span class="c1">// 2.a check for memory mapping</span>

<span class="cp">#define GPIOA_BASE        (AHB1PERIPH_BASE + GPIOA_OFFSET)
</span>
<span class="cp">#define RCC_OFFSET        (0x3800UL)
#define RCC_BASE          (AHB1PERIPH_BASE + RCC_OFFSET)
</span>
<span class="c1">// 3. From the reference manual, look for AHB1 bus registers</span>

<span class="c1">// ENR =&gt; (EN)able(R)egister</span>
<span class="cp">#define AHB1EN_R_OFFSET   (0x30UL)
#define RCC_AHB1EN_R      (*(volatile unsigned int *)(RCC_BASE + AHB1EN_R_OFFSET))
</span>
<span class="c1">// Mode R (mode register)</span>
<span class="cp">#define MODE_R_OFFSET     (0x00UL)
#define GPIOA_MODE_R      (*(volatile unsigned int *)(GPIOA_BASE + MODE_R_OFFSET))
</span>
<span class="c1">// Output data register</span>
<span class="cp">#define OD_R_OFFSET       (0x14UL)
#define GPIOA_OD_R        (*(volatile unsigned int *)(GPIOA_BASE + OD_R_OFFSET))
</span>
<span class="cp">#define  GPIOAEN          (1U&lt;&lt;0) // 0b 0000 0000 0000 0000 0000 0000 0000 0001
</span>
<span class="cp">#define PIN5              (1U&lt;&lt;5)
#define LED_PIN           PIN5
</span>
<span class="k">typedef</span> <span class="k">struct</span> <span class="p">{</span>
    <span class="k">volatile</span> <span class="kt">uint32_t</span> <span class="n">DUMMY</span><span class="p">[</span><span class="mi">12</span><span class="p">];</span>
    <span class="k">volatile</span> <span class="kt">uint32_t</span> <span class="n">AHB1ENR</span><span class="p">;</span>
<span class="p">}</span> <span class="n">RCC_TypeDef</span><span class="p">;</span>

<span class="k">typedef</span> <span class="k">struct</span> <span class="p">{</span>
    <span class="k">volatile</span> <span class="kt">uint32_t</span> <span class="n">MODER</span><span class="p">;</span>
    <span class="k">volatile</span> <span class="kt">uint32_t</span> <span class="n">DUMMY</span><span class="p">[</span><span class="mi">4</span><span class="p">];</span>
    <span class="k">volatile</span> <span class="kt">uint32_t</span> <span class="n">ODR</span><span class="p">;</span>
<span class="p">}</span> <span class="n">GPIO_TypeDef</span><span class="p">;</span>

<span class="cm">/*
 * (1UL&lt;&lt;10) Set bit 10 to 1
 * &amp;=~ (1UL&lt;&lt;11) Set bit 11 to 0
 */</span>

<span class="cp">#define RCC   ((RCC_TypeDef *)(RCC_BASE))
#define GPIOA ((GPIO_TypeDef *)(GPIOA_BASE))
</span>
<span class="kt">int</span> <span class="nf">main</span><span class="p">(</span><span class="kt">void</span><span class="p">)</span>
<span class="p">{</span>
    <span class="c1">// 1. Enable clock access to GPIOA (using OR to prevent overwriting other possible set bits)</span>
    <span class="n">RCC</span><span class="o">-&gt;</span><span class="n">AHB1ENR</span> <span class="o">|=</span> <span class="n">GPIOAEN</span><span class="p">;</span>

    <span class="c1">// 2. Set PA5 as output pin</span>
    <span class="n">GPIOA</span><span class="o">-&gt;</span><span class="n">MODER</span> <span class="o">|=</span> <span class="p">(</span><span class="mi">1U</span><span class="o">&lt;&lt;</span><span class="mi">10</span><span class="p">);</span>
    <span class="n">GPIOA</span><span class="o">-&gt;</span><span class="n">MODER</span> <span class="o">&amp;=~</span> <span class="p">(</span><span class="mi">1U</span><span class="o">&lt;&lt;</span><span class="mi">11</span><span class="p">);</span>

    <span class="k">while</span> <span class="p">(</span><span class="mi">1</span><span class="p">)</span> <span class="p">{</span>
        <span class="c1">// 3. Set PA5 high (turn on the LED)</span>
        <span class="n">GPIOA</span><span class="o">-&gt;</span><span class="n">ODR</span> <span class="o">|=</span> <span class="n">LED_PIN</span><span class="p">;</span>
        <span class="k">for</span> <span class="p">(</span><span class="kt">int</span> <span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">i</span> <span class="o">&lt;</span> <span class="mi">100000</span><span class="p">;</span> <span class="n">i</span><span class="o">++</span><span class="p">){}</span>
    <span class="p">}</span>

<span class="p">}</span>
</code></pre></div></div>

<p>The snippet is part of my notes from the course, it is really basic and is set
in order to show how things work under the hood before jumping into real
development using the extremely vast array of headers and libraries mapping
peripherals and registers already available.</p>

<p><span class="to-be-continued"><code class="language-plaintext highlighter-rouge">つづく</code></span></p>]]></content><author><name></name></author><summary type="html"><![CDATA[Dipping toes in the bare metal programming world]]></summary></entry><entry><title type="html">Dogfight! - Client MVP</title><link href="/posts/dogfight-client-mvp/" rel="alternate" type="text/html" title="Dogfight! - Client MVP" /><published>2025-01-16T00:00:00+00:00</published><updated>2025-01-16T00:00:00+00:00</updated><id>/posts/dogfight-client-mvp</id><content type="html" xml:base="/posts/dogfight-client-mvp/"><![CDATA[<p>In the previous parts the focus has been on laying some foundations for the
core logic of the game, there is still plenty to do and the code is barely
prototypical, no real test has been done yet but it’s good enough to try
and show something on a terminal and a window, what we have so far:</p>

<ul>
  <li>A TCP server accepting incoming connection and creating player contexts</li>
  <li>A game state to represent entities in a fixed size grid
    <ul>
      <li>Spaceships</li>
      <li>Bullets</li>
      <li>Power-ups (this not yet governed in practice but the concept is there)</li>
    </ul>
  </li>
  <li>A rudimentary event system to apply changes and actions to the game state</li>
  <li>A binary based protocol to exchange payloads</li>
</ul>

<p>The next bit is a client side to communicate with the server these information
and see it reflected based on the user input, we’re gonna need</p>

<ul>
  <li>A TCP client to connect to the server component</li>
  <li>An implementation of the binary protocol, this is gonna be from scratch as
well as we’re not using any structured library</li>
  <li>A graphic layer to display the state of the game, among which I considered:
    <ul>
      <li><a href="https://invisible-island.net/ncurses/"><code class="language-plaintext highlighter-rouge">ncurses</code></a>, the oldest, a staple of TUI applications and old school roguelikes</li>
      <li><a href="https://github.com/libtcod/libtcod"><code class="language-plaintext highlighter-rouge">tcod</code></a>, mostly oriented to roguelike games development</li>
      <li>a mesh of headers in the <a href="https://github.com/nothings/stb">stb style</a> (awesome btw)</li>
      <li><a href="https://www.raylib.com/"><code class="language-plaintext highlighter-rouge">raylib</code></a> this was my latest choice originally, and the one I decided to keep pursuing</li>
    </ul>
  </li>
  <li>A way to capture the user input and serialized it to be sent to the server</li>
</ul>

<h3 id="brief-intro-on-the-language-of-choice">Brief intro on the language of choice</h3>

<p>I will use the original
<a href="https://github.com/codepr/battletank/blob/main/battletank_client.c">battletank_client.c</a>
as a starting point, it’s pretty raw and simple but the logic is roughly the
same. This time around I will implement it in a different language than C. I
love C, it’s my main go-to choice for low-level applications and, for how many
flaws and dangers and annoyances it poses, I can’t help but find joy in
implementing stuff in it. So it was naturally my first choice here as well, but
after having implemented a basic version of the game already, I decided to take
the chance to give an honest try to one of the other new kids on the block.</p>

<p>The choices were mostly driven by two requirements</p>

<ul>
  <li>System language, no garbage collection</li>
  <li>Simplicity, not too much frills, in the style of C</li>
</ul>

<p>There are a growing number of so-called C-killer languages being shipped
consistently in the recent years, none of them really worthy of the title yet,
but some feel at least like a good start with some pretty nice design and
features; in the end, my shortlist was</p>

<ul>
  <li>Zig</li>
  <li>Odin</li>
  <li>Hare</li>
</ul>

<p><code class="language-plaintext highlighter-rouge">Hare</code> fell immediately as a second contender, I don’t know much of the
language itself but for some reason I wasn’t impressed by its showcase, one
cool point is the
<a href="https://harelang.org/documentation/faq.html#why-qbe-instead-of-llvm">qbe</a>,
felt nice to see a new language not relying on LLVM as a backend but producing
(and releasing for adoption for other compilers too) something around 70% of
LLVM with 90% of bloat less, pretty impressive feat.</p>

<p>The final choice has really been a toss of the coin between <code class="language-plaintext highlighter-rouge">Odin</code> and <code class="language-plaintext highlighter-rouge">Zig</code>.
Both provide a nice set of features and ports to <code class="language-plaintext highlighter-rouge">Raylib</code> (which was already
the main choice for the GUI layer). <code class="language-plaintext highlighter-rouge">Zig</code> seemed a little more mature and is
showing some impressive feats recently with some great products shipped in it
(<code class="language-plaintext highlighter-rouge">TigerBeetle</code>, <code class="language-plaintext highlighter-rouge">Bun</code> and more recently <code class="language-plaintext highlighter-rouge">Ghostty</code> are all great quality and
high performance software) plus it was slightly higher on my TO-TRY list.</p>

<h3 id="starting-the-client">Starting the client</h3>

<p>For the client side, we will start from where we left, probably not the best
choice but it somehow felt natural to begin the development of it by the main
connecting point, the protocol.</p>

<div class="language-zig highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">const</span> <span class="n">std</span> <span class="o">=</span> <span class="nb">@import</span><span class="p">(</span><span class="s">"std"</span><span class="p">);</span>
<span class="k">const</span> <span class="n">net</span> <span class="o">=</span> <span class="n">std</span><span class="p">.</span><span class="py">net</span><span class="p">;</span>
<span class="k">const</span> <span class="n">testing</span> <span class="o">=</span> <span class="n">std</span><span class="p">.</span><span class="py">testing</span><span class="p">;</span>

<span class="k">const</span> <span class="n">max_players</span><span class="p">:</span> <span class="kt">usize</span> <span class="o">=</span> <span class="mi">5</span><span class="p">;</span>
<span class="k">const</span> <span class="n">max_bullets</span><span class="p">:</span> <span class="kt">usize</span> <span class="o">=</span> <span class="mi">5</span><span class="p">;</span>
<span class="k">const</span> <span class="n">player_size</span><span class="p">:</span> <span class="kt">usize</span> <span class="o">=</span> <span class="nb">@sizeOf</span><span class="p">(</span><span class="n">Player</span><span class="p">)</span> <span class="o">+</span> <span class="mi">36</span><span class="p">;</span>
<span class="k">const</span> <span class="n">power_up_size</span><span class="p">:</span> <span class="kt">usize</span> <span class="o">=</span> <span class="nb">@sizeOf</span><span class="p">(</span><span class="n">PowerUp</span><span class="p">);</span>

<span class="k">const</span> <span class="n">Vector2D</span> <span class="o">=</span> <span class="k">struct</span> <span class="p">{</span>
    <span class="n">x</span><span class="p">:</span> <span class="kt">i32</span><span class="p">,</span>
    <span class="n">y</span><span class="p">:</span> <span class="kt">i32</span><span class="p">,</span>

    <span class="k">pub</span> <span class="k">fn</span> <span class="n">print</span><span class="p">(</span><span class="n">self</span><span class="p">:</span> <span class="n">Vector2D</span><span class="p">,</span> <span class="n">writer</span><span class="p">:</span> <span class="n">anytype</span><span class="p">)</span> <span class="o">!</span><span class="k">void</span> <span class="p">{</span>
        <span class="k">try</span> <span class="n">writer</span><span class="p">.</span><span class="nf">print</span><span class="p">(</span><span class="s">"({d}, {d})"</span><span class="p">,</span> <span class="o">.</span><span class="p">{</span> <span class="n">self</span><span class="p">.</span><span class="py">x</span><span class="p">,</span> <span class="n">self</span><span class="p">.</span><span class="py">y</span> <span class="p">});</span>
    <span class="p">}</span>
<span class="p">};</span>

<span class="k">pub</span> <span class="k">const</span> <span class="n">Direction</span> <span class="o">=</span> <span class="k">enum</span> <span class="p">{</span>
    <span class="n">idle</span><span class="p">,</span>
    <span class="n">up</span><span class="p">,</span>
    <span class="n">down</span><span class="p">,</span>
    <span class="n">left</span><span class="p">,</span>
    <span class="n">right</span><span class="p">,</span>

    <span class="k">pub</span> <span class="k">fn</span> <span class="n">print</span><span class="p">(</span><span class="n">self</span><span class="p">:</span> <span class="n">Direction</span><span class="p">,</span> <span class="n">writer</span><span class="p">:</span> <span class="n">anytype</span><span class="p">)</span> <span class="o">!</span><span class="k">void</span> <span class="p">{</span>
        <span class="k">const</span> <span class="n">dir_names</span> <span class="o">=</span> <span class="p">[</span><span class="mi">_</span><span class="p">][]</span><span class="k">const</span> <span class="kt">u8</span><span class="p">{</span>
            <span class="s">"idle"</span><span class="p">,</span> <span class="s">"up"</span><span class="p">,</span> <span class="s">"down"</span><span class="p">,</span> <span class="s">"left"</span><span class="p">,</span> <span class="s">"right"</span><span class="p">,</span>
        <span class="p">};</span>
        <span class="k">try</span> <span class="n">writer</span><span class="p">.</span><span class="nf">print</span><span class="p">(</span><span class="s">"{s}"</span><span class="p">,</span> <span class="o">.</span><span class="p">{</span><span class="n">dir_names</span><span class="p">[</span><span class="n">@intFromEnum</span><span class="p">(</span><span class="n">self</span><span class="p">)]});</span>
    <span class="p">}</span>
<span class="p">};</span>

<span class="k">pub</span> <span class="k">const</span> <span class="n">Bullet</span> <span class="o">=</span> <span class="k">struct</span> <span class="p">{</span>
    <span class="n">position</span><span class="p">:</span> <span class="n">Vector2D</span><span class="p">,</span>
    <span class="n">direction</span><span class="p">:</span> <span class="n">Direction</span><span class="p">,</span>
    <span class="n">active</span><span class="p">:</span> <span class="k">bool</span><span class="p">,</span>

    <span class="k">pub</span> <span class="k">fn</span> <span class="n">print</span><span class="p">(</span><span class="n">self</span><span class="p">:</span> <span class="n">Bullet</span><span class="p">,</span> <span class="n">writer</span><span class="p">:</span> <span class="n">anytype</span><span class="p">)</span> <span class="o">!</span><span class="k">void</span> <span class="p">{</span>
        <span class="k">try</span> <span class="n">writer</span><span class="p">.</span><span class="nf">print</span><span class="p">(</span><span class="s">"    position: "</span><span class="p">,</span> <span class="o">.</span><span class="p">{});</span>
        <span class="k">try</span> <span class="n">self</span><span class="p">.</span><span class="py">position</span><span class="p">.</span><span class="nf">print</span><span class="p">(</span><span class="n">writer</span><span class="p">);</span>
        <span class="k">try</span> <span class="n">writer</span><span class="p">.</span><span class="nf">print</span><span class="p">(</span><span class="s">" Direction: "</span><span class="p">,</span> <span class="o">.</span><span class="p">{});</span>
        <span class="k">try</span> <span class="n">self</span><span class="p">.</span><span class="py">direction</span><span class="p">.</span><span class="nf">print</span><span class="p">(</span><span class="n">writer</span><span class="p">);</span>
        <span class="k">try</span> <span class="n">writer</span><span class="p">.</span><span class="nf">print</span><span class="p">(</span><span class="s">" Active: {s}</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span> <span class="o">.</span><span class="p">{</span><span class="k">if</span> <span class="p">(</span><span class="n">self</span><span class="p">.</span><span class="py">active</span><span class="p">)</span> <span class="s">"true"</span> <span class="k">else</span> <span class="s">"false"</span><span class="p">});</span>
    <span class="p">}</span>
<span class="p">};</span>

<span class="k">pub</span> <span class="k">const</span> <span class="n">Player</span> <span class="o">=</span> <span class="k">struct</span> <span class="p">{</span>
    <span class="n">position</span><span class="p">:</span> <span class="n">Vector2D</span><span class="p">,</span>
    <span class="n">hp</span><span class="p">:</span> <span class="kt">i32</span><span class="p">,</span>
    <span class="n">direction</span><span class="p">:</span> <span class="n">Direction</span><span class="p">,</span>
    <span class="n">alive</span><span class="p">:</span> <span class="k">bool</span><span class="p">,</span>
    <span class="n">bullets</span><span class="p">:</span> <span class="p">[</span><span class="n">max_bullets</span><span class="p">]</span><span class="n">Bullet</span><span class="p">,</span>

    <span class="k">pub</span> <span class="k">fn</span> <span class="n">print</span><span class="p">(</span><span class="n">self</span><span class="p">:</span> <span class="n">Player</span><span class="p">,</span> <span class="n">writer</span><span class="p">:</span> <span class="n">anytype</span><span class="p">)</span> <span class="o">!</span><span class="k">void</span> <span class="p">{</span>
        <span class="k">try</span> <span class="n">writer</span><span class="p">.</span><span class="nf">print</span><span class="p">(</span><span class="s">"   position: "</span><span class="p">,</span> <span class="o">.</span><span class="p">{});</span>
        <span class="k">try</span> <span class="n">self</span><span class="p">.</span><span class="py">position</span><span class="p">.</span><span class="nf">print</span><span class="p">(</span><span class="n">writer</span><span class="p">);</span>
        <span class="k">try</span> <span class="n">writer</span><span class="p">.</span><span class="nf">print</span><span class="p">(</span><span class="s">"   HP: {d}</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span> <span class="o">.</span><span class="p">{</span><span class="n">self</span><span class="p">.</span><span class="py">hp</span><span class="p">});</span>
        <span class="k">try</span> <span class="n">writer</span><span class="p">.</span><span class="nf">print</span><span class="p">(</span><span class="s">"   Direction: "</span><span class="p">,</span> <span class="o">.</span><span class="p">{});</span>
        <span class="k">try</span> <span class="n">self</span><span class="p">.</span><span class="py">direction</span><span class="p">.</span><span class="nf">print</span><span class="p">(</span><span class="n">writer</span><span class="p">);</span>
        <span class="k">try</span> <span class="n">writer</span><span class="p">.</span><span class="nf">print</span><span class="p">(</span><span class="s">" Alive: {s}</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span> <span class="o">.</span><span class="p">{</span><span class="k">if</span> <span class="p">(</span><span class="n">self</span><span class="p">.</span><span class="py">alive</span><span class="p">)</span> <span class="s">"true"</span> <span class="k">else</span> <span class="s">"false"</span><span class="p">});</span>

        <span class="k">try</span> <span class="n">writer</span><span class="p">.</span><span class="nf">print</span><span class="p">(</span><span class="s">"   Bullets:</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span> <span class="o">.</span><span class="p">{});</span>
        <span class="k">for</span> <span class="p">(</span><span class="n">self</span><span class="p">.</span><span class="py">bullets</span><span class="p">)</span> <span class="p">|</span><span class="n">bullet</span><span class="p">|</span> <span class="p">{</span>
            <span class="k">try</span> <span class="n">bullet</span><span class="p">.</span><span class="nf">print</span><span class="p">(</span><span class="n">writer</span><span class="p">);</span>
        <span class="p">}</span>
    <span class="p">}</span>
<span class="p">};</span>

<span class="k">const</span> <span class="n">PowerUpKind</span> <span class="o">=</span> <span class="k">enum</span> <span class="p">{</span>
    <span class="n">none</span><span class="p">,</span>
    <span class="n">hp_plus_onene</span><span class="p">,</span>
    <span class="n">hp_plus_threeee</span><span class="p">,</span>
    <span class="n">ammo_plus_onene</span><span class="p">,</span>

    <span class="k">pub</span> <span class="k">fn</span> <span class="n">print</span><span class="p">(</span><span class="n">self</span><span class="p">:</span> <span class="n">PowerUpKind</span><span class="p">,</span> <span class="n">writer</span><span class="p">:</span> <span class="n">anytype</span><span class="p">)</span> <span class="o">!</span><span class="k">void</span> <span class="p">{</span>
        <span class="k">const</span> <span class="n">kind_names</span> <span class="o">=</span> <span class="p">[</span><span class="mi">_</span><span class="p">][]</span><span class="k">const</span> <span class="kt">u8</span><span class="p">{</span>
            <span class="s">"none"</span><span class="p">,</span> <span class="s">"hp_plus_one"</span><span class="p">,</span> <span class="s">"hp_plus_three"</span><span class="p">,</span> <span class="s">"ammo_plus_one"</span><span class="p">,</span>
        <span class="p">};</span>
        <span class="k">try</span> <span class="n">writer</span><span class="p">.</span><span class="nf">print</span><span class="p">(</span><span class="s">"{s}"</span><span class="p">,</span> <span class="o">.</span><span class="p">{</span><span class="n">kind_names</span><span class="p">[</span><span class="n">@intFromEnum</span><span class="p">(</span><span class="n">self</span><span class="p">)]});</span>
    <span class="p">}</span>
<span class="p">};</span>

<span class="k">pub</span> <span class="k">const</span> <span class="n">GameStatus</span> <span class="o">=</span> <span class="k">enum</span> <span class="p">{</span>
    <span class="n">in_progresss</span><span class="p">,</span>
    <span class="n">closed</span><span class="p">,</span>

    <span class="k">pub</span> <span class="k">fn</span> <span class="n">print</span><span class="p">(</span><span class="n">self</span><span class="p">:</span> <span class="n">GameStatus</span><span class="p">,</span> <span class="n">writer</span><span class="p">:</span> <span class="n">anytype</span><span class="p">)</span> <span class="o">!</span><span class="k">void</span> <span class="p">{</span>
        <span class="k">const</span> <span class="n">status_names</span> <span class="o">=</span> <span class="p">[</span><span class="mi">_</span><span class="p">][]</span><span class="k">const</span> <span class="kt">u8</span><span class="p">{</span> <span class="s">"In Progress"</span><span class="p">,</span> <span class="s">"Closed"</span> <span class="p">};</span>
        <span class="k">try</span> <span class="n">writer</span><span class="p">.</span><span class="nf">print</span><span class="p">(</span><span class="s">"{s}"</span><span class="p">,</span> <span class="o">.</span><span class="p">{</span><span class="n">status_names</span><span class="p">[</span><span class="n">@intFromEnum</span><span class="p">(</span><span class="n">self</span><span class="p">)]});</span>
    <span class="p">}</span>
<span class="p">};</span>

<span class="k">pub</span> <span class="k">const</span> <span class="n">PowerUp</span> <span class="o">=</span> <span class="k">struct</span> <span class="p">{</span>
    <span class="n">position</span><span class="p">:</span> <span class="n">Vector2D</span><span class="p">,</span>
    <span class="n">kind</span><span class="p">:</span> <span class="n">PowerUpKind</span><span class="p">,</span>

    <span class="k">pub</span> <span class="k">fn</span> <span class="n">print</span><span class="p">(</span><span class="n">self</span><span class="p">:</span> <span class="n">PowerUp</span><span class="p">,</span> <span class="n">writer</span><span class="p">:</span> <span class="n">anytype</span><span class="p">)</span> <span class="o">!</span><span class="k">void</span> <span class="p">{</span>
        <span class="k">try</span> <span class="n">writer</span><span class="p">.</span><span class="nf">print</span><span class="p">(</span><span class="s">"position: "</span><span class="p">,</span> <span class="o">.</span><span class="p">{});</span>
        <span class="k">try</span> <span class="n">self</span><span class="p">.</span><span class="py">position</span><span class="p">.</span><span class="nf">print</span><span class="p">(</span><span class="n">writer</span><span class="p">);</span>
        <span class="k">try</span> <span class="n">writer</span><span class="p">.</span><span class="nf">print</span><span class="p">(</span><span class="s">" Kind: "</span><span class="p">,</span> <span class="o">.</span><span class="p">{});</span>
        <span class="k">try</span> <span class="n">self</span><span class="p">.</span><span class="py">kind</span><span class="p">.</span><span class="nf">print</span><span class="p">(</span><span class="n">writer</span><span class="p">);</span>
        <span class="k">try</span> <span class="n">writer</span><span class="p">.</span><span class="nf">print</span><span class="p">(</span><span class="s">"</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span> <span class="o">.</span><span class="p">{});</span>
    <span class="p">}</span>
<span class="p">};</span>

<span class="k">pub</span> <span class="k">const</span> <span class="n">GameState</span> <span class="o">=</span> <span class="k">struct</span> <span class="p">{</span>
    <span class="n">players</span><span class="p">:</span> <span class="n">std</span><span class="p">.</span><span class="nf">StringHashMap</span><span class="p">(</span><span class="n">Player</span><span class="p">),</span>
    <span class="n">status</span><span class="p">:</span> <span class="n">GameStatus</span><span class="p">,</span>
    <span class="n">power_ups</span><span class="p">:</span> <span class="p">[]</span><span class="n">PowerUp</span><span class="p">,</span>

    <span class="k">pub</span> <span class="k">fn</span> <span class="n">print</span><span class="p">(</span><span class="n">self</span><span class="p">:</span> <span class="n">GameState</span><span class="p">,</span> <span class="n">writer</span><span class="p">:</span> <span class="n">anytype</span><span class="p">)</span> <span class="o">!</span><span class="k">void</span> <span class="p">{</span>
        <span class="k">try</span> <span class="n">writer</span><span class="p">.</span><span class="nf">print</span><span class="p">(</span><span class="s">"GameState:</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span> <span class="o">.</span><span class="p">{});</span>
        <span class="k">try</span> <span class="n">writer</span><span class="p">.</span><span class="nf">print</span><span class="p">(</span><span class="s">" Power ups:</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span> <span class="o">.</span><span class="p">{});</span>
        <span class="k">for</span> <span class="p">(</span><span class="n">self</span><span class="p">.</span><span class="py">power_ups</span><span class="p">)</span> <span class="p">|</span><span class="n">power_up</span><span class="p">|</span> <span class="p">{</span>
            <span class="k">try</span> <span class="n">power_up</span><span class="p">.</span><span class="nf">print</span><span class="p">(</span><span class="n">writer</span><span class="p">);</span>
        <span class="p">}</span>

        <span class="k">try</span> <span class="n">writer</span><span class="p">.</span><span class="nf">print</span><span class="p">(</span><span class="s">" Players:</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span> <span class="o">.</span><span class="p">{});</span>
        <span class="k">var</span> <span class="n">iterator</span> <span class="o">=</span> <span class="n">self</span><span class="p">.</span><span class="py">players</span><span class="p">.</span><span class="nf">iterator</span><span class="p">();</span>
        <span class="k">var</span> <span class="n">player_index</span><span class="p">:</span> <span class="kt">usize</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
        <span class="k">while</span> <span class="p">(</span><span class="n">iterator</span><span class="p">.</span><span class="nf">next</span><span class="p">())</span> <span class="p">|</span><span class="n">entry</span><span class="p">|</span> <span class="p">{</span>
            <span class="k">try</span> <span class="n">writer</span><span class="p">.</span><span class="nf">print</span><span class="p">(</span><span class="s">"  Player {d} - {s}:</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span> <span class="o">.</span><span class="p">{</span> <span class="n">player_index</span><span class="p">,</span> <span class="n">entry</span><span class="p">.</span><span class="py">key_ptr</span><span class="o">.*</span> <span class="p">});</span>
            <span class="k">try</span> <span class="n">entry</span><span class="p">.</span><span class="py">value_ptr</span><span class="p">.</span><span class="nf">print</span><span class="p">(</span><span class="n">writer</span><span class="p">);</span>
            <span class="n">player_index</span> <span class="o">+=</span> <span class="mi">1</span><span class="p">;</span>
        <span class="p">}</span>
    <span class="p">}</span>
<span class="p">};</span>

<span class="k">pub</span> <span class="k">fn</span> <span class="n">decode</span><span class="p">(</span><span class="n">buffer</span><span class="p">:</span> <span class="p">[]</span><span class="k">const</span> <span class="kt">u8</span><span class="p">,</span> <span class="n">allocator</span><span class="p">:</span> <span class="n">std</span><span class="p">.</span><span class="py">mem</span><span class="p">.</span><span class="py">Allocator</span><span class="p">)</span> <span class="o">!</span><span class="n">GameState</span> <span class="p">{</span>
    <span class="k">var</span> <span class="n">buffered_stream</span> <span class="o">=</span> <span class="n">std</span><span class="p">.</span><span class="py">io</span><span class="p">.</span><span class="nf">fixedBufferStream</span><span class="p">(</span><span class="n">buffer</span><span class="p">);</span>
    <span class="k">var</span> <span class="n">reader</span> <span class="o">=</span> <span class="n">buffered_stream</span><span class="p">.</span><span class="nf">reader</span><span class="p">();</span>

    <span class="k">const</span> <span class="n">total_length</span> <span class="o">=</span> <span class="k">try</span> <span class="n">reader</span><span class="p">.</span><span class="nf">readInt</span><span class="p">(</span><span class="kt">i32</span><span class="p">,</span> <span class="p">.</span><span class="py">big</span><span class="p">);</span>
    <span class="k">const</span> <span class="n">game_status</span> <span class="o">=</span> <span class="k">try</span> <span class="n">reader</span><span class="p">.</span><span class="nf">readByte</span><span class="p">();</span>

    <span class="k">const</span> <span class="n">power_ups</span> <span class="o">=</span> <span class="k">try</span> <span class="n">decode_power_ups</span><span class="p">(</span><span class="n">reader</span><span class="p">,</span> <span class="n">allocator</span><span class="p">);</span>

    <span class="k">const</span> <span class="n">usize_total_length</span><span class="p">:</span> <span class="kt">usize</span> <span class="o">=</span> <span class="nb">@intCast</span><span class="p">(</span><span class="n">total_length</span><span class="p">);</span>

    <span class="c">// Rough calculation of the players count based on the bytes already</span>
    <span class="c">// read and the size expected for each player</span>
    <span class="k">const</span> <span class="n">players_count</span> <span class="o">=</span> <span class="p">(</span><span class="n">usize_total_length</span> <span class="o">-</span> <span class="p">(</span><span class="n">power_ups</span><span class="p">.</span><span class="py">len</span> <span class="o">*</span> <span class="n">power_up_size</span><span class="p">)</span> <span class="o">-</span> <span class="nb">@sizeOf</span><span class="p">(</span><span class="kt">u8</span><span class="p">)</span> <span class="o">-</span> <span class="nb">@sizeOf</span><span class="p">(</span><span class="kt">i32</span><span class="p">)</span> <span class="o">-</span> <span class="nb">@sizeOf</span><span class="p">(</span><span class="kt">i16</span><span class="p">))</span> <span class="o">/</span> <span class="n">player_size</span><span class="p">;</span>

    <span class="k">var</span> <span class="n">players</span> <span class="o">=</span> <span class="n">std</span><span class="p">.</span><span class="nf">StringHashMap</span><span class="p">(</span><span class="n">Player</span><span class="p">).</span><span class="nf">init</span><span class="p">(</span><span class="n">allocator</span><span class="p">);</span>
    <span class="k">for</span> <span class="p">(</span><span class="mi">0</span><span class="o">..</span><span class="n">players_count</span><span class="p">)</span> <span class="p">|</span><span class="mi">_</span><span class="p">|</span> <span class="p">{</span>
        <span class="k">var</span> <span class="n">player</span> <span class="o">=</span> <span class="n">Player</span><span class="p">{</span>
            <span class="p">.</span><span class="py">position</span> <span class="o">=</span> <span class="n">Vector2D</span><span class="p">{</span>
                <span class="p">.</span><span class="py">x</span> <span class="o">=</span> <span class="k">try</span> <span class="n">reader</span><span class="p">.</span><span class="nf">readInt</span><span class="p">(</span><span class="kt">i32</span><span class="p">,</span> <span class="p">.</span><span class="py">big</span><span class="p">),</span>
                <span class="p">.</span><span class="py">y</span> <span class="o">=</span> <span class="k">try</span> <span class="n">reader</span><span class="p">.</span><span class="nf">readInt</span><span class="p">(</span><span class="kt">i32</span><span class="p">,</span> <span class="p">.</span><span class="py">big</span><span class="p">),</span>
            <span class="p">},</span>
            <span class="p">.</span><span class="py">hp</span> <span class="o">=</span> <span class="k">try</span> <span class="n">reader</span><span class="p">.</span><span class="nf">readInt</span><span class="p">(</span><span class="kt">u8</span><span class="p">,</span> <span class="p">.</span><span class="py">big</span><span class="p">),</span>
            <span class="p">.</span><span class="py">alive</span> <span class="o">=</span> <span class="k">try</span> <span class="n">reader</span><span class="p">.</span><span class="nf">readInt</span><span class="p">(</span><span class="kt">u8</span><span class="p">,</span> <span class="p">.</span><span class="py">big</span><span class="p">)</span> <span class="o">!=</span> <span class="mi">0</span><span class="p">,</span> <span class="c">// Deserialize bool as u8</span>
            <span class="p">.</span><span class="py">direction</span> <span class="o">=</span> <span class="n">@enumFromInt</span><span class="p">(</span><span class="k">try</span> <span class="n">reader</span><span class="p">.</span><span class="nf">readInt</span><span class="p">(</span><span class="kt">u8</span><span class="p">,</span> <span class="p">.</span><span class="py">big</span><span class="p">)),</span>
            <span class="p">.</span><span class="py">bullets</span> <span class="o">=</span> <span class="k">undefined</span><span class="p">,</span>
        <span class="p">};</span>

        <span class="k">var</span> <span class="n">binary_player_id</span><span class="p">:</span> <span class="p">[</span><span class="mi">36</span><span class="p">]</span><span class="kt">u8</span> <span class="o">=</span> <span class="k">undefined</span><span class="p">;</span>
        <span class="mi">_</span> <span class="o">=</span> <span class="k">try</span> <span class="n">reader</span><span class="p">.</span><span class="nf">readAll</span><span class="p">(</span><span class="o">&amp;</span><span class="n">binary_player_id</span><span class="p">);</span>

        <span class="k">const</span> <span class="n">player_id</span> <span class="o">=</span> <span class="k">try</span> <span class="n">std</span><span class="p">.</span><span class="py">fmt</span><span class="p">.</span><span class="nf">allocPrint</span><span class="p">(</span><span class="n">allocator</span><span class="p">,</span> <span class="s">"{s}"</span><span class="p">,</span> <span class="o">.</span><span class="p">{</span><span class="n">binary_player_id</span><span class="p">[</span><span class="mi">0</span><span class="o">..</span><span class="p">]});</span>

        <span class="k">var</span> <span class="n">bullets</span><span class="p">:</span> <span class="p">[</span><span class="n">max_bullets</span><span class="p">]</span><span class="n">Bullet</span> <span class="o">=</span> <span class="k">undefined</span><span class="p">;</span>
        <span class="k">for</span> <span class="p">(</span><span class="o">&amp;</span><span class="n">bullets</span><span class="p">)</span> <span class="p">|</span><span class="o">*</span><span class="n">bullet</span><span class="p">|</span> <span class="p">{</span>
            <span class="n">bullet</span><span class="p">.</span><span class="py">position</span> <span class="o">=</span> <span class="n">Vector2D</span><span class="p">{</span>
                <span class="p">.</span><span class="py">x</span> <span class="o">=</span> <span class="k">try</span> <span class="n">reader</span><span class="p">.</span><span class="nf">readInt</span><span class="p">(</span><span class="kt">i32</span><span class="p">,</span> <span class="p">.</span><span class="py">big</span><span class="p">),</span>
                <span class="p">.</span><span class="py">y</span> <span class="o">=</span> <span class="k">try</span> <span class="n">reader</span><span class="p">.</span><span class="nf">readInt</span><span class="p">(</span><span class="kt">i32</span><span class="p">,</span> <span class="p">.</span><span class="py">big</span><span class="p">),</span>
            <span class="p">};</span>
            <span class="n">bullet</span><span class="p">.</span><span class="py">active</span> <span class="o">=</span> <span class="k">try</span> <span class="n">reader</span><span class="p">.</span><span class="nf">readInt</span><span class="p">(</span><span class="kt">u8</span><span class="p">,</span> <span class="p">.</span><span class="py">big</span><span class="p">)</span> <span class="o">!=</span> <span class="mi">0</span><span class="p">;</span> <span class="c">// Deserialize bool as u8</span>
            <span class="n">bullet</span><span class="p">.</span><span class="py">direction</span> <span class="o">=</span> <span class="n">@enumFromInt</span><span class="p">(</span><span class="k">try</span> <span class="n">reader</span><span class="p">.</span><span class="nf">readInt</span><span class="p">(</span><span class="kt">u8</span><span class="p">,</span> <span class="p">.</span><span class="py">big</span><span class="p">));</span>
        <span class="p">}</span>
        <span class="n">player</span><span class="p">.</span><span class="py">bullets</span> <span class="o">=</span> <span class="n">bullets</span><span class="p">;</span> <span class="c">// Assign bullets array</span>

        <span class="k">try</span> <span class="n">players</span><span class="p">.</span><span class="nf">put</span><span class="p">(</span><span class="n">player_id</span><span class="p">,</span> <span class="n">player</span><span class="p">);</span>
    <span class="p">}</span>

    <span class="k">return</span> <span class="n">GameState</span><span class="p">{</span> <span class="p">.</span><span class="py">players</span> <span class="o">=</span> <span class="n">players</span><span class="p">,</span> <span class="p">.</span><span class="py">power_ups</span> <span class="o">=</span> <span class="n">power_ups</span><span class="p">,</span> <span class="p">.</span><span class="py">status</span> <span class="o">=</span> <span class="n">@enumFromInt</span><span class="p">(</span><span class="n">game_status</span><span class="p">)</span> <span class="p">};</span>
<span class="p">}</span>

<span class="k">fn</span> <span class="n">decode_power_ups</span><span class="p">(</span><span class="n">reader</span><span class="p">:</span> <span class="n">anytype</span><span class="p">,</span> <span class="n">allocator</span><span class="p">:</span> <span class="n">std</span><span class="p">.</span><span class="py">mem</span><span class="p">.</span><span class="py">Allocator</span><span class="p">)</span> <span class="o">!</span><span class="p">[]</span><span class="n">PowerUp</span> <span class="p">{</span>
    <span class="k">const</span> <span class="n">raw_size</span> <span class="o">=</span> <span class="k">try</span> <span class="n">reader</span><span class="p">.</span><span class="nf">readInt</span><span class="p">(</span><span class="kt">i16</span><span class="p">,</span> <span class="p">.</span><span class="py">big</span><span class="p">);</span>
    <span class="k">const</span> <span class="n">size</span> <span class="o">=</span> <span class="nb">@divExact</span><span class="p">(</span><span class="n">raw_size</span><span class="p">,</span> <span class="p">((</span><span class="nb">@sizeOf</span><span class="p">(</span><span class="kt">i32</span><span class="p">)</span> <span class="o">*</span> <span class="mi">2</span><span class="p">)</span> <span class="o">+</span> <span class="nb">@sizeOf</span><span class="p">(</span><span class="kt">u8</span><span class="p">)));</span>

    <span class="k">if</span> <span class="p">(</span><span class="n">size</span> <span class="o">&lt;</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span>
        <span class="k">return</span> <span class="k">error</span><span class="p">.</span><span class="py">InvalidSize</span><span class="p">;</span> <span class="c">// Return an error for negative size</span>
    <span class="p">}</span>

    <span class="k">const</span> <span class="n">usize_size</span><span class="p">:</span> <span class="kt">usize</span> <span class="o">=</span> <span class="nb">@intCast</span><span class="p">(</span><span class="n">size</span><span class="p">);</span>

    <span class="k">const</span> <span class="n">power_ups</span> <span class="o">=</span> <span class="k">try</span> <span class="n">allocator</span><span class="p">.</span><span class="nf">alloc</span><span class="p">(</span><span class="n">PowerUp</span><span class="p">,</span> <span class="n">usize_size</span><span class="p">);</span>
    <span class="k">for</span> <span class="p">(</span><span class="n">power_ups</span><span class="p">)</span> <span class="p">|</span><span class="o">*</span><span class="n">power_up</span><span class="p">|</span> <span class="p">{</span>
        <span class="n">power_up</span><span class="p">.</span><span class="py">position</span> <span class="o">=</span> <span class="n">Vector2D</span><span class="p">{</span> <span class="p">.</span><span class="py">x</span> <span class="o">=</span> <span class="k">try</span> <span class="n">reader</span><span class="p">.</span><span class="nf">readInt</span><span class="p">(</span><span class="kt">i32</span><span class="p">,</span> <span class="p">.</span><span class="py">big</span><span class="p">),</span> <span class="p">.</span><span class="py">y</span> <span class="o">=</span> <span class="k">try</span> <span class="n">reader</span><span class="p">.</span><span class="nf">readInt</span><span class="p">(</span><span class="kt">i32</span><span class="p">,</span> <span class="p">.</span><span class="py">big</span><span class="p">)</span> <span class="p">};</span>
        <span class="n">power_up</span><span class="p">.</span><span class="py">kind</span> <span class="o">=</span> <span class="n">@enumFromInt</span><span class="p">(</span><span class="k">try</span> <span class="n">reader</span><span class="p">.</span><span class="nf">readByte</span><span class="p">());</span>
    <span class="p">}</span>

    <span class="k">return</span> <span class="n">power_ups</span><span class="p">;</span>
<span class="p">}</span>

</code></pre></div></div>
<p>Basically the <code class="language-plaintext highlighter-rouge">Zig</code> representation of the defined structures in the previous
part, enriched with a convenient debugging utility in the form of a simple
print function for each entity.</p>

<p>The gamestate is only one half of the puzzle, we must also define how to
interact with it, specifically how to send events, this part is crucial for the
client side.</p>

<div class="language-zig highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">const</span> <span class="n">std</span> <span class="o">=</span> <span class="nb">@import</span><span class="p">(</span><span class="s">"std"</span><span class="p">);</span>
<span class="k">const</span> <span class="n">gs</span> <span class="o">=</span> <span class="nb">@import</span><span class="p">(</span><span class="s">"gamestate.zig"</span><span class="p">);</span>

<span class="k">pub</span> <span class="k">const</span> <span class="n">PlayerId</span> <span class="o">=</span> <span class="p">[</span><span class="mi">36</span><span class="p">]</span><span class="kt">u8</span><span class="p">;</span>

<span class="k">const</span> <span class="n">move_u8</span><span class="p">:</span> <span class="kt">u8</span> <span class="o">=</span> <span class="mi">2</span><span class="p">;</span>
<span class="k">const</span> <span class="n">shoot_u8</span><span class="p">:</span> <span class="kt">u8</span> <span class="o">=</span> <span class="mi">3</span><span class="p">;</span>

<span class="k">pub</span> <span class="k">const</span> <span class="n">Event</span> <span class="o">=</span> <span class="k">union</span><span class="p">(</span><span class="k">enum</span><span class="p">)</span> <span class="p">{</span>
    <span class="n">move</span><span class="p">:</span> <span class="n">MoveEvent</span><span class="p">,</span>
    <span class="n">shoot</span><span class="p">:</span> <span class="n">PlayerId</span><span class="p">,</span>

    <span class="k">pub</span> <span class="k">const</span> <span class="n">MoveEvent</span> <span class="o">=</span> <span class="k">struct</span> <span class="p">{</span>
        <span class="n">player_id</span><span class="p">:</span> <span class="n">PlayerId</span><span class="p">,</span>
        <span class="n">direction</span><span class="p">:</span> <span class="n">gs</span><span class="p">.</span><span class="py">Direction</span><span class="p">,</span>
    <span class="p">};</span>
<span class="p">};</span>

<span class="k">pub</span> <span class="k">fn</span> <span class="n">encode</span><span class="p">(</span><span class="n">event</span><span class="p">:</span> <span class="n">Event</span><span class="p">,</span> <span class="n">allocator</span><span class="p">:</span> <span class="n">std</span><span class="p">.</span><span class="py">mem</span><span class="p">.</span><span class="py">Allocator</span><span class="p">)</span> <span class="o">!</span><span class="p">[]</span><span class="kt">u8</span> <span class="p">{</span>
    <span class="k">return</span> <span class="k">switch</span> <span class="p">(</span><span class="n">event</span><span class="p">)</span> <span class="p">{</span>
        <span class="p">.</span><span class="py">move</span> <span class="o">=&gt;</span> <span class="p">|</span><span class="n">move_event</span><span class="p">|</span> <span class="p">{</span>
            <span class="k">const</span> <span class="n">buffer</span> <span class="o">=</span> <span class="k">try</span> <span class="n">allocator</span><span class="p">.</span><span class="nf">alloc</span><span class="p">(</span><span class="kt">u8</span><span class="p">,</span> <span class="nb">@sizeOf</span><span class="p">(</span><span class="kt">u8</span><span class="p">)</span> <span class="o">+</span> <span class="nb">@sizeOf</span><span class="p">(</span><span class="n">Event</span><span class="p">.</span><span class="py">MoveEvent</span><span class="p">));</span>
            <span class="k">errdefer</span> <span class="n">allocator</span><span class="p">.</span><span class="nf">free</span><span class="p">(</span><span class="n">buffer</span><span class="p">);</span> <span class="c">// Ensure buffer is freed on error</span>

            <span class="k">var</span> <span class="n">buffered_stream</span> <span class="o">=</span> <span class="n">std</span><span class="p">.</span><span class="py">io</span><span class="p">.</span><span class="nf">fixedBufferStream</span><span class="p">(</span><span class="n">buffer</span><span class="p">);</span>
            <span class="k">var</span> <span class="n">writer</span> <span class="o">=</span> <span class="n">buffered_stream</span><span class="p">.</span><span class="nf">writer</span><span class="p">();</span>
            <span class="k">try</span> <span class="n">writer</span><span class="p">.</span><span class="nf">writeInt</span><span class="p">(</span><span class="kt">u8</span><span class="p">,</span> <span class="n">move_u8</span><span class="p">,</span> <span class="p">.</span><span class="py">big</span><span class="p">);</span>
            <span class="k">try</span> <span class="n">writer</span><span class="p">.</span><span class="nf">writeInt</span><span class="p">(</span><span class="kt">u8</span><span class="p">,</span> <span class="n">@intFromEnum</span><span class="p">(</span><span class="n">move_event</span><span class="p">.</span><span class="py">direction</span><span class="p">),</span> <span class="p">.</span><span class="py">big</span><span class="p">);</span>
            <span class="k">try</span> <span class="n">writer</span><span class="p">.</span><span class="nf">writeAll</span><span class="p">(</span><span class="o">&amp;</span><span class="n">move_event</span><span class="p">.</span><span class="py">player_id</span><span class="p">);</span>

            <span class="k">return</span> <span class="n">buffer</span><span class="p">;</span>
        <span class="p">},</span>
        <span class="p">.</span><span class="py">shoot</span> <span class="o">=&gt;</span> <span class="p">|</span><span class="n">player_id</span><span class="p">|</span> <span class="p">{</span>
            <span class="k">const</span> <span class="n">buffer</span> <span class="o">=</span> <span class="k">try</span> <span class="n">allocator</span><span class="p">.</span><span class="nf">alloc</span><span class="p">(</span><span class="kt">u8</span><span class="p">,</span> <span class="nb">@sizeOf</span><span class="p">(</span><span class="kt">u8</span><span class="p">)</span> <span class="o">+</span> <span class="nb">@sizeOf</span><span class="p">(</span><span class="n">PlayerId</span><span class="p">));</span>
            <span class="k">errdefer</span> <span class="n">allocator</span><span class="p">.</span><span class="nf">free</span><span class="p">(</span><span class="n">buffer</span><span class="p">);</span> <span class="c">// Ensure buffer is freed on error</span>

            <span class="k">var</span> <span class="n">buffered_stream</span> <span class="o">=</span> <span class="n">std</span><span class="p">.</span><span class="py">io</span><span class="p">.</span><span class="nf">fixedBufferStream</span><span class="p">(</span><span class="n">buffer</span><span class="p">);</span>
            <span class="k">var</span> <span class="n">writer</span> <span class="o">=</span> <span class="n">buffered_stream</span><span class="p">.</span><span class="nf">writer</span><span class="p">();</span>
            <span class="k">try</span> <span class="n">writer</span><span class="p">.</span><span class="nf">writeInt</span><span class="p">(</span><span class="kt">u8</span><span class="p">,</span> <span class="n">shoot_u8</span><span class="p">,</span> <span class="p">.</span><span class="py">big</span><span class="p">);</span>
            <span class="k">try</span> <span class="n">writer</span><span class="p">.</span><span class="nf">writeAll</span><span class="p">(</span><span class="o">&amp;</span><span class="n">player_id</span><span class="p">);</span>

            <span class="k">return</span> <span class="n">buffer</span><span class="p">;</span>
        <span class="p">},</span>
    <span class="p">};</span>
<span class="p">}</span>
</code></pre></div></div>

<h3 id="tcp-connection">TCP connection</h3>

<p>Going fast here, the next step required is the connection piece to actually
talk with the server side, starting from some helper functions. Zig has a pretty
rich and well defined network layer so the resulting module is very straight
forward and self-contained.</p>

<p>What we need to support is a request-reply paradigm essentially</p>

<ul>
  <li>A <code class="language-plaintext highlighter-rouge">receiveUpdate</code> function to read the serialized gamestate from the wire coming
from the server</li>
  <li>A <code class="language-plaintext highlighter-rouge">sendUpdate</code> to interact and modify the received gamestate and update it
on the server side</li>
</ul>

<div class="language-zig highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">const</span> <span class="n">std</span> <span class="o">=</span> <span class="nb">@import</span><span class="p">(</span><span class="s">"std"</span><span class="p">);</span>
<span class="k">const</span> <span class="n">net</span> <span class="o">=</span> <span class="n">std</span><span class="p">.</span><span class="py">net</span><span class="p">;</span>
<span class="k">const</span> <span class="n">gs</span> <span class="o">=</span> <span class="nb">@import</span><span class="p">(</span><span class="s">"gamestate.zig"</span><span class="p">);</span>

<span class="k">const</span> <span class="n">bufsize</span><span class="p">:</span> <span class="kt">usize</span> <span class="o">=</span> <span class="mi">1024</span><span class="p">;</span>

<span class="k">pub</span> <span class="k">fn</span> <span class="n">connect</span><span class="p">(</span><span class="n">host</span><span class="p">:</span> <span class="p">[]</span><span class="k">const</span> <span class="kt">u8</span><span class="p">,</span> <span class="n">port</span><span class="p">:</span> <span class="nb">comptime_int</span><span class="p">)</span> <span class="o">!</span><span class="n">net</span><span class="p">.</span><span class="py">Stream</span> <span class="p">{</span>
    <span class="k">const</span> <span class="n">peer</span> <span class="o">=</span> <span class="k">try</span> <span class="n">net</span><span class="p">.</span><span class="py">Address</span><span class="p">.</span><span class="nf">parseIp4</span><span class="p">(</span><span class="n">host</span><span class="p">,</span> <span class="n">port</span><span class="p">);</span>
    <span class="k">const</span> <span class="n">stream</span> <span class="o">=</span> <span class="k">try</span> <span class="n">net</span><span class="p">.</span><span class="nf">tcpConnectToAddress</span><span class="p">(</span><span class="n">peer</span><span class="p">);</span>
    <span class="k">return</span> <span class="n">stream</span><span class="p">;</span>
<span class="p">}</span>

<span class="c">// TODO Placeholder, this will carry some additional logic going forward</span>
<span class="k">pub</span> <span class="k">fn</span> <span class="n">handshake</span><span class="p">(</span><span class="n">stream</span><span class="p">:</span> <span class="o">*</span><span class="k">const</span> <span class="n">net</span><span class="p">.</span><span class="py">Stream</span><span class="p">,</span> <span class="n">buffer</span><span class="p">:</span> <span class="o">*</span><span class="p">[</span><span class="mi">36</span><span class="p">]</span><span class="kt">u8</span><span class="p">)</span> <span class="o">!</span><span class="k">void</span> <span class="p">{</span>
    <span class="mi">_</span> <span class="o">=</span> <span class="k">try</span> <span class="n">stream</span><span class="p">.</span><span class="nf">reader</span><span class="p">().</span><span class="nf">read</span><span class="p">(</span><span class="n">buffer</span><span class="p">);</span>
<span class="p">}</span>

<span class="k">pub</span> <span class="k">fn</span> <span class="n">receiveUpdate</span><span class="p">(</span><span class="n">stream</span><span class="p">:</span> <span class="o">*</span><span class="k">const</span> <span class="n">net</span><span class="p">.</span><span class="py">Stream</span><span class="p">,</span> <span class="n">allocator</span><span class="p">:</span> <span class="n">std</span><span class="p">.</span><span class="py">mem</span><span class="p">.</span><span class="py">Allocator</span><span class="p">)</span> <span class="o">!</span><span class="n">gs</span><span class="p">.</span><span class="py">GameState</span> <span class="p">{</span>
    <span class="k">var</span> <span class="n">buffer</span><span class="p">:</span> <span class="p">[</span><span class="n">bufsize</span><span class="p">]</span><span class="kt">u8</span> <span class="o">=</span> <span class="k">undefined</span><span class="p">;</span>

    <span class="mi">_</span> <span class="o">=</span> <span class="k">try</span> <span class="n">stream</span><span class="p">.</span><span class="nf">reader</span><span class="p">().</span><span class="nf">read</span><span class="p">(</span><span class="o">&amp;</span><span class="n">buffer</span><span class="p">);</span>

    <span class="k">return</span> <span class="n">gs</span><span class="p">.</span><span class="nf">decode</span><span class="p">(</span><span class="o">&amp;</span><span class="n">buffer</span><span class="p">,</span> <span class="n">allocator</span><span class="p">);</span>
<span class="p">}</span>

<span class="k">pub</span> <span class="k">fn</span> <span class="n">sendUpdate</span><span class="p">(</span><span class="n">stream</span><span class="p">:</span> <span class="o">*</span><span class="k">const</span> <span class="n">net</span><span class="p">.</span><span class="py">Stream</span><span class="p">,</span> <span class="n">buffer</span><span class="p">:</span> <span class="p">[]</span><span class="k">const</span> <span class="kt">u8</span><span class="p">)</span> <span class="o">!</span><span class="k">void</span> <span class="p">{</span>
    <span class="k">try</span> <span class="n">stream</span><span class="p">.</span><span class="nf">writer</span><span class="p">().</span><span class="nf">writeAll</span><span class="p">(</span><span class="n">buffer</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Besides the typical TCP stream features to connect, there is an <code class="language-plaintext highlighter-rouge">handshake</code>
function defined which will later on handle the handshake between the server
and clients, thinking about authentication, tokens and reconnection logic to
resume a game etc. For now it carries the simplest placeholder of reading what
comes first thing from the server, i.e. the generated player ID.</p>

<h3 id="reading-the-first-game-state">Reading the first game state</h3>

<p>We have all the elements for a basic interaction now, i.e. a connection to the
server and print the original state of the game as a new player joining the game.
This can be done, for simplicty, in the main function to begin with</p>

<div class="language-zig highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c">//! Dogfight! client side</span>
<span class="c">//!</span>
<span class="c">//! Connects to the gameserver running at port 6699 and receive the gamestate</span>
<span class="c">//! with its own positioned ship</span>
<span class="k">const</span> <span class="n">std</span> <span class="o">=</span> <span class="nb">@import</span><span class="p">(</span><span class="s">"std"</span><span class="p">);</span>
<span class="k">const</span> <span class="n">net</span> <span class="o">=</span> <span class="nb">@import</span><span class="p">(</span><span class="s">"network.zig"</span><span class="p">);</span>
<span class="k">const</span> <span class="n">gs</span> <span class="o">=</span> <span class="nb">@import</span><span class="p">(</span><span class="s">"gamestate.zig"</span><span class="p">);</span>

<span class="k">const</span> <span class="n">server_port</span> <span class="o">=</span> <span class="mi">6699</span><span class="p">;</span>

<span class="k">pub</span> <span class="k">fn</span> <span class="n">main</span><span class="p">()</span> <span class="k">anyerror</span><span class="o">!</span><span class="k">void</span> <span class="p">{</span>
    <span class="n">std</span><span class="p">.</span><span class="py">debug</span><span class="p">.</span><span class="nf">print</span><span class="p">(</span><span class="s">"Connecting to game server {}</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span> <span class="o">.</span><span class="p">{</span><span class="n">server_port</span><span class="p">});</span>

    <span class="k">const</span> <span class="n">allocator</span> <span class="o">=</span> <span class="n">std</span><span class="p">.</span><span class="py">heap</span><span class="p">.</span><span class="py">page_allocator</span><span class="p">;</span>
    <span class="c">// Connect to peer</span>
    <span class="k">const</span> <span class="n">stream</span> <span class="o">=</span> <span class="k">try</span> <span class="n">net</span><span class="p">.</span><span class="nf">connect</span><span class="p">(</span><span class="s">"127.0.0.1"</span><span class="p">,</span> <span class="n">server_port</span><span class="p">);</span>
    <span class="k">defer</span> <span class="n">stream</span><span class="p">.</span><span class="nf">close</span><span class="p">();</span>

    <span class="c">// Read player ID</span>
    <span class="k">var</span> <span class="n">player_id</span><span class="p">:</span> <span class="p">[</span><span class="mi">36</span><span class="p">]</span><span class="kt">u8</span> <span class="o">=</span> <span class="k">undefined</span><span class="p">;</span>
    <span class="k">try</span> <span class="n">net</span><span class="p">.</span><span class="nf">handshake</span><span class="p">(</span><span class="o">&amp;</span><span class="n">stream</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">player_id</span><span class="p">);</span>
    <span class="n">std</span><span class="p">.</span><span class="py">debug</span><span class="p">.</span><span class="nf">print</span><span class="p">(</span><span class="s">"Player ID: {s}</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span> <span class="o">.</span><span class="p">{</span><span class="n">player_id</span><span class="p">});</span>

    <span class="c">// Receive the game state from the server</span>
    <span class="k">const</span> <span class="n">gamestate</span> <span class="o">=</span> <span class="k">try</span> <span class="n">net</span><span class="p">.</span><span class="nf">receiveUpdate</span><span class="p">(</span><span class="o">&amp;</span><span class="n">stream</span><span class="p">,</span> <span class="n">allocator</span><span class="p">);</span>
    <span class="c">// Log it to console for debug</span>
    <span class="k">const</span> <span class="n">stdout</span> <span class="o">=</span> <span class="n">std</span><span class="p">.</span><span class="py">io</span><span class="p">.</span><span class="nf">getStdOut</span><span class="p">().</span><span class="nf">writer</span><span class="p">();</span>
    <span class="k">try</span> <span class="n">gamestate</span><span class="p">.</span><span class="nf">print</span><span class="p">(</span><span class="n">stdout</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>

<p>A simple <code class="language-plaintext highlighter-rouge">mix run --no-halt</code> on the <code class="language-plaintext highlighter-rouge">Elixir</code> server side to run the server
component, a <code class="language-plaintext highlighter-rouge">zig build run</code> in the client directory and the game state
should be printed on the terminal.</p>]]></content><author><name></name></author><category term="elixir" /><category term="networking" /><category term="c" /><category term="game-development" /><category term="low-level" /><summary type="html"><![CDATA[Old school multiplayer arcade game development journey]]></summary></entry><entry><title type="html">Dogfight! - Binary protocol</title><link href="/posts/dogfight-binary-protocol/" rel="alternate" type="text/html" title="Dogfight! - Binary protocol" /><published>2025-01-10T00:00:00+00:00</published><updated>2025-01-10T00:00:00+00:00</updated><id>/posts/dogfight-binary-protocol</id><content type="html" xml:base="/posts/dogfight-binary-protocol/"><![CDATA[<p>A very simple core that should allow for basic interactions is roughly defined,
the prototype is currently comprised by:</p>

<ul>
  <li>A TCP server, with very rudimentary handling of connections</li>
  <li>A game state, representing the main structure of any given match
    <ul>
      <li>A default spaceship</li>
    </ul>
  </li>
  <li>An event-based system for game state updates</li>
</ul>

<p>The next step is to setup a proper transport layer to communicate with
the connecting clients and start to show something. Before moving on
with the first implementation of a client and some basic graphics,
we’re going to define how this communication layer will be implemented.</p>

<p>Although various formats can be used, from a text based protocol, JSON or
other various binary based structure serialization buffers such as
protobuf or thrift, we’re going to roll our own micro protocol by hand.
One suggestion could be to use the built-in <code class="language-plaintext highlighter-rouge">:erlang.to_binary_term/2</code>
and <code class="language-plaintext highlighter-rouge">:erlang.term_to_binary/1</code>, but I like the idea of writing a small
compact format that I can easily support on the client side without having
to dig deeper into the Erlang binary format.</p>

<p>As a first draft, it should be something that allows to represent two main
entities</p>

<ul>
  <li>The game state</li>
  <li>The game events</li>
</ul>

<h2 id="the-game-events">The game events</h2>

<p>We start from the simplest and smallest of the two, literally two or three
fields stuffed in a byte array, focussing on just two actions which can be
easily represented as</p>

<ul>
  <li><code class="language-plaintext highlighter-rouge">move</code> - <code class="language-plaintext highlighter-rouge">player_id</code> - <code class="language-plaintext highlighter-rouge">direction</code>
    <ul>
      <li>action: <code class="language-plaintext highlighter-rouge">u8</code></li>
      <li>direction: <code class="language-plaintext highlighter-rouge">u8</code></li>
      <li>player_id: <code class="language-plaintext highlighter-rouge">[]u8</code></li>
    </ul>
  </li>
  <li><code class="language-plaintext highlighter-rouge">shoot</code> - <code class="language-plaintext highlighter-rouge">player_id</code>
    <ul>
      <li>action: <code class="language-plaintext highlighter-rouge">u8</code></li>
      <li>player_id: <code class="language-plaintext highlighter-rouge">[]u8</code></li>
    </ul>
  </li>
</ul>

<h3 id="implementation-helpers">Implementation helpers</h3>

<p>Again, a little behaviour can be useful here and doesn’t really represent an
excessive burden, if one day we wanted to add multiple protocols, this would
provide a simple interface for that</p>

<div class="language-elixir highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">defmodule</span> <span class="no">Dogfight</span><span class="o">.</span><span class="no">Game</span><span class="o">.</span><span class="no">Codec</span> <span class="k">do</span>
  <span class="nv">@moduledoc</span> <span class="sd">"""
  Generic encoding and decoding behaviour
  """</span>

  <span class="n">alias</span> <span class="no">Dogfight</span><span class="o">.</span><span class="no">Game</span><span class="o">.</span><span class="no">State</span>
  <span class="n">alias</span> <span class="no">Dogfight</span><span class="o">.</span><span class="no">Game</span><span class="o">.</span><span class="no">Event</span>

  <span class="nv">@callback</span> <span class="n">encode</span><span class="p">(</span><span class="no">State</span><span class="o">.</span><span class="n">t</span><span class="p">())</span> <span class="p">::</span> <span class="n">binary</span><span class="p">()</span>
  <span class="nv">@callback</span> <span class="n">decode</span><span class="p">(</span><span class="n">binary</span><span class="p">())</span> <span class="p">::</span> <span class="p">{</span><span class="ss">:ok</span><span class="p">,</span> <span class="no">State</span><span class="o">.</span><span class="n">t</span><span class="p">()}</span> <span class="o">|</span> <span class="p">{</span><span class="ss">:error</span><span class="p">,</span> <span class="ss">:codec_error</span><span class="p">}</span>

  <span class="nv">@callback</span> <span class="n">encode_event</span><span class="p">(</span><span class="no">Event</span><span class="o">.</span><span class="n">t</span><span class="p">())</span> <span class="p">::</span> <span class="n">binary</span><span class="p">()</span>
  <span class="nv">@callback</span> <span class="n">decode_event</span><span class="p">(</span><span class="n">binary</span><span class="p">())</span> <span class="p">::</span> <span class="p">{</span><span class="ss">:ok</span><span class="p">,</span> <span class="no">Event</span><span class="o">.</span><span class="n">t</span><span class="p">()}</span> <span class="o">|</span> <span class="p">{</span><span class="ss">:error</span><span class="p">,</span> <span class="ss">:codec_error</span><span class="p">}</span>
<span class="k">end</span>
</code></pre></div></div>

<p>Probably not necessary but let’s start by explicitly dividing the responsibilities
of the encoder/decoder functions by the target struct that they will handle. Let’s
also add a small helper module to deal with binary payloads. Luckily, <code class="language-plaintext highlighter-rouge">Elixir</code>
allows for very convenient handling of input from the wire, pattern matching really
shines here.</p>

<div class="language-elixir highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">defmodule</span> <span class="no">Dogfight</span><span class="o">.</span><span class="no">Game</span><span class="o">.</span><span class="no">Codecs</span><span class="o">.</span><span class="no">Helpers</span> <span class="k">do</span>
  <span class="nv">@moduledoc</span> <span class="sd">"""
  This module provides helper functions for encoding data into binary format.

  The module supports encoding integers of various sizes (half word, word,
  double word, quad word) and binary data.
  """</span>

  <span class="nv">@half_word</span> <span class="mi">8</span>
  <span class="nv">@word</span> <span class="mi">16</span>
  <span class="nv">@double_word</span> <span class="mi">32</span>
  <span class="nv">@quad_word</span> <span class="mi">64</span>

  <span class="nv">@type</span> <span class="n">word_size</span> <span class="p">::</span> <span class="ss">:half_word</span> <span class="o">|</span> <span class="ss">:word</span> <span class="o">|</span> <span class="ss">:double_word</span> <span class="o">|</span> <span class="ss">:quad_word</span> <span class="o">|</span> <span class="ss">:binary</span>
  <span class="nv">@type</span> <span class="n">field</span> <span class="p">::</span> <span class="p">{</span><span class="n">integer</span><span class="p">(),</span> <span class="n">word_size</span><span class="p">()}</span>

  <span class="nv">@doc</span> <span class="sd">"""
  Encodes a list of fields into a binary.

  Each field is a tuple containing an integer and a word size. The word size
  determines the number of bits used to encode the integer.

  ## Parameters

    - fields: A list of tuples where each tuple contains an integer and a word size.

  ## Returns

    - A binary representing the encoded fields.

  ## Examples

      iex&gt; Dogfight.Game.Codecs.Helpers.encode_list([{1, :half_word}, {2, :word}])
      &lt;&lt;1::8, 2::16&gt;&gt;
  """</span>
  <span class="nv">@spec</span> <span class="n">encode_list</span><span class="p">([</span><span class="n">field</span><span class="p">(),</span> <span class="o">...</span><span class="p">])</span> <span class="p">::</span> <span class="n">binary</span><span class="p">()</span>
  <span class="k">def</span> <span class="n">encode_list</span><span class="p">(</span><span class="n">fields</span><span class="p">)</span> <span class="k">do</span>
    <span class="n">fields</span>
    <span class="o">|&gt;</span> <span class="no">Enum</span><span class="o">.</span><span class="n">map</span><span class="p">(</span><span class="k">fn</span> <span class="p">{</span><span class="n">field</span><span class="p">,</span> <span class="n">word_size</span><span class="p">}</span> <span class="o">-&gt;</span>
      <span class="k">case</span> <span class="n">word_size</span> <span class="k">do</span>
        <span class="ss">:half_word</span> <span class="o">-&gt;</span> <span class="n">encode_half_word</span><span class="p">(</span><span class="n">field</span><span class="p">)</span>
        <span class="ss">:word</span> <span class="o">-&gt;</span> <span class="n">encode_word</span><span class="p">(</span><span class="n">field</span><span class="p">)</span>
        <span class="ss">:double_word</span> <span class="o">-&gt;</span> <span class="n">encode_double_word</span><span class="p">(</span><span class="n">field</span><span class="p">)</span>
        <span class="ss">:quad_word</span> <span class="o">-&gt;</span> <span class="n">encode_quad_word</span><span class="p">(</span><span class="n">field</span><span class="p">)</span>
        <span class="ss">:binary</span> <span class="o">-&gt;</span> <span class="n">field</span>
      <span class="k">end</span>
    <span class="k">end</span><span class="p">)</span>
    <span class="o">|&gt;</span> <span class="no">IO</span><span class="o">.</span><span class="n">iodata_to_binary</span><span class="p">()</span>
  <span class="k">end</span>

  <span class="k">def</span> <span class="n">encode_half_word</span><span class="p">(</span><span class="n">data</span><span class="p">),</span> <span class="k">do</span><span class="p">:</span> <span class="n">encode_integer</span><span class="p">(</span><span class="n">data</span><span class="p">,</span> <span class="nv">@half_word</span><span class="p">)</span>
  <span class="k">def</span> <span class="n">encode_word</span><span class="p">(</span><span class="n">data</span><span class="p">),</span> <span class="k">do</span><span class="p">:</span> <span class="n">encode_integer</span><span class="p">(</span><span class="n">data</span><span class="p">,</span> <span class="nv">@word</span><span class="p">)</span>
  <span class="k">def</span> <span class="n">encode_double_word</span><span class="p">(</span><span class="n">data</span><span class="p">),</span> <span class="k">do</span><span class="p">:</span> <span class="n">encode_integer</span><span class="p">(</span><span class="n">data</span><span class="p">,</span> <span class="nv">@double_word</span><span class="p">)</span>
  <span class="k">def</span> <span class="n">encode_quad_word</span><span class="p">(</span><span class="n">data</span><span class="p">),</span> <span class="k">do</span><span class="p">:</span> <span class="n">encode_integer</span><span class="p">(</span><span class="n">data</span><span class="p">,</span> <span class="nv">@quad_word</span><span class="p">)</span>

  <span class="k">def</span> <span class="n">encode_integer</span><span class="p">(</span><span class="no">true</span><span class="p">,</span> <span class="n">size</span><span class="p">),</span> <span class="k">do</span><span class="p">:</span> <span class="n">encode_integer</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span> <span class="n">size</span><span class="p">)</span>
  <span class="k">def</span> <span class="n">encode_integer</span><span class="p">(</span><span class="no">false</span><span class="p">,</span> <span class="n">size</span><span class="p">),</span> <span class="k">do</span><span class="p">:</span> <span class="n">encode_integer</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="n">size</span><span class="p">)</span>
  <span class="k">def</span> <span class="n">encode_integer</span><span class="p">(</span><span class="n">data</span><span class="p">,</span> <span class="n">size</span><span class="p">)</span> <span class="ow">when</span> <span class="n">is_integer</span><span class="p">(</span><span class="n">data</span><span class="p">),</span> <span class="k">do</span><span class="p">:</span> <span class="o">&lt;&lt;</span><span class="n">data</span><span class="p">::</span><span class="n">integer</span><span class="o">-</span><span class="n">size</span><span class="p">(</span><span class="n">size</span><span class="p">)</span><span class="o">&gt;&gt;</span>

  <span class="k">def</span> <span class="n">half_word_size</span><span class="p">,</span> <span class="k">do</span><span class="p">:</span> <span class="nv">@half_word</span>
  <span class="k">def</span> <span class="n">word_size</span><span class="p">,</span> <span class="k">do</span><span class="p">:</span> <span class="nv">@word</span>
  <span class="k">def</span> <span class="n">double_word_size</span><span class="p">,</span> <span class="k">do</span><span class="p">:</span> <span class="nv">@double_word</span>
  <span class="k">def</span> <span class="n">quad_word_size</span><span class="p">,</span> <span class="k">do</span><span class="p">:</span> <span class="nv">@quad_word</span>
<span class="k">end</span>
</code></pre></div></div>

<p>We’re gonna serialize our payloads in network order aka big-endian, the main
function of interest here is <code class="language-plaintext highlighter-rouge">encode_list/1</code> which allows to pass a list of
tuples in the form of <code class="language-plaintext highlighter-rouge">{field_value, value_size}</code>, this will be convenient
when implementing the actual serialization of the game state.</p>

<h2 id="the-game-state">The game state</h2>

<p>Let’s start simple, an header containing the full length of the packet and
then straight with the payload</p>

<ul>
  <li><strong>Header</strong>
    <ul>
      <li>total_length: <code class="language-plaintext highlighter-rouge">usize</code></li>
    </ul>
  </li>
  <li><strong>Payload</strong>
    <ul>
      <li>status: <code class="language-plaintext highlighter-rouge">u8</code></li>
      <li>power_ups_count: <code class="language-plaintext highlighter-rouge">u16</code></li>
      <li>power_ups
        <ul>
          <li>pos_x: <code class="language-plaintext highlighter-rouge">i32</code></li>
          <li>pos_y: <code class="language-plaintext highlighter-rouge">i32</code></li>
          <li>kind: <code class="language-plaintext highlighter-rouge">u8</code></li>
        </ul>
      </li>
      <li>players
        <ul>
          <li>pos_x: <code class="language-plaintext highlighter-rouge">i32</code></li>
          <li>pos_y: <code class="language-plaintext highlighter-rouge">i32</code></li>
          <li>hp: <code class="language-plaintext highlighter-rouge">i8</code></li>
          <li>alive: <code class="language-plaintext highlighter-rouge">u8</code></li>
          <li>direction: <code class="language-plaintext highlighter-rouge">u8</code></li>
          <li>player_id: <code class="language-plaintext highlighter-rouge">[]u8</code></li>
          <li>bullets
            <ul>
              <li>pos_x: <code class="language-plaintext highlighter-rouge">i32</code></li>
              <li>pos_y: <code class="language-plaintext highlighter-rouge">i32</code></li>
              <li>active: <code class="language-plaintext highlighter-rouge">u8</code></li>
              <li>direction: <code class="language-plaintext highlighter-rouge">u8</code></li>
            </ul>
          </li>
        </ul>
      </li>
    </ul>
  </li>
</ul>

<p>Pretty small, non-optimized format containing all the information needed to have
basic movements and interaction between entities. For many fields we could
actually use some <code class="language-plaintext highlighter-rouge">nibbles</code> or encode more aggressively by using each byte
space more efficiently, for example, <code class="language-plaintext highlighter-rouge">status</code>, <code class="language-plaintext highlighter-rouge">direction</code> and <code class="language-plaintext highlighter-rouge">kind</code> are
likely to not ever represent more then a bunch of values, <code class="language-plaintext highlighter-rouge">active</code> and <code class="language-plaintext highlighter-rouge">alive</code>
are even just 1 bit, so all those informations could be easily packed in 1 or 2
bytes but it’s not that pressuring as the full payload is still fairly small</p>

<p>What we expect to receive on the client side, after the deserialization of the
payload, is something resembling the following</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>//
// The structure is simple enough for the time being, just a variable power up
// count followed by the players (spaceships), each one has only a 5 bullets.
//
// GameState
// ---------
//  Status: In Progress
//
//  Power ups
//  ---------
//   Kind: hp_plus_three
//   Position: (407, 209)
//
//  Players
//  -------
//   Player 0 - 5afd1a7c-50c6-4a55-be57-0f02cef8e48e:
//    position: (533, 353)
//    HP: 5 Direction: up Alive: true
//
//    Bullets
//    -------
//     position: (343, 123) Direction: up Active: true
//     position: (241, 123) Direction: up Active: true
//     position: (167, 123) Direction: up Active: true
//     position: (101, 123) Direction: up Active: true
//     position: (0, 0) Direction: idle Active: false
</code></pre></div></div>

<h3 id="implementation">Implementation</h3>

<p>The following module will basically expose the <code class="language-plaintext highlighter-rouge">Dogfight.Game.Codec</code> behaviour
defined above with a couple of helpers to translate atoms to integer back and
forth to simplify the encoding. Those values could probably be sent through the
wire as strings too, avoiding the endianness handling altogether, I just find it
more appropriate and often more compact to encode them as integer values.</p>

<div class="language-elixir highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">defmodule</span> <span class="no">Dogfight</span><span class="o">.</span><span class="no">Game</span><span class="o">.</span><span class="no">Codecs</span><span class="o">.</span><span class="no">BinaryCodec</span> <span class="k">do</span>
  <span class="nv">@moduledoc</span> <span class="sd">"""
  Binary implementation of the game encoding and decoding logic

  Handles

  - Game state
  - Game event
  """</span>

  <span class="nv">@behaviour</span> <span class="no">Dogfight</span><span class="o">.</span><span class="no">Game</span><span class="o">.</span><span class="no">Codec</span>

  <span class="n">alias</span> <span class="no">Dogfight</span><span class="o">.</span><span class="no">Game</span><span class="o">.</span><span class="no">Codecs</span><span class="o">.</span><span class="no">Helpers</span>
  <span class="n">alias</span> <span class="no">Dogfight</span><span class="o">.</span><span class="no">Game</span><span class="o">.</span><span class="no">State</span>
  <span class="n">alias</span> <span class="no">Dogfight</span><span class="o">.</span><span class="no">Game</span><span class="o">.</span><span class="no">Vec2</span>

  <span class="nv">@power_up_size</span> <span class="no">Helpers</span><span class="o">.</span><span class="n">double_word_size</span><span class="p">()</span> <span class="o">*</span> <span class="mi">2</span> <span class="o">+</span> <span class="no">Helpers</span><span class="o">.</span><span class="n">half_word_size</span><span class="p">()</span>
  <span class="nv">@bullet_size</span> <span class="no">Helpers</span><span class="o">.</span><span class="n">double_word_size</span><span class="p">()</span> <span class="o">*</span> <span class="mi">2</span> <span class="o">+</span> <span class="no">Helpers</span><span class="o">.</span><span class="n">half_word_size</span><span class="p">()</span> <span class="o">*</span> <span class="mi">2</span>
  <span class="c1"># (32 bytes and 4 hyphens as per UUID)</span>
  <span class="nv">@player_id_size</span> <span class="no">Helpers</span><span class="o">.</span><span class="n">double_word_size</span><span class="p">()</span> <span class="o">+</span> <span class="mi">4</span>
  <span class="nv">@spaceship_size</span> <span class="nv">@player_id_size</span> <span class="o">+</span> <span class="mi">5</span> <span class="o">*</span> <span class="nv">@bullet_size</span> <span class="o">+</span>
                    <span class="mi">3</span> <span class="o">*</span> <span class="no">Helpers</span><span class="o">.</span><span class="n">double_word_size</span><span class="p">()</span> <span class="o">+</span> <span class="mi">2</span> <span class="o">*</span> <span class="no">Helpers</span><span class="o">.</span><span class="n">half_word_size</span><span class="p">()</span>

  <span class="nv">@impl</span> <span class="no">true</span>
  <span class="k">def</span> <span class="n">encode_event</span><span class="p">(</span><span class="n">event</span><span class="p">)</span> <span class="k">do</span>
    <span class="k">case</span> <span class="n">event</span> <span class="k">do</span>
      <span class="p">{</span><span class="ss">:move</span><span class="p">,</span> <span class="p">{</span><span class="n">player_id</span><span class="p">,</span> <span class="n">direction</span><span class="p">}}</span> <span class="o">-&gt;</span>
        <span class="no">Helpers</span><span class="o">.</span><span class="n">encode_list</span><span class="p">([</span>
          <span class="p">{</span><span class="n">event_to_int</span><span class="p">(</span><span class="ss">:move</span><span class="p">),</span> <span class="ss">:half_word</span><span class="p">},</span>
          <span class="p">{</span><span class="n">direction_to_int</span><span class="p">(</span><span class="n">direction</span><span class="p">),</span> <span class="ss">:half_word</span><span class="p">},</span>
          <span class="p">{</span><span class="n">player_id</span><span class="p">,</span> <span class="ss">:binary</span><span class="p">}</span>
        <span class="p">])</span>

      <span class="p">{</span><span class="n">player_action</span><span class="p">,</span> <span class="n">player_id</span><span class="p">}</span>
      <span class="ow">when</span> <span class="n">player_action</span> <span class="ow">in</span> <span class="p">[</span><span class="ss">:player_connection</span><span class="p">,</span> <span class="ss">:player_disconnection</span><span class="p">,</span> <span class="ss">:shoot</span><span class="p">]</span> <span class="o">-&gt;</span>
        <span class="no">Helpers</span><span class="o">.</span><span class="n">encode_list</span><span class="p">([</span>
          <span class="p">{</span><span class="n">event_to_int</span><span class="p">(</span><span class="n">player_action</span><span class="p">),</span> <span class="ss">:half_word</span><span class="p">},</span>
          <span class="p">{</span><span class="n">player_id</span><span class="p">,</span> <span class="ss">:binary</span><span class="p">}</span>
        <span class="p">])</span>
    <span class="k">end</span>
  <span class="k">end</span>

  <span class="nv">@impl</span> <span class="no">true</span>
  <span class="k">def</span> <span class="n">decode_event</span><span class="p">(</span><span class="n">binary</span><span class="p">)</span> <span class="k">do</span>
    <span class="o">&lt;&lt;</span><span class="n">action</span><span class="p">::</span><span class="n">big</span><span class="o">-</span><span class="n">integer</span><span class="o">-</span><span class="n">size</span><span class="p">(</span><span class="mi">8</span><span class="p">),</span> <span class="n">rest</span><span class="p">::</span><span class="n">binary</span><span class="o">&gt;&gt;</span> <span class="o">=</span> <span class="n">binary</span>

    <span class="n">action</span> <span class="o">=</span> <span class="n">int_to_event</span><span class="p">(</span><span class="n">action</span><span class="p">)</span>

    <span class="k">case</span> <span class="n">action</span> <span class="k">do</span>
      <span class="ss">:move</span> <span class="o">-&gt;</span>
        <span class="o">&lt;&lt;</span><span class="n">direction</span><span class="p">::</span><span class="n">big</span><span class="o">-</span><span class="n">integer</span><span class="o">-</span><span class="n">size</span><span class="p">(</span><span class="mi">8</span><span class="p">),</span> <span class="n">player_id</span><span class="p">::</span><span class="n">binary</span><span class="o">-</span><span class="n">size</span><span class="p">(</span><span class="nv">@player_id_size</span><span class="p">)</span><span class="o">&gt;&gt;</span> <span class="o">=</span>
          <span class="n">rest</span>

        <span class="p">{</span><span class="ss">:ok</span><span class="p">,</span> <span class="p">{</span><span class="n">action</span><span class="p">,</span> <span class="p">{</span><span class="n">player_id</span><span class="p">,</span> <span class="n">int_to_direction</span><span class="p">(</span><span class="n">direction</span><span class="p">)}}}</span>

      <span class="n">player_action</span> <span class="ow">when</span> <span class="n">player_action</span> <span class="ow">in</span> <span class="p">[</span><span class="ss">:player_connection</span><span class="p">,</span> <span class="ss">:player_disconnection</span><span class="p">,</span> <span class="ss">:shoot</span><span class="p">]</span> <span class="o">-&gt;</span>
        <span class="o">&lt;&lt;</span><span class="n">player_id</span><span class="p">::</span><span class="n">binary</span><span class="o">-</span><span class="n">size</span><span class="p">(</span><span class="nv">@player_id_size</span><span class="p">)</span><span class="o">&gt;&gt;</span> <span class="o">=</span> <span class="n">rest</span>

        <span class="p">{</span><span class="ss">:ok</span><span class="p">,</span> <span class="p">{</span><span class="n">action</span><span class="p">,</span> <span class="n">player_id</span><span class="p">}}</span>
    <span class="k">end</span>
  <span class="k">rescue</span>
    <span class="n">_e</span> <span class="o">-&gt;</span> <span class="p">{</span><span class="ss">:error</span><span class="p">,</span> <span class="ss">:codec_error</span><span class="p">}</span>
  <span class="k">end</span>

  <span class="c1"># For now we can just handle the basic events from the</span>
  <span class="c1"># client, which are only move and shoot really</span>
  <span class="k">defp</span> <span class="n">event_to_int</span><span class="p">(</span><span class="n">action</span><span class="p">)</span> <span class="k">do</span>
    <span class="k">case</span> <span class="n">action</span> <span class="k">do</span>
      <span class="ss">:player_connection</span> <span class="o">-&gt;</span> <span class="mi">0</span>
      <span class="ss">:player_disconnection</span> <span class="o">-&gt;</span> <span class="mi">1</span>
      <span class="ss">:move</span> <span class="o">-&gt;</span> <span class="mi">2</span>
      <span class="ss">:shoot</span> <span class="o">-&gt;</span> <span class="mi">3</span>
    <span class="k">end</span>
  <span class="k">end</span>

  <span class="k">defp</span> <span class="n">int_to_event</span><span class="p">(</span><span class="n">intval</span><span class="p">)</span> <span class="k">do</span>
    <span class="k">case</span> <span class="n">intval</span> <span class="k">do</span>
      <span class="mi">0</span> <span class="o">-&gt;</span> <span class="ss">:player_connection</span>
      <span class="mi">1</span> <span class="o">-&gt;</span> <span class="ss">:player_disconnection</span>
      <span class="mi">2</span> <span class="o">-&gt;</span> <span class="ss">:move</span>
      <span class="mi">3</span> <span class="o">-&gt;</span> <span class="ss">:shoot</span>
    <span class="k">end</span>
  <span class="k">end</span>

  <span class="nv">@doc</span> <span class="s2">"Encode a `Game.State` struct into a raw binary payload"</span>
  <span class="nv">@impl</span> <span class="no">true</span>
  <span class="k">def</span> <span class="n">encode</span><span class="p">(</span><span class="n">game_state</span><span class="p">)</span> <span class="k">do</span>
    <span class="n">binary_ships</span> <span class="o">=</span>
      <span class="n">game_state</span><span class="o">.</span><span class="n">players</span>
      <span class="o">|&gt;</span> <span class="no">Enum</span><span class="o">.</span><span class="n">map</span><span class="p">(</span><span class="k">fn</span> <span class="p">{</span><span class="n">player_id</span><span class="p">,</span> <span class="n">spaceship</span><span class="p">}</span> <span class="o">-&gt;</span> <span class="n">encode_spaceship</span><span class="p">(</span><span class="n">player_id</span><span class="p">,</span> <span class="n">spaceship</span><span class="p">)</span> <span class="k">end</span><span class="p">)</span>
      <span class="o">|&gt;</span> <span class="no">IO</span><span class="o">.</span><span class="n">iodata_to_binary</span><span class="p">()</span>

    <span class="n">binary_power_ups</span> <span class="o">=</span>
      <span class="n">game_state</span><span class="o">.</span><span class="n">power_ups</span> <span class="o">|&gt;</span> <span class="no">Enum</span><span class="o">.</span><span class="n">map</span><span class="p">(</span><span class="o">&amp;</span><span class="n">encode_power_up</span><span class="o">/</span><span class="mi">1</span><span class="p">)</span> <span class="o">|&gt;</span> <span class="no">IO</span><span class="o">.</span><span class="n">iodata_to_binary</span><span class="p">()</span>

    <span class="n">binary_ships_byte_size</span> <span class="o">=</span> <span class="n">byte_size</span><span class="p">(</span><span class="n">binary_ships</span><span class="p">)</span>
    <span class="n">binary_power_ups_byte_size</span> <span class="o">=</span> <span class="n">byte_size</span><span class="p">(</span><span class="n">binary_power_ups</span><span class="p">)</span>

    <span class="n">total_length</span> <span class="o">=</span>
      <span class="n">binary_ships_byte_size</span> <span class="o">+</span>
        <span class="n">binary_power_ups_byte_size</span> <span class="o">+</span>
        <span class="no">Helpers</span><span class="o">.</span><span class="n">double_word_size</span><span class="p">()</span> <span class="o">+</span>
        <span class="no">Helpers</span><span class="o">.</span><span class="n">half_word_size</span><span class="p">()</span> <span class="o">+</span>
        <span class="no">Helpers</span><span class="o">.</span><span class="n">word_size</span><span class="p">()</span>

    <span class="no">Helpers</span><span class="o">.</span><span class="n">encode_list</span><span class="p">([</span>
      <span class="p">{</span><span class="n">total_length</span><span class="p">,</span> <span class="ss">:double_word</span><span class="p">},</span>
      <span class="p">{</span><span class="n">status_to_int</span><span class="p">(</span><span class="n">game_state</span><span class="o">.</span><span class="n">status</span><span class="p">),</span> <span class="ss">:half_word</span><span class="p">},</span>
      <span class="p">{</span><span class="n">binary_power_ups_byte_size</span><span class="p">,</span> <span class="ss">:word</span><span class="p">},</span>
      <span class="p">{</span><span class="n">binary_power_ups</span><span class="p">,</span> <span class="ss">:binary</span><span class="p">},</span>
      <span class="p">{</span><span class="n">binary_ships</span><span class="p">,</span> <span class="ss">:binary</span><span class="p">}</span>
    <span class="p">])</span>
  <span class="k">end</span>

  <span class="k">defp</span> <span class="n">encode_spaceship</span><span class="p">(</span><span class="n">player_id</span><span class="p">,</span> <span class="n">spaceship</span><span class="p">)</span> <span class="k">do</span>
    <span class="n">direction</span> <span class="o">=</span> <span class="n">direction_to_int</span><span class="p">(</span><span class="n">spaceship</span><span class="o">.</span><span class="n">direction</span><span class="p">)</span>

    <span class="n">binary_bullets</span> <span class="o">=</span>
      <span class="n">spaceship</span><span class="o">.</span><span class="n">bullets</span>
      <span class="o">|&gt;</span> <span class="no">Enum</span><span class="o">.</span><span class="n">map</span><span class="p">(</span><span class="o">&amp;</span><span class="n">encode_bullet</span><span class="o">/</span><span class="mi">1</span><span class="p">)</span>
      <span class="o">|&gt;</span> <span class="no">IO</span><span class="o">.</span><span class="n">iodata_to_binary</span><span class="p">()</span>

    <span class="p">%{</span><span class="ss">x:</span> <span class="n">x</span><span class="p">,</span> <span class="ss">y:</span> <span class="n">y</span><span class="p">}</span> <span class="o">=</span> <span class="n">spaceship</span><span class="o">.</span><span class="n">position</span>

    <span class="no">Helpers</span><span class="o">.</span><span class="n">encode_list</span><span class="p">([</span>
      <span class="p">{</span><span class="n">x</span><span class="p">,</span> <span class="ss">:double_word</span><span class="p">},</span>
      <span class="p">{</span><span class="n">y</span><span class="p">,</span> <span class="ss">:double_word</span><span class="p">},</span>
      <span class="p">{</span><span class="n">spaceship</span><span class="o">.</span><span class="n">hp</span><span class="p">,</span> <span class="ss">:half_word</span><span class="p">},</span>
      <span class="p">{</span><span class="k">if</span><span class="p">(</span><span class="n">spaceship</span><span class="o">.</span><span class="n">alive?</span><span class="p">,</span> <span class="k">do</span><span class="p">:</span> <span class="mi">1</span><span class="p">,</span> <span class="k">else</span><span class="p">:</span> <span class="mi">0</span><span class="p">),</span> <span class="ss">:half_word</span><span class="p">},</span>
      <span class="p">{</span><span class="n">direction</span><span class="p">,</span> <span class="ss">:half_word</span><span class="p">},</span>
      <span class="p">{</span><span class="n">player_id</span><span class="p">,</span> <span class="ss">:binary</span><span class="p">},</span>
      <span class="p">{</span><span class="n">binary_bullets</span><span class="p">,</span> <span class="ss">:binary</span><span class="p">}</span>
    <span class="p">])</span>
  <span class="k">end</span>

  <span class="k">defp</span> <span class="n">encode_power_up</span><span class="p">(</span><span class="n">power_up</span><span class="p">)</span> <span class="k">do</span>
    <span class="n">kind</span> <span class="o">=</span> <span class="n">power_up_to_int</span><span class="p">(</span><span class="n">power_up</span><span class="o">.</span><span class="n">kind</span><span class="p">)</span>
    <span class="p">%{</span><span class="ss">position:</span> <span class="p">%{</span><span class="ss">x:</span> <span class="n">x</span><span class="p">,</span> <span class="ss">y:</span> <span class="n">y</span><span class="p">}}</span> <span class="o">=</span> <span class="n">power_up</span>

    <span class="no">Helpers</span><span class="o">.</span><span class="n">encode_list</span><span class="p">([</span>
      <span class="p">{</span><span class="n">x</span><span class="p">,</span> <span class="ss">:double_word</span><span class="p">},</span>
      <span class="p">{</span><span class="n">y</span><span class="p">,</span> <span class="ss">:double_word</span><span class="p">},</span>
      <span class="p">{</span><span class="n">kind</span><span class="p">,</span> <span class="ss">:half_word</span><span class="p">}</span>
    <span class="p">])</span>
  <span class="k">end</span>

  <span class="k">defp</span> <span class="n">encode_bullet</span><span class="p">(</span><span class="n">bullet</span><span class="p">)</span> <span class="k">do</span>
    <span class="n">direction</span> <span class="o">=</span> <span class="n">direction_to_int</span><span class="p">(</span><span class="n">bullet</span><span class="o">.</span><span class="n">direction</span><span class="p">)</span>

    <span class="p">%{</span><span class="ss">x:</span> <span class="n">x</span><span class="p">,</span> <span class="ss">y:</span> <span class="n">y</span><span class="p">}</span> <span class="o">=</span> <span class="n">bullet</span><span class="o">.</span><span class="n">position</span>

    <span class="no">Helpers</span><span class="o">.</span><span class="n">encode_list</span><span class="p">([</span>
      <span class="p">{</span><span class="n">x</span><span class="p">,</span> <span class="ss">:double_word</span><span class="p">},</span>
      <span class="p">{</span><span class="n">y</span><span class="p">,</span> <span class="ss">:double_word</span><span class="p">},</span>
      <span class="p">{</span><span class="k">if</span><span class="p">(</span><span class="n">bullet</span><span class="o">.</span><span class="n">active?</span><span class="p">,</span> <span class="k">do</span><span class="p">:</span> <span class="mi">1</span><span class="p">,</span> <span class="k">else</span><span class="p">:</span> <span class="mi">0</span><span class="p">),</span> <span class="ss">:half_word</span><span class="p">},</span>
      <span class="p">{</span><span class="n">direction</span><span class="p">,</span> <span class="ss">:half_word</span><span class="p">}</span>
    <span class="p">])</span>
  <span class="k">end</span>

  <span class="nv">@doc</span> <span class="s2">"Decode a raw binary payload into a `Game.State` struct"</span>
  <span class="nv">@impl</span> <span class="no">true</span>
  <span class="k">def</span> <span class="n">decode</span><span class="p">(</span><span class="n">binary</span><span class="p">)</span> <span class="k">do</span>
    <span class="o">&lt;&lt;</span><span class="n">_total_length</span><span class="p">::</span><span class="n">big</span><span class="o">-</span><span class="n">integer</span><span class="o">-</span><span class="n">size</span><span class="p">(</span><span class="mi">32</span><span class="p">),</span> <span class="n">status</span><span class="p">::</span><span class="n">big</span><span class="o">-</span><span class="n">integer</span><span class="o">-</span><span class="n">size</span><span class="p">(</span><span class="mi">8</span><span class="p">),</span>
      <span class="n">power_ups_len</span><span class="p">::</span><span class="n">big</span><span class="o">-</span><span class="n">integer</span><span class="o">-</span><span class="n">size</span><span class="p">(</span><span class="mi">16</span><span class="p">),</span>
      <span class="n">rest</span><span class="p">::</span><span class="n">binary</span><span class="o">&gt;&gt;</span> <span class="o">=</span>
      <span class="n">binary</span>

    <span class="o">&lt;&lt;</span><span class="n">power_ups_bin</span><span class="p">::</span><span class="n">binary</span><span class="o">-</span><span class="n">size</span><span class="p">(</span><span class="n">power_ups_len</span><span class="p">),</span> <span class="n">player_records</span><span class="p">::</span><span class="n">binary</span><span class="o">&gt;&gt;</span> <span class="o">=</span> <span class="n">rest</span>

    <span class="n">power_ups</span> <span class="o">=</span>
      <span class="n">power_ups_bin</span>
      <span class="o">|&gt;</span> <span class="n">chunk_bits</span><span class="p">(</span><span class="nv">@power_up_size</span><span class="p">)</span>
      <span class="o">|&gt;</span> <span class="no">Enum</span><span class="o">.</span><span class="n">map</span><span class="p">(</span><span class="o">&amp;</span><span class="n">decode_power_up!</span><span class="o">/</span><span class="mi">1</span><span class="p">)</span>

    <span class="n">players</span> <span class="o">=</span>
      <span class="n">player_records</span>
      <span class="o">|&gt;</span> <span class="n">chunk_bits</span><span class="p">(</span><span class="nv">@spaceship_size</span><span class="p">)</span>
      <span class="o">|&gt;</span> <span class="no">Enum</span><span class="o">.</span><span class="n">map</span><span class="p">(</span><span class="o">&amp;</span><span class="n">decode_spaceship!</span><span class="o">/</span><span class="mi">1</span><span class="p">)</span>
      <span class="o">|&gt;</span> <span class="no">Map</span><span class="o">.</span><span class="n">new</span><span class="p">()</span>

    <span class="p">{</span><span class="ss">:ok</span><span class="p">,</span>
     <span class="p">%</span><span class="no">State</span><span class="p">{</span>
       <span class="ss">players:</span> <span class="n">players</span><span class="p">,</span>
       <span class="ss">power_ups:</span> <span class="n">power_ups</span><span class="p">,</span>
       <span class="ss">status:</span> <span class="n">int_to_status</span><span class="p">(</span><span class="n">status</span><span class="p">)</span>
     <span class="p">}}</span>
  <span class="k">rescue</span>
    <span class="c1"># TODO add custom errors</span>
    <span class="n">_e</span> <span class="o">-&gt;</span> <span class="p">{</span><span class="ss">:error</span><span class="p">,</span> <span class="ss">:codec_error</span><span class="p">}</span>
  <span class="k">end</span>

  <span class="k">defp</span> <span class="n">decode_power_up!</span><span class="p">(</span>
         <span class="o">&lt;&lt;</span><span class="n">power_up_x</span><span class="p">::</span><span class="n">big</span><span class="o">-</span><span class="n">integer</span><span class="o">-</span><span class="n">size</span><span class="p">(</span><span class="mi">32</span><span class="p">),</span> <span class="n">power_up_y</span><span class="p">::</span><span class="n">big</span><span class="o">-</span><span class="n">integer</span><span class="o">-</span><span class="n">size</span><span class="p">(</span><span class="mi">32</span><span class="p">),</span>
           <span class="n">power_up_kind</span><span class="p">::</span><span class="n">big</span><span class="o">-</span><span class="n">integer</span><span class="o">-</span><span class="n">size</span><span class="p">(</span><span class="mi">8</span><span class="p">)</span><span class="o">&gt;&gt;</span>
       <span class="p">)</span> <span class="k">do</span>
    <span class="p">%{</span><span class="ss">position:</span> <span class="p">%</span><span class="no">Vec2</span><span class="p">{</span><span class="ss">x:</span> <span class="n">power_up_x</span><span class="p">,</span> <span class="ss">y:</span> <span class="n">power_up_y</span><span class="p">},</span> <span class="ss">kind:</span> <span class="n">int_to_power_up</span><span class="p">(</span><span class="n">power_up_kind</span><span class="p">)}</span>
  <span class="k">end</span>

  <span class="k">defp</span> <span class="n">decode_spaceship!</span><span class="p">(</span>
         <span class="o">&lt;&lt;</span><span class="n">x</span><span class="p">::</span><span class="n">big</span><span class="o">-</span><span class="n">integer</span><span class="o">-</span><span class="n">size</span><span class="p">(</span><span class="mi">32</span><span class="p">),</span> <span class="n">y</span><span class="p">::</span><span class="n">big</span><span class="o">-</span><span class="n">integer</span><span class="o">-</span><span class="n">size</span><span class="p">(</span><span class="mi">32</span><span class="p">),</span> <span class="n">hp</span><span class="p">::</span><span class="n">big</span><span class="o">-</span><span class="n">integer</span><span class="o">-</span><span class="n">size</span><span class="p">(</span><span class="mi">8</span><span class="p">),</span>
           <span class="n">alive</span><span class="p">::</span><span class="n">big</span><span class="o">-</span><span class="n">integer</span><span class="o">-</span><span class="n">size</span><span class="p">(</span><span class="mi">8</span><span class="p">),</span> <span class="n">direction</span><span class="p">::</span><span class="n">big</span><span class="o">-</span><span class="n">integer</span><span class="o">-</span><span class="n">size</span><span class="p">(</span><span class="mi">8</span><span class="p">),</span>
           <span class="n">player_id</span><span class="p">::</span><span class="n">binary</span><span class="o">-</span><span class="n">size</span><span class="p">(</span><span class="nv">@player_id_size</span><span class="p">),</span> <span class="n">bullets</span><span class="p">::</span><span class="n">binary</span><span class="o">&gt;&gt;</span>
       <span class="p">)</span> <span class="k">do</span>
    <span class="p">{</span><span class="n">player_id</span><span class="p">,</span>
     <span class="p">%{</span>
       <span class="ss">position:</span> <span class="p">%</span><span class="no">Vec2</span><span class="p">{</span><span class="ss">x:</span> <span class="n">x</span><span class="p">,</span> <span class="ss">y:</span> <span class="n">y</span><span class="p">},</span>
       <span class="ss">hp:</span> <span class="n">hp</span><span class="p">,</span>
       <span class="ss">direction:</span> <span class="n">int_to_direction</span><span class="p">(</span><span class="n">direction</span><span class="p">),</span>
       <span class="ss">alive?:</span> <span class="n">alive</span> <span class="o">==</span> <span class="mi">1</span><span class="p">,</span>
       <span class="ss">bullets:</span>
         <span class="n">bullets</span>
         <span class="o">|&gt;</span> <span class="n">chunk_bits</span><span class="p">(</span><span class="nv">@bullet_size</span><span class="p">)</span>
         <span class="o">|&gt;</span> <span class="no">Enum</span><span class="o">.</span><span class="n">map</span><span class="p">(</span><span class="o">&amp;</span><span class="n">decode_bullet!</span><span class="o">/</span><span class="mi">1</span><span class="p">)</span>
     <span class="p">}}</span>
  <span class="k">end</span>

  <span class="k">defp</span> <span class="n">decode_bullet!</span><span class="p">(</span>
         <span class="o">&lt;&lt;</span><span class="n">x</span><span class="p">::</span><span class="n">big</span><span class="o">-</span><span class="n">integer</span><span class="o">-</span><span class="n">size</span><span class="p">(</span><span class="mi">32</span><span class="p">),</span> <span class="n">y</span><span class="p">::</span><span class="n">big</span><span class="o">-</span><span class="n">integer</span><span class="o">-</span><span class="n">size</span><span class="p">(</span><span class="mi">32</span><span class="p">),</span> <span class="n">active</span><span class="p">::</span><span class="n">big</span><span class="o">-</span><span class="n">integer</span><span class="o">-</span><span class="n">size</span><span class="p">(</span><span class="mi">8</span><span class="p">),</span>
           <span class="n">direction</span><span class="p">::</span><span class="n">big</span><span class="o">-</span><span class="n">integer</span><span class="o">-</span><span class="n">size</span><span class="p">(</span><span class="mi">8</span><span class="p">)</span><span class="o">&gt;&gt;</span>
       <span class="p">)</span> <span class="k">do</span>
    <span class="p">%{</span>
      <span class="ss">position:</span> <span class="p">%{</span><span class="ss">x:</span> <span class="n">x</span><span class="p">,</span> <span class="ss">y:</span> <span class="n">y</span><span class="p">},</span>
      <span class="ss">active?:</span> <span class="k">if</span><span class="p">(</span><span class="n">active</span> <span class="o">==</span> <span class="mi">0</span><span class="p">,</span> <span class="k">do</span><span class="p">:</span> <span class="no">false</span><span class="p">,</span> <span class="k">else</span><span class="p">:</span> <span class="no">true</span><span class="p">),</span>
      <span class="ss">direction:</span> <span class="n">int_to_direction</span><span class="p">(</span><span class="n">direction</span><span class="p">)</span>
    <span class="p">}</span>
  <span class="k">end</span>

  <span class="k">defp</span> <span class="n">chunk_bits</span><span class="p">(</span><span class="n">binary</span><span class="p">,</span> <span class="n">n</span><span class="p">)</span> <span class="k">do</span>
    <span class="n">for</span> <span class="o">&lt;&lt;</span><span class="n">chunk</span><span class="p">::</span><span class="n">binary</span><span class="o">-</span><span class="n">size</span><span class="p">(</span><span class="n">n</span><span class="p">)</span> <span class="o">&lt;-</span> <span class="n">binary</span><span class="o">&gt;&gt;</span><span class="p">,</span> <span class="k">do</span><span class="p">:</span> <span class="o">&lt;&lt;</span><span class="n">chunk</span><span class="p">::</span><span class="n">binary</span><span class="o">-</span><span class="n">size</span><span class="p">(</span><span class="n">n</span><span class="p">)</span><span class="o">&gt;&gt;</span>
  <span class="k">end</span>

  <span class="k">defp</span> <span class="n">power_up_to_int</span><span class="p">(</span><span class="no">nil</span><span class="p">),</span> <span class="k">do</span><span class="p">:</span> <span class="mi">0</span>
  <span class="k">defp</span> <span class="n">power_up_to_int</span><span class="p">(</span><span class="ss">:hp_plus_one</span><span class="p">),</span> <span class="k">do</span><span class="p">:</span> <span class="mi">1</span>
  <span class="k">defp</span> <span class="n">power_up_to_int</span><span class="p">(</span><span class="ss">:hp_plus_three</span><span class="p">),</span> <span class="k">do</span><span class="p">:</span> <span class="mi">2</span>
  <span class="k">defp</span> <span class="n">power_up_to_int</span><span class="p">(</span><span class="ss">:ammo_plus_one</span><span class="p">),</span> <span class="k">do</span><span class="p">:</span> <span class="mi">3</span>

  <span class="k">defp</span> <span class="n">int_to_power_up</span><span class="p">(</span><span class="mi">0</span><span class="p">),</span> <span class="k">do</span><span class="p">:</span> <span class="no">nil</span>
  <span class="k">defp</span> <span class="n">int_to_power_up</span><span class="p">(</span><span class="mi">1</span><span class="p">),</span> <span class="k">do</span><span class="p">:</span> <span class="ss">:hp_plus_one</span>
  <span class="k">defp</span> <span class="n">int_to_power_up</span><span class="p">(</span><span class="mi">2</span><span class="p">),</span> <span class="k">do</span><span class="p">:</span> <span class="ss">:hp_plus_three</span>
  <span class="k">defp</span> <span class="n">int_to_power_up</span><span class="p">(</span><span class="mi">3</span><span class="p">),</span> <span class="k">do</span><span class="p">:</span> <span class="ss">:ammo_plus_one</span>

  <span class="k">defp</span> <span class="n">status_to_int</span><span class="p">(</span><span class="no">nil</span><span class="p">),</span> <span class="k">do</span><span class="p">:</span> <span class="mi">0</span>
  <span class="k">defp</span> <span class="n">status_to_int</span><span class="p">(</span><span class="ss">:in_progress</span><span class="p">),</span> <span class="k">do</span><span class="p">:</span> <span class="mi">1</span>
  <span class="k">defp</span> <span class="n">status_to_int</span><span class="p">(</span><span class="ss">:closed</span><span class="p">),</span> <span class="k">do</span><span class="p">:</span> <span class="mi">2</span>

  <span class="k">defp</span> <span class="n">int_to_status</span><span class="p">(</span><span class="mi">0</span><span class="p">),</span> <span class="k">do</span><span class="p">:</span> <span class="no">nil</span>
  <span class="k">defp</span> <span class="n">int_to_status</span><span class="p">(</span><span class="mi">1</span><span class="p">),</span> <span class="k">do</span><span class="p">:</span> <span class="ss">:in_progress</span>
  <span class="k">defp</span> <span class="n">int_to_status</span><span class="p">(</span><span class="mi">2</span><span class="p">),</span> <span class="k">do</span><span class="p">:</span> <span class="ss">:closed</span>

  <span class="k">def</span> <span class="n">direction_to_int</span><span class="p">(</span><span class="ss">:idle</span><span class="p">),</span> <span class="k">do</span><span class="p">:</span> <span class="mi">0</span>
  <span class="k">def</span> <span class="n">direction_to_int</span><span class="p">(</span><span class="ss">:up</span><span class="p">),</span> <span class="k">do</span><span class="p">:</span> <span class="mi">1</span>
  <span class="k">def</span> <span class="n">direction_to_int</span><span class="p">(</span><span class="ss">:down</span><span class="p">),</span> <span class="k">do</span><span class="p">:</span> <span class="mi">2</span>
  <span class="k">def</span> <span class="n">direction_to_int</span><span class="p">(</span><span class="ss">:left</span><span class="p">),</span> <span class="k">do</span><span class="p">:</span> <span class="mi">3</span>
  <span class="k">def</span> <span class="n">direction_to_int</span><span class="p">(</span><span class="ss">:right</span><span class="p">),</span> <span class="k">do</span><span class="p">:</span> <span class="mi">4</span>

  <span class="k">def</span> <span class="n">int_to_direction</span><span class="p">(</span><span class="mi">0</span><span class="p">),</span> <span class="k">do</span><span class="p">:</span> <span class="ss">:idle</span>
  <span class="k">def</span> <span class="n">int_to_direction</span><span class="p">(</span><span class="mi">1</span><span class="p">),</span> <span class="k">do</span><span class="p">:</span> <span class="ss">:up</span>
  <span class="k">def</span> <span class="n">int_to_direction</span><span class="p">(</span><span class="mi">2</span><span class="p">),</span> <span class="k">do</span><span class="p">:</span> <span class="ss">:down</span>
  <span class="k">def</span> <span class="n">int_to_direction</span><span class="p">(</span><span class="mi">3</span><span class="p">),</span> <span class="k">do</span><span class="p">:</span> <span class="ss">:left</span>
  <span class="k">def</span> <span class="n">int_to_direction</span><span class="p">(</span><span class="mi">4</span><span class="p">),</span> <span class="k">do</span><span class="p">:</span> <span class="ss">:right</span>
<span class="k">end</span>
</code></pre></div></div>

<h4 id="references">References</h4>

<ul>
  <li><a href="https://en.wikipedia.org/wiki/Nibble">nibble</a></li>
  <li><a href="https://en.wikipedia.org/wiki/Endianness">endianness</a></li>
</ul>]]></content><author><name></name></author><category term="elixir" /><category term="networking" /><category term="zig" /><category term="game-development" /><category term="low-level" /><summary type="html"><![CDATA[Old school multiplayer arcade game development journey]]></summary></entry><entry><title type="html">Dogfight! - Elixir server</title><link href="/posts/dogfight-server-elixir/" rel="alternate" type="text/html" title="Dogfight! - Elixir server" /><published>2025-01-01T00:00:00+00:00</published><updated>2025-01-01T00:00:00+00:00</updated><id>/posts/dogfight-server-elixir</id><content type="html" xml:base="/posts/dogfight-server-elixir/"><![CDATA[<p>Starting with the server side of the game (code can be found in the <a href="https://github.com/codepr/dogfight/tree/main/server">GitHub
repository</a>), Elixir is
pretty neat and allows for various ways to implement a distributed TCP server.
Going for the most simple and straight forward approach, leaving refinements
and polish for later. After more than 3 years of using Elixir full time, I
believe it’s really one of the cleanest backend languages around, extremely
productive and convenient (pattern matching in Elixir slaps), there are two
points that make it stand-out and interesting to try for this little project:</p>

<ol>
  <li>
    <p><strong>Concurrency and Scalability</strong>: Built on the Erlang VM (BEAM), which is
known for its ability to handle a large number of concurrent processes with
low latency, a real outstanding piece of engineering created by the major
telecom experts on earth. This makes Elixir highly suitable for applications
that require high concurrency and scalability, such as real-time systems and
web applications.</p>
  </li>
  <li>
    <p><strong>Fault Tolerance</strong>: Elixir inherits Erlang’s “let it crash” philosophy,
which encourages developers to write systems that can recover from failures
automatically, this results in highly resilient applications that can continue
to operate even when parts of the system fail. Each process operates in
isolation and supervision trees allow for automatically recovery; potentially,
it’s possible to ship code really fast without the need of being too defensive
and tune the system based on the evolution of the use-cases.</p>
  </li>
</ol>

<p>The main idea for the application is to write a highly concurrent and distributed
TCP based system to handle players, for the first iteration:</p>

<ul>
  <li>A TCP entry point that acts as an acceptor process, its only purpose will be
to accept new connecting clients and spawn process inside the cluster to handle
the lifetime of the connection (i.e. the game)</li>
  <li><code class="language-plaintext highlighter-rouge">GenServer</code> is the main abstraction (kinda low level but easy to use for a first
run) that will be used to represent
    <ul>
      <li>A game server, or better, an event handler, this will be the entity representing
the main source of truth for connected clients, it’s basically an instantiated
game. For the first iteration we will have a single running game server;
subsequently, it will extended to be spawned on-demand as part of a hosting game
logic from players requesting it</li>
      <li>Each connecting player, once the acceptor complete the handshake, it
will demand the managing of the client to its own process</li>
    </ul>
  </li>
  <li>The main structures exchanged between server and clients will be
    <ul>
      <li>A game state, will contain all the players, initially capped to a maximum
to keep things simple, and each player will have a fixed amount of projectiles
to shoot</li>
      <li>Events, which may be directional in nature, such as
        <ul>
          <li><code class="language-plaintext highlighter-rouge">MOVE UP</code></li>
          <li><code class="language-plaintext highlighter-rouge">MOVE DOWN</code></li>
          <li><code class="language-plaintext highlighter-rouge">MOVE LEFT</code></li>
          <li><code class="language-plaintext highlighter-rouge">MOVE RIGHT</code></li>
          <li><code class="language-plaintext highlighter-rouge">SHOOT</code> (this will follow the direction of the projectile, which in turns
 will coincide with the player direction)</li>
          <li><code class="language-plaintext highlighter-rouge">SPAWN POWER UP</code>
And so on</li>
        </ul>
      </li>
    </ul>
  </li>
  <li>Each player process and game process will be spawned inside a cluster on multiple
nodes, with location transparency and message passing this will be a breeze</li>
</ul>

<h2 id="a-very-basic-tcp-server-acceptor">A very basic TCP server acceptor</h2>

<p>We’re gonna use <code class="language-plaintext highlighter-rouge">:gen_tcp</code> from the builtin <code class="language-plaintext highlighter-rouge">Erlang</code> library, starting the
listen loop in a dedicated <code class="language-plaintext highlighter-rouge">Task</code> is enough for the time being, management of
newly connected clients will be demanded to their own proper <code class="language-plaintext highlighter-rouge">GenServer</code>.</p>

<p>Let’s start with something really bare-bone, basically a slight variation of
the <a href="https://hexdocs.pm/elixir/task-and-gen-tcp.html">official docs</a> example is
all we need to get started</p>

<div class="language-elixir highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">defmodule</span> <span class="no">Dogfight</span><span class="o">.</span><span class="no">Server</span> <span class="k">do</span>
  <span class="nv">@moduledoc</span> <span class="sd">"""

  Main entry point of the server, accepts connecting clients and defer their
  ownership to the game server. Each connected client is handled by a
  `GenServer`, the `Dogfight.Player`. The `Dogfight.Game.EventHandler` governs
  the main logic of an instantiated game and broadcasts updates to each
  registered player.

  """</span>
  <span class="kn">require</span> <span class="no">Logger</span>

  <span class="n">alias</span> <span class="no">Dogfight</span><span class="o">.</span><span class="no">Game</span><span class="o">.</span><span class="no">Event</span><span class="p">,</span> <span class="ss">as:</span> <span class="no">GameEvent</span>
  <span class="n">alias</span> <span class="no">Dogfight</span><span class="o">.</span><span class="no">ClusterServiceSupervisor</span>

  <span class="k">def</span> <span class="n">listen</span><span class="p">(</span><span class="n">port</span><span class="p">)</span> <span class="k">do</span>
    <span class="c1"># The options below mean:</span>
    <span class="c1">#</span>
    <span class="c1"># 1. `:binary` - receives data as binaries (instead of lists)</span>
    <span class="c1"># 2. `packet: :raw` - receives data as it comes (stream of binary over the wire)</span>
    <span class="c1"># 3. `active: true` - non blocking on `:gen_tcp.recv/2`, doesn't wait for data to be available</span>
    <span class="c1"># 4. `reuseaddr: true` - allows us to reuse the address if the listener crashes</span>
    <span class="c1">#</span>
    <span class="p">{</span><span class="ss">:ok</span><span class="p">,</span> <span class="n">socket</span><span class="p">}</span> <span class="o">=</span>
      <span class="ss">:gen_tcp</span><span class="o">.</span><span class="n">listen</span><span class="p">(</span><span class="n">port</span><span class="p">,</span> <span class="p">[</span><span class="ss">:binary</span><span class="p">,</span> <span class="ss">packet:</span> <span class="ss">:raw</span><span class="p">,</span> <span class="ss">active:</span> <span class="no">true</span><span class="p">,</span> <span class="ss">reuseaddr:</span> <span class="no">true</span><span class="p">])</span>

    <span class="no">Logger</span><span class="o">.</span><span class="n">info</span><span class="p">(</span><span class="s2">"Accepting connections on port </span><span class="si">#{</span><span class="n">port</span><span class="si">}</span><span class="s2">"</span><span class="p">)</span>
    <span class="n">accept_loop</span><span class="p">(</span><span class="n">socket</span><span class="p">)</span>
  <span class="k">end</span>

  <span class="k">defp</span> <span class="n">accept_loop</span><span class="p">(</span><span class="n">socket</span><span class="p">)</span> <span class="k">do</span>
    <span class="n">with</span> <span class="p">{</span><span class="ss">:ok</span><span class="p">,</span> <span class="n">client_socket</span><span class="p">}</span> <span class="o">&lt;-</span> <span class="ss">:gen_tcp</span><span class="o">.</span><span class="n">accept</span><span class="p">(</span><span class="n">socket</span><span class="p">),</span>
         <span class="n">player_id</span> <span class="o">&lt;-</span> <span class="no">UUID</span><span class="o">.</span><span class="n">uuid4</span><span class="p">(),</span>
         <span class="n">player_spec</span> <span class="o">&lt;-</span> <span class="n">player_spec</span><span class="p">(</span><span class="n">client_socket</span><span class="p">,</span> <span class="n">player_id</span><span class="p">),</span>
         <span class="p">{</span><span class="ss">:ok</span><span class="p">,</span> <span class="n">pid</span><span class="p">}</span> <span class="o">&lt;-</span> <span class="no">Horde</span><span class="o">.</span><span class="no">DynamicSupervisor</span><span class="o">.</span><span class="n">start_child</span><span class="p">(</span><span class="no">ClusterServiceSupervisor</span><span class="p">,</span> <span class="n">player_spec</span><span class="p">),</span>
         <span class="ss">:ok</span> <span class="o">&lt;-</span> <span class="ss">:gen_tcp</span><span class="o">.</span><span class="n">send</span><span class="p">(</span><span class="n">client_socket</span><span class="p">,</span> <span class="n">player_id</span><span class="p">)</span> <span class="k">do</span>
      <span class="no">Dogfight</span><span class="o">.</span><span class="no">Game</span><span class="o">.</span><span class="no">EventHandler</span><span class="o">.</span><span class="n">apply_event</span><span class="p">(</span><span class="n">pid</span><span class="p">,</span> <span class="no">GameEvent</span><span class="o">.</span><span class="n">player_connection</span><span class="p">(</span><span class="n">player_id</span><span class="p">))</span>
      <span class="ss">:gen_tcp</span><span class="o">.</span><span class="n">controlling_process</span><span class="p">(</span><span class="n">client_socket</span><span class="p">,</span> <span class="n">pid</span><span class="p">)</span>
    <span class="k">else</span>
      <span class="n">error</span> <span class="o">-&gt;</span> <span class="no">Logger</span><span class="o">.</span><span class="n">error</span><span class="p">(</span><span class="s2">"Failed to accept connection, reason: </span><span class="si">#{</span><span class="n">inspect</span><span class="p">(</span><span class="n">error</span><span class="p">)</span><span class="si">}</span><span class="s2">"</span><span class="p">)</span>
    <span class="k">end</span>

    <span class="n">accept_loop</span><span class="p">(</span><span class="n">socket</span><span class="p">)</span>
  <span class="k">end</span>

  <span class="k">defp</span> <span class="n">player_spec</span><span class="p">(</span><span class="n">socket</span><span class="p">,</span> <span class="n">player_id</span><span class="p">)</span> <span class="k">do</span>
    <span class="p">%{</span>
      <span class="ss">id:</span> <span class="no">Dogfight</span><span class="o">.</span><span class="no">Player</span><span class="p">,</span>
      <span class="ss">start:</span> <span class="p">{</span><span class="no">Dogfight</span><span class="o">.</span><span class="no">Player</span><span class="p">,</span> <span class="ss">:start_link</span><span class="p">,</span> <span class="p">[</span><span class="n">player_id</span><span class="p">,</span> <span class="n">socket</span><span class="p">]},</span>
      <span class="ss">type:</span> <span class="ss">:worker</span><span class="p">,</span>
      <span class="ss">restart:</span> <span class="ss">:transient</span>
    <span class="p">}</span>
  <span class="k">end</span>
<span class="k">end</span>
</code></pre></div></div>

<p>The main interesting points are</p>
<ul>
  <li><code class="language-plaintext highlighter-rouge">accept_loop/1</code> is a recursive call, once a new client connect it dispatches it to a process and
call itself again</li>
  <li>if the accept call succeed, the first thing we do is to generate an UUID. This part is currently
very brittle, no protocol defined to establish an handshake yet, but later on what we expect to do
is a sort of authentication, with a token session state to handle reconnections and security concerns,
not a priority at the moment.</li>
  <li>After the ID generation, we’re gonna use <code class="language-plaintext highlighter-rouge">Horde</code> <a href="https://hexdocs.pm/elixir/DynamicSupervisor.html">dynamic supervisor</a>
to spawn processes that are automatically cluster-aware, the node where they will be spawned is completely
abstracted away and doesn’t matter. (For a simpler solution we could even manually start the <code class="language-plaintext highlighter-rouge">GenSever</code> with a
call to <code class="language-plaintext highlighter-rouge">start_link/1</code>, it’s really that simple).</li>
  <li>The <code class="language-plaintext highlighter-rouge">Dogfight.Game.EventHandler.register_player/2</code> call basically add the <code class="language-plaintext highlighter-rouge">pid</code> of the newly instantiated process
to the existing game server (as explained above, there will be a single global game server for the first version). This
assumes that the game server is already running here, started at application level already.</li>
  <li>Finally, we transfer the ownership of the connected socket to the newly spawned process through <code class="language-plaintext highlighter-rouge">:gen_tcp.controlling_process/2</code></li>
</ul>

<h2 id="the-player-process">The player process</h2>

<p>After the accept has been successfully performed, the <code class="language-plaintext highlighter-rouge">player_spec/2</code> call
provides all that’s required for the <code class="language-plaintext highlighter-rouge">Horde.DynamicSupervisor</code> to spawn a
<code class="language-plaintext highlighter-rouge">Dogfight.Player</code> process somewhere in the cluster. Conceptually it’s nothing
more than a connection state, and thus it’s pretty trivial. The registration of
each PID to the game server can be a little cumbersome later on, and it may be
a good opportunity to explore some pub-sub based solution such as the one shipped
by <a href="https://hexdocs.pm/phoenix_pubsub/Phoenix.PubSub.html">Phoenix</a>; for the time
being it’s ok to keep things simple.</p>

<div class="language-elixir highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">defmodule</span> <span class="no">Dogfight</span><span class="o">.</span><span class="no">Player</span> <span class="k">do</span>
  <span class="nv">@moduledoc</span> <span class="sd">"""
  This module represents a player in the Dogfight game. It handles the player's
  connection, actions, and communication with the game server.
  """</span>

  <span class="kn">require</span> <span class="no">Logger</span>
  <span class="kn">use</span> <span class="no">GenServer</span>

  <span class="n">alias</span> <span class="no">Dogfight</span><span class="o">.</span><span class="no">Game</span><span class="o">.</span><span class="no">Codecs</span><span class="o">.</span><span class="no">BinaryCodec</span>
  <span class="n">alias</span> <span class="no">Dogfight</span><span class="o">.</span><span class="no">Game</span><span class="o">.</span><span class="no">Event</span><span class="p">,</span> <span class="ss">as:</span> <span class="no">GameEvent</span>

  <span class="k">def</span> <span class="n">start_link</span><span class="p">(</span><span class="n">player_id</span><span class="p">,</span> <span class="n">socket</span><span class="p">)</span> <span class="k">do</span>
    <span class="no">GenServer</span><span class="o">.</span><span class="n">start_link</span><span class="p">(</span><span class="bp">__MODULE__</span><span class="p">,</span> <span class="p">{</span><span class="n">player_id</span><span class="p">,</span> <span class="n">socket</span><span class="p">})</span>
  <span class="k">end</span>

  <span class="k">def</span> <span class="n">init</span><span class="p">({</span><span class="n">player_id</span><span class="p">,</span> <span class="n">socket</span><span class="p">})</span> <span class="k">do</span>
    <span class="no">Logger</span><span class="o">.</span><span class="n">info</span><span class="p">(</span><span class="s2">"Player </span><span class="si">#{</span><span class="n">player_id</span><span class="si">}</span><span class="s2"> connected, registering to game server"</span><span class="p">)</span>
    <span class="p">{</span><span class="ss">:ok</span><span class="p">,</span> <span class="p">%{</span><span class="ss">player_id:</span> <span class="n">player_id</span><span class="p">,</span> <span class="ss">socket:</span> <span class="n">socket</span><span class="p">}}</span>
  <span class="k">end</span>

  <span class="k">def</span> <span class="n">handle_info</span><span class="p">({</span><span class="ss">:tcp</span><span class="p">,</span> <span class="n">_socket</span><span class="p">,</span> <span class="n">data</span><span class="p">},</span> <span class="n">state</span><span class="p">)</span> <span class="k">do</span>
    <span class="n">with</span> <span class="p">{</span><span class="ss">:ok</span><span class="p">,</span> <span class="n">event</span><span class="p">}</span> <span class="o">&lt;-</span> <span class="no">BinaryCodec</span><span class="o">.</span><span class="n">decode_event</span><span class="p">(</span><span class="n">data</span><span class="p">)</span> <span class="k">do</span>
      <span class="no">Dogfight</span><span class="o">.</span><span class="no">Game</span><span class="o">.</span><span class="no">EventHandler</span><span class="o">.</span><span class="n">apply_event</span><span class="p">(</span><span class="n">self</span><span class="p">(),</span> <span class="n">event</span><span class="p">)</span>
    <span class="k">else</span>
      <span class="p">{</span><span class="ss">:ok</span><span class="p">,</span> <span class="ss">:codec_error</span><span class="p">}</span> <span class="o">-&gt;</span> <span class="no">Logger</span><span class="o">.</span><span class="n">error</span><span class="p">(</span><span class="s2">"Decode failed, unknown event"</span><span class="p">)</span>
    <span class="k">end</span>

    <span class="p">{</span><span class="ss">:noreply</span><span class="p">,</span> <span class="n">state</span><span class="p">}</span>
  <span class="k">end</span>

  <span class="k">def</span> <span class="n">handle_info</span><span class="p">({</span><span class="ss">:tcp_closed</span><span class="p">,</span> <span class="n">_socket</span><span class="p">},</span> <span class="n">state</span><span class="p">)</span> <span class="k">do</span>
    <span class="no">Logger</span><span class="o">.</span><span class="n">info</span><span class="p">(</span><span class="s2">"Player </span><span class="si">#{</span><span class="n">state</span><span class="o">.</span><span class="n">player_id</span><span class="si">}</span><span class="s2"> disconnected"</span><span class="p">)</span>

    <span class="no">Dogfight</span><span class="o">.</span><span class="no">Game</span><span class="o">.</span><span class="no">EventHandler</span><span class="o">.</span><span class="n">apply_event</span><span class="p">(</span>
      <span class="n">self</span><span class="p">(),</span>
      <span class="no">GameEvent</span><span class="o">.</span><span class="n">player_disconnection</span><span class="p">(</span><span class="n">state</span><span class="o">.</span><span class="n">player_id</span><span class="p">)</span>
    <span class="p">)</span>

    <span class="p">{</span><span class="ss">:noreply</span><span class="p">,</span> <span class="n">state</span><span class="p">}</span>
  <span class="k">end</span>

  <span class="k">def</span> <span class="n">handle_info</span><span class="p">({</span><span class="ss">:tcp_error</span><span class="p">,</span> <span class="n">_socket</span><span class="p">,</span> <span class="n">reason</span><span class="p">},</span> <span class="n">state</span><span class="p">)</span> <span class="k">do</span>
    <span class="no">Logger</span><span class="o">.</span><span class="n">error</span><span class="p">(</span><span class="s2">"Player transmission error </span><span class="si">#{</span><span class="n">inspect</span><span class="p">(</span><span class="n">reason</span><span class="p">)</span><span class="si">}</span><span class="s2">"</span><span class="p">)</span>
    <span class="p">{</span><span class="ss">:noreply</span><span class="p">,</span> <span class="n">state</span><span class="p">}</span>
  <span class="k">end</span>

  <span class="k">def</span> <span class="n">handle_info</span><span class="p">({</span><span class="ss">:update</span><span class="p">,</span> <span class="n">game_state</span><span class="p">},</span> <span class="n">state</span><span class="p">)</span> <span class="k">do</span>
    <span class="n">send_game_update</span><span class="p">(</span><span class="n">state</span><span class="o">.</span><span class="n">socket</span><span class="p">,</span> <span class="n">game_state</span><span class="p">)</span>
    <span class="p">{</span><span class="ss">:noreply</span><span class="p">,</span> <span class="n">state</span><span class="p">}</span>
  <span class="k">end</span>

  <span class="k">defp</span> <span class="n">send_game_update</span><span class="p">(</span><span class="n">socket</span><span class="p">,</span> <span class="n">game_state</span><span class="p">)</span> <span class="k">do</span>
    <span class="ss">:gen_tcp</span><span class="o">.</span><span class="n">send</span><span class="p">(</span><span class="n">socket</span><span class="p">,</span> <span class="no">BinaryCodec</span><span class="o">.</span><span class="n">encode</span><span class="p">(</span><span class="n">game_state</span><span class="p">))</span>
  <span class="k">end</span>
<span class="k">end</span>
</code></pre></div></div>

<p>Most of the handlers are the typical ones for a bare bone TCP connection, the
only real addition is the <code class="language-plaintext highlighter-rouge">send_game_update/2</code> call which forward a message to
the connected client, in this case the binary encoded game state as the
payload.</p>

<p>The main TCP handler, is responsible for receiving packets from the client, in
this first iteration, it’s main responsibility will be to just decode the
payload into an event to be applied to the game state; currently there is no
definition of this component, it will be the main topic of the next section.</p>

<h2 id="a-little-event-handling-system">A little event handling system</h2>

<p>As explained above, the game handling will be designed as a sort of event
queue, where each event will influence the state of the game; naturally, the
first brick required is the definition of what an event is, in this game,
events could include things like</p>

<ul>
  <li>player connections and/or drop</li>
  <li>player movements</li>
  <li>shooting</li>
  <li>power-up generation and placement</li>
  <li>power-up collection</li>
</ul>

<p>First thing, we’re gonna define a very simple set of events that we can extend
later</p>

<div class="language-elixir highlighter-rouge"><div class="highlight"><pre class="highlight"><code>
<span class="k">defmodule</span> <span class="no">Dogfight</span><span class="o">.</span><span class="no">Game</span><span class="o">.</span><span class="no">Event</span> <span class="k">do</span>
  <span class="nv">@moduledoc</span> <span class="sd">"""
  This structure represents any game event to be applied to a game state to
  transition to a new state
  """</span>

  <span class="n">alias</span> <span class="no">Dogfight</span><span class="o">.</span><span class="no">Game</span><span class="o">.</span><span class="no">State</span>

  <span class="nv">@type</span> <span class="n">t</span> <span class="p">::</span>
          <span class="p">{</span><span class="ss">:player_connection</span><span class="p">,</span> <span class="no">State</span><span class="o">.</span><span class="n">player_id</span><span class="p">()}</span>
          <span class="o">|</span> <span class="p">{</span><span class="ss">:player_disconnection</span><span class="p">,</span> <span class="no">State</span><span class="o">.</span><span class="n">player_id</span><span class="p">()}</span>
          <span class="o">|</span> <span class="p">{</span><span class="ss">:move</span><span class="p">,</span> <span class="p">{</span><span class="no">State</span><span class="o">.</span><span class="n">player_id</span><span class="p">(),</span> <span class="no">State</span><span class="o">.</span><span class="n">direction</span><span class="p">()}}</span>
          <span class="o">|</span> <span class="p">{</span><span class="ss">:shoot</span><span class="p">,</span> <span class="no">State</span><span class="o">.</span><span class="n">player_id</span><span class="p">()}</span>
          <span class="o">|</span> <span class="p">{</span><span class="ss">:pickup_power_up</span><span class="p">,</span> <span class="no">State</span><span class="o">.</span><span class="n">player_id</span><span class="p">()}</span>
          <span class="o">|</span> <span class="p">{</span><span class="ss">:spawn_power_up</span><span class="p">,</span> <span class="no">State</span><span class="o">.</span><span class="n">power_up_kind</span><span class="p">()}</span>
          <span class="o">|</span> <span class="p">{</span><span class="ss">:start_game</span><span class="p">}</span>
          <span class="o">|</span> <span class="p">{</span><span class="ss">:end_game</span><span class="p">}</span>

  <span class="k">def</span> <span class="n">player_connection</span><span class="p">(</span><span class="n">player_id</span><span class="p">),</span> <span class="k">do</span><span class="p">:</span> <span class="p">{</span><span class="ss">:player_connection</span><span class="p">,</span> <span class="n">player_id</span><span class="p">}</span>
  <span class="k">def</span> <span class="n">player_disconnection</span><span class="p">(</span><span class="n">player_id</span><span class="p">),</span> <span class="k">do</span><span class="p">:</span> <span class="p">{</span><span class="ss">:player_disconnection</span><span class="p">,</span> <span class="n">player_id</span><span class="p">}</span>
  <span class="k">def</span> <span class="n">move</span><span class="p">(</span><span class="n">player_id</span><span class="p">,</span> <span class="n">direction</span><span class="p">),</span> <span class="k">do</span><span class="p">:</span> <span class="p">{</span><span class="ss">:move</span><span class="p">,</span> <span class="p">{</span><span class="n">player_id</span><span class="p">,</span> <span class="n">direction</span><span class="p">}}</span>
  <span class="k">def</span> <span class="n">shoot</span><span class="p">(</span><span class="n">player_id</span><span class="p">),</span> <span class="k">do</span><span class="p">:</span> <span class="p">{</span><span class="ss">:shoot</span><span class="p">,</span> <span class="n">player_id</span><span class="p">}</span>
<span class="k">end</span>
</code></pre></div></div>

<h3 id="the-game-event-handler">The game event handler</h3>

<p>Now that we have a small set of events, we want to process them. The event
handler is the main process that govern the state of a game, it is in essence a
match which can be hosted by one of the player to invite others to join. At the
moment this logic is yet to be implemented and it’s essentially skipped, there
is a single game event handler started as part of the application startup.</p>

<p>At the core, it’s basically an event queue, events received by connected clients as
well as time based generated for the natural progression of the game are handled here,
applying them to the associated <code class="language-plaintext highlighter-rouge">Game.State</code> will provide new updated versions of the
game.</p>

<p>The main responsibilities of the <code class="language-plaintext highlighter-rouge">GenServer</code> is to provide a single source of truth to
all the connected clients:</p>

<ul>
  <li>Each connecting player, after having received an identifier, are registered to the
game server (this happens in the acceptor TCP server via the <code class="language-plaintext highlighter-rouge">register_player/2</code> call)</li>
  <li>Once a player is registered, a ship is spawned in a random position for them via the
<code class="language-plaintext highlighter-rouge">GameState.add_player/2</code> for the given ID.</li>
  <li>Broadcast periodic updates of the game state to all connected clients to keep updating
the GUI of each of them (currently set at 50 ms by <code class="language-plaintext highlighter-rouge">@tick_rate_ms</code>, but it’s not really
important now)</li>
</ul>

<div class="language-elixir highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">defmodule</span> <span class="no">Dogfight</span><span class="o">.</span><span class="no">Game</span><span class="o">.</span><span class="no">EventHandler</span> <span class="k">do</span>
  <span class="nv">@moduledoc</span> <span class="sd">"""
  Represents a game server for the Dogfight game. It handles the game state and
  player actions. It is intended as the main source of thruth for each instantiated game,
  broadcasting the game state to each connected player every `@tick` milliseconds.
  """</span>
  <span class="kn">require</span> <span class="no">Logger</span>
  <span class="kn">use</span> <span class="no">GenServer</span>

  <span class="n">alias</span> <span class="no">Dogfight</span><span class="o">.</span><span class="no">Game</span><span class="o">.</span><span class="no">State</span><span class="p">,</span> <span class="ss">as:</span> <span class="no">GameState</span>

  <span class="nv">@tick_rate_ms</span> <span class="mi">50</span>

  <span class="k">def</span> <span class="n">start_link</span><span class="p">(</span><span class="n">_</span><span class="p">)</span> <span class="k">do</span>
    <span class="no">GenServer</span><span class="o">.</span><span class="n">start_link</span><span class="p">(</span><span class="bp">__MODULE__</span><span class="p">,</span> <span class="p">%{},</span> <span class="ss">name:</span> <span class="bp">__MODULE__</span><span class="p">)</span>
  <span class="k">end</span>

  <span class="nv">@impl</span> <span class="no">true</span>
  <span class="k">def</span> <span class="n">init</span><span class="p">(</span><span class="n">_</span><span class="p">)</span> <span class="k">do</span>
    <span class="n">game_state</span> <span class="o">=</span> <span class="no">GameState</span><span class="o">.</span><span class="n">new</span><span class="p">()</span>
    <span class="n">schedule_tick</span><span class="p">()</span>
    <span class="p">{</span><span class="ss">:ok</span><span class="p">,</span> <span class="p">%{</span><span class="ss">players:</span> <span class="p">%{},</span> <span class="ss">game_state:</span> <span class="n">game_state</span><span class="p">}}</span>
  <span class="k">end</span>

  <span class="k">def</span> <span class="n">register_player</span><span class="p">(</span><span class="n">pid</span><span class="p">,</span> <span class="n">player_id</span><span class="p">)</span> <span class="k">do</span>
    <span class="no">GenServer</span><span class="o">.</span><span class="n">call</span><span class="p">(</span><span class="bp">__MODULE__</span><span class="p">,</span> <span class="p">{</span><span class="ss">:register_player</span><span class="p">,</span> <span class="n">pid</span><span class="p">,</span> <span class="n">player_id</span><span class="p">})</span>
    <span class="no">GenServer</span><span class="o">.</span><span class="n">cast</span><span class="p">(</span><span class="bp">__MODULE__</span><span class="p">,</span> <span class="p">{</span><span class="ss">:add_player</span><span class="p">,</span> <span class="n">player_id</span><span class="p">})</span>
  <span class="k">end</span>

  <span class="k">def</span> <span class="n">apply_event</span><span class="p">(</span><span class="n">pid</span><span class="p">,</span> <span class="n">event</span><span class="p">)</span> <span class="k">do</span>
    <span class="k">case</span> <span class="n">event</span> <span class="k">do</span>
      <span class="p">{</span><span class="ss">:player_connection</span><span class="p">,</span> <span class="n">player_id</span><span class="p">}</span> <span class="o">-&gt;</span>
        <span class="no">GenServer</span><span class="o">.</span><span class="n">call</span><span class="p">(</span><span class="bp">__MODULE__</span><span class="p">,</span> <span class="p">{</span><span class="ss">:register_player</span><span class="p">,</span> <span class="n">pid</span><span class="p">,</span> <span class="n">player_id</span><span class="p">})</span>
        <span class="no">GenServer</span><span class="o">.</span><span class="n">cast</span><span class="p">(</span><span class="bp">__MODULE__</span><span class="p">,</span> <span class="p">{</span><span class="ss">:add_player</span><span class="p">,</span> <span class="n">player_id</span><span class="p">})</span>

      <span class="p">{</span><span class="ss">:player_disconnection</span><span class="p">,</span> <span class="n">player_id</span><span class="p">}</span> <span class="o">-&gt;</span>
        <span class="no">GenServer</span><span class="o">.</span><span class="n">call</span><span class="p">(</span><span class="bp">__MODULE__</span><span class="p">,</span> <span class="p">{</span><span class="ss">:unregister_player</span><span class="p">,</span> <span class="n">player_id</span><span class="p">})</span>
        <span class="no">GenServer</span><span class="o">.</span><span class="n">cast</span><span class="p">(</span><span class="bp">__MODULE__</span><span class="p">,</span> <span class="p">{</span><span class="ss">:drop_player</span><span class="p">,</span> <span class="n">player_id</span><span class="p">})</span>

      <span class="n">event</span> <span class="o">-&gt;</span>
        <span class="no">GenServer</span><span class="o">.</span><span class="n">cast</span><span class="p">(</span><span class="bp">__MODULE__</span><span class="p">,</span> <span class="p">{</span><span class="ss">:apply_event</span><span class="p">,</span> <span class="n">pid</span><span class="p">,</span> <span class="n">event</span><span class="p">})</span>
    <span class="k">end</span>
  <span class="k">end</span>

  <span class="k">defp</span> <span class="n">schedule_tick</span><span class="p">()</span> <span class="k">do</span>
    <span class="no">Process</span><span class="o">.</span><span class="n">send_after</span><span class="p">(</span><span class="n">self</span><span class="p">(),</span> <span class="ss">:tick</span><span class="p">,</span> <span class="nv">@tick_rate_ms</span><span class="p">)</span>
  <span class="k">end</span>

  <span class="nv">@impl</span> <span class="no">true</span>
  <span class="k">def</span> <span class="n">handle_info</span><span class="p">(</span><span class="ss">:tick</span><span class="p">,</span> <span class="n">state</span><span class="p">)</span> <span class="k">do</span>
    <span class="n">new_game_state</span> <span class="o">=</span> <span class="no">GameState</span><span class="o">.</span><span class="n">update</span><span class="p">(</span><span class="n">state</span><span class="o">.</span><span class="n">game_state</span><span class="p">)</span>
    <span class="n">updated_state</span> <span class="o">=</span> <span class="p">%{</span><span class="n">state</span> <span class="o">|</span> <span class="ss">game_state:</span> <span class="n">new_game_state</span><span class="p">}</span>
    <span class="n">broadcast_game_state</span><span class="p">(</span><span class="n">updated_state</span><span class="p">)</span>
    <span class="n">schedule_tick</span><span class="p">()</span>
    <span class="p">{</span><span class="ss">:noreply</span><span class="p">,</span> <span class="n">updated_state</span><span class="p">}</span>
  <span class="k">end</span>

  <span class="nv">@impl</span> <span class="no">true</span>
  <span class="k">def</span> <span class="n">handle_call</span><span class="p">({</span><span class="ss">:register_player</span><span class="p">,</span> <span class="n">pid</span><span class="p">,</span> <span class="n">player_id</span><span class="p">},</span> <span class="n">from</span><span class="p">,</span> <span class="n">state</span><span class="p">)</span> <span class="k">do</span>
    <span class="n">new_state</span> <span class="o">=</span>
      <span class="no">Map</span><span class="o">.</span><span class="n">update!</span><span class="p">(</span><span class="n">state</span><span class="p">,</span> <span class="ss">:players</span><span class="p">,</span> <span class="k">fn</span> <span class="n">players</span> <span class="o">-&gt;</span>
        <span class="no">Map</span><span class="o">.</span><span class="n">put</span><span class="p">(</span><span class="n">players</span><span class="p">,</span> <span class="n">player_id</span><span class="p">,</span> <span class="n">pid</span><span class="p">)</span>
      <span class="k">end</span><span class="p">)</span>

    <span class="p">{</span><span class="ss">:reply</span><span class="p">,</span> <span class="n">from</span><span class="p">,</span> <span class="n">new_state</span><span class="p">}</span>
  <span class="k">end</span>

  <span class="nv">@impl</span> <span class="no">true</span>
  <span class="k">def</span> <span class="n">handle_call</span><span class="p">({</span><span class="ss">:unregister_player</span><span class="p">,</span> <span class="n">player_id</span><span class="p">},</span> <span class="n">from</span><span class="p">,</span> <span class="n">state</span><span class="p">)</span> <span class="k">do</span>
    <span class="n">new_state</span> <span class="o">=</span>
      <span class="no">Map</span><span class="o">.</span><span class="n">update!</span><span class="p">(</span><span class="n">state</span><span class="p">,</span> <span class="ss">:players</span><span class="p">,</span> <span class="k">fn</span> <span class="n">players</span> <span class="o">-&gt;</span>
        <span class="no">Map</span><span class="o">.</span><span class="n">delete</span><span class="p">(</span><span class="n">players</span><span class="p">,</span> <span class="n">player_id</span><span class="p">)</span>
      <span class="k">end</span><span class="p">)</span>

    <span class="p">{</span><span class="ss">:reply</span><span class="p">,</span> <span class="n">from</span><span class="p">,</span> <span class="n">new_state</span><span class="p">}</span>
  <span class="k">end</span>

  <span class="nv">@impl</span> <span class="no">true</span>
  <span class="k">def</span> <span class="n">handle_cast</span><span class="p">({</span><span class="ss">:add_player</span><span class="p">,</span> <span class="n">player_id</span><span class="p">},</span> <span class="n">state</span><span class="p">)</span> <span class="k">do</span>
    <span class="n">game_state</span> <span class="o">=</span>
      <span class="k">case</span> <span class="no">GameState</span><span class="o">.</span><span class="n">add_player</span><span class="p">(</span><span class="n">state</span><span class="o">.</span><span class="n">game_state</span><span class="p">,</span> <span class="n">player_id</span><span class="p">)</span> <span class="k">do</span>
        <span class="p">{</span><span class="ss">:ok</span><span class="p">,</span> <span class="n">game_state</span><span class="p">}</span> <span class="o">-&gt;</span>
          <span class="n">broadcast_game_state</span><span class="p">(%{</span><span class="n">state</span> <span class="o">|</span> <span class="ss">game_state:</span> <span class="n">game_state</span><span class="p">})</span>
          <span class="n">game_state</span>

        <span class="p">{</span><span class="ss">:error</span><span class="p">,</span> <span class="n">error</span><span class="p">}</span> <span class="o">-&gt;</span>
          <span class="no">Logger</span><span class="o">.</span><span class="n">error</span><span class="p">(</span><span class="s2">"Failed spawining spaceship, reason: </span><span class="si">#{</span><span class="n">inspect</span><span class="p">(</span><span class="n">error</span><span class="p">)</span><span class="si">}</span><span class="s2">"</span><span class="p">)</span>
          <span class="n">state</span><span class="o">.</span><span class="n">game_state</span>
      <span class="k">end</span>

    <span class="p">{</span><span class="ss">:noreply</span><span class="p">,</span> <span class="p">%{</span><span class="n">state</span> <span class="o">|</span> <span class="ss">game_state:</span> <span class="n">game_state</span><span class="p">}}</span>
  <span class="k">end</span>

  <span class="nv">@impl</span> <span class="no">true</span>
  <span class="k">def</span> <span class="n">handle_cast</span><span class="p">({</span><span class="ss">:drop_player</span><span class="p">,</span> <span class="n">player_id</span><span class="p">},</span> <span class="n">state</span><span class="p">)</span> <span class="k">do</span>
    <span class="p">{</span><span class="ss">:noreply</span><span class="p">,</span> <span class="p">%{</span><span class="n">state</span> <span class="o">|</span> <span class="ss">game_state:</span> <span class="no">GameState</span><span class="o">.</span><span class="n">drop_player</span><span class="p">(</span><span class="n">state</span><span class="o">.</span><span class="n">game_state</span><span class="p">,</span> <span class="n">player_id</span><span class="p">)}}</span>
  <span class="k">end</span>

  <span class="nv">@impl</span> <span class="no">true</span>
  <span class="k">def</span> <span class="n">handle_cast</span><span class="p">({</span><span class="ss">:apply_event</span><span class="p">,</span> <span class="n">_pid</span><span class="p">,</span> <span class="n">event</span><span class="p">},</span> <span class="n">state</span><span class="p">)</span> <span class="k">do</span>
    <span class="n">updated_state</span> <span class="o">=</span>
      <span class="n">with</span> <span class="p">{</span><span class="ss">:ok</span><span class="p">,</span> <span class="n">game_state</span><span class="p">}</span> <span class="o">&lt;-</span> <span class="no">GameState</span><span class="o">.</span><span class="n">apply_event</span><span class="p">(</span><span class="n">state</span><span class="o">.</span><span class="n">game_state</span><span class="p">,</span> <span class="n">event</span><span class="p">)</span> <span class="k">do</span>
        <span class="n">updated_state</span> <span class="o">=</span> <span class="p">%{</span><span class="n">state</span> <span class="o">|</span> <span class="ss">game_state:</span> <span class="n">game_state</span><span class="p">}</span>
        <span class="n">broadcast_game_state</span><span class="p">(</span><span class="n">updated_state</span><span class="p">)</span>
        <span class="n">updated_state</span>
      <span class="k">else</span>
        <span class="n">e</span> <span class="o">-&gt;</span>
          <span class="no">Logger</span><span class="o">.</span><span class="n">error</span><span class="p">(</span><span class="s2">"Failed to apply event, reason: </span><span class="si">#{</span><span class="n">inspect</span><span class="p">(</span><span class="n">e</span><span class="p">)</span><span class="si">}</span><span class="s2">"</span><span class="p">)</span>
          <span class="n">state</span>
      <span class="k">end</span>

    <span class="p">{</span><span class="ss">:noreply</span><span class="p">,</span> <span class="n">updated_state</span><span class="p">}</span>
  <span class="k">end</span>

  <span class="k">defp</span> <span class="n">broadcast_game_state</span><span class="p">(</span><span class="n">state</span><span class="p">)</span> <span class="k">do</span>
    <span class="no">Enum</span><span class="o">.</span><span class="n">each</span><span class="p">(</span><span class="no">Map</span><span class="o">.</span><span class="n">values</span><span class="p">(</span><span class="n">state</span><span class="o">.</span><span class="n">players</span><span class="p">),</span> <span class="k">fn</span> <span class="n">pid</span> <span class="o">-&gt;</span>
      <span class="n">send</span><span class="p">(</span><span class="n">pid</span><span class="p">,</span> <span class="p">{</span><span class="ss">:update</span><span class="p">,</span> <span class="n">state</span><span class="o">.</span><span class="n">game_state</span><span class="p">})</span>
    <span class="k">end</span><span class="p">)</span>
  <span class="k">end</span>
<span class="k">end</span>
</code></pre></div></div>

<p>The <code class="language-plaintext highlighter-rouge">init/1</code> call is pretty cheap, no need to add an <code class="language-plaintext highlighter-rouge">handle_continue/2</code> as it
won’t really block. To be noted that the registration of a new player, performs
a call and a cast. That because we want to make sure the state is updated
before spawning the ship for the new player. Also there is no check yet for
already existing ships, disconnections and reconnection etc. For all the
unexpected cases we just log error or crash, it will be for a second pass to
actually clean up and harden the code. It’s pretty simple and straight-forward.</p>

<h2 id="the-game-state">The game state</h2>

<p>The game state represents the main entities present in a game, it’s the main
payload of information exchanged between clients and server to keep all the
players in sync.</p>

<p>It is indeed pretty simple and definitely not optimized at the moment, there’s
a number of things that can be improved and done differently, some of the
fields are a carry over from the original C implementation which I’m gonna use
as the main test-bed, but the aim is to then update the state for a better v2
representation. For the sake of simplicity, the first version will carry the
following entities:</p>

<ul>
  <li>A set of players active in the game, for simplicity it will be a simple map
<code class="language-plaintext highlighter-rouge">player_id -&gt; spaceship</code>
    <ul>
      <li>Each player’s spaceship is represented by
        <ul>
          <li><strong>position:</strong> a vector 2D x and y representing the position in the screen</li>
          <li><strong>hp:</strong> just health points, initially set at 5</li>
          <li><strong>direction:</strong> self-explanatory, this can be (not exactly precise but work for a prototype)
            <ul>
              <li><code class="language-plaintext highlighter-rouge">:idle | :up | :down | :left | :right</code></li>
            </ul>
          </li>
          <li><strong>alive?:</strong> an alive boolean flag</li>
          <li><strong>bullets:</strong> set to a maximum of 5, composed by
            <ul>
              <li><strong>position:</strong> a vector 2D x and y representing the position in the screen</li>
              <li><strong>direction:</strong> again, a direction, this will be aligned with the player direction</li>
              <li><strong>active?:</strong> an active boolean flag</li>
            </ul>
          </li>
        </ul>
      </li>
    </ul>
  </li>
  <li><strong>power_ups:</strong> these will be spawned randomly on a time-basis, made of
    <ul>
      <li><strong>position:</strong> a vector 2D x and y representing the position in the screen</li>
      <li><strong>kind:</strong> an atom representing the type of effect it will have on the player that
captures it, initially can be
        <ul>
          <li><code class="language-plaintext highlighter-rouge">:hp_plus_one | :hp_plus_three | :ammo_plus_one | nil</code></li>
        </ul>
      </li>
    </ul>
  </li>
</ul>

<p>At the moment we’re not gonna handle scaling and screen size for each player, it is
initially assumed that each player will play in a small 800x600 window.</p>

<h3 id="the-main-entity-spaceship">The main entity, spaceship</h3>

<p>Before we create the <code class="language-plaintext highlighter-rouge">Game.State</code> module, it may be a good idea to define a
simple behaviour for the spaceship: generally, I tend to be against
abstractions and go for the most concrete implementation first, but I have the
feeling the spaceship will be one of the things that will be easily generated
with different characteristics. It’s a small interface anyway, in Elixir the
coupling is not as prohibitive as other languages anyway.</p>

<div class="language-elixir highlighter-rouge"><div class="highlight"><pre class="highlight"><code>
<span class="k">defmodule</span> <span class="no">Dogfight</span><span class="o">.</span><span class="no">Game</span><span class="o">.</span><span class="no">Spaceship</span> <span class="k">do</span>
  <span class="nv">@moduledoc</span> <span class="sd">"""
  Spaceship behaviour
  """</span>

  <span class="n">alias</span> <span class="no">Dogfight</span><span class="o">.</span><span class="no">Game</span><span class="o">.</span><span class="no">State</span>

  <span class="nv">@type</span> <span class="n">t</span> <span class="p">::</span> <span class="n">any</span><span class="p">()</span>

  <span class="nv">@callback</span> <span class="n">move</span><span class="p">(</span><span class="n">t</span><span class="p">(),</span> <span class="no">State</span><span class="o">.</span><span class="n">direction</span><span class="p">())</span> <span class="p">::</span> <span class="n">t</span><span class="p">()</span>
  <span class="nv">@callback</span> <span class="n">shoot</span><span class="p">(</span><span class="n">t</span><span class="p">())</span> <span class="p">::</span> <span class="n">t</span><span class="p">()</span>
  <span class="nv">@callback</span> <span class="n">update_bullets</span><span class="p">(</span><span class="n">t</span><span class="p">())</span> <span class="p">::</span> <span class="n">t</span><span class="p">()</span>
<span class="k">end</span>
</code></pre></div></div>

<p>The main traits we’re interested in any given spaceship are</p>

<ul>
  <li>The move logic, think about slow heavily-armed spaceship or quick smaller fighters etc.</li>
  <li>The shoot logic, again, heavy hitters, barrage rockets or multi-directional machine guns.</li>
  <li>The update bullets logic, fast bullets, lasers, or slow big boys etc.</li>
</ul>

<p>A first simple implementation, something like a no-frills no-fancy default
spaceship, the <code class="language-plaintext highlighter-rouge">DefaultSpaceship</code> follows</p>

<div class="language-elixir highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">defmodule</span> <span class="no">Dogfight</span><span class="o">.</span><span class="no">Game</span><span class="o">.</span><span class="no">DefaultSpaceship</span> <span class="k">do</span>
  <span class="nv">@moduledoc</span> <span class="sd">"""
  DefaultSpaceship module, represents the main entity of each player, a default
  spaceship without any particular trait, with a base speed of 3 units and base HP
  of 5.
  """</span>

  <span class="k">defmodule</span> <span class="no">Bullet</span> <span class="k">do</span>
    <span class="nv">@moduledoc</span> <span class="sd">"""
    Bullet inner module, represents a bullet of the spaceship entity
    """</span>

    <span class="nv">@type</span> <span class="n">t</span> <span class="p">::</span> <span class="p">%</span><span class="bp">__MODULE__</span><span class="p">{</span>
            <span class="ss">position:</span> <span class="no">Vec2</span><span class="o">.</span><span class="n">t</span><span class="p">(),</span>
            <span class="ss">direction:</span> <span class="no">State</span><span class="o">.</span><span class="n">direction</span><span class="p">(),</span>
            <span class="ss">active?:</span> <span class="n">boolean</span><span class="p">(),</span>
            <span class="ss">boundaries:</span> <span class="p">%{</span><span class="ss">width:</span> <span class="n">non_neg_integer</span><span class="p">(),</span> <span class="ss">height:</span> <span class="n">non_neg_integer</span><span class="p">()}</span>
          <span class="p">}</span>

    <span class="k">defstruct</span> <span class="p">[</span><span class="ss">:position</span><span class="p">,</span> <span class="ss">:direction</span><span class="p">,</span> <span class="ss">:active?</span><span class="p">,</span> <span class="ss">:boundaries</span><span class="p">]</span>

    <span class="n">alias</span> <span class="no">Dogfight</span><span class="o">.</span><span class="no">Game</span><span class="o">.</span><span class="no">State</span>
    <span class="n">alias</span> <span class="no">Dogfight</span><span class="o">.</span><span class="no">Game</span><span class="o">.</span><span class="no">Vec2</span>

    <span class="nv">@bullet_base_speed</span> <span class="mi">6</span>

    <span class="k">def</span> <span class="n">new</span><span class="p">(</span><span class="n">width</span><span class="p">,</span> <span class="n">height</span><span class="p">)</span> <span class="k">do</span>
      <span class="p">%</span><span class="bp">__MODULE__</span><span class="p">{</span>
        <span class="ss">position:</span> <span class="p">%</span><span class="no">Vec2</span><span class="p">{</span><span class="ss">x:</span> <span class="mi">0</span><span class="p">,</span> <span class="ss">y:</span> <span class="mi">0</span><span class="p">},</span>
        <span class="ss">direction:</span> <span class="no">State</span><span class="o">.</span><span class="n">idle</span><span class="p">(),</span>
        <span class="ss">active?:</span> <span class="no">false</span><span class="p">,</span>
        <span class="ss">boundaries:</span> <span class="p">%{</span><span class="ss">width:</span> <span class="n">width</span><span class="p">,</span> <span class="ss">height:</span> <span class="n">height</span><span class="p">}</span>
      <span class="p">}</span>
    <span class="k">end</span>

    <span class="k">def</span> <span class="n">update</span><span class="p">(%{</span><span class="ss">active?:</span> <span class="no">false</span><span class="p">}</span> <span class="o">=</span> <span class="n">bullet</span><span class="p">),</span> <span class="k">do</span><span class="p">:</span> <span class="n">bullet</span>

    <span class="k">def</span> <span class="n">update</span><span class="p">(</span><span class="n">bullet</span><span class="p">)</span> <span class="k">do</span>
      <span class="n">position</span> <span class="o">=</span> <span class="n">bullet</span><span class="o">.</span><span class="n">position</span>

      <span class="n">bullet</span> <span class="o">=</span>
        <span class="k">case</span> <span class="n">bullet</span><span class="o">.</span><span class="n">direction</span> <span class="k">do</span>
          <span class="ss">:up</span> <span class="o">-&gt;</span>
            <span class="p">%{</span><span class="n">bullet</span> <span class="o">|</span> <span class="ss">position:</span> <span class="no">Vec2</span><span class="o">.</span><span class="n">add_y</span><span class="p">(</span><span class="n">position</span><span class="p">,</span> <span class="o">-</span><span class="nv">@bullet_base_speed</span><span class="p">)}</span>

          <span class="ss">:down</span> <span class="o">-&gt;</span>
            <span class="p">%{</span><span class="n">bullet</span> <span class="o">|</span> <span class="ss">position:</span> <span class="no">Vec2</span><span class="o">.</span><span class="n">add_y</span><span class="p">(</span><span class="n">position</span><span class="p">,</span> <span class="nv">@bullet_base_speed</span><span class="p">)}</span>

          <span class="ss">:left</span> <span class="o">-&gt;</span>
            <span class="p">%{</span><span class="n">bullet</span> <span class="o">|</span> <span class="ss">position:</span> <span class="no">Vec2</span><span class="o">.</span><span class="n">add_x</span><span class="p">(</span><span class="n">position</span><span class="p">,</span> <span class="o">-</span><span class="nv">@bullet_base_speed</span><span class="p">)}</span>

          <span class="ss">:right</span> <span class="o">-&gt;</span>
            <span class="p">%{</span><span class="n">bullet</span> <span class="o">|</span> <span class="ss">position:</span> <span class="no">Vec2</span><span class="o">.</span><span class="n">add_x</span><span class="p">(</span><span class="n">position</span><span class="p">,</span> <span class="nv">@bullet_base_speed</span><span class="p">)}</span>

          <span class="n">_</span> <span class="o">-&gt;</span>
            <span class="n">bullet</span>
        <span class="k">end</span>

      <span class="n">boundaries_check</span><span class="p">(</span><span class="n">bullet</span><span class="p">)</span>
    <span class="k">end</span>

    <span class="k">defp</span> <span class="n">boundaries_check</span><span class="p">(%{</span><span class="ss">position:</span> <span class="p">%{</span><span class="ss">x:</span> <span class="n">x</span><span class="p">},</span> <span class="ss">boundaries:</span> <span class="p">%{</span><span class="ss">width:</span> <span class="n">width</span><span class="p">}}</span> <span class="o">=</span> <span class="n">bullet</span><span class="p">)</span>
         <span class="ow">when</span> <span class="n">x</span> <span class="o">&lt;</span> <span class="mi">0</span> <span class="ow">or</span> <span class="n">x</span> <span class="o">&gt;=</span> <span class="n">width</span><span class="p">,</span>
         <span class="k">do</span><span class="p">:</span> <span class="p">%{</span><span class="n">bullet</span> <span class="o">|</span> <span class="ss">active?:</span> <span class="no">false</span><span class="p">}</span>

    <span class="k">defp</span> <span class="n">boundaries_check</span><span class="p">(%{</span><span class="ss">position:</span> <span class="p">%{</span><span class="ss">y:</span> <span class="n">y</span><span class="p">},</span> <span class="ss">boundaries:</span> <span class="p">%{</span><span class="ss">height:</span> <span class="n">height</span><span class="p">}}</span> <span class="o">=</span> <span class="n">bullet</span><span class="p">)</span>
         <span class="ow">when</span> <span class="n">y</span> <span class="o">&lt;</span> <span class="mi">0</span> <span class="ow">or</span> <span class="n">y</span> <span class="o">&gt;=</span> <span class="n">height</span><span class="p">,</span>
         <span class="k">do</span><span class="p">:</span> <span class="p">%{</span><span class="n">bullet</span> <span class="o">|</span> <span class="ss">active?:</span> <span class="no">false</span><span class="p">}</span>

    <span class="k">defp</span> <span class="n">boundaries_check</span><span class="p">(</span><span class="n">bullet</span><span class="p">),</span> <span class="k">do</span><span class="p">:</span> <span class="n">bullet</span>
  <span class="k">end</span>

  <span class="nv">@behaviour</span> <span class="no">Dogfight</span><span class="o">.</span><span class="no">Game</span><span class="o">.</span><span class="no">Spaceship</span>

  <span class="n">alias</span> <span class="no">Dogfight</span><span class="o">.</span><span class="no">Game</span><span class="o">.</span><span class="no">State</span>
  <span class="n">alias</span> <span class="no">Dogfight</span><span class="o">.</span><span class="no">Game</span><span class="o">.</span><span class="no">Bullet</span>
  <span class="n">alias</span> <span class="no">Dogfight</span><span class="o">.</span><span class="no">Game</span><span class="o">.</span><span class="no">Vec2</span>

  <span class="nv">@type</span> <span class="n">t</span> <span class="p">::</span> <span class="p">%</span><span class="bp">__MODULE__</span><span class="p">{</span>
          <span class="ss">position:</span> <span class="no">Vec2</span><span class="o">.</span><span class="n">t</span><span class="p">(),</span>
          <span class="ss">hp:</span> <span class="n">non_neg_integer</span><span class="p">(),</span>
          <span class="ss">direction:</span> <span class="no">State</span><span class="o">.</span><span class="n">direction</span><span class="p">(),</span>
          <span class="ss">alive?:</span> <span class="n">boolean</span><span class="p">(),</span>
          <span class="ss">bullets:</span> <span class="p">[</span><span class="no">Bullet</span><span class="o">.</span><span class="n">t</span><span class="p">(),</span> <span class="o">...</span><span class="p">]</span>
        <span class="p">}</span>

  <span class="k">defstruct</span> <span class="p">[</span><span class="ss">:position</span><span class="p">,</span> <span class="ss">:hp</span><span class="p">,</span> <span class="ss">:direction</span><span class="p">,</span> <span class="ss">:alive?</span><span class="p">,</span> <span class="ss">:bullets</span><span class="p">]</span>

  <span class="nv">@base_hp</span> <span class="mi">5</span>
  <span class="nv">@base_bullet_count</span> <span class="mi">5</span>
  <span class="nv">@base_spaceship_speed</span> <span class="mi">3</span>

  <span class="k">def</span> <span class="n">spawn</span><span class="p">(</span><span class="n">width</span><span class="p">,</span> <span class="n">height</span><span class="p">)</span> <span class="k">do</span>
    <span class="p">%</span><span class="bp">__MODULE__</span><span class="p">{</span>
      <span class="ss">position:</span> <span class="no">Vec2</span><span class="o">.</span><span class="n">random</span><span class="p">(</span><span class="n">width</span><span class="p">,</span> <span class="n">height</span><span class="p">),</span>
      <span class="ss">direction:</span> <span class="no">State</span><span class="o">.</span><span class="n">idle</span><span class="p">(),</span>
      <span class="ss">hp:</span> <span class="nv">@base_hp</span><span class="p">,</span>
      <span class="ss">alive?:</span> <span class="no">true</span><span class="p">,</span>
      <span class="ss">bullets:</span>
        <span class="no">Stream</span><span class="o">.</span><span class="n">repeatedly</span><span class="p">(</span><span class="k">fn</span> <span class="o">-&gt;</span> <span class="bp">__MODULE__</span><span class="o">.</span><span class="no">Bullet</span><span class="o">.</span><span class="n">new</span><span class="p">(</span><span class="n">width</span><span class="p">,</span> <span class="n">height</span><span class="p">)</span> <span class="k">end</span><span class="p">)</span>
        <span class="o">|&gt;</span> <span class="no">Enum</span><span class="o">.</span><span class="n">take</span><span class="p">(</span><span class="nv">@base_bullet_count</span><span class="p">)</span>
    <span class="p">}</span>
  <span class="k">end</span>

  <span class="nv">@impl</span> <span class="no">true</span>
  <span class="k">def</span> <span class="n">move</span><span class="p">(</span><span class="n">spaceship</span><span class="p">,</span> <span class="n">direction</span><span class="p">)</span> <span class="k">do</span>
    <span class="n">position</span> <span class="o">=</span> <span class="n">spaceship</span><span class="o">.</span><span class="n">position</span>

    <span class="n">updated_position</span> <span class="o">=</span>
      <span class="k">case</span> <span class="n">direction</span> <span class="k">do</span>
        <span class="ss">:up</span> <span class="o">-&gt;</span> <span class="no">Vec2</span><span class="o">.</span><span class="n">add_y</span><span class="p">(</span><span class="n">position</span><span class="p">,</span> <span class="o">-</span><span class="nv">@base_spaceship_speed</span><span class="p">)</span>
        <span class="ss">:down</span> <span class="o">-&gt;</span> <span class="no">Vec2</span><span class="o">.</span><span class="n">add_y</span><span class="p">(</span><span class="n">position</span><span class="p">,</span> <span class="nv">@base_spaceship_speed</span><span class="p">)</span>
        <span class="ss">:left</span> <span class="o">-&gt;</span> <span class="no">Vec2</span><span class="o">.</span><span class="n">add_x</span><span class="p">(</span><span class="n">position</span><span class="p">,</span> <span class="o">-</span><span class="nv">@base_spaceship_speed</span><span class="p">)</span>
        <span class="ss">:right</span> <span class="o">-&gt;</span> <span class="no">Vec2</span><span class="o">.</span><span class="n">add_x</span><span class="p">(</span><span class="n">position</span><span class="p">,</span> <span class="nv">@base_spaceship_speed</span><span class="p">)</span>
        <span class="ss">:idle</span> <span class="o">-&gt;</span> <span class="n">position</span>
      <span class="k">end</span>

    <span class="p">%{</span><span class="n">spaceship</span> <span class="o">|</span> <span class="ss">direction:</span> <span class="n">direction</span><span class="p">,</span> <span class="ss">position:</span> <span class="n">updated_position</span><span class="p">}</span>
  <span class="k">end</span>

  <span class="nv">@impl</span> <span class="no">true</span>
  <span class="k">def</span> <span class="n">shoot</span><span class="p">(</span><span class="n">spaceship</span><span class="p">)</span> <span class="k">do</span>
    <span class="n">bullets</span> <span class="o">=</span>
      <span class="n">spaceship</span><span class="o">.</span><span class="n">bullets</span>
      <span class="o">|&gt;</span> <span class="no">Enum</span><span class="o">.</span><span class="n">map_reduce</span><span class="p">(</span><span class="no">false</span><span class="p">,</span> <span class="k">fn</span>
        <span class="n">bullet</span><span class="p">,</span> <span class="no">false</span> <span class="ow">when</span> <span class="n">bullet</span><span class="o">.</span><span class="n">active?</span> <span class="o">==</span> <span class="no">false</span> <span class="o">-&gt;</span>
          <span class="p">{</span>
            <span class="p">%{</span>
             <span class="n">bullet</span>
             <span class="o">|</span> <span class="ss">active?:</span> <span class="no">true</span><span class="p">,</span>
               <span class="ss">direction:</span> <span class="n">spaceship</span><span class="o">.</span><span class="n">direction</span><span class="p">,</span>
               <span class="ss">position:</span> <span class="n">spaceship</span><span class="o">.</span><span class="n">position</span>
           <span class="p">},</span> <span class="no">true</span><span class="p">}</span>

        <span class="n">bullet</span><span class="p">,</span> <span class="n">updated</span> <span class="o">-&gt;</span>
          <span class="p">{</span><span class="n">bullet</span><span class="p">,</span> <span class="n">updated</span><span class="p">}</span>
      <span class="k">end</span><span class="p">)</span>
      <span class="o">|&gt;</span> <span class="n">elem</span><span class="p">(</span><span class="mi">0</span><span class="p">)</span>

    <span class="p">%{</span>
      <span class="n">spaceship</span>
      <span class="o">|</span> <span class="ss">bullets:</span> <span class="n">bullets</span>
    <span class="p">}</span>
  <span class="k">end</span>

  <span class="nv">@impl</span> <span class="no">true</span>
  <span class="k">def</span> <span class="n">update_bullets</span><span class="p">(%{</span><span class="ss">alive?:</span> <span class="no">false</span><span class="p">}</span> <span class="o">=</span> <span class="n">spaceship</span><span class="p">),</span> <span class="k">do</span><span class="p">:</span> <span class="n">spaceship</span>

  <span class="nv">@impl</span> <span class="no">true</span>
  <span class="k">def</span> <span class="n">update_bullets</span><span class="p">(</span><span class="n">spaceship</span><span class="p">)</span> <span class="k">do</span>
    <span class="p">%{</span><span class="n">spaceship</span> <span class="o">|</span> <span class="ss">bullets:</span> <span class="no">Enum</span><span class="o">.</span><span class="n">map</span><span class="p">(</span><span class="n">spaceship</span><span class="o">.</span><span class="n">bullets</span><span class="p">,</span> <span class="o">&amp;</span><span class="bp">__MODULE__</span><span class="o">.</span><span class="no">Bullet</span><span class="o">.</span><span class="n">update</span><span class="o">/</span><span class="mi">1</span><span class="p">)}</span>
  <span class="k">end</span>
<span class="k">end</span>
</code></pre></div></div>

<p>As shown, it’s a rather basic implementation that does very little, but
everything we expect from a spaceship nonetheless:</p>

<ul>
  <li>Base HP of 5 units, each bullet will deal 1 unit of damage to begin</li>
  <li>Base bullet count of 5, power ups may provide additional ammos, bullets are meant to
automatically replenish over time</li>
  <li>A base movement speed of 3 units, this will be largely dependent on the client FPS</li>
  <li>An internal representation of the most elementary bullet with a base speed double of
the ship</li>
</ul>

<p>As a final side-note, there is a <code class="language-plaintext highlighter-rouge">Vec2</code> utility module called there, we will omit
its implementation as it’s pretty straight forward and very short, it does exactly
what it looks like, vector 2D capabilities such as</p>

<ul>
  <li>spawn of a random vector</li>
  <li>sum of 2 vectors</li>
  <li>sum of a constant on both coordinates</li>
</ul>

<p>We finally define the main entity, this will carry the main piece of information
to keep all the client in sync:</p>

<div class="language-elixir highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">defmodule</span> <span class="no">Dogfight</span><span class="o">.</span><span class="no">Game</span><span class="o">.</span><span class="no">State</span> <span class="k">do</span>
  <span class="nv">@moduledoc</span> <span class="sd">"""
  Game state management, Ships and bullet logics, including collision.
  Represents the source of truth for each connecting player, and its updated
  based on the input of each one of the connected active players
  """</span>

  <span class="n">alias</span> <span class="no">Dogfight</span><span class="o">.</span><span class="no">Game</span><span class="o">.</span><span class="no">Event</span>
  <span class="n">alias</span> <span class="no">Dogfight</span><span class="o">.</span><span class="no">Game</span><span class="o">.</span><span class="no">Spaceship</span>
  <span class="n">alias</span> <span class="no">Dogfight</span><span class="o">.</span><span class="no">Game</span><span class="o">.</span><span class="no">DefaultSpaceship</span>
  <span class="n">alias</span> <span class="no">Dogfight</span><span class="o">.</span><span class="no">Game</span><span class="o">.</span><span class="no">Vec2</span>

  <span class="nv">@type</span> <span class="n">player_id</span> <span class="p">::</span> <span class="no">String</span><span class="o">.</span><span class="n">t</span><span class="p">()</span>

  <span class="nv">@type</span> <span class="n">power_up_kind</span> <span class="p">::</span> <span class="ss">:hp_plus_one</span> <span class="o">|</span> <span class="ss">:hp_plus_three</span> <span class="o">|</span> <span class="ss">:ammo_plus_one</span> <span class="o">|</span> <span class="no">nil</span>

  <span class="nv">@type</span> <span class="n">power_up</span> <span class="p">::</span> <span class="p">%{</span>
          <span class="ss">position:</span> <span class="no">Vec2</span><span class="o">.</span><span class="n">t</span><span class="p">(),</span>
          <span class="ss">kind:</span> <span class="n">power_up_kind</span><span class="p">()</span>
        <span class="p">}</span>

  <span class="nv">@type</span> <span class="n">direction</span> <span class="p">::</span> <span class="ss">:idle</span> <span class="o">|</span> <span class="ss">:up</span> <span class="o">|</span> <span class="ss">:down</span> <span class="o">|</span> <span class="ss">:left</span> <span class="o">|</span> <span class="ss">:right</span>

  <span class="nv">@typep</span> <span class="n">status</span> <span class="p">::</span> <span class="ss">:in_progress</span> <span class="o">|</span> <span class="ss">:closed</span> <span class="o">|</span> <span class="no">nil</span>

  <span class="nv">@type</span> <span class="n">t</span> <span class="p">::</span> <span class="p">%</span><span class="bp">__MODULE__</span><span class="p">{</span>
          <span class="ss">players:</span> <span class="p">%{</span><span class="n">player_id</span><span class="p">()</span> <span class="o">=&gt;</span> <span class="no">Spaceship</span><span class="o">.</span><span class="n">t</span><span class="p">()},</span>
          <span class="ss">power_ups:</span> <span class="p">[</span><span class="n">power_up</span><span class="p">()],</span>
          <span class="ss">status:</span> <span class="n">status</span><span class="p">()</span>
        <span class="p">}</span>

  <span class="k">defstruct</span> <span class="p">[</span><span class="ss">:players</span><span class="p">,</span> <span class="ss">:power_ups</span><span class="p">,</span> <span class="ss">:status</span><span class="p">]</span>

  <span class="nv">@screen_width</span> <span class="mi">800</span>
  <span class="nv">@screen_height</span> <span class="mi">600</span>

  <span class="k">def</span> <span class="n">new</span> <span class="k">do</span>
    <span class="p">%</span><span class="bp">__MODULE__</span><span class="p">{</span>
      <span class="ss">power_ups:</span> <span class="p">[],</span>
      <span class="ss">status:</span> <span class="ss">:closed</span><span class="p">,</span>
      <span class="ss">players:</span> <span class="p">%{}</span>
    <span class="p">}</span>
  <span class="k">end</span>

  <span class="nv">@spec</span> <span class="n">add_player</span><span class="p">(</span><span class="n">t</span><span class="p">(),</span> <span class="n">player_id</span><span class="p">())</span> <span class="p">::</span> <span class="p">{</span><span class="ss">:ok</span><span class="p">,</span> <span class="n">t</span><span class="p">()}</span> <span class="o">|</span> <span class="p">{</span><span class="ss">:error</span><span class="p">,</span> <span class="ss">:dismissed_ship</span><span class="p">}</span>
  <span class="k">def</span> <span class="n">add_player</span><span class="p">(</span><span class="n">game_state</span><span class="p">,</span> <span class="n">player_id</span><span class="p">)</span> <span class="k">do</span>
    <span class="k">case</span> <span class="no">Map</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="n">game_state</span><span class="o">.</span><span class="n">players</span><span class="p">,</span> <span class="n">player_id</span><span class="p">)</span> <span class="k">do</span>
      <span class="no">nil</span> <span class="o">-&gt;</span>
        <span class="n">players</span> <span class="o">=</span>
          <span class="no">Map</span><span class="o">.</span><span class="n">put</span><span class="p">(</span>
            <span class="n">game_state</span><span class="o">.</span><span class="n">players</span><span class="p">,</span>
            <span class="n">player_id</span><span class="p">,</span>
            <span class="no">DefaultSpaceship</span><span class="o">.</span><span class="n">spawn</span><span class="p">(</span><span class="nv">@screen_width</span><span class="p">,</span> <span class="nv">@screen_height</span><span class="p">)</span>
          <span class="p">)</span>

        <span class="p">{</span><span class="ss">:ok</span><span class="p">,</span> <span class="p">%{</span><span class="n">game_state</span> <span class="o">|</span> <span class="ss">players:</span> <span class="n">players</span><span class="p">}}</span>

      <span class="p">%{</span><span class="ss">alive?:</span> <span class="no">true</span><span class="p">}</span> <span class="o">-&gt;</span>
        <span class="p">{</span><span class="ss">:ok</span><span class="p">,</span> <span class="n">game_state</span><span class="p">}</span>

      <span class="n">_spaceship</span> <span class="o">-&gt;</span>
        <span class="p">{</span><span class="ss">:error</span><span class="p">,</span> <span class="ss">:dismissed_ship</span><span class="p">}</span>
    <span class="k">end</span>
  <span class="k">end</span>

  <span class="nv">@spec</span> <span class="n">drop_player</span><span class="p">(</span><span class="n">t</span><span class="p">(),</span> <span class="n">player_id</span><span class="p">())</span> <span class="p">::</span> <span class="n">t</span><span class="p">()</span>
  <span class="k">def</span> <span class="n">drop_player</span><span class="p">(</span><span class="n">game_state</span><span class="p">,</span> <span class="n">player_id</span><span class="p">)</span> <span class="k">do</span>
    <span class="no">Map</span><span class="o">.</span><span class="n">update!</span><span class="p">(</span><span class="n">game_state</span><span class="p">,</span> <span class="ss">:players</span><span class="p">,</span> <span class="k">fn</span> <span class="n">players</span> <span class="o">-&gt;</span>
      <span class="no">Map</span><span class="o">.</span><span class="n">delete</span><span class="p">(</span><span class="n">players</span><span class="p">,</span> <span class="n">player_id</span><span class="p">)</span>
    <span class="k">end</span><span class="p">)</span>
  <span class="k">end</span>

  <span class="nv">@spec</span> <span class="n">update</span><span class="p">(</span><span class="n">t</span><span class="p">())</span> <span class="p">::</span> <span class="n">t</span><span class="p">()</span>
  <span class="k">def</span> <span class="n">update</span><span class="p">(</span><span class="n">game_state</span><span class="p">)</span> <span class="k">do</span>
    <span class="p">%{</span>
      <span class="n">game_state</span>
      <span class="o">|</span> <span class="ss">players:</span>
          <span class="no">Map</span><span class="o">.</span><span class="n">new</span><span class="p">(</span><span class="n">game_state</span><span class="o">.</span><span class="n">players</span><span class="p">,</span> <span class="k">fn</span> <span class="p">{</span><span class="n">player_id</span><span class="p">,</span> <span class="n">spaceship</span><span class="p">}</span> <span class="o">-&gt;</span>
            <span class="p">{</span><span class="n">player_id</span><span class="p">,</span> <span class="no">DefaultSpaceship</span><span class="o">.</span><span class="n">update_bullets</span><span class="p">(</span><span class="n">spaceship</span><span class="p">)}</span>
          <span class="k">end</span><span class="p">)</span>
    <span class="p">}</span>
  <span class="k">end</span>

  <span class="k">defp</span> <span class="n">fetch_spaceship</span><span class="p">(</span><span class="n">players_map</span><span class="p">,</span> <span class="n">player_id</span><span class="p">)</span> <span class="k">do</span>
    <span class="k">case</span> <span class="no">Map</span><span class="o">.</span><span class="n">fetch</span><span class="p">(</span><span class="n">players_map</span><span class="p">,</span> <span class="n">player_id</span><span class="p">)</span> <span class="k">do</span>
      <span class="ss">:error</span> <span class="o">-&gt;</span> <span class="p">{</span><span class="ss">:error</span><span class="p">,</span> <span class="ss">:dismissed_ship</span><span class="p">}</span>
      <span class="p">%{</span><span class="ss">alive?:</span> <span class="no">false</span><span class="p">}</span> <span class="o">-&gt;</span> <span class="p">{</span><span class="ss">:error</span><span class="p">,</span> <span class="ss">:dismissed_ship</span><span class="p">}</span>
      <span class="p">{</span><span class="ss">:ok</span><span class="p">,</span> <span class="n">_spaceship</span><span class="p">}</span> <span class="o">=</span> <span class="n">ok</span> <span class="o">-&gt;</span> <span class="n">ok</span>
    <span class="k">end</span>
  <span class="k">end</span>

  <span class="nv">@spec</span> <span class="n">apply_event</span><span class="p">(</span><span class="n">t</span><span class="p">(),</span> <span class="no">Event</span><span class="o">.</span><span class="n">t</span><span class="p">())</span> <span class="p">::</span> <span class="p">{</span><span class="ss">:ok</span><span class="p">,</span> <span class="n">t</span><span class="p">()}</span> <span class="o">|</span> <span class="p">{</span><span class="ss">:error</span><span class="p">,</span> <span class="ss">:dismissed_ship</span><span class="p">}</span>
  <span class="k">def</span> <span class="n">apply_event</span><span class="p">(</span><span class="n">game_state</span><span class="p">,</span> <span class="p">{</span><span class="ss">:move</span><span class="p">,</span> <span class="p">{</span><span class="n">player_id</span><span class="p">,</span> <span class="n">direction</span><span class="p">}})</span> <span class="k">do</span>
    <span class="n">with</span> <span class="p">{</span><span class="ss">:ok</span><span class="p">,</span> <span class="n">spaceship</span><span class="p">}</span> <span class="o">&lt;-</span> <span class="n">fetch_spaceship</span><span class="p">(</span><span class="n">game_state</span><span class="o">.</span><span class="n">players</span><span class="p">,</span> <span class="n">player_id</span><span class="p">)</span> <span class="k">do</span>
      <span class="p">{</span><span class="ss">:ok</span><span class="p">,</span>
       <span class="p">%{</span>
         <span class="n">game_state</span>
         <span class="o">|</span> <span class="ss">players:</span>
             <span class="no">Map</span><span class="o">.</span><span class="n">put</span><span class="p">(</span><span class="n">game_state</span><span class="o">.</span><span class="n">players</span><span class="p">,</span> <span class="n">player_id</span><span class="p">,</span> <span class="no">DefaultSpaceship</span><span class="o">.</span><span class="n">move</span><span class="p">(</span><span class="n">spaceship</span><span class="p">,</span> <span class="n">direction</span><span class="p">))</span>
       <span class="p">}}</span>
    <span class="k">end</span>
  <span class="k">end</span>

  <span class="k">def</span> <span class="n">apply_event</span><span class="p">(</span><span class="n">game_state</span><span class="p">,</span> <span class="p">{</span><span class="ss">:shoot</span><span class="p">,</span> <span class="n">player_id</span><span class="p">})</span> <span class="k">do</span>
    <span class="n">with</span> <span class="p">{</span><span class="ss">:ok</span><span class="p">,</span> <span class="n">spaceship</span><span class="p">}</span> <span class="o">&lt;-</span> <span class="n">fetch_spaceship</span><span class="p">(</span><span class="n">game_state</span><span class="o">.</span><span class="n">players</span><span class="p">,</span> <span class="n">player_id</span><span class="p">)</span> <span class="k">do</span>
      <span class="p">{</span><span class="ss">:ok</span><span class="p">,</span>
       <span class="p">%{</span>
         <span class="n">game_state</span>
         <span class="o">|</span> <span class="ss">players:</span> <span class="no">Map</span><span class="o">.</span><span class="n">put</span><span class="p">(</span><span class="n">game_state</span><span class="o">.</span><span class="n">players</span><span class="p">,</span> <span class="n">player_id</span><span class="p">,</span> <span class="no">DefaultSpaceship</span><span class="o">.</span><span class="n">shoot</span><span class="p">(</span><span class="n">spaceship</span><span class="p">))</span>
       <span class="p">}}</span>
    <span class="k">end</span>
  <span class="k">end</span>

  <span class="k">def</span> <span class="n">apply_event</span><span class="p">(</span><span class="n">game_state</span><span class="p">,</span> <span class="p">{</span><span class="ss">:spawn_power_up</span><span class="p">,</span> <span class="n">power_up_kind</span><span class="p">})</span> <span class="k">do</span>
    <span class="n">power_up</span> <span class="o">=</span> <span class="p">%{</span><span class="ss">position:</span> <span class="no">Vec2</span><span class="o">.</span><span class="n">random</span><span class="p">(</span><span class="nv">@screen_width</span><span class="p">,</span> <span class="nv">@screen_height</span><span class="p">),</span> <span class="ss">kind:</span> <span class="n">power_up_kind</span><span class="p">}</span>

    <span class="p">{</span><span class="ss">:ok</span><span class="p">,</span>
     <span class="p">%{</span>
       <span class="n">game_state</span>
       <span class="o">|</span> <span class="ss">power_ups:</span> <span class="p">[</span><span class="n">power_up</span> <span class="o">|</span> <span class="n">game_state</span><span class="o">.</span><span class="n">power_ups</span><span class="p">]</span>
     <span class="p">}}</span>
  <span class="k">end</span>

  <span class="k">def</span> <span class="n">apply_event</span><span class="p">(</span><span class="n">_game_state</span><span class="p">,</span> <span class="n">_event</span><span class="p">),</span> <span class="k">do</span><span class="p">:</span> <span class="k">raise</span><span class="p">(</span><span class="s2">"Not implemented"</span><span class="p">)</span>

  <span class="k">def</span> <span class="n">idle</span><span class="p">,</span> <span class="k">do</span><span class="p">:</span> <span class="ss">:idle</span>
  <span class="k">def</span> <span class="n">move_up</span><span class="p">,</span> <span class="k">do</span><span class="p">:</span> <span class="ss">:up</span>
  <span class="k">def</span> <span class="n">move_down</span><span class="p">,</span> <span class="k">do</span><span class="p">:</span> <span class="ss">:down</span>
  <span class="k">def</span> <span class="n">move_left</span><span class="p">,</span> <span class="k">do</span><span class="p">:</span> <span class="ss">:left</span>
  <span class="k">def</span> <span class="n">move_right</span><span class="p">,</span> <span class="k">do</span><span class="p">:</span> <span class="ss">:right</span>
  <span class="k">def</span> <span class="n">shoot</span><span class="p">,</span> <span class="k">do</span><span class="p">:</span> <span class="ss">:shoot</span>
<span class="k">end</span>
</code></pre></div></div>

<p>A small set of functions to manage a basic game state:</p>

<ul>
  <li><code class="language-plaintext highlighter-rouge">new/0</code> just creates an empty, or base state</li>
  <li><code class="language-plaintext highlighter-rouge">add_player/2</code> called by the <code class="language-plaintext highlighter-rouge">EventHandler</code>, it’s triggered on connect when a new
player connects</li>
  <li><code class="language-plaintext highlighter-rouge">apply_event/2</code> called by the <code class="language-plaintext highlighter-rouge">EventHandler</code> when any event coming from players
or scheduled on a time-basis</li>
  <li><code class="language-plaintext highlighter-rouge">update/1</code> this is a kind of engine for the state, it’s called on a time-basis
by the <code class="language-plaintext highlighter-rouge">EventHandler</code> and it allows the state to naturally evolve based on
it’s current configuration; for example, if there are 3 bullets active across
2 players that fired, this function will update their position according to their
speed (and possibly acceleration/deceleration later on, if we feel like adding that
logic too)</li>
</ul>

<p>We will need to update the main entry point of the program, the <code class="language-plaintext highlighter-rouge">Application</code> to
correctly start a supervision tree including the <code class="language-plaintext highlighter-rouge">Server</code> and the <code class="language-plaintext highlighter-rouge">EventHandler</code>.</p>

<div class="language-elixir highlighter-rouge"><div class="highlight"><pre class="highlight"><code>
<span class="k">defmodule</span> <span class="no">Dogfight</span><span class="o">.</span><span class="no">Application</span> <span class="k">do</span>
  <span class="nv">@moduledoc</span> <span class="sd">"""
  Documentation for `Dogfight`.
  Server component for the Dogfight battle arena game, developed to have
  some fun with game dev and soft-real time distributed systems.
  """</span>

  <span class="kn">use</span> <span class="no">Application</span>

  <span class="k">def</span> <span class="n">start</span><span class="p">(</span><span class="n">_type</span><span class="p">,</span> <span class="n">_args</span><span class="p">)</span> <span class="k">do</span>
    <span class="n">port</span> <span class="o">=</span> <span class="no">String</span><span class="o">.</span><span class="n">to_integer</span><span class="p">(</span><span class="no">System</span><span class="o">.</span><span class="n">get_env</span><span class="p">(</span><span class="s2">"DOGFIGHT_TCP_PORT"</span><span class="p">)</span> <span class="o">||</span> <span class="s2">"6699"</span><span class="p">)</span>

    <span class="n">children</span> <span class="o">=</span> <span class="p">[</span>
      <span class="p">{</span><span class="no">Cluster</span><span class="o">.</span><span class="no">Supervisor</span><span class="p">,</span> <span class="p">[</span><span class="n">topologies</span><span class="p">(),</span> <span class="p">[</span><span class="ss">name:</span> <span class="no">Dogfight</span><span class="o">.</span><span class="no">ClusterSupervisor</span><span class="p">]]},</span>
      <span class="p">{</span>
        <span class="no">Horde</span><span class="o">.</span><span class="no">Registry</span><span class="p">,</span>
        <span class="ss">name:</span> <span class="no">Dogfight</span><span class="o">.</span><span class="no">ClusterRegistry</span><span class="p">,</span> <span class="ss">keys:</span> <span class="ss">:unique</span><span class="p">,</span> <span class="ss">members:</span> <span class="ss">:auto</span>
      <span class="p">},</span>
      <span class="p">{</span>
        <span class="no">Horde</span><span class="o">.</span><span class="no">DynamicSupervisor</span><span class="p">,</span>
        <span class="ss">name:</span> <span class="no">Dogfight</span><span class="o">.</span><span class="no">ClusterServiceSupervisor</span><span class="p">,</span> <span class="ss">strategy:</span> <span class="ss">:one_for_one</span><span class="p">,</span> <span class="ss">members:</span> <span class="ss">:auto</span>
      <span class="p">},</span>
      <span class="no">Dogfight</span><span class="o">.</span><span class="no">Game</span><span class="o">.</span><span class="no">EventHandler</span><span class="p">,</span>
      <span class="no">Supervisor</span><span class="o">.</span><span class="n">child_spec</span><span class="p">({</span><span class="no">Task</span><span class="p">,</span> <span class="k">fn</span> <span class="o">-&gt;</span> <span class="no">Dogfight</span><span class="o">.</span><span class="no">Server</span><span class="o">.</span><span class="n">listen</span><span class="p">(</span><span class="n">port</span><span class="p">)</span> <span class="k">end</span><span class="p">},</span> <span class="ss">restart:</span> <span class="ss">:permanent</span><span class="p">)</span>
    <span class="p">]</span>

    <span class="n">opts</span> <span class="o">=</span> <span class="p">[</span><span class="ss">strategy:</span> <span class="ss">:one_for_one</span><span class="p">,</span> <span class="ss">name:</span> <span class="no">Dogfight</span><span class="o">.</span><span class="no">Supervisor</span><span class="p">]</span>
    <span class="no">Supervisor</span><span class="o">.</span><span class="n">start_link</span><span class="p">(</span><span class="n">children</span><span class="p">,</span> <span class="n">opts</span><span class="p">)</span>
  <span class="k">end</span>

  <span class="c1"># Can also read this from conf files, but to keep it simple just hardcode it for now.</span>
  <span class="c1"># It is also possible to use different strategies for autodiscovery.</span>
  <span class="c1"># Following strategy works best for docker setup we using for this app.</span>
  <span class="k">defp</span> <span class="n">topologies</span> <span class="k">do</span>
    <span class="p">[</span>
      <span class="ss">game_state_nodes:</span> <span class="p">[</span>
        <span class="ss">strategy:</span> <span class="no">Cluster</span><span class="o">.</span><span class="no">Strategy</span><span class="o">.</span><span class="no">Epmd</span><span class="p">,</span>
        <span class="ss">config:</span> <span class="p">[</span>
          <span class="ss">hosts:</span> <span class="p">[</span><span class="ss">:"app@n1.dev"</span><span class="p">,</span> <span class="ss">:"app@n2.dev"</span><span class="p">,</span> <span class="ss">:"app@n3.dev"</span><span class="p">]</span>
        <span class="p">]</span>
      <span class="p">]</span>
    <span class="p">]</span>
  <span class="k">end</span>
<span class="k">end</span>
</code></pre></div></div>
<p>Code can be found in the <a href="https://github.com/codepr/dogfight/tree/main/server">GitHub repository</a>.</p>

<h4 id="references">References</h4>

<ul>
  <li><a href="https://www.erlang.org/blog/a-brief-beam-primer/">BEAM</a></li>
  <li><a href="https://hexdocs.pm/elixir/GenServer.html">GenServer</a></li>
  <li><a href="https://hexdocs.pm/horde/getting_started.html">horde</a></li>
  <li><a href="https://hexdocs.pm/libcluster/readme.html">libcluster</a></li>
</ul>]]></content><author><name></name></author><category term="elixir" /><category term="networking" /><category term="c" /><category term="game-development" /><category term="low-level" /><summary type="html"><![CDATA[Old school multiplayer arcade game development journey]]></summary></entry><entry><title type="html">Dogfight!</title><link href="/posts/dogfight/" rel="alternate" type="text/html" title="Dogfight!" /><published>2024-12-29T00:00:00+00:00</published><updated>2024-12-29T00:00:00+00:00</updated><id>/posts/dogfight</id><content type="html" xml:base="/posts/dogfight/"><![CDATA[<p>A couple of weeks ago, inspired by an as gripping as nerdy tv series (check out
<a href="https://en.wikipedia.org/wiki/Halt_and_Catch_Fire_(TV_series)">Halt and Catch
Fire</a> 🔥), I
explored a little idea, a small, self-contained low level project to build an
old arcade game - battletank arena - with a little kink in the form of a
centralized TCP server to handle multiplayer through the network.</p>

<p>An important note, I’m <strong>not</strong> a game developer, and don’t have any significant
experience on it, best practices and patterns, the code and design in general
will likely be subpar to say the least, the aim is just having fun while
learning somthing.</p>

<p>The rules were simple</p>

<ul>
  <li>Keep it small and simple to keep it fun</li>
  <li>Use the least amount of external dependencies as possible</li>
</ul>

<p>I think I somewhat respected them as much as possible, not pedantically but in a
good measure. During the development I</p>

<ul>
  <li>Developed a first simpler version using <code class="language-plaintext highlighter-rouge">ncurses</code> to handle the “GUI”</li>
  <li>Implemented some very dumb synchronization logic to keep FPS consistent between players</li>
  <li>An even dumber binary protocol to communicate, game state being the only payload exchanged</li>
  <li>A micro set of features (bullets, power ups, life points etc)</li>
</ul>

<p>I then moved on and got intrigued by the possibility of extending the client side of the
game with a little more ambitious graphical layer, using <code class="language-plaintext highlighter-rouge">raylib</code>. The library is pretty
solid, small but powerful; I still kept most of the core feature-set as simple as possible,
in no particular order:</p>

<ul>
  <li>Select server based with non-blocking sockets, no async libraries such as <code class="language-plaintext highlighter-rouge">UV</code> and the like</li>
  <li>The binary protocol exchanges the entire game state, while it’s fine for the size is pretty
contained, it would’ve been simple to evolve it to send delta updates</li>
  <li>All the controls are keyboard based, no oblique movements, acceleration</li>
  <li>No scaling/proper handling of the screen size of each client (nasty bugs are totally ignored)</li>
  <li>No optimizations of sort, not lag compensation, path prediction and other
painful network management issues</li>
  <li>No sprite animations, bullet explosions and so on</li>
</ul>

<p>All of the above are “easy” upgrades that could be tackled, what caught my
curiosity is, on paper, this kind of games are mostly heavily I/O based, the
heavy work is made on client side to render something eye-pleasing essentially,
how well would it work an highly scalable TCP server in another language
instead of C to manage the multiplayer stack, e.g. games, rooms, chats possibly
and so on. So I decided to give it a go in Elixir and port my
<code class="language-plaintext highlighter-rouge">battletank-server</code> to an elixir based implementation, which would make it
painless to distribute in a cluster and happily use all the resources of all
the nodes.</p>

<h2 id="goals">Goals</h2>

<p>Once again, simplicity and a small codebase are paramount here, the project
aims to be a variations on the previous iteration, hopefully with some
additions enabled by how convenient <code class="language-plaintext highlighter-rouge">elixir</code> is for network-based soft real-time
applications. There is no expectations or quality goals, it is intended to be
an exploratory and fun work.</p>

<p>Moreover, the development will be divided in the server side and the client
side, leveraging most of the work previously done.</p>

<h3 id="server-side">Server side</h3>

<ul>
  <li>An <code class="language-plaintext highlighter-rouge">elixir</code> based TCP server, probably using the builtin <code class="language-plaintext highlighter-rouge">:gen_tcp</code> from <code class="language-plaintext highlighter-rouge">erlang</code></li>
  <li>Binary encoding/decoding of the game state, subsequently add delta updates as suggested above</li>
  <li>Distributed by default, probably throwing <code class="language-plaintext highlighter-rouge">libcluster</code> and <code class="language-plaintext highlighter-rouge">horde</code> to the mix to make it smoother</li>
  <li>Game lobbies, chats, maybe a leaderboard</li>
</ul>

<h3 id="client-side">Client side</h3>

<ul>
  <li>Adding support to delta encoding/decoding of the game state</li>
  <li>Explore further <code class="language-plaintext highlighter-rouge">raylib</code>, nicer sprites and possibly animations</li>
  <li>A menu or something to enable a game lobby or hosting a game</li>
  <li>In-game chat support</li>
  <li>Possibly written in <code class="language-plaintext highlighter-rouge">Zig</code>. Although I can already leverage a C implementation
it may be a good opportunity to explore a new language, plus Zig has full ABI
compatibility with C and a nice <a href="https://github.com/ryupold/raylib.zig">Raylib</a> port</li>
</ul>

<p>I reckon all of the above, although it doesn’t look like that much, is pretty
ambitious and will take some time to be achieved. There will be bugs.</p>

<h2 id="roadmap">Roadmap</h2>

<p>Coding will happen during free-time and will not necessarily be consistent, I will try to journal
as much as possible, but the high-level roadmap is expected to be something like</p>

<ul>
  <li>Server - basic TCP server setup</li>
  <li>Server - add support for the binary protocol used to communicate in <code class="language-plaintext highlighter-rouge">battletank</code></li>
  <li>Server - add support for game instantiation and distribution across multiple nodes</li>
  <li>Client - some graphical improvements</li>
  <li>Both - delta encoding/decoding of the game state</li>
  <li>Both - extending the protocol to control pre-game logic (lobbies, game hosting etc)</li>
  <li>Both - cherries on top, refinements</li>
</ul>

<p>I’ll try to stick to it by documenting this little journey in short articles,
with the following currently available in various stage of development (some are
to be considered stretch goals)</p>

<ul>
  <li><a href="https://codepr.github.io/posts/dogfight-server-elixir/">Dogfight! - Elixir server</a></li>
  <li><a href="https://codepr.github.io/posts/dogfight-binary-protocol/">Dogfight! - Binary protocol</a></li>
  <li><a href="https://codepr.github.io/posts/dogfight-client-mvp/">Dogfight! - Client MVP</a></li>
  <li><em>Dogfight! - Zig, checking Raylib.zig</em></li>
  <li><em>Dogfight! - Elixir and Zig, game state revamp</em></li>
  <li><em>Dogfight! - Elixir and Zig, delta state encoding</em></li>
  <li><em>Dogfight! - Elixir and Zig, hosting a game</em></li>
  <li><em>Dogfight! - Elixir and Zig, in game chat</em></li>
</ul>

<h4 id="references">References</h4>

<ul>
  <li><a href="https://elixir-lang.org/">elixir</a></li>
  <li><a href="https://invisible-island.net/ncurses/">ncurses</a></li>
  <li><a href="https://www.raylib.com/index.html">Raylib</a></li>
  <li><a href="https://man7.org/linux/man-pages/man2/select.2.html">select</a></li>
</ul>]]></content><author><name></name></author><category term="elixir" /><category term="networking" /><category term="c" /><category term="game-development" /><category term="low-level" /><summary type="html"><![CDATA[Old school multiplayer arcade game development journey]]></summary></entry><entry><title type="html">Battletank!</title><link href="/posts/battletank/" rel="alternate" type="text/html" title="Battletank!" /><published>2024-09-28T00:00:00+00:00</published><updated>2024-09-28T00:00:00+00:00</updated><id>/posts/battletank</id><content type="html" xml:base="/posts/battletank/"><![CDATA[<p>Small project idea to have some fun with C, a micro multiplayer classic game
with as less dependencies as possible. I’ve always been curious about the
challenges of game development so I started from one of the simplest ideas. The
latest update of the code can be found in the repository
<a href="https://github.com/codepr/battletank.git">codepr/battletank.git</a>.</p>

<h2 id="the-game">The game</h2>

<p>It’s about a best effort terminal based implementation of the classic
battle-tank, starting from a single player single tank and extending it to work
as a multiplayer server with sockets to sync the game state across players.
To begin with, the only dependency is <code class="language-plaintext highlighter-rouge">ncurses</code>, later on, if I get confident enough,
I will consider something fancier such as <a href="https://www.raylib.com/index.html"><code class="language-plaintext highlighter-rouge">Raylib</code></a>.</p>

<h3 id="why">Why</h3>

<p>To have some fun, small old school programs are fun to mess with. In addition,
although I worked for a brief stint for an AAA gaming company, I was mainly
developing on the back-end side of the product, pre-game lobbies, chat rooms and
game server deployments; but didn’t really know much about the inner logic of
the game itself. This was a good opportunity to try and learn something about
game development starting from the basics.</p>

<h3 id="ideas">Ideas</h3>

<p>In no particular order, and not necessarily mandatory:</p>
<ul>
  <li>Implement a very simple and stripped down game logic ✅
    <ul>
      <li>1 player, 1 tank, 1 bullet ✅</li>
      <li>Handle keyboard input ✅</li>
      <li>Design a better structured game state, multiple tanks, each tank loaded with bullets</li>
    </ul>
  </li>
  <li>Multiplayer through TCP sockets
    <ul>
      <li>Implement a TCP server ✅</li>
      <li>Implement a TCP client ✅</li>
      <li>Implement a network protocol (text based or binary) ✅</li>
      <li>The clients will send their input, server handle the game state and broadcasts it to the connected clients ✅</li>
      <li>Heartbeat server side, drop inactive clients</li>
      <li>Small chat? Maybe integrate <a href="https://github.com/codepr/chatlite.git">chatlite</a></li>
      <li>Ensure screen size scaling is maintained in sync with players</li>
    </ul>
  </li>
  <li>Walls</li>
  <li>Life points</li>
  <li>Bullets count</li>
  <li>Recharge bullets on a time basis</li>
  <li>Power ups (faster bullets? Larger hit box? Mines?)</li>
  <li>Explore SDL2 or Raylib for some graphic or sprites</li>
</ul>

<h2 id="main-challenges">Main challenges</h2>

<p>The game is pretty simple, the logic is absolutely elementary, just x, y axis
as direction for both the tank and the bullets. The main challenges are
represented by keeping players in sync and ensure that the battlefield size is
correctly scaled for each one of them (not touched this yet).</p>

<p>For the communication part I chose the simplest and ubiquitous solution, a
non-blocking server on <code class="language-plaintext highlighter-rouge">select</code> (yikes, maybe at least <code class="language-plaintext highlighter-rouge">poll</code>) and a timeout on
read client, this way the main loops are not blocked indefinitely and the game
state can flow. There are certainly infinitely better ways to do it, but
avoiding threading and excessive engineered solutions was part of the scope.
There is not plan to make anything more than have some fun out of this project,
one simple upgrade would be to migrate the server implementation to <code class="language-plaintext highlighter-rouge">epoll</code> or
<code class="language-plaintext highlighter-rouge">kqueue</code>, however it’s definitely not a game to be expected to have a
sufficiently high number of players to prove problematic to handle for the good
old <code class="language-plaintext highlighter-rouge">select</code> call.</p>

<p>Something I find more interesting is delving a little more into graphics, I
literally don’t know anything about it but I can see libraries such as <code class="language-plaintext highlighter-rouge">Raylib</code>
seem to make it simple enough to generate some basic animations and sprites,
the program is sufficiently simple to try and plug it in, hopefully without too
much troubles.</p>

<h2 id="implementation">Implementation</h2>

<p>I won’t go deep in details, some parts are to be considered boilerplate, such as
serialization, TCP helpers and such. To get into details of those parts, the
repository is <a href="https://github.com/codepr/battletank.git">codepr/battletank.git</a>.</p>

<p>The game can be divided in the main modules:</p>
<ul>
  <li>The <strong>game state</strong>
    <ul>
      <li>Tank</li>
      <li>Bullet</li>
    </ul>
  </li>
  <li>A <strong>game server</strong>, handles  the game state and serves as the unique authoritative source of truth.</li>
  <li>A <strong>game client</strong>, connects to the server, provides a very crude terminal based
graphic battlefield and handles input from the player.</li>
  <li>A <strong>protocol</strong> to communicate. Initially I went for a text-based protocol, but
I’m not very fond of them, so I decided for a binary one eventually, a very
simple one.</li>
</ul>

<h3 id="the-game-state">The game state</h3>

<p>The game state is the most simple I could imagine to begin with</p>
<ul>
  <li>An array of tanks</li>
  <li>Each tank has 1 bullet</li>
</ul>

<p>Short and sweet, to keep in sync, the server is only required to update the
coordinates of each tank and bullet and send them to the clients. This
structure is probably where additional improvements mentioned in the intro
paragraph could live, power ups, walls, bullets and their kinds, mines etc.</p>

<hr />

<p><strong>game_state.h</strong></p>
<hr />

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Possible directions a tank or bullet can move.</span>
<span class="k">typedef</span> <span class="k">enum</span> <span class="p">{</span> <span class="n">IDLE</span><span class="p">,</span> <span class="n">UP</span><span class="p">,</span> <span class="n">DOWN</span><span class="p">,</span> <span class="n">LEFT</span><span class="p">,</span> <span class="n">RIGHT</span> <span class="p">}</span> <span class="n">Direction</span><span class="p">;</span>

<span class="c1">// Only fire for now, can add something else such as</span>
<span class="c1">// DROP_MINE etc.</span>
<span class="k">typedef</span> <span class="k">enum</span> <span class="p">{</span>
    <span class="n">FIRE</span> <span class="o">=</span> <span class="mi">5</span><span class="p">,</span>
<span class="p">}</span> <span class="n">Action</span><span class="p">;</span>

<span class="c1">// Represents a bullet with its position, direction, and status.</span>
<span class="c1">// Can include bullet kinds as a possible update for the future.</span>
<span class="k">typedef</span> <span class="k">struct</span> <span class="p">{</span>
    <span class="kt">int</span> <span class="n">x</span><span class="p">;</span>
    <span class="kt">int</span> <span class="n">y</span><span class="p">;</span>
    <span class="n">Direction</span> <span class="n">direction</span><span class="p">;</span>
    <span class="n">bool</span> <span class="n">active</span><span class="p">;</span>
<span class="p">}</span> <span class="n">Bullet</span><span class="p">;</span>

<span class="c1">// Represents a tank with its position, direction, and status.</span>
<span class="c1">// Contains a single bullet for simplicity, can be extended in</span>
<span class="c1">// the future to handle multiple bullets, life points, power-ups etc.</span>
<span class="k">typedef</span> <span class="k">struct</span> <span class="p">{</span>
    <span class="kt">int</span> <span class="n">x</span><span class="p">;</span>
    <span class="kt">int</span> <span class="n">y</span><span class="p">;</span>
    <span class="n">Direction</span> <span class="n">direction</span><span class="p">;</span>
    <span class="n">bool</span> <span class="n">alive</span><span class="p">;</span>
    <span class="n">Bullet</span> <span class="n">bullet</span><span class="p">;</span>
<span class="p">}</span> <span class="n">Tank</span><span class="p">;</span>

<span class="k">typedef</span> <span class="k">struct</span> <span class="p">{</span>
    <span class="n">Tank</span> <span class="o">*</span><span class="n">players</span><span class="p">;</span>
    <span class="kt">size_t</span> <span class="n">players_count</span><span class="p">;</span>
    <span class="kt">size_t</span> <span class="n">player_index</span><span class="p">;</span>
<span class="p">}</span> <span class="n">Game_State</span><span class="p">;</span>

<span class="c1">// General game state managing</span>
<span class="kt">void</span> <span class="nf">game_state_init</span><span class="p">(</span><span class="n">Game_State</span> <span class="o">*</span><span class="n">state</span><span class="p">);</span>
<span class="kt">void</span> <span class="nf">game_state_free</span><span class="p">(</span><span class="n">Game_State</span> <span class="o">*</span><span class="n">state</span><span class="p">);</span>
<span class="kt">void</span> <span class="nf">game_state_update</span><span class="p">(</span><span class="n">Game_State</span> <span class="o">*</span><span class="n">state</span><span class="p">);</span>

<span class="c1">// Tank management</span>
<span class="kt">void</span> <span class="nf">game_state_spawn_tank</span><span class="p">(</span><span class="n">Game_State</span> <span class="o">*</span><span class="n">state</span><span class="p">,</span> <span class="kt">size_t</span> <span class="n">index</span><span class="p">);</span>
<span class="kt">void</span> <span class="nf">game_state_dismiss_tank</span><span class="p">(</span><span class="n">Game_State</span> <span class="o">*</span><span class="n">state</span><span class="p">,</span> <span class="kt">size_t</span> <span class="n">index</span><span class="p">);</span>
<span class="kt">void</span> <span class="nf">game_state_update_tank</span><span class="p">(</span><span class="n">Game_State</span> <span class="o">*</span><span class="n">state</span><span class="p">,</span> <span class="kt">size_t</span> <span class="n">tank_index</span><span class="p">,</span>
                            <span class="kt">unsigned</span> <span class="n">action</span><span class="p">);</span>
</code></pre></div></div>
<hr />

<h4 id="tanks-and-bullets">Tanks and bullets</h4>

<p>Although the structures introduced are trivial, some helper functions to manage
tanks and bullets can come handy; when the server starts, the first thing will
be to init a global game state. When a new player connects, a tank will be
spawned in the battlefield, I opted for a random position spawning in a small
set of coordinates. In hindsight, I could’ve easily set a fixed number of
players such as 10, I went for a dynamic array on auto-pilot basically. To be
noted that as of now I’m not really correctly freeing the allocated tanks
(these are the only structure that is heap allocated) as it’s not really
necessary, the memory will be released at shutdown of the program anyway and
the number of expected players is not that big. That said, it’s definitely best
practice to handle the case correctly, I may address that at a later stage.</p>

<hr />

<p><strong>game_state.c</strong></p>
<hr />

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">void</span> <span class="nf">game_state_init</span><span class="p">(</span><span class="n">Game_State</span> <span class="o">*</span><span class="n">state</span><span class="p">)</span> <span class="p">{</span>
    <span class="n">state</span><span class="o">-&gt;</span><span class="n">players_count</span> <span class="o">=</span> <span class="mi">2</span><span class="p">;</span>
    <span class="n">state</span><span class="o">-&gt;</span><span class="n">players</span> <span class="o">=</span> <span class="n">calloc</span><span class="p">(</span><span class="mi">2</span><span class="p">,</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">Tank</span><span class="p">));</span>
    <span class="k">for</span> <span class="p">(</span><span class="kt">size_t</span> <span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">i</span> <span class="o">&lt;</span> <span class="n">state</span><span class="o">-&gt;</span><span class="n">players_count</span><span class="p">;</span> <span class="o">++</span><span class="n">i</span><span class="p">)</span> <span class="p">{</span>
        <span class="n">state</span><span class="o">-&gt;</span><span class="n">players</span><span class="p">[</span><span class="n">i</span><span class="p">].</span><span class="n">alive</span> <span class="o">=</span> <span class="nb">false</span><span class="p">;</span>
        <span class="n">state</span><span class="o">-&gt;</span><span class="n">players</span><span class="p">[</span><span class="n">i</span><span class="p">].</span><span class="n">bullet</span><span class="p">.</span><span class="n">active</span> <span class="o">=</span> <span class="nb">false</span><span class="p">;</span>
    <span class="p">}</span>
<span class="p">}</span>

<span class="kt">void</span> <span class="nf">game_state_free</span><span class="p">(</span><span class="n">Game_State</span> <span class="o">*</span><span class="n">state</span><span class="p">)</span> <span class="p">{</span> <span class="n">free</span><span class="p">(</span><span class="n">state</span><span class="o">-&gt;</span><span class="n">players</span><span class="p">);</span> <span class="p">}</span>

<span class="kt">void</span> <span class="nf">game_state_spawn_tank</span><span class="p">(</span><span class="n">Game_State</span> <span class="o">*</span><span class="n">state</span><span class="p">,</span> <span class="kt">size_t</span> <span class="n">index</span><span class="p">)</span> <span class="p">{</span>
    <span class="c1">// Extend the players pool if we're at capacity</span>
    <span class="k">if</span> <span class="p">(</span><span class="n">index</span> <span class="o">&gt;</span> <span class="n">state</span><span class="o">-&gt;</span><span class="n">players_count</span><span class="p">)</span> <span class="p">{</span>
        <span class="n">state</span><span class="o">-&gt;</span><span class="n">players_count</span> <span class="o">*=</span> <span class="mi">2</span><span class="p">;</span>
        <span class="n">state</span><span class="o">-&gt;</span><span class="n">players</span> <span class="o">=</span> <span class="n">realloc</span><span class="p">(</span><span class="n">state</span><span class="o">-&gt;</span><span class="n">players</span><span class="p">,</span> <span class="n">state</span><span class="o">-&gt;</span><span class="n">players_count</span><span class="p">);</span>
    <span class="p">}</span>
    <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">state</span><span class="o">-&gt;</span><span class="n">players</span><span class="p">[</span><span class="n">index</span><span class="p">].</span><span class="n">alive</span><span class="p">)</span> <span class="p">{</span>
        <span class="n">state</span><span class="o">-&gt;</span><span class="n">players</span><span class="p">[</span><span class="n">index</span><span class="p">].</span><span class="n">alive</span> <span class="o">=</span> <span class="nb">true</span><span class="p">;</span>
        <span class="n">state</span><span class="o">-&gt;</span><span class="n">players</span><span class="p">[</span><span class="n">index</span><span class="p">].</span><span class="n">x</span> <span class="o">=</span> <span class="n">RANDOM</span><span class="p">(</span><span class="mi">15</span><span class="p">,</span> <span class="mi">25</span><span class="p">);</span>
        <span class="n">state</span><span class="o">-&gt;</span><span class="n">players</span><span class="p">[</span><span class="n">index</span><span class="p">].</span><span class="n">y</span> <span class="o">=</span> <span class="n">RANDOM</span><span class="p">(</span><span class="mi">15</span><span class="p">,</span> <span class="mi">25</span><span class="p">);</span>
        <span class="n">state</span><span class="o">-&gt;</span><span class="n">players</span><span class="p">[</span><span class="n">index</span><span class="p">].</span><span class="n">direction</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
    <span class="p">}</span>
<span class="p">}</span>

<span class="kt">void</span> <span class="nf">game_state_dismiss_tank</span><span class="p">(</span><span class="n">Game_State</span> <span class="o">*</span><span class="n">state</span><span class="p">,</span> <span class="kt">size_t</span> <span class="n">index</span><span class="p">)</span> <span class="p">{</span>
    <span class="n">state</span><span class="o">-&gt;</span><span class="n">players</span><span class="p">[</span><span class="n">index</span><span class="p">].</span><span class="n">alive</span> <span class="o">=</span> <span class="nb">false</span><span class="p">;</span>
<span class="p">}</span>

</code></pre></div></div>
<hr />

<p>And here to follow the remaining functions needed to actually update the state of the game,
mainly manipulation of the X, Y axis for the tank and bullet directions based on actions
coming from each client.</p>

<p>To check collision initially I just check that the coordinates of a given tank
collide with those of a given bullet. Admittedly I didn’t focus much on
that (after all there isn’t even a score logic yet), for a first test run I
was more interested into seeing actually tanks moving and be in sync with
each other through the network, but <code class="language-plaintext highlighter-rouge">check_collision</code> still provides a good
starting point to expand on later.</p>

<hr />

<p><strong>game_state.c</strong></p>
<hr />

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">static</span> <span class="kt">void</span> <span class="nf">fire_bullet</span><span class="p">(</span><span class="n">Tank</span> <span class="o">*</span><span class="n">tank</span><span class="p">)</span> <span class="p">{</span>
    <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">tank</span><span class="o">-&gt;</span><span class="n">bullet</span><span class="p">.</span><span class="n">active</span><span class="p">)</span> <span class="p">{</span>
        <span class="n">tank</span><span class="o">-&gt;</span><span class="n">bullet</span><span class="p">.</span><span class="n">active</span> <span class="o">=</span> <span class="nb">true</span><span class="p">;</span>
        <span class="n">tank</span><span class="o">-&gt;</span><span class="n">bullet</span><span class="p">.</span><span class="n">x</span> <span class="o">=</span> <span class="n">tank</span><span class="o">-&gt;</span><span class="n">x</span><span class="p">;</span>
        <span class="n">tank</span><span class="o">-&gt;</span><span class="n">bullet</span><span class="p">.</span><span class="n">y</span> <span class="o">=</span> <span class="n">tank</span><span class="o">-&gt;</span><span class="n">y</span><span class="p">;</span>
        <span class="n">tank</span><span class="o">-&gt;</span><span class="n">bullet</span><span class="p">.</span><span class="n">direction</span> <span class="o">=</span> <span class="n">tank</span><span class="o">-&gt;</span><span class="n">direction</span><span class="p">;</span>
    <span class="p">}</span>
<span class="p">}</span>

<span class="kt">void</span> <span class="nf">game_state_update_tank</span><span class="p">(</span><span class="n">Game_State</span> <span class="o">*</span><span class="n">state</span><span class="p">,</span> <span class="kt">size_t</span> <span class="n">tank_index</span><span class="p">,</span>
                            <span class="kt">unsigned</span> <span class="n">action</span><span class="p">)</span> <span class="p">{</span>
    <span class="k">switch</span> <span class="p">(</span><span class="n">action</span><span class="p">)</span> <span class="p">{</span>
        <span class="k">case</span> <span class="n">UP</span><span class="p">:</span>
            <span class="n">state</span><span class="o">-&gt;</span><span class="n">players</span><span class="p">[</span><span class="n">tank_index</span><span class="p">].</span><span class="n">y</span><span class="o">--</span><span class="p">;</span>
            <span class="n">state</span><span class="o">-&gt;</span><span class="n">players</span><span class="p">[</span><span class="n">tank_index</span><span class="p">].</span><span class="n">direction</span> <span class="o">=</span> <span class="n">UP</span><span class="p">;</span>
            <span class="k">break</span><span class="p">;</span>
        <span class="k">case</span> <span class="n">DOWN</span><span class="p">:</span>
            <span class="n">state</span><span class="o">-&gt;</span><span class="n">players</span><span class="p">[</span><span class="n">tank_index</span><span class="p">].</span><span class="n">y</span><span class="o">++</span><span class="p">;</span>
            <span class="n">state</span><span class="o">-&gt;</span><span class="n">players</span><span class="p">[</span><span class="n">tank_index</span><span class="p">].</span><span class="n">direction</span> <span class="o">=</span> <span class="n">DOWN</span><span class="p">;</span>
            <span class="k">break</span><span class="p">;</span>
        <span class="k">case</span> <span class="n">LEFT</span><span class="p">:</span>
            <span class="n">state</span><span class="o">-&gt;</span><span class="n">players</span><span class="p">[</span><span class="n">tank_index</span><span class="p">].</span><span class="n">x</span><span class="o">--</span><span class="p">;</span>
            <span class="n">state</span><span class="o">-&gt;</span><span class="n">players</span><span class="p">[</span><span class="n">tank_index</span><span class="p">].</span><span class="n">direction</span> <span class="o">=</span> <span class="n">LEFT</span><span class="p">;</span>
            <span class="k">break</span><span class="p">;</span>
        <span class="k">case</span> <span class="n">RIGHT</span><span class="p">:</span>
            <span class="n">state</span><span class="o">-&gt;</span><span class="n">players</span><span class="p">[</span><span class="n">tank_index</span><span class="p">].</span><span class="n">x</span><span class="o">++</span><span class="p">;</span>
            <span class="n">state</span><span class="o">-&gt;</span><span class="n">players</span><span class="p">[</span><span class="n">tank_index</span><span class="p">].</span><span class="n">direction</span> <span class="o">=</span> <span class="n">RIGHT</span><span class="p">;</span>
            <span class="k">break</span><span class="p">;</span>
        <span class="k">case</span> <span class="n">FIRE</span><span class="p">:</span>
            <span class="n">fire_bullet</span><span class="p">(</span><span class="o">&amp;</span><span class="n">state</span><span class="o">-&gt;</span><span class="n">players</span><span class="p">[</span><span class="n">tank_index</span><span class="p">]);</span>
            <span class="k">break</span><span class="p">;</span>
        <span class="nl">default:</span>
            <span class="k">break</span><span class="p">;</span>
    <span class="p">}</span>
<span class="p">}</span>

<span class="k">static</span> <span class="kt">void</span> <span class="nf">update_bullet</span><span class="p">(</span><span class="n">Bullet</span> <span class="o">*</span><span class="n">bullet</span><span class="p">)</span> <span class="p">{</span>
    <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">bullet</span><span class="o">-&gt;</span><span class="n">active</span><span class="p">)</span> <span class="k">return</span><span class="p">;</span>
    <span class="k">switch</span> <span class="p">(</span><span class="n">bullet</span><span class="o">-&gt;</span><span class="n">direction</span><span class="p">)</span> <span class="p">{</span>
        <span class="k">case</span> <span class="n">UP</span><span class="p">:</span>
            <span class="n">bullet</span><span class="o">-&gt;</span><span class="n">y</span><span class="o">--</span><span class="p">;</span>
            <span class="k">break</span><span class="p">;</span>
        <span class="k">case</span> <span class="n">DOWN</span><span class="p">:</span>
            <span class="n">bullet</span><span class="o">-&gt;</span><span class="n">y</span><span class="o">++</span><span class="p">;</span>
            <span class="k">break</span><span class="p">;</span>
        <span class="k">case</span> <span class="n">LEFT</span><span class="p">:</span>
            <span class="n">bullet</span><span class="o">-&gt;</span><span class="n">x</span> <span class="o">-=</span> <span class="mi">2</span><span class="p">;</span>
            <span class="k">break</span><span class="p">;</span>
        <span class="k">case</span> <span class="n">RIGHT</span><span class="p">:</span>
            <span class="n">bullet</span><span class="o">-&gt;</span><span class="n">x</span> <span class="o">+=</span> <span class="mi">2</span><span class="p">;</span>
            <span class="k">break</span><span class="p">;</span>
        <span class="nl">default:</span>
            <span class="k">break</span><span class="p">;</span>
    <span class="p">}</span>
    <span class="k">if</span> <span class="p">(</span><span class="n">bullet</span><span class="o">-&gt;</span><span class="n">x</span> <span class="o">&lt;</span> <span class="mi">0</span> <span class="o">||</span> <span class="n">bullet</span><span class="o">-&gt;</span><span class="n">x</span> <span class="o">&gt;=</span> <span class="n">COLS</span> <span class="o">||</span> <span class="n">bullet</span><span class="o">-&gt;</span><span class="n">y</span> <span class="o">&lt;</span> <span class="mi">0</span> <span class="o">||</span>
        <span class="n">bullet</span><span class="o">-&gt;</span><span class="n">y</span> <span class="o">&gt;=</span> <span class="n">LINES</span><span class="p">)</span> <span class="p">{</span>
        <span class="n">bullet</span><span class="o">-&gt;</span><span class="n">active</span> <span class="o">=</span> <span class="nb">false</span><span class="p">;</span>
    <span class="p">}</span>
<span class="p">}</span>

<span class="k">static</span> <span class="kt">void</span> <span class="nf">check_collision</span><span class="p">(</span><span class="n">Tank</span> <span class="o">*</span><span class="n">tank</span><span class="p">,</span> <span class="n">Bullet</span> <span class="o">*</span><span class="n">bullet</span><span class="p">)</span> <span class="p">{</span>
    <span class="k">if</span> <span class="p">(</span><span class="n">bullet</span><span class="o">-&gt;</span><span class="n">active</span> <span class="o">&amp;&amp;</span> <span class="n">tank</span><span class="o">-&gt;</span><span class="n">x</span> <span class="o">==</span> <span class="n">bullet</span><span class="o">-&gt;</span><span class="n">x</span> <span class="o">&amp;&amp;</span> <span class="n">tank</span><span class="o">-&gt;</span><span class="n">y</span> <span class="o">==</span> <span class="n">bullet</span><span class="o">-&gt;</span><span class="n">y</span><span class="p">)</span> <span class="p">{</span>
        <span class="n">tank</span><span class="o">-&gt;</span><span class="n">alive</span> <span class="o">=</span> <span class="nb">false</span><span class="p">;</span>
        <span class="n">bullet</span><span class="o">-&gt;</span><span class="n">active</span> <span class="o">=</span> <span class="nb">false</span><span class="p">;</span>
    <span class="p">}</span>
<span class="p">}</span>

<span class="cm">/**
 * Updates the game state by advancing bullets and checking for collisions
 * between tanks and bullets.
 *
 * - Creates an array of pointers to each player's bullet for easy access during
 *   collision checks.
 * - For each player:
 *   - Updates their bullet by calling `update_bullet`.
 *   - Checks for collisions between the player's tank and every other player's
 *     bullet using `check_collision`.
 * - Skips collision checks between a player and their own bullet.
 */</span>
<span class="kt">void</span> <span class="nf">game_state_update</span><span class="p">(</span><span class="n">Game_State</span> <span class="o">*</span><span class="n">state</span><span class="p">)</span> <span class="p">{</span>
    <span class="n">Bullet</span> <span class="o">*</span><span class="n">bullets</span><span class="p">[</span><span class="n">state</span><span class="o">-&gt;</span><span class="n">players_count</span><span class="p">];</span>
    <span class="k">for</span> <span class="p">(</span><span class="kt">size_t</span> <span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">i</span> <span class="o">&lt;</span> <span class="n">state</span><span class="o">-&gt;</span><span class="n">players_count</span><span class="p">;</span> <span class="o">++</span><span class="n">i</span><span class="p">)</span>
        <span class="n">bullets</span><span class="p">[</span><span class="n">i</span><span class="p">]</span> <span class="o">=</span> <span class="o">&amp;</span><span class="n">state</span><span class="o">-&gt;</span><span class="n">players</span><span class="p">[</span><span class="n">i</span><span class="p">].</span><span class="n">bullet</span><span class="p">;</span>

    <span class="k">for</span> <span class="p">(</span><span class="kt">size_t</span> <span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">i</span> <span class="o">&lt;</span> <span class="n">state</span><span class="o">-&gt;</span><span class="n">players_count</span><span class="p">;</span> <span class="o">++</span><span class="n">i</span><span class="p">)</span> <span class="p">{</span>
        <span class="n">update_bullet</span><span class="p">(</span><span class="o">&amp;</span><span class="n">state</span><span class="o">-&gt;</span><span class="n">players</span><span class="p">[</span><span class="n">i</span><span class="p">].</span><span class="n">bullet</span><span class="p">);</span>
        <span class="k">for</span> <span class="p">(</span><span class="kt">size_t</span> <span class="n">j</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">j</span> <span class="o">&lt;</span> <span class="n">state</span><span class="o">-&gt;</span><span class="n">players_count</span><span class="p">;</span> <span class="o">++</span><span class="n">j</span><span class="p">)</span> <span class="p">{</span>
            <span class="k">if</span> <span class="p">(</span><span class="n">j</span> <span class="o">==</span> <span class="n">i</span><span class="p">)</span> <span class="k">continue</span><span class="p">;</span>  <span class="c1">// Skip self collision</span>
            <span class="n">check_collision</span><span class="p">(</span><span class="o">&amp;</span><span class="n">state</span><span class="o">-&gt;</span><span class="n">players</span><span class="p">[</span><span class="n">i</span><span class="p">],</span> <span class="n">bullets</span><span class="p">[</span><span class="n">j</span><span class="p">]);</span>
        <span class="p">}</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>
<hr />

<h4 id="the-client-side">The client side</h4>

<p>The client is the main entry point for each player, once started it connects to
the battletank server and provides a very crude terminal based graphic battlefield
and handles input from the player:</p>

<ul>
  <li>upon connection,it syncs with the server on the game state, receiving
an index that uniquely identifies the player tank in the game state</li>
  <li>the server continually broadcasts the game state to keep the clients in
sync</li>
  <li>clients will send actions to the servers such as movements or bullet fire</li>
  <li>the server will update the general game state and let it be broadcast in
the following cycle</li>
</ul>

<h5 id="out-of-scope-for-now">Out of scope (for now)</h5>

<p>The points above provide a very rudimentary interface to just see something work,
there are many improvements and limitations to be overcome in the pure technical
aspect that are not yet handled, some of these in no particular order:</p>

<ul>
  <li>screen size scaling: each client can have a different screen size, this makes it
tricky to ensure a consistent experience between all the participants, in the
current state of things, a lot of glitches are likely to happen due to this fact.</li>
  <li>clients disconnections and re-connections, reusing existing tanks if already
instantiated</li>
  <li>heartbeat logic to ensure clients aliveness</li>
</ul>

<p>These are all interesting challenges (well, probably the heartbeat and proper
client tracking are less exciting, but the screen scaling is indeed
interesting) and some of these limitations may be address in an hypothetical
<code class="language-plaintext highlighter-rouge">battletank v0.0.2</code> depending on inspiration.</p>

<p>Moving on with the code, the first part of the client side requires some helpers to
handle the UI, as agreed, this is not gonna be a graphical game (yet?) so <code class="language-plaintext highlighter-rouge">ncurses</code>
provides very handy and neat functions to draw something basic on terminal. I don’t
know much about the library itself but by the look of the APIs and their behaviour,
from my understanding of the docs it provides some nice wrappers around manipulation
of escape sequences for VT100 terminals and compatibles, similarly operating in raw
mode allowing for a fine-grained control over the keyboard input and such.</p>

<hr />

<p><strong>battletank_client.c</strong></p>
<hr />

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">static</span> <span class="kt">void</span> <span class="nf">init_screen</span><span class="p">(</span><span class="kt">void</span><span class="p">)</span> <span class="p">{</span>
    <span class="c1">// Start curses mode</span>
    <span class="n">initscr</span><span class="p">();</span>
    <span class="n">cbreak</span><span class="p">();</span>
    <span class="c1">// Don't echo keypresses to the screen</span>
    <span class="n">noecho</span><span class="p">();</span>
    <span class="c1">// Enable keypad mode</span>
    <span class="n">keypad</span><span class="p">(</span><span class="n">stdscr</span><span class="p">,</span> <span class="n">TRUE</span><span class="p">);</span>
    <span class="n">nodelay</span><span class="p">(</span><span class="n">stdscr</span><span class="p">,</span> <span class="n">TRUE</span><span class="p">);</span>
    <span class="c1">// Hide the cursor</span>
    <span class="n">curs_set</span><span class="p">(</span><span class="n">FALSE</span><span class="p">);</span>
<span class="p">}</span>

<span class="k">static</span> <span class="kt">void</span> <span class="nf">render_tank</span><span class="p">(</span><span class="k">const</span> <span class="n">Tank</span> <span class="o">*</span><span class="k">const</span> <span class="n">tank</span><span class="p">)</span> <span class="p">{</span>
    <span class="k">if</span> <span class="p">(</span><span class="n">tank</span><span class="o">-&gt;</span><span class="n">alive</span><span class="p">)</span> <span class="p">{</span>
        <span class="c1">// Draw the tank at its current position</span>
        <span class="n">mvaddch</span><span class="p">(</span><span class="n">tank</span><span class="o">-&gt;</span><span class="n">y</span><span class="p">,</span> <span class="n">tank</span><span class="o">-&gt;</span><span class="n">x</span><span class="p">,</span> <span class="sc">'T'</span><span class="p">);</span>
    <span class="p">}</span>
<span class="p">}</span>

<span class="k">static</span> <span class="kt">void</span> <span class="nf">render_bullet</span><span class="p">(</span><span class="k">const</span> <span class="n">Bullet</span> <span class="o">*</span><span class="k">const</span> <span class="n">bullet</span><span class="p">)</span> <span class="p">{</span>
    <span class="k">if</span> <span class="p">(</span><span class="n">bullet</span><span class="o">-&gt;</span><span class="n">active</span><span class="p">)</span> <span class="p">{</span>
        <span class="c1">// Draw the bullet at its current position</span>
        <span class="n">mvaddch</span><span class="p">(</span><span class="n">bullet</span><span class="o">-&gt;</span><span class="n">y</span><span class="p">,</span> <span class="n">bullet</span><span class="o">-&gt;</span><span class="n">x</span><span class="p">,</span> <span class="sc">'o'</span><span class="p">);</span>
    <span class="p">}</span>
<span class="p">}</span>

<span class="k">static</span> <span class="kt">void</span> <span class="nf">render_game</span><span class="p">(</span><span class="k">const</span> <span class="n">Game_State</span> <span class="o">*</span><span class="n">state</span><span class="p">)</span> <span class="p">{</span>
    <span class="n">clear</span><span class="p">();</span>
    <span class="k">for</span> <span class="p">(</span><span class="kt">size_t</span> <span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">i</span> <span class="o">&lt;</span> <span class="n">state</span><span class="o">-&gt;</span><span class="n">players_count</span><span class="p">;</span> <span class="o">++</span><span class="n">i</span><span class="p">)</span> <span class="p">{</span>
        <span class="n">render_tank</span><span class="p">(</span><span class="o">&amp;</span><span class="n">state</span><span class="o">-&gt;</span><span class="n">players</span><span class="p">[</span><span class="n">i</span><span class="p">]);</span>
        <span class="n">render_bullet</span><span class="p">(</span><span class="o">&amp;</span><span class="n">state</span><span class="o">-&gt;</span><span class="n">players</span><span class="p">[</span><span class="n">i</span><span class="p">].</span><span class="n">bullet</span><span class="p">);</span>
    <span class="p">}</span>
    <span class="n">refresh</span><span class="p">();</span>
<span class="p">}</span>

<span class="k">static</span> <span class="kt">unsigned</span> <span class="nf">handle_input</span><span class="p">(</span><span class="kt">void</span><span class="p">)</span> <span class="p">{</span>
    <span class="kt">unsigned</span> <span class="n">action</span> <span class="o">=</span> <span class="n">IDLE</span><span class="p">;</span>
    <span class="kt">int</span> <span class="n">ch</span> <span class="o">=</span> <span class="n">getch</span><span class="p">();</span>
    <span class="k">switch</span> <span class="p">(</span><span class="n">ch</span><span class="p">)</span> <span class="p">{</span>
        <span class="k">case</span> <span class="n">KEY_UP</span><span class="p">:</span>
            <span class="n">action</span> <span class="o">=</span> <span class="n">UP</span><span class="p">;</span>
            <span class="k">break</span><span class="p">;</span>
        <span class="k">case</span> <span class="n">KEY_DOWN</span><span class="p">:</span>
            <span class="n">action</span> <span class="o">=</span> <span class="n">DOWN</span><span class="p">;</span>
            <span class="k">break</span><span class="p">;</span>
        <span class="k">case</span> <span class="n">KEY_LEFT</span><span class="p">:</span>
            <span class="n">action</span> <span class="o">=</span> <span class="n">LEFT</span><span class="p">;</span>
            <span class="k">break</span><span class="p">;</span>
        <span class="k">case</span> <span class="n">KEY_RIGHT</span><span class="p">:</span>
            <span class="n">action</span> <span class="o">=</span> <span class="n">RIGHT</span><span class="p">;</span>
            <span class="k">break</span><span class="p">;</span>
        <span class="k">case</span> <span class="sc">' '</span><span class="p">:</span>
            <span class="n">action</span> <span class="o">=</span> <span class="n">FIRE</span><span class="p">;</span>
            <span class="k">break</span><span class="p">;</span>
    <span class="p">}</span>
    <span class="k">return</span> <span class="n">action</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>
<hr />

<p>In the last function <code class="language-plaintext highlighter-rouge">handle_input</code> the <code class="language-plaintext highlighter-rouge">unsigned action</code> returned will
be the main command we send to the server side (pretty simple huh? Ample
margin to enrich this semantic).</p>

<p>Next in line comes the networking helpers, required to manage the communication
with the server side, connection, send and receive:</p>

<hr />

<p><strong>battletank_client.c</strong></p>
<hr />

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">static</span> <span class="kt">int</span> <span class="nf">socket_connect</span><span class="p">(</span><span class="k">const</span> <span class="kt">char</span> <span class="o">*</span><span class="n">host</span><span class="p">,</span> <span class="kt">int</span> <span class="n">port</span><span class="p">)</span> <span class="p">{</span>
    <span class="k">struct</span> <span class="n">sockaddr_in</span> <span class="n">serveraddr</span><span class="p">;</span>
    <span class="k">struct</span> <span class="n">hostent</span> <span class="o">*</span><span class="n">server</span><span class="p">;</span>
    <span class="k">struct</span> <span class="n">timeval</span> <span class="n">tv</span> <span class="o">=</span> <span class="p">{</span><span class="mi">0</span><span class="p">,</span> <span class="mi">10000</span><span class="p">};</span>

    <span class="c1">// socket: create the socket</span>
    <span class="kt">int</span> <span class="n">sfd</span> <span class="o">=</span> <span class="n">socket</span><span class="p">(</span><span class="n">AF_INET</span><span class="p">,</span> <span class="n">SOCK_STREAM</span><span class="p">,</span> <span class="mi">0</span><span class="p">);</span>
    <span class="k">if</span> <span class="p">(</span><span class="n">sfd</span> <span class="o">&lt;</span> <span class="mi">0</span><span class="p">)</span> <span class="k">goto</span> <span class="n">err</span><span class="p">;</span>

    <span class="n">setsockopt</span><span class="p">(</span><span class="n">sfd</span><span class="p">,</span> <span class="n">SOL_SOCKET</span><span class="p">,</span> <span class="n">SO_RCVTIMEO</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">tv</span><span class="p">,</span> <span class="k">sizeof</span><span class="p">(</span><span class="k">struct</span> <span class="n">timeval</span><span class="p">));</span>
    <span class="n">setsockopt</span><span class="p">(</span><span class="n">sfd</span><span class="p">,</span> <span class="n">SOL_SOCKET</span><span class="p">,</span> <span class="n">SO_SNDTIMEO</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">tv</span><span class="p">,</span> <span class="k">sizeof</span><span class="p">(</span><span class="k">struct</span> <span class="n">timeval</span><span class="p">));</span>

    <span class="c1">// gethostbyname: get the server's DNS entry</span>
    <span class="n">server</span> <span class="o">=</span> <span class="n">gethostbyname</span><span class="p">(</span><span class="n">host</span><span class="p">);</span>
    <span class="k">if</span> <span class="p">(</span><span class="n">server</span> <span class="o">==</span> <span class="nb">NULL</span><span class="p">)</span> <span class="k">goto</span> <span class="n">err</span><span class="p">;</span>

    <span class="c1">// build the server's address</span>
    <span class="n">bzero</span><span class="p">((</span><span class="kt">char</span> <span class="o">*</span><span class="p">)</span><span class="o">&amp;</span><span class="n">serveraddr</span><span class="p">,</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">serveraddr</span><span class="p">));</span>
    <span class="n">serveraddr</span><span class="p">.</span><span class="n">sin_family</span> <span class="o">=</span> <span class="n">AF_INET</span><span class="p">;</span>
    <span class="n">bcopy</span><span class="p">((</span><span class="kt">char</span> <span class="o">*</span><span class="p">)</span><span class="n">server</span><span class="o">-&gt;</span><span class="n">h_addr</span><span class="p">,</span> <span class="p">(</span><span class="kt">char</span> <span class="o">*</span><span class="p">)</span><span class="o">&amp;</span><span class="n">serveraddr</span><span class="p">.</span><span class="n">sin_addr</span><span class="p">.</span><span class="n">s_addr</span><span class="p">,</span>
          <span class="n">server</span><span class="o">-&gt;</span><span class="n">h_length</span><span class="p">);</span>
    <span class="n">serveraddr</span><span class="p">.</span><span class="n">sin_port</span> <span class="o">=</span> <span class="n">htons</span><span class="p">(</span><span class="n">port</span><span class="p">);</span>

    <span class="c1">// connect: create a connection with the server</span>
    <span class="k">if</span> <span class="p">(</span><span class="n">connect</span><span class="p">(</span><span class="n">sfd</span><span class="p">,</span> <span class="p">(</span><span class="k">const</span> <span class="k">struct</span> <span class="n">sockaddr</span> <span class="o">*</span><span class="p">)</span><span class="o">&amp;</span><span class="n">serveraddr</span><span class="p">,</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">serveraddr</span><span class="p">))</span> <span class="o">&lt;</span>
        <span class="mi">0</span><span class="p">)</span>
        <span class="k">goto</span> <span class="n">err</span><span class="p">;</span>

    <span class="k">return</span> <span class="n">sfd</span><span class="p">;</span>

<span class="nl">err:</span>

    <span class="n">perror</span><span class="p">(</span><span class="s">"socket(2) opening socket failed"</span><span class="p">);</span>
    <span class="k">return</span> <span class="o">-</span><span class="mi">1</span><span class="p">;</span>
<span class="p">}</span>

<span class="k">static</span> <span class="kt">int</span> <span class="nf">client_connect</span><span class="p">(</span><span class="k">const</span> <span class="kt">char</span> <span class="o">*</span><span class="n">host</span><span class="p">,</span> <span class="kt">int</span> <span class="n">port</span><span class="p">)</span> <span class="p">{</span>
    <span class="k">return</span> <span class="n">socket_connect</span><span class="p">(</span><span class="n">host</span><span class="p">,</span> <span class="n">port</span><span class="p">);</span>
<span class="p">}</span>

<span class="k">static</span> <span class="kt">int</span> <span class="nf">client_send_data</span><span class="p">(</span><span class="kt">int</span> <span class="n">sockfd</span><span class="p">,</span> <span class="k">const</span> <span class="kt">unsigned</span> <span class="kt">char</span> <span class="o">*</span><span class="n">data</span><span class="p">,</span> <span class="kt">size_t</span> <span class="n">datasize</span><span class="p">)</span> <span class="p">{</span>
    <span class="kt">ssize_t</span> <span class="n">n</span> <span class="o">=</span> <span class="n">network_send</span><span class="p">(</span><span class="n">sockfd</span><span class="p">,</span> <span class="n">data</span><span class="p">,</span> <span class="n">datasize</span><span class="p">);</span>
    <span class="k">if</span> <span class="p">(</span><span class="n">n</span> <span class="o">&lt;</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span>
        <span class="n">perror</span><span class="p">(</span><span class="s">"write() error"</span><span class="p">);</span>
        <span class="n">close</span><span class="p">(</span><span class="n">sockfd</span><span class="p">);</span>
        <span class="n">exit</span><span class="p">(</span><span class="n">EXIT_FAILURE</span><span class="p">);</span>
    <span class="p">}</span>
    <span class="k">return</span> <span class="n">n</span><span class="p">;</span>
<span class="p">}</span>

<span class="k">static</span> <span class="kt">int</span> <span class="nf">client_recv_data</span><span class="p">(</span><span class="kt">int</span> <span class="n">sockfd</span><span class="p">,</span> <span class="kt">unsigned</span> <span class="kt">char</span> <span class="o">*</span><span class="n">data</span><span class="p">)</span> <span class="p">{</span>
    <span class="kt">ssize_t</span> <span class="n">n</span> <span class="o">=</span> <span class="n">network_recv</span><span class="p">(</span><span class="n">sockfd</span><span class="p">,</span> <span class="n">data</span><span class="p">);</span>
    <span class="k">if</span> <span class="p">(</span><span class="n">n</span> <span class="o">&lt;</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span>
        <span class="n">perror</span><span class="p">(</span><span class="s">"read() error"</span><span class="p">);</span>
        <span class="n">close</span><span class="p">(</span><span class="n">sockfd</span><span class="p">);</span>
        <span class="n">exit</span><span class="p">(</span><span class="n">EXIT_FAILURE</span><span class="p">);</span>
    <span class="p">}</span>
    <span class="k">return</span> <span class="n">n</span><span class="p">;</span>
<span class="p">}</span>

</code></pre></div></div>
<hr />

<p>All simple boilerplate code mostly, to handle a fairly traditional TCP
connection, the only bit that’s interesting here is represented by the
lines</p>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">setsockopt</span><span class="p">(</span><span class="n">sfd</span><span class="p">,</span> <span class="n">SOL_SOCKET</span><span class="p">,</span> <span class="n">SO_RCVTIMEO</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">tv</span><span class="p">,</span> <span class="k">sizeof</span><span class="p">(</span><span class="k">struct</span> <span class="n">timeval</span><span class="p">));</span>
<span class="n">setsockopt</span><span class="p">(</span><span class="n">sfd</span><span class="p">,</span> <span class="n">SOL_SOCKET</span><span class="p">,</span> <span class="n">SO_SNDTIMEO</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">tv</span><span class="p">,</span> <span class="k">sizeof</span><span class="p">(</span><span class="k">struct</span> <span class="n">timeval</span><span class="p">));</span>
</code></pre></div></div>

<p>These two lines ensure that the <code class="language-plaintext highlighter-rouge">read</code> system call times out after a certain
period, seemingly simulating a non-blocking socket behaviour (not really, but
the part that’s interesting for us). This is the very first solution and the
simplest that came to mind but it allows to run a recv loop without blocking
indefinitely, as the server will constantly push updates, the client wants to be
as up-to-date as possible to keep rendering an accurate and consistent game
state.</p>

<p>This part happens in the <code class="language-plaintext highlighter-rouge">game_loop</code> function, a very slim and stripped down
client-side engine logic to render and gather inputs from the client to the
server:</p>

<hr />

<p><strong>battletank_client.c</strong></p>
<hr />

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Main game loop, capture input from the player and communicate with the game</span>
<span class="c1">// server</span>
<span class="k">static</span> <span class="kt">void</span> <span class="nf">game_loop</span><span class="p">(</span><span class="kt">void</span><span class="p">)</span> <span class="p">{</span>
    <span class="kt">int</span> <span class="n">sockfd</span> <span class="o">=</span> <span class="n">client_connect</span><span class="p">(</span><span class="s">"127.0.0.1"</span><span class="p">,</span> <span class="mi">6699</span><span class="p">);</span>
    <span class="k">if</span> <span class="p">(</span><span class="n">sockfd</span> <span class="o">&lt;</span> <span class="mi">0</span><span class="p">)</span> <span class="n">exit</span><span class="p">(</span><span class="n">EXIT_FAILURE</span><span class="p">);</span>
    <span class="n">Game_State</span> <span class="n">state</span><span class="p">;</span>
    <span class="n">game_state_init</span><span class="p">(</span><span class="o">&amp;</span><span class="n">state</span><span class="p">);</span>
    <span class="kt">unsigned</span> <span class="kt">char</span> <span class="n">buf</span><span class="p">[</span><span class="n">BUFSIZE</span><span class="p">];</span>
    <span class="c1">// Sync the game state for the first time</span>
    <span class="kt">int</span> <span class="n">n</span> <span class="o">=</span> <span class="n">client_recv_data</span><span class="p">(</span><span class="n">sockfd</span><span class="p">,</span> <span class="n">buf</span><span class="p">);</span>
    <span class="n">protocol_deserialize_game_state</span><span class="p">(</span><span class="n">buf</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">state</span><span class="p">);</span>
    <span class="kt">unsigned</span> <span class="n">action</span> <span class="o">=</span> <span class="n">IDLE</span><span class="p">;</span>

    <span class="k">while</span> <span class="p">(</span><span class="mi">1</span><span class="p">)</span> <span class="p">{</span>
        <span class="n">action</span> <span class="o">=</span> <span class="n">handle_input</span><span class="p">();</span>
        <span class="k">if</span> <span class="p">(</span><span class="n">action</span> <span class="o">!=</span> <span class="n">IDLE</span><span class="p">)</span> <span class="p">{</span>
            <span class="n">memset</span><span class="p">(</span><span class="n">buf</span><span class="p">,</span> <span class="mh">0x00</span><span class="p">,</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">buf</span><span class="p">));</span>
            <span class="n">n</span> <span class="o">=</span> <span class="n">protocol_serialize_action</span><span class="p">(</span><span class="n">action</span><span class="p">,</span> <span class="n">buf</span><span class="p">);</span>
            <span class="n">client_send_data</span><span class="p">(</span><span class="n">sockfd</span><span class="p">,</span> <span class="n">buf</span><span class="p">,</span> <span class="n">n</span><span class="p">);</span>
        <span class="p">}</span>
        <span class="n">n</span> <span class="o">=</span> <span class="n">client_recv_data</span><span class="p">(</span><span class="n">sockfd</span><span class="p">,</span> <span class="n">buf</span><span class="p">);</span>
        <span class="n">protocol_deserialize_game_state</span><span class="p">(</span><span class="n">buf</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">state</span><span class="p">);</span>
        <span class="n">render_game</span><span class="p">(</span><span class="o">&amp;</span><span class="n">state</span><span class="p">);</span>
    <span class="p">}</span>
<span class="p">}</span>

<span class="kt">int</span> <span class="nf">main</span><span class="p">(</span><span class="kt">void</span><span class="p">)</span> <span class="p">{</span>
    <span class="n">init_screen</span><span class="p">();</span>
    <span class="n">game_loop</span><span class="p">();</span>
    <span class="n">endwin</span><span class="p">();</span>
    <span class="k">return</span> <span class="mi">0</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>
<hr />

<p>The main function is as light as it gets, just initializes the <code class="language-plaintext highlighter-rouge">ncurses</code> screen to
easily calculate <code class="language-plaintext highlighter-rouge">COLS</code> and <code class="language-plaintext highlighter-rouge">LINES</code> the straight to the game loop, with the
flow being:</p>

<ul>
  <li>Connection to the server</li>
  <li>Sync of the game state, including other possibly already connected players</li>
  <li>Non blocking wait for input, if made, send it to the server to update the
game state for everyone connected</li>
  <li>Receive data from the server, i.e. the game state, non blocking.</li>
</ul>

<h4 id="the-server">The server</h4>

<p>The server side handles the game state and serves as the unique authoritative
source of truth.</p>

<ul>
  <li>clients sync at their first connection and their tank is spawned in the
battlefield, the server will send a unique identifier to the clients (an
int index for the time being, that represents the tank assigned to the
player in the game state)</li>
</ul>

<p>As with the client, a bunch of communication helpers to handle the TCP
connections. The server will be a TCP non-blocking server (man <code class="language-plaintext highlighter-rouge">select</code> /
<code class="language-plaintext highlighter-rouge">poll</code> / <code class="language-plaintext highlighter-rouge">epoll</code> / <code class="language-plaintext highlighter-rouge">kqueue</code>), relying on <code class="language-plaintext highlighter-rouge">select</code> call to handle I/O events.
Select is not the most efficient mechanism for I/O multiplexing, it’s in fact
quite dated, the first approach to the problem, it’s a little quirky and among
other things it requires to linearly scan over all the monitored descriptors
each time an event is detected, it’s also an user-space call, which adds a
minor over-head in context switching and it’s limited to 1024 file descriptor
in total but:</p>

<ul>
  <li>It’s ubiquitous, basically every *nix system provides the call</li>
  <li>It’s very simple to use and provides everything required for a PoC</li>
  <li>It’s more than enough for the use case, even with tenth of players
it would handle the load very well, <code class="language-plaintext highlighter-rouge">poll</code> and <code class="language-plaintext highlighter-rouge">epoll</code> are really
designed towards other scales, in the order of 10K of connected sockets.</li>
</ul>

<hr />

<p><strong>battletank_server.c</strong></p>
<hr />

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// We don't expect big payloads</span>
<span class="cp">#define BUFSIZE 1024
#define BACKLOG 128
#define TIMEOUT 30000 // 30 ms
</span>
<span class="c1">// Generic global game state</span>
<span class="k">static</span> <span class="n">Game_State</span> <span class="n">game_state</span> <span class="o">=</span> <span class="p">{</span><span class="mi">0</span><span class="p">};</span>

<span class="cm">/* Set non-blocking socket */</span>
<span class="k">static</span> <span class="kt">int</span> <span class="nf">set_nonblocking</span><span class="p">(</span><span class="kt">int</span> <span class="n">fd</span><span class="p">)</span> <span class="p">{</span>
    <span class="kt">int</span> <span class="n">flags</span><span class="p">,</span> <span class="n">result</span><span class="p">;</span>
    <span class="n">flags</span> <span class="o">=</span> <span class="n">fcntl</span><span class="p">(</span><span class="n">fd</span><span class="p">,</span> <span class="n">F_GETFL</span><span class="p">,</span> <span class="mi">0</span><span class="p">);</span>

    <span class="k">if</span> <span class="p">(</span><span class="n">flags</span> <span class="o">==</span> <span class="o">-</span><span class="mi">1</span><span class="p">)</span> <span class="k">goto</span> <span class="n">err</span><span class="p">;</span>

    <span class="n">result</span> <span class="o">=</span> <span class="n">fcntl</span><span class="p">(</span><span class="n">fd</span><span class="p">,</span> <span class="n">F_SETFL</span><span class="p">,</span> <span class="n">flags</span> <span class="o">|</span> <span class="n">O_NONBLOCK</span><span class="p">);</span>
    <span class="k">if</span> <span class="p">(</span><span class="n">result</span> <span class="o">==</span> <span class="o">-</span><span class="mi">1</span><span class="p">)</span> <span class="k">goto</span> <span class="n">err</span><span class="p">;</span>

    <span class="k">return</span> <span class="mi">0</span><span class="p">;</span>

<span class="nl">err:</span>

    <span class="n">fprintf</span><span class="p">(</span><span class="n">stderr</span><span class="p">,</span> <span class="s">"set_nonblocking: %s</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span> <span class="n">strerror</span><span class="p">(</span><span class="n">errno</span><span class="p">));</span>
    <span class="k">return</span> <span class="o">-</span><span class="mi">1</span><span class="p">;</span>
<span class="p">}</span>

<span class="k">static</span> <span class="kt">int</span> <span class="nf">server_listen</span><span class="p">(</span><span class="k">const</span> <span class="kt">char</span> <span class="o">*</span><span class="n">host</span><span class="p">,</span> <span class="kt">int</span> <span class="n">port</span><span class="p">,</span> <span class="kt">int</span> <span class="n">backlog</span><span class="p">)</span> <span class="p">{</span>
    <span class="kt">int</span> <span class="n">listen_fd</span> <span class="o">=</span> <span class="o">-</span><span class="mi">1</span><span class="p">;</span>
    <span class="k">const</span> <span class="k">struct</span> <span class="n">addrinfo</span> <span class="n">hints</span> <span class="o">=</span> <span class="p">{.</span><span class="n">ai_family</span> <span class="o">=</span> <span class="n">AF_UNSPEC</span><span class="p">,</span>
                                   <span class="p">.</span><span class="n">ai_socktype</span> <span class="o">=</span> <span class="n">SOCK_STREAM</span><span class="p">,</span>
                                   <span class="p">.</span><span class="n">ai_flags</span> <span class="o">=</span> <span class="n">AI_PASSIVE</span><span class="p">};</span>
    <span class="k">struct</span> <span class="n">addrinfo</span> <span class="o">*</span><span class="n">result</span><span class="p">,</span> <span class="o">*</span><span class="n">rp</span><span class="p">;</span>
    <span class="kt">char</span> <span class="n">port_str</span><span class="p">[</span><span class="mi">6</span><span class="p">];</span>

    <span class="n">snprintf</span><span class="p">(</span><span class="n">port_str</span><span class="p">,</span> <span class="mi">6</span><span class="p">,</span> <span class="s">"%i"</span><span class="p">,</span> <span class="n">port</span><span class="p">);</span>

    <span class="k">if</span> <span class="p">(</span><span class="n">getaddrinfo</span><span class="p">(</span><span class="n">host</span><span class="p">,</span> <span class="n">port_str</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">hints</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">result</span><span class="p">)</span> <span class="o">!=</span> <span class="mi">0</span><span class="p">)</span> <span class="k">goto</span> <span class="n">err</span><span class="p">;</span>

    <span class="cm">/* Create a listening socket */</span>
    <span class="k">for</span> <span class="p">(</span><span class="n">rp</span> <span class="o">=</span> <span class="n">result</span><span class="p">;</span> <span class="n">rp</span> <span class="o">!=</span> <span class="nb">NULL</span><span class="p">;</span> <span class="n">rp</span> <span class="o">=</span> <span class="n">rp</span><span class="o">-&gt;</span><span class="n">ai_next</span><span class="p">)</span> <span class="p">{</span>
        <span class="n">listen_fd</span> <span class="o">=</span> <span class="n">socket</span><span class="p">(</span><span class="n">rp</span><span class="o">-&gt;</span><span class="n">ai_family</span><span class="p">,</span> <span class="n">rp</span><span class="o">-&gt;</span><span class="n">ai_socktype</span><span class="p">,</span> <span class="n">rp</span><span class="o">-&gt;</span><span class="n">ai_protocol</span><span class="p">);</span>
        <span class="k">if</span> <span class="p">(</span><span class="n">listen_fd</span> <span class="o">&lt;</span> <span class="mi">0</span><span class="p">)</span> <span class="k">continue</span><span class="p">;</span>

        <span class="cm">/* set SO_REUSEADDR so the socket will be reusable after process kill */</span>
        <span class="k">if</span> <span class="p">(</span><span class="n">setsockopt</span><span class="p">(</span><span class="n">listen_fd</span><span class="p">,</span> <span class="n">SOL_SOCKET</span><span class="p">,</span> <span class="n">SO_REUSEADDR</span><span class="p">,</span> <span class="o">&amp;</span><span class="p">(</span><span class="kt">int</span><span class="p">){</span><span class="mi">1</span><span class="p">},</span>
                       <span class="k">sizeof</span><span class="p">(</span><span class="kt">int</span><span class="p">))</span> <span class="o">&lt;</span> <span class="mi">0</span><span class="p">)</span>
            <span class="k">goto</span> <span class="n">err</span><span class="p">;</span>

        <span class="cm">/* Bind it to the addr:port opened on the network interface */</span>
        <span class="k">if</span> <span class="p">(</span><span class="n">bind</span><span class="p">(</span><span class="n">listen_fd</span><span class="p">,</span> <span class="n">rp</span><span class="o">-&gt;</span><span class="n">ai_addr</span><span class="p">,</span> <span class="n">rp</span><span class="o">-&gt;</span><span class="n">ai_addrlen</span><span class="p">)</span> <span class="o">==</span> <span class="mi">0</span><span class="p">)</span>
            <span class="k">break</span><span class="p">;</span>  <span class="c1">// Succesful bind</span>
        <span class="n">close</span><span class="p">(</span><span class="n">listen_fd</span><span class="p">);</span>
    <span class="p">}</span>

    <span class="n">freeaddrinfo</span><span class="p">(</span><span class="n">result</span><span class="p">);</span>
    <span class="k">if</span> <span class="p">(</span><span class="n">rp</span> <span class="o">==</span> <span class="nb">NULL</span><span class="p">)</span> <span class="k">goto</span> <span class="n">err</span><span class="p">;</span>

    <span class="cm">/*
     * Let's make the socket non-blocking (strongly advised to use the
     * eventloop)
     */</span>
    <span class="p">(</span><span class="kt">void</span><span class="p">)</span><span class="n">set_nonblocking</span><span class="p">(</span><span class="n">listen_fd</span><span class="p">);</span>

    <span class="cm">/* Finally let's make it listen */</span>
    <span class="k">if</span> <span class="p">(</span><span class="n">listen</span><span class="p">(</span><span class="n">listen_fd</span><span class="p">,</span> <span class="n">backlog</span><span class="p">)</span> <span class="o">!=</span> <span class="mi">0</span><span class="p">)</span> <span class="k">goto</span> <span class="n">err</span><span class="p">;</span>

    <span class="k">return</span> <span class="n">listen_fd</span><span class="p">;</span>
<span class="nl">err:</span>
    <span class="k">return</span> <span class="o">-</span><span class="mi">1</span><span class="p">;</span>
<span class="p">}</span>

<span class="k">static</span> <span class="kt">int</span> <span class="nf">server_accept</span><span class="p">(</span><span class="kt">int</span> <span class="n">server_fd</span><span class="p">)</span> <span class="p">{</span>
    <span class="kt">int</span> <span class="n">fd</span><span class="p">;</span>
    <span class="k">struct</span> <span class="n">sockaddr_in</span> <span class="n">addr</span><span class="p">;</span>
    <span class="n">socklen_t</span> <span class="n">addrlen</span> <span class="o">=</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">addr</span><span class="p">);</span>

    <span class="cm">/* Let's accept on listening socket */</span>
    <span class="n">fd</span> <span class="o">=</span> <span class="n">accept</span><span class="p">(</span><span class="n">server_fd</span><span class="p">,</span> <span class="p">(</span><span class="k">struct</span> <span class="n">sockaddr</span> <span class="o">*</span><span class="p">)</span><span class="o">&amp;</span><span class="n">addr</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">addrlen</span><span class="p">);</span>
    <span class="k">if</span> <span class="p">(</span><span class="n">fd</span> <span class="o">&lt;=</span> <span class="mi">0</span><span class="p">)</span> <span class="k">goto</span> <span class="n">exit</span><span class="p">;</span>

    <span class="p">(</span><span class="kt">void</span><span class="p">)</span><span class="n">set_nonblocking</span><span class="p">(</span><span class="n">fd</span><span class="p">);</span>
    <span class="k">return</span> <span class="n">fd</span><span class="p">;</span>
<span class="nl">exit:</span>
    <span class="k">if</span> <span class="p">(</span><span class="n">errno</span> <span class="o">!=</span> <span class="n">EWOULDBLOCK</span> <span class="o">&amp;&amp;</span> <span class="n">errno</span> <span class="o">!=</span> <span class="n">EAGAIN</span><span class="p">)</span> <span class="n">perror</span><span class="p">(</span><span class="s">"accept"</span><span class="p">);</span>
    <span class="k">return</span> <span class="o">-</span><span class="mi">1</span><span class="p">;</span>
<span class="p">}</span>

<span class="k">static</span> <span class="kt">int</span> <span class="nf">broadcast</span><span class="p">(</span><span class="kt">int</span> <span class="o">*</span><span class="n">client_fds</span><span class="p">,</span> <span class="k">const</span> <span class="kt">unsigned</span> <span class="kt">char</span> <span class="o">*</span><span class="n">buf</span><span class="p">,</span> <span class="kt">size_t</span> <span class="n">count</span><span class="p">)</span> <span class="p">{</span>
    <span class="kt">int</span> <span class="n">written</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
    <span class="k">for</span> <span class="p">(</span><span class="kt">int</span> <span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">i</span> <span class="o">&lt;</span> <span class="n">FD_SETSIZE</span><span class="p">;</span> <span class="n">i</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span>
        <span class="k">if</span> <span class="p">(</span><span class="n">client_fds</span><span class="p">[</span><span class="n">i</span><span class="p">]</span> <span class="o">&gt;=</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span>
            <span class="c1">// TODO check for errors writing</span>
            <span class="n">written</span> <span class="o">+=</span> <span class="n">network_send</span><span class="p">(</span><span class="n">client_fds</span><span class="p">[</span><span class="n">i</span><span class="p">],</span> <span class="n">buf</span><span class="p">,</span> <span class="n">count</span><span class="p">);</span>
        <span class="p">}</span>
    <span class="p">}</span>

    <span class="k">return</span> <span class="n">written</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>
<hr />

<p>Again, the main just initializes the <code class="language-plaintext highlighter-rouge">ncurses</code> screen (this is the reason why
the PoC will assume that the players will play from their own full size
terminal, as currently there is no scaling mechanism in place to ensure
consistency) and run the main <code class="language-plaintext highlighter-rouge">select</code> loop waiting for connections. Clients
are tracked in the simplest way possible by using an array and each new
connected client will be assigned its index in the main array as the index for
his tank in the game state.</p>

<hr />

<p><strong>battletank_server.c</strong></p>
<hr />

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">static</span> <span class="kt">void</span> <span class="nf">server_loop</span><span class="p">(</span><span class="kt">int</span> <span class="n">server_fd</span><span class="p">)</span> <span class="p">{</span>
    <span class="n">fd_set</span> <span class="n">readfds</span><span class="p">;</span>
    <span class="kt">int</span> <span class="n">client_fds</span><span class="p">[</span><span class="n">FD_SETSIZE</span><span class="p">];</span>
    <span class="kt">int</span> <span class="n">maxfd</span> <span class="o">=</span> <span class="n">server_fd</span><span class="p">;</span>
    <span class="kt">int</span> <span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
    <span class="kt">unsigned</span> <span class="kt">char</span> <span class="n">buf</span><span class="p">[</span><span class="n">BUFSIZE</span><span class="p">];</span>
    <span class="k">struct</span> <span class="n">timeval</span> <span class="n">tv</span> <span class="o">=</span> <span class="p">{</span><span class="mi">0</span><span class="p">,</span> <span class="n">TIMEOUT</span><span class="p">};</span>
    <span class="kt">unsigned</span> <span class="kt">long</span> <span class="kt">long</span> <span class="n">current_time_ns</span> <span class="o">=</span> <span class="mi">0</span><span class="p">,</span> <span class="n">remaining_us</span> <span class="o">=</span> <span class="mi">0</span><span class="p">,</span>
                       <span class="n">last_update_time_ns</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>

    <span class="c1">// Initialize client_fds array</span>
    <span class="k">for</span> <span class="p">(</span><span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">i</span> <span class="o">&lt;</span> <span class="n">FD_SETSIZE</span><span class="p">;</span> <span class="n">i</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span>
        <span class="n">client_fds</span><span class="p">[</span><span class="n">i</span><span class="p">]</span> <span class="o">=</span> <span class="o">-</span><span class="mi">1</span><span class="p">;</span>
    <span class="p">}</span>

    <span class="k">while</span> <span class="p">(</span><span class="mi">1</span><span class="p">)</span> <span class="p">{</span>
        <span class="n">FD_ZERO</span><span class="p">(</span><span class="o">&amp;</span><span class="n">readfds</span><span class="p">);</span>
        <span class="n">FD_SET</span><span class="p">(</span><span class="n">server_fd</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">readfds</span><span class="p">);</span>

        <span class="k">for</span> <span class="p">(</span><span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">i</span> <span class="o">&lt;</span> <span class="n">FD_SETSIZE</span><span class="p">;</span> <span class="n">i</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span>
            <span class="k">if</span> <span class="p">(</span><span class="n">client_fds</span><span class="p">[</span><span class="n">i</span><span class="p">]</span> <span class="o">&gt;=</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span>
                <span class="n">FD_SET</span><span class="p">(</span><span class="n">client_fds</span><span class="p">[</span><span class="n">i</span><span class="p">],</span> <span class="o">&amp;</span><span class="n">readfds</span><span class="p">);</span>
                <span class="k">if</span> <span class="p">(</span><span class="n">client_fds</span><span class="p">[</span><span class="n">i</span><span class="p">]</span> <span class="o">&gt;</span> <span class="n">maxfd</span><span class="p">)</span> <span class="p">{</span>
                    <span class="n">maxfd</span> <span class="o">=</span> <span class="n">client_fds</span><span class="p">[</span><span class="n">i</span><span class="p">];</span>
                <span class="p">}</span>
            <span class="p">}</span>
        <span class="p">}</span>
        <span class="n">memset</span><span class="p">(</span><span class="n">buf</span><span class="p">,</span> <span class="mh">0x00</span><span class="p">,</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">buf</span><span class="p">));</span>
        <span class="kt">int</span> <span class="n">num_events</span> <span class="o">=</span> <span class="n">select</span><span class="p">(</span><span class="n">maxfd</span> <span class="o">+</span> <span class="mi">1</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">readfds</span><span class="p">,</span> <span class="nb">NULL</span><span class="p">,</span> <span class="nb">NULL</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">tv</span><span class="p">);</span>

        <span class="k">if</span> <span class="p">(</span><span class="n">num_events</span> <span class="o">==</span> <span class="o">-</span><span class="mi">1</span><span class="p">)</span> <span class="p">{</span>
            <span class="n">perror</span><span class="p">(</span><span class="s">"select() error"</span><span class="p">);</span>
            <span class="n">exit</span><span class="p">(</span><span class="n">EXIT_FAILURE</span><span class="p">);</span>
        <span class="p">}</span>

        <span class="k">if</span> <span class="p">(</span><span class="n">FD_ISSET</span><span class="p">(</span><span class="n">server_fd</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">readfds</span><span class="p">))</span> <span class="p">{</span>
            <span class="c1">// New connection request</span>
            <span class="kt">int</span> <span class="n">client_fd</span> <span class="o">=</span> <span class="n">server_accept</span><span class="p">(</span><span class="n">server_fd</span><span class="p">);</span>
            <span class="k">if</span> <span class="p">(</span><span class="n">client_fd</span> <span class="o">&lt;</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span>
                <span class="n">perror</span><span class="p">(</span><span class="s">"accept() error"</span><span class="p">);</span>
                <span class="k">continue</span><span class="p">;</span>
            <span class="p">}</span>

            <span class="k">for</span> <span class="p">(</span><span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">i</span> <span class="o">&lt;</span> <span class="n">FD_SETSIZE</span><span class="p">;</span> <span class="n">i</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span>
                <span class="k">if</span> <span class="p">(</span><span class="n">client_fds</span><span class="p">[</span><span class="n">i</span><span class="p">]</span> <span class="o">&lt;</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span>
                    <span class="n">client_fds</span><span class="p">[</span><span class="n">i</span><span class="p">]</span> <span class="o">=</span> <span class="n">client_fd</span><span class="p">;</span>
                    <span class="n">game_state</span><span class="p">.</span><span class="n">player_index</span> <span class="o">=</span> <span class="n">i</span><span class="p">;</span>
                    <span class="k">break</span><span class="p">;</span>
                <span class="p">}</span>
            <span class="p">}</span>

            <span class="k">if</span> <span class="p">(</span><span class="n">i</span> <span class="o">==</span> <span class="n">FD_SETSIZE</span><span class="p">)</span> <span class="p">{</span>
                <span class="n">fprintf</span><span class="p">(</span><span class="n">stderr</span><span class="p">,</span> <span class="s">"Too many clients</span><span class="se">\n</span><span class="s">"</span><span class="p">);</span>
                <span class="n">close</span><span class="p">(</span><span class="n">client_fd</span><span class="p">);</span>
                <span class="k">continue</span><span class="p">;</span>
            <span class="p">}</span>

            <span class="n">printw</span><span class="p">(</span><span class="s">"[info] New player connected</span><span class="se">\n</span><span class="s">"</span><span class="p">);</span>
            <span class="n">printw</span><span class="p">(</span><span class="s">"[info] Syncing game state</span><span class="se">\n</span><span class="s">"</span><span class="p">);</span>
            <span class="n">printw</span><span class="p">(</span><span class="s">"[info] Player assigned [%ld] tank</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span>
                   <span class="n">game_state</span><span class="p">.</span><span class="n">player_index</span><span class="p">);</span>

            <span class="c1">// Spawn a tank in a random position for the new connected</span>
            <span class="c1">// player</span>
            <span class="n">game_state_spawn_tank</span><span class="p">(</span><span class="o">&amp;</span><span class="n">game_state</span><span class="p">,</span> <span class="n">game_state</span><span class="p">.</span><span class="n">player_index</span><span class="p">);</span>

            <span class="c1">// Send the game state</span>
            <span class="kt">ssize_t</span> <span class="n">bytes</span> <span class="o">=</span> <span class="n">protocol_serialize_game_state</span><span class="p">(</span><span class="o">&amp;</span><span class="n">game_state</span><span class="p">,</span> <span class="n">buf</span><span class="p">);</span>
            <span class="n">bytes</span> <span class="o">=</span> <span class="n">network_send</span><span class="p">(</span><span class="n">client_fd</span><span class="p">,</span> <span class="n">buf</span><span class="p">,</span> <span class="n">bytes</span><span class="p">);</span>
            <span class="k">if</span> <span class="p">(</span><span class="n">bytes</span> <span class="o">&lt;</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span>
                <span class="n">perror</span><span class="p">(</span><span class="s">"network_send() error"</span><span class="p">);</span>
                <span class="k">continue</span><span class="p">;</span>
            <span class="p">}</span>
            <span class="n">printw</span><span class="p">(</span><span class="s">"[info] Game state sync completed (%d bytes)</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span> <span class="n">bytes</span><span class="p">);</span>
        <span class="p">}</span>

        <span class="k">for</span> <span class="p">(</span><span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">i</span> <span class="o">&lt;</span> <span class="n">FD_SETSIZE</span><span class="p">;</span> <span class="n">i</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span>
            <span class="kt">int</span> <span class="n">fd</span> <span class="o">=</span> <span class="n">client_fds</span><span class="p">[</span><span class="n">i</span><span class="p">];</span>
            <span class="k">if</span> <span class="p">(</span><span class="n">fd</span> <span class="o">&gt;=</span> <span class="mi">0</span> <span class="o">&amp;&amp;</span> <span class="n">FD_ISSET</span><span class="p">(</span><span class="n">fd</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">readfds</span><span class="p">))</span> <span class="p">{</span>
                <span class="kt">ssize_t</span> <span class="n">count</span> <span class="o">=</span> <span class="n">network_recv</span><span class="p">(</span><span class="n">fd</span><span class="p">,</span> <span class="n">buf</span><span class="p">);</span>
                <span class="k">if</span> <span class="p">(</span><span class="n">count</span> <span class="o">&lt;=</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span>
                    <span class="n">close</span><span class="p">(</span><span class="n">fd</span><span class="p">);</span>
                    <span class="n">game_state_dismiss_tank</span><span class="p">(</span><span class="o">&amp;</span><span class="n">game_state</span><span class="p">,</span> <span class="n">i</span><span class="p">);</span>
                    <span class="n">client_fds</span><span class="p">[</span><span class="n">i</span><span class="p">]</span> <span class="o">=</span> <span class="o">-</span><span class="mi">1</span><span class="p">;</span>
                    <span class="n">printw</span><span class="p">(</span><span class="s">"[info] Player [%d] disconnected</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span> <span class="n">i</span><span class="p">);</span>
                <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
                    <span class="kt">unsigned</span> <span class="n">action</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
                    <span class="n">protocol_deserialize_action</span><span class="p">(</span><span class="n">buf</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">action</span><span class="p">);</span>
                    <span class="n">printw</span><span class="p">(</span>
                        <span class="s">"[info] Received an action %s from player [%d] (%ld "</span>
                        <span class="s">"bytes)</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span>
                        <span class="n">str_action</span><span class="p">(</span><span class="n">action</span><span class="p">),</span> <span class="n">i</span><span class="p">,</span> <span class="n">count</span><span class="p">);</span>
                    <span class="n">game_state_update_tank</span><span class="p">(</span><span class="o">&amp;</span><span class="n">game_state</span><span class="p">,</span> <span class="n">i</span><span class="p">,</span> <span class="n">action</span><span class="p">);</span>
                    <span class="n">printw</span><span class="p">(</span><span class="s">"[info] Updating game state completed</span><span class="se">\n</span><span class="s">"</span><span class="p">);</span>
                <span class="p">}</span>
            <span class="p">}</span>
        <span class="p">}</span>

        <span class="c1">// Send update to the connected clients, currently with a TIMEOUT of</span>
        <span class="c1">// 16ms is roughly equal to 60 FPS. Checks for the last update sent and</span>
        <span class="c1">// adjust the select timeout so to make it as precise and smooth as</span>
        <span class="c1">// possible and respect the deadline</span>
        <span class="n">current_time_ns</span> <span class="o">=</span> <span class="n">get_microseconds_timestamp</span><span class="p">();</span>
        <span class="n">remaining_us</span> <span class="o">=</span> <span class="n">current_time_ns</span> <span class="o">-</span> <span class="n">last_update_time_ns</span><span class="p">;</span>
        <span class="k">if</span> <span class="p">(</span><span class="n">remaining_us</span> <span class="o">&gt;=</span> <span class="n">TIMEOUT</span><span class="p">)</span> <span class="p">{</span>
            <span class="c1">// Main update loop here</span>
            <span class="n">game_state_update</span><span class="p">(</span><span class="o">&amp;</span><span class="n">game_state</span><span class="p">);</span>
            <span class="kt">size_t</span> <span class="n">bytes</span> <span class="o">=</span> <span class="n">protocol_serialize_game_state</span><span class="p">(</span><span class="o">&amp;</span><span class="n">game_state</span><span class="p">,</span> <span class="n">buf</span><span class="p">);</span>
            <span class="n">broadcast</span><span class="p">(</span><span class="n">client_fds</span><span class="p">,</span> <span class="n">buf</span><span class="p">,</span> <span class="n">bytes</span><span class="p">);</span>
            <span class="n">last_update_time_ns</span> <span class="o">=</span> <span class="n">get_microseconds_timestamp</span><span class="p">();</span>
            <span class="n">tv</span><span class="p">.</span><span class="n">tv_sec</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
            <span class="n">tv</span><span class="p">.</span><span class="n">tv_usec</span> <span class="o">=</span> <span class="n">TIMEOUT</span><span class="p">;</span>
        <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
            <span class="n">tv</span><span class="p">.</span><span class="n">tv_sec</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
            <span class="n">tv</span><span class="p">.</span><span class="n">tv_usec</span> <span class="o">=</span> <span class="n">TIMEOUT</span> <span class="o">-</span> <span class="n">remaining_us</span><span class="p">;</span>
        <span class="p">}</span>
        <span class="c1">// We're using ncurses for convenience to initialize ROWS and LINES</span>
        <span class="c1">// without going raw mode in the terminal, this requires a refresh to</span>
        <span class="c1">// print the logs</span>
        <span class="n">refresh</span><span class="p">();</span>
    <span class="p">}</span>
<span class="p">}</span>

<span class="kt">int</span> <span class="nf">main</span><span class="p">(</span><span class="kt">void</span><span class="p">)</span> <span class="p">{</span>
    <span class="n">srand</span><span class="p">(</span><span class="n">time</span><span class="p">(</span><span class="nb">NULL</span><span class="p">));</span>
    <span class="c1">// Use ncurses as its handy to calculate the screen size</span>
    <span class="n">initscr</span><span class="p">();</span>
    <span class="n">scrollok</span><span class="p">(</span><span class="n">stdscr</span><span class="p">,</span> <span class="n">TRUE</span><span class="p">);</span>

    <span class="n">printw</span><span class="p">(</span><span class="s">"[info] Starting server %d %d</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span> <span class="n">COLS</span><span class="p">,</span> <span class="n">LINES</span><span class="p">);</span>
    <span class="n">game_state_init</span><span class="p">(</span><span class="o">&amp;</span><span class="n">game_state</span><span class="p">);</span>

    <span class="kt">int</span> <span class="n">server_fd</span> <span class="o">=</span> <span class="n">server_listen</span><span class="p">(</span><span class="s">"127.0.0.1"</span><span class="p">,</span> <span class="mi">6699</span><span class="p">,</span> <span class="n">BACKLOG</span><span class="p">);</span>
    <span class="k">if</span> <span class="p">(</span><span class="n">server_fd</span> <span class="o">&lt;</span> <span class="mi">0</span><span class="p">)</span> <span class="n">exit</span><span class="p">(</span><span class="n">EXIT_FAILURE</span><span class="p">);</span>

    <span class="n">server_loop</span><span class="p">(</span><span class="n">server_fd</span><span class="p">);</span>

    <span class="k">return</span> <span class="mi">0</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>

<p>An interesting bit is the syncing of the framerate client-side with the udpates
coming from the server, the initial implementation relies on <code class="language-plaintext highlighter-rouge">select</code> timing
out every at 30ms, that means that the game will update consistently when there
is not input from any client (e.g. every one is not moving), but realistically,
all the players will be moving frequently, resulting in the <code class="language-plaintext highlighter-rouge">select</code> call to
detect I/O events on the observed sockets before the <strong>TIMEOUT</strong> deadline.</p>

<p>This may generate weird and funny bugs, such as bullets flying much faster than
expected when tanks are moving. A naive but simple approach to solve the
problem is to track the timestamp in nanoseconds of each update and update the
<code class="language-plaintext highlighter-rouge">select</code> timeout accordingly.</p>

<ul>
  <li>check for remaining us (microseconds) left to reach the <strong>TIMEOUT</strong> deadline
in the current cycle, if we’re already beyond, send a gamestate update,
record the last update us and reset the <code class="language-plaintext highlighter-rouge">select</code> timeout</li>
  <li>if the remaining us have not yet reached the deadline, update the <code class="language-plaintext highlighter-rouge">select</code>
timeout to <strong>TIMEOUT</strong> - remaining</li>
</ul>

<p>This way, the update frequency is ensured to be mostly consitent at roughly the
same time each cycle.</p>

<hr />

<p><br />
<img src="/assets/images/battletank-term.gif#content-image" alt="Battletank terminal" />
<br /></p>

<p>That’s all folks, an extremely small and simple battletank should allow
multiple players to join and shoot single bullets. No collisions nor scores or
life points yet, but it’s a starting point, in roughly 600 LOC:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>battletank <span class="o">(</span>main<span class="o">)</span> <span class="nv">$ </span><span class="nb">ls</span> <span class="k">*</span>.[c,h] | xargx cloc
       8 text files.
       8 unique files.
       0 files ignored.

github.com/AlDanial/cloc v 2.02  <span class="nv">T</span><span class="o">=</span>0.02 s <span class="o">(</span>521.2 files/s, 61767.2 lines/s<span class="o">)</span>
<span class="nt">-------------------------------------------------------------------------------</span>
Language                     files          blank        comment           code
<span class="nt">-------------------------------------------------------------------------------</span>
C                                5            134            172            563
C/C++ Header                     3             17             10             52
<span class="nt">-------------------------------------------------------------------------------</span>
SUM:                             8            151            182            615
<span class="nt">-------------------------------------------------------------------------------</span>
</code></pre></div></div>

<h4 id="references">References</h4>

<ul>
  <li><a href="https://invisible-island.net/ncurses/">ncurses</a></li>
  <li><a href="https://www.raylib.com/index.html">Raylib</a></li>
  <li><a href="https://www.libsdl.org/">SDL2</a></li>
  <li><a href="https://linux.die.net/man/2/setsockopt">setsockopt</a></li>
  <li><a href="https://man7.org/linux/man-pages/man2/select.2.html">select</a></li>
  <li><a href="https://idea.popcount.org/2017-01-06-select-is-fundamentally-broken/">Select is fundamentally broken</a></li>
  <li><a href="https://beej.us/guide/bgnet/html/split/slightly-advanced-techniques.html#serialization">beej.us - serialization techniques</a></li>
</ul>]]></content><author><name></name></author><category term="c" /><category term="game-development" /><category term="low-level" /><category term="terminal" /><category term="ncurses" /><summary type="html"><![CDATA[Dumb terminal-based implementation of the old classic Battletank]]></summary></entry><entry><title type="html">Rust code platform - part 1</title><link href="/posts/rust-reetcode/" rel="alternate" type="text/html" title="Rust code platform - part 1" /><published>2024-09-22T00:00:00+00:00</published><updated>2024-09-22T00:00:00+00:00</updated><id>/posts/rust-reetcode</id><content type="html" xml:base="/posts/rust-reetcode/"><![CDATA[<p>Small project idea to have some fun with Rust and try to build something as
close as possible to being production-ready. This project also aims to dabble a
bit with the language on the web backend side.</p>

<h3 id="constraints">Constraints</h3>

<ul>
  <li>Minimal requirements</li>
  <li>Observability</li>
</ul>

<p>The idea I was thinking about is a simplified coding platform in the neetcode
style. The system will be seeded with a set of problems, users can register,
admin users can also upload new problems (although authentication and
authorization may be out of scope for the time being). Problems can be run
inside self-contained environments (think about Docker containers, maybe
providing a subset of pre-built images for each programming language offered,
C, C++, Python and Go can be a good starting point).</p>

<h3 id="tech-stack">Tech stack</h3>

<p>Rust is the chosen language for this project, I’m not really familiar yet with
the current trends and best practices, last time I checked <code class="language-plaintext highlighter-rouge">actix</code> and <code class="language-plaintext highlighter-rouge">sqlx</code>
were the backbone of any backend application. After some recognition work, this
is what I came up with:</p>

<ul>
  <li>Actix - REST APIs</li>
  <li>Postgres - Diesel or sqlx</li>
  <li>Prometheus</li>
  <li>Grafana</li>
</ul>

<p>Axum and Pavex also look interesting, however it’s not the point of the project
to explore different  frameworks to achieve the same result, it’s the overall
practicality of Rust for the web that I’d like to refresh. We’re probably gonna
use Docker and <code class="language-plaintext highlighter-rouge">docker-compose</code> to run the entire environment locally in dev,
it’s a convenient way to emulate a staging/prod environment without the hassle
of deploying stuff to an Heroku dyno or any other cloud provider.</p>

<h3 id="data-model">Data model</h3>

<p>The main entity in the application is represented by the problems. They will
include a title, a description and some metadata such as the category, the
difficulty and a skeleton based on the language, something like</p>

<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w">
    </span><span class="err">title:</span><span class="w"> </span><span class="s2">"Two Sum"</span><span class="p">,</span><span class="w">
    </span><span class="err">description:</span><span class="w"> </span><span class="s2">"Given an array of integers `nums` and an integer `target`, return the integers `i` and `j`  such that `nums[i] + nums[j] = target`.."</span><span class="p">,</span><span class="w">
    </span><span class="err">categories:</span><span class="w"> </span><span class="p">[</span><span class="s2">"arrays"</span><span class="p">],</span><span class="w">
    </span><span class="err">difficulty:</span><span class="w"> </span><span class="s2">"easy"</span><span class="p">,</span><span class="w">
    </span><span class="err">starting_snippets:</span><span class="w"> </span><span class="p">{</span><span class="w">
        </span><span class="nl">"c"</span><span class="p">:</span><span class="w"> </span><span class="s2">"int *two_sum(int *nums, size_t len, int target) .."</span><span class="p">,</span><span class="w">
        </span><span class="nl">"python"</span><span class="p">:</span><span class="w"> </span><span class="s2">"def two_sum(nums, target)..."</span><span class="w">
    </span><span class="p">},</span><span class="w">
    </span><span class="err">solved:</span><span class="w"> </span><span class="s2">"false"</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>

<p>To start very simple they could be stored in a JSON or YAML static file on
a filesystem, I prefer to store them in the DB to allow easier query
capabilities and at a later stage, extensibility and upload of new problems.
Every problem can have multiple solutions clearly, so that would be another
table to be joined as a <code class="language-plaintext highlighter-rouge">has_many</code> relationship with the problem.</p>

<p>To cut short, a loosely detailed definition of the schemas we’re going to need:</p>

<ul>
  <li>User
    <ul>
      <li>email: string</li>
      <li>nickname: string</li>
      <li><strong>has_many</strong>(Problem)</li>
    </ul>
  </li>
  <li>Problem
    <ul>
      <li>title: string</li>
      <li>description: string</li>
      <li>categories: list(string)</li>
      <li>difficulty: string</li>
      <li>starting_snippets: map</li>
      <li>test_cases: list(string)</li>
      <li>solved: boolean</li>
      <li>editorial: map</li>
      <li><strong>has_many</strong>(Solution)</li>
    </ul>
  </li>
  <li>Solution
    <ul>
      <li>description: string</li>
      <li>snippet: string</li>
    </ul>
  </li>
</ul>

<p>Keeping things simple as a first draft, we can assume that the images
repositories with various metadata (e.g. languages versions, OS etc) can be
stored locally in a static JSON or YAML file.</p>

<h3 id="apis">APIs</h3>

<p>It will be a pure REST application, no frontend at all, but it should provide
all the required to be easily integrated in a JS app.</p>

<p><strong>ASSUMPION</strong> we can start with a set of problems and editorial pre-seeded in
the system, on a later stage we can decide to increment the functionalities and
allow users roles, auth, and upload of new problems, contests maybe and notifications
to subscribed users.</p>

<h4 id="user-management">User management</h4>

<p>Creation of a new user and retrieve of the user, probably with some sort of
stats and solved problems etc.</p>

<ul>
  <li><strong>POST</strong>  <code class="language-plaintext highlighter-rouge">/v1/users</code></li>
  <li><strong>GET</strong>   <code class="language-plaintext highlighter-rouge">/v1/users/:user_id</code></li>
</ul>

<h4 id="problems-management">Problems management</h4>

<p>Creation of a new problem, listing of all the problems, probably opt-in filters
by category, difficulty and pagination etc; retrieve of a specific problem by
ID. Definitely submission of a solution to any given problem and listing.</p>

<ul>
  <li><strong>GET</strong>   <code class="language-plaintext highlighter-rouge">/v1/problems/:problem_id</code></li>
  <li><strong>GET</strong>   <code class="language-plaintext highlighter-rouge">/v1/problems</code></li>
  <li><strong>POST</strong>  <code class="language-plaintext highlighter-rouge">/v1/problems/:problem_id</code></li>
</ul>

<h3 id="authentication--rbac">Authentication &amp; RBAC</h3>

<p><strong>TBD</strong></p>]]></content><author><name></name></author><category term="rust" /><category term="backend" /><category term="database" /><summary type="html"><![CDATA[Small project idea to have some fun with Rust and try to build something as close as possible to being production-ready. This project also aims to dabble a bit with the language on the web backend side. Constraints Minimal requirements Observability The idea I was thinking about is a simplified coding platform in the neetcode style. The system will be seeded with a set of problems, users can register, admin users can also upload new problems (although authentication and authorization may be out of scope for the time being). Problems can be run inside self-contained environments (think about Docker containers, maybe providing a subset of pre-built images for each programming language offered, C, C++, Python and Go can be a good starting point). Tech stack Rust is the chosen language for this project, I’m not really familiar yet with the current trends and best practices, last time I checked actix and sqlx were the backbone of any backend application. After some recognition work, this is what I came up with: Actix - REST APIs Postgres - Diesel or sqlx Prometheus Grafana Axum and Pavex also look interesting, however it’s not the point of the project to explore different frameworks to achieve the same result, it’s the overall practicality of Rust for the web that I’d like to refresh. We’re probably gonna use Docker and docker-compose to run the entire environment locally in dev, it’s a convenient way to emulate a staging/prod environment without the hassle of deploying stuff to an Heroku dyno or any other cloud provider. Data model The main entity in the application is represented by the problems. They will include a title, a description and some metadata such as the category, the difficulty and a skeleton based on the language, something like { title: "Two Sum", description: "Given an array of integers `nums` and an integer `target`, return the integers `i` and `j` such that `nums[i] + nums[j] = target`..", categories: ["arrays"], difficulty: "easy", starting_snippets: { "c": "int *two_sum(int *nums, size_t len, int target) ..", "python": "def two_sum(nums, target)..." }, solved: "false" } To start very simple they could be stored in a JSON or YAML static file on a filesystem, I prefer to store them in the DB to allow easier query capabilities and at a later stage, extensibility and upload of new problems. Every problem can have multiple solutions clearly, so that would be another table to be joined as a has_many relationship with the problem. To cut short, a loosely detailed definition of the schemas we’re going to need: User email: string nickname: string has_many(Problem) Problem title: string description: string categories: list(string) difficulty: string starting_snippets: map test_cases: list(string) solved: boolean editorial: map has_many(Solution) Solution description: string snippet: string Keeping things simple as a first draft, we can assume that the images repositories with various metadata (e.g. languages versions, OS etc) can be stored locally in a static JSON or YAML file. APIs It will be a pure REST application, no frontend at all, but it should provide all the required to be easily integrated in a JS app. ASSUMPION we can start with a set of problems and editorial pre-seeded in the system, on a later stage we can decide to increment the functionalities and allow users roles, auth, and upload of new problems, contests maybe and notifications to subscribed users. User management Creation of a new user and retrieve of the user, probably with some sort of stats and solved problems etc. POST /v1/users GET /v1/users/:user_id Problems management Creation of a new problem, listing of all the problems, probably opt-in filters by category, difficulty and pagination etc; retrieve of a specific problem by ID. Definitely submission of a solution to any given problem and listing. GET /v1/problems/:problem_id GET /v1/problems POST /v1/problems/:problem_id Authentication &amp; RBAC TBD]]></summary></entry><entry><title type="html">Time-series adventures</title><link href="/posts/timeseries-adventures/" rel="alternate" type="text/html" title="Time-series adventures" /><published>2024-04-13T00:00:00+00:00</published><updated>2024-04-13T00:00:00+00:00</updated><id>/posts/timeseries-adventures</id><content type="html" xml:base="/posts/timeseries-adventures/"><![CDATA[<p>Databases, how they interact with the filesystem at low-level, have always
represented a fascinating topic for me. I implemented countless in-memory
key-value stores at various level of abstraction and various stages of
development; with multiple languages, Scala, Python, Elixir, Go. Gradually
going more and more in details, it was finally time to try and write something
a bit more challenging.</p>

<p>A relational database was too big of a step to begin with and being the
persistence the very first big problem I could think of facing almost
immediately, I wanted something that could be implemented based on a simple but
extremely versatile and powerful structure, the log.
I wanted something that could be implemented on top of a this simple concept,
in my mind the system should’ve basically been a variation of a Kafka commit
log, but instead of forwarding binary chunks to connected consumers (sendfile
and how Kafka works internally is another quite interesting topic I explored
and I’d like to dig into a bit more in the future); a time-series seemed
interesting and fitting my initial thoughts.</p>

<p>The programming language to write it in was the next decision to make, I considered a couple of choices:</p>

<ul>
  <li><strong>Elixir/Erlang</strong> - a marvel, fantastic back-end language, the BEAM is a piece of art, I use Elixir daily at work though so I craved for something of a change my scenery</li>
  <li><strong>Go</strong> - great design, simplicity and productivity like no others, also a bit boring too</li>
  <li><strong>Rust</strong> - different beast from anything else, requires discipline and perseverance to retain productivity, I like it, but for it’s very nature and focus on safety, I always feel kinda constrained and fighting the compiler more than the problem I’m solving (gotta put some more effort into it to make it click completely). Will certainly pick it up again for something else in the future.</li>
  <li><strong>C++</strong> - Nope, this was a joke, didn’t consider this at all</li>
</ul>

<p>Eventually I always find myself coming back to a place of comfort with C. I
can’t really say the reason, it’s a powerful tool with many dangers and when I
used it for my toy projects in the past, I almost never felt enough confidence
to say “this could to be used in a prod environment”. I fought with myself on
it for many years, learning Rust, flirting with Go, but I eventually gave up
and embraced my comfort place. They’re great languages, I used Go for work for
a while and Rust seems a good C++ replacement most of time, provided you use it
with consistency (borrow checker and lifetimes can be hellish to deal and
re-learn if out of shape).</p>

<p>With C, I reckon it all boils down to it’s compactness, conceptually simple and
leaving very little to imagination of what’s happening under the hood. I’m
perfectly conscious how easy it is to introduce memory-related bugs, and
implementing the same dynamic array for each project can get old pretty fast;
but what matters to me, ultimately, is having fun, and C provides me with that.
Timeseries have always been a fascinating topic for me</p>

<h2 id="design">Design</h2>

<p>Conceptually it’s not really an efficient or particularly smart architecture,
I approached the implementation on a best-effort way, the idea being to delay
as much as possible the need of non-basic data-structures such as arrays.</p>

<p>The two main segments are just fixed size arrays where each position stores a
pointer to the first position of a dynamic array. This to be able to store all
the data points included in the time range that the segment covers</p>

<p><img src="/assets/images/Roach_segment.png#content-image-1" alt="Segments" /></p>

<p>Each time a new record is to be inserted, first it is appended as a new entry
in the Write-Ahead-Log (WAL) and only then it is store in the in-memory segment
where it belongs. The WAL acts as a disaster recovery policy, it is solely
responsible for storing incoming records in order to be able to read them and
re-populate the segment on restarts.</p>

<p><img src="/assets/images/roach_disk_2.png#content-image-1" alt="Disk_1" /></p>

<p>In short:</p>

<ul>
  <li>two segments of 15 minutes each
    <ul>
      <li>each position represents a second, so 900 is the length of the segments</li>
      <li>each second points to a dynamic array storing positions based on the microsecond portion of the timestamp of the point</li>
    </ul>
  </li>
  <li>each time a new point is inserted, it is first stored on a WAL on disk in order to be able to recover in case of crash</li>
  <li>once the main segment is full, or a timestamp too far in the future is received (i.e. more than 15 minutes past the 1st point store)
    <ul>
      <li>the tail segment gets persisted on disk and becomes immutable, WAL is cleared</li>
      <li>the head segment gets persisted on disk and becomes immutable, WAL is cleared</li>
      <li>the head segment becomes the new tail segment</li>
      <li>a new head in-memory segment is generated</li>
    </ul>
  </li>
  <li>immutable segments on disk are paired with an index file to read records from the past</li>
</ul>

<p>Although there are plenty of improvements, fixes and  other things to take care
of, this function is roughly at the heart of the logic (there is a secondary
logic in here for which we flush on disk also based on the WAL size, nothing of
interest for the purpose of simplicity)</p>

<hr />

<hr />

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cm">/*
 * Set a record in a timeseries.
 *
 * This function sets a record with the specified timestamp and value in the
 * given timeseries. The function handles the storage of records in memory and
 * on disk to ensure data integrity and efficient usage of resources.
 *
 * @param ts A pointer to the Timeseries structure representing the timeseries.
 * @param timestamp The timestamp of the record to be set, in nanoseconds.
 * @param value The value of the record to be set.
 * @return 0 on success, -1 on failure.
 */</span>
<span class="kt">int</span> <span class="nf">ts_insert</span><span class="p">(</span><span class="n">Timeseries</span> <span class="o">*</span><span class="n">ts</span><span class="p">,</span> <span class="kt">uint64_t</span> <span class="n">timestamp</span><span class="p">,</span> <span class="n">double_t</span> <span class="n">value</span><span class="p">)</span> <span class="p">{</span>
    <span class="c1">// Extract seconds and nanoseconds from timestamp</span>
    <span class="kt">uint64_t</span> <span class="n">sec</span> <span class="o">=</span> <span class="n">timestamp</span> <span class="o">/</span> <span class="p">(</span><span class="kt">uint64_t</span><span class="p">)</span><span class="mf">1e9</span><span class="p">;</span>
    <span class="kt">uint64_t</span> <span class="n">nsec</span> <span class="o">=</span> <span class="n">timestamp</span> <span class="o">%</span> <span class="p">(</span><span class="kt">uint64_t</span><span class="p">)</span><span class="mf">1e9</span><span class="p">;</span>

    <span class="kt">char</span> <span class="n">pathbuf</span><span class="p">[</span><span class="n">MAX_PATH_SIZE</span><span class="p">];</span>
    <span class="n">snprintf</span><span class="p">(</span><span class="n">pathbuf</span><span class="p">,</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">pathbuf</span><span class="p">),</span> <span class="s">"%s/%s/%s"</span><span class="p">,</span> <span class="n">BASE_PATH</span><span class="p">,</span> <span class="n">ts</span><span class="o">-&gt;</span><span class="n">db_data_path</span><span class="p">,</span>
             <span class="n">ts</span><span class="o">-&gt;</span><span class="n">name</span><span class="p">);</span>

    <span class="c1">// if the limit is reached we dump the chunks into disk and create 2 new</span>
    <span class="c1">// ones</span>
    <span class="k">if</span> <span class="p">(</span><span class="n">wal_size</span><span class="p">(</span><span class="o">&amp;</span><span class="n">ts</span><span class="o">-&gt;</span><span class="n">head</span><span class="p">.</span><span class="n">wal</span><span class="p">)</span> <span class="o">&gt;=</span> <span class="n">TS_FLUSH_SIZE</span><span class="p">)</span> <span class="p">{</span>
        <span class="kt">uint64_t</span> <span class="n">base</span> <span class="o">=</span> <span class="n">ts</span><span class="o">-&gt;</span><span class="n">prev</span><span class="p">.</span><span class="n">base_offset</span> <span class="o">&gt;</span> <span class="mi">0</span> <span class="o">?</span> <span class="n">ts</span><span class="o">-&gt;</span><span class="n">prev</span><span class="p">.</span><span class="n">base_offset</span>
                                                 <span class="o">:</span> <span class="n">ts</span><span class="o">-&gt;</span><span class="n">head</span><span class="p">.</span><span class="n">base_offset</span><span class="p">;</span>
        <span class="kt">size_t</span> <span class="n">partition_nr</span> <span class="o">=</span> <span class="n">ts</span><span class="o">-&gt;</span><span class="n">partition_nr</span> <span class="o">==</span> <span class="mi">0</span> <span class="o">?</span> <span class="mi">0</span> <span class="o">:</span> <span class="n">ts</span><span class="o">-&gt;</span><span class="n">partition_nr</span> <span class="o">-</span> <span class="mi">1</span><span class="p">;</span>

        <span class="k">if</span> <span class="p">(</span><span class="n">ts</span><span class="o">-&gt;</span><span class="n">partitions</span><span class="p">[</span><span class="n">partition_nr</span><span class="p">].</span><span class="n">clog</span><span class="p">.</span><span class="n">base_timestamp</span> <span class="o">&lt;</span> <span class="n">base</span><span class="p">)</span> <span class="p">{</span>
            <span class="k">if</span> <span class="p">(</span><span class="n">partition_init</span><span class="p">(</span><span class="o">&amp;</span><span class="n">ts</span><span class="o">-&gt;</span><span class="n">partitions</span><span class="p">[</span><span class="n">ts</span><span class="o">-&gt;</span><span class="n">partition_nr</span><span class="p">],</span> <span class="n">pathbuf</span><span class="p">,</span>
                               <span class="n">base</span><span class="p">)</span> <span class="o">&lt;</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span>
                <span class="k">return</span> <span class="o">-</span><span class="mi">1</span><span class="p">;</span>
            <span class="p">}</span>
            <span class="n">partition_nr</span> <span class="o">=</span> <span class="n">ts</span><span class="o">-&gt;</span><span class="n">partition_nr</span><span class="p">;</span>
            <span class="n">ts</span><span class="o">-&gt;</span><span class="n">partition_nr</span><span class="o">++</span><span class="p">;</span>
        <span class="p">}</span>

        <span class="c1">// Dump chunks into disk and create new ones</span>
        <span class="k">if</span> <span class="p">(</span><span class="n">partition_flush_chunk</span><span class="p">(</span><span class="o">&amp;</span><span class="n">ts</span><span class="o">-&gt;</span><span class="n">partitions</span><span class="p">[</span><span class="n">partition_nr</span><span class="p">],</span> <span class="o">&amp;</span><span class="n">ts</span><span class="o">-&gt;</span><span class="n">prev</span><span class="p">)</span> <span class="o">&lt;</span> <span class="mi">0</span><span class="p">)</span>
            <span class="k">return</span> <span class="o">-</span><span class="mi">1</span><span class="p">;</span>
        <span class="k">if</span> <span class="p">(</span><span class="n">partition_flush_chunk</span><span class="p">(</span><span class="o">&amp;</span><span class="n">ts</span><span class="o">-&gt;</span><span class="n">partitions</span><span class="p">[</span><span class="n">partition_nr</span><span class="p">],</span> <span class="o">&amp;</span><span class="n">ts</span><span class="o">-&gt;</span><span class="n">head</span><span class="p">)</span> <span class="o">&lt;</span> <span class="mi">0</span><span class="p">)</span>
            <span class="k">return</span> <span class="o">-</span><span class="mi">1</span><span class="p">;</span>

        <span class="c1">// Reset clean both head and prev in-memory chunks</span>
        <span class="n">ts_deinit</span><span class="p">(</span><span class="n">ts</span><span class="p">);</span>
    <span class="p">}</span>
    <span class="c1">// Let it crash for now if the timestamp is out of bounds in the ooo</span>
    <span class="k">if</span> <span class="p">(</span><span class="n">sec</span> <span class="o">&lt;</span> <span class="n">ts</span><span class="o">-&gt;</span><span class="n">head</span><span class="p">.</span><span class="n">base_offset</span><span class="p">)</span> <span class="p">{</span>
        <span class="c1">// If the chunk is empty, it also means the base offset is 0, we set</span>
        <span class="c1">// it here with the first record inserted</span>
        <span class="k">if</span> <span class="p">(</span><span class="n">ts</span><span class="o">-&gt;</span><span class="n">prev</span><span class="p">.</span><span class="n">base_offset</span> <span class="o">==</span> <span class="mi">0</span><span class="p">)</span>
            <span class="n">ts_chunk_init</span><span class="p">(</span><span class="o">&amp;</span><span class="n">ts</span><span class="o">-&gt;</span><span class="n">prev</span><span class="p">,</span> <span class="n">pathbuf</span><span class="p">,</span> <span class="n">sec</span><span class="p">,</span> <span class="mi">0</span><span class="p">);</span>

        <span class="c1">// Persist to disk for disaster recovery</span>
        <span class="n">wal_append_record</span><span class="p">(</span><span class="o">&amp;</span><span class="n">ts</span><span class="o">-&gt;</span><span class="n">prev</span><span class="p">.</span><span class="n">wal</span><span class="p">,</span> <span class="n">timestamp</span><span class="p">,</span> <span class="n">value</span><span class="p">);</span>

        <span class="c1">// If we successfully insert the record, we can return</span>
        <span class="k">if</span> <span class="p">(</span><span class="n">ts_chunk_record_fit</span><span class="p">(</span><span class="o">&amp;</span><span class="n">ts</span><span class="o">-&gt;</span><span class="n">prev</span><span class="p">,</span> <span class="n">sec</span><span class="p">)</span> <span class="o">==</span> <span class="mi">0</span><span class="p">)</span>
            <span class="k">return</span> <span class="n">ts_chunk_set_record</span><span class="p">(</span><span class="o">&amp;</span><span class="n">ts</span><span class="o">-&gt;</span><span class="n">prev</span><span class="p">,</span> <span class="n">sec</span><span class="p">,</span> <span class="n">nsec</span><span class="p">,</span> <span class="n">value</span><span class="p">);</span>
    <span class="p">}</span>

    <span class="k">if</span> <span class="p">(</span><span class="n">ts</span><span class="o">-&gt;</span><span class="n">head</span><span class="p">.</span><span class="n">base_offset</span> <span class="o">==</span> <span class="mi">0</span><span class="p">)</span>
        <span class="n">ts_chunk_init</span><span class="p">(</span><span class="o">&amp;</span><span class="n">ts</span><span class="o">-&gt;</span><span class="n">head</span><span class="p">,</span> <span class="n">pathbuf</span><span class="p">,</span> <span class="n">sec</span><span class="p">,</span> <span class="mi">1</span><span class="p">);</span>

    <span class="c1">// Persist to disk for disaster recovery</span>
    <span class="n">wal_append_record</span><span class="p">(</span><span class="o">&amp;</span><span class="n">ts</span><span class="o">-&gt;</span><span class="n">head</span><span class="p">.</span><span class="n">wal</span><span class="p">,</span> <span class="n">timestamp</span><span class="p">,</span> <span class="n">value</span><span class="p">);</span>
    <span class="c1">// Check if the timestamp is in range of the current chunk, otherwise</span>
    <span class="c1">// create a new in-memory segment</span>
    <span class="k">if</span> <span class="p">(</span><span class="n">ts_chunk_record_fit</span><span class="p">(</span><span class="o">&amp;</span><span class="n">ts</span><span class="o">-&gt;</span><span class="n">head</span><span class="p">,</span> <span class="n">sec</span><span class="p">)</span> <span class="o">&lt;</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span>
        <span class="c1">// Flush the prev chunk to persistence</span>
        <span class="k">if</span> <span class="p">(</span><span class="n">partition_flush_chunk</span><span class="p">(</span><span class="o">&amp;</span><span class="n">ts</span><span class="o">-&gt;</span><span class="n">partitions</span><span class="p">[</span><span class="n">ts</span><span class="o">-&gt;</span><span class="n">partition_nr</span><span class="p">],</span>
                                  <span class="o">&amp;</span><span class="n">ts</span><span class="o">-&gt;</span><span class="n">prev</span><span class="p">)</span> <span class="o">&lt;</span> <span class="mi">0</span><span class="p">)</span>
            <span class="k">return</span> <span class="o">-</span><span class="mi">1</span><span class="p">;</span>
        <span class="c1">// Clean up the prev chunk and delete it's WAL</span>
        <span class="n">ts_chunk_destroy</span><span class="p">(</span><span class="o">&amp;</span><span class="n">ts</span><span class="o">-&gt;</span><span class="n">prev</span><span class="p">);</span>
        <span class="n">wal_delete</span><span class="p">(</span><span class="o">&amp;</span><span class="n">ts</span><span class="o">-&gt;</span><span class="n">prev</span><span class="p">.</span><span class="n">wal</span><span class="p">);</span>
        <span class="c1">// Set the current head as new prev</span>
        <span class="n">ts</span><span class="o">-&gt;</span><span class="n">prev</span> <span class="o">=</span> <span class="n">ts</span><span class="o">-&gt;</span><span class="n">head</span><span class="p">;</span>
        <span class="c1">// Reset the current head as new head</span>
        <span class="n">ts_chunk_destroy</span><span class="p">(</span><span class="o">&amp;</span><span class="n">ts</span><span class="o">-&gt;</span><span class="n">head</span><span class="p">);</span>
        <span class="n">wal_delete</span><span class="p">(</span><span class="o">&amp;</span><span class="n">ts</span><span class="o">-&gt;</span><span class="n">head</span><span class="p">.</span><span class="n">wal</span><span class="p">);</span>
    <span class="p">}</span>
    <span class="c1">// Insert it into the head chunk</span>
    <span class="k">return</span> <span class="n">ts_chunk_set_record</span><span class="p">(</span><span class="o">&amp;</span><span class="n">ts</span><span class="o">-&gt;</span><span class="n">head</span><span class="p">,</span> <span class="n">sec</span><span class="p">,</span> <span class="n">nsec</span><span class="p">,</span> <span class="n">value</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>

<h2 id="the-current-state">The current state</h2>

<p>At the current stage of development, it’s still a very crude core set of
features but it seems to be working as expected, with definitely many edge
cases and assertions to solve; the heart of the DB is there, and can be built
into a dynamic library to be used on a server. The repository can be found at
<a href="https://github.com/codepr/roach">https://github.com/codepr/roach</a>.</p>

<h3 id="main-features">Main features</h3>

<ul>
  <li><strong>Fixed size records:</strong> to keep things simple each record is represented by just a timestamp with nanoseconds precision and a double</li>
  <li><strong>In memory segments:</strong> Data is stored in time series format, allowing efficient querying and retrieval based on timestamp, with the last slice of data in memory, composed by two segments (currently covering 15 minutes of data each)
    <ul>
      <li>The last 15 minutes of data</li>
      <li>The previous 15 minutes for records out of order, totaling 30 minutes</li>
    </ul>
  </li>
  <li><strong>Commit Log:</strong> Persistence is achieved using a commit log at the base, ensuring durability of data on disk.</li>
  <li><strong>Write-Ahead Log (WAL):</strong> In-memory segments are managed using a write-ahead log, providing durability and recovery in case of crashes or failures.</li>
</ul>

<h3 id="whats-in-the-road-map">What’s in the road map</h3>

<ul>
  <li>Duplicate points policy</li>
  <li>CRC32 of records for data integrity</li>
  <li>Adopt an arena for memory allocations</li>
  <li>Memory mapped indexes, above a threshold enable binary search</li>
  <li>Schema definitions</li>
</ul>

<h3 id="timeseries-library-apis">Timeseries library APIs</h3>

<ul>
  <li><code class="language-plaintext highlighter-rouge">tsdb_init(1)</code> creates a new database</li>
  <li><code class="language-plaintext highlighter-rouge">tsdb_close(1)</code> closes the database</li>
  <li><code class="language-plaintext highlighter-rouge">ts_create(3)</code> creates a new Timeseries in a given database</li>
  <li><code class="language-plaintext highlighter-rouge">ts_get(2)</code> retrieve an existing Timeseries from a database</li>
  <li><code class="language-plaintext highlighter-rouge">ts_insert(3)</code> inserts a new point into the Timeseries</li>
  <li><code class="language-plaintext highlighter-rouge">ts_find(3)</code> finds a point inside the Timeseries</li>
  <li><code class="language-plaintext highlighter-rouge">ts_range(4)</code> finds a range of points in the Timeseries, returning a vector with the results</li>
  <li><code class="language-plaintext highlighter-rouge">ts_close(1)</code> closes a Timeseries</li>
</ul>

<h4 id="writing-a-makefile">Writing a Makefile</h4>

<p>A simple <code class="language-plaintext highlighter-rouge">Makefile</code> to build the library as a <code class="language-plaintext highlighter-rouge">.so</code> file that can be linked to any project as an external lightweight dependency or used alone.</p>
<hr />

<hr />

<div class="language-makefile highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">CC</span><span class="o">=</span>gcc
<span class="nv">CFLAGS</span><span class="o">=</span><span class="nt">-Wall</span> <span class="nt">-Wextra</span> <span class="nt">-Werror</span> <span class="nt">-Wunused</span> <span class="nt">-std</span><span class="o">=</span>c11 <span class="nt">-pedantic</span> <span class="nt">-ggdb</span> <span class="nt">-D_DEFAULT_SOURCE</span><span class="o">=</span>200809L <span class="nt">-Iinclude</span> <span class="nt">-Isrc</span>
<span class="nv">LDFLAGS</span><span class="o">=</span><span class="nt">-L</span><span class="nb">.</span> <span class="nt">-ltimeseries</span>

<span class="nv">LIB_SOURCES</span><span class="o">=</span>src/timeseries.c src/partition.c src/wal.c src/disk_io.c src/binary.c src/logging.c src/persistent_index.c src/commit_log.c
<span class="nv">LIB_OBJECTS</span><span class="o">=</span><span class="nv">$(LIB_SOURCES:.c=.o)</span>

<span class="nl">libtimeseries.so</span><span class="o">:</span> <span class="nf">$(LIB_OBJECTS)</span>
	<span class="nv">$(CC)</span> <span class="nt">-shared</span> <span class="nt">-o</span> <span class="nv">$@</span> <span class="nv">$(LIB_OBJECTS)</span>
<span class="nl">%.o</span><span class="o">:</span> <span class="nf">%.c</span>
	<span class="nv">$(CC)</span> <span class="nv">$(CFLAGS)</span> <span class="nt">-fPIC</span> <span class="nt">-c</span> <span class="nv">$&lt;</span> <span class="nt">-o</span> <span class="nv">$@</span>
<span class="nl">clean</span><span class="o">:</span>
	<span class="p">@</span><span class="nb">rm</span> <span class="nt">-f</span> <span class="nv">$(LIB_OBJECTS)</span> libtimeseries.so

</code></pre></div></div>

<p>Building the library is a simple thing now, just a single command <code class="language-plaintext highlighter-rouge">make</code>, to link it to a main, just a one liner</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>gcc <span class="nt">-o</span> my_project main.c <span class="nt">-I</span>/path/to/timeseries/include <span class="nt">-L</span>/path/to/timeseries <span class="nt">-ltimeseries</span>
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">LD_LIBRARY_PATH=/path/to/timeseries.so ./my_project</code> to run the main, a basic example of interaction with the library</p>
<hr />

<hr />

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code>
<span class="cp">#include</span> <span class="cpf">"timeseries.h"</span><span class="cp">
</span>
<span class="kt">int</span> <span class="nf">main</span><span class="p">()</span> <span class="p">{</span>
    <span class="c1">// Initialize the database</span>
    <span class="n">Timeseries_DB</span> <span class="o">*</span><span class="n">db</span> <span class="o">=</span> <span class="n">tsdb_init</span><span class="p">(</span><span class="s">"testdb"</span><span class="p">);</span>
    <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">db</span><span class="p">)</span>
        <span class="n">abort</span><span class="p">();</span>
    <span class="c1">// Create a timeseries, retention is not implemented yet</span>
    <span class="n">Timeseries</span> <span class="o">*</span><span class="n">ts</span> <span class="o">=</span> <span class="n">ts_create</span><span class="p">(</span><span class="n">db</span><span class="p">,</span> <span class="s">"temperatures"</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="n">DP_IGNORE</span><span class="p">);</span>
    <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">ts</span><span class="p">)</span>
        <span class="n">abort</span><span class="p">();</span>
    <span class="c1">// Insert records into the timeseries</span>
    <span class="n">ts_insert</span><span class="p">(</span><span class="o">&amp;</span><span class="n">ts</span><span class="p">,</span> <span class="mi">1710033421702081792</span><span class="p">,</span> <span class="mi">25</span><span class="p">.</span><span class="mi">5</span><span class="p">);</span>
    <span class="n">ts_insert</span><span class="p">(</span><span class="o">&amp;</span><span class="n">ts</span><span class="p">,</span> <span class="mi">1710033422047657984</span><span class="p">,</span> <span class="mi">26</span><span class="p">.</span><span class="mi">0</span><span class="p">);</span>
    <span class="c1">// Find a record by timestamp</span>
    <span class="n">Record</span> <span class="n">r</span><span class="p">;</span>
    <span class="kt">int</span> <span class="n">result</span> <span class="o">=</span> <span class="n">ts_find</span><span class="p">(</span><span class="o">&amp;</span><span class="n">ts</span><span class="p">,</span> <span class="mi">1710033422047657984</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">r</span><span class="p">);</span>
    <span class="k">if</span> <span class="p">(</span><span class="n">result</span> <span class="o">==</span> <span class="mi">0</span><span class="p">)</span>
        <span class="n">printf</span><span class="p">(</span><span class="s">"Record found: timestamp=%lu, value=%.2lf</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span> <span class="n">r</span><span class="p">.</span><span class="n">timestamp</span><span class="p">,</span> <span class="n">r</span><span class="p">.</span><span class="n">value</span><span class="p">);</span>
    <span class="k">else</span>
        <span class="n">printf</span><span class="p">(</span><span class="s">"Record not found.</span><span class="se">\n</span><span class="s">"</span><span class="p">);</span>
    <span class="c1">// Release the timeseries</span>
    <span class="n">ts_close</span><span class="p">(</span><span class="o">&amp;</span><span class="n">ts</span><span class="p">);</span>
    <span class="c1">// Close the database</span>
    <span class="n">tsdb_close</span><span class="p">(</span><span class="n">db</span><span class="p">);</span>

    <span class="k">return</span> <span class="mi">0</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>

<h2 id="a-server-draft">A server draft</h2>

<p>Event based server (rely on <a href="https://github.com/codepr/ev.git">ev</a> at least
initially), TCP as the main transport protocol, text-based custom protocol
inspired by RESP but simpler:</p>

<ul>
  <li><code class="language-plaintext highlighter-rouge">$</code> string type</li>
  <li><code class="language-plaintext highlighter-rouge">!</code> error type</li>
  <li><code class="language-plaintext highlighter-rouge">#</code> array type</li>
  <li><code class="language-plaintext highlighter-rouge">:</code> integer type</li>
  <li><code class="language-plaintext highlighter-rouge">;</code> float type</li>
  <li><code class="language-plaintext highlighter-rouge">\r\n</code> delimiter</li>
</ul>

<p>With the following encoding:</p>

<p><code class="language-plaintext highlighter-rouge">&lt;type&gt;&lt;length&gt;\r\n&lt;payload&gt;\r\n</code></p>

<p>For example a simple hello string would be</p>

<p><code class="language-plaintext highlighter-rouge">$5\r\nHello\r\n</code></p>

<h3 id="simple-query-language">Simple query language</h3>

<p>Definition of a simple, text-based format for clients to interact with the server, allowing them to send commands and receive responses.</p>

<h4 id="basic-outline">Basic outline</h4>

<ul>
  <li><strong>Text-Based Format:</strong> Use a text-based format where each command and response is represented as a single line of text.</li>
  <li><strong>Commands:</strong> Define a set of commands that clients can send to the server to perform various operations such as inserting data, querying data, and managing the database.</li>
  <li><strong>Responses:</strong> Define the format of responses that the server sends back to clients after processing commands. Responses should provide relevant information or acknowledge the completion of the requested operation.</li>
</ul>

<h4 id="core-commands">Core commands</h4>

<p>Define the basic operations in a SQL-like query language</p>

<ul>
  <li>
    <p><strong>CREATE</strong> creates a database or a timeseries</p>

    <p><code class="language-plaintext highlighter-rouge">CREATE &lt;database name&gt;</code></p>

    <p><code class="language-plaintext highlighter-rouge">CREATE &lt;timeseries name&gt; INTO &lt;database name&gt; [&lt;retention period&gt;] [&lt;duplication policy&gt;]</code></p>
  </li>
  <li>
    <p><strong>INSERT</strong> insertion of point(s) in a timeseries</p>

    <p><code class="language-plaintext highlighter-rouge">INSERT &lt;timeseries name&gt; INTO &lt;database name&gt; &lt;timestamp | *&gt; &lt;value&gt;, ...</code></p>
  </li>
  <li>
    <p><strong>SELECT</strong> query a timeseries, selection of point(s) and aggregations</p>

    <p><code class="language-plaintext highlighter-rouge">SELECT &lt;timeseries name&gt; FROM &lt;database name&gt; AT/RANGE &lt;start_timestamp&gt; TO &lt;end_timestamp&gt; WHERE value [&gt;|&lt;|=|&lt;=|&gt;=|!=] &lt;literal&gt; AGGREGATE [AVG|MIN|MAX] BY &lt;literal&gt;</code></p>
  </li>
  <li>
    <p><strong>DELETE</strong> delete a timeseries or a database</p>

    <p><code class="language-plaintext highlighter-rouge">DELETE &lt;database name&gt; DELETE &lt;timeseries name&gt; FROM &lt;database name&gt;</code></p>
  </li>
</ul>

<h3 id="flow">Flow:</h3>

<ol>
  <li>
    <p><strong>Client Sends Command:</strong> Clients send commands to the server in the specified text format.</p>
  </li>
  <li>
    <p><strong>Server Parses Command:</strong> The server parses the received command and executes the corresponding operation on the timeseries database.</p>
  </li>
  <li>
    <p><strong>Server Sends Response:</strong> After processing the command, the server sends a response back to the client indicating the result of the operation or providing requested data.</p>
  </li>
  <li>
    <p><strong>Client Processes Response:</strong> Clients receive and process the response from the server, handling success or error conditions accordingly.</p>
  </li>
</ol>]]></content><author><name></name></author><category term="c" /><category term="unix" /><category term="system-programming" /><category term="database" /><summary type="html"><![CDATA[Small summary of my journey implementing a little time-series library]]></summary></entry><entry><title type="html">Exploring nerves on an RPI4</title><link href="/posts/phoenix-livebook-nerves/" rel="alternate" type="text/html" title="Exploring nerves on an RPI4" /><published>2024-04-12T00:00:00+00:00</published><updated>2024-04-12T00:00:00+00:00</updated><id>/posts/phoenix-livebook-nerves</id><content type="html" xml:base="/posts/phoenix-livebook-nerves/"><![CDATA[<p>Small dump of a tiny presentation I made for a rather small audience on
<a href="https://elixir-lang.org/">Elixir</a> <a href="https://nerves-project.org/">Nerves</a> and
<a href="https://hexdocs.pm/phoenix_live_view/Phoenix.LiveView.html">Phoenix liveview</a>.
Can be followed as a mini tutorial step by step to get an IoT system running a
livebook with a small application backed by Phoenix.</p>

<h3 id="requirements">Requirements</h3>

<h4 id="hardware">Hardware</h4>

<ul>
  <li>Raspberry PI 4 Model The B</li>
  <li>Micro SD</li>
</ul>

<h4 id="software-dependencies">Software dependencies</h4>

<ul>
  <li>Elixir + mix <a href="https://elixir-lang.org/install.html#macos">install</a> (alternatively <a href="https://github.com/asdf-vm/asdf">asdf</a>)</li>
  <li>Docker</li>
</ul>

<h3 id="touched-arguments">Touched arguments</h3>

<ul>
  <li>Little history, development</li>
  <li>Docs “Getting started” and the actual procedure to burn a firmware</li>
  <li>Burn firmware into RPI4 Model B</li>
  <li>Connect to livebook on http://nerves.local
    <ul>
      <li>Walk through some examples</li>
    </ul>
  </li>
  <li>Connect through ssh by <code class="language-plaintext highlighter-rouge">ssh nerves.local</code>
    <ul>
      <li><code class="language-plaintext highlighter-rouge">h Toolshed</code></li>
      <li><code class="language-plaintext highlighter-rouge">weather/0</code> :)</li>
    </ul>
  </li>
  <li>Try MQTT communication
    <ul>
      <li><code class="language-plaintext highlighter-rouge">Mix.install</code> is not yet supported</li>
      <li><code class="language-plaintext highlighter-rouge">mix archive.install hex nerves_bootstrap</code></li>
    </ul>
  </li>
  <li>Something better: Phoenix LiveView on the board
    <ul>
      <li>Poncho Project</li>
      <li>Small <code class="language-plaintext highlighter-rouge">GenServer</code> to publish data periodically</li>
      <li>ssh into the board, <code class="language-plaintext highlighter-rouge">log_attach</code></li>
    </ul>
  </li>
  <li>Nerves HUB</li>
  <li>Peridio</li>
</ul>

<h3 id="quickstart">Quickstart</h3>

<p>Check <a href="https://nerves-project.org/">nerves</a>, the getting started link points directly to
the simplest example: <a href="https://github.com/livebook-dev/nerves_livebook/blob/main/README.md">running livebook on embedded systems</a></p>

<ul>
  <li><code class="language-plaintext highlighter-rouge">fwup</code> as the quickest setup.</li>
  <li>exploring some pre-loaded samples</li>
  <li>check the same commands through an <code class="language-plaintext highlighter-rouge">ssh</code> session: <code class="language-plaintext highlighter-rouge">ssh nerves.local</code> pw: <code class="language-plaintext highlighter-rouge">nerves</code>
    <ul>
      <li><code class="language-plaintext highlighter-rouge">h Toolshed</code></li>
      <li><code class="language-plaintext highlighter-rouge">log_attach</code> to attach to the Elixir <code class="language-plaintext highlighter-rouge">Logger</code> (useful to debug running apps)</li>
      <li><code class="language-plaintext highlighter-rouge">weather</code> :)</li>
    </ul>
  </li>
  <li>test communication with a simple snippet</li>
</ul>

<h4 id="testing-mqtt-communication-setup">Testing MQTT communication, setup:</h4>

<p>Run an MQTT broker on the host machine, let’s use <code class="language-plaintext highlighter-rouge">mosquitto</code> (if an <code class="language-plaintext highlighter-rouge">Address
not available error</code> happens, use the following conf)</p>

<div class="language-conf highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">persistence</span> <span class="n">false</span>
<span class="n">log_dest</span> <span class="n">file</span> /<span class="n">mosquitto</span>/<span class="n">log</span>/<span class="n">mosquitto</span>.<span class="n">log</span>
<span class="n">log_dest</span> <span class="n">stdout</span>
<span class="n">listener</span> <span class="m">1883</span>
<span class="n">allow_anonymous</span> <span class="n">true</span>
</code></pre></div></div>

<p>Finally run a <code class="language-plaintext highlighter-rouge">mosquitto</code> container mounting the local config</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>docker run <span class="nt">-it</span> <span class="nt">-p</span> 1883:1883 <span class="nt">-v</span> <span class="nv">$PWD</span>/mosquitto.conf:/mosquitto/config/mosquitto.conf eclipse-mosquitto
</code></pre></div></div>

<p>Create a new livebook and on the setup paste this dependency</p>

<div class="language-elixir highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="no">Mix</span><span class="o">.</span><span class="n">install</span><span class="p">([{</span><span class="ss">:emqtt</span><span class="p">,</span> <span class="ss">github:</span> <span class="s2">"emqx/emqtt"</span><span class="p">,</span> <span class="ss">tag:</span> <span class="s2">"1.4.4"</span><span class="p">,</span> <span class="ss">system_env:</span> <span class="p">[{</span><span class="s2">"BUILD_WITHOUT_QUIC"</span><span class="p">,</span> <span class="s2">"1"</span><span class="p">}]}])</span>
</code></pre></div></div>

<p>Publish random temperature values in a subsequent <code class="language-plaintext highlighter-rouge">code</code> block</p>

<div class="language-elixir highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">client_id</span> <span class="o">=</span> <span class="s2">"weather_sensor"</span>
<span class="n">report_topic</span> <span class="o">=</span> <span class="s2">"reports/</span><span class="si">#{</span><span class="n">client_id</span><span class="si">}</span><span class="s2">/temperature"</span>
<span class="n">host</span> <span class="o">=</span> <span class="s1">'192.168.10.12'</span> <span class="c1"># This should point to the local machine address, where our MQTT broker is listening</span>
<span class="n">port</span> <span class="o">=</span> <span class="mi">1883</span>

<span class="n">emqtt_opts</span> <span class="o">=</span> <span class="p">%{</span>
  <span class="ss">host:</span> <span class="n">host</span><span class="p">,</span>
  <span class="ss">port:</span> <span class="n">port</span><span class="p">,</span>
  <span class="ss">clean_start:</span> <span class="no">false</span><span class="p">,</span>
  <span class="ss">name:</span> <span class="ss">:emqtt</span>
<span class="p">}</span>

<span class="p">{</span><span class="ss">:ok</span><span class="p">,</span> <span class="n">pid</span><span class="p">}</span> <span class="o">=</span> <span class="ss">:emqtt</span><span class="o">.</span><span class="n">start_link</span><span class="p">(</span><span class="n">emqtt_opts</span><span class="p">)</span>
<span class="p">{</span><span class="ss">:ok</span><span class="p">,</span> <span class="n">_</span><span class="p">}</span> <span class="o">=</span> <span class="ss">:emqtt</span><span class="o">.</span><span class="n">connect</span><span class="p">(</span><span class="n">pid</span><span class="p">)</span>

<span class="n">temperature</span> <span class="o">=</span> <span class="mf">10.0</span> <span class="o">+</span> <span class="mf">2.0</span> <span class="o">*</span> <span class="ss">:rand</span><span class="o">.</span><span class="n">normal</span><span class="p">()</span>
<span class="n">message</span> <span class="o">=</span> <span class="p">%{</span><span class="s2">"timestamp"</span> <span class="o">=&gt;</span> <span class="no">System</span><span class="o">.</span><span class="n">system_time</span><span class="p">(</span><span class="ss">:millisecond</span><span class="p">),</span> <span class="s2">"temperature"</span> <span class="o">=&gt;</span> <span class="n">temperature</span><span class="p">}</span>
<span class="n">payload</span> <span class="o">=</span> <span class="no">Jason</span><span class="o">.</span><span class="n">encode!</span><span class="p">(</span><span class="n">message</span><span class="p">)</span>
<span class="ss">:emqtt</span><span class="o">.</span><span class="n">publish</span><span class="p">(</span><span class="n">pid</span><span class="p">,</span> <span class="n">report_topic</span><span class="p">,</span> <span class="n">payload</span><span class="p">)</span>
<span class="ss">:emqtt</span><span class="o">.</span><span class="n">stop</span><span class="p">(</span><span class="n">pid</span><span class="p">)</span>
</code></pre></div></div>

<p>Oh no, must rebuild the livebook :( one current limitation of livebook in
<code class="language-plaintext highlighter-rouge">nerves</code> is that <code class="language-plaintext highlighter-rouge">Mix.install</code> is not supported yet. Livebook can be customized
easily on the host machine.</p>

<p>Flash <code class="language-plaintext highlighter-rouge">nerves_livebook</code> into device (<code class="language-plaintext highlighter-rouge">rpi4</code>)</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>git clone https://github.com/livebook-dev/nerves_livebook.git
<span class="nb">cd </span>nerves_livebook
</code></pre></div></div>

<p>Add dependency to the <code class="language-plaintext highlighter-rouge">mix.exs</code></p>

<hr />

<p><strong>mix.exs</strong></p>
<hr />

<div class="language-elixir highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">defp</span> <span class="n">deps</span> <span class="k">do</span>
    <span class="p">[</span>
      <span class="o">...</span><span class="p">,</span>
      <span class="p">{</span><span class="ss">:emqtt</span><span class="p">,</span> <span class="ss">github:</span> <span class="s2">"emqx/emqtt"</span><span class="p">,</span> <span class="ss">tag:</span> <span class="s2">"1.4.4"</span><span class="p">,</span> <span class="ss">system_env:</span> <span class="p">[{</span><span class="s2">"BUILD_WITHOUT_QUIC"</span><span class="p">,</span> <span class="s2">"1"</span><span class="p">}]}</span>
      <span class="o">...</span>
    <span class="p">]</span>
<span class="k">end</span>
</code></pre></div></div>

<p>Build the firmware and push it to the device</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>
<span class="c"># Set the MIX_TARGET to the desired platform (rpi0, bbb, rpi3, etc.)</span>
<span class="nb">export </span><span class="nv">MIX_TARGET</span><span class="o">=</span>rpi4
mix deps.get
mix firmware

<span class="c"># Option 1: Insert a MicroSD card</span>
mix burn

<span class="c"># Option 2: Upload to an existing Nerves Livebook device</span>
mix firmware.gen.script
./upload.sh livebook@nerves.local
</code></pre></div></div>

<p>Now the previous snippet should work correctly and we should see the message
published onto our MQTT broker.</p>

<h3 id="daring-something-more">Daring something more</h3>

<p>Let’s expand the previous snippet: a <code class="language-plaintext highlighter-rouge">GenServer</code> perpetually generating
temperature values and also accepting command from the broker</p>

<div class="language-elixir highlighter-rouge"><div class="highlight"><pre class="highlight"><code>
<span class="k">defmodule</span> <span class="no">WeatherSensor</span> <span class="k">do</span>
  <span class="nv">@moduledoc</span> <span class="no">false</span>
  <span class="kn">use</span> <span class="no">GenServer</span>

  <span class="nv">@client_id</span> <span class="s2">"weather_sensor"</span>
  <span class="nv">@report_topic</span> <span class="s2">"reports/</span><span class="si">#{</span><span class="nv">@client_id</span><span class="si">}</span><span class="s2">/temperature"</span>
  <span class="nv">@host</span> <span class="s1">'192.168.10.12'</span> <span class="c1"># This should point to the local machine address, where our MQTT broker is listening</span>
  <span class="nv">@port</span> <span class="mi">1883</span>
  <span class="nv">@interval_ms</span> <span class="mi">2000</span>

  <span class="k">def</span> <span class="n">start_link</span><span class="p">([])</span> <span class="k">do</span>
    <span class="no">GenServer</span><span class="o">.</span><span class="n">start_link</span><span class="p">(</span><span class="bp">__MODULE__</span><span class="p">,</span> <span class="p">[])</span>
  <span class="k">end</span>

  <span class="k">def</span> <span class="n">init</span><span class="p">([])</span> <span class="k">do</span>
    <span class="p">{</span><span class="ss">:ok</span><span class="p">,</span> <span class="n">pid</span><span class="p">}</span> <span class="o">=</span> <span class="ss">:emqtt</span><span class="o">.</span><span class="n">start_link</span><span class="p">(</span><span class="n">emqtt_opts</span><span class="p">())</span>

    <span class="n">state</span> <span class="o">=</span> <span class="p">%{</span>
      <span class="ss">interval:</span> <span class="nv">@interval_ms</span><span class="p">,</span>
      <span class="ss">timer:</span> <span class="no">nil</span><span class="p">,</span>
      <span class="ss">report_topic:</span> <span class="nv">@report_topic</span><span class="p">,</span>
      <span class="ss">pid:</span> <span class="n">pid</span>
    <span class="p">}</span>

    <span class="p">{</span><span class="ss">:ok</span><span class="p">,</span> <span class="n">set_timer</span><span class="p">(</span><span class="n">state</span><span class="p">),</span> <span class="p">{</span><span class="ss">:continue</span><span class="p">,</span> <span class="ss">:start_emqtt</span><span class="p">}}</span>
  <span class="k">end</span>

  <span class="k">defp</span> <span class="n">emqtt_opts</span><span class="p">,</span>
    <span class="k">do</span><span class="p">:</span> <span class="p">%{</span>
      <span class="ss">host:</span> <span class="nv">@host</span><span class="p">,</span>
      <span class="ss">port:</span> <span class="nv">@port</span><span class="p">,</span>
      <span class="ss">clean_start:</span> <span class="no">false</span><span class="p">,</span>
      <span class="ss">name:</span> <span class="ss">:emqtt</span>
    <span class="p">}</span>

  <span class="k">def</span> <span class="n">handle_continue</span><span class="p">(</span><span class="ss">:start_emqtt</span><span class="p">,</span> <span class="p">%{</span><span class="ss">pid:</span> <span class="n">pid</span><span class="p">}</span> <span class="o">=</span> <span class="n">state</span><span class="p">)</span> <span class="k">do</span>
    <span class="p">{</span><span class="ss">:ok</span><span class="p">,</span> <span class="n">_</span><span class="p">}</span> <span class="o">=</span> <span class="ss">:emqtt</span><span class="o">.</span><span class="n">connect</span><span class="p">(</span><span class="n">pid</span><span class="p">)</span>

    <span class="p">{</span><span class="ss">:ok</span><span class="p">,</span> <span class="n">_</span><span class="p">,</span> <span class="n">_</span><span class="p">}</span> <span class="o">=</span> <span class="ss">:emqtt</span><span class="o">.</span><span class="n">subscribe</span><span class="p">(</span><span class="n">pid</span><span class="p">,</span> <span class="p">{</span><span class="s2">"commands/</span><span class="si">#{</span><span class="nv">@client_id</span><span class="si">}</span><span class="s2">/set_interval"</span><span class="p">,</span> <span class="mi">1</span><span class="p">})</span>
    <span class="p">{</span><span class="ss">:noreply</span><span class="p">,</span> <span class="n">state</span><span class="p">}</span>
  <span class="k">end</span>

  <span class="k">def</span> <span class="n">handle_info</span><span class="p">(</span><span class="ss">:tick</span><span class="p">,</span> <span class="p">%{</span><span class="ss">report_topic:</span> <span class="n">topic</span><span class="p">,</span> <span class="ss">pid:</span> <span class="n">pid</span><span class="p">}</span> <span class="o">=</span> <span class="n">state</span><span class="p">)</span> <span class="k">do</span>
    <span class="n">report_temperature</span><span class="p">(</span><span class="n">pid</span><span class="p">,</span> <span class="n">topic</span><span class="p">)</span>
    <span class="p">{</span><span class="ss">:noreply</span><span class="p">,</span> <span class="n">set_timer</span><span class="p">(</span><span class="n">state</span><span class="p">)}</span>
  <span class="k">end</span>

  <span class="k">def</span> <span class="n">handle_info</span><span class="p">({</span><span class="ss">:publish</span><span class="p">,</span> <span class="n">publish</span><span class="p">},</span> <span class="n">state</span><span class="p">)</span> <span class="k">do</span>
    <span class="n">handle_publish</span><span class="p">(</span><span class="n">parse_topic</span><span class="p">(</span><span class="n">publish</span><span class="p">),</span> <span class="n">publish</span><span class="p">,</span> <span class="n">state</span><span class="p">)</span>
  <span class="k">end</span>

  <span class="k">defp</span> <span class="n">handle_publish</span><span class="p">([</span><span class="s2">"commands"</span><span class="p">,</span> <span class="n">_</span><span class="p">,</span> <span class="s2">"set_interval"</span><span class="p">],</span> <span class="p">%{</span><span class="ss">payload:</span> <span class="n">payload</span><span class="p">},</span> <span class="n">state</span><span class="p">)</span> <span class="k">do</span>
    <span class="p">{</span><span class="ss">:noreply</span><span class="p">,</span> <span class="n">set_timer</span><span class="p">(%{</span><span class="n">state</span> <span class="o">|</span> <span class="ss">interval:</span> <span class="no">String</span><span class="o">.</span><span class="n">to_integer</span><span class="p">(</span><span class="n">payload</span><span class="p">)})}</span>
  <span class="k">end</span>

  <span class="k">defp</span> <span class="n">handle_publish</span><span class="p">(</span><span class="n">_</span><span class="p">,</span> <span class="n">_</span><span class="p">,</span> <span class="n">state</span><span class="p">)</span> <span class="k">do</span>
    <span class="p">{</span><span class="ss">:noreply</span><span class="p">,</span> <span class="n">state</span><span class="p">}</span>
  <span class="k">end</span>

  <span class="k">defp</span> <span class="n">parse_topic</span><span class="p">(%{</span><span class="ss">topic:</span> <span class="n">topic</span><span class="p">})</span> <span class="k">do</span>
    <span class="no">String</span><span class="o">.</span><span class="n">split</span><span class="p">(</span><span class="n">topic</span><span class="p">,</span> <span class="s2">"/"</span><span class="p">,</span> <span class="ss">trim:</span> <span class="no">true</span><span class="p">)</span>
  <span class="k">end</span>

  <span class="k">defp</span> <span class="n">set_timer</span><span class="p">(</span><span class="n">state</span><span class="p">)</span> <span class="k">do</span>
    <span class="k">if</span> <span class="n">state</span><span class="o">.</span><span class="n">timer</span> <span class="k">do</span>
      <span class="no">Process</span><span class="o">.</span><span class="n">cancel_timer</span><span class="p">(</span><span class="n">state</span><span class="o">.</span><span class="n">timer</span><span class="p">)</span>
    <span class="k">end</span>

    <span class="n">timer</span> <span class="o">=</span> <span class="no">Process</span><span class="o">.</span><span class="n">send_after</span><span class="p">(</span><span class="n">self</span><span class="p">(),</span> <span class="ss">:tick</span><span class="p">,</span> <span class="n">state</span><span class="o">.</span><span class="n">interval</span><span class="p">)</span>
    <span class="p">%{</span><span class="n">state</span> <span class="o">|</span> <span class="ss">timer:</span> <span class="n">timer</span><span class="p">}</span>
  <span class="k">end</span>

  <span class="k">defp</span> <span class="n">report_temperature</span><span class="p">(</span><span class="n">pid</span><span class="p">,</span> <span class="n">topic</span><span class="p">)</span> <span class="k">do</span>
    <span class="n">temperature</span> <span class="o">=</span> <span class="mf">10.0</span> <span class="o">+</span> <span class="mf">2.0</span> <span class="o">*</span> <span class="ss">:rand</span><span class="o">.</span><span class="n">normal</span><span class="p">()</span>
    <span class="n">message</span> <span class="o">=</span> <span class="p">%{</span><span class="s2">"timestamp"</span> <span class="o">=&gt;</span> <span class="no">System</span><span class="o">.</span><span class="n">system_time</span><span class="p">(</span><span class="ss">:millisecond</span><span class="p">),</span> <span class="s2">"temperature"</span> <span class="o">=&gt;</span> <span class="n">temperature</span><span class="p">}</span>
    <span class="n">payload</span> <span class="o">=</span> <span class="no">Jason</span><span class="o">.</span><span class="n">encode!</span><span class="p">(</span><span class="n">message</span><span class="p">)</span>
    <span class="ss">:emqtt</span><span class="o">.</span><span class="n">publish</span><span class="p">(</span><span class="n">pid</span><span class="p">,</span> <span class="n">topic</span><span class="p">,</span> <span class="n">payload</span><span class="p">)</span>
  <span class="k">end</span>
<span class="k">end</span>
</code></pre></div></div>

<p>On a subsequent block, we can run the <code class="language-plaintext highlighter-rouge">GenServer</code></p>

<div class="language-elixir highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="ss">:ok</span><span class="p">,</span> <span class="n">pid</span><span class="p">}</span> <span class="o">=</span> <span class="no">WeatherSensor</span><span class="o">.</span><span class="n">start_link</span><span class="p">([])</span>
</code></pre></div></div>

<p>On the host we should see data coming every ~2s, we can confirm it by peeking into the
<code class="language-plaintext highlighter-rouge">mosquitto</code> logs or by subscribing to the topic on <code class="language-plaintext highlighter-rouge">localhost</code></p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>mosquitto_sub <span class="nt">-t</span> reports/weather_sensor/temperature <span class="nt">-h</span> 127.0.0.1
</code></pre></div></div>

<h3 id="phoenix-liveview">Phoenix liveview</h3>

<p>Let’s build a simple Phoenix app to subscribe to the topic and see real-time
data coming through <code class="language-plaintext highlighter-rouge">LiveView</code>. Let’s not forget <code class="language-plaintext highlighter-rouge">--no-ecto</code> we don’t need DBs
here, <code class="language-plaintext highlighter-rouge">--no-mailer</code>, <code class="language-plaintext highlighter-rouge">--no-gettext</code>, <code class="language-plaintext highlighter-rouge">--no-dashboard</code>, and <code class="language-plaintext highlighter-rouge">--live</code>. This
should cut out a fair amount of boilerplate.</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>mix phx.new temp_dashboard <span class="nt">--no-ecto</span> <span class="nt">--no-mailer</span> <span class="nt">--no-gettext</span> <span class="nt">--no-dashboard</span> <span class="nt">--live</span>
</code></pre></div></div>

<p>First thing, let’s add the required dependencies to the <code class="language-plaintext highlighter-rouge">mix.exs</code></p>

<hr />

<p><strong>temp_dashboard/mix.exs</strong></p>
<hr />

<div class="language-elixir highlighter-rouge"><div class="highlight"><pre class="highlight"><code>  <span class="k">defp</span> <span class="n">deps</span> <span class="k">do</span>
    <span class="p">[</span>
      <span class="o">...</span><span class="p">,</span>
      <span class="p">{</span><span class="ss">:emqtt</span><span class="p">,</span> <span class="ss">github:</span> <span class="s2">"emqx/emqtt"</span><span class="p">,</span> <span class="ss">tag:</span> <span class="s2">"1.4.4"</span><span class="p">,</span> <span class="ss">system_env:</span> <span class="p">[{</span><span class="s2">"BUILD_WITHOUT_QUIC"</span><span class="p">,</span> <span class="s2">"1"</span><span class="p">}]},</span>
      <span class="p">{</span><span class="ss">:contex</span><span class="p">,</span> <span class="ss">github:</span> <span class="s2">"mindok/contex"</span><span class="p">},</span> <span class="c1"># We will need this for SVG charts</span>
      <span class="o">...</span>
    <span class="p">]</span>
  <span class="k">end</span>
</code></pre></div></div>

<p>Update the dependencies with the newly added bit</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>mix deps.get
</code></pre></div></div>

<p>Let’s add few lines to the main configuration and the development one:</p>

<hr />

<p><strong>temp_dashboard/config/config.exs</strong></p>
<hr />

<div class="language-elixir highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">config</span> <span class="ss">:temp_dashboard</span><span class="p">,</span> <span class="ss">:emqtt</span><span class="p">,</span>
  <span class="ss">host:</span> <span class="s1">'192.168.10.12'</span><span class="p">,</span>  <span class="c1"># Again, the MQTT broker address here</span>
  <span class="ss">port:</span> <span class="mi">1883</span>

<span class="n">config</span> <span class="ss">:temp_dashboard</span><span class="p">,</span> <span class="ss">:sensor_id</span><span class="p">,</span> <span class="s2">"weather_sensor"</span>

<span class="c1"># Period for chart</span>
<span class="n">config</span> <span class="ss">:temp_dashboard</span><span class="p">,</span> <span class="ss">:timespan</span><span class="p">,</span> <span class="mi">60</span>
</code></pre></div></div>

<p>Same on <code class="language-plaintext highlighter-rouge">temp_dashboard/config/dev.exs</code>.</p>

<p>Let’s now generate a <code class="language-plaintext highlighter-rouge">LiveView</code> controller</p>

<div class="language-elixir highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">mix</span> <span class="n">phx</span><span class="o">.</span><span class="n">gen</span><span class="o">.</span><span class="n">live</span> <span class="no">Measurements</span> <span class="no">Temperature</span> <span class="n">temperatures</span>  <span class="o">--</span><span class="n">no</span><span class="o">-</span><span class="n">schema</span> <span class="o">--</span><span class="n">no</span><span class="o">-</span><span class="n">context</span>
</code></pre></div></div>

<p>We can remove some of them for our single-page app</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">rm </span>lib/temp_dashboard_web/live/temperature_live/form_component.<span class="k">*</span>
<span class="nb">rm </span>lib/temp_dashboard_web/live/temperature_live/show.<span class="k">*</span>
<span class="nb">rm </span>lib/temp_dashboard_web/live/live_helpers.ex
</code></pre></div></div>

<p>Finally, we want to add some business logic to our app, fire up the editor on
<code class="language-plaintext highlighter-rouge">temp_dashboard/lib/temp_dashboard_web/live/temperature_live/index.ex</code> and
replace the content with the following snippet.</p>

<hr />

<p><strong>temp_dashboard/lib/temp_dashboard_web/live/temperature_live/index.ex</strong></p>
<hr />

<div class="language-elixir highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">defmodule</span> <span class="no">TempDashboardWeb</span><span class="o">.</span><span class="no">TemperatureLive</span><span class="o">.</span><span class="no">Index</span> <span class="k">do</span>
  <span class="kn">use</span> <span class="no">TempDashboardWeb</span><span class="p">,</span> <span class="ss">:live_view</span>

  <span class="kn">require</span> <span class="no">Logger</span>

  <span class="nv">@impl</span> <span class="no">true</span>
  <span class="k">def</span> <span class="n">mount</span><span class="p">(</span><span class="n">_params</span><span class="p">,</span> <span class="n">_session</span><span class="p">,</span> <span class="n">socket</span><span class="p">)</span> <span class="k">do</span>
    <span class="n">reports</span> <span class="o">=</span> <span class="p">[]</span>
    <span class="n">emqtt_opts</span> <span class="o">=</span> <span class="no">Application</span><span class="o">.</span><span class="n">get_env</span><span class="p">(</span><span class="ss">:temp_dashboard</span><span class="p">,</span> <span class="ss">:emqtt</span><span class="p">)</span>
    <span class="no">Logger</span><span class="o">.</span><span class="n">info</span><span class="p">(</span><span class="n">emqtt_opts</span><span class="p">)</span>
    <span class="p">{</span><span class="ss">:ok</span><span class="p">,</span> <span class="n">pid</span><span class="p">}</span> <span class="o">=</span> <span class="ss">:emqtt</span><span class="o">.</span><span class="n">start_link</span><span class="p">(</span><span class="n">emqtt_opts</span><span class="p">)</span>
    <span class="p">{</span><span class="ss">:ok</span><span class="p">,</span> <span class="n">_</span><span class="p">}</span> <span class="o">=</span> <span class="ss">:emqtt</span><span class="o">.</span><span class="n">connect</span><span class="p">(</span><span class="n">pid</span><span class="p">)</span>
    <span class="c1"># Listen reports</span>
    <span class="p">{</span><span class="ss">:ok</span><span class="p">,</span> <span class="n">_</span><span class="p">,</span> <span class="n">_</span><span class="p">}</span> <span class="o">=</span> <span class="ss">:emqtt</span><span class="o">.</span><span class="n">subscribe</span><span class="p">(</span><span class="n">pid</span><span class="p">,</span> <span class="s2">"reports/#"</span><span class="p">)</span>

    <span class="p">{</span><span class="ss">:ok</span><span class="p">,</span>
     <span class="n">assign</span><span class="p">(</span><span class="n">socket</span><span class="p">,</span>
       <span class="ss">reports:</span> <span class="n">reports</span><span class="p">,</span>
       <span class="ss">pid:</span> <span class="n">pid</span><span class="p">,</span>
       <span class="ss">plot:</span> <span class="no">nil</span><span class="p">,</span>
       <span class="ss">interval:</span> <span class="no">nil</span>
     <span class="p">)}</span>
  <span class="k">end</span>

  <span class="nv">@impl</span> <span class="no">true</span>
  <span class="k">def</span> <span class="n">handle_params</span><span class="p">(</span><span class="n">_params</span><span class="p">,</span> <span class="n">_url</span><span class="p">,</span> <span class="n">socket</span><span class="p">)</span> <span class="k">do</span>
    <span class="p">{</span><span class="ss">:noreply</span><span class="p">,</span> <span class="n">socket</span><span class="p">}</span>
  <span class="k">end</span>

  <span class="nv">@impl</span> <span class="no">true</span>
  <span class="k">def</span> <span class="n">handle_event</span><span class="p">(</span><span class="s2">"set-interval"</span><span class="p">,</span> <span class="p">%{</span><span class="s2">"interval"</span> <span class="o">=&gt;</span> <span class="n">interval_s</span><span class="p">},</span> <span class="n">socket</span><span class="p">)</span> <span class="k">do</span>
    <span class="k">case</span> <span class="no">Integer</span><span class="o">.</span><span class="n">parse</span><span class="p">(</span><span class="n">interval_s</span><span class="p">)</span> <span class="k">do</span>
      <span class="p">{</span><span class="n">interval</span><span class="p">,</span> <span class="s2">""</span><span class="p">}</span> <span class="o">-&gt;</span>
        <span class="n">id</span> <span class="o">=</span> <span class="no">Application</span><span class="o">.</span><span class="n">get_env</span><span class="p">(</span><span class="ss">:temp_dashboard</span><span class="p">,</span> <span class="ss">:sensor_id</span><span class="p">)</span>
        <span class="c1"># Send command to device</span>
        <span class="n">topic</span> <span class="o">=</span> <span class="s2">"commands/</span><span class="si">#{</span><span class="n">id</span><span class="si">}</span><span class="s2">/set_interval"</span>

        <span class="ss">:ok</span> <span class="o">=</span>
          <span class="ss">:emqtt</span><span class="o">.</span><span class="n">publish</span><span class="p">(</span>
            <span class="n">socket</span><span class="o">.</span><span class="n">assigns</span><span class="p">[</span><span class="ss">:pid</span><span class="p">],</span>
            <span class="n">topic</span><span class="p">,</span>
            <span class="n">interval_s</span><span class="p">,</span>
            <span class="ss">retain:</span> <span class="no">true</span>
          <span class="p">)</span>

        <span class="p">{</span><span class="ss">:noreply</span><span class="p">,</span> <span class="n">assign</span><span class="p">(</span><span class="n">socket</span><span class="p">,</span> <span class="ss">interval:</span> <span class="n">interval</span><span class="p">)}</span>

      <span class="n">_</span> <span class="o">-&gt;</span>
        <span class="p">{</span><span class="ss">:noreply</span><span class="p">,</span> <span class="n">socket</span><span class="p">}</span>
    <span class="k">end</span>
  <span class="k">end</span>

  <span class="k">def</span> <span class="n">handle_event</span><span class="p">(</span><span class="n">name</span><span class="p">,</span> <span class="n">data</span><span class="p">,</span> <span class="n">socket</span><span class="p">)</span> <span class="k">do</span>
    <span class="no">Logger</span><span class="o">.</span><span class="n">info</span><span class="p">(</span><span class="s2">"handle_event: </span><span class="si">#{</span><span class="n">inspect</span><span class="p">([</span><span class="n">name</span><span class="p">,</span> <span class="n">data</span><span class="p">])</span><span class="si">}</span><span class="s2">"</span><span class="p">)</span>
    <span class="p">{</span><span class="ss">:noreply</span><span class="p">,</span> <span class="n">socket</span><span class="p">}</span>
  <span class="k">end</span>

  <span class="nv">@impl</span> <span class="no">true</span>
  <span class="k">def</span> <span class="n">handle_info</span><span class="p">({</span><span class="ss">:publish</span><span class="p">,</span> <span class="n">packet</span><span class="p">},</span> <span class="n">socket</span><span class="p">)</span> <span class="k">do</span>
    <span class="no">Logger</span><span class="o">.</span><span class="n">info</span><span class="p">(</span><span class="s2">"handle_event: </span><span class="si">#{</span><span class="n">inspect</span><span class="p">(</span><span class="n">packet</span><span class="p">)</span><span class="si">}</span><span class="s2">"</span><span class="p">)</span>
    <span class="n">handle_publish</span><span class="p">(</span><span class="n">parse_topic</span><span class="p">(</span><span class="n">packet</span><span class="p">),</span> <span class="n">packet</span><span class="p">,</span> <span class="n">socket</span><span class="p">)</span>
  <span class="k">end</span>

  <span class="k">defp</span> <span class="n">handle_publish</span><span class="p">([</span><span class="s2">"reports"</span><span class="p">,</span> <span class="n">id</span><span class="p">,</span> <span class="s2">"temperature"</span><span class="p">],</span> <span class="p">%{</span><span class="ss">payload:</span> <span class="n">payload</span><span class="p">},</span> <span class="n">socket</span><span class="p">)</span> <span class="k">do</span>
    <span class="k">if</span> <span class="n">id</span> <span class="o">==</span> <span class="no">Application</span><span class="o">.</span><span class="n">get_env</span><span class="p">(</span><span class="ss">:temp_dashboard</span><span class="p">,</span> <span class="ss">:sensor_id</span><span class="p">)</span> <span class="k">do</span>
      <span class="n">report</span> <span class="o">=</span> <span class="no">Jason</span><span class="o">.</span><span class="n">decode!</span><span class="p">(</span><span class="n">payload</span><span class="p">)</span>
      <span class="p">{</span><span class="n">reports</span><span class="p">,</span> <span class="n">plot</span><span class="p">}</span> <span class="o">=</span> <span class="n">update_reports</span><span class="p">(</span><span class="n">report</span><span class="p">,</span> <span class="n">socket</span><span class="p">)</span>
      <span class="p">{</span><span class="ss">:noreply</span><span class="p">,</span> <span class="n">assign</span><span class="p">(</span><span class="n">socket</span><span class="p">,</span> <span class="ss">reports:</span> <span class="n">reports</span><span class="p">,</span> <span class="ss">plot:</span> <span class="n">plot</span><span class="p">)}</span>
    <span class="k">else</span>
      <span class="p">{</span><span class="ss">:noreply</span><span class="p">,</span> <span class="n">socket</span><span class="p">}</span>
    <span class="k">end</span>
  <span class="k">end</span>

  <span class="k">defp</span> <span class="n">update_reports</span><span class="p">(%{</span><span class="s2">"timestamp"</span> <span class="o">=&gt;</span> <span class="n">ts</span><span class="p">,</span> <span class="s2">"temperature"</span> <span class="o">=&gt;</span> <span class="n">val</span><span class="p">},</span> <span class="n">socket</span><span class="p">)</span> <span class="k">do</span>
    <span class="n">new_report</span> <span class="o">=</span> <span class="p">{</span><span class="no">DateTime</span><span class="o">.</span><span class="n">from_unix!</span><span class="p">(</span><span class="n">ts</span><span class="p">,</span> <span class="ss">:millisecond</span><span class="p">),</span> <span class="n">val</span><span class="p">}</span>
    <span class="n">now</span> <span class="o">=</span> <span class="no">DateTime</span><span class="o">.</span><span class="n">utc_now</span><span class="p">()</span>

    <span class="n">deadline</span> <span class="o">=</span>
      <span class="no">DateTime</span><span class="o">.</span><span class="n">add</span><span class="p">(</span>
        <span class="no">DateTime</span><span class="o">.</span><span class="n">utc_now</span><span class="p">(),</span>
        <span class="o">-</span><span class="mi">2</span> <span class="o">*</span> <span class="no">Application</span><span class="o">.</span><span class="n">get_env</span><span class="p">(</span><span class="ss">:temp_dashboard</span><span class="p">,</span> <span class="ss">:timespan</span><span class="p">),</span>
        <span class="ss">:second</span>
      <span class="p">)</span>

    <span class="n">reports</span> <span class="o">=</span>
      <span class="p">[</span><span class="n">new_report</span> <span class="o">|</span> <span class="n">socket</span><span class="o">.</span><span class="n">assigns</span><span class="p">[</span><span class="ss">:reports</span><span class="p">]]</span>
      <span class="o">|&gt;</span> <span class="no">Enum</span><span class="o">.</span><span class="n">filter</span><span class="p">(</span><span class="k">fn</span> <span class="p">{</span><span class="n">dt</span><span class="p">,</span> <span class="n">_</span><span class="p">}</span> <span class="o">-&gt;</span> <span class="no">DateTime</span><span class="o">.</span><span class="n">compare</span><span class="p">(</span><span class="n">dt</span><span class="p">,</span> <span class="n">deadline</span><span class="p">)</span> <span class="o">==</span> <span class="ss">:gt</span> <span class="k">end</span><span class="p">)</span>
      <span class="o">|&gt;</span> <span class="no">Enum</span><span class="o">.</span><span class="n">sort</span><span class="p">()</span>

    <span class="p">{</span><span class="n">reports</span><span class="p">,</span> <span class="n">plot</span><span class="p">(</span><span class="n">reports</span><span class="p">,</span> <span class="n">deadline</span><span class="p">,</span> <span class="n">now</span><span class="p">)}</span>
  <span class="k">end</span>

  <span class="k">defp</span> <span class="n">parse_topic</span><span class="p">(%{</span><span class="ss">topic:</span> <span class="n">topic</span><span class="p">})</span> <span class="k">do</span>
    <span class="no">String</span><span class="o">.</span><span class="n">split</span><span class="p">(</span><span class="n">topic</span><span class="p">,</span> <span class="s2">"/"</span><span class="p">,</span> <span class="ss">trim:</span> <span class="no">true</span><span class="p">)</span>
  <span class="k">end</span>

  <span class="k">defp</span> <span class="n">plot</span><span class="p">(</span><span class="n">reports</span><span class="p">,</span> <span class="n">deadline</span><span class="p">,</span> <span class="n">now</span><span class="p">)</span> <span class="k">do</span>
    <span class="n">x_scale</span> <span class="o">=</span>
      <span class="no">Contex</span><span class="o">.</span><span class="no">TimeScale</span><span class="o">.</span><span class="n">new</span><span class="p">()</span>
      <span class="o">|&gt;</span> <span class="no">Contex</span><span class="o">.</span><span class="no">TimeScale</span><span class="o">.</span><span class="n">domain</span><span class="p">(</span><span class="n">deadline</span><span class="p">,</span> <span class="n">now</span><span class="p">)</span>
      <span class="o">|&gt;</span> <span class="no">Contex</span><span class="o">.</span><span class="no">TimeScale</span><span class="o">.</span><span class="n">interval_count</span><span class="p">(</span><span class="mi">10</span><span class="p">)</span>

    <span class="n">y_scale</span> <span class="o">=</span>
      <span class="no">Contex</span><span class="o">.</span><span class="no">ContinuousLinearScale</span><span class="o">.</span><span class="n">new</span><span class="p">()</span>
      <span class="o">|&gt;</span> <span class="no">Contex</span><span class="o">.</span><span class="no">ContinuousLinearScale</span><span class="o">.</span><span class="n">domain</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="mi">30</span><span class="p">)</span>

    <span class="n">options</span> <span class="o">=</span> <span class="p">[</span>
      <span class="ss">smoothed:</span> <span class="no">false</span><span class="p">,</span>
      <span class="ss">custom_x_scale:</span> <span class="n">x_scale</span><span class="p">,</span>
      <span class="ss">custom_y_scale:</span> <span class="n">y_scale</span><span class="p">,</span>
      <span class="ss">custom_x_formatter:</span> <span class="o">&amp;</span><span class="n">x_formatter</span><span class="o">/</span><span class="mi">1</span><span class="p">,</span>
      <span class="ss">axis_label_rotation:</span> <span class="mi">45</span>
    <span class="p">]</span>

    <span class="n">reports</span>
    <span class="o">|&gt;</span> <span class="no">Enum</span><span class="o">.</span><span class="n">map</span><span class="p">(</span><span class="k">fn</span> <span class="p">{</span><span class="n">dt</span><span class="p">,</span> <span class="n">val</span><span class="p">}</span> <span class="o">-&gt;</span> <span class="p">[</span><span class="n">dt</span><span class="p">,</span> <span class="n">val</span><span class="p">]</span> <span class="k">end</span><span class="p">)</span>
    <span class="o">|&gt;</span> <span class="no">Contex</span><span class="o">.</span><span class="no">Dataset</span><span class="o">.</span><span class="n">new</span><span class="p">()</span>
    <span class="o">|&gt;</span> <span class="no">Contex</span><span class="o">.</span><span class="no">Plot</span><span class="o">.</span><span class="n">new</span><span class="p">(</span><span class="no">Contex</span><span class="o">.</span><span class="no">LinePlot</span><span class="p">,</span> <span class="mi">600</span><span class="p">,</span> <span class="mi">250</span><span class="p">,</span> <span class="n">options</span><span class="p">)</span>
    <span class="o">|&gt;</span> <span class="no">Contex</span><span class="o">.</span><span class="no">Plot</span><span class="o">.</span><span class="n">to_svg</span><span class="p">()</span>
  <span class="k">end</span>

  <span class="k">defp</span> <span class="n">x_formatter</span><span class="p">(</span><span class="n">datetime</span><span class="p">),</span> <span class="k">do</span><span class="p">:</span> <span class="no">Calendar</span><span class="o">.</span><span class="n">strftime</span><span class="p">(</span><span class="n">datetime</span><span class="p">,</span> <span class="s2">"%H:%M:%S"</span><span class="p">)</span>
<span class="k">end</span>
</code></pre></div></div>

<p>We also need a template view for our page, let’s replace the <code class="language-plaintext highlighter-rouge">index.html.heex</code></p>

<hr />

<p><strong>temp_dashboard/lib/temp_dashboard_web/live/temperature_live/index.html.heex</strong></p>
<hr />

<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt">&lt;div&gt;</span>
  <span class="nt">&lt;</span><span class="err">%=</span> <span class="na">if</span> <span class="err">@</span><span class="na">plot</span> <span class="na">do</span> <span class="err">%</span><span class="nt">&gt;</span>
    <span class="nt">&lt;</span><span class="err">%=</span> <span class="err">@</span><span class="na">plot</span> <span class="err">%</span><span class="nt">&gt;</span>
  <span class="nt">&lt;</span><span class="err">%</span> <span class="na">end</span> <span class="err">%</span><span class="nt">&gt;</span>
<span class="nt">&lt;/div&gt;</span>

<span class="nt">&lt;div&gt;</span>
  <span class="nt">&lt;form</span> <span class="na">phx-submit=</span><span class="s">"set-interval"</span><span class="nt">&gt;</span>
    <span class="nt">&lt;label</span> <span class="na">for=</span><span class="s">"interval"</span><span class="nt">&gt;</span>Interval<span class="nt">&lt;/label&gt;</span>
    <span class="nt">&lt;input</span> <span class="na">type=</span><span class="s">"text"</span> <span class="na">name=</span><span class="s">"interval"</span> <span class="na">value=</span><span class="s">{@interval}/</span><span class="nt">&gt;</span>
    <span class="nt">&lt;input</span> <span class="na">type=</span><span class="s">"submit"</span> <span class="na">value=</span><span class="s">"Set interval"</span><span class="nt">/&gt;</span>
  <span class="nt">&lt;/form&gt;</span>
<span class="nt">&lt;/div&gt;</span>
</code></pre></div></div>

<p>Last step, adding a handler to route our requests to index, removing the
existing <code class="language-plaintext highlighter-rouge">/</code> <code class="language-plaintext highlighter-rouge">scope</code></p>

<hr />

<p><strong>lib/temp_dasboard_web/router.ex</strong></p>
<hr />

<div class="language-elixir highlighter-rouge"><div class="highlight"><pre class="highlight"><code>  <span class="n">scope</span> <span class="s2">"/"</span><span class="p">,</span> <span class="no">TempDashboardWeb</span> <span class="k">do</span>
    <span class="n">pipe_through</span> <span class="ss">:browser</span>

    <span class="n">live</span> <span class="s2">"/"</span><span class="p">,</span> <span class="no">TemperatureLive</span><span class="o">.</span><span class="no">Index</span>
  <span class="k">end</span>
</code></pre></div></div>

<p>Get the dependencies and run the application, we should see it connecting to
the broker</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>mix phx.server
</code></pre></div></div>

<p>Let’s open the brower to <code class="language-plaintext highlighter-rouge">localhost:4000</code>.</p>

<h3 id="phoenix-liveview-on-rpi4">Phoenix LiveView on RPI4</h3>

<p>A “poncho project” is similar to an umbrella project except that it’s actually
multiple separate-but-related Elixir apps that use path dependencies instead of
<code class="language-plaintext highlighter-rouge">in_umbrella</code> dependencies</p>

<h4 id="references">References</h4>

<ul>
  <li><a href="https://www.raspberrypi.com/products/raspberry-pi-4-model-b/">Raspberry PI 4B - https://www.raspberrypi.com/products/raspberry-pi-4-model-b/</a></li>
  <li><a href="https://mqtt.org/">MQTT - https://mqtt.org/</a></li>
  <li><a href="https://www.docker.com/">Docker - https://www.docker.com/</a></li>
  <li><a href="https://elixir-lang.org/">Elixir - https://elixir-lang.org/</a></li>
  <li><a href="https://hexdocs.pm/elixir/introduction-to-mix.html">Mix - https://hexdocs.pm/elixir/introduction-to-mix.html</a></li>
  <li><a href="https://hexdocs.pm/elixir/GenServer.html">GenServer - https://hexdocs.pm/elixir/GenServer.html</a></li>
  <li><a href="https://livebook.dev/">Livebook - https://livebook.dev/</a></li>
  <li><a href="https://nerves-project.org/">Nerves - https://nerves-project.org/</a></li>
  <li><a href="https://hexdocs.pm/phoenix_live_view/Phoenix.LiveView.html">Phoenix liveview - https://hexdocs.pm/phoenix_live_view/Phoenix.LiveView.html</a></li>
</ul>]]></content><author><name></name></author><category term="elixir" /><category term="iot" /><summary type="html"><![CDATA[Trying out nerves and phoenix liveview on a Raspberry PI 4]]></summary></entry><entry><title type="html">Pubsub with asyncio</title><link href="/posts/asyncio-pubsub/" rel="alternate" type="text/html" title="Pubsub with asyncio" /><published>2019-10-27T00:00:00+00:00</published><updated>2019-10-27T00:00:00+00:00</updated><id>/posts/asyncio-pubsub</id><content type="html" xml:base="/posts/asyncio-pubsub/"><![CDATA[<p>Pubsub is one of the most recognizable patterns in computer science, employed
as a simple solution to a wide range of problems, with different
implementations. <!--more-->As an example, let’s suppose you need to dispatch
some data with different computation times to different destinations, sure the
first thing coming to mind is a broker or a simple job queue, and there’s a
plethora of battle-tested solutions out there, from <code class="language-plaintext highlighter-rouge">Apache Kafka</code> to
<code class="language-plaintext highlighter-rouge">RabbitMQ</code> to even <code class="language-plaintext highlighter-rouge">Redis</code> or the solid AWS <code class="language-plaintext highlighter-rouge">SNS/SQS</code> combination, where topics
can be defined on <code class="language-plaintext highlighter-rouge">SNS</code> with Lambdas or <code class="language-plaintext highlighter-rouge">SQS</code> queuing triggered at each
message received. Why? Because why not, it gives solid performances and near
unlimited scalability for just some bucks per month, with low to none
maintenance costs beside your business logic; something to consider if you
already have a part or your entire backend hosted on AWS.</p>

<p>Sometimes though it can be an overkill or all we need is just a prototype, a
proof of concept, in those cases a simple micro-service can do well enough with
little costs.</p>

<h3 id="asyncio-pubsub">Asyncio pubsub</h3>

<p>So in our case we’ll face a classic pubsub problem, with a bunch of data
sources that have to be delivered to multiple consumers. The first solution
that comes to mind attacks the problem by using a multi-threaded pattern, by
running as many threads as the number of sources, each one with a loop
synchronized with timers to dispatch results.</p>

<p>Asyncio offers two different approaches to this by using a single thread,
making it much simpler in terms of synchronization and flow of the application,
leveraging cooperative concurrency with <code class="language-plaintext highlighter-rouge">coroutines</code> or using a synchronous
callback-based system.</p>

<h4 id="cooperative-concurrency-coroutine-solution">Cooperative concurrency, coroutine solution</h4>

<p>Starting with the <code class="language-plaintext highlighter-rouge">coroutines</code> approach, we define a simple <code class="language-plaintext highlighter-rouge">AsyncDeliver</code> class
which will act as out <code class="language-plaintext highlighter-rouge">publisher</code> component, with 3 different sources, running
respectively once every 5, 10 and 20 seconds; we’ll need 3 different topics for
these tasks, for simplicity we’ll call them, <code class="language-plaintext highlighter-rouge">5sec-delivery</code>, <code class="language-plaintext highlighter-rouge">10sec-delivery</code>
and <code class="language-plaintext highlighter-rouge">20sec-delivery</code> respectively.</p>

<p>Let’s move one with the implementation.</p>

<hr />

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">import</span> <span class="nn">asyncio</span>
<span class="kn">import</span> <span class="nn">collections</span>

<span class="k">class</span> <span class="nc">AsyncDeliver</span><span class="p">:</span>

    <span class="s">"""Publisher component, deliver data from different sources to subscribed
    consumers. This version uses a defaultdict with list as main values to
    track subscribers (our consumers) for each topic.
    Subscriber objects are expected to implement a simple interface with just
    an update method for now.
    """</span>

    <span class="k">def</span> <span class="nf">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
        <span class="bp">self</span><span class="p">.</span><span class="n">subscribers</span> <span class="o">=</span> <span class="n">collections</span><span class="p">.</span><span class="n">defaultdict</span><span class="p">(</span><span class="nb">list</span><span class="p">)</span>

    <span class="k">def</span> <span class="nf">subscribe</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">subscriber</span><span class="p">,</span> <span class="n">topic</span><span class="p">):</span>
        <span class="bp">self</span><span class="p">.</span><span class="n">subscribers</span><span class="p">[</span><span class="n">topic</span><span class="p">].</span><span class="n">append</span><span class="p">(</span><span class="n">subscriber</span><span class="p">)</span>

    <span class="k">def</span> <span class="nf">deliver</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">data</span><span class="p">,</span> <span class="n">topic</span><span class="p">):</span>
        <span class="s">"""Call update method of each subscriber for the topic. As long as
        the calls are non-blocking there's no need to make the asynchronous, but
        nothing would've stopped us to do so if needed.
        """</span>
        <span class="k">if</span> <span class="n">topic</span> <span class="ow">not</span> <span class="ow">in</span> <span class="bp">self</span><span class="p">.</span><span class="n">subscribers</span><span class="p">:</span>
            <span class="k">return</span>
        <span class="k">for</span> <span class="n">subscriber</span> <span class="ow">in</span> <span class="bp">self</span><span class="p">.</span><span class="n">subscribers</span><span class="p">[</span><span class="n">topic</span><span class="p">]:</span>
            <span class="n">subscriber</span><span class="p">.</span><span class="n">update</span><span class="p">(</span><span class="n">data</span><span class="p">)</span>

    <span class="k">async</span> <span class="k">def</span> <span class="nf">run</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
        <span class="s">"""Main entry-point, here we instantiate all the tasks, 5 and 10 seconds
        delivery, using the 20 seconds as our "blocking" call.
        A shutdown method would be called on application stop to cancel all
        running tasks and gracefully close the event loop.
        """</span>
        <span class="n">loop</span> <span class="o">=</span> <span class="n">asyncio</span><span class="p">.</span><span class="n">get_running_loop</span><span class="p">()</span>
        <span class="n">loop</span><span class="p">.</span><span class="n">create_task</span><span class="p">(</span><span class="bp">self</span><span class="p">.</span><span class="n">deliver_every_5s</span><span class="p">())</span>
        <span class="n">loop</span><span class="p">.</span><span class="n">create_task</span><span class="p">(</span><span class="bp">self</span><span class="p">.</span><span class="n">deliver_every_10s</span><span class="p">())</span>
        <span class="k">try</span><span class="p">:</span>
            <span class="k">await</span> <span class="bp">self</span><span class="p">.</span><span class="n">deliver_every_20s</span><span class="p">()</span>
        <span class="k">except</span> <span class="nb">KeyboardInterrupt</span><span class="p">:</span>
            <span class="k">await</span> <span class="bp">self</span><span class="p">.</span><span class="n">shutdown</span><span class="p">()</span>

    <span class="k">async</span> <span class="k">def</span> <span class="nf">shutdown</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
        <span class="n">loop</span> <span class="o">=</span> <span class="n">asyncio</span><span class="p">.</span><span class="n">get_running_loop</span><span class="p">()</span>
        <span class="n">tasks</span> <span class="o">=</span> <span class="p">[</span><span class="n">task</span> <span class="k">for</span> <span class="n">task</span> <span class="ow">in</span> <span class="n">asyncio</span><span class="p">.</span><span class="n">Task</span><span class="p">.</span><span class="n">all_tasks</span><span class="p">()</span> <span class="k">if</span> <span class="n">task</span> <span class="ow">is</span> <span class="ow">not</span>
                 <span class="n">asyncio</span><span class="p">.</span><span class="n">tasks</span><span class="p">.</span><span class="n">Task</span><span class="p">.</span><span class="n">current_task</span><span class="p">()]</span>
        <span class="k">for</span> <span class="n">task</span> <span class="ow">in</span> <span class="n">tasks</span><span class="p">:</span>
            <span class="n">task</span><span class="p">.</span><span class="n">cancel</span><span class="p">()</span>
        <span class="k">await</span> <span class="n">asyncio</span><span class="p">.</span><span class="n">gather</span><span class="p">(</span><span class="o">*</span><span class="n">tasks</span><span class="p">,</span> <span class="n">return_exceptions</span><span class="o">=</span><span class="bp">True</span><span class="p">)</span>
        <span class="n">loop</span><span class="p">.</span><span class="n">stop</span><span class="p">()</span>
        <span class="n">loop</span><span class="p">.</span><span class="n">close</span><span class="p">()</span>

    <span class="k">async</span> <span class="k">def</span> <span class="nf">deliver_every_5s</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
        <span class="k">while</span> <span class="bp">True</span><span class="p">:</span>
            <span class="k">try</span><span class="p">:</span>
                <span class="k">await</span> <span class="n">asyncio</span><span class="p">.</span><span class="n">sleep</span><span class="p">(</span><span class="mi">5</span><span class="p">)</span>
                <span class="bp">self</span><span class="p">.</span><span class="n">deliver</span><span class="p">(</span><span class="s">"5 seconds delivery"</span><span class="p">,</span> <span class="s">"5sec-delivery"</span><span class="p">)</span>
            <span class="k">except</span> <span class="n">asyncio</span><span class="p">.</span><span class="n">CancelledError</span><span class="p">:</span>
                <span class="k">break</span>

    <span class="k">async</span> <span class="k">def</span> <span class="nf">deliver_every_10s</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
        <span class="k">while</span> <span class="bp">True</span><span class="p">:</span>
            <span class="k">try</span><span class="p">:</span>
                <span class="k">await</span> <span class="n">asyncio</span><span class="p">.</span><span class="n">sleep</span><span class="p">(</span><span class="mi">10</span><span class="p">)</span>
                <span class="bp">self</span><span class="p">.</span><span class="n">deliver</span><span class="p">(</span><span class="s">"10 seconds delivery"</span><span class="p">,</span> <span class="s">"10sec-delivery"</span><span class="p">)</span>
            <span class="k">except</span> <span class="n">asyncio</span><span class="p">.</span><span class="n">CancelledError</span><span class="p">:</span>
                <span class="k">break</span>

    <span class="k">async</span> <span class="k">def</span> <span class="nf">deliver_every_20s</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
        <span class="k">while</span> <span class="bp">True</span><span class="p">:</span>
            <span class="k">try</span><span class="p">:</span>
                <span class="k">await</span> <span class="n">asyncio</span><span class="p">.</span><span class="n">sleep</span><span class="p">(</span><span class="mi">20</span><span class="p">)</span>
                <span class="bp">self</span><span class="p">.</span><span class="n">deliver</span><span class="p">(</span><span class="s">"20 seconds delivery"</span><span class="p">,</span> <span class="s">"20sec-delivery"</span><span class="p">)</span>
            <span class="k">except</span> <span class="n">asyncio</span><span class="p">.</span><span class="n">CancelledError</span><span class="p">:</span>
                <span class="k">break</span>
</code></pre></div></div>
<hr />

<p>Let’s define a simple subscriber interface, it will only need an <code class="language-plaintext highlighter-rouge">update</code>
method. Sub-classes can easily define some logic or different communication
types such as HTTP clients, Kinesis/Kafka producers.</p>

<p>To be noted that for the sake of the example, all <code class="language-plaintext highlighter-rouge">deliver</code> methods doesn’t
block in any way, but it would be trivial to declare <code class="language-plaintext highlighter-rouge">async</code> even the deliver
method and await on all subscribers on the <code class="language-plaintext highlighter-rouge">AsyncDeliver</code> class by calling
<code class="language-plaintext highlighter-rouge">asyncio.gather</code> coroutine.</p>

<hr />

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">import</span> <span class="nn">abc</span>

<span class="k">class</span> <span class="nc">Subscriber</span><span class="p">(</span><span class="n">abc</span><span class="p">.</span><span class="n">ABC</span><span class="p">):</span>

    <span class="k">def</span> <span class="nf">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">name</span><span class="p">):</span>
        <span class="bp">self</span><span class="p">.</span><span class="n">name</span> <span class="o">=</span> <span class="n">name</span>

    <span class="o">@</span><span class="n">abc</span><span class="p">.</span><span class="n">abstractmethod</span>
    <span class="k">def</span> <span class="nf">update</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">data</span><span class="p">):</span>
        <span class="k">raise</span> <span class="nb">NotImplementedError</span><span class="p">()</span>

<span class="k">class</span> <span class="nc">NullSubscriber</span><span class="p">(</span><span class="n">Subscriber</span><span class="p">):</span>

    <span class="s">"""Simple subscriber that does nothing but printing the updates it
    receives
    """</span>

    <span class="k">def</span> <span class="nf">update</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">data</span><span class="p">):</span>
        <span class="s">"""We expect this method to do non-blocking operations, if something have
        to do with I/O in a blocking manner, we'd better re-define it as a
        coroutine method which can be awaited.
        """</span>
        <span class="k">print</span><span class="p">(</span><span class="sa">f</span><span class="s">"[</span><span class="si">{</span><span class="bp">self</span><span class="p">.</span><span class="n">name</span><span class="si">}</span><span class="s">] Received data: </span><span class="si">{</span><span class="n">data</span><span class="si">}</span><span class="s">"</span><span class="p">)</span>

</code></pre></div></div>
<hr />

<p>And finally let’s smoke-test it by running a simple scenario, 3 consumers
<code class="language-plaintext highlighter-rouge">Toki</code>, <code class="language-plaintext highlighter-rouge">Shu</code> and <code class="language-plaintext highlighter-rouge">Raoh</code> (names have clearly no interlacing on one another)</p>

<hr />

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">if</span> <span class="n">__name__</span> <span class="o">==</span> <span class="s">'__main__'</span><span class="p">:</span>
    <span class="n">deliverboy</span> <span class="o">=</span> <span class="n">AsyncDeliver</span><span class="p">()</span>
    <span class="n">subscriber_one</span> <span class="o">=</span> <span class="n">NullSubscriber</span><span class="p">(</span><span class="s">'Toki'</span><span class="p">)</span>
    <span class="n">subscriber_two</span> <span class="o">=</span> <span class="n">NullSubscriber</span><span class="p">(</span><span class="s">'Shu'</span><span class="p">)</span>
    <span class="n">subscriber_three</span> <span class="o">=</span> <span class="n">NullSubscriber</span><span class="p">(</span><span class="s">'Raoh'</span><span class="p">)</span>
    <span class="n">deliverboy</span><span class="p">.</span><span class="n">subscribe</span><span class="p">(</span><span class="n">subscriber_one</span><span class="p">,</span> <span class="s">"5sec-delivery"</span><span class="p">)</span>
    <span class="n">deliverboy</span><span class="p">.</span><span class="n">subscribe</span><span class="p">(</span><span class="n">subscriber_two</span><span class="p">,</span> <span class="s">"10sec-delivery"</span><span class="p">)</span>
    <span class="n">deliverboy</span><span class="p">.</span><span class="n">subscribe</span><span class="p">(</span><span class="n">subscriber_three</span><span class="p">,</span> <span class="s">"20sec-delivery"</span><span class="p">)</span>
    <span class="k">try</span><span class="p">:</span>
        <span class="n">asyncio</span><span class="p">.</span><span class="n">run</span><span class="p">(</span><span class="n">deliverboy</span><span class="p">.</span><span class="n">run</span><span class="p">())</span>
    <span class="k">except</span> <span class="nb">KeyboardInterrupt</span><span class="p">:</span>
        <span class="k">pass</span>
</code></pre></div></div>
<hr />

<h4 id="callback-based-approach">Callback based approach</h4>

<p>In python, <code class="language-plaintext highlighter-rouge">asyncio</code> implementation support either coroutines as well as
callbacks, this was borrowed from the <code class="language-plaintext highlighter-rouge">Twisted</code> framework which predates the
<code class="language-plaintext highlighter-rouge">asyncio</code> coming, and by no surprise, built-in APIs for TCP/UDP communication
offer both patterns, dividing them in <a href="https://docs.python.org/3/library/asyncio-protocol.html">low-level
APIs</a> and <a href="https://docs.python.org/3/library/asyncio-stream.html">high-level
APIs</a>, the first using
a callback approach and the latter, called streams, using a wrapper around
those low-level callbacks which makes them simple to use as coroutines.</p>

<hr />

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">import</span> <span class="nn">asyncio</span>
<span class="kn">import</span> <span class="nn">collections</span>

<span class="k">class</span> <span class="nc">CallbackDeliver</span><span class="p">:</span>

    <span class="s">"""Callback based implementation, subscriber interface retain the
    implementation, this time we'll use the `AbstractEventLoop.call_later`
    synchronous call to schedule again the run of a callable callback.
    """</span>

    <span class="k">def</span> <span class="nf">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
        <span class="bp">self</span><span class="p">.</span><span class="n">subscribers</span> <span class="o">=</span> <span class="n">collections</span><span class="p">.</span><span class="n">defaultdict</span><span class="p">(</span><span class="nb">list</span><span class="p">)</span>
        <span class="bp">self</span><span class="p">.</span><span class="n">event</span> <span class="o">=</span> <span class="bp">None</span>

    <span class="k">def</span> <span class="nf">subscribe</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">subscriber</span><span class="p">,</span> <span class="n">topic</span><span class="p">):</span>
        <span class="bp">self</span><span class="p">.</span><span class="n">subscribers</span><span class="p">[</span><span class="n">topic</span><span class="p">].</span><span class="n">append</span><span class="p">(</span><span class="n">subscriber</span><span class="p">)</span>

    <span class="k">def</span> <span class="nf">deliver</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">data</span><span class="p">,</span> <span class="n">topic</span><span class="p">):</span>
        <span class="k">if</span> <span class="n">topic</span> <span class="ow">not</span> <span class="ow">in</span> <span class="bp">self</span><span class="p">.</span><span class="n">subscribers</span><span class="p">:</span>
            <span class="k">return</span>
        <span class="k">for</span> <span class="n">subscriber</span> <span class="ow">in</span> <span class="bp">self</span><span class="p">.</span><span class="n">subscribers</span><span class="p">[</span><span class="n">topic</span><span class="p">]:</span>
            <span class="n">subscriber</span><span class="p">.</span><span class="n">update</span><span class="p">(</span><span class="n">data</span><span class="p">)</span>

    <span class="k">async</span> <span class="k">def</span> <span class="nf">run</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
        <span class="n">loop</span> <span class="o">=</span> <span class="n">asyncio</span><span class="p">.</span><span class="n">get_running_loop</span><span class="p">()</span>
        <span class="bp">self</span><span class="p">.</span><span class="n">event</span> <span class="o">=</span> <span class="n">asyncio</span><span class="p">.</span><span class="n">Event</span><span class="p">(</span><span class="n">loop</span><span class="o">=</span><span class="n">loop</span><span class="p">)</span>
        <span class="n">loop</span><span class="p">.</span><span class="n">call_later</span><span class="p">(</span><span class="mi">5</span><span class="p">,</span> <span class="bp">self</span><span class="p">.</span><span class="n">deliver_every_5s</span><span class="p">)</span>
        <span class="n">loop</span><span class="p">.</span><span class="n">call_later</span><span class="p">(</span><span class="mi">10</span><span class="p">,</span> <span class="bp">self</span><span class="p">.</span><span class="n">deliver_every_10s</span><span class="p">)</span>
        <span class="n">loop</span><span class="p">.</span><span class="n">call_later</span><span class="p">(</span><span class="mi">20</span><span class="p">,</span> <span class="bp">self</span><span class="p">.</span><span class="n">deliver_every_20s</span><span class="p">)</span>
        <span class="k">await</span> <span class="bp">self</span><span class="p">.</span><span class="n">event</span><span class="p">.</span><span class="n">wait</span><span class="p">()</span>

    <span class="k">def</span> <span class="nf">shutdown</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
        <span class="bp">self</span><span class="p">.</span><span class="n">event</span><span class="p">.</span><span class="nb">set</span><span class="p">()</span>

    <span class="k">def</span> <span class="nf">deliver_every_5s</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
        <span class="bp">self</span><span class="p">.</span><span class="n">deliver</span><span class="p">(</span><span class="s">"5 seconds delivery"</span><span class="p">,</span> <span class="s">"5sec-delivery"</span><span class="p">)</span>
        <span class="n">asyncio</span><span class="p">.</span><span class="n">get_running_loop</span><span class="p">().</span><span class="n">call_later</span><span class="p">(</span><span class="mi">5</span><span class="p">,</span> <span class="bp">self</span><span class="p">.</span><span class="n">deliver_every_5s</span><span class="p">)</span>

    <span class="k">def</span> <span class="nf">deliver_every_10s</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
        <span class="bp">self</span><span class="p">.</span><span class="n">deliver</span><span class="p">(</span><span class="s">"10 seconds delivery"</span><span class="p">,</span> <span class="s">"10sec-delivery"</span><span class="p">)</span>
        <span class="n">asyncio</span><span class="p">.</span><span class="n">get_running_loop</span><span class="p">().</span><span class="n">call_later</span><span class="p">(</span><span class="mi">10</span><span class="p">,</span> <span class="bp">self</span><span class="p">.</span><span class="n">deliver_every_10s</span><span class="p">)</span>

    <span class="k">def</span> <span class="nf">deliver_every_20s</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
        <span class="bp">self</span><span class="p">.</span><span class="n">deliver</span><span class="p">(</span><span class="s">"20 seconds delivery"</span><span class="p">,</span> <span class="s">"20sec-delivery"</span><span class="p">)</span>
        <span class="n">asyncio</span><span class="p">.</span><span class="n">get_running_loop</span><span class="p">().</span><span class="n">call_later</span><span class="p">(</span><span class="mi">20</span><span class="p">,</span> <span class="bp">self</span><span class="p">.</span><span class="n">deliver_every_20s</span><span class="p">)</span>

</code></pre></div></div>
<hr />

<p>To simplify a bit more it’s pretty straight to move some code inside a
decorator to make a method a looping callback.</p>

<hr />

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">import</span> <span class="nn">asyncio</span>
<span class="kn">import</span> <span class="nn">functools</span>

<span class="k">def</span> <span class="nf">run_every</span><span class="p">(</span><span class="n">seconds</span><span class="p">):</span>
    <span class="k">def</span> <span class="nf">inner_func</span><span class="p">(</span><span class="n">func</span><span class="p">):</span>
        <span class="o">@</span><span class="n">functools</span><span class="p">.</span><span class="n">wraps</span><span class="p">(</span><span class="n">func</span><span class="p">)</span>
        <span class="k">def</span> <span class="nf">func_wrapper</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="o">*</span><span class="n">args</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">):</span>
            <span class="n">loop</span> <span class="o">=</span> <span class="n">asyncio</span><span class="p">.</span><span class="n">get_running_loop</span><span class="p">()</span>
            <span class="n">func</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="o">*</span><span class="n">args</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">)</span>
            <span class="n">loop</span><span class="p">.</span><span class="n">call_later</span><span class="p">(</span><span class="n">seconds</span><span class="p">,</span> <span class="n">func_wrapper</span><span class="p">,</span> <span class="bp">self</span><span class="p">,</span> <span class="o">*</span><span class="n">args</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">)</span>
        <span class="k">return</span> <span class="n">func_wrapper</span>
    <span class="k">return</span> <span class="n">inner_func</span>

</code></pre></div></div>
<hr />

<p>This allow us to write our delivery methods with no need to retrieve the
running event loop, a detail in the decorator definition that should be taken
in consideration is that in the <code class="language-plaintext highlighter-rouge">AbstractEventLoop.call_later</code> call we pass in
as callable argument the <code class="language-plaintext highlighter-rouge">func_wrapper</code> defined inside the <code class="language-plaintext highlighter-rouge">inner_func</code> and not
only the <code class="language-plaintext highlighter-rouge">func</code> like we would’ve done normally. This for the simple reason that
we need to re-apply the decorator at each scheduling, if we passed just the
<code class="language-plaintext highlighter-rouge">func</code> the loop would’ve just run the next iteration of the callable without
re-scheduling it again.</p>

<hr />

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">@</span><span class="n">run_every</span><span class="p">(</span><span class="mi">5</span><span class="p">)</span>
<span class="k">def</span> <span class="nf">deliver_every_5s</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
    <span class="bp">self</span><span class="p">.</span><span class="n">deliver</span><span class="p">(</span><span class="s">"5 seconds delivery"</span><span class="p">,</span> <span class="s">"5sec-delivery"</span><span class="p">)</span>
</code></pre></div></div>

<p>Again we can run a simple main, that should result the same as the previous
coroutine-based implementation</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">if</span> <span class="n">__name__</span> <span class="o">==</span> <span class="s">'__main__'</span><span class="p">:</span>
    <span class="n">deliverboy</span> <span class="o">=</span> <span class="n">CallbackDeliver</span><span class="p">()</span>
    <span class="n">subscriber_one</span> <span class="o">=</span> <span class="n">NullSubscriber</span><span class="p">(</span><span class="s">'Toki'</span><span class="p">)</span>
    <span class="n">subscriber_two</span> <span class="o">=</span> <span class="n">NullSubscriber</span><span class="p">(</span><span class="s">'Shu'</span><span class="p">)</span>
    <span class="n">subscriber_three</span> <span class="o">=</span> <span class="n">NullSubscriber</span><span class="p">(</span><span class="s">'Raoh'</span><span class="p">)</span>
    <span class="n">deliverboy</span><span class="p">.</span><span class="n">subscribe</span><span class="p">(</span><span class="n">subscriber_one</span><span class="p">,</span> <span class="s">"5sec-delivery"</span><span class="p">)</span>
    <span class="n">deliverboy</span><span class="p">.</span><span class="n">subscribe</span><span class="p">(</span><span class="n">subscriber_two</span><span class="p">,</span> <span class="s">"10sec-delivery"</span><span class="p">)</span>
    <span class="n">deliverboy</span><span class="p">.</span><span class="n">subscribe</span><span class="p">(</span><span class="n">subscriber_three</span><span class="p">,</span> <span class="s">"20sec-delivery"</span><span class="p">)</span>
    <span class="k">try</span><span class="p">:</span>
        <span class="n">asyncio</span><span class="p">.</span><span class="n">run</span><span class="p">(</span><span class="n">deliverboy</span><span class="p">.</span><span class="n">run</span><span class="p">())</span>
    <span class="k">except</span> <span class="nb">KeyboardInterrupt</span><span class="p">:</span>
        <span class="n">deliverboy</span><span class="p">.</span><span class="n">shutdown</span><span class="p">()</span>
</code></pre></div></div>
<hr />

<p>And that’s it, these two snippets should be a good comparison of the two
methods, overall, as we can see, there are not many differences between them,
both systems accomplish to the results we expected, callback approach is probably
neater and require a bit less code to be written, but it assumes that <code class="language-plaintext highlighter-rouge">deliver</code>
method doesn’t block in any situation, so for the majority of cases it expects
some sort of buffering or queues to be involved. The coroutine-based instead
can be easily edited to make asynchronous even the <code class="language-plaintext highlighter-rouge">deliver</code> method in order to
not block the loop at any time.</p>]]></content><author><name></name></author><category term="asyncio" /><category term="python" /><summary type="html"><![CDATA[Simple pubsub solution using asyncio for different destinations]]></summary></entry></feed>