FAQ.#

Why did you make this?#

After finishing the jam game “Goodboy Advance”, Rik and I decided to make a post-jam version, which eventually became Goodboy Galaxy. The old jam code sucked, so we planned to rewrite it from scratch. And C was slowing us down a lot - it’s verbose, lacks good abstraction mechanisms, and does a terrible job at protecting you from your own mistakes.

At that time I was also learning Nim to make desktop games with SDL - something it’s very good at. I wasn’t expecting Nim to work on the GBA. After all, many compiled languages such as Haxe and Swift have hefty runtimes making them unsuitable for embedded platforms. But I gave it a try and to my surprise it worked brilliantly!

I wrapped the entirety of libtonc to create a library called nim-tonc. As I grew comfortable with Nim, I learned how to write more idiomatic code instead of treating it as just a “nicer C”. I renamed the library to natu and started using my experience to create nicer APIs:

  • Before: REG_DISPCNT = (REG_DISPCNT and not DCNT_MODE_MASK) or DCNT_MODE4

  • After:   dispcnt.mode = 4

While working on the game, I tackled problems such as image conversion, palette management and sprite tile alloction. These eventually made their way into Natu for re-use in future projects.

Natu is for those who want to enjoy writing GBA games in a flexible compiled language that feels like a scripting language. You get to work directly with the hardware and build your own engine, but you don’t have to reinvent the entire wheel.


How does Natu compare to …#

libtonc#

libtonc is the accompanying library to the excellent Tonc tutorial. It exposes the entire GBA hardware in C, and provides several utilities such as fixed point math, text rendering, and interrupt management.

Natu started as a wrapper for libtonc, also wrapping additional libraries such as Maxmod and posprintf. New features were added, some libtonc modules were eventually replaced with idiomatic Nim code, and others by alternatives such as ugba’s interrupt manager. However many parts remain, notably Tonc Text Engine and the Surface API. Additionally, the legacy module contains the original libtonc I/O registers and constants, some of which have no idiomatic replacement yet, for example DMA functions.

Butano#

Butano is a high-level engine using modern C++ ideas such as RAII to save you the burden of managing resources (VRAM, palettes, etc.).

Natu is lower-level than Butano, encouraging you to read and write I/O registers directly. Like Butano, it provides an asset conversion system, and allocators for Obj VRAM and Obj Pal RAM. Unlike Butano, you have to call the allocators manually. Also there are no allocators for backgrounds, you just manage them by hand.

It’s worth noting that Nim’s ARC memory-management strategy allows you to implement the same RAII patterns as in C++, so you could use Natu to build a “Butano-lite” if you wanted. If you were to do this, you’d find the resulting code to be way shorter!


Nimble says “Package 'natu' has an incorrect structure”?#

If you see a warning like this, don’t worry:

Warning: Package 'natu' has an incorrect structure. It should contain a single directory
hierarchy for source files, named 'natupkg', but file 'backgrounds.nim' is in a directory
named 'natu' instead. This will be an error in the future.

The structure is fine, Nimble is just overly prescriptive. The warning predates several additions to the language such as the ./ prefix for local imports, and the std/ prefix for stdlib imports. Hopefully they’ll remove it soon.


Error “<thing> is not in region .iwram#

This likely means your global / top-level variables are occupying too much IWRAM, which is the small region of memory where all variables go by default (see Where do my code & data end up in memory?).

To fix this, you could try any of the following:

  • If you have some values that never changes, you could make them const.

  • If the variables aren’t performance-critical, use {.codegenDecl:DataInEwram.} to move them into EWRAM.

  • Look out for memory-hungry variables such as large arrays/objects, and try reducing their size by using smaller datatypes, using the {.bitsize.} pragma to pack your objects, and removing unnecesary fields.

  • Make your variables local to allocate them on the stack, or turn them into ref objects or manually-managed ptrs to allocate them on the heap. This ensures that they won’t consume memory when they’re not in use.


Where do my code & data end up in memory?#

By default:

  • Procedures are compiled to Thumb code and placed in ROM.

  • Local variables are allocated on the stack, which is in IWRAM.

  • `ref` objects are allocated on the heap, which is in EWRAM.

  • Top-level variables are generally placed in IWRAM, but:

    • `let` variables go in ROM only if they’re ordinal types (or arrays of such) whose values are known at compile-time.

    • `const`s go in ROM, or may have their values substituted in-place at the point of usage (if they’re small).

The following modifiers are available for use with the codegenDecl pragma to control how code and data are placed.

  • ArmCodeInIwram - the procedure/function will be compiled to ARM code and placed in IWRAM. This can give a substantial speed boost!

  • ThumbCodeInEwram - the procedure/function will be compiled to Thumb code and placed in EWRAM. This may be occasionally useful for purposes such as a “cartridge removed!” screen, or for code which reads from SRAM.

  • DataInEwram - the top-level variable will be placed in EWRAM instead of IWRAM.


How do I get a pointer to constant data?#

This can be troublesome, because you can’t take the address of a const in Nim, and let won’t put data in ROM unless it’s a simple array.

To work around this issue, you might consider outputting your data as C or ASM files and then using the importc pragma to access it from Nim.

// TODO: use a struct example instead.
const char marioImg[] = "\000@vwF@wvw@uDt@U\004@@U\004\000\000D\000........";
{.compile: "mario.c".}
let marioImg* {.importc, codegenDecl:"extern const $# $#".}: array[256, uint8]

The downside to this is that you can’t use marioImg at compile-time anymore.

A better solution is often to avoid pointers altogether and use enums instead:

# TODO

This is also what the Natu project tool does when converting graphics / bgs / sounds.