Mass processing queue vs game thread

Hello,

recently a massive change to mass processing queue was submitted - it no longer blocks main thread. I have originally disliked the idea of mass blocking the main thread, but have come since to rely on this feature. Many parts of our project still run on game thread (and probably will for a long time. Maybe one day, years from now, we will move most code to mass. Hopefully), and this code needs synchronous access to mass data.

A simple way to ensure safety of this access was the main-thread-blocking execution mode. If the game thread would be under-utilized, it is possible to manually schedule some main-thread updates to this time block (by creating a main-thread processor that has no query, and just runs some external code) - this allows to run mass-safe code (code that doesn’t interact with any mass data - e.g. weather). I don’t know if this model is viable for an actual game, haven’t tested it. It’s my current working theory how our project would be doable.

The immediate issue right now, since this change, is that some Ticks just access mass, and now they randomly run during mass processing, which asserts everywhere and sometimes crashes. On 5.8 release branch (changelist 55477932), there is a modification that forces the main-thread blocking mode in editor (which is, in my opinion, a horrible idea - the difference between the two modes is quite significant for it to be by-default different behavior between game and editor), but that requires creation of dummy events. Since it’s not on main, I am assuming it’s some hotfix. Is there an intended way to run the processing queue in the old main-thread blocking method?

If not, how do you, in your projects, ensure that main thread can access mass data safely? Or is that a use case you never encounter? I frequently use OnProcessingPhaseStarted/Ended callbacks. However, we still have blueprint code running some parts of the game logic (typically the parts we are prototyping), and a lot of them tick (e.g. enhanced player input).

[Attachment Removed]

Have you all pulled in any of the changes for Mass that are in the UE6 Main stream? The team had been working with 5.8 as the last release, so there were some refactors and modules used that are not being included with 5.9. There are a good number of fixes and changes around the processing queue and IsProcessing. Trying to make sure I share the best, most-relevant information and not give you things that you won’t find in UE5. We do have a repo and possible patch file (file may be slightly out-of-date) for bringing UE6 Mass into UE5 if the team would want to fully utilize the processing queue.

-James

[Attachment Removed]

You are correct with how it currently behaves. On UE5 main, a phase returns to the game thread once its game-thread processors finish. The worker processors, the command flush and OnPhaseEnd complete later, in a game-thread cleanup task. While the tick manager waits on that, it keeps servicing other game-thread tasks, so unrelated ticks in the same tick group can run mid-phase while IsProcessing() is still true. That is where your asserts come from.

UE5 main has no supported switch back to blocking for game worlds, and mass.UseProcessingQueue=0 does not restore it either. What works today:

  1. Add a tick prerequisite on the phase tick function for C++ ticks that read Mass data. The dependent tick then runs after the command flush and OnPhaseEnd. Note that it gets demoted to the later of the two tick groups.
  2. Move Blueprint ticks to TG_PostUpdateWork, which hosts no phase. Do not rely on TG_EndPhysics, because the DuringPhysics phase can still be running then.
  3. For global blocking while debugging or prototyping, set mass.FullyParallel=0. Phases then run synchronously on the game thread, but processors no longer run in parallel.

Blocking has since returned as an opt-in in //UE6/Main. The code has moved (MassEntity->MassCore), so the CLs will not apply cleanly, but the logic is small to port:

  • CL 56759405 adds FMassProcessingPhaseManager::SetBlockMainThreadDuringProcessing. Game and PIE worlds stay non-blocking unless you call it.
  • CL 58513306 makes the blocking path return an already-completed event, so OnPhaseEnd fires once and no dummy events are needed.
  • CL 56897355 (optional) adds mass.debug.DetectExternalFragmentAccess, which logs every callstack that touches fragment memory from outside Mass mid-phase. It is useful for finding the ticks to fix.

With the first two ported, opt in from the game thread once the simulation subsystem exists with something like this: SimulationSubsystem->GetMutablePhaseManager().SetBlockMainThreadDuringProcessing(true); It requires mass.UseProcessingQueue=1 and mass.FullyParallel=1. The wait does not pump game-thread tasks, so a processor waiting on a game-thread task will hang, and the game thread stalls for every phase. Also note that 5.8 PIE was always blocking, so behaviour validated there does not carry over to game worlds. For your own code, check IsProcessing() and route changes through Defer() when it is true. Your OnProcessingPhaseStarted/Finished callbacks are safe windows.

We have a repo that has the changes needed to run UE6 Mass in UE5, and it is what I would recommend for anyone using Mass for large parts of their project. It requires you to login to an account that has access to the Unreal Engine GitHub (you may get a 404/403 error when trying if you are not logged in). It can be found here: https://github.com/jaredschibel-epic/UnrealEngine/tree/mass-ue6-integration. I can also try to get a patch file to share what is needed as well. We are trying to add in things for hotfixes where it meets criteria, but unfortunately most of our code has drifted with the refactor that it is not possible to port everything back to UE5 easily.

-James

[Attachment Removed]

My apologies! Those are the CLs from the FN stream which were in the relevant JIRAs. Here are the correct CL numbers for UE6 main:

  • CL 56759424 - blocks main thread during processing
  • CL 58514906 - blocking path returns a completed event
  • CL 56897369 - external fragment access CVar for seeing any places attempting access while Mass is processing

I know you found the relevant ones (excellent work btw!) but this is for those who come after.

-James

[Attachment Removed]

We are currently on 54933501 (the one ue6 is branched from), as we have not yet decided whether to switch to ue6 or stay on ue5. We do however have cherry-picked 55107447, because we had the deadlock appearing quite frequently - that is the change that causes processing queue to run in non blocking mode.

We don’t have any UE6 changes, nor do we have any changes from ue5/release-5.8

[Attachment Removed]

I have merged the two changelists (I cannot access the changelists you provided, but I think i found the corresponding changes on ue6/main), and one more changelist (that one moved bBlockMainThread to Execute_GetEventRef’s parameter list, instead of class member) , and then the blocking behavior seems to work fine.

Thank you, Martin

[Attachment Removed]