Why do Decorated Sub Behavior Trees & Level Streaming not play nice?

If you have a behavior tree that calls other behavior trees, and the subtrees have decorators, the decorators are not cleaned up properly on level unload, which results in a crash if/when the level is reloaded.

Here’s a small repro case, created in a clean, blank project.

Create a new level, LevelA, and attach. Create a behavior tree that has a task that invokes another tree, and ensure the subtree has a decorator. Put a single Character Actor on LevelA. Create a new blueprint based on AIController that starts up a behavior tree (Run Behavior Tree on Event BeginPlay). Set the Character to use the new AIController.

Main tree:

61608-behaviortree.png

Sub tree:

61609-subbehaviortree.png

Ensure LevelA is a streaming level. In the main level blueprint, add code to unload/reload LevelA:

Upon the second load of LevelA, observe the following assertion is fired and the runtime is terminated:
Assertion failed: World->PersistentLevel [File:E:\p4\UE4\Engine\Source\Runtime\Engine\Private\LevelStreaming.cpp] [Line: 390]

Additional information from the log:

World exists but PersistentLevel doesn’t for /Game/Levels/UEDPIE_0_LevelA, most likely caused by reference to world of unloaded level and GC setting reference to NULL while keeping world object

Log file attached: [link text][4]

We attempted the following workaround in BehaviorTreeTypes.cpp, in CleanupNodes:

for (int32 DecoratorIndex = 0; DecoratorIndex < ChildInfo.Decorators.Num(); DecoratorIndex++)
{
    ChildInfo.Decorators[DecoratorIndex]->CleanupInSubtree(OwnerComp, ChildInfo.Decorators[DecoratorIndex]->GetNodeMemory<uint8>(*this), CleanupType);
    ChildInfo.Decorators[DecoratorIndex] = nullptr; // add this line
}
ChildInfo.Decorators.Empty(); // add this line

and a change in BTTask_RunBehavior.cpp to add IsValid to this line:

while (NodeIt && NodeIt->GetFName().IsValid() && NodeIt->GetNextNode() != this)

This eliminated the crash, but also affected the decorators, so we know it’s not quite correct.

Thanks for setting up a question regarding this as we ran in to the same issue. I would like to add some more information regarding this:-

  1. The repro steps cause the crash, the only difference is that the decorator has to be in the Sub Tree root node(First node after ROOT).
  2. The workaround mentioned above causes issues with the general use of decorators.
  3. Another workaround is to just add an additional composite node between Sub Tree root node and ROOT. (No engine changes required)

This also leads to the speculation that Injected Decorator Nodes are not cleaned properly. Look at UBTTask_RunBehavior::InjectNodes() and CleanupMemory()