<?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>DEV Community: mimi chat</title>
    <description>The latest articles on DEV Community by mimi chat (@mimi_chat_b3bf7a39a962a82).</description>
    <link>https://dev.to/mimi_chat_b3bf7a39a962a82</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.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4205435%2F98d46a60-aa6b-44b8-afe7-a98ea95ebe64.jpg</url>
      <title>DEV Community: mimi chat</title>
      <link>https://dev.to/mimi_chat_b3bf7a39a962a82</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/mimi_chat_b3bf7a39a962a82"/>
    <language>en</language>
    <item>
      <title>Running a 21k-line PHP game at 60 fps in the browser with WebAssembly</title>
      <dc:creator>mimi chat</dc:creator>
      <pubDate>Sat, 24 Oct 2026 20:21:59 +0000</pubDate>
      <link>https://dev.to/mimi_chat_b3bf7a39a962a82/running-a-21k-line-php-game-at-60-fps-in-the-browser-with-webassembly-2cd3</link>
      <guid>https://dev.to/mimi_chat_b3bf7a39a962a82/running-a-21k-line-php-game-at-60-fps-in-the-browser-with-webassembly-2cd3</guid>
      <description>&lt;p&gt;I wanted to see how far PHP in WebAssembly could go. So I wrote a fan remake of &lt;strong&gt;Sonic the Hedgehog (Master System)&lt;/strong&gt; entirely in PHP, about 21k lines, and made it run both natively and in the browser.&lt;/p&gt;

&lt;p&gt;👉 &lt;strong&gt;Play it, no install: &lt;a href="https://sonic.mimichat.fr" rel="noopener noreferrer"&gt;https://sonic.mimichat.fr&lt;/a&gt;&lt;/strong&gt; (desktop and mobile)&lt;/p&gt;

&lt;h2&gt;
  
  
  How PHP runs in a browser
&lt;/h2&gt;

&lt;p&gt;PHP 8.3 is compiled to WebAssembly with Emscripten, together with an SDL2 extension for PHP. A small C glue starts PHP (&lt;code&gt;php_embed_init&lt;/code&gt;), runs the game, then plugs into the browser loop: on every &lt;code&gt;requestAnimationFrame&lt;/code&gt;, one tick of the game's ReactPHP event loop.&lt;/p&gt;

&lt;p&gt;The tricky part: ReactPHP's &lt;code&gt;Loop::run()&lt;/code&gt; is blocking, and a browser can't allow that. So the page queues a &lt;code&gt;futureTick&lt;/code&gt; that stops the loop right after its first turn, and the C glue then advances it one tick at a time. PHP is never shut down between frames (&lt;code&gt;php_embed_shutdown()&lt;/code&gt; is never called): every object and SDL resource stays in memory, the whole game state lives across frames.&lt;/p&gt;

&lt;p&gt;SDL2's Emscripten port renders to WebGL and plays sound through Web Audio. Saves go to IndexedDB (IDBFS).&lt;/p&gt;

&lt;p&gt;One lesson learned: use &lt;strong&gt;one&lt;/strong&gt; browser loop only. A second &lt;code&gt;setTimeout(0)&lt;/code&gt; loop ran the game between frames too, saturating the main thread so that input and audio came last.&lt;/p&gt;

&lt;h2&gt;
  
  
  Online multiplayer, up to 6 players
&lt;/h2&gt;

&lt;p&gt;Race, capture the flag, rings, deathmatch. Browsers talk directly over WebRTC; a relay written in PHP handles rooms and signaling, with coturn for difficult networks. A player on the native desktop version can join the same room through the relay. The network layer is its own library: php-wasm-network.&lt;/p&gt;

&lt;h2&gt;
  
  
  The website
&lt;/h2&gt;

&lt;p&gt;Symfony 8.1 on FrankenPHP: live rooms, stats, faithful replays of every game, and editors for levels, tiles (drawing and collisions), sprites, sounds and music. A published level is playable in the game right away. Visitors appear live as little Sonics running across the page, through Mercure subscription events.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Play: &lt;a href="https://sonic.mimichat.fr" rel="noopener noreferrer"&gt;https://sonic.mimichat.fr&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Build a level in the editor and play it in-game&lt;/li&gt;
&lt;li&gt;Create a private room and send the link to a friend&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Code: &lt;a href="https://github.com/davidnurdin/sonicGamePhp" rel="noopener noreferrer"&gt;sonicGamePhp&lt;/a&gt; (game), &lt;a href="https://github.com/davidnurdin/php-wasm-sdl" rel="noopener noreferrer"&gt;php-wasm-sdl&lt;/a&gt; (PHP + SDL in WASM), &lt;a href="https://github.com/davidnurdin/php-wasm-network" rel="noopener noreferrer"&gt;php-wasm-network&lt;/a&gt; (networking).&lt;/p&gt;

&lt;p&gt;It's a non-commercial fan project, Sonic belongs to SEGA. Questions about PHP in WASM are welcome in the comments!&lt;/p&gt;

</description>
      <category>php</category>
      <category>webassembly</category>
      <category>gamedev</category>
      <category>showdev</category>
    </item>
  </channel>
</rss>
