UPrimaryDataAssets do NOT load or cast properly?

Create a new, blank, C++ project, with starter content.

Create a custom PrimaryDataAsset class:

UTESTPrimaryDataAsset.h -

#pragma once

#include "CoreMinimal.h"

#include "Engine/DataAsset.h"
#include "Engine/DataTable.h"

#include "TESTPrimaryDataAsset.generated.h"


/**
*
*/
UCLASS(Blueprintable, BlueprintType)
class PRIMARYASSETTEST_API UTESTPrimaryDataAsset : public UPrimaryDataAsset
{
	GENERATED_BODY()
    
public:

	UPROPERTY(EditDefaultsOnly, BlueprintReadWrite, Category = "Asset", AssetRegistrySearchable)
	FName ID;

	UPROPERTY(EditDefaultsOnly, BlueprintReadWrite, Category = "Assets", AssetRegistrySearchable, meta = (AssetBundles = "Table", MetaClass = "UDataTable"))
	TSoftObjectPtr<UDataTable> AssetTable;


	UTESTPrimaryDataAsset(const FObjectInitializer& ObjectInitializer = FObjectInitializer::Get());


	/**
	* -----------------------------------
	* Methods
	*
	*/

	/** PackageDataAssets use the root UTESTPrimaryDataAsset class name and their (globally) unique package id as their primary asset id */
	inline static FName PrimaryAssetType() {
		return UTESTPrimaryDataAsset::StaticClass()->GetFName();
	}

	/** PackageDataAssets use the root UCOREPackageDataAsset class name and their (globally) unique package id as their primary asset id */
	inline static FPrimaryAssetId PrimaryAssetId(FName TargetPackageID) {
		return FPrimaryAssetId(UTESTPrimaryDataAsset::PrimaryAssetType(), TargetPackageID);
	}
		


	/**
	* -----------------------------------
	* UPrimaryDataAsset overrides
	*
	*/
	
	/** PackageDataAssets use the root UCOREPackageDataAsset class name and their (globally) unique package id as their primary asset id */
	FPrimaryAssetId GetPrimaryAssetId() const override;

};

UTESTPrimaryDataAsset.cpp -

#include "TESTPrimaryDataAsset.h"

#include "PrimaryAssetTest.h"


/**
* -----------------------------------
* Actions
*
*/
UTESTPrimaryDataAsset::UTESTPrimaryDataAsset(const FObjectInitializer& ObjectInitializer)
	:
	UPrimaryDataAsset(ObjectInitializer),
	ID(GetFName()),
	AssetTable(nullptr)
{}



FPrimaryAssetId UTESTPrimaryDataAsset::GetPrimaryAssetId() const {
	return UTESTPrimaryDataAsset::PrimaryAssetId((ID == "" ? GetFName() : ID));

}

Create a custom GameMode class:

PrimaryAssetTestGameModeBase.h -

#pragma once

#include "CoreMinimal.h"
#include "GameFramework/GameModeBase.h"

#include "TESTPrimaryDataAsset.h"

#include "PrimaryAssetTestGameModeBase.generated.h"

/**
 * 
 */
UCLASS()
class PRIMARYASSETTEST_API APrimaryAssetTestGameModeBase : public AGameModeBase
{
	GENERATED_BODY()

public:

	uint8 bScanPerformed : 1;

	uint8 bIsLoadingData : 1;

	uint8 bIsDataLoaded : 1;

	uint8 bHasDataLoadBeenTried : 1;

	
	/**
	* -----------------------------------
	* Constructor
	*
	*/

	APrimaryAssetTestGameModeBase();


	/**
	* -----------------------------------
	* Methods
	*
	*/

	void PostDebugMessage(const FString& DbgMsg, int32 Type = 0, FColor ScreenColor = FColor::Silver, float DisplaySecs = 1000.0f);

	void PostDebugWarning(const FString& DbgMsg, float DisplaySecs = 1000.0f) {
		PostDebugMessage(DbgMsg, 1, FColor::Orange, DisplaySecs);

	}

	void PostDebugErr(const FString& DbgMsg, float DisplaySecs = 1000.0f) {
		PostDebugMessage(DbgMsg, 2, FColor::Red, DisplaySecs);

	}

	UFUNCTION()
	void ScanForPrimaryAssets();

