Big Fat Structs
How should data me modeled? This is one of the core question we encounter every time we program.
We can model data after a domain. Players have health, mana, movement speed, etc. What properties could an object have? It would be turned into a field.
While we could use interfaces for fields for now let’s ignore that aspect.
Why
But first lets focus on the why. Why would we even do that?
There is one big aspect when creating programming projects. How can we be as lean and flexible as possible? Writing interfaces, creating object hierarchies, abstractions are all good and fine. But what if some abstractions, interfaces, hierarchies are wrong? Objects that should have fields don’t have them. Assumptions that where made where flawed.
Having one struct with all fields make it trivial adding new ones or removing them. Because in the end It’s nothing more than linear data. When you have a position and velocity field every object COULD be turned into something we can move around in the game world. We get emergent behavior.
Thing about displaying 3D Text in the game world. The primitive looks probably something like:
class Text3D
{
Vector3D Position3D;
String Text;
}
Now how would you add the following capability, a text can be influenced by velocity and moved around that way? Some languages allow for adding new fields to be mixed in existing objects others do not.
Most will create a higher abstraction:
/// Thinking exercise for you: "How would you name such an object?"
class Velocity3DText : Text3D
{
Vector3D Velocity3D;
void Update(float deltaTime)
{
Position3D += Velocity3D * deltaTime
}
}
The main problem was that our architecture was not flexible enough, we didn’t anticipate that users want to add velocity to Text3D
How
struct Player
{
double health;
double mana;
Vector3D MovementSpeed;
}
struct Enemy
{
double health;
double mana;
Vector3D MovementSpeed;
AI AIController;
}
struct ConstructorEnemy
{
double health;
double mana;
Vector3D MovementSpeed;
AI AIController;
BuildPlan BuildPlan;
}
Or we write every possible field into one struct type.
enum ThingKind
{
PLAYER,
ENEMY,
CONSTRUCTOR_ENEMY
}
struct Thing
{
ThingKind Kind;
double health;
double mana;
Vector3D MovementSpeed;
AI AIController;
BuildPlan BuildPlan;
}
When we want to do something with our thing struct that is specific to only one kind we check that kind before. It’s important to understand that a kind is not a component. We are not saying that a thing is a velocity component. We are saying that a thing is a bigger concept like a player.
Furthermore, we could add a form of mixing capabilities into a thing using enums.
[Flags]
public enum Capability
{
None = 0,
CanWalk = 1 << 0,
CanSwim = 1 << 1,
CanFly = 1 << 2,
CanCastSpells = 1 << 3,
CanOpenDoors = 1 << 4,
}
enum ThingKind
{
PLAYER,
ENEMY,
CONSTRUCTOR_ENEMY
}
struct Thing
{
ThingKind Kind;
double health;
double mana;
Vector3D MovementSpeed;
AI AIController;
BuildPlan BuildPlan;
Capability Capabilities;
bool Has(Capability capability)
=> (Capabilities & capability) == capability;
bool HasAny(Capability capability)
=> (Capabilities & capability) != 0;
void Add(Capability capability)
=> Capabilities |= capability;
void Remove(Capability capability)
=> Capabilities &= ~capability;
}
Then we could not dispatch on the ThingKind but instead on capabilities add or remove them add runtime. While every thing has the required fields we check if they have the capability instead.
Conclusion
So should every program have one type of big-fat-struct? No I generally don’t think so. But maybe every module/library should have. Mixing it with opaque pointers and exposing a capability struct could be useful. But I have to do more research on that idea. Also see the definitive guide for software design the part about API design.