Tick system, custom dependency handling

I’d like to implement a tick system using a EventHandler (Or System.Action). I already have a tick system implemented using a simple OnTick method defined inside a monobehavior.

Currently I’m not using ticks anywhere. Almost all my jobs are called from OnUpdate().

I’m now looking to subscribe a whole bunch of my systems to this tick event and not use OnUpdate() anymore. The idea is to not create jobs every single frame, but every tick instead, or multiple ticks, while at the same time keeping the jobhandle dependency chain that comes with the OnUpdate() method. I want my jobs to do their work over multiple frames. Therefore if I create a job in SystemA in an OnTick event and SystemB has a job in its own OnTick event that depends on SystemA, how would SystemB know it should execute after A is done? Would a simple ‘UpdateAfter(typeOf(SystemA))’ work? I wouldn’t think so since the OnTick method is just an event that doesn’t and can’t return a JobHandle.

What about the jobs that are still scheduled in OnUpdate() (such as movement/rotation)? I’m pretty sure that those systems will start complaining about dependencies since I’ll be modifying the components/buffers they need in some OnTick methods and the job handle in the OnUpdate method won’t know about them because basically now I have 2 separate job handle chains, assuming I got the tick system working of course.

Here’s the tick system in case it’s needed:

What you want to do is not add systems by default to the game loop and instead call the systems’ Update method in response to the tick. As long as all the systems are created with the same world, automatic dependency management will still work.

Another option is to have all your systems be assigned to a custom ComponentSystemGroup, and in that ComponentSystemGroup you override the OnUpdate to do nothing and then in a different method call base.OnUpdate.

But using OnUpdate causes jobs to force complete in one frame rather than over multiple frames.

That’s not totally true. A JobComponentSystem completes the jobs it schedules the last time it ran right before calling OnUpdate, assuming the jobs weren’t forced to completion by another system in the chain. If you want some systems to run on tick and some per frame, you may want to make sure they run in separate worlds so that the per-frame systems don’t complete the per-tick systems.

Right, that’s what I was thinking. This is not going to be as trivial as it is for monos.

I looked into World and it sounds like they were intended for this purpose. It looks like entities are unique per World. This poses a big problem. Imagine the following simplified use case:

There are 20k entities, a moving system working on paths stored into buffers and a path computing system. I want the last system to run per tick while the first one per frame. So the last system run in World B and the first system in World A. All these 2 systems need to work on the same 20k entities though. So how exactly is that supposed to work since Worlds have unique entities, as in, unrelated to entities in other Worlds? I assume I would have to use MoveEntitesFrom() at the end of the tick, or earlier if World B finishes its jobs, to transfer entities from the simulation world (B) to the moving world (A) but wouldn’t that cause a massive lag spike? I mean, 20k entities with hundreds of elements in their dynamic buffers must be something. And what happens with the old 20k entities in World A? Do I have to destroy them before(after?) moving in new ones?

What about the other way around: World B needs the new translations from World A in order to compute new paths. Therefore, I would need to MoveEntitesFrom() from World A into World B right before B starts executing a new tick. But I also want to keep them in World A to continue translating those entities. Does that mean I have to make a copy of the 20k entities and move them onto World B so B can work on those? All this syncing between worlds also makes me doubt there won’t be any lag spikes especially since Worlds have their own copies of entities.

This is not trivial at all and I can’t find any code examples on how to properly use worlds and sync them.

Edit: Another problem I can foresee is the actual moving of entities from world to world. Assume world A also renders entities. Then when B finishes computing and transferring its entities into A, the new entities that were just transferred will have the position that was sent into B at the beginning of the tick since B doesn’t do any actual translation. So on screen it will look like entities are backing away. How is this solved?

This is turning into a nightmare.

Oh. This is pathfinding? For pathfinding you want to copy the data the algorithm needs out of all your entities into NativeArrays and then run a single IJob on it and not force it to complete. Having the pathfinding algorithm work directly on Entity data while that entity data is being modified every frame is a terrible idea and why making a snapshot copy and resolving that snapshot several frames in the future is a much better one.

No it’s not about pathfinding. I already compute paths the way you described it. However, the pathfinding system does some extra work in the OnUpdate method, which is to request paths every once in a while to refresh them (think unit chasing another unit). It’s this that I want to put into a tick method. That and target finding, spatial algorithms, ai decision states, constructing, anything to do with resources and so on.

Basically only rendering, animation and translation needs to be done per frame. Everything else should be done over multiple frames.

Anyway, I’ve came up with a general approach on how to implement this. I’ll try implementing it myself in a few weeks and I’ll report my findings here. If this won’t work out, I’ll try Worlds. But here’s the general idea step by step for others who might find this topic:

