Fatal Error: Failed to find property AttributeValue

The Situation

I’m currently working on making my framework cross-compatible with Unreal Engine 4 and 5, as well as creating helper macros to make the code easier to work with as time goes on.

Attributes come in two flavors… one of those is a Quantitative one, which can represent a number of any type, so long as it has certain operators. That means that an Integer Attribute, Float Attribute, and Byte Attribute may have the same underlying code - and I mean the EXACT same underlying code. However, because these classes have to work with Blueprints, and Blueprints of course don’t support templates, I have to copy-paste a ton of code between classes just to make them consistent.

Obviously, that’s super inefficient, so I wrote a few handy little macros instead:

ConnAttribute.h in module ConnFramework
//----------------------------------------------------------------------------------------------------------------------
// Easy macros for making QUANTITATIVE attributes.
// This keeps attribute code in one place for easy revisions... otherwise it would be mostly copy-paste.
//
// These don't work with all values, e.g. structs that don't have native operator (==/>=/<=/>/</!=/etc.) overrides.
// To work around this, a recommended method is to create "wrapper structs" that contain the overrides.
// The framework does this for FVector (FConnVector), FRotator (FConnRotator), and FTransform (FConnTransform).
// These overrides can be found in Types/ConnPropWrappers.h under the ConnFramework module (this module)
//----------------------------------------------------------------------------------------------------------------------

/**
 * Helper macro for an attribute, allowing for easy creation of quantitative attribute classes given a type.
 * This is meant to reduce the copy-paste load from the normal attribute classes.
 * This macro contains PUBLIC properties and/or methods, and should be placed in the header (.h or .hpp) file.
 *
 * FuncSizeType: Type to use for function inputs and return values. They must be static_cast convertible to SizeType.
 * In most cases using the same type as SizeType will suffice. This is mostly useful for any types meant to be transparent
 * to the end user. Take FConnVector and FVector as an example. FConnVector contains the operator overrides to make a
 * Vector attribute work, but functions still should be taking or returning FVector for maximum transparency.
 * Note that this override must contain an operator= and constructor to copy FuncSizeType to SizeType.
 */
#define CONN_DECLARE_QUANTITATIVE_ATTRIBUTE_PUBLIC(SizeType, FuncSizeType, UpdateDelegate)								\
	UPROPERTY(Category = "Attribute|Delegates", BlueprintAssignable)													\
	UpdateDelegate OnAttributeUpdated;																					\
	UPROPERTY(Category = "Attribute", EditDefaultsOnly, BlueprintReadWrite,												\
			  Replicated, meta = (EditCondition = "bClampMin", EditConditionHides, DisplayAfter = "bClampMin"))			\
	SizeType MinValue;																									\
	UPROPERTY(Category = "Attribute", EditDefaultsOnly, BlueprintReadWrite,												\
			  Replicated, meta = (EditCondition = "bClampMax", EditConditionHides, DisplayAfter = "bClampMax"))			\
	SizeType MaxValue;																									\
	UFUNCTION(Category = "Attribute|Functions", BlueprintCallable)	void SetValue(const FuncSizeType InValue);			\
	UFUNCTION(Category = "Attribute|Functions", BlueprintPure)	inline FuncSizeType GetValue() const;					\
	UFUNCTION(Category = "Attribute|Functions", BlueprintPure)	inline FuncSizeType GetOldValue() const;

/**
 * Helper macro for an attribute, allowing for easy creation of quantitative attribute classes given a type.
 * This is meant to reduce the copy-paste load from the normal attribute classes.
 * This macro contains PROTECTED properties and/or methods, and should be placed in the header (.h or .hpp) file.
 */
#define CONN_DECLARE_QUANTITATIVE_ATTRIBUTE_PROTECTED(SizeType)															\
	UPROPERTY(Category = "Attribute", EditDefaultsOnly, ReplicatedUsing = OnRep_AttributeValue)							\
	SizeType AttributeValue;

/**
 * Helper macro for an attribute, allowing for easy creation of quantitative attribute classes given a type.
 * This is meant to reduce the copy-paste load from the normal attribute classes.
 * This macro contains PRIVATE properties and/or methods, and should be placed in the header (.h or .hpp) file.
 */
#define CONN_DECLARE_QUANTITATIVE_ATTRIBUTE_PRIVATE(SizeType)															\
	virtual void NativeBeginPlay(AActor* InOuter) override final;														\
	virtual bool CompareAttribute(UConnAttributeBase* Other, const EConnAttributeComparator Operation) const override	\
	final;																												\
	GET_LIFETIME_REPLICATED_PROPS_OVERRIDE																				\
	UFUNCTION()	void OnRep_AttributeValue() const;																		\
	UPROPERTY(Replicated) SizeType AttributeValueOld;

