Behavior Tree, Decorator and Level Streaming crash

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:
16f20c0418119542c5e58d56c37e70e3079de7f7.png
Sub tree:
fe6c36dea8a6037b1f12776892e105a3766a0aa7.png

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)

[=]
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:

BehaviorTree.PNG&stc=1

Sub tree:

SubBehaviorTree.PNG&stc=1

LevelA, a streaming level, is loaded, then unloaded, then reloaded using the U and L keys:

&stc=1

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)

[/]

Hi ,

Please post this to the answerhub in the bug reports section (link in signature) so we can assist you more in depth. In your answerhub post, please include what steps you are taking to reproduce this on your end as well as crash logs, callstack, and dxdiag. You can get your crash logs for a crash by going to \Unreal Projects\PROJECTNAME\saved\logs. Additionally, please let us know if you have attempted this in a clean, blank project with no additional content and whether this reproduces there or if it is limited to an individual project.

Posted atanswers.unrealengine.com/questions/314367/why-do-decorated-sub-behavior-trees-level-streamin.html