DEV Community

Cover image for Designing the trait system
Omar Sherif Fathy
Omar Sherif Fathy

Posted on • Updated on

Designing the trait system

Table of content

Intro

I think a game engine is simply a tool to help you describe worlds. Making games is basically building virtual worlds then inviting others to explore them. In order to make that enjoyable you want to make sure you can properly describe everything in your virtual worlds to the player. Your main goal is to properly describe that virtual world to the player.

I think the same goes for almost any other piece of software. Your goal as a programmer is to describe the value of the product using numbers and characters. It all boils down to bits and bytes packed all together in a specific order to form the instructions that will then get executed either sequentially or in parallel.

While working on my prototype engine, I realized that I need to create a special system that is capable of describing objects in this virtual world.
That's what I would like to focus on today. I will write about designing the trait system of my prototype engine.

PrototypeEngine
Looking at the "Properties" tab on the right, this is exactly why we need to implement such system.

Why

First, we need to understand exactly why we have to build a trait system, like why do we need one and why do we need to describe objects in a virtual world.

My answer to these questions is because we want to compare things, it's hard to compare things if we can't describe them. We want to compare colors and materials, abilities and flaws, we want to look at this 3D world and be able to understand how to play the game or how to control things around us.
In reality we use the 5 senses to guide us through the world around us. We can compare objects by tracing the effect of each object on our senses. For example, you can immediately deduce that the object is made out of some metal if it's shiny, your eyes can compare between different surfaces and their reactions when exposed to light sources. Same applies for the sound when you hit the object.
Exploring a virtual world is easier when we're able to compare things around us. The higher the number of details or properties considered in the comparison the better.
Long story short, we need to be able to attach as much details as possible to the objects. I like to call these details "traits".
Each trait is a way to identify a group of features or properties that the object implements.

Technical details

We know that usually objects have an open and dynamic set of traits. We need to be able to attach and detach traits dynamically while the software is running. We also expect the operations to be very fast, we might end up having thousands or millions of objects in the world. Each will have its own traits and will need to dynamically attach and detach traits.
The main goal here is to be able to query the system and instantly get all objects which implement certain traits.
Now let's write some code ... grab some popcorn 🍿🥤

API

Before we rush into writing code let's first add a basic example to show how we want the code to end up looking ...

As you can see we want to have traits described as normal structures and we might only mark them as a PROTOTYPE_TRAIT just to help the compiler detect which structures are actually traits.

We start off by spawning some objects, probably we will have some sort of memory pool which will handle allocation. Once we have an object we want to immediately attach and detach any of the traits previously defined to it. We know that any trait is containing only data and we should not add any logic to it, we want to avoid adding functions to traits structures. This is very close to how entity component system "ECS" splits the data and the logic.

As shown above, the engine will need to group objects of certain traits together to apply some logic on them. So we know that we also want to keep traits of same types close together in memory to have as much cache hits as possible.

Start

We now know how roughly the system works and we want to start implementing it, the final code might not be typically what I have now in the engine, but it should act as a good start for those interested in the idea or design ...

As shown above, we basically write the same code for each defined trait in our code base. This indicates that something is probably wrong. One might stop and think about creating some abstract class and let any new trait inherit from that base class. I think this is a very bad idea as it throws the performance out of the window before we even start. We don't want to waste time on runtime consulting some virtual tables to figure out which function to call. One might also think about templates and maybe do the same thing but statically at compile time. I think C++ templates or macros in general are closer to the ideal solution for me but I think they're not providing much help in corner cases, I think I need to manually do some code generation.

I started off by creating an llvm/clang compiler pass which is basically going to help me identify which structures are marked as traits. Once I know that, I keep the structure name along with all the fields it has in a json format. After passing through all the code base I end up having json file that has an array of structures.
Here's a simple compiler pass code ..

Now that I have the information I need, I use jinja and I provide my templated .h and .cpp files then using the captured json data I start rendering the templates which will end up generating the whole code for me automatically. It's the exact same way you render some html page using json data that you grab from the database ...
Here is how the json file looks like ..

Conclusion

I think there's no perfect solution for any problem, but this worked really well for me.
You might think this is over engineered, I used to think so, but once I actually realized how much work I avoided using this method I started to believe that this just fits my needs.
There are so many holes that you will have to face, for example integrating that in your CMake build system and running the pass to generate the code that will then get included and compiled with the core engine code ....
I also found that method very useful when it comes to generating UI code. Most of the UI code is being automatically generated for me using this tactic, so I mainly avoided a lot of work by simply writing code that will generate the main UI code for me according to the layout of the traits.

Sometimes you need a bit of help from compilers to help you implement a system that has something to do with games .... Who would have thought ...
I think one should not underestimate the power of compilers.
I know I technically didn't really need a whole compiler for my example here but when I went deep into implementing a complete version of the system for my engine I found a huge need for the information provided by the compiler.
If you decide to implement that on your own, you would probably need information about the types of the fields of each trait structure. Sometimes you want to make sure that the type itself is not a container for example, or maybe you want to make sure that the type is not a pointer or maybe want to know the size of the type ... Things like that helps you build a solid ground for your system.

In case you would like to try the trait system in prototype engine, here is a preview link for the experimental WebGL2 port

I know I haven't still talked in depth about that system but I think I'll have to stop here and continue later on. Stay tuned.

Thanks for reading !

Top comments (0)