Rituals»Blog

January 2017 Update

I've been having a difficult time trying to sum up January 2017. My best attempt has been "it's been a month, certainly," which isn't particularly descriptive. This post also spans a lot of December, too, which doesn't help.

Let's see... For the Ludum Dare I rewrote a lot of my personal library code, giving me more concrete ideas for how to clean up Rituals, and required me to make some tweaks to my metaprogramming/header tool. In terms of new features, I only added a few things: variadic args procedures to make text handling easier, an easy to use memory pool, and switching to double pointers for types that use it; but they make a lot of messy code disappear.

Miblo brought up the topic of code cleanliness in a wider sense in last month's post, so I thought I'd explain more of my thoughts on it. The short version: read Casey's post on semantic compression. 90% or so of the code cleanliness problems I whine about come from me not being very good at noticing opportunities to do that. Some of the biggest examples of this are when defining Sprites and Entities in code; the definitions cover multiple lines and often set the same few properties, generating a lot of clutter. I missed the opportunity to introduce convenience functions partly because they all look slightly different, partly because I was changing names as I was developing them, but mostly because I didn't know what the common use cases for these objects were when I was setting things up. I reworked a lot of stuff for my Ludum Dare game (sadly, I didn't finish it), and creating utility functions that made it easy to set the important fields on common structs made a lot of code cleaner and easier to understand at a glance.

There are a few cleanliness problems outside of semantic compression (though, possibly still related to compressing the information in your code). Firstly: argument order for similar procedures. Rituals' code has several procedures that need to render something that has text in it. Sometimes it's text, or text with a background, or a button. I find if I need to do something a few times I'll make a procedure that wraps the base one to make life a little easier, but sometimes different constraints or slips in concentration lead to me changing the order of arguments (say your rendering procedures follow a different pattern than printf ones; what happens when you try and combine them?) Over time, this can build up to something quite confusing. While your editor can help you with this (vim has some tricks I just found out about), ideally you can give similar procedures similar signatures. Secondly, especially when making a game, over time you end up mixing some amount of game data into the code. While this isn't a problem depending on the scale of your project, it's generally a fine line to walk. Long definitions take up screen space (something I consider quite valuable) and can make your code less flexible, but moving and centralizing definitions can lead to complex, abstract code, made worse in C/C++ by the lack of reflection (compared to say, C# or Python). As a project goes from small to large, it probably ends up moving data from inside the code to outside, and it feels like Rituals has hit at least two of those (entities, particle effects), at once.

Another part of clean code is organizing commonly-edited procedures together. If you're using a single-translation unit build ("unity build"), you might well not write headers, so you have to arrange your procedures as they depend upon each other. Coming from C#, I find this somewhat frustrating--often I'll want to write a bunch of utility functions at the bottom of a file and reference them in an update or render procedure at the top--so I started writing a tool on stream to do it for Rituals. I'd like to announce that I'm going to roll this off into a separate project, jokingly named "Wirmpht" after all the different things it tries to do. The current implementation is quite messy, but I plan on rearchitecting most of it and adding some missing features. Its current lack of any kind of error or warning generation can make it difficult to use right now, and there are features I never finished satisfactorily (introspection data, serializer generation, to name two). I already find the occasionally buggy and limited form of the tool quite helpful, so I'd like to imagine a complete and polished version would be widely useful.

I haven't been able to jump back into streaming quite as quickly as I'd have liked, but I'll start streaming Wirmpht programming this month, moving on to Rituals when it's ready. Instead of trying to stream every day, I plan on three days a week, mostly on Tuesday, Thursday, and Saturday, starting on the 7th.
xel, Edited by xel on
Thanks for sharing, I found this useful. Could you elaborate on how this tool you're making solves the dependency problem?
Ah, that was something I cut.

Wirmpht generates prototypes for procedures, forward-declares structs, and can copy typedefs and struct/union definitions, outputting them all to a file. You just include this file before your code, and it's like having written a header on your own.

In order to use the copied structs, you have to wrap structures in #if 0 or similar (I use #ifndef REFLECTED and just don't define it in the real build).