<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: Renuo AG</title>
    <description>The latest articles on Forem by Renuo AG (@renuo).</description>
    <link>https://forem.com/renuo</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Forganization%2Fprofile_image%2F8323%2F737348b6-33d2-4d6c-83fd-f9ae9644617f.png</url>
      <title>Forem: Renuo AG</title>
      <link>https://forem.com/renuo</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/renuo"/>
    <language>en</language>
    <item>
      <title>Shopify's 2000</title>
      <dc:creator>Josua Schmid</dc:creator>
      <pubDate>Fri, 19 Sep 2025 22:05:55 +0000</pubDate>
      <link>https://forem.com/renuo/shopifys-2000-175l</link>
      <guid>https://forem.com/renuo/shopifys-2000-175l</guid>
      <description>&lt;p&gt;Since &lt;a href="https://renuo.ch" rel="noopener noreferrer"&gt;Renuo&lt;/a&gt; is a member of the &lt;a href="https://rubyonrails.org/foundation/renuo" rel="noopener noreferrer"&gt;Rails Foundation&lt;/a&gt;, &lt;a class="mentioned-user" href="https://dev.to/coorasse"&gt;@coorasse&lt;/a&gt; and me were invited to the speakers dinner at Rails World 2025.&lt;/p&gt;

&lt;p&gt;We met people from Shopify and we started discussing. What do they even know about the one-man-agency-Rails we're using in our agency? They have their huge Rails 0.5 originated monolith and are &lt;a href="https://shopify.engineering/deconstructing-monolith-designing-software-maximizes-developer-productivity" rel="noopener noreferrer"&gt;proud of it&lt;/a&gt;. Don't they over-optimize for the monolith case? Far-from-agency-reality use cases, right?&lt;/p&gt;

&lt;p&gt;What I did not know until that point was that Shopify has 2000 further Rails applications running. They know our agency problems better than you might think.&lt;/p&gt;

</description>
      <category>rails</category>
      <category>ruby</category>
    </item>
    <item>
      <title>Small Dev Fascination</title>
      <dc:creator>Josua Schmid</dc:creator>
      <pubDate>Fri, 12 Sep 2025 08:12:21 +0000</pubDate>
      <link>https://forem.com/renuo/small-dev-fascination-303d</link>
      <guid>https://forem.com/renuo/small-dev-fascination-303d</guid>
      <description>&lt;p&gt;I host a meetup called &lt;a href="https://www.meetup.com/rubyonrails-ch" rel="noopener noreferrer"&gt;Railshöck&lt;/a&gt; in Zürich. From time to time someone shows up and holds a presentation from another world. They have small companies with one or two employees and they custom-tailor their Rails workflow to an extent which fascinates me. They own their development stack so completely that it's difficult to understand what they do at all. But it works for them in their own bubble. Without the need to share conventions over a large company.&lt;/p&gt;

&lt;p&gt;The latest two examples I want to mention are &lt;a href="https://sedlmair.ch/" rel="noopener noreferrer"&gt;Christian Seldmair&lt;/a&gt; with &lt;a href="https://svelte-on-rails.dev/" rel="noopener noreferrer"&gt;Svelte on Rails&lt;/a&gt; and &lt;a href="https://kalsan.ch/" rel="noopener noreferrer"&gt;Sandro Kalbermatter&lt;/a&gt; with &lt;a href="https://github.com/kalsan/compony" rel="noopener noreferrer"&gt;compony&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Svelte on Rails&lt;/strong&gt; is the realisation of the fact that you may want to sprinkle some reactiveness into your frontend without having the flicker effect like with Stimulus connects (worked around with the morph-dom and turbo). It's quite an elegant niche solution for a very interesting Rails problem using vite and server side rendering.&lt;/p&gt;

&lt;p&gt;And &lt;strong&gt;compony&lt;/strong&gt; is sheer DSL madness made exactly for the brain of Sandro. But because it's so productive for him, he discovered a new software design pattern. It's called the &lt;a href="https://github.com/kalsan/anchormodel" rel="noopener noreferrer"&gt;Anchor Model&lt;/a&gt;. It solves the problem of singleton database entities (think enum).&lt;/p&gt;

</description>
      <category>rails</category>
      <category>ruby</category>
    </item>
    <item>
      <title>The Testing Psyche</title>
      <dc:creator>Josua Schmid</dc:creator>
      <pubDate>Tue, 29 Apr 2025 08:15:39 +0000</pubDate>
      <link>https://forem.com/renuo/the-testing-psyche-3pjg</link>
      <guid>https://forem.com/renuo/the-testing-psyche-3pjg</guid>
      <description>&lt;p&gt;I'm only seeing dots when my Ruby RSpec test suite is running. But a lot is going on in my brain.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgpk7ooa3vp1rg71jmruc.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgpk7ooa3vp1rg71jmruc.png" alt="Image description" width="800" height="188"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Are the failing tests related in an easy-to-fix way or not?&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;These are either very easy to fix, because they are close together. It's probably about one conceptual fix. Or the test file can be deleted because the implementation vanished.&lt;/li&gt;
&lt;li&gt;I hope, this is related in a predictable way, like that I'm calling the service which is broken or vanished.&lt;/li&gt;
&lt;li&gt;Oh-oh is there a case-distinction? I hope the &lt;code&gt;FF&lt;/code&gt; correspond to the &lt;code&gt;FF&lt;/code&gt; in 1.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Resolution&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;code&gt;Factory not registered: "user"&lt;/code&gt;. Good for me. I know that I deleted it.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Missing partial shared/emails/loyalty/_email_salutation&lt;/code&gt;. What now? This seems weirdly unrelated. No idea why this happens.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;undefined local variable or method 'user'&lt;/code&gt;. Good for me. This is in fact related to 1.&lt;/li&gt;
&lt;/ol&gt;

</description>
      <category>ruby</category>
      <category>tdd</category>
      <category>rspec</category>
    </item>
    <item>
      <title>web-console and processes</title>
      <dc:creator>Josua Schmid</dc:creator>
      <pubDate>Tue, 15 Apr 2025 08:17:50 +0000</pubDate>
      <link>https://forem.com/renuo/web-console-and-processes-m89</link>
      <guid>https://forem.com/renuo/web-console-and-processes-m89</guid>
      <description>&lt;p&gt;Sometimes I see &lt;code&gt;No exception information available&lt;/code&gt; in &lt;a href="https://github.com/rails/web-console" rel="noopener noreferrer"&gt;web-console&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkoep96y3xtnhulh3pfbv.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkoep96y3xtnhulh3pfbv.png" alt="web-console failing me" width="800" height="253"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Digging in the &lt;a href="https://github.com/rails/web-console/blob/859bc60340e15b27f57ef345f0439f2a4d2bf9dc/lib/web_console/locales/en.yml#L6" rel="noopener noreferrer"&gt;web-console source&lt;/a&gt; finds me this:&lt;/p&gt;

&lt;p&gt;&lt;em&gt;If you happen to run on a multi-process server (like Unicorn or Puma) the process this request hit doesn't store %{id} in memory. Consider turning the number of processes/workers to one (1) or using a different server in development.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;So I look at my &lt;code&gt;config/puma.rb&lt;/code&gt;, change it and never see this error message again.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3hozpdur0mdyt9zrgg4t.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3hozpdur0mdyt9zrgg4t.png" alt="Image description" width="800" height="182"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>ruby</category>
      <category>rails</category>
      <category>debugging</category>
    </item>
    <item>
      <title>Catch JS Errors with Playwright</title>
      <dc:creator>Josua Schmid</dc:creator>
      <pubDate>Wed, 26 Mar 2025 11:18:25 +0000</pubDate>
      <link>https://forem.com/renuo/catch-js-errors-with-playwright-4ekm</link>
      <guid>https://forem.com/renuo/catch-js-errors-with-playwright-4ekm</guid>
      <description>&lt;p&gt;Selenium has issues. I'm using it with Capybara and the latest encounter was a &lt;a href="https://github.com/teamcapybara/capybara/issues/2770" rel="noopener noreferrer"&gt;&lt;code&gt;Net::ReadTimeout&lt;/code&gt;&lt;/a&gt;. I switched to Playwright following &lt;a href="https://justin.searls.co/posts/running-rails-system-tests-with-playwright-instead-of-selenium" rel="noopener noreferrer"&gt;Justin Searls guide&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;One issue I encountered after the migration was that browser console logs were not accessible anymore with &lt;code&gt;page.driver.browser.logs.get(:browser)&lt;/code&gt;. We use them to fail tests on JavaScript errors.&lt;/p&gt;

