Hey! I’m working on an expression based condition system, and I ran into the good old serialization / instance issue related to Unreal’s class default (CDO) system.
Basically I want to have something similar to a TObjectPtr that is nullptr on the CDO, and when the user edits instances of the actor in the editor, only that instance changes without ever affecting the CDO. The issue is, I don’t know where to initialize my pointer (new expression on heap) and also how to handle the “reset to default” because that would make it nullptr instead of whatever default I want.
Here’s my setup.
// Base class for expression to inherit from
// Each expression can contain child expressions.
// They all have Serialize methods that converts them to/from string.
class ACTORIO_API FActorIOExpressionBase : public TSharedFromThis<FActorIOExpressionBase>
{
// Stuff such as Serialize, Evaluate, ToString, FromString, etc..
}
// The struct that stores the expressions in Unreal ecosystem.
// It has detail customization in the editor for editing expressions.
USTRUCT()
struct ACTORIO_API FActorIOScriptCondition
{
GENERATED_BODY()
public:
FActorIOScriptCondition();
// This calls Expr = MakeShared<FActorIOGroupExpression>();
void Initialize();
bool operator==(const FActorIOScriptCondition& Other) const;
bool operator!=(const FActorIOScriptCondition& Other) const { return !(*this == Other); }
bool Serialize(FArchive& Ar);
bool ExportTextItem(FString& ValueStr, FActorIOScriptCondition const& DefaultValue, UObject* Parent, int32 PortFlags, UObject* ExportRootScope) const;
bool ImportTextItem(const TCHAR*& Buffer, int32 PortFlags, UObject* Parent, FOutputDevice* ErrorText);
bool Evaluate(UObject* Executor);
FActorIOGroupExpression* GetExpression() const { return Expr.Get(); }
protected:
// The expression owned by this struct.
// Should be TUniquePtr but had weird PoD type compile issues.
TSharedPtr<FActorIOGroupExpression> Expr;
}
template<>
struct TStructOpsTypeTraits<FActorIOScriptCondition> : public TStructOpsTypeTraitsBase2<FActorIOScriptCondition>
{
enum
{
WithSerializer = true,
WithExportTextItem = true,
WithImportTextItem = true,
WithIdenticalViaEquality = true
};
};
// An actor that wants to store and use condition expressions.
UCLASS()
class ACTORIO_API ALogicCondition : public ALogicActorBase
{
GENERATED_BODY()
public:
// Struct instance. This holds the expressions.
// Question is where to initialize this?
UPROPERTY(EditInstanceOnly)
FActorIOScriptCondition Condition;
}
So far I tried initializing during serialization when loading, but serialization runs a bunch of times so it doesn’t seem like a good fit. Also tried during actor PostLoad and PostActorCreated but every single time when I don’t save my changes and just start the map, the “reset to default” button on the property simply sets the expressions back to whatever the last edited state was. This persists to different maps as well, so I must be modifying the CDO which shouldn’t happen.
Obviously this is a complex question / issue, but unfortunately apart from surface level stuff there aren’t any resources online about this topic and I feel like this isn’t something I can deduct from looking at the engine code.
If somehow you can solve this or give me directions, I’ll buy you a beer!