Managing Long System Files

I generally try to follow the rule that, if several jobs always need to run is sequence - based on the same conditions - then the whole group of them is scheduled from a single system.

It seems like this would cut down on unnecessary runtime system checks, which can be quite expensive (sometimes over 0.1ms per system in Editor - maybe less in builds). I’ve been reorganizing my projects to limit the number of different systems in general, and I have seen some speed ups as a result.

But it can result in some very long, very cluttered System files. If you’re system needs to schedule more than 2-3 jobs, things can get lengthy.

I’m wondering how you have been organizing these. Do you split your job code up into multiple files, and just schedule them from a single system? Or do you just leave them all in single, long system files?

Just curious to hear your approach. Thanks!

The last time I check 1000 systems with properly skipped OnUpdate because of query mismatch only take 0.3ms. Therefore I split system liberally so that the class name make sense with all their queries in its OnCreate. There are multiple jobs only when prior one are data gather job that require some native arrays to be deallocate on completion on the latter jobs.

1 Like

I just seperate my jobs into independent files. This keeps the system file small, all its really responsible for is scheduling and let’s me unit test then independently.

Alternatively just use partial classes. Seen few people do that.

1 Like

out of curiosity, how often are you using SetFilterChanged in the required EntityQueries for your systems?

I’m think my slowdown compared to yours may be because I typically never add or remove components, to avoid archetype fragmentation. All my entities start with all the components they’ll ever need, and the jobs only run if certain components change.

But the side effect of that is that those systems always update and always schedule their jobs (even if those jobs don’t end up running).

like so many things in ECS land, my assumption that avoiding archetype fragmentation (and ECB use) would be the most performant approach may have been a naive one. I’m considering changing my approach and abandoning this rule.

I haven’t use it much but it should be used much more. I was too feared of system bugs where one won’t update and the first suspect would be “that changed filter?”, and another thinking that “that is no frame that it won’t change”, and lastly I thought it won’t scale well because of 2 filters limit per EQ, that the main work skipping by EQ should be the component typing then add changed filter as a small bonus rather than relying heavily on them (= if there is no changed filter, the performance would be unbearable).

In reality it is just a matter of first identifying if the system’s work is a mutation, then second, if the mutation target is exclusively written by this system. (Feels like synchronization, so you don’t need this system to reset the value caused by others even when mutation source didn’t change) And put it in even if today there is no chance it won’t change, when system order around in the future maybe you will get free performance.

Lastly the fear of change filter wrecking the world was fixed as I moved on from unit testing individual system to test an entire world by default unless if 1 system is very complicated (in which I would dice up the system, possible because my tests didn’t refer to any specific system but the unit is instead a combination of data). Actually this change in testing approach is directly related to your long system file problem because previously I tested on system and the test that locked to a system are adding more work when I want to refactor in a way that works the same functionally. (The test that was calling .Update on 1 system now only perform part of old function, the game works but only tests themselves fail.)

Usually the system that could use changed filter is not complicated (3-5 lines ForEach, etc.) but you fear the update won’t proc, then changed filter is not unit testable reliably enough. You could handpick some prior system to update to proc the change but it feels arbitrary, have to aware which system actually lands prior by update order sorting in real run, test can’t evolve when more are added to the world which would be considered the changed filter bug worth catching, etc. I can now just pop some changed filter in OnCreate and run all tests again.