	UFUNCTION()
	void OnPrimaryDataLoaded(FPrimaryAssetId TgtAssetID);


	/**
	* -----------------------------------
	* AGameModeBase overrides
	*
	*/

	/** Performs tasks during the active gameplay loop; most activity is coordinated here */
	void Tick(float DeltaSeconds) override;

};

PrimaryAssetTestGameModeBase.cpp

#include "PrimaryAssetTestGameModeBase.h"

#include "PrimaryAssetTest.h"

#include "Runtime/Engine/Classes/Engine/AssetManager.h"

//Constructor
APrimaryAssetTestGameModeBase::APrimaryAssetTestGameModeBase()
	:
	AGameModeBase(),
	bScanPerformed(false),
	bIsLoadingData(false),
	bIsDataLoaded(false),
	bHasDataLoadBeenTried(false)
{
	bPauseable = false;
	bAllowTickBeforeBeginPlay = true;

	PrimaryActorTick.bCanEverTick = true;
	PrimaryActorTick.bStartWithTickEnabled = true;
	PrimaryActorTick.bTickEvenWhenPaused = true;
	PrimaryActorTick.TickGroup = TG_PrePhysics;
	PrimaryActorTick.SetTickFunctionEnable(true);

}

/**
* -----------------------------------
* Methods
*
*/

void APrimaryAssetTestGameModeBase::PostDebugMessage(const FString& DbgMsg, int32 Type, FColor ScreenColor, float DisplaySecs) {
	static int32 MessageID = 0;
	switch (Type) {
	case 1:
	{
		UE_LOG(SWarn, Warning, TEXT("%s"), *DbgMsg);
		ScreenColor = FColor::Orange;

	}
	break;

	case 2:
	{
		UE_LOG(SWErr, Error, TEXT("%s"), *DbgMsg);
		ScreenColor = FColor::Red;

	}
	break;

	default:
		//fallthrough
	case 0:
	{
		UE_LOG(SWDebug, Log, TEXT("%s"), *DbgMsg);

	}
	break;

	}
	if (GEngine) {
		GEngine->AddOnScreenDebugMessage(MessageID++, DisplaySecs, ScreenColor, *DbgMsg);

	}

}

void APrimaryAssetTestGameModeBase::ScanForPrimaryAssets() {

	TArray<FString> A_Paths = { "/Game/TestData" };

	PostDebugMessage("Scanning for Primary Data Assets of TYPE: '" + UTESTPrimaryDataAsset::PrimaryAssetType().ToString() + "' in Path: '" + A_Paths[0] + "'", 0, FColor::Silver);

	UAssetManager& AssetMgr = UAssetManager::Get();

	const UAssetManagerSettings& AMSettings = AssetMgr.GetSettings();

	AssetMgr.StartBulkScanning();

	int32 Found = AssetMgr.ScanPathsForPrimaryAssets(UTESTPrimaryDataAsset::PrimaryAssetType(), A_Paths, UTESTPrimaryDataAsset::StaticClass(), true, false, true);

	AssetMgr.StopBulkScanning();

	bScanPerformed = true;

	PostDebugMessage("Scan Found #" + FString::FromInt(Found) + " Primary Assets of TYPE: '" + UTESTPrimaryDataAsset::PrimaryAssetType().ToString() + "'", 0, FColor::Silver);


}

