Live data from Hacker News

Ported my C game to WASM, here's every bug that I hit

ernesernesto.github.io

51–60 of 116 posts

Re: Ported my C game to WASM, here's every bug that I hit

#51
post #24
post #21

Earlier quoted context omitted.

You can call JS in which you can manipulate the DOM. Of course architecturally (also regarding your file access) it's better to use the wasm for logic as much as possible where the web (HTML/JS) provides the UI and IO, data flows into wasm for work and results flow back to the web. This also has the benefit that you can keep your original C/C++ source code much more platform agnostic which helps reusability and testi…

> You can call JS in which you can manipulate the DOM. Well sure. But for me, the promise of WASM was to make the browser "just another platform". Now it's "this special platform where you have to access some of the most important functionality through FFI interop with a very high-level, very opinionated language". > Of course architecturally (also regarding your file access) it's better to use the wasm for logic as…

Hmm well I guess I don't quite get what counts as "just another platform." Surely every platform is going to have the native APIs that you need to abstract over. Why is WASM different?

Is it just a matter of WASM being too new to have full featured wrappers and APIs for your language of choice?

Re: Ported my C game to WASM, here's every bug that I hit

#52
FTA: I was serializing asset structs directly to disk (pak file) that had raw pointers in them

I’m surprised that that works in WASM. Wouldn’t a tiny change in your memory usage (say if you toggle your “log startup progress” flag) load data at a different address?

Re: Ported my C game to WASM, here's every bug that I hit

#53
I've been porting Micropolis (SimCity Classic) to WASM / WebGPU / Svelte 5. Emscripten + Embind compile the C++ engine and glue it to TypeScript/Svelte/Runes/Reactivity; TypeScript owns UI, rendering, and callback handlers.

I agree with the article's main lessons: wasm32 pointer size, don't serialize structs with pointers, debug native 32-bit when you can, WebGL/WebGPU is stricter than desktop GL, Emscripten export flags still bite. I hit some of the same categories; the parts that were actually tricky for Micropolis are below.

Svelte 5 runes ($state, $derived, etc.) work in plain .ts modules, not just .svelte templates. That matters because the WASM bridge is a reactive module the HUD, command bus, and Vitest all import -- not a component-only trick. The file has to be MicropolisReactive.svelte.ts so runes compile under the same Vite/SvelteKit pipeline as the app; plain .ts breaks in Node with "$state is not defined".

Embind API surface -- what to expose and what to leave out:

https://github.com/SimHacker/MicropolisCore/blob/main/packag...

  // This file uses emscripten's embind to bind C++ classes,
  // C structures, functions, enums, and contents into JavaScript,
  // so you can even subclass C++ classes in JavaScript,
  // for implementing plugins and user interfaces.
  //
  // Wrapping the entire Micropolis class from the Micropolis (open-source
  // version of SimCity) code into Emscripten for JavaScript access is a
  // large and complex task, mainly due to the size and complexity of the
  // class. The class encompasses almost every aspect of the simulation,
  // including map generation, simulation logic, user interface
  // interactions, and more.
The comments in that file go on to describe the strategy for wrapping: Core Simulation Logic, Memory and Performance Considerations, Direct Memory Access, User Interface and Rendering, Callbacks and Interactivity, and Optimizations.

The engine callback virtual interface bridged C++ to JS via JSCallback:

https://github.com/SimHacker/MicropolisCore/blob/main/packag...

In the old NeWS/Hyperlook, TCL/Tk/X11, SWIG/Python/PyGTK, and SWIG/Python/TurboGears/AMF/Flash versions, this callback interface used to be a stringly typed general purpose event callback interface, which I tightened up into a strict C++ interface and corresponding typescript interface, so embind could help me integrate it safely and cleanly with TypeScript and Svelte Runes.

TypeScript handlers that update rune-backed state (sendMessage, didTool, budget hooks, etc.):

https://github.com/SimHacker/MicropolisCore/blob/main/apps/m...

Simulator attach/detach, singleton engine load, wiring JSCallback into Micropolis:

https://github.com/SimHacker/MicropolisCore/blob/main/apps/m...

The pattern: C++ fires callbacks with enough context for the UI; TS updates $state; components read micropolisReactive (peek / poke / memory / getSnapshot) instead of calling Embind or touching HEAP* directly. That is where the rubber hits the road for interactivity.

Heap access is its own footgun. Emscripten may expose Module.wasmMemory, HEAPU16, or neither until init; some getters throw if you read too early. Centralized helper:

https://github.com/SimHacker/MicropolisCore/blob/main/apps/m...

Bridge design, Vitest against real WASM, teardown order with Embind lifetimes:

https://github.com/SimHacker/MicropolisCore/blob/main/docume...

Map rendering: WebGPU tile renderer with canvas fallback (legacy WebGL frozen, now reimplementing in WebGPU). The renderer reads 16 bit flags + tile indices from direct simulator memory views into WASM linear memory (mapData / mopData), not per-frame Embind copies.

https://github.com/SimHacker/MicropolisCore/blob/main/packag...

https://github.com/SimHacker/MicropolisCore/blob/main/docume...

City saves are a defined binary format (.cty), not fwrite of engine structs. Live map data is views into WASM linear memory (mapData / mopData), not embedded native pointers -- same idea as the article's side-table fix, but that is how this codebase is already structured.

