UE Package Error:ObservedKeyNames.Num()>0

Recently, I encountered a very strange error during the project packaging:

1
2
// package log
Ensure condition failed: ObservedKeyNames.Num() > 0 [File:D:\Build\++UE4+Release-4.18+Compile\Sync\Engine\Source\Runtime\AIModule\Private\BehaviorTree\Decorators\BTDecorator_BlueprintBase.cpp] [Line: 67]


I debugged the UE4 code and analyzed the cause and solution process.

First, let’s look at the source code location of the error:

1
2
3
4
5
6
7
8
9
10
11
12
13
// Runtime\AIModule\Private\BehaviorTree\Decorators\BTDecorator_BlueprintBase.cpp [Line: 67]
void UBTDecorator_BlueprintBase::PostLoad()
{
Super::PostLoad();

if (GetFlowAbortMode() != EBTFlowAbortMode::None && bIsObservingBB)
{
ObservedKeyNames.Reset();
UClass* StopAtClass = UBTDecorator_BlueprintBase::StaticClass();
BlueprintNodeHelpers::CollectBlackboardSelectors(this, StopAtClass, ObservedKeyNames);
ensure(ObservedKeyNames.Num() > 0);
}
}

The conditions for entering ensure(ObservedKeyNames.Num() > 0); are:

  1. GetFlowAbortMode() != EBTFlowAbortMode::None; GetFlowAbortMode() retrieves the Observe abort value from the FlowControl category of the BehaviorTree Decorator node.

  2. bIsObservingBB is true; the default value of bIsObservingBB is false, which is later obtained through UBTDecorator_BlueprintBase::PostInitProperties()->UBTDecorator_BlueprintBase::InitializeProperties();.

1
2
3
4
5
6
7
8
9
10
11
// Runtime\AIModule\Private\BehaviorTree\Decorators\BTDecorator_BlueprintBase.cpp
void UBTDecorator_BlueprintBase::InitializeProperties()
{
if (HasAnyFlags(RF_ClassDefaultObject))
{
UClass* StopAtClass = UBTDecorator_BlueprintBase::StaticClass();
BlueprintNodeHelpers::CollectPropertyData(this, StopAtClass, PropertyData);

bIsObservingBB = BlueprintNodeHelpers::HasAnyBlackboardSelectors(this, StopAtClass);
}
}

This function is used to determine whether the current Decorator has FBlackboardSelectors properties by calling BlueprintNodeHelpers::HasAnyBlackboardSelectors.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
// Runtime\AIModule\Private\BehaviorTree\BlueprintNodeHelpers.cpp
bool BlueprintNodeHelpers::HasAnyBlackboardSelectors(const UObject* Ob, const UClass* StopAtClass)
{
bool bResult = false;

for (UProperty* TestProperty = Ob->GetClass()->PropertyLink; TestProperty; TestProperty = TestProperty->PropertyLinkNext)
{
// stop when reaching base class
if (TestProperty->GetOuter() == StopAtClass)
{
break;
}

// skip properties without any setup data
if (TestProperty->HasAnyPropertyFlags(CPF_Transient) ||
TestProperty->HasAnyPropertyFlags(CPF_DisableEditOnInstance))
{
continue;
}

const UStructProperty* StructProp = Cast<const UStructProperty>(TestProperty);
if (StructProp && StructProp->GetCPPType(NULL, CPPF_None).Contains(GET_STRUCT_NAME_CHECKED(FBlackboardKeySelector)))
{
bResult = true;
break;
}
}

return bResult;
}

In other words: If the Decorator contains an FBlackboardKeySelector property, bIsObservingBB will be true.

Thus, processing within the condition is straightforward:

1
2
3
4
5
6
7
8
9
10
11
12
13
// Runtime\AIModule\Private\BehaviorTree\Decorators\BTDecorator_BlueprintBase.cpp [Line: 67]
void UBTDecorator_BlueprintBase::PostLoad()
{
Super::PostLoad();

if (GetFlowAbortMode() != EBTFlowAbortMode::None && bIsObservingBB)
{
ObservedKeyNames.Reset();
UClass* StopAtClass = UBTDecorator_BlueprintBase::StaticClass();
BlueprintNodeHelpers::CollectBlackboardSelectors(this, StopAtClass, ObservedKeyNames);
ensure(ObservedKeyNames.Num() > 0);
}
}

It calls BlueprintNodeHelpers::CollectBlackboardSelectors to obtain the list of all FBlackboardKeySelector names from the current Decorator object.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// Runtime\AIModule\Private\BehaviorTree\BlueprintNodeHelpers.cpp
void BlueprintNodeHelpers::CollectBlackboardSelectors(const UObject* Ob, const UClass* StopAtClass, TArray<FName>& KeyNames)
{
for (UProperty* TestProperty = Ob->GetClass()->PropertyLink; TestProperty; TestProperty = TestProperty->PropertyLinkNext)
{
// stop when reaching base class
if (TestProperty->GetOuter() == StopAtClass)
{
break;
}

// skip properties without any setup data
if (TestProperty->HasAnyPropertyFlags(CPF_Transient) ||
TestProperty->HasAnyPropertyFlags(CPF_DisableEditOnInstance))
{
continue;
}

const UStructProperty* StructProp = Cast<const UStructProperty>(TestProperty);
if (StructProp && StructProp->GetCPPType(NULL, CPPF_None).Contains(GET_STRUCT_NAME_CHECKED(FBlackboardKeySelector)))
{
const FBlackboardKeySelector* PropData = TestProperty->ContainerPtrToValuePtr<FBlackboardKeySelector>(Ob);
KeyNames.AddUnique(PropData->SelectedKeyName);
}
}
}

