Download

Dynamically created primary assets not registering with asset manager

Hi All,
Just wondered if I was missing something when I try and register a new primary asset in memory it doesn’t get picked up. I didnt go the AddDynamicAsset route as that explicitly says it’s for assets that are just for holding in memory and my goal is to have a physical asset mirrored on the file system as well.

I’m creating a simple player primary asset for new players as they log in linking them to their characters inventories etc.


UPackage* package = CreatePackage(*packagePath);

UGyPlayerOwner* playerOwner = NewObject<UGyPlayerOwner>(package, UGyPlayerOwner::StaticClass(), *uniquePlayerOwnerID.ToString(), EObjectFlags::RF_Public | EObjectFlags::RF_Standalone | EObjectFlags::RF_MarkAsRootSet);


Once I’ve populate it I call asset created this looks like it should register it with the asset manager, it has delegates etc that looks like that’s what it is supposed to do however if I then search for this new asset with the asset manager after I’ve called this is can’t find it.


FAssetRegistryModule::AssetCreated(playerOwner);

I know it’s configured correctly and registering with the asset manager at start up because I’m also saving it out to the file system


 playerOwner->MarkPackageDirty();
bool bSuccess = UPackage::SavePackage(package, playerOwner, EObjectFlags::RF_Public | EObjectFlags::RF_Standalone, *filePath);


If I then restart the editor or manually resave the created asset the asset manager can then find it to load just fine however I’m creating these on the server so that’s not an option to restart the game instance each time.

So for now I just return the new object which work fine and then in the create process I kick off an async scan of the directory where I created the asset at which point the asset manager will pick it up without a restart and all is good. This is working fine, I’d move the scan part into a separate thread running as some sort of queued batch if it was in production in case it got battered under load but it seems to work just felt a bit clunky.



AssetManager.ScanPathForPrimaryAssets(UGyAssetManager::PlayerOwnerType, TEXT("/Game/Gy/GameState/PlayerOwner/" + uniquePlayerOwnerID.ToString()), UGyPlayerOwner::StaticClass(), false, false, false);


any thoughts if I have missed something ?

Mark

I’ll answer my own question a bit
So I think it’s all a mute point, when I ran it properly on a dedicated server in a separate process it all went bang.

From looking at the errors it looks like trying to dynamical create and add assets on the server is not something the asset management system is designed for (unless I’ve got this wrong!) So I guess it’s just for asset creation and packaging etc. rather than adding dynamic content at run time.


FAssetRegistryModule::AssetCreated(playerCharacter);

gives


[2021.03.08-20.25.00:245] 22]LogWindows: Error: === Critical error: ===
[2021.03.08-20.25.00:248] 22]LogWindows: Error:
[2021.03.08-20.25.00:249] 22]LogWindows: Error: Assertion failed: GIsEditor [FileD:/Build/++UE4/Sync/Engine/Source/Runtime/AssetRegistry/Private/AssetRegistry.cpp] [Line: 1589]
[2021.03.08-20.25.00:250] 22]LogWindows: Error: Updating the AssetRegistry is only available in editor

if I remove that and just try and save the package (I tried removing flags etc)


    bool bSuccess = UPackage::SavePackage(package, playerCharacter, EObjectFlags::RF_NoFlags, *filePath);

gives


[2021.03.08-19.09.20:221] 23]LogWindows: Error: === Critical error: ===
[2021.03.08-19.09.20:224] 23]LogWindows: Error:
[2021.03.08-19.09.20:225] 23]LogWindows: Error: Fatal error: [File d:/Build/++UE4/Sync/Engine/Source/Runtime/CoreUObject/Private/UObject/UObjectGlobals.cpp] [Line: 287]
[2021.03.08-19.09.20:228] 23]LogWindows: Error: Illegal call to StaticFindObjectFast() while serializing object data or garbage collecting!

the specific line in the source code is the bit it fails on


 UObjectGlobals.cpp line 230


if (GIsSavingPackage || IsGarbageCollectingOnGameThread())
{
UE_LOG(LogUObjectGlobals, Fatal,TEXT("Illegal call to StaticFindObjectFast() while serializing object data or garbage collecting!"));
}


