Nick Aversano
← PrevNext →
4 min read

Why I Write C

29 June 2023
By Nick Aversano
I started learning how to write my own game engines 5 years ago. In doing so, I've learned a ton about how computers actually work. C++ has been a great choice in many ways. It has also been an extremely humbling choice—I've encountered many things I didn't understand. Now I know some things, and I know that I don't know even more things. There's always more to learn!

Since the jQuery days, I had been doing frontend and backend web development. I've written web backends in almost every language I know. And I spent a ton of time writing React applications. After writing many many "web apps" I started to feel overwhelmingly frustrated with the web ecosystem. And with the trend of software in general. There are: slow build times, projects with 10k+ dependencies all for a pretty crappy UX. Seems like a bad trade!

I started writing web projects with less dependencies and implemented my own libraries. I enjoyed figuring out how everything works, and created new solutions that were simpler. Often it was not even that much code—I only wrote exactly what I needed. My last React project had 2 dependencies: esbuild and preact. There was no scrollbar in the node_modules folder! But I still felt like I could do better. Browsers are so wasteful and slow. And web apps feel so much worse than well-written native apps.

The initial spark for writing my own game engine came from the web. I wanted to get back into game development, but was extremely frustrated by the 5+ second build times of Game Maker Studio. I wondered if I could bring hot reloading from the web to game making? My computer should be able to reload things instantly!

I decided to use C because I knew that lots of game engines are written in C and C++. I also knew that my code could run as fast as the hardware would allow. I also decided to take it one step further by not allowing myself to use many libraries. I do use the C Runtime Library (CRT) and some third-party single-file header libraries, but I try them to a minimum. It's the Handmade ethos if you will.

What I learned to love about C is it gives you almost nothing. You are in control of everything that happens. You're just about as close to the computer as you can get without writing assembly code. And you can even see the instructions if you want to!

⚠️ As it turns out CPUs are more complicated these days, and there are things called micro-ops which some instructions break further down into. But let's not get into that here!

Learning how to make game engines from scratch is also a great way to learn how to build applications from scratch. Games have to do what every application on your computer needs to do. They need to open a window, get user input, display the results, and play sound. And they have to run in 16ms per frame, every frame. And they have to build a fun experience on top of that!

At their core, games need to:

  1. Get input from the outside world
  2. Do some updates based on that input
  3. Display the result to the user


In practice, what this actually looks like is more like:

f64 then = OS_Time();

while (!ShouldCloseApp())
    f64 now = OS_Time();
    f64 delta_time = then - now;
    now = then;



I mean, really more like this timestep, but hopefully you get the idea.

Because I had to build everything myself, I had to learn how everything works from first principles. I didn't write everything myself. I got lots of inspiration and code snippets from others. The trick was finding a good balance between using what I knew and exploring what paths others had gone down.

I eventually decided to write a base layer that I could build on top of similar to what you get in other programming languages. And if something has a bug, that means I could fix it! And if something has a bug, that means I have to fix it.

This base layer includes:

I also built a core layer on top of the base layer that includes:

And finally the game itself typically includes:

As it turns out things at the operating system level get a little bit tricky. The world is a mess of complexity and Conway's Law. Microsoft APIs are no exception:

You really appreciate everything when you have nothing.

C gives you a first-principles mindset to problems in computing more generally. In some sense, you know exactly what the hardware is doing. Too many things are built on top of towers of abstractions, and it's harder to reason about what's actually going on. Why do we do things the way we do them now? Are we getting good results? If not, why not?

It seems like modern day programming is inundated with libraries, OOP, TDD, slowness, bad UX, and outright malicious practices. And there are lots of programmers who don't even try to get better. They just repeat the same dogma. Maybe because it's all they know. Maybe they're scared of confronting what they don't know?

I think it's safe to say that the software industry has massive problems today. Programmers have failed. Software continues to get slower and worse. Users are increasingly being used for business profits with little or no care.

Maybe as a culture we've also forgotten how fast computers used to be? I certainly did! I had forgotten how fast applications used to start up. There were no load times, no updates, no restarts. Things just worked better.

Don't believe me? Check this out.

If we did literally did nothing, then software that is 20 years old should be 1000x faster today by De Morgan's Law. And yet, the software we use today is slower by at least an order of magnitude! And that's being generous.

So maybe we need to change our mindset?

Maybe we need to start caring?

Maybe we need to stop using so many goddamn dependencies.

We need to count in milliseconds, not seconds.

We need to get back to the basics.

And that is why I write C.

I still encounter many things I don't understand and probably always will. But I've learned that I can learn anything with enough time and practice. It's okay to get inspiration from others, but try not to worry about exactly how others solved their problems. Solve it for yourself. Make sure you really understand. Start where you are, use what you have, and do what you can. And remember to be kind to yourself!