Data/Entity structural changes - When in the frame?

Hey,

I’m having a hard time structuring my updates.
Begin/EndSimulationEntityCommandBufferSystem,
SceneSystem.UnloadScene,
SceneSystem.LoadSceneAsync

All of these can be used freely so there’s little guidance on when to do things. Up until now I’ve just picked more or less always picked Begin for the EntityCommandBuffer except in cases were things needed to be more structured. But when implementing level loading I’m now running into issues where I’m adding components to deleted Entities because they have cleanup components keeping them alive.

TL;DR; please give me some advice on a general rule to follow. Structural changes early or late in the frame? Is there some general rule or idea I’m not seeing?

Thank you very much :slight_smile:

edit:
Or is the gist that the only real differences between those to is that there’s rendering in between (so really no difference at all) and that it’s more about the relative ordering of changes and updates?

edit2:
This is what GPT says, we live in truely good and/or bad times:

**Before the update phase** If you make structural changes before the update phase, you'll be working with a known set of entities and components throughout the update phase. This can help ensure consistency and make it easier to reason about the state of the game world during the update phase.

However, making changes before the update phase may not be appropriate for all types of changes. For example, if you're adding new entities that are dependent on the current state of the game world, making those changes before the update phase may not work. In that case, you may need to make the changes after the update phase.

**After the update phase** Making structural changes after the update phase can be useful if you need to create or remove entities based on the current state of the game world. For example, if the player collects a power-up that spawns a new enemy, you might want to create that enemy after the update phase to ensure that it spawns in the correct location.

However, making changes after the update phase may cause the game to briefly pause or stutter if the changes are significant. For example, if you're removing a large number of entities, that could cause a noticeable slowdown. To mitigate this, you may want to spread out the changes over multiple frames, or use a job system to distribute the work across multiple threads.

In general, the best approach depends on the specific needs of your game and the types of changes you're making. As always, it's a good idea to test and profile your game to ensure that the changes you're making don't negatively impact performance or introduce bugs.

You shouldn’t be rely using ChatGPT for DOTS development. Knowledge of chat GPT is cut off in 2021. You will have outdated and inprecise information related to fast evolving DOTS. DOTS 2 years ago was significantly different than it is now. Also number of data is very narrow, as not that many DOTS dev was then and even now. Unfortunately, significant portion of DOTS expertise is getting lost in discord, where internet crawlers don’t have access to. That means ChatGPT like tools, will have limited expertise.

However regarding your questions, we should ask again, why and when do you consider for structural changes?

So you said level cleanup.
I personally aim doing structural changes once in a frame, whenever possible. With DOTS 1.0 and enable/disable components that is even easier to do so. But not always.

If you prioritise Deletion, Creation systems first and then rest of logic systems, then you may be good to go.
Issues that you may consider with a design, where you have job.complete calls. As this will playback entity command buffers during runtime and may mess up some logic, if you are not careful with a design.

You want control events, which whenever are collected at the runtime, you only execute them at one point in a game update logic.

Also, you can consider using state components. These helps tracking let’s say deleted entities. And you can act upon as such.

There ir is so many ways you can tackle such problems however. And surely you will see multiple options, when others DOTS devs will add comments to this thread.

Surely, remember about stress testing and profiling often. As you may be trying tackling none existing problems, for your use case. While potential bottle necks may be laying in completely different area of the systems.

2 Likes

I’d say it depends on your architecture;

General idea is to make as little structural changes as possible in less ECB systems as possible.
Main reason for that - you don’t want to have “bubbles” during simulation where jobs are stalled by performing sync points and cause threads to do nothing in that time.

You can always introduce and insert your own ECB systems as well to adjust where things should be executed.

For example, for the hybrid app, I’m using the following setup:

– Begin Frame Simulation Phase – (Mixed Systems)
| → NextFrameECBSystem ← Everything that has changed in AfterSimulation phase applied here to avoid clashes with MB changes
| → BeginFrameECBSystem ← Everything that is generated by MonoBehaviours (EntityBehaviour) goes in here
|
– Before Simulation Phase – (Mixed)
| → Systems which execution is required to avoid frame skips, like input polling, time adjustments, custom triggers etc;
| → BeginSimulationECBSystem ← Results are written here;
|
– Simulation Phase – (Job systems only – Schedule // ScheduleParallel)
| → Actual simulation, all processing goes here if possible
| → EndSimulationECBSystem ← Results of the simulation written here, for the cases where data pass to the MB is required
|
– After Simulation Phase – (Mixed)
| → MonoBehaviours receive corresponding results via SystemBase, UI update, VFX spawning & pooling and such;
| → AfterSimECBSystem ← For the rare cases where data should be applied before LateUpdate
|
– Presentation Phase – (Mixed)
| → Stuff that is required to be processed directly after MB & Simulation is done;
EOF – Rendering

While it may look like a lot of “Mixed” systems, in reality bulk of the work is done in SimulationGroup, pushing managed systems / Main Thread usage to the minimum.

Regarding system ordering inside SimulationGroup / Load Balancing

Note that scheduling order of “systems” / job also matters. To improve performance you’d want to schedule heaviest jobs first, so that they have more time to execute while you’re scheduling / performing more main thread operations. Not always possible due to forced order of execution nature of game logic, but worth keeping in mind when ordering systems in groups.

So - Always group your systems to simplify load balancing later.
E.g. add “Ungroupped” group in SimulationGroup that runs last to simplify order adjustments later on.

But that’s a whole different topic on job load balancing, and it should be done after you’ve completed writing & adjusting logic as part of optimization.

Answering initial question:
For loading, you’d want use something like Begin Frame Simulation Phase to introduce new objects & entities without stalling jobs. For the cleanup you would also want to run logic before any simulation is done.

Pick what makes most sense and works best in terms of performance and game logic.

4 Likes