How can a static function return a pointer?

Sorry in advance for this dumb question…

I want to have a static structure which persists for the whole lifetime of a unit class. This structure is a data lookup which references an imported CSV file. Each record in the CSV file contains a unique integer which corresponds to a record. This data is loaded and never changed when the game begins.

I want to be able to access any of those data records at run time.

I have written a static function which accepts an integer and corresponds to the record ID. It should grab the appropriate record and return a pointer or something to a data row object. This is what I’m having a bit of difficulty with. How do you create a static function which returns a pointer or access to a data row object?

Header file:



/*This is a collection of data rows for each unit. This is loaded from a CSV file, which allows us to avoid creating a blueprint for each
unit type in the game. To add a new unit, we just create a new row in the data sheet :)*/
USTRUCT(BlueprintType)
struct FUnitData : public FTableRowBase
{
	GENERATED_USTRUCT_BODY()

public:
	//Name is included by base class

	//UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "UnitData")
	//	int32 UnitID;

	//The name of the unit
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "UnitData")
		FString UnitName;

	//The creature which composes this unit
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "UnitData")
		int32 CreatureDataID;

	/*How much gold this unit costs to maintain.*/
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "UnitData")
		int32 GoldUpkeep;

	/*This is how many creatures get spawned when we initialize the unit*/
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "UnitData")
		int32 Quantity;

	/*How much gold this unit costs to recruit.*/
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "UnitData")
		int32 GoldCost;

	/*How many recruits are required to form this unit*/
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "UnitData")
		int32 RecruitCost;

	/*This is a lookup into the abilities table to see what abilities the creature starts off with.*/
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "UnitData")
		int32 AbilityID;
};

UCLASS()
class MAGEMASTER2_API AUnit : public AActor
{
//...snip....
//maintain this so that we can use it for look ups when the unit blueprint is created
	static UDataTable* UnitLookupTable;

/*Fetches a unit data row with the given unit ID*/
	UFUNCTION(BlueprintCallable, Category = "Unit")
		static FUnitData* GetUnitData(int32 UnitID);
//...snip....
};



CPP implementation:



FUnitData* AUnit::GetUnitData(int32 UnitID)
{
	FUnitData* result = UnitLookupTable->FindRow<FUnitData>(*FString::FromInt(UnitID), TEXT(""));
	return result;
}


Error on return value in CPP file: Inappropriate ‘*’ on variable of type ‘FUnitData’, cannot have an exposed pointer to this type.

The header tool isn’t complaining about the static part, it’s complaining about using a pointer to a UStruct in a managed context. The engine does not support UStruct pointers tagged in UFUNCTIONs or UPROPERTYs, so there is no underlying functionality to generate the appropriate Blueprint pins, etc. You could return a reference to a FUnitData instead, but your function would have to be guaranteed to return a FUnitData at all times, which would not be the case if you somehow provide an invalid unit ID that causes the data table lookup to fail.

I would suggest changing your function to something like this:


UFUNCTION(BlueprintCallable, Category = "Unit")
static bool GetUnitData(int32 UnitID, FUnitData& OutUnitData);

If the table lookup fails, return false without touching OutUnitData. Otherwise, assign the obtained unit data to OutUnitData. The caller will obviously have to check that the function returned true before trying to use the unit data.

The other way of doing it would be to return a FUnitData by value instead of by pointer or reference. You would still need some way of flagging FUnitData as invalid, maybe by setting a UnitID of INDEX_NONE. If you plan to use this function mainly in blueprint, however, this approach will be less practical than the bool return value validation.

On a side note: Storing a UDataTable in an unmanaged static pointer like this is risky. If the table asset isn’t referred anywhere else in code through a tagged UPROPERTY, then you are not holding any references to the table and it will be garbage collected from under you. Also, being an unmanaged pointer, the cooking process won’t gather the data table, leading to it being excluded from cooked builds. The “proper Unreal Way” of handling something like this would be to move the table somewhere where it can be shared across all units as if static, i.e.: GameMode or GameInstance.

Oh, these are really good ideas! I should have thought of them myself, but it didn’t occur to me. I followed your suggestions and put this info within the game instance class and it’s working out great.