Project Dusk #8 - Realizations
First a working build:
"Global states are evil" isn't a new insight, but I am learning more reasons why this is the case firsthand. I haven't achieved hot reloading the game while retaining the game state. The reason is, that quite a few things taken for granted don't mix well with unloading and reloading code (just similar to how impossible it is in Unity to develop a game in a way that hot code reloading works). While global state variables are obviously problematic, there are other not-so-obvious global value types that we typically don't recognize as such. Function pointers for example. When creating the scene's content and creating objects and components, storing a function pointer inside a component is a sureway to not only make the object not serializable, it's also going to crash when trying to reload the code. The reason is, that the function pointer is pointing to the old code, which is no longer there. I assume C string literals are also problematic, but those can be copied easily.
To add insult to injury, I am suffering also from having worked out a lot of code before I came to the conclusion that this isn't really a demo sized coding test but an actual game. There's no better way to test an API if it works for making games than actually making a game, so ... this is something worthwhile to do. But I am suffering from a couple of problems from this approach:
- I encounter problems on the go, changing several times the way how the code is organized.
- Since I am never sure if the changes are going to be final, I am not refactoring the existing code.
- There is quite a bit of repetition.
The latter point is most annoying. Here's what I mean with that:
- A component is typically involving a struct definition
- It requires several functions that are used by the component type definition
- The component type definition declares the type, provides the struct's size and the methods that operate on the component type
- The component type id is stored in a shared variable so other components can use it to create new components
- The registration is called in the game's initialization code
- The component struct needs to be shared so other components can access it
Initially, I had 4 to 5 different files and several different locations in each file where I would write the new component content with quite a bit of repetition. I am attempting to reduce this to make it easier adding new components, but while it works perfectly well on paper, the actual implementation suffers from bad IDE support because it's macro based. The basic idea is rather simple:
1 // a list of all components "components.h"
2 COMPONENT(HealthComponent)
3 COMPONENT(MeshRendererComponent)
4 COMPONENT(VelocityComponent)
5 ...
6
7 // some other file: function declarations to register components
8 #define COMPONENT(name) void name##_register();
9 #include "components.h"
10 // produces: void HealthComponent_register(); ...
11 #undef COMPONENT
12
13 // in some function:
14 void registerComponents() {
15 #define COMPONENT(name) name##_register();
16 #include "components.h"
17 // produces: HealthComponent_register(); ...
18 #undef COMPONENT
19 }
This basic example (my code works differently) shows how to reuse a header file that contains a list of component names to generate different looking code in different places. I actually changed the "components.h" file to include a file that depending on the context provides the declarations and definitions. So technically, this is reducing the repetition a lot and only the actual file and the list file needs to be modified when adding a component - which is really a great improvement. The downside is, that the IDE is quite confused how the file is supposed to work when I edit it and I regularly see error highlights or syntax highlighting that is off. So I am still trying to figure out a better way to solve this.