I think I’ll give up on this idea and just serialise what I want out and do that instead.

Small update in case anyone runs into this, I still gave up in the end and created own asset management load and save with serialized data and got the dynamic asset registering working instead which is cool, but I did get a bit further debugging what was going on before I called it a day and rolled my own solution.

If you go back and back through the code the Illegal call to StaticFindObjectFast is because the objects metadata is null after you have created it.

So if you call GetMetaData() on the package after you create it, that not only gets the meta data but sneakily creates it if it’s not there which is really not obvious anywhere.

See comments in engine code to that effect


/**
* Gets (after possibly creating) a metadata object for this package
*
* @return A valid UMetaData pointer for all objects in this package
*/
UMetaData* UPackage::GetMetaData()

This then lets you save etc. without the error and works fine when you run a dedicated server testing from command line etc. At this point I thought I had cracked it and could save dynamically created uasset files on the server but it was not to be.

I thought I had better check a proper dedicated server and when you build the dedicated server properly and package it so it’s not running in any sort of editor context it then fails saying you are not allowed to add metadata in server context or something along those lines, so then I added some inline conditions to not set metadata if it was a server build, then it started throwing other errors, I fiddled around with it for a bit and eventually decided I’d spent enough time on it and would be quicker to roll my own solution which I’m pleased I did as it’s loads simpler and really pleased with it!

I’m sure I’m just missing some sort of flag or something but it wasn’t obvious.

While I was typing this I did find a few other references in the savepackage.cpp that might be worth testing in the future but I’m done on this bit for now it feels like I’m trying to use it for something it’s not really designed for and I’ve got more flexibility with the solution I’ve come up with as well, it was just being able to load and save dynamically created assets on the fly I wanted to be able to do and can do that now.


if (!Package->HasAnyPackageFlags(PKG_ServerSideOnly))