Since above bIsObservingBB is true, it is expected that a non-empty list can be obtained here, meaning ObservedKeyNames.Num() is certainly >0. If not, it will trigger an assertion. I initially guessed that this issue arose due to inconsistencies between property acquisitions in UBTDecorator_BlueprintBase::InitializeProperties and UBTDecorator_BlueprintBase::PostLoad, as I found that bIsObservingBB was only set in UBTDecorator_BlueprintBase::InitializeProperties.

Thus, I created a function library to mimic BlueprintNodeHelpers::CollectBlackboardSelectors, exposing it to Blueprints for detection, which checks whether the passed Decorator has a BlackboardKey:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// AIModuleFlib.h
// Fill out your copyright notice in the Description page of Project Settings.

#pragma once

#include "CoreMinimal.h"
#include "Kismet/KismetSystemLibrary.h"
#include "Kismet/KismetStringLibrary.h"
#include "Kismet/BlueprintFunctionLibrary.h"
#include "BehaviorTree/Decorators/BTDecorator_BlueprintBase.h"

#include "AIModuleFlib.generated.h"

UCLASS()
class UAIModuleFlib : public UBlueprintFunctionLibrary
{
GENERATED_BODY()

public:
UFUNCTION(BlueprintCallable)
static TArray<FName> Z_CollectBlackboardSelectors(UObject* Ob, bool& result);

};

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
// AIModuleFlib.cpp
// Fill out your copyright notice in the Description page of Project Settings.

#include "AIModuleFlib.h"
#include "AIController.h"
#include "BehaviorTree/BlackboardComponent.h"
#include "BehaviorTree/BTFunctionLibrary.h"
#include "BlueprintNodeHelpers.h"
#include "BehaviorTree/BehaviorTree.h"
#include "BehaviorTree/BehaviorTreeTypes.h"

#define GET_STRUCT_NAME_CHECKED(StructName) \
((void)sizeof(StructName), TEXT(#StructName))


TArray<FName> UAIModuleFlib::Z_CollectBlackboardSelectors(UObject* Ob, bool& result)
{
result=false;
TArray<FName> KeyNames;
KeyNames.Empty();
for (UProperty* TestProperty = Ob->GetClass()->PropertyLink; TestProperty; TestProperty = TestProperty->PropertyLinkNext)
{
// skip properties without any setup data
if (TestProperty->HasAnyPropertyFlags(CPF_Transient) ||
TestProperty->HasAnyPropertyFlags(CPF_DisableEditOnInstance))
{
continue;
}

const UStructProperty* StructProp = Cast<const UStructProperty>(TestProperty);
if (StructProp && StructProp->GetCPPType(NULL, CPPF_None).Contains(GET_STRUCT_NAME_CHECKED(FBlackboardKeySelector)))
{
const FBlackboardKeySelector* PropData = TestProperty->ContainerPtrToValuePtr<FBlackboardKeySelector>(Ob);
KeyNames.AddUnique(PropData->SelectedKeyName);
}
}

result=KeyNames.Num()>0;

return KeyNames;
}

I tested our Decorator in Blueprint (taking BTD_Check_AttackTargetDistance.uasset as an example, which is also the problematic Decorator that fails to package):

However, in this test, I found that our Decorator did not contain a BlackboardKey, which was quite strange, as I found that the variable bIsObservingBB was only set in UBTDecorator_BlueprintBase::InitializeProperties, so it must have been set somewhere else.

Knowing this problem, I created a blank project, added a selector or sequence into an empty BehaviorTree, attached the BTD_Check_AttackTargetDistance.uasset Decorator, and designated a Pawn to use this behavior tree.

Then, I started debugging with breakpoints from engine startup (you can use this method for VS debugging: UE4 and VR Development Tech Notes #VS debugging standalone UE projects).
Ultimately, I found that this Decorator had a problem during serialization:
The call stack is as follows:

Data obtained:

It can be seen that the serialized property bIsObservingBB was set to true.

I suspect that this Decorator‘s uasset resource in Blueprint is problematic. We completely recreated the Decorator from BehaviorTree and copied the existing logic, and after replacing it in the behavior tree, the packaging was successful.
However, I am still unclear about the reason for the inconsistent serialized properties (I suspect that there was an issue with the serialized properties in the uasset), so the solution to this problem is simply to create a new Decorator and copy its implementation.
Those interested in analyzing this problem can download this Decorator (BTD_Check_AttackTargetDistance.uasset) for debugging.

Moreover, UE’s Blueprints are also resources; while convenient, similar serialization issues can be quite frustrating.

Additionally, UE’s serialization is designed to implement both serialization and deserialization through the same interface:

1
2
3
4
5
6
7
8
9
10
11
// Serializer.
FArchive& operator<<( FArchive& Ar, FPropertyTag& Tag )
{
// Name.
Ar << Tag.Name;
if ((Tag.Name == NAME_None) || !Tag.Name.IsValid())
{
return Ar;
}
// ...
}

It follows the identical logic, while the difference lies in the type of FArchive being either Loading or Writing, which can be determined through the FArchive‘s IsLoading interface:

1
bool FArchive::IsLoading() const{}
The article is finished. If you have any questions, please comment and communicate.

Scan the QR code on WeChat and follow me.

Title:UE Package Error:ObservedKeyNames.Num()>0
Author:LIPENGZHA
Publish Date:2019/01/17 09:50
Word Count:3.4k Words
Link:https://en.imzlp.com/posts/359/
License: CC BY-NC-SA 4.0
Reprinting of the full article is prohibited.
Your donation will encourage me to keep creating!