/**
 * Helper macro for an attribute, allowing for easy creation of quantitative attribute classes given a type.
 * This is meant to reduce the copy-paste load from the normal attribute classes.
 * This macro contains the SOURCE CODE for an attribute class, and should be placed in the source (.cpp) file.
 *
 * FuncSizeType: Type to use for function inputs and return values. They must be static_cast convertible to SizeType.
 * In most cases using the same type as SizeType will suffice. This is mostly useful for any types meant to be transparent
 * to the end user. Take FConnVector and FVector as an example. FConnVector contains the operator overrides to make a
 * Vector attribute work, but functions still should be taking or returning FVector for maximum transparency.
 * Note that this override must contain an operator= and constructor to copy FuncSizeType to SizeType.
 */
#define CONN_DEFINE_QUANTITATIVE_ATTRIBUTE(ClassName, SizeType, FuncSizeType, DefaultValue, DefaultMinValue,			\
	DefaultMaxValue)																									\
ClassName::ClassName():																									\
	MinValue(DefaultMinValue),																							\
	MaxValue(DefaultMaxValue),																							\
	AttributeValue(DefaultValue),																						\
	AttributeValueOld(DefaultValue)																						\
{																														\
}																														\
void ClassName::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const							\
{																														\
	Super::GetLifetimeReplicatedProps(OutLifetimeProps);																\
	DOREPLIFETIME(ClassName, AttributeValue);																			\
	DOREPLIFETIME(ClassName, AttributeValueOld);																		\
	DOREPLIFETIME(ClassName, MinValue);																					\
	DOREPLIFETIME(ClassName, MaxValue);																					\
}																														\
void ClassName::OnRep_AttributeValue() const																			\
{																														\
	OnAttributeUpdated.Broadcast(this, AttributeValue, AttributeValueOld);												\
}																														\
void ClassName::NativeBeginPlay(AActor* InOuter)																		\
{																														\
	CHECK_CONN_OBJ_AUTH()																								\
	Super::NativeBeginPlay(InOuter);																					\
	if(bClampMin && bClampMax)																							\
	{																													\
		checkf(MinValue < MaxValue, TEXT("Min and Max overlap in %s"), *GetNameSafe(GetClass()))						\
	}																													\
	SetValue(AttributeValue);																							\
}																														\
bool ClassName::CompareAttribute(UConnAttributeBase* Other, const EConnAttributeComparator Operation) const				\
{																														\
	if(const ClassName* RightHand = Cast<ClassName>(Other))																\
	{																													\
		switch (Operation)																								\
		{																												\
			case EConnAttributeComparator::NotEqual: return GetValue() != RightHand->GetValue();						\
			case EConnAttributeComparator::Equal: return GetValue() == RightHand->GetValue();							\
			case EConnAttributeComparator::GreaterThan: return GetValue() > RightHand->GetValue();						\
			case EConnAttributeComparator::LessThan: return GetValue() < RightHand->GetValue();							\
			case EConnAttributeComparator::GreaterEqual: return GetValue() >= RightHand->GetValue();					\
			case EConnAttributeComparator::LessEqual: return GetValue() <= RightHand->GetValue();						\
			default: BadCase(); return false;																			\
		}																												\
	}																													\
	return false;																										\
}																														\
void ClassName::SetValue(const FuncSizeType InValue)																	\
{																														\
	if(ObjectHasAuthority())																							\
	{																													\
		AttributeValueOld = AttributeValue;																				\
		SizeType NewVal = InValue;																						\
		NewVal = bClampMin && NewVal < MinValue ? MinValue : NewVal;													\
		NewVal = bClampMax && NewVal > MaxValue ? MaxValue : NewVal;													\
		AttributeValue = NewVal;																						\
		OnRep_AttributeValue();																							\
	}																													\
}																														\
FuncSizeType ClassName::GetValue() const																				\
{																														\
	return static_cast<FuncSizeType>(AttributeValue);																	\
}																														\
FuncSizeType ClassName::GetOldValue() const																				\
{																														\
	return static_cast<FuncSizeType>(AttributeValueOld);																\
}

And here, I’ve used the macros to declare a Float Attribute.

ConnAttributeFlt.h in module ConnScript (this module contains "stock" classes packaged with the framework)
// Copyright William Antonio Pimentel-Tonche. All rights reserved.

#pragma once

#include "CFCoreMinimal.h"
#include "Classes/ConnAttribute.h"

#include "ConnAttributeFlt.generated.h"

DECLARE_DYNAMIC_MULTICAST_DELEGATE_ThreeParams
(FConnOnAttributeUpdatedFlt, const UConnAttributeFlt*, Updated, float, NewValue, float, OldValue);

