Unsplash

In ECS you should not hard specify the update position of system, but you have to condition the system in a way that “ended up” being in the correct place 100% no matter what the final order.

Supposed that you are designing a DestroyPacketSystem . The intention is to clean up all chunks which contains Packet component on every frame so the next frame no one can use it the 2nd time.

The design might be

  1. DestroyPacketSystem enqueueing destroy commands to EndFrameBarrier and you call it a day. Every one can now work safely knowing that they work before packets are destroyed right?
  2. DestroyPacketSystem destroys all packet immediately using chunk iteration or EntityManager with component group argument. But it’s update order says update BEFORE EndFrameBarrier .

Either design might work, but there are pitfall about under specifying update order. Both ways you are relying on the name of EndFrameBarrier

The real ECS way

You might ended up with correct order for now but that design still has flaws.

The real ECS way to do that is your destroy packet system should have no update order on it at all, but all users of packet must specify [UpdateBefore(DestroyPacketSystem)].

It might sounds like a massive hassle, but think about it if your system wants to use the packet then it’s own requirement is that “it must be positioned before the packet being destroyed”. When you look at ONLY that system you can say to your self “Yes, this gonna work”. If you are not explicit about being before DestroyPacketSystem then you are relying on the “name” of EndFrameBarrier which DestroyPacketSystem is using. (Either as ECB enqueue target or as an update order)

This way you are following the self-contained design where the system knows how to correctly position itself by ONLY itself.

Using update group

Or all packet user could be in an update group PacketUserGroup, then that group has update before destroy packet, that also works too. Or you can put UpdateAfter PacketUserGroup on the DestroyPacketSystem, you are instead specifying how the destroy packet system will correctly work assuming everything in that group is correctly there.

What exactly is EndFrameBarrier

As of preview 21

I consider EndFrameBarrier a hack, one might just inject and use the barrier without saying UpdateBefore EndFrameBarrier because “the name says so” but that’s not so ECS… (anyways if you want to say so, the correct would be AFTER not before. You will see why soon.)

But why is it working “mostly”? Because EndFrameBarrier is using update order BEFORE Initialization step. (inverse of what the name says!)

When update order is using a PlayerLoop step it is IN that step. Before initialization means that it *should* be the first thing in a frame.

You might be just lucky that it works for now

Hence, the correct way of using EndFrameBarrier is for all users to have to say UpdateAfter EndFrameBarrier, which feels weird because the name of that system should be BeginFrameBarrier, almost the same definition when used because previous frame’s end frame and next frame’s begin is just a bit apart, but more true to its update position.

When all your system is using EFB without saying anything about order they would be all AFTER the EFB by default, just because the default step of all systems are positioned in Update player loop step.

How the system was named and positioned like that (before Initialization) seems to have an intention of allowing you to use the EFB without specifying order in relation to it and have it magically works. Thanks solely to player loop positioning Initialization step before Update step.

What if you want to use Initialization step?

But what if your system has a (rare) specification of have to work in Initialization step in order to function? In that case it would be uncertain if your system would be before or after EndFrameBarrier, you can’t trust the system’s “name” anymore.

In my game I have only 1 system which wants to work in Initialization step. And just because that system has update order in relation to something and something which the chain ended up at EFB, now the update order algorithm put almost all systems in the Initialization step together! What a sight! Only a few systems remains on Update step, because no one really says they “have to work in Update step” but that happens to be the default that works.

When this bug happen of course, it is uncertain for everyone dragged into Initialization step now if they will be before EFB or after EFB since they said nothing about it.

Now, systems in Initialization are rare so it should be fine to just use EFB as is right? The way in the design at the beginning of this article guarantee that destroy packet system will also be in Initialization step and all other systems would be run after the packet was destroyed by default, as long as you don’t put anything in Initialization step. (This order inference only works because you use BEFORE EFB, if you use AFTER EFB it could be in Initialization or any step after it.)

Bug?

But one thing that might be a bug is that I found EFB being in Update step instead of Initialization. I recall this does not happen until recently and I don’t know why. Here, you see that EndFrameBarrier is not in Initialization step but far below in Update step. (And not exactly “end frame” either!)

Suppose that this happen in your game that designed DestroyPacketSystem like discussed so far.

Your destroy packet system only says it should update BEFORE EFB (or playback destroy commands at EFB), then it could be at any position during Update step! (when this bug occurs)

Chaos ensue when the packet will be mass-destroyed during update. You may get the desirable update order for now, but when you add new system you are at mercy of update arranger’s input order again, which currently depends on alphabetical order of class name in your assembly also ordered alphabetically. (If using default world)

This make it possible to introduce ordering bug randomly when

  1. You add new systems which its class name comes before your currently working system alphabetically.
  2. You just feel like a nice day to refactor the code by renaming classes and suddenly things become weird. This happen multiple times in my game when I “underspec” the systems and bugs randomly pops up for no reason. Always look for update order if you think what you did has absolutely nothing to do with the bug before your eyes right now.

So.. to be safe and correct the destroy packet system should work on its own without relying on EFB and have everyone says update before it. Some system that ended up after it is totally fine, since they “don’t want to use the packet anyways”. Massive hassle I know but I believe that’s how modular design works.