StateTree state doesn't always exit?

I have a state tree task that works basically the same as Epics FStateTreeMoveToTask example, using an internally allocated and tracked UAITask_MoveTo.

Like FStateTreeMoveToTask, the ExitState function calls ExternalCancel on the move task if it isn’t already finished.

However, I noticed that my AI was doing some weird movement behavior, and on further debugging, I discovered that there were multiple UAITask_MoveTo in the TaskPriorityQueue in UGameplayTasksComponent coming from the state tree

I would not expect this to be possible, as I am not using any parallel move states in this situation, and the ExitState should be cleaning up the last allocated UAITask_MoveTo, as it does in FStateTreeMoveToTask.

After a bit more experimentation and debugging, I noticed that StateCompleted may be called on a state, and ExitState doesn’t, providing an opportunity for the state to “leak” data, in this case the UAITask_MoveTo gameplay task created to do work for the state.

I wanted to ask whether this was to be expected or not. This seems like a bug that even Epic isn’t accounting for in their example.

If this is to be expected, I guess any cleanup code such as this would generally need to be duplicated across StateExit and StateCompleted?

It looks like if the State::Tick fails(return EStateTreeRunStatus::Failed), StateCompleted will be called, and ExitState will not be called for the same state, missing any cleanup you might be expecting to do in the ExitState.

The callstack for the StateCompleted is

FStateTreeTasks_AIMoveTo::StateCompleted(FStateTreeExecutionContext &, EStateTreeRunStatus, const FStateTreeActiveStates &) StateTreeTasks_AIMoveTo.cpp:82 FStateTreeExecutionContext::StateCompleted() StateTreeExecutionContext.cpp:2077 FStateTreeExecutionContext::TickUpdateTasksInternal(const float) StateTreeExecutionContext.cpp:584 FStateTreeExecutionContext::Tick(const float) StateTreeExecutionContext.cpp:526 UStateTreeComponent::TickComponent(float, ELevelTick, FActorComponentTickFunction *) StateTreeComponent.cpp:109 Note: This is with bShouldStateChangeOnReselect=false, I notice that FStateTreeMoveToTask doesn’t disable this option.

We have tasks that only cleanup in ExitState while using bShouldStateChangeOnReselect=false in FN. Looking over code, most places using that do utilize StateCompleted for cleanup, and we use both ExitState and StateCompleted for FMassUseSmartObjectTask.

Are you seeing this happening in the engine MoveTo task as well? I know we are reworking both MoveTo and RunEQS tasks to use the tickless pattern for 5.6, but I have not seen or heard any reports from our FN teams about leaks with the tasks.

-James

I tested this out on 5.6, but I am seeing the behavior of calling ExitState even if the task has bShouldStateChangeOnReselect = false. The ExitTask fires whenever the state leaves the active state hierarchy. So if there are intermediate states using the task, it does not register the ExitState for the transition. For example:

-Root -Claim Resource: <- contains the task that does not fire again on reselect -Move To -Use Resource -Wait Your TurnWhen it transitions from Move To->Use Resource, ExitState does not fire. It does fire when going from Use Resource->Wait Your Turn. It will call to StateComplete if the transitions are using state complete/succeed/failed transitions. If you are using Event transitions, the StateCompleted method is not triggered. There may have been a change that did this as much work has been done for StateTree between 5.5 and 5.6.

For the gameplay task, I will pass this info over to a colleague on the gameplay team. I think the reasoning was to avoid possible perf hits with AddUnique by also using RemoveSwap in UGameplayTasksComponent::OnGameplayTaskDeactivated to remove all instances of the tasks inside of KnownTasks. The comments around it tell me the team is aware of possible duplicates inside the array, but I will check to see if there is more reasoning behind this implementation.

If the built in FStateTreeMoveToTask were to use bShouldStateChangeOnReselect=false, I’m pretty sure it would have the same issue. I can see when trying to make a test project with vanilla 5.4.4, that when FStateTreeMoveToTask completes, from the task tick returning success, StateCompleted is called(but is not implemented on FStateTreeMoveToTask. ExitState is still called on it to do its cleanup, but would not be if Task.bShouldStateChangeOnReselect==false

[Image Removed]

I got rid of bShouldStateChangeOnReselect==false in the aftermath to this issue. It seems like a dangerous option to use, if it can’t ensure ExitState is called as a result of StateCompleted. This hole in coverage, seems outside the scope of what bShouldStateChangeOnReselect is meant to cover.

Related to this task leakage, I also noticed when this issue cropped up, that the UGameplayTasksComponent KnownTasks can accumulate a list of tasks of the same task pointer.

I addressed this like so

[Image Removed]

Our State Tree is pretty big, so It’s difficult to pin down exactly what the circumstance is that manifested the issue.