How to avoid hardcode? (UPrimaryDataAsset)

I have a critical point in my code and I don’t like it.
It’s a horrible static hardcode full of literals in my Master Data Asset.

But without it is impossible (at least with my knowledge) to get an instance of the Master Asset.

UCLASS()
class UMasterDataAsset : public UPrimaryDataAsset
{
    GENERATED_BODY()

    UPROPERTY(EditAnywhere)
    TArray<TSoftObjectPtr<UPrimaryDataAsset>> Assets;  

    static inline const FName   PrimaryAssetTypeName = TEXT("Master");
    static inline const FName   FileName             = TEXT("PDA_Master");
    static inline const FString AssetPath            = TEXT("/Game/DataAssets/PDA_Master.PDA_Master");
    static inline const FPrimaryAssetId ID           = FPrimaryAssetId(FPrimaryAssetType(PrimaryAssetTypeName) , FileName);

	static const FSoftObjectPath& GetSoftObjectPath()
	{
		static const FSoftObjectPath Path(AssetPath);
		return Path;
	}

	static UClientMasterAsset* Load()
	{
		UObject* Obj = GetSoftObjectPath().TryLoad();
		return IsValid(Obj) ? Cast<UClientMasterAsset>(Obj) : nullptr;
	}


}

I also tried this but I always get an empty list.
The list is not empty only after the assets have been loaded into memory (not before).

TArray<FPrimaryAssetId>AssetIds;
UAssetManager::Get().GetPrimaryAssetIdList(FPrimaryAssetType(“Master”), AssetIds);

Any way to do this without hardcode?

Thank you so much!!

hardcoding is not a crime. it depends how you do it.

values will be “hardcoded” at a point anyway.

hardcoding values is often a problem when they are magic values (eg values scattered not bound to a variable with a nice name). but having value tables is pretty valid and still used nowadays. you can find plenty in the engine code.

in my project i use cpp mostly. so hardcoding is pretty natural. i keep the values in specific locations and structures.

if you don’t need to change the values often, hardcoding is ideal.

if you do need to change them, it depends how you want to handle that.

either have a developer settings object.

or you can have a blueprintfunctionlibrary marked as Config so that you can set them in your ini files (one of my favorites).

your code is pretty good, it looks acceptable for many settings. i guess it depends if it creates issues with your project. if it’s not broken then you don’t need to/can’t fix it.

all literal values get packaged in the binary anyway so it’s fine.

having them as static const is good. you can refer them by the class name, which gives you a namespace. which is pretty neat.

i have a special namespace ProjConsts where i store other namespaces with static const values that i reference from multiple places. it has served me really well.

i guess the question is why you don’t like it? there’s nothing inherently bad or horrible about what you’ve done.
are you putting a subjective aesthetic preference above functionality and practicality? (this is just a neutral comment i’m not judging, just trying to understand your goal and values).

what would you prefer and why?

2 Likes

The problem is that if I move the file from folder or rename it everything breaks… that’s why I want to avoid hardcoded paths.

Basically this is the biggest weakness of my entire project.
/Game/DataAssets/PDA_Master.PDA_Master"

If this breaks, literally everything breaks.

i see.

hmm. let me think some alternatives. these are some ideas:

in ue when you move something everything breaks, so it’s not that rare.

there are core redirectors which can help you. and can be modified on ini files and even after packaging.

also when you move an asset, doesn’t it leave an invisible redirector in that folder? is that not helping?

how often do you move that asset?

a developer settings seems like a good option. you can store the values there and when moving an asset they usually get updated (unless my memory fails me). i think you get a warning otherwise, which is not a huge feat since you can add a warning yourself too.

by your post count i imagine you know what i’m talking about, otherwise is this Developer Settings · Unreal🌱Garden

i think it will add a bit of code to get the values, you’ll have to execute a function instead of referencing a static value.

you can always get the default object of the settings, so it can be used in static contexes iirc.

would that help?