&lt;p&gt;One solution is to record them from the Playwright page with an event handler.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;before&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:each&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;type: :system&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;js: &lt;/span&gt;&lt;span class="kp"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;example&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
  &lt;span class="n"&gt;example&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;metadata&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:js_console_messages&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;||=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
  &lt;span class="no"&gt;Capybara&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;current_session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;driver&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;with_playwright_page&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
    &lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'console'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;lambda&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
      &lt;span class="n"&gt;example&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;metadata&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:js_console_messages&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="ss"&gt;type: &lt;/span&gt;&lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;type&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="ss"&gt;text: &lt;/span&gt;&lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="ss"&gt;location: &lt;/span&gt;&lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;location&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;after&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:each&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;type: :system&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;js: &lt;/span&gt;&lt;span class="kp"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;example&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;example&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;metadata&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:js_console_messages&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;present?&lt;/span&gt;
    &lt;span class="n"&gt;aggregate_failures&lt;/span&gt; &lt;span class="s1"&gt;'javascript console messages'&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="n"&gt;example&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;metadata&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:js_console_messages&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;each&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:type&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s1"&gt;'error'&lt;/span&gt;
          &lt;span class="n"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:text&lt;/span&gt;&lt;span class="p"&gt;]).&lt;/span&gt;&lt;span class="nf"&gt;to&lt;/span&gt; &lt;span class="n"&gt;be_nil&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"JS Error: &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:text&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
        &lt;span class="k"&gt;elsif&lt;/span&gt; &lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:type&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s1"&gt;'warning'&lt;/span&gt;
          &lt;span class="nb"&gt;warn&lt;/span&gt; &lt;span class="s2"&gt;"WARN: JS warning at &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:location&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;:&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:text&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
        &lt;span class="k"&gt;end&lt;/span&gt;
      &lt;span class="k"&gt;end&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The output of a failed test will look like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F37986nfyw1t2kz9rshxj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F37986nfyw1t2kz9rshxj.png" alt="page.execute_script('inexistentFunction()') causes Playwright::Error" width="800" height="293"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The previous solution to simply look at &lt;code&gt;page.driver.browser.logs.get(:browser)&lt;/code&gt; felt more natural to me. I'd be happy to receive a hint on how to catch JS errors in Ruby more nicely. &lt;/p&gt;

</description>
      <category>capybara</category>
      <category>selenium</category>
      <category>playwright</category>
      <category>ruby</category>
    </item>
    <item>
      <title>Exploring Rust: A Rubyist's Perspective</title>
      <dc:creator>Dani Bengl</dc:creator>
      <pubDate>Fri, 28 Feb 2025 21:24:18 +0000</pubDate>
      <link>https://forem.com/renuo/exploring-rust-a-rubyists-perspective-45f0</link>
      <guid>https://forem.com/renuo/exploring-rust-a-rubyists-perspective-45f0</guid>
      <description>&lt;p&gt;At Renuo, we love Ruby. It's simple, elegant, and powerful. But let's be honest, Ruby isn't the fastest language out there.&lt;/p&gt;

&lt;p&gt;Over the last couple of months, I've been exploring low-level programming, hoping to bridge the gap between the high-level world of Ruby and the lower-level world of systems programming. To do this, I started working on my first Rust project: a blazingly fast voxel "game" called &lt;a href="https://github.com/CuddlyBunion341/rsmc" rel="noopener noreferrer"&gt;rsmc&lt;/a&gt;. It features a terrain generator, meshing, a scalable client-server architecture, and custom serialized messages for high-speed communication. This project has been my playground for learning Rust, and in this post, I'll share some of the lessons I've learned along the way.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnx2cywgbq0i2jatmqt6o.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnx2cywgbq0i2jatmqt6o.png" alt="Early stage of development in RSMC. Renet visualiser for simultanous client/server connections." width="800" height="381"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Rust?
&lt;/h2&gt;

&lt;p&gt;Rust is one of the most appreciated programming languages, as highlighted in the &lt;a href="https://octoverse.github.com/" rel="noopener noreferrer"&gt;GitHub Octoverse Survey&lt;/a&gt;. It offers memory safety, high performance, and strong tooling, making it a solid choice for both small utilities and large-scale applications. Many of the tools I use daily, like &lt;a href="https://github.com/alacritty/alacritty" rel="noopener noreferrer"&gt;Alacritty&lt;/a&gt; and &lt;a href="https://1password.com/" rel="noopener noreferrer"&gt;1Password&lt;/a&gt;, benefit from Rust's speed and reliability.&lt;/p&gt;

&lt;h3&gt;
  
  
  Key Benefits
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;Performance:&lt;/strong&gt; Comparable to C and C++, but with safety mechanisms that prevent common errors.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Memory safety:&lt;/strong&gt; Eliminates null pointer dereferences, segmentation faults, and data races.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Modern syntax:&lt;/strong&gt; Readable and expressive, making it accessible despite its low-level capabilities.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Powerful tooling:&lt;/strong&gt; &lt;a href="https://doc.rust-lang.org/cargo/" rel="noopener noreferrer"&gt;Cargo&lt;/a&gt; simplifies dependency management, builds, and testing.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For my voxel game, Rust's speed and safety make it an excellent choice for terrain generation, networking, and real-time interactions. Unlike dynamically typed languages such as Ruby, Rust catches entire categories of bugs at compile time, improving maintainability.&lt;/p&gt;

&lt;p&gt;Beyond CLI tools, Rust powers game engines, operating systems, simulations, and even web browsers, proving its adaptability across different domains.&lt;/p&gt;

&lt;h2&gt;
  
  
  Bevy: Game Development Made Fun
&lt;/h2&gt;

&lt;p&gt;Since my project is built with &lt;a href="https://bevyengine.org/" rel="noopener noreferrer"&gt;Bevy&lt;/a&gt;, understanding its core concepts was very important. Bevy's entity-component-system (ECS) architecture makes game development modular and efficient, allowing for highly decoupled systems.&lt;/p&gt;

&lt;p&gt;Some of my favorite takeaways:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;Systems:&lt;/strong&gt; Keep them small and focused on one task. This way they are easier to test and extend.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Plugins:&lt;/strong&gt; Encapsulate resources, systems, and components into distinguishable modules.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Events:&lt;/strong&gt; Use events to the fullest extent to decouple systems and keep code modular.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;States:&lt;/strong&gt; Run systems only when they are relevant (e.g., Menu, Playing). This helps with UI and logic separation. In particular this PR: &lt;a href="https://github.com/CuddlyBunion341/rsmc/pull/32" rel="noopener noreferrer"&gt;#32&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Bevy makes structuring a game engine intuitive, and its Rust-first approach ensures safety and performance while keeping things flexible. If you're interested in learning more about the ECS approach to game development, I wrote a blog article about planning an ECS: &lt;a href="https://dev.to/renuo/multiplayer-in-rust-using-renet-and-bevy-17p6"&gt;Multiplayer in Rust Using Renet and Bevy&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Feature Flags: Shipping Less in Production
&lt;/h2&gt;

&lt;p&gt;Feature flags allow enabling or disabling specific functionality at compile time, making it easy to toggle features based on configuration.&lt;/p&gt;

&lt;p&gt;In my voxel game, feature flags help manage debugging tools like wireframe rendering and debug UI. What's neat about these feature flags is that debug code doesn't get shipped in production, reducing binary size and keeping the release build clean.&lt;/p&gt;