// Skip transient, editor only, and excluded client/server objects
if (ToTest)
{

If anyone interested in dynamic loading and registering of assets into asset manager and keeping them in memory etc I can share separate post what I found on that as the documentation is all out of date with some vital bit’s missing and that took a bit of figuring out as well.

Mark

That is something i am fighting right now, That is getting assets as in weapon parts loaded at runtime. i’ve got all the weapon assembly code finished and inventory manager done but i cant seem to get my parts to be made dynamically. Everything i’ve tried leads to a nightmare. Sooner or later i will come across the right answer.

Happy to share what I’ve figured out, I need to do a bit more testing to make sure it’s exactly how I think it’s working but for now it’s working well enough. There is 1 scrap of documentation I found for this from some old fortnight code but it’s out of date as of 4.26.1 and really not clear regardless. so this is as a result of me digging through source, might need a bit of refinement but it works.

  1. the big piece that is not clear anywhere, if you want to load assets from outside the cooked project dynamically the asset type can’t already be being managed by the asset manager or it will fail. So you need to have the asset ID and item type not in asset manager, here’s where I commented out my DefaultGame.ini file to that effect when testing this.

Then Load the asset you want, in my case I’ve not done anything special, just created a new primary asset object, modified the values and serialized it. Here’s code snippet but I just create the object then return that. The GetSerializedAssetPath just makes a generic path string for me using the assetID and type, I don’t think you actualy need a path here but I’ve just done it for completeness. the path will look something like “/Game/Gy/GameState/assetType/assetID” in my case.


template< typename T >
T* UGyAssetMan::CreatePackagedAssetObject(const FName& assetID, const FPrimaryAssetType& assetType)
{

FString packagePath = GetSerializedAssetPath(assetID, assetType);

UPackage* package = CreatePackage(*packagePath);

return NewObject<T>(package, T::StaticClass(), *assetID.ToString(), EObjectFlags::RF_Public);
}

In the asset I’ve overridden the GetPrimaryAssetID() so it returns the asset ID in the format I want that then works with all my asset management code.


FPrimaryAssetId UGyPlayerOwner::GetPrimaryAssetId() const
{
Super::GetPrimaryAssetId();
return FPrimaryAssetId(ItemType, UniquePlayerID);
}

Then using pretty standard boiler plate code load the serrilized data I’ve loaded into the dynamic asset



template< typename T >
T* UGyAssetMan::LoadSerializedAsset(const FPrimaryAssetId& assetID)
{

FGySaveData dataRecord;

bool bLoadedData = LoadSerializedAsset(assetID, dataRecord);

FMemoryReader memoryReader(dataRecord.ObjectSerializedData);
FGySaveGameArchive archive(memoryReader);

T* obj = nullptr;

if (bLoadedData)
{
obj = CreatePackagedAssetObject<T>(assetID);

if (obj != nullptr)
{
obj->Serialize(archive);
}
}

return obj;
}

So now I have the dynamic asset created, if I want it around in memory so I can use the asset manager to get it in the future etc. I need to register it dynamically with the asset manager, this is the bit where there is no current documentation, I’ll type what I think is going on but I do need to test a bit more for loading bundles of assets, this example is just loading one asset.

So I get the PrimaryAssetId from the object I just created which is via the override I posted earlier.

I then create a FSoftObjectPath from the object, this just creates a string representation of the relevant bits of the Object that the asset manager knows how to deal with from what I can see. (I’ve also stated using these soft pointers in my serialized assets they are great for that as well)

FAssetBundleData you can just add multiple FSoftObjectPath it’s just a struct with an array in it so you can add multiple at once but you do the add dynamic call slightly differently.

Then I call AddDynamicAsset with those details which adds it to the asset manager (but doesn’t load it in the asset manager so you can’t search for it yet.)

If you want to add bundles instead you call it with an empty soft object path and it then looks at the FAssetBundleData instead from what I can see (I need to test that properly) so in my case it would be something like AddDynamic(playerAssetID, FAssetBundleData(), gB) to do that

Then you load the asset into the asset manager, the bundles in this case are your asset meta tags, now I’m not sure if these work dynamic or not this needs testing as well but I’ve really just kept my dynamic objects light with GUID references in them to anything else I might want to load and managing loading like that which is what the tags are for as well but I just found it easier, I might try tags again later. (something like UPROPERTY(…, meta = (AssetBundles = “UI”)) in the asset object)


bool UGyAssetMan::RegisterDynamicAsset(UObject* assetToLoad)
{
if (assetToLoad == nullptr)
{
return false;
}


UGyAssetManager& AssetManager = UGyAssetManager::Get();

if (!AssetManager.IsValid())
{
UE_LOG(GyLog, Warning, TEXT("UGyAssetMan::RegisterDynamicAsset AssetManagerNullPointer"))
return false;
}


//get the primary asset ID
FPrimaryAssetId playerAssetID = assetToLoad->GetPrimaryAssetId();

//get all the data from where the current object is
FSoftObjectPath assetPath(assetToLoad);

FAssetBundleData gB;

AssetManager.AddDynamicAsset(playerAssetID, assetPath, gB);

TArray<FName> bundles;
AssetManager.LoadPrimaryAsset(playerAssetID, bundles);

return true;
}


then it’s in memory and you can just reference it whenever you like with a one liner from anywhere in your code where UObject is whatever you object you want to cast to is.


UObject* obj = Cast<UObject>(AssetManager.GetPrimaryAssetObject(primaryAssetID));

I need to do a bit more work on it but that’s the gist of it from what I can see!

hope that’s helpful

Mark

Interesting. What i’m doing is using a 4 letter number combo at the start. Example GY67|BM16|DG4K|SIL4 Each 4 letter number combo represents part of a class name. Example Wep_BM16 will be a barrel. What i need to do is add Wep_ and BM16 together as one string to be Wep_BM16, then take that string make a class from it. The i can use the class Wep_BM16 to spawn my weapon or weapon part. My spawn code on is done, I just need to figure out the string to class conversion.

​​​​​​​Example what letters represent
gun|barrel|clip|scope
GY67|BM16|DG4K|SIL4

If I understand correctly I think that’s pretty much what I’ve done for spawning my player pawns on login

So I register the character sub class I’m deriving all my player actors from with the asset manager for scanning that directory so just AGyCharacterBase : public ACharacter,

then in that base class I’ve overridden the get primary asset ID with a custom asset type and my own unique ID which is just a unique FName this could be your weapon type and the string name you want to use for that class so I set PrimaryCharacterID in blueprint but you could do something clever creating a short name from the parent class etc I’m sure or just strip off the _C etc from the GetFName if it’s ablueprint etc.

ItemType = UGyAssetManager::BaseCharacterType;


FPrimaryAssetId AGyCharacterBase::GetPrimaryAssetId() const
{

Super::GetPrimaryAssetId();

FPrimaryAssetId assetID;

if (PrimaryCharacterID.IsNone())
{
assetID = FPrimaryAssetId(ItemType, GetFName());
}
else
{
assetID = FPrimaryAssetId(ItemType, PrimaryCharacterID);
}

return assetID;
}

I made sure I have blueprints included to scan by the asset manager

then to load it I’ve done sync load on this as no point starting the game before the pawn has spawned but can to async as well

I can use the meta tags if I don’t want to load the whole asset etc. but in this instance I do so something like this where you form the primary asset id with your custom type followed by the FNAME you want to load
In my case I have the type stored in the asset manager and I’ve already stored the FName in a replicated struct on the player controller so grab it from there in gamemode.
FPrimaryAssetId(UGyAssetManager::BaseCharacterType, pC->PlayerReplication.PlayerClass)

this is forced sync load but will still make a delegate call on success you can remove the WaitUntilComplete call to make it async and do a call back from the delegate once it fires as it’s in memory at that point and can just load it with Cast(GetPrimaryAssetObject(assetID));

this code is running in my custom asset manager


UClass* UGyAssetManager::SynchronouslyLoadAsset(const FPrimaryAssetId& assetID)
{
if (!assetID.IsValid())
{
UE_LOG(GyLog, Warning, TEXT("UGyAssetManager::SynchronouslyLoadAsset Invalid assetID passed aborting %s"), *assetID.ToString());
return nullptr;
}

TArray<FName> bundles;

FStreamableDelegate Delegate = FStreamableDelegate::CreateUObject(this, &UGyAssetManager::OnAssetLoaded, assetID);

TSharedPtr<FStreamableHandle> lPa = LoadPrimaryAsset(assetID, bundles, Delegate);

/**
* If the asset is not in memory wait until it's loaded
* timeout after 2 secs
*
* if lPa is not valid it means its already loaded so just return
*/
if (lPa.IsValid())
{
UE_LOG(GyLog, Display, TEXT("UGyAssetManager::SynchronouslyLoadAsset Loaded Asset %s not in memory"), *assetID.ToString());
EAsyncPackageState::Type status = lPa->WaitUntilComplete(2.0f);
if (status == EAsyncPackageState::TimeOut)
{
UE_LOG(GyLog, Warning, TEXT("UGyAssetManager::SynchronouslyLoadAsset Timed out trying to load Asset %s"), *assetID.ToString());
}
}

/* This wont force load just return whats in memory or null pointer if nothing loaded */
return Cast<UClass>(GetPrimaryAssetObject(assetID));
}

void UGyAssetManager::OnAssetLoaded(FPrimaryAssetId assetID)
{
/* Called by UGyAssetManager::SynchronouslyLoadAsset just stub, might need it later */
UE_LOG(GyLog, Display, TEXT("UGyAssetManager::OnAssetLoaded Loaded Asset Item %s"), *assetID.ToString());
}





​​​​​​​something like that perhaps

I am doing the same for my character as my weapons



//head|shirt|pants|facemask|shoes|elbowpads
characterSetup = "|3222|6396|8478|5247|5454|9293";  // Casual Caucasian Player


I pushed my weapon game to the side for now and am working on a racing game, but would like to finish the weapon code.

Where i’m stuck at is getting the FName this> Wep_BM16 into the actual class. Once i get that, i can store the classes in an array in PlayerState file
ThePRI->GunParts*. So that i can use to actually spawn the pieces.

like i posted here https://forums.unrealengine.com/deve…-active-handle