Workflow:

  • Disable all systems from auto running and within each system create your own update method that takes a jobhandle and has to return one
  • Create two groups of systems: PerFrame and PerTick
  • Create a dedicated monobehaviour for running your logic
  • Inside this mono, create 2 job handle fields: PerFrameHandle and PerTickHandle
  • Inside the Update of the mono, run all the PerFrame systems every frame passing in the per frame handle using the custom method implemented in step 1.
  • Every x seconds, do the same with the other group of systems using the PerTickHandle, but before it starts, make sure to complete the PerFrameHandle

Requirements:

There have to be as few dependencies between the PerFrame jobs and the PerTick jobs as possible. PerTick jobs cannot write to components used by the PerFrame jobs as that will lead to race conditions. Similarly, PerFrame jobs cannot write to components used by the PerTick jobs. Therefore we need the PerTick jobs to work on copies of data. So before a new tick starts, copy all the data written to by the PerFrame jobs into a new container and send that to the PerTick jobs. Likewise, when the tick ends, copy all the data needed by the PerFrame jobs to the PerTick jobs so they work on that.

I supposed if you use Worlds, you’d use the same kind of workflow, but from what I understand, Worlds can only process unique entities which means you’d need some kind of mapping system between entities as well since you can’t pass only just components between each World like my proposed solution.

So Unity already kind of built and designed around this as they already split Systems into Simulation and Presentation.

When they first introduced these groupsSimulationSystemGroup actually ran in FixedUpdate giving it a fixed tick rate whereasPresentationSystemGroup ran in Update giving it a frame tick rate.

Due to technical issues (not certain what the issue was, I had no issue with it and really liked the setup) this was reverted. They said they intended to bring this behaviour back (and the Physics package actually seems to expect this) once the issues have been fixed but that was 6+ months ago so I’m not sure if that’s still the intention.

I guess that explains why the majority of the threads I read concerning this topic date back so long ago.

Anyway, that’s good to know. A new package is supposed to come out soon too so maybe that will contain this split feature and that would save me from trying to write up my own solution which isn’t even guaranteed to work.

Update:

I just tried a rudimentary job tick system as described 2 posts above. I got it to work using the requirements I posted above, though I haven’t gotten to the syncing part yet. However I already ran into an error so this is why I’m posting something now.

InvalidOperationException: The system ECSProject.BuildingsSystem reads ECSProject.Stats via HospitalSystem:GetNearbyUnitsJob but that type was not returned as a job dependency. To ensure correct behavior of other systems, the job or a dependency of it must be returned from the OnUpdate method.
Unity.Entities.JobComponentSystem.AfterOnUpdate (Unity.Jobs.JobHandle outputJob, System.Boolean throwException) (at Library/PackageCache/com.unity.entities@0.1.1-preview/Unity.Entities/ComponentSystem.cs:900)

HospitalSystem is my most lightweight system, so I thought I should start testing the waters with that. I put that system in the tick group while everything else is still in the OnUpdate method. I disabled the JobsDebugger and there’s no more error and everything seems to work fine so far. I’m seeing the correct behavior, as in, units around hospitals are being healed on ticks. However I’m wondering if it’s a good idea to ignore that error and keep going with my current approach.

It seems that even though BOTH the hospital system and the BuildingsSystem only ever read from components and never ever write (all components are marked as readonly both in jobs and their entity queries), as per my requirements above, it looks like Unity is still complaining. (Small note: The hospital system does write something, but all it does is add an event to a list in an IJob to another unrelated system and unrelated to the ECSProject.Stats that the error complains about).

Does anyone know if it’s safe to proceed? Both systems just read from the Stats component but it looks like Unity doesn’t like that. I tried putting the hospital system into its own ComponentSystemGroup but that didn’t change anything. Could it maybe be a case of a system that does write to the Stats component, but the debugger doesn’t properly pick it up and blames another system that’s in the chain?

No. The debugger is freaking out because it knows you are accessing the data as readonly but doesn’t know what the JobHandle is, so if something in the future was scheduled to write to the data (in this case ECSProject.Stats), it wouldn’t know what JobHandle to use as a dependency.

So you’re saying that as long as I don’t write anything to the data in the future, in this case ECSProject.Stats, it would be fine ? Because I will always make sure to not have any concurrency problems myself. The current implementation does have concurrency problems since I only just got started and I need to do a lot of refactoring.

Sorry I’m going to be blunt; it is incredibly dumb to ignore errors.

Of course, but what I’m trying to say is:

The error should eventually go away after I refactor the whole code to work in ticks and frames and that any common data between the two groups would be copies. At least I think, which is why I’m asking. I kind of don’t feel like spending a few hours on refactoring only to find out that the error still persists. In that case, I would just revert to a backup and re-think my approach or try out worlds.

If the job was really ReadOnly, you could return the dependency back and it wouldn’t force completion until the next tick.