void APrimaryAssetTestGameModeBase::OnPrimaryDataLoaded(FPrimaryAssetId TgtAssetID) {

	if (!bIsLoadingData) {
		return;

	}
	bHasDataLoadBeenTried = true;
	bIsLoadingData = false;

	PostDebugMessage("Primary Data Asset LOADED || Target Core Package ID: '" + TgtAssetID.ToString() + "'", 0, FColor::Emerald);

	UAssetManager& AssetMgr = UAssetManager::Get();

	UTESTPrimaryDataAsset* TgtAsset = AssetMgr.GetPrimaryAssetObject<UTESTPrimaryDataAsset>(TgtAssetID);

	if (!TgtAsset) {
		PostDebugWarning("Primary Asset: '" + TgtAssetID.ToString() + "' Does not exist in memory!");
		FAssetData AssetData;
		if (AssetMgr.GetPrimaryAssetData(TgtAssetID, AssetData)) {
			PostDebugMessage("Retrieved Asset Data!- LOADING", 0, FColor::Emerald);
			UObject* GenObj = AssetData.ToSoftObjectPath().TryLoad();
			if (GenObj) {
				PostDebugMessage("Generic object EXISTS, CASTING", 0, FColor::Emerald);
				TgtAsset = Cast<UTESTPrimaryDataAsset>(GenObj);
				if (!TgtAsset) {
					PostDebugErr("FAILED TO CAST loaded asset to UTESTPrimaryDataAsset object!");
					UPrimaryDataAsset* GenAsset = Cast<UPrimaryDataAsset>(GenObj);
					if (!GenAsset) {
						PostDebugErr("FAILED TO CAST loaded asset to generic UPrimaryDataAsset object! - WTF IS THIS THING?!");

					}

				}

			}

		}

	}

	TArray<FName> AssetBundleState;
	TArray<FSoftObjectPath> AssetBundlePaths;
	TSharedPtr<FStreamableHandle> AssetHandle = AssetMgr.GetPrimaryAssetHandle(TgtAssetID, false, &AssetBundleState);

	PostDebugMessage("Asset Bundle Request includes " + FString::FromInt(AssetBundleState.Num()) + " Asset Bundles", 0, FColor::Emerald);

	for (int32 Idx = 0; Idx < AssetBundleState.Num(); Idx++) {
		PostDebugMessage("AssetBundleID at IDX #" + FString::FromInt(Idx) + ": '" + AssetBundleState[Idx].ToString() + "'", 0, FColor::Emerald);

	}
	if (AssetHandle.IsValid()) {
		AssetHandle->GetRequestedAssets(AssetBundlePaths);
		PostDebugMessage("Asset Handle lists " + FString::FromInt(AssetBundlePaths.Num()) + " Paths", 0, FColor::Emerald);
		for (int32 Idx = 0; Idx < AssetBundlePaths.Num(); Idx++) {
			PostDebugMessage("Path at IDX #" + FString::FromInt(Idx) + ": '" + AssetBundlePaths[Idx].ToString() + "'", 0, FColor::Emerald);

		}
		if (!TgtAsset && AssetBundlePaths.IsValidIndex(0)) {
			PostDebugMessage("Forcing Primary Asset load from Listed FSoftObjectPath: '" + AssetBundlePaths[0].ToString() + "'", 0, FColor::Emerald);
			UObject* GenObj = AssetBundlePaths[0].TryLoad();
			if (GenObj) {
				PostDebugMessage("Successfully loaded object from Listed FSoftObjectPath: '" + AssetBundlePaths[0].ToString() + "'", 0, FColor::Emerald);
				TgtAsset = Cast<UTESTPrimaryDataAsset>(GenObj);
				if (!TgtAsset) {
					PostDebugErr("FAILED TO CAST Listed FSoftObjectPath asset to UTESTPrimaryDataAsset object!");
					UPrimaryDataAsset* GenAsset = Cast<UPrimaryDataAsset>(GenObj);
					if (!GenAsset) {
						PostDebugErr("FAILED TO CAST Listed FSoftObjectPath asset to generic UPrimaryDataAsset object! - WTF IS THIS THING?!");

					}

				}

			}
			else {
				PostDebugWarning("FAILED TO LOAD Listed FSoftObjectPath asset: '" + AssetBundlePaths[0].ToString() + "'");

			}

		}

	}


	if (TgtAsset) {
		bIsDataLoaded = true;
		PostDebugMessage("Primary Asset Loaded Properly", 0, FColor::Emerald);
		if (!TgtAsset->AssetTable.IsValid()) {
			PostDebugWarning("PrimaryAsset Loaded with Error - Table is NOT IN memory");
		}
		else {
			PostDebugMessage("PrimaryAsset Loaded Successfully - Table is VALID and in memory", 0, FColor::Green);


		}

	} else {
		PostDebugErr("FAILED TO RETRIEVE LOADED DATA ASSET!!!");

	}

}

