Mana Engine: Colleague’s Challenge

It may surprise you to know that even at work, I advocate ECS architectures. Crazy, I know. But I do — a lot. I probably annoy my colleagues (even Rob, I’m sure) with how much I talk about it, but hey… if they find me annoying that’s not my problem!
Today I had the chance to speak to one of my colleagues who hasn’t heard my rant about ECS architectures yet. As we were talking, he told me that his big problem with ECS architectures is that they’re difficult to iterate on features. He’s accustomed to object-model architectures, you see, and not an engineering type. Reasonably so, he wants an engine that even a designer can rapidly iterate with.
“Object model architectures are just easier to iterate with, right?”
I paused, thought for a moment carefully weighing my next answer.
“No.”
I went home deciding I needed to back up my claim.
So here’s the deal. He’s a designer type, and wants to test an idea quickly before committing engineering resources to it. Hacking stuff in, tossing in flags, hard coding, solutions — all of these are okay in this case. The key is just seeing if it’s fun, and then to formalize it later. In the design process, he wants to reject bad ideas as early as possible.
He’s a 4X player, so his example of a feature to test whether it’s fun or not was the following: When a character walks onto a tile, if he has a certain flag (lets say he has a buff), all the trees on that tile light on fire.
I told him that not only is that easy to test in an ECS (especially Mana Engine) but it’s so easy to implement, that the “hacky” way is pretty close to the “right” way to do it. Like Rob said, “doing the right thing should be easier than falling on your face.”
The problem is, I don’t have a 4X game to prove this. But I have a little app that runs boids flocking (rendered as chairs, because… flying chairs). It looks like garbage and the terrain is in progress, but I’m a systems architect — not an artist, technical or otherwise.

They aren’t in frame, but there are about 1000 boids processing. Here’s what the frame looks like in Google Tracing.

I’ve gone into detail about what’s going on in this frame in a previous post, so I won’t go into it here.
And now… to implement something that is at least similar to the example my colleague mentioned.
Let’s pretend that this is something where the flying chairs are in combat. And when they fly near to each other, they get a “Power in Numbers” buff. Extra damage, or armor, or whatever. I don’t have any combat in the app, so I’m just going to draw lines between “buffed” chairs to represent the fact that they are buffed.
So here it is: For each boid, if there are 5 or more boids near it, it and all those near it receive the buff.
I already have a component that is the boids’ position. I also already have a spatial division container that the boids are sorted into every frame. And I have a debug rendering component as well. So here’s the beginning of my system declaration:
class BoidPowerInNumbersBuff : public Mana::Ecs::AuthorizedSystem<const BoidCells,
Mana::Game::DebugRendering, const BoidPosition>
{
//Gonna write stuff here.
}Medium is squashing the code a bit, so reading it is weird, but this is creating a new system called BoidPowerInNumbersBuff that has const access to BoidCells, BoidPosition, and write access to DebugRendering. Check out this post if you’re not familiar with system declaration in Mana Engine.
I’ll argue that this is a very natural first step. How many times have you asked yourself “how do I get to that data from here?” and end up writing either a daisy chain of pointers (blah->foo->bar->position) or taking the time to write an accessor? In Mana, you write a system declaring it your data and start writing. Here’s what I wrote next:
class BoidPowerInNumbersBuff : public Mana::Ecs::AuthorizedSystem<const BoidCells,
Mana::Game::DebugRendering, const BoidPosition>
{
void Update() override final
{
auto&& cells = *GetSingleton<const BoidCells>();
auto&& debugRendering = *GetMutableSingleton<Mana::Game::DebugRendering>();
auto posProxy = GetWorldProxyByTypes<const BoidPosition>();
}
};Pretty boiler-plate at this point. Getting references to the singletons, and creating a proxy so I can iterate over BoidPositions. Here’s the system fully written.
class BoidPowerInNumbersBuff : public Mana::Ecs::AuthorizedSystem<const BoidCells,
Mana::Game::DebugRendering, const BoidPosition>
{
void Update() override final
{
auto&& cells = *GetSingleton<const BoidCells>();
auto&& debugRendering = *GetMutableSingleton<Mana::Game::DebugRendering>();
auto posProxy = GetWorldProxyByTypes<const BoidPosition>();
for (auto&[id, pos] : posProxy)
{
// Get all objects in 50 meters, up to 5 of them.
auto objects = cells.cells.GetObjectsInRange(pos->position, 50.0f, 5);
// Only buff when close to 5 or more.
if (objects.Count() < 5) { continue; }
for (auto id : objects)
{
auto other = posProxy.Get(*id);
if (!other) { continue; } //Maybe assert? auto&& otherPos = other.Get<const BoidPosition>();
debugRendering.DrawLine(pos->position, otherPos->position, MnGfx::Color::Blue);
}
}
}
};
Just for completeness, here’s the code to register the system.
world.AddSystem<Boid::BoidPowerInNumbersBuff>();Hold on. Side bar for a moment. There’s a special treat in there. I’m showing off how Structured Bindings is making it super easy to access EntityViews. Here’s the line:
for (auto&[id, pos] : posProxy)If this looks weird to you, it’s just “for each entity in posProxy” where we’ve given variable names to the id and component(s) posProxy gives access to. Thanks, Rob, for implementing that feature!
Okay, back to the code in question. It took me about 12 minutes to write. And I was tending to children while writing it. So it’s certainly quick to implement. How does it work?
Here is a screenshot of it working in action.

And here is a Google Trace of a frame.

The new system is kinda chunking the framerate here. And I expected that. Again, remember that we are “hacking” in a test feature to see if it’s even fun.
In addition to this, this is work a designer or junior engineer can reasonably do. It’s not performant, but it’s good enough that we can test it. If the designer decides it’s a good idea, he can then improve it’s performance or ask an engineer to do it.
An embarrassingly easy optimization to make would be to go wide using a ParallelFor.
To wrap up, I’d like to run down a checklist of it’s qualities.
- Easy to implement.
- Non-invasive — it’s added code, so you’re not editing someone else’s code to make it happen. No merge conflicts. No writing code in a temporary hacky place.
- Just like every system in Mana Engine, it’s thread safe!
- It’s not that hacky — the real solution would still have the same general structure of code. No
//todo hack. don’t access data from here.So you didn’t waste time hacking something in you’re going to take out. You instead laid the groundwork for the “real” solution.
All in all, I’d say that rapid iteration is easier in Mana Engine in almost every respect.