If I wasn’t lazy, I would gif a “Good luck with that” scene from one of my best artistic reference animations.

1 Like

A few hours or a few days? Because a few hours is not much for DOTS preview work. If I were you, I would make an experimental project specifically to test out driving data in the frame update loop and the tick loop until I got the pattern down.

I’d like to see your best artistic reference animation to be honest.

Well I’m sure you know what ‘a few x amount of time’ actually means in programming ;). I’ll actually do just that and I’ll report back. In ‘a few hours’ of course.

1 Like

SUCCESS!

Kind of… Let me start by saying that this solution I’ve got going on isn’t for everyone. There are 2 main restrictions to make this work with the following implementation, which not very many people will be okay with, including myself. First: Entity debugger doesn’t work, some exception pops up when trying to open it. Second: You can’t use any systems in other packages, because of their OnUpdate() methods (That means no RenderMesh, probably no Unity.Physics and so on) If you want to make those work, you’d need to modify the source code by disabling them and calling them yourself.

What’s more is that you need to duplicate data that’s being shared between the 2 groups. So you need 2 translation components, 2 dynamic buffers if you do pathing, 2 animation data if you do animation and so on. So it’s a little more complex than usual, but if you keep the data sharing between the two groups small, it wouldn’t be a big deal. In my case, that data is not a lot of code. It’s just translation, dynamic buffers for pathing and animation data, plus 3-4 other misc components.

Last point: The bottle neck is the sync point. Copying 2 million float3’s from one field into the other in one single bursted frame apparently takes 7ms in the editor. So in order to optimize this, you’d probably need to use pointers to pass data around efficiently. You’d probably also have to not use component data for this, since to my knowledge, you can’t get a pointer to an array of component data.

Now is this worth it? For me personally, in its current implementation, no. Even if the sync point were to be optimized, I’d still have to find a solution to the entity debugger or create my own debugging tools. I would also have to restrict myself to only using my own systems and no other system, not even Unity’s render system. This last point is not such a big deal, but meh.

However, if you plan on making Factorio-type games, or They Are Billions-type games with better pathing and more complex simulation, then you’d definitely need to delegate work to ticks. At that point, it would probably be better to create your own engine though.

Here’s the code for anyone interested or if you want to attempt improving it. I left some code to run the jobs in the normal way as well if you’d like to compare. Remember it’s just for proof of concept, no optimizations were done. I might come back to this some other time to figure out the debugger bug and optimize transfer of data. Until then, I’ll go back to normal ECS workflow and wait for the smart minds at Unity to figure out this whole Simulation and Presentation thing.

5161763–511766–Test.cs (1.63 KB)
5161763–511769–SystemsManager.cs (3.97 KB)
5161763–511772–PathingSystem.cs (2.22 KB)
5161763–511775–MoveSystem.cs (1.7 KB)

Ok. I am even more confused about what you want, but what you have is definitely not it. As a little exercise both for you and for my understanding, try drawing out an ideal multi-frame timeline which shows the major jobs (along with their inputs and outputs) and includes the main thread and a few worker threads. Because what you got right now is just going to give you a frame spike every tick.

Actually I’m giving up on this. There are simply too many roadblocks to accomplish what I envision. I see you tried something similar last year and ran into similar problems.

Unity seems dead set on forcing you to always return your job handles from the OnUpdate method, otherwise it will complain, even when you know for sure that dependencies are correctly set from a logical point of view. Then once you figure out a way around the complaining, it will of course break unity’s internal systems and the entity debugger.

I just optimized the syncing point. Basically what I did was to simply skip a frame while the syncing is being done, just for fun to see if that’s what it takes to make everything work flawlessly ( I know skipping a frame while rendering is… not ideal). It did work and everything seemed fine and dandy for 1m entities. But then I set 10 million entities to move around. The idea was to give more work to the jobs to simulate a real world scenario with a lot more components and data because my testing is only being done on about 4 components. But Unity started throwing tons of warnings like this one

Internal: deleting an allocation that is older than its permitted lifetime of 4 frames (age = 9)

Who knows what that even means in practical terms… Was the job stopped midway or what exactly does it mean? Does it mean Unity still doesn’t allow jobs to be completed over more than 4 frames? Then a tick system is doomed to fail no matter what approach I take. The game still seemed to work as the entities moved around, but suddenly jumped from running at a spikeless smooth 0.4ms per frame with 1m entities and a tick of 0.3s to a random 600ms every tick with 10m entities. This is caused by the per frame job of moving entities on the screen that has to be completed in one frame before the tick starts, so in a real world scenario, you would not have 10m entities to work on every frame, but still. Who knows what other errors I’d be running into later down the road. That, the entity debugger breaking and every other internal unity system breaking is enough for me to move on. I’ll wait and see if Unity will ever remove this restriction of being forced to use OnUpdate as Tertle said earlier.