/** Per-frame actions for this object */
void APrimaryAssetTestGameModeBase::Tick(float DeltaSeconds) {

	if (!bHasDataLoadBeenTried) {
		FString Message;

		if (!bScanPerformed) {
			PostDebugMessage("Triggering Scan for PrimaryAssets");
			ScanForPrimaryAssets();
			PostDebugMessage("Scan Complete");

		} else if (!bIsDataLoaded) {
			if (!bIsLoadingData) {

				PostDebugMessage("Triggering PrimaryAsset Load");


				bIsLoadingData = true;

				UAssetManager& AssetMgr = UAssetManager::Get();

				FPrimaryAssetType DataAssetType = UTESTPrimaryDataAsset::PrimaryAssetType();



				PostDebugMessage("Retrieving PrimaryAssetId list from AssetManager for Assets of type: '" + DataAssetType.ToString() + "'");


				TArray<FPrimaryAssetId> DataAssets;
				AssetMgr.GetPrimaryAssetIdList(DataAssetType, DataAssets);

				FPrimaryAssetId TgtAssetID = UTESTPrimaryDataAsset::PrimaryAssetId(TEXT("TESTDataAsset"));


				PostDebugMessage("ID List Holds #" + FString::FromInt(DataAssets.Num()) + " Assets of type: '" + DataAssetType.ToString() + "'");

				for (int32 Idx = 0; Idx < DataAssets.Num(); Idx++) {
					PostDebugMessage("Listed DataAsset: '" + DataAssets[Idx].ToString() + "'");

				}



				PostDebugMessage("Triggering PrimaryAsset LOAD: '" + TgtAssetID.ToString() + "'");


				TSharedPtr<FStreamableHandle> LoadingHandle = AssetMgr.LoadPrimaryAsset(
					TgtAssetID,
					{ TEXT("Table") },
					FStreamableDelegate::CreateUFunction(this, TEXT("OnPrimaryDataLoaded"), TgtAssetID),
					1
				);


				if (LoadingHandle.IsValid()) {
					PostDebugMessage("Successfully Triggered PrimaryAsset Loading: " + TgtAssetID.ToString());

				} else {
					PostDebugErr("!!FAILED!! to begin PrimaryAsset Loading: " + TgtAssetID.ToString());

				}


			} else {
				PostDebugMessage("PrimaryAsset is Loading...", 0, FColor::Yellow);

			}

		}

	}
	Super::Tick(DeltaSeconds);

}

Compile, then open the editor.

Register your custom data asset class with the editor. Include the proper directory.


Create a new instance of your custom PrimaryDataAsset in the proper directory.


Include an existing asset in your PrimaryDataAsset. Here I’ve used a custom Data Table.


PIE.

Why doesn’t this work?

According to the Asset Manager, I successfully loaded my custom data asset class object BUT it WILL NOT cast to my custom class, nor the UPrimaryDataAsset class.

How is that possible?

RELEVANT UE LOG PORTION::