alternatively you can minimize the issue, by having a datatable with softlinks to the objects and constants that you need. then you define the values with an instance of the datatable in bps. and set it on a settings object. so you only have one point of failure which hopefully doesn’t need to move all the time. and ue will take care of updating the references because you’re in bp-land. i think.

so you have only one data table for project config, then on the row you get the data you need, for example the constants for this master data asset. but that seems like a lot of overhead to me. also i’m not sure if a masterDataAsset would have to be loaded in order for the datatable to be accessible.

they way i see it, the major issue is paths, not exactly hardcoding.

i think there are object ids (as in uuids) but i haven’t really used them much. these will be inmune to moving.

depending on how often you reference these assets you can also reference them by name and do a search in the asset registry. and it could reduce the chances of a broken reference.

1 Like

I’m moving those files very frequently now.
Because I’m just starting to use assets.
I’ve only been learning how to use assets for a week.
I’m looking for the best way to get a proper setup.
So I’m trying a few things and none of them seem to be optimal… assets are causing a ton of problems.
At this point I want to separate the assets that are loaded on the server and on the client separately.
So, actually there will be two Masters… and two critical paths that can be broken.

It seems that redirectors only work when something is referenced in bluprints.
I’ve used Developer Settings before. But redirectors don’t work there. If you move something there, it breaks too.
(At least that’s what I’ve seen in all versions prior to UE5.6)

I will investigate the Asset Registry and the uuids

Thank you very much for your help @nande

awesome. i also use the same approach. experiment to find the optimal.

keep in mind that once you settle the breakage will decrease.

i’ve been almost exclusively cpp for a few years, it has its rough edges but i love it more than using bps.

oh bummer. i’ll try with my project when i remember and see if that helps.

i just tested in my game. it does follows redirections, but once you fix it, it doesn’t use the new one… which i would report to ue as a bug.

awesome.

the asset registry is pretty cool, as you can iterate it async, and control loading/unloading of assets too.

my pleasure. thanks for the kindness :slight_smile: good luck and keep experimenting. :slight_smile:

you might also want to try using TSoftObjectPtr which has a load function.

and also TAssetPtr.

both of which could follow redirections potentially (just a hunch, untested, worth trying). Asynchronous Loading with TAssetPtr<T> · Unreal🌱Garden

here’s a demo of me testing the asset redirection with a developer settings reference https://www.youtube.com/watch?v=oi_CXqxlkvA

TSoftObjectPtr(FSoftObjectPath(“/Game/DataAssets/PDA_Master.PDA_Master”));

i use that all the time. it has the benefit that you can reference actors in the level. it’s ultra helpful.

for example, i can hardcode this and in runtime, when the actor gets loaded, the ptr becomes automagically populated. (i have dynamic loading of assets and actors)

