StateTrees on jobs

Hi! I was looking forward to 5.6 to be able to run state trees on jobs. However, I have trouble finding how to do it.

I saw the recent Witcher 4 tech demo presentations on Unreal Fest and they claimed their StateTrees (probably even called through SmartObject slots) were running on jobs in parallel. They claimed they used the Mass framework.

I saw the new StateTreeAsyncExecutionContext.h/cpp with Weak and Strong contexts and my StateTrees are using those when I debug them. But everything is still running on Game thread, even when I use Mass framework. I also haven’t found any setting in editor that would cause the trees execute on jobs.

So I assume 5.6 brought only core support for StateTrees on jobs, but I have to implement it at least partially myself. Am I correct? Is there any documentation about that or some hints to what code pieces are necessary for that? I could try searching deeper in the code, but without at least some general knowledge about how it is supposed to work, that seems like a lot of unnecessarily wasted time with discutable outcome.

Also what are the limitations? Is it possible to run blueprint state tree tasks in parallel, if I make sure I use only thread safe nodes? From what I read in some code comments, can I execute only read-only state trees that guard / search / listen to something on jobs, but to start some movement / animation I have to schedule it on Game thread?

I would appreciate at least some general direction about what is necessary and where to start looking. If there is some documentation / guide I haven’t found or some is currently being made, that would be perfect.

You need to use the Mass Framework to run the state tree on jobs, but you won’t be able to access UObject. To access the UObject, the state tree has to run on the game threads.

You cannot run Blueprint tasks in parallel.

The WeakContext and the StrongContext are used when your task triggers an async task or a job. That job can’t access the state tree data (threading issue). It has to copy the needed data first. The job also needs a WeakContext, created from the regular context. Once the job is completed, it can inform the state tree via a StrongContext (constructed from the WeakContext). That communication is not thread-safe. You are responsible for making it thread-safe. The StrongContext can update the task’s instance data and finish it.

To achieve Witcher 4 tech demo quality.

  1. Tasks should not be ticked. In the enter state, trigger an async/job. And completes the task once the async/job is completed. See the “Scheduled Tick” feature.
  2. AI should have LOD. Only some should AI use the Blueprint (the more important one). The other AI should use the Mass Framework.

I should have been clearer about UObject access. Sorry.

Because of the Garbage Collector, UObject life cycle is bound to the game thread. Because of Blueprint, Blueprintable objects are bound to the game thread.

Mass doesn’t use Blueprintable object. It can run on jobs. Mass do cleaver manipulation to prevent the UStateTree from being garbage collected. StateTree can be accessed read-only from jobs.

StateTree that use the UStateTreeComponent are meant to be used with Blueprintable Object. Like you said, with UStateTreeComponent, the state tree execution context is actually always running on the game thread, but its tasks can run asynchronously. BUT from a job, you can access the StateTree object with the help of the StrongExecutionContext. The access is not thread-safe. It only guarantees that the data that you are accessing is valid or not. For example, the StateTree is not garbage collected, or the state tree execution didn’t stop. You will need to guard from 2 jobs that access the same data in write mode, and you need to guard from a job that accesses the data and FStateTreeExecutionContext::Tick/Stop.

When using UStateTreeComponent, it is easier to write your job with copied data, and when the job is completed, push a task on the game thread to write the data back. In some cases, it is faster and slower in others … it depends on what you are doing and how often you are doing it. It is safer (no need to write lock code), but when doing a lot of them, it adds an overhead. Mutexes are slow if you need them once in a while.

`void FMyTask::EnterState()
{

MultiThreadedAsyncJob.Start(MyData, WeakContext = Context.MakeWeakExecutionContext());
// The task is running, it will completes later.
return EStateTreeRunStatus::Running;
}

FMultiThreadedAsyncJob::Start()
{
// Manipulate MyData on another thread

// push a task on the game thread
ExecuteOnGameThread(UE_SOURCE_LOCATION, NewData=MoveTemp(MyData), WeakContext
{
// Will lock all the pointer to prevents Garbage Collection.
TStateTreeStrongExecutionContext StrongContext = WeakContext.CreateStrongContext();
// Access the instance data. Test the pointer, it can be invalid if the task doesn’t exist anymore (the tree stopped or the state changed).
FMyTask::InstanceData* InstanceData = StrongContext.GetInstanceDataPtrFMyTask::InstanceData();
if (InstanceData)
{
InstanceData->Toto = NewData.Toto;
// Finish the task. Similar to when you return Success in FMyTask::Tick
//It will make sure the state tree to tick when it can. It can be this frame or the next frame. Depending when in the frame loop this triggers and if it was already in the scheduler.
StrongContext.FinishTask(EStateTreeFinishTaskType::Successed);
}
}`

Thank you, that’s exactly what I was interested in and mostly what I was expecting.

Just to be clear, when you are talking about not being able to access “the UObject” you mean the base class of the StateTree itself?

So the StateTree itself is actually always running on game thread, but its tasks can be run asynchronously? And from them I can access the contexts but not the StateTree UObject?

I also don’t expect to be able to access any other UObjects, that makes sense.

Awesome, thank you! This is more than enough for me to play with it.

You cannot access any other UObjects safely. You need to handle it yourself.

You should not access UStateTree directly (from jobs). Use the accessors in FStateTreeContextExecution.