Engine version 4.9. 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 the level is reloaded.
Here’s a small repro case.
Put a single Character Actor on LevelA, with an AIController that starts up a behavior tree (Run Behavior Tree on Event BeginPlay). The behavior tree has a task that invokes another tree, and the subtree has a decorator.
Main tree:
Sub tree:
LevelA, a streaming level, is loaded, then unloaded, then reloaded using the U and L keys:
Upon the second load, 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
External Referencers:
: (root) UnrealEdEngine /Engine/Transient.UnrealEdEngine_0->PlayWorld
: World /Game/StarterContent/Maps/UEDPIE_0_Minimal_Default.Minimal_Default->AISystem
: AISystem /Game/StarterContent/Maps/UEDPIE_0_Minimal_Default.Minimal_Default:AISystem_5->BehaviorTreeManager
: BehaviorTreeManager /Game/StarterContent/Maps/UEDPIE_0_Minimal_Default.Minimal_Default:BehaviorTreeManager_5->LoadedTemplates
: BTComposite_Sequence /Game/StarterContent/Maps/UEDPIE_0_Minimal_Default.Minimal_Default:BehaviorTreeManager_5.BTComposite_Sequence_0->Children
: BTDecorator_ForceSuccess /Game/Levels/UEDPIE_0_LevelA.LevelA:PersistentLevel.CharAIController_C_2.BTComponent.BTDecorator_ForceSuccess_4->Outer
: (PendingKill) BehaviorTreeComponent /Game/Levels/UEDPIE_0_LevelA.LevelA:PersistentLevel.CharAIController_C_2.BTComponent->Outer
: (PendingKill) CharAIController_C /Game/Levels/UEDPIE_0_LevelA.LevelA:PersistentLevel.CharAIController_C_2->Outer
: (PendingKill) Level /Game/Levels/UEDPIE_0_LevelA.LevelA:PersistentLevel->Outer
: (target) World /Game/Levels/UEDPIE_0_LevelA.LevelA
Here is the workaround we added to the engine.
In BehaviorTreeTypes.cpp, change the relevant section of CleanupNodes to:
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**
In BTTask_RunBehavior.cpp, change the line “while (NodeIt && NodeIt->GetNextNode() != this)” to
while (NodeIt && NodeIt->GetFName().IsValid() && NodeIt->GetNextNode() != this)