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:
Sub tree:
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.