&lt;p&gt;In &lt;code&gt;Cargo.toml&lt;/code&gt;, you can define feature flags like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight toml"&gt;&lt;code&gt;&lt;span class="nn"&gt;[features]&lt;/span&gt; 
&lt;span class="py"&gt;egui_layer&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
&lt;span class="py"&gt;terrain_visualizer&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"egui_layer"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="py"&gt;renet_visualizer&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"egui_layer"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="err"&gt;`&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And then use them in your code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="nd"&gt;#[cfg(feature&lt;/span&gt; &lt;span class="nd"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"egui_layer"&lt;/span&gt;&lt;span class="nd"&gt;)]&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;   
&lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="nn"&gt;bevy_inspector_egui&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;bevy_egui&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;EguiPlugin&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="nf"&gt;.add_plugins&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;DefaultPlugins&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="nf"&gt;.add_plugins&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;EguiPlugin&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The downside is that testing all possible feature combinations is a challenge. With just five features, you already have 32 different configurations to check. But that's the price of flexibility.&lt;/p&gt;

&lt;h2&gt;
  
  
  Cargo Watch: Automating Workflows
&lt;/h2&gt;

&lt;p&gt;During the development of rsmc, I often needed to recompile code, and manually restarting my binary after every change quickly became tedious. I really wish I had discovered &lt;a href="https://docs.rs/crate/cargo-watch/latest" rel="noopener noreferrer"&gt;&lt;code&gt;cargo-watch&lt;/code&gt;&lt;/a&gt; earlier.&lt;/p&gt;

&lt;p&gt;Just install it and let the watcher do its thing:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;cargo watch &lt;span class="nt"&gt;-x&lt;/span&gt; &lt;span class="s1"&gt;'run --bin client'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Composition Over Inheritance
&lt;/h2&gt;

&lt;p&gt;Coming from Ruby, where inheritance is common, Rust's approach felt different. Rust doesn't have classes. It uses structs and traits. This forced me to use composition over inheritance and think differently about code structure.&lt;/p&gt;

&lt;p&gt;Here's an example from my terrain generator:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="n"&gt;NoiseFunctionParams&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="n"&gt;octaves&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;u32&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="n"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;f64&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="c1"&gt;// ... &lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;  

&lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="n"&gt;HeightParams&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;     
  &lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="n"&gt;noise&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;NoiseFunctionParams&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// Composition!&lt;/span&gt;
  &lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="n"&gt;splines&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Vec&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Vec2&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Instead of inheriting from a base class, &lt;code&gt;HeightParams&lt;/code&gt; contains a &lt;code&gt;NoiseFunctionParams&lt;/code&gt; struct. This keeps the code flexible and avoids deep inheritance hierarchies.&lt;/p&gt;

&lt;h2&gt;
  
  
  Macros: Code That Writes Code
&lt;/h2&gt;

&lt;p&gt;Rust's macros are like Ruby's metaprogramming but more structured and powerful. They help eliminate boilerplate while maintaining type safety.&lt;/p&gt;

&lt;p&gt;Here's a macro I used to &lt;a href="https://github.com/CuddlyBunion341/rsmc/blob/main/src/client/terrain/util/blocks.rs" rel="noopener noreferrer"&gt;define blocks&lt;/a&gt; in my project:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="nd"&gt;macro_rules!&lt;/span&gt; &lt;span class="n"&gt;add_block&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$block_id:expr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$is_solid:expr&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;Block&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;$block_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;is_solid&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;$is_solid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="n"&gt;BLOCKS&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Block&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="mi"&gt;14&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="nd"&gt;add_block!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nn"&gt;BlockId&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Air&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="nd"&gt;add_block!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nn"&gt;BlockId&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Grass&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="c1"&gt;// ...&lt;/span&gt;
&lt;span class="p"&gt;];&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Macros don't affect runtime performance. They are a zero-cost abstraction, a great feature of Rust.&lt;/p&gt;

&lt;h2&gt;
  
  
  Rust's Learning Curve: Worth the Effort?
&lt;/h2&gt;

&lt;p&gt;Rust isn't the easiest language to pick up. The borrow checker takes time to understand, and coming from Ruby, its verbosity stands out. Ruby achieves more with fewer symbols. While Rust's explicitness helps with maintainability and reducing hidden behaviors, it also means writing more boilerplate.&lt;/p&gt;

&lt;p&gt;One of the biggest disadvantages to me is compile times. They can be frustrating since Rust enforces strict checks, but this reduces runtime errors. There's even an &lt;a href="https://xkcd.com/303/" rel="noopener noreferrer"&gt;XKCD comic&lt;/a&gt; about it.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fyjbrwv9is0lo4a9cmmw6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fyjbrwv9is0lo4a9cmmw6.png" alt="XKCD 303 - The #1 programmer excuse for legitimately slacking off: " width="413" height="360"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;From my experience, Rust has its trade-offs. Catching many errors at compile time reduces debugging effort, but the strict rules and verbosity make writing new code slower compared to Ruby. That said, Rust's language servers provide excellent refactoring support, which makes working with larger projects easier.&lt;/p&gt;

&lt;p&gt;For quick prototyping and iteration, scripting languages such as Ruby are still the better choice. However, when stability, performance, and long-term maintainability matters, Rust seems to be the better pick.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Rust isn't just another language. It changes how you think about programming. It makes you more aware of memory, safety, and performance. The learning curve is steep, but if you stick with it, the rewards are worth it.&lt;/p&gt;

&lt;p&gt;I hope you enjoyed this journey into Rust! If you're a Ruby developer who has also tried Rust, what challenges have you faced? I'd love to hear your thoughts!&lt;/p&gt;

</description>
      <category>rust</category>
      <category>gamedev</category>
    </item>
    <item>
      <title>Multiplayer in Rust using Renet and Bevy</title>
      <dc:creator>Dani Bengl</dc:creator>
      <pubDate>Wed, 03 Jul 2024 15:24:33 +0000</pubDate>
      <link>https://forem.com/renuo/multiplayer-in-rust-using-renet-and-bevy-17p6</link>
      <guid>https://forem.com/renuo/multiplayer-in-rust-using-renet-and-bevy-17p6</guid>
      <description>&lt;p&gt;Here at Renuo, we specialize in web technologies such as Ruby on Rails, React, Angular, and Spring. One of our core company values is continuous learning: we love exploring new technologies even beyond our usual scope of expertise.&lt;/p&gt;

&lt;p&gt;Inspired by Michael’s Unity Powerday, I decided to delve into how multiplayer games operate. As a team, we held a competition to implement FPS (First-Person Shooter) games using C# boilerplate. Initially, the sheer amount of boilerplate required felt overwhelming. Beyond that, I wanted to understand client/server data synchronization at a lower level of abstraction.&lt;/p&gt;

&lt;p&gt;My recent experiences with &lt;a href="https://rustlang.org/" rel="noopener noreferrer"&gt;Rust&lt;/a&gt; and &lt;a href="https://bevyengine.org/" rel="noopener noreferrer"&gt;Bevy&lt;/a&gt; convinced me to write this blog article to share my newfound learnings of game development.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Choose Rust
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Advantages
&lt;/h3&gt;

&lt;p&gt;Rust is a statically typed, memory-safe, multi-paradigm programming language that matches the performance of C. Due to its safety, concurrency features, and modern syntax, it has gained popularity among developers in recent years.&lt;/p&gt;

&lt;p&gt;Some notable software written in Rust includes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Rapier3d&lt;/strong&gt;: A performant physics engine often used with ThreeJS.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Ripgrep&lt;/strong&gt;: A performant command line search tool.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Alacritty&lt;/strong&gt;: A performant, minimalistic cross-platform terminal emulator.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Warp&lt;/strong&gt;: A performant, modern terminal IDE.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tauri&lt;/strong&gt;: A performant and lightweight alternative to ElectronJS.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Amethyst&lt;/strong&gt;: A performant tiling window manager for MacOS.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Condorium Blockchain&lt;/strong&gt;: A performant and secure blockchain technology.&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;I mean, there is no such thing as a perfect programming language Rust is merely a statically type low-level multi-paradigm perfect programming language&lt;br&gt;
&lt;a href="https://www.youtube.com/watch?v=TGfQu0bQTKc&amp;amp;t=95s" rel="noopener noreferrer"&gt;YouTube interview&lt;/a&gt; by &lt;a href="https://www.youtube.com/@programmersarealsohuman5909" rel="noopener noreferrer"&gt;Programmers Are Also Human&lt;/a&gt;, Rust is a perfect programming language.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6owazv7mdfhl28879ywg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6owazv7mdfhl28879ywg.png" alt="Ferris the Rustacean" width="749" height="435"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Picking a game engine
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;There are currently 5 games written in Rust.  And 50 game engines.&lt;br&gt;
Interview with a Senior Rust Developer - &lt;a href="https://www.youtube.com/watch?v=TGfQu0bQTKc&amp;amp;t=168s" rel="noopener noreferrer"&gt;2:52&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;There are too many game engines available for Rust. An excellent resource is &lt;a href="https://arewegameyet.rs/ecosystem/engines/" rel="noopener noreferrer"&gt;Are We Game Yet&lt;/a&gt;. I also recommend &lt;a href="https://www.geeksforgeeks.org/rust-game-engines/" rel="noopener noreferrer"&gt;this article by GeeksforGeeks&lt;/a&gt;, which makes picking the optimal engine easier.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Difference Between Bevy and Other Engines
&lt;/h3&gt;

&lt;p&gt;While big game engines like Godot, Unity, and Unreal Engine come with graphical editors, Bevy focuses on providing a simple yet powerful, multithreaded system to manage game state with minimal code.&lt;/p&gt;

&lt;h2&gt;
  
  
  Understanding ECS
&lt;/h2&gt;

&lt;p&gt;The &lt;a href="https://en.wikipedia.org/wiki/Entity_component_system" rel="noopener noreferrer"&gt;ECS&lt;/a&gt; (Entity Component System) is a software pattern that emphasizes a modular design. It is commonly utilized in game and game engine development. This approach separates the data and behaviour of game entities into components, making it easier to manage and organize complex systems.&lt;/p&gt;

&lt;h3&gt;
  
  
  Components of ECS
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Entities:&lt;/strong&gt; Unique identifiers of a group of components (A u32 wrapper in bevy).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Components:&lt;/strong&gt; Modular data pieces that represent specific Entity attributes. (A struct that derives the Component macro in bevy)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;System:&lt;/strong&gt; Logic that operates on entities and their components. (A struct that derives the Resource macro in bevy)&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Systems in Bevy
&lt;/h3&gt;

&lt;p&gt;Systems in Bevy are functions that take various parameters such as queries, EventReaders, assets, and resources and apply logic to them.&lt;/p&gt;

&lt;p&gt;One powerful feature of Bevy systems is the Query interface. It allows you to fetch specific data for entities in your project. For instance, if no entity is found, the &lt;code&gt;single_mut()&lt;/code&gt; function will raise an error. Multiple queries are possible as long as entities do not overlap.&lt;/p&gt;

&lt;p&gt;Below is an example where the &lt;code&gt;MyPlayer&lt;/code&gt; component doesn't contain any data but is used to denote that the entity belongs to the client player.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;update_player_movement_system&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="n"&gt;keyboard_events&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;EventReader&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;KeyboardInput&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Query&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="n"&gt;Transform&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;MyPlayer&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="n"&gt;transform&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="nf"&gt;.single_mut&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;keyboard_events&lt;/span&gt;&lt;span class="nf"&gt;.read&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="n"&gt;delta_position&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;Vec3&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;0.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0.0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="k"&gt;match&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="py"&gt;.key_code&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nn"&gt;KeyCode&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;KeyW&lt;/span&gt; &lt;span class="k"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;delta_position&lt;/span&gt;&lt;span class="py"&gt;.z&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mf"&gt;0.1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="nn"&gt;KeyCode&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;KeyS&lt;/span&gt; &lt;span class="k"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;delta_position&lt;/span&gt;&lt;span class="py"&gt;.z&lt;/span&gt; &lt;span class="o"&gt;-=&lt;/span&gt; &lt;span class="mf"&gt;0.1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="nn"&gt;KeyCode&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;KeyA&lt;/span&gt; &lt;span class="k"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;delta_position&lt;/span&gt;&lt;span class="py"&gt;.x&lt;/span&gt; &lt;span class="o"&gt;-=&lt;/span&gt; &lt;span class="mf"&gt;0.1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="nn"&gt;KeyCode&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;KeyD&lt;/span&gt; &lt;span class="k"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;delta_position&lt;/span&gt;&lt;span class="py"&gt;.x&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mf"&gt;0.1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="k"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;new_position&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;transform&lt;/span&gt;&lt;span class="py"&gt;.translation&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;delta_position&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="n"&gt;transform&lt;/span&gt;&lt;span class="py"&gt;.translation&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;new_position&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The example above has a flaw: the player position update has a fixed step. Instead of using a fixed-step update, consider using the time passed since the last step. This will ensure a consistent movement speed regardless of the frame rate.&lt;/p&gt;

&lt;h2&gt;
  
  
  Picking Networking Libraries
&lt;/h2&gt;

&lt;p&gt;We need to decide on networking libraries after choosing Bevy as our game engine. Here are a few options:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Matchbox&lt;/li&gt;
&lt;li&gt;Naia&lt;/li&gt;
&lt;li&gt;Renet&lt;/li&gt;
&lt;li&gt;Bootleg_networking&lt;/li&gt;
&lt;li&gt;Spicy_networking&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I chose &lt;strong&gt;Renet&lt;/strong&gt; because of its popularity and my good experiences with its boilerplate. Additionally, I included &lt;strong&gt;Serde&lt;/strong&gt; for efficient binary message encoding.&lt;/p&gt;

&lt;h2&gt;
  
  
  Sketching the scene
&lt;/h2&gt;

&lt;p&gt;Before coding, let's sketch a simple scene:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Camera:&lt;/strong&gt; Renders the scene.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Plane:&lt;/strong&gt; Represents the floor.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Green Cube:&lt;/strong&gt; Represents the player.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Red Cubes:&lt;/strong&gt; Represent other players.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Attributes to synchronize:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Position:&lt;/strong&gt; &lt;code&gt;Vec3&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Input method:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Keyboard (WASD):&lt;/strong&gt; Used to translate the player.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Handling Player inputs
&lt;/h3&gt;

&lt;p&gt;There are three main ways to handle player inputs:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Client-side:&lt;/strong&gt; The client handles inputs, moves the player, and sends the position to the server.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Server-side:&lt;/strong&gt; The client sends input data to the server, and the server responds with the position.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Hybrid:&lt;/strong&gt; The client handles inputs and shares them with the server, which then responds with position synchronization.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The client-side approach can reduce latency, but is less secure. The server-side approach is more secure but adds server load. The hybrid approach offers a balance, but is more complex.&lt;/p&gt;

&lt;h2&gt;
  
  
  Planning
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Client
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Type&lt;/th&gt;
&lt;th&gt;Name&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Component&lt;/td&gt;
&lt;td&gt;PlayerEntity(ClientId)&lt;/td&gt;
&lt;td&gt;Represents an enemy player entity.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Component&lt;/td&gt;
&lt;td&gt;MyPlayer&lt;/td&gt;
&lt;td&gt;Marks the current player entity.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Event&lt;/td&gt;
&lt;td&gt;PlayerSpawnEvent(ClientId)&lt;/td&gt;
&lt;td&gt;Emitted when a player joins. Adds a player object to the scene.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Event&lt;/td&gt;
&lt;td&gt;PlayerDespawnEvent(ClientId)&lt;/td&gt;
&lt;td&gt;Emitted when a player leaves. Removes a player object from the scene.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Event&lt;/td&gt;
&lt;td&gt;PlayerMoveEvent(ClientId, Vec3)&lt;/td&gt;
&lt;td&gt;Emitted by the player controller when a player moves.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Event&lt;/td&gt;
&lt;td&gt;LobbySyncEvent(HashMap)&lt;/td&gt;
&lt;td&gt;Emitted when the client receives sync messages from the server. Updates other player positions using their ID and position.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;System&lt;/td&gt;
&lt;td&gt;send_message_system&lt;/td&gt;
&lt;td&gt;Shares MyPlayer position data with the server.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;System&lt;/td&gt;
&lt;td&gt;receive_message_system&lt;/td&gt;
&lt;td&gt;Processes messages received from the server.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;System&lt;/td&gt;
&lt;td&gt;update_player_movement_system&lt;/td&gt;
&lt;td&gt;Updates player position from keyboard input.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;System&lt;/td&gt;
&lt;td&gt;setup_system&lt;/td&gt;
&lt;td&gt;Sets up the scene with a camera, a ground plane, and a mesh for the current player.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;System&lt;/td&gt;
&lt;td&gt;handle_player_spawn_event_system&lt;/td&gt;
&lt;td&gt;Adds enemy players to the scene once they join in.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;System&lt;/td&gt;
&lt;td&gt;handle_lobby_sync_event_system&lt;/td&gt;
&lt;td&gt;Updates enemy player positions and potentially spawns missed players into the scene.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Server
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Type&lt;/th&gt;
&lt;th&gt;Name&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Resource&lt;/td&gt;
&lt;td&gt;PlayerLobby(HashMap)&lt;/td&gt;
&lt;td&gt;Holds attributes of all players currently in the game. Used to synchronize these attributes with the clients.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;System&lt;/td&gt;
&lt;td&gt;send_message_system&lt;/td&gt;
&lt;td&gt;Broadcasts player positions to keep enemy player positions in clients up-to-date.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;System&lt;/td&gt;
&lt;td&gt;receive_message_system&lt;/td&gt;
&lt;td&gt;Updates player lobby position based on messages received from the RenetClient.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;System&lt;/td&gt;
&lt;td&gt;handle_events_system&lt;/td&gt;
&lt;td&gt;Handles events such as ClientConnected and ClientDisconnected from the Bevy Renet plugin.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Deciding on a project structure
&lt;/h2&gt;

&lt;p&gt;I separated the ECS components into specific modules to structure the Bevy project and used two entry points: one for the client and one for the server. Shared code, such as structures for Client-Server communication, can be placed in a global &lt;code&gt;lib&lt;/code&gt; module.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;src
├── client
│   ├── components.rs
│   ├── events.rs
│   ├── main.rs
│   ├── resources.rs
│   └── systems.rs
├── lib.rs
└── server
    ├── main.rs
    ├── resources.rs
    └── systems.rs
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Defining various entry points is as simple as adding this to the &lt;code&gt;Cargo.toml&lt;/code&gt; file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[[bin]]
name = "server"
path = "src/server/main.rs"

[[bin]]
name = "client"
path = "src/client/main.rs"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Afterwards, the binaries can be run with the &lt;code&gt;--bin&lt;/code&gt; argument:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;cargo run --bin server
cargo run --bin client
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Setting up Boilerplate
&lt;/h2&gt;

&lt;p&gt;To integrate &lt;code&gt;bevy_renet&lt;/code&gt; into the bevy project, I followed the &lt;a href="https://github.com/lucaspoffo/renet/blob/master/bevy_renet/README.md" rel="noopener noreferrer"&gt;Bevy Renet documentation&lt;/a&gt;. In my setup, I used these two default channels:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Unreliable:&lt;/strong&gt; Used for sending and receiving messages for player attribute synchronization. (We don't care about every state change, we can pick the last one)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;ReliableOrdered:&lt;/strong&gt; Used for sending and receiving messages for player actions such as joining and leaving.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Synchronising player positions
&lt;/h2&gt;

&lt;p&gt;Here's an example of sending player attributes from the client to the server:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;send_message_system&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;ResMut&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;RenetClient&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Query&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;MyPlayer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;Transform&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;transform&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="nf"&gt;.single&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;player_sync&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;PlayerAttributes&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;position&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;transform&lt;/span&gt;&lt;span class="py"&gt;.translation&lt;/span&gt;&lt;span class="nf"&gt;.into&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;bincode&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;serialize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;player_sync&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="nf"&gt;.unwrap&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="nf"&gt;.send_message&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nn"&gt;DefaultChannel&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Unreliable&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Handling messages from the client on the server:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;receive_message_system&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="n"&gt;server&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;ResMut&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;RenetServer&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="n"&gt;player_lobby&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;ResMut&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;PlayerLobby&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;client_id&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;server&lt;/span&gt;&lt;span class="nf"&gt;.clients_id&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;server&lt;/span&gt;&lt;span class="nf"&gt;.receive_message&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;client_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nn"&gt;DefaultChannel&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Unreliable&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nf"&gt;Some&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;player&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;PlayerAttributes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;bincode&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;deserialize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="nf"&gt;.unwrap&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
            &lt;span class="n"&gt;player_lobby&lt;/span&gt;&lt;span class="na"&gt;.0&lt;/span&gt;&lt;span class="nf"&gt;.insert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;client_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;player&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Sending attributes of all players back to the client:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;send_message_system&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="n"&gt;server&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;ResMut&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;RenetServer&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;player_lobby&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Res&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;PlayerLobby&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;chanel&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;DefaultChannel&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Unreliable&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;lobby&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;player_lobby&lt;/span&gt;&lt;span class="na"&gt;.0&lt;/span&gt;&lt;span class="nf"&gt;.clone&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;multiplayer_demo&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;ServerMessage&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;LobbySync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;lobby&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;bincode&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;serialize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="nf"&gt;.unwrap&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="nf"&gt;print_lobby&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;player_lobby&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;server&lt;/span&gt;&lt;span class="nf"&gt;.broadcast_message&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;chanel&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Synchronizing the client scene with the player attributes from the server:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;handle_lobby_sync_event_system&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="n"&gt;spawn_events&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;EventWriter&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;PlayerSpawnEvent&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="n"&gt;sync_events&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;EventReader&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;LobbySyncEvent&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Query&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;PlayerEntity&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="n"&gt;Transform&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;my_clinet_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Res&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;MyClientId&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;event_option&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;sync_events&lt;/span&gt;&lt;span class="nf"&gt;.read&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="nf"&gt;.last&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;event_option&lt;/span&gt;&lt;span class="nf"&gt;.is_none&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;event_option&lt;/span&gt;&lt;span class="nf"&gt;.unwrap&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;client_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;player_sync&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="na"&gt;.0&lt;/span&gt;&lt;span class="nf"&gt;.iter&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;client_id&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;my_clinet_id&lt;/span&gt;&lt;span class="na"&gt;.0&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;continue&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="n"&gt;found&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;player_entity&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="n"&gt;transform&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="nf"&gt;.iter_mut&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;client_id&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;player_entity&lt;/span&gt;&lt;span class="na"&gt;.0&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;new_position&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;player_sync&lt;/span&gt;&lt;span class="py"&gt;.position&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
                &lt;span class="n"&gt;transform&lt;/span&gt;&lt;span class="py"&gt;.translation&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;new_position&lt;/span&gt;&lt;span class="nf"&gt;.into&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
                &lt;span class="n"&gt;found&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;found&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nd"&gt;info!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Spawning player {}: {:?}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;client_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;player_sync&lt;/span&gt;&lt;span class="py"&gt;.position&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="n"&gt;spawn_events&lt;/span&gt;&lt;span class="nf"&gt;.send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;PlayerSpawnEvent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;client_id&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;The multiplayer demo project demonstrates the intricate planning and attention to detail needed to synchronize player attributes between the client and server. This showcases the complexity of creating a seamless multiplayer experience at a lower level.&lt;/p&gt;

&lt;p&gt;For more detailed code, visit the MIT-Licensed &lt;a href="https://github.com/CuddlyBunion341/bevy-multiplayer" rel="noopener noreferrer"&gt;GitHub repository&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>rust</category>
      <category>gamedev</category>
    </item>
    <item>
      <title>2 Simple Configurations to Supercharge your TMUX Setup!</title>
      <dc:creator>Dani Bengl</dc:creator>
      <pubDate>Sun, 14 Apr 2024 11:22:48 +0000</pubDate>
      <link>https://forem.com/renuo/2-simple-configurations-to-supercharge-your-tmux-setup-5572</link>
      <guid>https://forem.com/renuo/2-simple-configurations-to-supercharge-your-tmux-setup-5572</guid>
      <description>&lt;p&gt;Tmux is a terminal multiplexer. It is a tool for managing multiple shells inside a single terminal window. It is integrated into most modern terminal emulators like Iterm2 or Warp.  In this blog article, I will show you two simple configurations that drastically improved my tmux experience.&lt;/p&gt;

&lt;h2&gt;
  
  
  Autoresizing panes
&lt;/h2&gt;

&lt;p&gt;The main problem I had with tmux when starting out was that panes were not automatically resized as I was used from Warp:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqjyfdlwmxlsb9bkqhxfv.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqjyfdlwmxlsb9bkqhxfv.gif" alt="Managing window panes in tmux with auto-resizing not configured." width="760" height="586"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Don't get confused by the remapped tmux prefix. The main thing to notice here is that I am using &lt;code&gt;&amp;lt;PREFIX&amp;gt; + E&lt;/code&gt; to spread the panes out evenly.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What we now need is:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The command to resize the windows&lt;/li&gt;
&lt;li&gt;The hook(s) for executing the command&lt;/li&gt;
&lt;li&gt;Update the tmux configuration with the hook and the command.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Part1: Finding the command
&lt;/h3&gt;

&lt;p&gt;According to the manual entry for tmux:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;select-layout [-Enop] [-t target-pane] [layout-name]
                             (alias: selectl)
                 Choose a specific layout for a window.  If layout-name is not given, the last preset layout used (if any) is
                 reapplied.  -n and -p are equivalent to the next-layout and previous-layout commands.  -o applies the last
                 set layout if possible (undoes the most recent layout change).  -E spreads the current pane and any panes
                 next to it out evenly.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We can use the &lt;code&gt;select-layout -E&lt;/code&gt; command to spread the panes evenly.&lt;/p&gt;

&lt;h3&gt;
  
  
  Part2: Finding the hooks
&lt;/h3&gt;

&lt;p&gt;The first part is done. Now we need the hooks.&lt;/p&gt;

&lt;p&gt;Unsurprisingly, this can also be found in the manual entry:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;%window-pane-changed window-id pane-id
     The active pane in the window with ID window-id changed to the pane with ID pane-id.

...

window-resized          Run when a window is resized.  This may be after the client-resized hook is run.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Part3: Updating the configuration file
&lt;/h3&gt;

&lt;p&gt;If you &lt;a href="https://unix.stackexchange.com/questions/644819/is-it-possible-to-move-tmux-conf-to-config-folder" rel="noopener noreferrer"&gt;haven't moved&lt;/a&gt; the &lt;code&gt;.tmux.conf&lt;/code&gt; to &lt;code&gt;.config&lt;/code&gt;, you can find the default configuration file for MacOS at &lt;code&gt;~/.tmux.conf&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;To execute the command on the resize and pane changed hooks, we can add these lines to the tmux configuration:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# .tmux.conf&lt;/span&gt;
...
set-hook &lt;span class="nt"&gt;-g&lt;/span&gt; window-pane-changed &lt;span class="s1"&gt;'select-layout -E'&lt;/span&gt;
set-hook &lt;span class="nt"&gt;-g&lt;/span&gt; client-resized &lt;span class="s1"&gt;'select-layout -E'&lt;/span&gt;
...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is how the panes behave after adjusting the configuration:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fu4pg3e849imrdnyf78q7.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fu4pg3e849imrdnyf78q7.gif" alt="Creating window panes in tmux with auto-resizing configured." width="760" height="586"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;According to the manual entry, we can also shorten the command to the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;set-hook &lt;span class="nt"&gt;-g&lt;/span&gt; window-pane-changed &lt;span class="s1"&gt;'selectl -E'&lt;/span&gt;
set-hook &lt;span class="nt"&gt;-g&lt;/span&gt; client-resized &lt;span class="s1"&gt;'selectl -E'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Dimming Inactive Panes
&lt;/h2&gt;

&lt;p&gt;Next to automatically resizing windows, we can also automatically dim panes that aren't active by using the following configuration:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;set-hook &lt;span class="nt"&gt;-g&lt;/span&gt; pane-focus-out &lt;span class="s1"&gt;'select-pane -P bg=colour233,fg=colour10'&lt;/span&gt;
set-hook &lt;span class="nt"&gt;-g&lt;/span&gt; pane-focus-in &lt;span class="s1"&gt;'select-pane -P bg=default,fg=default'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5kgr00agqsw70mb5qrau.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5kgr00agqsw70mb5qrau.gif" alt="Switching panes in tmux with dimming enabled." width="800" height="617"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now, this is a much nicer alternative than the active border color. If you are wondering how I got rid of it, I used this configuration:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;set&lt;/span&gt; &lt;span class="nt"&gt;-g&lt;/span&gt; pane-active-border-style &lt;span class="s2"&gt;"bg=#000000,fg=white"&lt;/span&gt;
&lt;span class="nb"&gt;set&lt;/span&gt; &lt;span class="nt"&gt;-g&lt;/span&gt; pane-border-style &lt;span class="s2"&gt;"bg=#000000,fg=white"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Remember that &lt;code&gt;black&lt;/code&gt; and &lt;code&gt;#000000&lt;/code&gt; are not the same thing when configuring your terminal, as the color names are defined by your color scheme.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;While tmux isn't as usable/powerful out of the box as the integrated tmux in Iterm or Warp, with some simple configuration, it can be significantly improved. The manual page for tmux is the go to source for looking up commands and events and will save a lot of time googling.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Tools and Sources&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Screen recorder: &lt;a href="https://cleanshot.com/" rel="noopener noreferrer"&gt;CleanShotX&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Keystroke recorder: &lt;a href="https://github.com/keycastr/keycastr" rel="noopener noreferrer"&gt;KeyCastr&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Terminal Emulator: &lt;a href="https://github.com/alacritty/alacritty" rel="noopener noreferrer"&gt;Alacritty&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Other configuration: &lt;a href="https://github.com/CuddlyBunion341/dotfiles" rel="noopener noreferrer"&gt;My Dotfiles&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>cli</category>
      <category>beginners</category>
      <category>productivity</category>
      <category>opensource</category>
    </item>
    <item>
      <title>Much Compliance and Config</title>
      <dc:creator>Josua Schmid</dc:creator>
      <pubDate>Thu, 21 Mar 2024 10:05:54 +0000</pubDate>
      <link>https://forem.com/renuo/much-compliance-and-config-2kda</link>
      <guid>https://forem.com/renuo/much-compliance-and-config-2kda</guid>
      <description>&lt;p&gt;Dear developers&lt;/p&gt;

&lt;p&gt;Please refrain from creating GitHub repositories like this one:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffh6bgg6eq4utb143bc3b.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffh6bgg6eq4utb143bc3b.png" alt="GitHub directory tree" width="552" height="1622"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The main thing you want to show me is your code. What you need is the following:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;README.md&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;LICENSE.md&lt;/code&gt; (maybe, mention it in &lt;code&gt;README.md&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;main.js&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;package.json&lt;/code&gt;, &lt;code&gt;package-lock.json&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;.env.sample&lt;/code&gt; (maybe)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These are 6 files. I don't want to juggle 16 files in my brain to find what I'm looking for in the repo root directory. Please use folders to hide what one doesn't need to see at a first glance.&lt;/p&gt;

&lt;p&gt;If you want another example of how all this configuration madness spoils my greppability of code bases, have a look at what JavaScript did to Rails in my &lt;a href="https://discuss.rubyonrails.org/t/since-when-are-half-the-files-in-the-rails-root-folder-for-javascript/75019"&gt;May of WTF post&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>github</category>
      <category>javascript</category>
    </item>
    <item>
      <title>Extending Hotwire beyond Ruby on Rails</title>
      <dc:creator>Dani Bengl</dc:creator>
      <pubDate>Fri, 08 Mar 2024 19:30:00 +0000</pubDate>
      <link>https://forem.com/renuo/extending-hotwire-beyond-ruby-on-rails-k7c</link>
      <guid>https://forem.com/renuo/extending-hotwire-beyond-ruby-on-rails-k7c</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F07t16uvg252gn2vhn6ox.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F07t16uvg252gn2vhn6ox.gif" alt=" " width="600" height="375"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This blog article is inspired by a &lt;a href="https://www.writesoftwarewell.com/understanding-hotwire-turbo-streams/" rel="noopener noreferrer"&gt;Hotwire Turbo streams tutorial for Sinatra&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;When you search for Hotwire tutorials on Google, you’ll find that most of the results are related to Ruby on Rails. Even the Hotwire guides predominantly use Ruby on Rails as an example for implementing Turbo Streams.&lt;/p&gt;

&lt;p&gt;This does not really come as a surprise, as the framework has been created by the same company behind the Ruby on Rails framework.&lt;/p&gt;

&lt;p&gt;However, it’s important to recognize that Hotwire is not exclusively a Rails framework. In this blog article, I aim to convince you that Hotwire can be used beyond the Rails context, especially Turbo, its core feature.&lt;/p&gt;

&lt;h2&gt;
  
  
  Goals
&lt;/h2&gt;

&lt;p&gt;In this blog article, I will explain:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Setting up a BunJS Application
&lt;/li&gt;
&lt;li&gt;Implementing client and server-side Web Sockets
&lt;/li&gt;
&lt;li&gt;Using turbo streams to update the UI
&lt;/li&gt;
&lt;li&gt;Creating a stimulus controller attached to the DOM&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Step 1: Set up a BunJS Application
&lt;/h2&gt;

&lt;p&gt;Initially, I considered using Java Spring for this blog, but I encountered challenges with Web Sockets. Instead, I opted for a much simpler TypeScript application.&lt;/p&gt;

&lt;p&gt;Let’s start by initializing a new BunJS application:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;mkdir &lt;/span&gt;bunjs-turbo-demo
&lt;span class="nb"&gt;cd &lt;/span&gt;bunjs-turbo-demo
bun init
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, let’s run the application:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;bun run index.ts
Hello via Bun!
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 2: Implement a Simple Web Socket / HTTP Server with BunJS
&lt;/h2&gt;

&lt;p&gt;Web Sockets serve as the backbone for real-time communication in our project. In this step, we’ll create a basic Web socket HTTP server using Bun. Web Sockets are essential for enabling bidirectional communication between clients (such as web browsers) and the server.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Multiple Publisher - Multiple Subscriber Pattern
&lt;/h3&gt;

&lt;p&gt;Our approach involves implementing the &lt;strong&gt;multiple publisher - multiple subscriber pattern&lt;/strong&gt;. Here’s how it works:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Client Interaction&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Clients (users' web browsers) initiate actions, such as sending chat messages or requesting updates.
&lt;/li&gt;
&lt;li&gt;These interactions trigger Web Socket connections to the server.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Server Processing&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The server receives messages from multiple clients.
&lt;/li&gt;
&lt;li&gt;It processes these messages and prepares appropriate responses.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Broadcasting Updates&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;When a client sends a message (e.g., a new chat message), the server broadcasts it to all connected clients.
&lt;/li&gt;
&lt;li&gt;This ensures that everyone receives the latest updates in real time.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Seamless Communication&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Web Sockets allow seamless, low-latency communication.
&lt;/li&gt;
&lt;li&gt;Clients can instantly receive updates without the need for manual page refreshes.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Here is a simple sequence diagram showing the core idea of this project:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqrxh7bkqbr2r0h1tyr0i.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqrxh7bkqbr2r0h1tyr0i.png" alt="Sequence diagram of the client-server interactions" width="614" height="589"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Implement the server
&lt;/h3&gt;

&lt;p&gt;For brevity, I’ve omitted the code for view helpers such as &lt;code&gt;layoutHTML&lt;/code&gt; and &lt;code&gt;chatRoomHTML&lt;/code&gt;. These helpers handle rendering HTML components and chat room layouts. While important, their details won’t significantly impact the core concepts discussed in this blog.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;topic&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;chatroom&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nx"&gt;Bun&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;serve&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;port&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;8080&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;server&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;URL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;pathname&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;layoutHTML&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Chatroom&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;chatRoomHTML&lt;/span&gt;&lt;span class="p"&gt;()),&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Content-Type&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;text/html&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;

    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;pathname&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/subscribe&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;server&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;upgrade&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Couldn't upgrade to a WebSocket connection&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;404!&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;websocket&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ws&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Websocket opened&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="nx"&gt;ws&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;subscribe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;topic&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="nx"&gt;ws&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;publishText&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;topic&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;messageHTML&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Someone joined the chat&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="nf"&gt;message&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ws&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Websocket received: &lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="nx"&gt;ws&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;publishText&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;topic&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;messageHTML&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Anonymous: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="nf"&gt;close&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ws&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Websocket closed&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="nx"&gt;ws&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;publishText&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;topic&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;messageHTML&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Someone left the chat&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;publishToSelf&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Implement the client
&lt;/h4&gt;

&lt;p&gt;The application is incomplete without the client, which connects to the Web Socket server. Following the Web Socket server connection documentation, connecting to the backend is straightforward:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;WebSocket&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;ws://localhost:8080/subscribe&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;form&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;chat-form&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;chatFeed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;chat-feed&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;message&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;chatFeed&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;innerHTML&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="nx"&gt;form&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;submit&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;preventDefault&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;formData&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;FormData&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;form&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;message&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;formData&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;message&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="nx"&gt;form&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;reset&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Don't forget to add the route for the JavaScript file to the backend and include it inside your view using a module script tag. Also, make sure to implement the backend response.&lt;/p&gt;

&lt;h5&gt;
  
  
  The problem
&lt;/h5&gt;

&lt;p&gt;As you can see here, every change in UI needs to be manually implemented. At the moment, we are listening to a single event and updating one single element. When the application grows in complexity, the JavaScript code grows, too. What if I told you that we can "almost" completely eliminate the client code by introducing Turbo streams?&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 3: Implement Turbo streams
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://turbo.hotwired.dev/" rel="noopener noreferrer"&gt;Turbo&lt;/a&gt; is a vital part of the Hotwire Framework. It enables you to dramatically reduce the amount of custom JavaScript you need to write.&lt;/p&gt;

&lt;p&gt;The most relevant feature for this application are the turbo streams. They enable us to deliver page changes in the form of HTML over the wire.&lt;/p&gt;

&lt;p&gt;Importing Turbo is as simple as including this snippet of code inside our layout file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;script &lt;/span&gt;&lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"module"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;hotwiredTurbo&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://cdn.jsdelivr.net/npm/@hotwired/turbo@8.0.3/+esm&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In order to use Web Sockets for Turbo streams in the frontend, we can use the following snippet from the &lt;a href="https://turbo.hotwired.dev/handbook/streams" rel="noopener noreferrer"&gt;stream documentation&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;turbo-stream-source&lt;/span&gt; &lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"ws://localhost:8080/subscribe"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In order to update the UI when a message is sent, we broadcast the following HTML through Web Sockets:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;turbo-stream&lt;/span&gt; &lt;span class="na"&gt;action=&lt;/span&gt;&lt;span class="s"&gt;"append"&lt;/span&gt; &lt;span class="na"&gt;target=&lt;/span&gt;&lt;span class="s"&gt;"chat-feed"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;template&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;p&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"notice"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
           Anonymous: Hello World!
        &lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/template&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/turbo-stream&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This method of HTML updates stands out for its transparency and simplicity. Firstly, we select an element with the &lt;code&gt;#chat-feed&lt;/code&gt; selector and then &lt;code&gt;append&lt;/code&gt; to it the contents of the broadcasted template. In this case, a paragraph containing the user message. This also eliminates &lt;em&gt;almost&lt;/em&gt; all the client-side JavaScript needed for page update.&lt;/p&gt;

&lt;h4&gt;
  
  
  Step 4: Implement Form Controller
&lt;/h4&gt;

&lt;p&gt;Before introducing turbo, we added a simple event listener to reset the Form after the data has been sent to the server. We now need to bring the functionality back, but without reusing the old code. We could use a turbo-stream to reset the form or even a turbo-frame, but rather than using that, I decided to use another library of the Hotwire framework, namely Stimulus:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Stimulus is a JavaScript framework with modest ambitions. It doesn’t seek to take over your entire front-end—in fact, it’s not concerned with rendering HTML at all. Instead, it’s designed to augment your HTML with just enough behavior to make it shine. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://stimulus.hotwired.dev/" rel="noopener noreferrer"&gt;https://stimulus.hotwired.dev/&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This is a simple code snippet for the Form Stimulus controller:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Application&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Controller&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://cdn.jsdelivr.net/npm/stimulus@3.2.2/+esm&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;FormController&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;Controller&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;clear&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;element&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;reset&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;application&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Application&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;start&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="nx"&gt;application&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;register&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;form&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;FormController&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is what the form HTML looks like, with data attributes used to attach the controller to the DOM and hook the events up to the corresponding controller methods:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;  &lt;span class="nt"&gt;&amp;lt;form&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"chat-form"&lt;/span&gt; &lt;span class="na"&gt;action=&lt;/span&gt;&lt;span class="s"&gt;"/submit"&lt;/span&gt; &lt;span class="na"&gt;method=&lt;/span&gt;&lt;span class="s"&gt;"post"&lt;/span&gt; &lt;span class="na"&gt;data-controller=&lt;/span&gt;&lt;span class="s"&gt;"form"&lt;/span&gt; &lt;span class="na"&gt;data-action=&lt;/span&gt;&lt;span class="s"&gt;"turbo:submit-end-&amp;gt;form#clear"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;label&lt;/span&gt; &lt;span class="na"&gt;for=&lt;/span&gt;&lt;span class="s"&gt;"message-input"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Message:&lt;span class="nt"&gt;&amp;lt;/label&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"message"&lt;/span&gt; &lt;span class="na"&gt;data-form-target=&lt;/span&gt;&lt;span class="s"&gt;"input"&lt;/span&gt; &lt;span class="na"&gt;required&lt;/span&gt; &lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"hidden"&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"clientId"&lt;/span&gt; &lt;span class="na"&gt;value=&lt;/span&gt;&lt;span class="s"&gt;"${clientId}"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"submit"&lt;/span&gt; &lt;span class="na"&gt;value=&lt;/span&gt;&lt;span class="s"&gt;"Send"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/form&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The event that works best for form submission in that case is &lt;a href="https://turbo.hotwired.dev/reference/events#turbo%3Asubmit-end" rel="noopener noreferrer"&gt;turbo:submit-end&lt;/a&gt;. Following the documentation of &lt;a href="https://stimulus.hotwired.dev/reference/actions#descriptors" rel="noopener noreferrer"&gt;Stimulus descriptors,&lt;/a&gt; we can call the &lt;code&gt;#clear()&lt;/code&gt; method after the form submission event. We are not using the &lt;code&gt;submit&lt;/code&gt; event because this would clear the form prematurely.&lt;/p&gt;

&lt;h4&gt;
  
  
  Conclusion
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Hotwire is a JavaScript framework that helps us make applications more interactive while keeping the JavaScript code to a minimum. While the framework has been created by the authors of Ruby on Rails, the framework itself is backend agnostic.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Turbo streams enable us to update client user interfaces asynchronously without the need for any (in some cases, just very little) frontend code.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Stimulus enables us to add simple JavaScript behavior to our HTML with the use of Stimulus Controllers and data-attributes.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Where can I find the source?
&lt;/h4&gt;

&lt;p&gt;You can find the complete chatting application with additional features such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Client identification via Query Parameters&lt;/li&gt;
&lt;li&gt;Random username generation&lt;/li&gt;
&lt;li&gt;Real-time user list&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The GitHub Repository for this project can be found here: &lt;a href="https://github.com/CuddlyBunion341/bunjs-turbo-demo" rel="noopener noreferrer"&gt;https://github.com/CuddlyBunion341/bunjs-turbo-demo&lt;/a&gt;&lt;/p&gt;

</description>
      <category>rails</category>
      <category>hotwire</category>
      <category>bunjs</category>
      <category>webdev</category>
    </item>
    <item>
      <title>standardrb, but more</title>
      <dc:creator>Josua Schmid</dc:creator>
      <pubDate>Thu, 08 Feb 2024 18:46:49 +0000</pubDate>
      <link>https://forem.com/renuo/standardrb-but-more-286m</link>
      <guid>https://forem.com/renuo/standardrb-but-more-286m</guid>
      <description>&lt;p&gt;Often project teams suffer from either too many or too strict &lt;a href="https://github.com/rubocop/rubocop"&gt;Rubocop&lt;/a&gt; rules (&lt;a href="https://docs.rubocop.org/rubocop/versioning.html#enablingdisabling-pending-cops-in-bulk"&gt;&lt;code&gt;NewCops&lt;/code&gt;&lt;/a&gt;) or from outdated ones because they locked the rules or Rubocop versions. &lt;a href="https://github.com/standardrb/standard"&gt;standardrb&lt;/a&gt; provides a good middle ground for people who care but also don't care fanatically. Its default settings are optimized for long-term maintainability and no-bikeshed. But we at &lt;a href="https://www.renuo.ch"&gt;Renuo&lt;/a&gt; are still missing a lot of rules, especially from Rubocop plugins like RSpec or performance cops. Since standardrb relies on Rubocop under the hood, there's an easy way for you to use a default but extended configuration. Simply opt-in to the rules you want while relying on standardrb for the rest. This inverts the team discussion time needed for your style guide from discussing every rule to discussing opt-in-exceptions.&lt;/p&gt;

&lt;p&gt;If you want to have a look at our approach: we created a gem called &lt;a href="https://github.com/renuo/renuocop/"&gt;renuocop&lt;/a&gt;. Our &lt;a href="https://github.com/renuo/renuocop/blob/main/config/base.yml"&gt;config file&lt;/a&gt; documents what we think is not strict enough in standardrb.&lt;/p&gt;

</description>
      <category>ruby</category>
      <category>rubocop</category>
      <category>linting</category>
    </item>
    <item>
      <title>The Full-Stack development experience</title>
      <dc:creator>Alessandro Rodi</dc:creator>
      <pubDate>Thu, 12 Oct 2023 12:48:13 +0000</pubDate>
      <link>https://forem.com/renuo/the-full-stack-development-experience-297n</link>
      <guid>https://forem.com/renuo/the-full-stack-development-experience-297n</guid>
      <description>&lt;p&gt;&lt;a href="https://rubyonrails.org/world" rel="noopener noreferrer"&gt;Rails World 2023&lt;/a&gt; closes many doors that have been opened recently and marks a turning point for Ruby On Rails. That is why this year's motto can be summarized as "&lt;a href="https://world.hey.com/dhh/the-one-person-framework-711e6318" rel="noopener noreferrer"&gt;The One Person Framework&lt;/a&gt;".&lt;/p&gt;

&lt;p&gt;The Web has certainly been a complex environment in recent years. This complexity was necessary to push browsers to the limit and use the most advanced features.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;But in 2023, this is no longer necessary!&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Assets distribution
&lt;/h2&gt;

&lt;p&gt;Let's think about CSS. For years, we used SASS to take advantage of variables and nested rules. Well, that is no longer necessary. These are both features made available directly by CSS now. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffne6srd4azvap3dne1pg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffne6srd4azvap3dne1pg.png" alt="CSS variables availability on browsers" width="800" height="436"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgh9l3h7x5cthamjgizr9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgh9l3h7x5cthamjgizr9.png" alt="CSS nesting availability on browser" width="800" height="516"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;So we can say goodbye to compiling SASS into CSS and enjoy the pleasure of writing directly in CSS. This makes us much faster in development and requires one less knowledge (SASS) for each Full-stack developer.&lt;/p&gt;

&lt;p&gt;A full-Stack developer is precisely what our profession provided for before the advent of powerful javascript frameworks for creating truly fluid web interfaces! However, &lt;strong&gt;it is time to simplify&lt;/strong&gt;! It is time to stop being Partial-stack developers and focus on productivity again.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Let's talk about JavaScript&lt;/strong&gt;. The language has matured so much in the last few years, and it is (almost) a joy to write in JavaScript. The consolidation of import maps for the distribution of JavaScript and CSS assets finally allows us to remove another element from our stack: bundling.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fksjbq7sec2v2wobuztyo.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fksjbq7sec2v2wobuztyo.png" alt="Import maps availability on browsers" width="800" height="469"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In 2023, thanks to importmaps, providing the browser with dozens of separate JavaScript files instead of a single file makes no difference! No more bundling! It is not only unnecessary but even &lt;strong&gt;harmful&lt;/strong&gt;! If we modify just one of our files, we can serve the browser a new version of that one file instead of the entire bundle. &lt;strong&gt;Long live simplicity!&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;An additional element that we can finally remove from our stack is the minification of JavaScript and CSS files. Thanks to algorithms like &lt;a href="https://github.com/google/brotli" rel="noopener noreferrer"&gt;brotli&lt;/a&gt; (with a very Swiss flavour) we no longer need to minify and compress our files before distributing them. Cloudflare, Nginx, or Apache will take care of everything for us. &lt;/p&gt;

&lt;p&gt;And there goes our stack thinning, and with that, our Full-stack developers can focus again on productivity instead of technicalities about how to distribute their work to Browsers.&lt;/p&gt;

&lt;p&gt;Ruby On Rails, thanks to &lt;a href="https://github.com/rails/propshaft" rel="noopener noreferrer"&gt;propshaft&lt;/a&gt;, closes a chapter. Welcome to 2023, where deploying JavaScript and CSS is a breeze. Welcome to the &lt;a href="https://world.hey.com/dhh/you-can-t-get-faster-than-no-build-7a44131c" rel="noopener noreferrer"&gt;no-build&lt;/a&gt; era.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8h0sfdfocgub8bwtjucv.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8h0sfdfocgub8bwtjucv.png" alt="DHH showing no-build speed comparison graph" width="800" height="600"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Hard drives are cool
&lt;/h2&gt;

&lt;p&gt;Another element that has radically changed in recent years is the speed of drives. With the advent of NVMe disks, the speed is ten times what we had ten years ago. That is why our stack can be further streamlined. To start with a new web application, we can use the disk for databases (SQLite), caches, WebSockets, and background jobs.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqsjg46yvf13tk3gnmicu.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqsjg46yvf13tk3gnmicu.png" alt="Speed of Hard Drives over the years from chatGPT so it must be true" width="800" height="290"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Great! It means you can have a web app with all the latest and most complex features...&lt;strong&gt;and zero dependencies&lt;/strong&gt;. &lt;/p&gt;

&lt;p&gt;When your application is used by millions of users worldwide, you can think about further optimizations, a better-performing database, or gain performance with more efficient caching systems. Until then, productivity is the key word. No frills and no dependencies. You can eliminate SQL and NoSQL databases from your stack and focus on development.&lt;/p&gt;

&lt;h2&gt;
  
  
  HTML-First architecture
&lt;/h2&gt;

&lt;p&gt;Let's get to the last chapter. The most controversial one, the one most discussed. The &lt;a href="https://hotwired.dev/" rel="noopener noreferrer"&gt;Hotwire&lt;/a&gt; architecture: HTML Over The Wire. Ruby On Rails started down this path in 2015 with Turbolinks, but it was not until 2021, with the arrival of Turbo, that years of work was consolidated.&lt;/p&gt;

&lt;p&gt;Today, we can confidently say that HTML Over The Wire is the right technology for maximizing productivity. When we maximize productivity, this is always at the expense of performance. With this technology, we will never have the performance of a JavaScript-First architecture. But what is it all about?&lt;/p&gt;

&lt;p&gt;When discussing &lt;strong&gt;JavaScript-First Architecture&lt;/strong&gt;, I have in mind applications where the backend and frontend, properly separated, communicate via JSON. The frontend uses JavaScript, and the backend also, possibly. The server and client communicate via JSON. The server is "dumb", it provides data to the client, which, thanks to the Frontend Framework from screaming, "builds" the entire web interface.&lt;/p&gt;

&lt;p&gt;The client then has the entire responsibility of building web pages.&lt;/p&gt;

&lt;p&gt;A truly efficient architecture that takes full advantage of all the capacity of modern devices, which have no difficulty in taking on the "onerous task." An architecture that maximizes performance...at the expense of productivity, of course. Not coincidentally, the first frameworks in this universe, Angular and React, were deployed by Google and Facebook, which have their main focus on performance. And we could do with more!&lt;/p&gt;

&lt;p&gt;GitHub itself, which is based on Ruby On Rails, now adopts React &lt;a href="https://news.ycombinator.com/item?id=33576722" rel="noopener noreferrer"&gt;in many parts of their frontend&lt;/a&gt; to be able to offer the best experience to its customers.&lt;/p&gt;

&lt;p&gt;When I discuss &lt;strong&gt;HTML-First Architecture&lt;/strong&gt;, however, I have in mind the Web of twenty years ago. The server, "intelligent", no longer solely transmits data to clients but directly HTML pages.&lt;/p&gt;

&lt;p&gt;The client/server communication protocol is based on HTML, and the client goes back to being "stupid."&lt;/p&gt;

&lt;p&gt;What changes today is that the client and server can communicate via HTML but update individual page elements instead of simply navigating to the next one.&lt;/p&gt;

&lt;p&gt;This concept of partial updates allows us to rethink very complex pages into smaller elements (just as React taught us!) and update only the parts we care about.&lt;/p&gt;

&lt;p&gt;The implementation offered by Ruby On Rails is called Turbo, and it allows for SPA-like user interfaces and experiences without having to learn a new framework.&lt;/p&gt;


    
    Your browser does not support the video tag.


&lt;p&gt;This element allows a Full-Stack developer to build applications with a modern flavour within a &lt;a href="https://m.signalvnoise.com/the-majestic-monolith/" rel="noopener noreferrer"&gt;majestic monolith&lt;/a&gt;, while continuing to work only with HTML at all times. No more client/server separation and complex synchronization to achieve modern, fluid interfaces. Just one application, the stack of which can fit entirely in one person's head.&lt;/p&gt;

&lt;p&gt;When they tell you about Multiple Page Apps vs. Single Page Apps, you will know &lt;strong&gt;this comparison no longer makes sense&lt;/strong&gt;. There are JavaScript-First Frameworks that can safely create MPA's via Server-side rendering, and there are HTML-First Frameworks which can provide the same experience as an SPA.&lt;/p&gt;

&lt;p&gt;HTML-First vs Javacript-First architecture is the distinction that needs to be made today.&lt;/p&gt;

&lt;h2&gt;
  
  
  App Store, Android Store
&lt;/h2&gt;

&lt;p&gt;Having come to this point, to think that one person can manage the entire stack for Web application development &lt;strong&gt;is not only likely, it is reality!&lt;/strong&gt; One person can develop a feature from start to finish: from database design, background processes, business logic, to frontend design. The Web stack was complex, but we finally simplified it!&lt;/p&gt;

&lt;p&gt;Yes, a Full-Stack developer in 2023 has the tools to build a feature from start to finish, without waiting for his colleague to "expose" the API.&lt;/p&gt;

&lt;p&gt;But there is a world that has always been quite obscure to us web developers: native iOS and Android app development. Native apps do not allow us all the simplifications we discussed above. Native app development follows other rules, and here I throw up my hands and give up.&lt;/p&gt;

&lt;p&gt;But Ruby On Rails, since a couple of weeks ago, has released the missing piece of the stack for creating hybrid applications: &lt;a href="https://strada.hotwired.dev/" rel="noopener noreferrer"&gt;Strada&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Thanks to Turbo and Strada, even our Full-Stack developer can make something that will be approved within the App Store. A hybrid app allows you to start from your existing Web application and release it as a native App once it is properly "wrapped" in a WebView. Turbo-ios and turbo-android, together with Strada, make it very easy to add all the native elements needed to optimize the user experience.&lt;/p&gt;

&lt;p&gt;To date, dozens of apps are already made with this technology in the App Store, and I challenge anyone to distinguish them from a native app. Web View indeed means Browser, and Browsers are really powerful today! With good design and good animations, they are indistinguishable from fully native apps. And the beauty is that it is still your Web app. You don't need to expose APIs, and rebuild your iOS and Android interfaces. Just reuse your existing ones! &lt;strong&gt;This is an incredible boost to productivity!&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What's next?
&lt;/h2&gt;

&lt;p&gt;At the beginning of the article, I wrote that this is a turning point, but what's next? The next few years will be focused on this simplification of the stack and on improving these new tools at our disposal. Improving productivity is also done through developer tools. We need time to improve the whole ecosystem around these technologies. There are many new things, and it takes time to absorb and digest them.&lt;/p&gt;

&lt;p&gt;Ruby On Rails continues to point the way, often independently and controversially, and we, after our recent membership in the Rails Foundation, are intent on following it.&lt;/p&gt;

&lt;p&gt;Check the &lt;a href="https://www.youtube.com/watch?v=iqXjGiQ_D-A" rel="noopener noreferrer"&gt;Keynote of Rails World 2023&lt;/a&gt;, to hear from the creator of Ruby On Rails, the concepts I took inspiration from, to write this blog post.&lt;/p&gt;

&lt;p&gt;&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/iqXjGiQ_D-A"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

</description>
      <category>rails</category>
      <category>ruby</category>
      <category>webdev</category>
      <category>javascript</category>
    </item>
  </channel>
</rss>
