(Please excuse the poor code formatting; WordPress acts a little funny about spacing.)
Last time, I gave a brief, non-exhaustive intro on what an ECS was, and also talked a little bit about pros and cons.
One thing I didn’t discuss was nodes. Nodes are containers specifically for components needed by a system. For instance, a PositionUpdateSystem class could have a PositionUpdateNode class associated with it for holding position and sprite related components. Something simple like this:
class PositionUpdateNode
{
public var position:PositionComponent;
public var speed:SpeedComponent;
}
One thing that struck me about this though is that this can lead to some stacked memory references when writing system logic that uses these nodes:
for (var:node in system_nodes)
{
node.position.x += node.speed.x;
node.position.y += node.speed.y;
}
Maybe this isn’t a big deal to some, but I personally don’t like these kind of multi-layered reference calls (yeah, its only three layers, but still.) They look kind of ugly and they can hurt readability. And this is just a simple example. This of course can be the price you pay when you choose to manipulate data directly as opposed to local member functions.
Why not just a few member functions on those components then? No. Although I’m not an ECS purist, the data/logic separation is a key facet of the ECS for good reason (at most so far, I’ve used simple constructors for setting up more complex data items that need initialization.) The more you attach to a component, the more that gets passed around and the more strain you can have on memory. Systems should be able to quickly and cleanly cycle through its list of nodes/components on standby. For smaller games it’s probably not such a big deal, but I don’t want to start any inefficiencies now.
So what to do? Just live with it? Well, I wasn’t quite content. Efficiency AND readability are important in their own right. I could do something like use temporary variables:
var i = node.image;
var p = node.position;
i.x = p.x;
i.y = p.y;
But this is also kind of shoddy and, frankly, may not help readability (or efficiency) all that much either. So how do we get clean code and still keep our data-oriented approach?
After some looking in Haxe, I found my solution: static extensions.
Think of static extensions as member functions that aren’t actually attached to the class themselves. What? Yes, you heard me. Consider the following:
class PositionManipulator
{
public static function move_by(comp:PositionComponent, d_x:Int, d_y:Int):Void
{
comp.x += d_x;
comp.y += d_y;
}
}
In and of itself its just a class with a static function, but if you utilize it as a static extension with the “using” keyword, now I can transform our original code into this:
using PositionManipulator;
for (var:node in system_nodes)
{
node.position.move_by(node.speed.x, node.speed.y);
}
I won’t go into all the details (click the link I provided instead,) but basically invoking the static extension causes its functions to attach themselves to the type that matches their first parameter. In reality, the call is transformed back into the static function call later, but for now its presented in a much more readable format. Of course, we still have the multi-layered speed references; we could clean that up to in some way, but for now I think it already looks better.
Now isn’t that easier on the eyes? That’s what I got for you today. More to come, but until then, keep trucking away on the code, games, and all that good stuff.
Leave a Reply