Why I find this stack interesting: original SimCity engine lineage, narrow Embind surface on purpose, reactive TS facade so automation and UI share one sim without reviving the old Python/SWIG/pyGTK path. Sprites (trains, choppers, generic orange monsters wrecking chaos and havoc -- definitely not Godzilla [TM], but possibly Trump adjacent) simulate in C++; compositing them in the WebGPU path is still work in progress.

The WebGPU renderer is being built as a general stack with pluggable layers, including Sims content rendering (characters, animations, terrain, objects, walls, floors, ui effects, etc).

Character animation demo:

https://vitamoo.space

VitaMoo code:

https://github.com/SimHacker/MicropolisCore/tree/main/packag...

Unified WebGPU Renderer:

https://github.com/SimHacker/MicropolisCore/blob/main/docume...

Render Core Package:

https://github.com/SimHacker/MicropolisCore/blob/main/docume...

Renderer Plugin Roadmap:

https://github.com/SimHacker/MicropolisCore/blob/main/docume...

Live Micropolis tile renderer and simulator demo (no other ui yet, work in progress):

https://micropolisweb.com

Demo of the simulator, cellular automata, and tile engine to Jerry Martin's music:

https://www.youtube.com/watch?v=319i7slXcbI

Repo:

https://github.com/SimHacker/MicropolisCore

Re: Ported my C game to WASM, here's every bug that I hit

#55
post #27
post #16

Earlier quoted context omitted.

ActiveX, Alchemy, PNaCL,...

JVM, Z-Machine, P-Code.

Woz's own Sweet 16 Dream Machine on the Integer Basic ROMS of the Apple II! ;)

https://en.wikipedia.org/wiki/SWEET16

https://techwithdave.davevw.com/2024/05/running-sweet-16-ste...

Re: Ported my C game to WASM, here's every bug that I hit

#56
post #27

Earlier quoted context omitted.

JVM, Z-Machine, P-Code.

I remember Java applets; when did z-machine and p-code make it to the browser?

Everything made it to the browser through emulation.

UCSD Pascal:

https://archive.org/details/UCSD_Pascal_1.1_1

Wizardry:

https://archive.org/details/WizardryProvingGrounds

Re: Ported my C game to WASM, here's every bug that I hit

#57
post #17
post #13

If you are porting anything from C into WebAssembly, keep in mind that you still inherit C based vulnerabilities. [0] [1] [0] https://soft.vub.ac.be/Publications/2022/vub-tr-soft-22-02.p... [1] https://www.usenix.org/system/files/sec20-lehmann.pdf

No worries, it is sandboxed. /s

Why /s? That does massively reduce the exposure

Re: Ported my C game to WASM, here's every bug that I hit

#58
I just finished a similar project for fun and education.

It was a 20-year-old codebase from my old game in win32 and DirectX 9.

I first ported it to native and also switched to bgfx for rendering. This was the bulk of the work - converting all of the old DirectX fixed function pipeline code to shaders. Luckily all modern shaders can simulate all of the old fixed-function DX pipeline features with little effort. Including the coordinate system. Loading DDS textures didn't present a major challenge either.

Had similar native asset loading as yours - no deserializer. It loaded an entire asset file into a preallocated memory block, used packed structures and converted file offsets to pointers after loading. I had to convert it to 64bit for native first.

The most surprising thing: I had no idea WASM is 32bit until I read your article! Once I ported to 64bit, I then ported to WASM and I didn't even encounter any arch related bugs. In hindsight I guess it's because most of the original code was 32bit and the asset file format is still 32bit format. When I ported to 64bit I used a deserializer, so I guess that's why it all worked out in the end.

For native audio I ended up using SoLoud library, but for emscripten I #ifdef'd it out to use inline JS instead. I figured there is no point in having all that extra audio library code compiling to WASM when modern browsers natively support playing audio, oggvorbis, etc. It worked out ok, but there's still a minor bug where the music doesn't loop perfectly. You can hear a split second gap between end/start. I haven't looked deeply into it yet.

Originally when we wrote the game we had banned ourselves from using C++ Exception handling and RTTI. The decision likely paid off as it makes the generated binary smaller and faster. Although I haven't had time to measure. Supposedly C++ exceptions introduce a much heavier overhead in Emscripten.

You can see the port in action at https://scorchedplanets.com

Re: Ported my C game to WASM, here's every bug that I hit

#59
post #38

Earlier quoted context omitted.

Specified by whom? Not the C standard for sure. It is indeed soecified by individual ABIs, and ABIs don't tend to do anything too weird, but that's another question.

looks like I was wrong, but here is the de-facto standard I was relying on over the years ;-). Not that I've memcpied many structs to file directly btw. http://www.catb.org/esr/structure-packing/

The general struct layout algorithm is that you lay out the first member at the address of the struct (this is guaranteed by C), and then subsequent fields in order (also guaranteed by C). What isn't guaranteed is how fields get their alignment, in particular shenanigans you can do with allocating fields in the padding of their prior field, and bitfields in general are horribly underspecified.

In practice, C doesn't do any padding shenanigans, but C++ does (but only for non-POD structs, and then you discover there's several slightly different definitions that mean basically "POD", so have fun predicting which one is the one that actually matters for your use case).

Re: Ported my C game to WASM, here's every bug that I hit

#60
post #52

FTA: I was serializing asset structs directly to disk (pak file) that had raw pointers in them I’m surprised that that works in WASM. Wouldn’t a tiny change in your memory usage (say if you toggle your “log startup progress” flag) load data at a different address?

Usually you do "pointer-fixup" where you convert them to relative-offsets on write and then back to absolute-offsets on read.
Post reply on HN