[2020.02.08-22.50.49:299][880]LogLoad: Game class is ‘PrimaryAssetTestGameModeBase’
[2020.02.08-22.50.49:301][880]LogWorld: Bringing World /Game/TestMaps/UEDPIE_0_TestMap01.TestMap01 up for play (max tick rate 0) at 2020.02.08-17.50.49
[2020.02.08-22.50.49:301][880]LogWorld: Bringing up level for play took: 0.001032
[2020.02.08-22.50.49:301][880]LogOnline: OSS: Creating online subsystem instance for: :Context_2
[2020.02.08-22.50.49:305][880]PIE: Play in editor start time for /Game/TestMaps/UEDPIE_0_TestMap01 -0.458
[2020.02.08-22.50.49:305][880]SWDebug: Triggering Scan for PrimaryAssets
[2020.02.08-22.50.49:305][880]SWDebug: Scanning for Primary Data Assets of TYPE: ‘TESTPrimaryDataAsset’ in Path: ‘/Game/TestData’
[2020.02.08-22.50.49:309][880]SWDebug: Scan Found #1 Primary Assets of TYPE: ‘TESTPrimaryDataAsset’
[2020.02.08-22.50.49:309][880]SWDebug: Scan Complete
[2020.02.08-22.50.49:379][881]SWDebug: Triggering PrimaryAsset Load
[2020.02.08-22.50.49:379][881]SWDebug: Retrieving PrimaryAssetId list from AssetManager for Assets of type: ‘TESTPrimaryDataAsset’
[2020.02.08-22.50.49:379][881]SWDebug: ID List Holds #1 Assets of type: ‘TESTPrimaryDataAsset’
[2020.02.08-22.50.49:379][881]SWDebug: Listed DataAsset: ‘TESTPrimaryDataAsset:TESTDataAsset’
[2020.02.08-22.50.49:379][881]SWDebug: Triggering PrimaryAsset LOAD: ‘TESTPrimaryDataAsset:TESTDataAsset’
[2020.02.08-22.50.49:379][881]SWDebug: Successfully Triggered PrimaryAsset Loading: TESTPrimaryDataAsset:TESTDataAsset
[2020.02.08-22.50.49:380][881]SWDebug: Primary Data Asset LOADED || Target Core Package ID: ‘TESTPrimaryDataAsset:TESTDataAsset’
[2020.02.08-22.50.49:380][881]SWarn: Warning: Primary Asset: ‘TESTPrimaryDataAsset:TESTDataAsset’ Does not exist in memory!
[2020.02.08-22.50.49:380][881]SWDebug: Retrieved Asset Data!- LOADING
[2020.02.08-22.50.49:380][881]SWDebug: Generic object EXISTS, CASTING
[2020.02.08-22.50.49:380][881]SWErr: Error: FAILED TO CAST loaded asset to UTESTPrimaryDataAsset object!
[2020.02.08-22.50.49:380][881]SWErr: Error: FAILED TO CAST loaded asset to generic UPrimaryDataAsset object! - WTF IS THIS THING?!
[2020.02.08-22.50.49:380][881]SWDebug: Asset Bundle Request includes 1 Asset Bundles
[2020.02.08-22.50.49:380][881]SWDebug: AssetBundleID at IDX #0: ‘Table’
[2020.02.08-22.50.49:380][881]SWDebug: Asset Handle lists 2 Paths
[2020.02.08-22.50.49:380][881]SWDebug: Path at IDX #0: ‘/Game/TestData/TESTDataAsset.TESTDataAsset_C’
[2020.02.08-22.50.49:380][881]SWDebug: Path at IDX #1: ‘/Game/TestData/TestTableData.TestTableData’
[2020.02.08-22.50.49:380][881]SWDebug: Forcing Primary Asset load from Listed FSoftObjectPath: ‘/Game/TestData/TESTDataAsset.TESTDataAsset_C’
[2020.02.08-22.50.49:381][881]SWDebug: Successfully loaded object from Listed FSoftObjectPath: ‘/Game/TestData/TESTDataAsset.TESTDataAsset_C’
[2020.02.08-22.50.49:381][881]SWErr: Error: FAILED TO CAST Listed FSoftObjectPath asset to UTESTPrimaryDataAsset object!
[2020.02.08-22.50.49:381][881]SWErr: Error: FAILED TO CAST Listed FSoftObjectPath asset to generic UPrimaryDataAsset object! - WTF IS THIS THING?!
[2020.02.08-22.50.49:381][881]SWErr: Error: FAILED TO RETRIEVE LOADED DATA ASSET!!!
[2020.02.08-22.52.16:886][613]LogContentBrowser: Native class hierarchy populated in 0.0198 seconds. Added 3019 classes and 722 folders.
[2020.02.08-22.52.16:896][613]LogContentBrowser: Native class hierarchy updated for ‘WidgetCarousel’ in 0.0006 seconds. Added 0 classes and 0 folders.
[2020.02.08-22.52.16:977][613]LogContentBrowser: Native class hierarchy updated for ‘AddContentDialog’ in 0.0006 seconds. Added 0 classes and 0 folders.
[2020.02.08-22.52.17:008][613]LogSlate: Took 0.000337 seconds to synchronously load lazily loaded font ‘…/…/…/Engine/Content/Editor/Slate/Fonts/FontAwesome.ttf’ (139K)
[2020.02.08-22.52.30:201][160]LogAssetEditorSubsystem: Opening Asset editor for Blueprint /Game/TestData/TESTDataAsset.TESTDataAsset
[2020.02.08-22.52.30:564][160]LogContentBrowser: Native class hierarchy updated for ‘BlueprintGraph’ in 0.0012 seconds. Added 121 classes and 0 folders.
[2020.02.08-22.52.41:572][575]LogSlate: Window ‘TESTDataAsset’ being destroyed
[2020.02.08-22.52.41:583][575]LogWorld: UWorld::CleanupWorld for World_0, bSessionEnded=true, bCleanupResources=true
[2020.02.08-22.52.41:583][575]LogSlate: InvalidateAllWidgets triggered. All widgets were invalidated

3 Likes