Motivation

Wanted to participate in B&W Jam

I was in a bit of a slump. I wanted to make a game for the B&W Jam, but I couldn’t figure out what I wanted to work on. I wanted to make use of Godot’s fantastic UI support and this stylish Ornate Pixel Art theme. I’d also decided to use Godot so I could get experience with working with an engine (instead of writing my own stuff), but my heart just wasn’t in it. I ended up doing nothing for most of the jam.

Really enjoyed WASM4 Jam

Part of my motivation for joining the jam in the first place was how much fun I had implementing Wired for the WASM4 Jam. I was pretty proud of the platforming and rope/wire physics. At some point I plan to return and finish up the game, but I’m often drawn away from projects by passing interests. I’m trying to make a habit of going back to projects from time to time and updating them, so I’ll get back to Wired eventually.

Mystery game, involving reading through letters

Eventually I did come up with an idea for the jam. The concept is a mystery game where you solve the mystery through reading letters and other documents. It would be in a fantasy setting, and the plot involves attempted regicide and a mandate from the ruler to find who is plotting treason. I was inspired by my time playing Return of the Obra Dinn, as well as Papers Please.

Still wasn’t inspired to write in Godot

Unfortunately I still didn’t want to work with Godot. It’s not that it’s bad, I just really like being in control of exactly what’s happening, and I don’t understand enough about Godot’s internals to for that.

Getting to work

At this point, I decided what I really wanted to do was write stuff in Zig. It’s been my favorite programming language for a couple years now due to it’s no-nonsense design. I also decided to target WASM4, since I’d enjoyed working with it so much. In order to make the game, I would need to make some sort of UI library; or at least that’s what I told myself :)

How to back it

I wanted to know what sorts of algorithms and data structures underlied GUI programs, though finding a resource to explain these concepts turned out to be more difficult than I thought it would be. I wanted something like Godot’s layout, but optimized to fit into the memory constriants of WASM4. I also wanted it to fit into a fixed buffer instead of using allocation, since on WASM4 the only allocator I had access to was Zig’s FixedBufferAllocator. Unfortunately, I couldn’t find anybody detailing the exact data structure I wanted, so I decided to just use a bunch of pointers and the FixedBufferAllocator.

Made it very easy to get it started

My initial approach was a doubly linked list that ran in two direction. I’ve recreated the gist of the code I had as an example:

1
2
3
4
5
6
7
const Node = struct {
    next: ?*Node,
    prev: ?*Node,
    child: ?*Node,
    parent: ?*Node,
    // ...
};

Each node pointed to the next node, the previous node, the first child node, and it’s immediate parent. This allowed me to work with the tree by doing recursive calls on nodes. In addition to pointers defining the tree, I also had:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
const Node = struct {
    bounds: @Vector(4, i32),
    layout: Layout,
    self: *anyopaque,
};

const Layout = enum {
    Relative,
    Center,
    Fill,
    Anchor,
    HList,
    VList,
    HDiv,
    VDiv,
};

Bounds stored the screen space coordinates of each edge, layout stored the function to layout the child nodes, and self was an opaque pointer for storing data. There’s a lot more detail I could go into here, but I want to keep this post short since I will be returning to the topic of UI with more about what I’ve learned.

Layout

Two recursive functions were used to layout nodes. The first function was depth first, and found the minimum size of each node. The second function was breadth first, and it calculated the bounding box for each node.

It’s working!

It didn’t take me too long to implement this, and I got items positioning themselves relative to each other pretty quickly. I was pretty impressed with myself!

Problems

However, this was not my final approach. Using this sort of data structure (and in the way that I was using it) ran me into a lot of edge cases.

Each node used a lot of ram

WASM is 32-bit, meaning that each pointer takes of 4 bytes of RAM. Having 4 pointers is 16 bytes of ram minimum, and we haven’t even added any actual data yet. While using this method I was using about 100 bytes per node, which can add up quickly on WASM4 in particular.

Infinite loops

It’s very easy to accidentally make an infinite loop in a linked list. I ran into the issue multiple times.

Lots of crashing

WASM4’s tight constraints and simplicity can make it a lot of fun to tinker with, but it can be very annoying when things start going wrong. On WASM4, I wouldn’t get proper stack traces/error traces when an something would go wrong. This made debugging much harder than it would otherwise be.

Takeaways

I ended up not submitting anything to the Black and White Jam because I started at the last second and ended up writing this UI stuff instead. I decided to start preparing for another jam instead, and continued working on the UI. I did learn a few things:

  • I don’t know as much about Zig as I would like (I didn’t know about @fieldParentPtr when I made the self pointer)
  • Pointers are powerful, but also a huge pain in the ass
  • Doing layout is actually pretty easy, it’s just the web that makes it seem difficult