Big wall of text incoming, thanks in advance to anyone bold enough to venture forth.
https://forums.unrealengine.com/core/image/gif;base64
I have a strange issue with UObject references being nulled out when the owning blueprint class is compiled. The setup is as follows:
A blueprint class, MyCharacter, inherits ACharacter. It adds a number of (custom) native components, one of them being a bit more complex than the others (which are very straight-forward). The complex one has the following basic layout data-wise:
UCLASS(ClassGroup=(Custom), meta=(BlueprintSpawnableComponent))
class UNCHAINED_API ULeetNpcProgramComponent : public UActorComponent
{
GENERATED_BODY()
public:
...]
UPROPERTY(Instanced, EditAnywhere, Category = "LeetNpc")
TArray<UNpcProgramContainer*> Programs;
};
So, an actor component with an array of UObject-derived containers:
UCLASS()
class UNCHAINED_API UNpcProgramContainer : public UObject
{
GENERATED_BODY()
public:
UNpcProgramContainer();
[Other POD properties here]
UPROPERTY(EditAnywhere, Category = "LeetNpc")
UNpcProgram* Program;
UPROPERTY(Instanced, EditAnywhere, Category = "LeetNpc")
ULeetUtilityAction* UtilityAction;
UPROPERTY(Instanced, EditAnywhere, Category = "LeetNpc")
ULeetBlackboard* Blackboard;
UPROPERTY(Instanced, EditAnywhere, Category = "LeetNpc")
TArray<ULeetEventDefinition*> ExecutionEvents;
};
UNpcProgramContainer::UNpcProgramContainer()
{
Program = CreateDefaultSubobject<UNpcProgram>(FName(TEXT("NpcProgram")));
UtilityAction = CreateDefaultSubobject<ULeetUtilityAction>(FName(TEXT("UtilityAction")));
Blackboard = CreateDefaultSubobject<ULeetBlackboard>(FName(TEXT("Blackboard")));
}
It’s these references (Program, UtilityAction and Blackboard; ExecutionEvents is empty) that are nulled out when the blueprint is recompiled. The POD properties all remain intact. I’ve tried with and without Instanced - I don’t really need that property except for ease of validating data through the editor detail view. (The normal use case is to view and manipulate all this data through a custom editor.) All objects are created with their logical parent as Outer, i.e. A creates and references B, B is created with A as its Outer. My thinking is that all these objects will be stored with their respective parents, ultimately leading to an in-world actor and thus persistent storage in the map. Saving and loading the entire object hierarchy to/from disk works fine.
I’ve played around with using other objects as Outer, e.g. having all objects live with the component’s Outer. This leads to hard crashes on blueprint recompile, typically when the object is reconstructed. It can also lead to hard crashes in serialization during a transactional snapshot (Modify() called on e.g. Container object).
In general, I get the feeling that I’m doing something that causes UE4 to be unable to properly reflect the object hierarchy under certain conditions, such as blueprint compilation. I’ve also had instances where seemingly unrelated data can’t be edited without a crash, such as a blueprint variable or a property on an unrelated component. I’ve only worked with UE4 properly a couple of months, so it may well be that I have something very basic wrong.
Finally, it seems that these problems go away, or are at least harder to reproduce, if the blueprint class instead derives a custom native class that creates the offending component (ULeetNpcProgramComponent).
For completeness sake, the referenced objects are laid out as follows: (It should be noted I’ve had these issues even without the references to UtilityAction and Blackboard)
Program:
UCLASS()
class UNCHAINED_API UNpcProgram : public UObject
{
GENERATED_BODY()
public:
UNpcProgram();
UPROPERTY(EditAnywhere, Category = "LeetNpc")
TArray<FNpcProgramEntry> Entries;
};
The Entries array is populated from a custom editor.
UtilityAction:
UCLASS()
class UNCHAINED_API ULeetUtilityAction : public UObject
{
GENERATED_BODY()
public:
UPROPERTY(EditAnywhere, Category = "LeetUtilityAi")
TArray<FConsiderationInstanceData> Considerations;
};
Where FConsiderationInstanceData is:
USTRUCT()
struct FConsiderationInstanceData
{
GENERATED_BODY()
public:
UPROPERTY(EditAnywhere, Category="LeetUtilityAi")
TSubclassOf<ULeetUtilityConsideration> ConsiderationClass;
UPROPERTY(EditAnywhere, Category="LeetUtilityAi")
ULeetUtilityConsideration* Consideration = nullptr;
UPROPERTY(EditAnywhere, Category = "LeetUtilityAi")
ULeetUtilityConsiderationCurve* Curve = nullptr;
};
Where ULeetUtilityConsideration is:
UCLASS(Blueprintable, EditInlineNew, meta=(DisplayName="Base Consideration"))
class UNCHAINED_API ULeetUtilityConsideration : public UObject
{
GENERATED_BODY()
public:
ULeetUtilityConsideration();
UFUNCTION(BlueprintNativeEvent, Category = "LeetUtilityAi")
float Evaluate(ULeetBlackboard* Ctx);
UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "LeetUtilityAi")
float Bonus = 0.0f;
UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "LeetUtilityAi")
float Multiplier = 1.0f;
UPROPERTY(Instanced, BlueprintReadWrite, EditAnywhere, Category = "LeetUtilityAi")
ULeetUtilityConsiderationCurve* DefaultCurve = nullptr;
protected:
virtual float Evaluate_Implementation(ULeetBlackboard*);
};
And LeetUtilityConsiderationCurve is:
UCLASS()
class UNCHAINED_API ULeetUtilityConsiderationCurve : public UObject
{
GENERATED_BODY()
public:
UPROPERTY(EditAnywhere, Category = "Utility Curve")
FRuntimeFloatCurve Curve;
};
The considerations are created from a custom editor.
Blackboard:
UCLASS(BlueprintType)
class UNCHAINED_API ULeetBlackboard : public UObject
{
GENERATED_BODY()
public:
[Functions here]
private:
UPROPERTY()
TMap<FName, int32> m_DataIndex;
UPROPERTY()
TArray<uint8> m_Data;
UPROPERTY()
TArray<UObject*> m_Objects;
};
The blackboard is only used at run-time so all blackboard containers are empty at the point the issue occurs.
Thanks for taking the time to read through this!
https://forums.unrealengine.com/core/image/gif;base64