/**
 * A Quantitative Attribute in the form of a float.
 */
UCLASS(Abstract, BlueprintType, Blueprintable, meta = (DisplayName = "[ CF ] Float Attribute"))
class CONNSCRIPT_API UConnAttributeFlt : public UConnAttributeQuantitative
{
	GENERATED_BODY()

public:

	UConnAttributeFlt();

	CONN_DECLARE_QUANTITATIVE_ATTRIBUTE_PUBLIC(float, float, FConnOnAttributeUpdatedFlt)

protected:

	CONN_DECLARE_QUANTITATIVE_ATTRIBUTE_PROTECTED(float)

private:

	CONN_DECLARE_QUANTITATIVE_ATTRIBUTE_PRIVATE(float)

};
ConnAttributeFlt.cpp
// Copyright William Antonio Pimentel-Tonche. All rights reserved.

#include "Attributes/ConnAttributeFlt.h"

#include "Net/UnrealNetwork.h"

CONN_DEFINE_QUANTITATIVE_ATTRIBUTE(UConnAttributeFlt, float, float, 0, 0, 0)

See just how much easier that is?

The Problem

That would be if I wasn’t getting an extremely odd error message when I try to create a Blueprint class derived from UConnAttributeFlt.

Developers are meant to create attributes as classes, which can then be instantiated and deleted as they are applied/removed. Health, for example, would be an attribute. I didn’t want to use structs because developers might want to make attributes of their own, beyond basic int/float types, though I’d be willing to fall back to those if it means I can force these macros to actually work.

Here is the error message and call stack:

Fatal error: [File:C:\UE_4.27\Engine\Source\Runtime\CoreUObject\Public\UObject/UnrealType.h] [Line: 5851] Failed to find Property AttributeValue in Class /Script/ConnScript.ConnAttributeFlt

UE4Editor_ConnScript!FindFieldChecked<FProperty>() [C:\UE_4.27\Engine\Source\Runtime\CoreUObject\Public\UObject\UnrealType.h:5851]
UE4Editor_ConnScript!GetReplicatedProperty() [C:\UE_4.27\Engine\Source\Runtime\Engine\Public\Net\UnrealNetwork.h:189]
UE4Editor_ConnScript!UConnAttributeFlt::GetLifetimeReplicatedProps() [C:\Users\impho\OneDrive\Rapidfire\CF_Port_UE4\Tsunami\Plugins\ConnFramework\Source\ConnScript\Private\Source\Attributes\ConnAttributeFlt.cpp:7]
UE4Editor_CoreUObject
UE4Editor_CoreUObject
UE4Editor_CoreUObject
UE4Editor_KismetCompiler
UE4Editor_Kismet
UE4Editor_Kismet
UE4Editor_Kismet
UE4Editor_Kismet
UE4Editor_UnrealEd
UE4Editor_UnrealEd
UE4Editor_AssetTools
UE4Editor_ContentBrowserAssetDataSource
UE4Editor_ContentBrowserAssetDataSource
UE4Editor_ContentBrowserData
UE4Editor_ContentBrowser
UE4Editor_ContentBrowser
UE4Editor_ContentBrowser
UE4Editor_ContentBrowser
UE4Editor_ContentBrowser
UE4Editor_Slate
UE4Editor_Slate
UE4Editor_Slate
UE4Editor_Slate
UE4Editor_Slate
UE4Editor_Slate
UE4Editor_Slate
UE4Editor_Slate
UE4Editor_Slate
UE4Editor_Slate
UE4Editor_ApplicationCore
UE4Editor_ApplicationCore
UE4Editor_ApplicationCore
UE4Editor_ApplicationCore
user32
user32
UE4Editor_ApplicationCore
UE4Editor
UE4Editor
UE4Editor
UE4Editor
UE4Editor
UE4Editor
kernel32
ntdll

Helpful Pointers

  • When I substitute the macro calls for the code inside (all of them), it seems to stop doing this. I tried such with an Integer Attribute (UConnAttributeInt, using type int32) and it was able to create the class without crashing. Unfortunately this would ruin the whole point of the macros so I would be furious (but have to accept it) if the absolute only solution is to just expand the macros and have to retype them every single time I ever make one single tiny little change to how they work. (Please implement better template support so this kind of thing isn’t necessary! I understand how hard it probably is though…)
  • My guess is this has to do with the replication stuff. I had to add the virtual void overrides because for some reason UHT refused to compile without those having been declared, even though it worked just fine previously.

Any pointers or ideas are appreciated. I know I’m working with a really strange and unusual framework but this kind of awkwardness is something I’m willing to deal with if it means I’ll have a flexible, powerful game framework for my future studio to use for years to come.