TSoftObjectPtr<AActor>(FSoftObjectPath("/Game/LifeDev/Game/Sys/Game_L.Game_L:PersistentLevel.LNPC04_UAID_D8BBC116E501A1E401_1207918287"

1 Like

Hi @nande

Thank you for taking the time to explain the alternatives and prepare the video. I’ll take your ideas on Asset Registry, UUIDs, and redirects into consideration; I really appreciate the effort and thought you put into it, and I’m sure it will be useful. It’s clear you invested a lot of time and care, and that’s something I truly value.

Best regards :heart:

1 Like

Hey thanks a lot! I love to help, and makes me super happy when it lands possitively. Thanks for acknowledging it. I did took extra time, your gratitude motivated me.

Let me know if you find a good method.
Thanks for the kindness, Good luck and all the best! <3

1 Like

Hi @nande

I will do it if I find a solution. Right now I am having another problem also related to assets. Do you remember I told you I was looking for an optimal configuration? (Server/Client/Shared)… Well, separating the assets this way is causing me some surreal problems. When I was using a single PrimaryDataAsset, everything worked perfectly. Now that I have three files, there’s no way it works. I think I’ve already tried every possible combination.

  • With Directories set and not set

  • With SpecificAsset set and not set

  • Both set

  • One set and the other not set…

I’m starting to think this might be a UE5.6 bug.
I even think I’m going to give up on trying to have three separate files in PrimaryAssetTypesToScan…

If it works with a single file, maybe it’s better to reference all three in just one main asset:

+MainDataAsset

  • Server

  • Client

  • Shared

My intention was to have separated responsibilities, but it seems like the only thing that will work without dying in the attempt.

Anyway, I’m attaching an image… if you have any idea, please let me know.

Thank you very much for your help and best regards. :heart:

LogAssetManager: Warning: Invalid Primary Asset Type ClientEssential:DA_Fonts: ChangeBundleStateForPrimaryAssets failed to find NameData
LogTemp: Error: UClientAssetsLoaderSubsystem::HandleLoadCompleted → Handle Is Invalid

Hi Ivan,

Hmm i don’t have much experience with primary data assets, though i’ve been looking at them and want to try them out.

Sounds like an interesting approach, but in my experience attempting to separate assets depending on platform is difficult. I think that ue itself needs certain info about other assets and it’s really hard to separate them. so…

it might. assets bundles are relatively new and still under development, iirc.

as for network stuff specifically i’d be a bit scared (for a lack of better word) to separate the assets, since there has to be binary integrity between both the client and server. i imagine it’s quite possible that in order to run the server ue might need to touch or see the client assets (at least while packaging). it’s very possible that an asset is loading indirectly other assets in ways that break other stuff.

also don’t forget that, iirc, cpp classes are always loaded. so it might mean that a class might be referencing an asset and you might think it won’t be loaded.

i tend to set asset references on constructor using fconstructorhelpers a ton, so i tend to keep that in mind.

have you considered chunking ? that’s a more older way of splitting assets. it’s a bit more limited than assets bundles but might be more reliable. as i imagine it had more time to stabilize.

i’m not sure but i think assets bundles might give new features for the new smart loading technology (i think on ps5 they were using it). i’m not sure you need that. It also might behave better with iostore (ucas/utoc). which i think is an “either or” option with paks.

Is that your own asset or one from the engine?
i wonder if you need to include the clientessential in your primary assets to scan, or maybe in one of your primary assets (like shared) or maybe in both server and clients.

here’s an interesting post by Ari [Solved] Troubleshooting Primary Assets

where he said

You are also able to specify different pak chunks for your primary assets, separating the pak files in your game.

So maybe you can use just one primary asset (like you said you did and worked) , and then specify different chunks for each one of your sections (server/client/shared). it might give you a close enough result to what you’re looking for.

and i’m sure you’ve seen these links, as i’ve seen them a few times, but maybe rubber-ducking sparks an idea. (also for people looking for help on the matter, which helped me a lot before)

i’m glad to help, best of lucks to you, keep it up, im sure you’ll settle on a the optimal alternative for you. <3 good luck :slight_smile:

pd: “asset bundles” is such a difficult thing to search online. it shows stuff from fab and gamesfromscratch.

1 Like

Hi @nande

ClientEssential:DA_Fonts: –> It’s just a label to say it’s a client asset.

I think I found the problem… it seems that when I had only one AssetMaster the AssetManager was doing an automatic cache of all assets including all internal references to the asset. So with a Solo AssetMaters everything worked great and there were no problems.

Apparently, Asset Manager isn’t doing the automatic caching when there are three AssetMasters. So I had to do it manually.

Something strange was happening so well with the Asset Manager configuration in ProjectSetting. Making a change to the configuration worked, but then it broke when restarting the editor(a pain)… It only worked if I put all the Assets in the same folder…

In the end, I’m not sure what I did, but I managed to get it working with three AssetMasters and three different folders.(Only after testing for hours).

I never really cared about assets until now… I was more concerned with learning and making sure everything worked… But now I know that you have to cook assets or they won’t work in the packaged game.

I wish I had known this before I had my whole code filled with hard references. Now it’s time for a brutal refactoring of all my code.

Thank you very much for your help @nande

I really appreciate it very much :heart:

They’re not. Bundles have been around since at least 4.16 when I started working with Unreal.

i tend to set asset references on constructor using fconstructorhelpers

While this may work for you, these are widely considered to be a terrible practice and do not scale to large projects or even small teams.

@Ivan3z

Because as part of the AssetManager configuration you tell it where it can look for primary assets of a specific type. If you make this path very narrow, they’ll have to be in the same folder. You could always make it something like “/Game/”, in which case you should be able to put them anywhere in the content directory.

1 Like

Hi @MagForceSeven

Can you tell me one thing please?

I’m going crazy with this..

It seems that Asstet Registry only works in the editor.

But I need to register the assets with this function

UAssetManager::Get().RegisterSpecificPrimaryAsset(const FPrimaryAssetId& PrimaryAssetId, const FAssetData& NewAssetData);

before using this other function

UAssetManager::Get().LoadPrimaryAsset(...)

Otherwise the second function will not work.

But the only way to get(AssetData& NewAssetData) is usingthe Asstet Registry.

So by that logic none of the three functions will work in runtime.

So if that is true I am forced to use this other function

UAssetManager::GetStreamableManager().RequestAsyncLoad(...)

But this function seems to return nullptr if the assets are already loaded into memory.
So it only works on the first play. On the second play, the assets are in memory and there’s no way to recover them because the Asset Manager doesn’t know they exist.

Obviously I need help with this..

Thank you so much

The Asset Manager and Registry both absolutely work in non-editor builds.

Generally the Asset Manager knows about primary assets by configuring the ‘Primary Asset Types to Scan’ in the Project Settings > Game > Asset Manager.

Then you would have properties for either FPrimaryAssetID or TSoftObjectPtr and use those with LoadPrimaryAsset. You shouldn’t need the Asset Registry for anything when it comes to primary assets.

Yes, that’s accurate. The thing to remember is that the return value is not a pointer to the asset being requested but a handle to the loading request. If the asset is already loaded, no loading request is required. So if you had a TSoftObjectPtr to an asset, called RequestAsyncLoad and it returns nullptr then you’d immediately be able to call TSoftObjectPtr.Get() and it should return the a pointer to the asset.

1 Like

It’s unlikely. At work we have dozens of asset types in this list. My home projects all have at least 5:

1 Like

Thanks for confirming this… the documentation says it’s an editor subsystem. That’s why I was thinking it only works in the editor.

I’ll take note.

Thank you very much for the help. @MagForceSeven :heart:

HI @MagForceSeven

Thanks for your previous reply. I understand that the AssetManager and AssetRegistry work fine in non-editor builds.

I’ve been testing further in the editor, and I’m still seeing issues on the first play after compiling or restarting the editor. Some assets don’t seem to get registered/scanned properly, even with only a few files.

The Assets Manager scan always fails on the first play after compiling or restarting the editor. It works fine from the second play onwards.

Forcing registration doesn’t fix it. Not all assets in the folders are properly registered/scanned, even with only 10 files.

Tested in UE5.6 and UE5.6.1 in the editor. Haven’t tried a cooked version yet. This behavior raises concerns about the system’s reliability. If this isn’t a bug, I’d like to know the cause and if there’s a way to prevent it.

This code is a summary of what I’m doing. On the first play, the list printed in PrintIDList() doesn’t show all the assets from the folders. The RegistryFailback() function fails on all attempts to register the missing assets. And in the LoadNextAsset() function, I get an invalid handle.

However, on subsequent plays everything works perfectly. So it’s all very puzzling.

//-----------------------------------------------------------------------------------------------------
void UClientAssetsLoaderSubsystem::Initialize(FSubsystemCollectionBase& Collection)
{
    Super::Initialize(Collection);
    check(UAssetManager::IsInitialized());

    UAssetManager& Manager = UAssetManager::Get();	

    Manager.CallOrRegister_OnCompletedInitialScan(
    FSimpleMulticastDelegate::FDelegate::CreateUObject
    (
	    this,      &UClientAssetsLoaderSubsystem::OnAssetManagerInitialScanComplete
    ));

}
//-----------------------------------------------------------------------------------------------------
void UClientAssetsLoaderSubsystem::OnAssetManagerInitialScanComplete()
{
	PrintIDList();
    DiscoverAssetsAsync();
}
//-----------------------------------------------------------------------------------------------------

void UClientAssetsLoaderSubsystem::PrintIDList()
{

    UAssetManager& Manager = UAssetManager::Get();

    TArray<FPrimaryAssetId> IdList;
    Manager.GetPrimaryAssetIdList(UClientMasterAsset::GetType(), IdList, EAssetManagerFilter::Default);

   for (FPrimaryAssetId &ID : IdList)
   {
	    UE_LOG(LogTemp, Log, TEXT("UClientAssetsLoaderSubsystem::PrintIDList --> Asset ID = %s"), *ID.ToString());
    }

}

//-----------------------------------------------------------------------------------------------------

bool UClientAssetsLoaderSubsystem::RegistryFailback(const FSoftObjectPath& Path, const FPrimaryAssetId &AssetId)
{

    FAssetData AssetData;UAssetManager& Manager = UAssetManager::Get();

    if(!Manager.GetAssetDataForPath(Path, AssetData))
    {
	    IAssetRegistry& Registry =      FModuleManager::LoadModuleChecked<FAssetRegistryModule>   ("AssetRegistry").Get();

	AssetData = Registry.GetAssetByObjectPath(Path, true);


	if (!AssetData.IsValid())
	{
		UE_LOG(LogTemp, Error,    TEXT("UClientAssetsLoaderSubsystem::RegistryFailback --> AssetData is not valid for path: %s"), *Path.ToString());
		return false;
	}
}

    if (!Manager.RegisterSpecificPrimaryAsset(AssetId, AssetData))
    {
	    UE_LOG(LogTemp, Error, TEXT("UClientAssetsLoaderSubsystem::RegistryFailback --> Registration failed for AssetId: %s"), *AssetId.ToString());
	    return false;
    }

return true;

}

//-----------------------------------------------------------------------------------------------------

void UClientAssetsLoaderSubsystem::LoadNextAsset()
{
    if (!RegistryFailback(AssetList[CurrentAssetIndex].AssetPath, AssetList[CurrentAssetIndex].AssetId))
     {
         UE_LOG(LogTemp, Error,    TEXT(“UClientAssetsLoaderSubsystem::LoadNextAsset → Asset is not registered: %s”), *AssetList[CurrentAssetIndex].AssetId.ToString());
	    return;		
      }


    TArray<FName> Bundles;
    Bundles.Add(FName("Default"));
    const FStreamableDelegate &Delegate =     FStreamableDelegate::CreateUObject(this, &UClientAssetsLoaderSubsystem::HandleLoadCompleted);

    UAssetManager& Manager = UAssetManager::Get()
    Handle = Manager.LoadPrimaryAsset(AssetList[CurrentAssetIndex].AssetId, Bundles,     Delegate, FStreamableManager::AsyncLoadHighPriority);

}
//-----------------------------------------------------------------------------------------------------

I’d really appreciate any guidance on what might cause this or how to ensure all assets are registered correctly on first play.

Thanks again,
Iván

1 Like

Considering the AssetManager and Registry are used by the Editor itself (more-so the Registry), it wouldn’t be high on my list of concerns. I’ve been using it since 4.16 and it’s always worked fine on the first run of the game for me across 3 professional and 4 hobby projects.

Nothing in that code looks obviously wrong, sorry. If you have engine source you could debug the asset manager. Or maybe there’s something in your logs that you’re overlooking. There’s a lot of interconnected parts with the scan list and the asset id setup and whatnot that can get messed up in subtle ways.

Sorry I can’t be more help at this point.

2 Likes

Thanks for your time and for confirming your experience with AssetManager. It helps to know it has been reliable for you — that rules out some doubts on my end. I’ll keep digging into my setup and logs to see if I can spot where things go wrong.

Thank you so much :heart:

1 Like