Asset registry refresh of a single asset containing multiple objects

We have a uasset file that contains multiple classes (meshes, materials, editor data).

To the user this shows as a single asset in the content browser. We have implemented a menu which shows these “sub-assets” using a FAssetPickerConfig with our own filter. In the menu you can see thumbnails of the assets.

When the uasset is updated new meshes and materials are put into the asset and the old ones are collected by the garbage collector.

The problem is that the cached information held by the assetregistry is no longer up to date. We call
FAssetRegistryModule::AssetCreated for all the new meshes and materials and this does work, we see those, but we also see the old ones.

When the garbage collector deletes them the asset registry is not notified.

Is there a function we can call after forcing a garbage collect (with CollectGarbage) that will force the assetregistry to update it’s cache for our uasset?

I am looking for something that would delete the old cached data and re-scan and re-cache the new data.

Thanks.

Hi Kevin,

This might just be a case of you needing to call AssetDeleted to let the asset registry know that those assets are about to be purged (you’d need to do this prior to your GC call).

The asset registry should also update from .uasset files when they’re changed on disk (due to the directory watcher).

Thanks,
Jamie.

Hi Jamie,

When will the directory watcher trigger an update? When the asset is saved?
I didn’t see any change until I reloaded the editor and looked at the asset again.
I am looking for a nice function to call that will cause the assetregistry to re-scan my uasset and re-update it’s cache preferably prior to saving.

Our use case for the assets is unusual:

We have an editor object which is created through the usual AssetTypeActions and through a factory.

In addition we create new objects with NewObject, setting them to RF_Public and putting them into the same package as the editor object.

The user can view the additional objects through a menu item exposed by the assettypeaction for the editor object.

The additional objects are UStaticMesh and UMaterialInstanceConstant. The editor object references them.

The assetregistry scans our uasset and caches information about the UStaticMesh and UMaterialInstanceConstant.

If we create new UStaticMesh and new UMaterialInstanceConstant and the references to the old ones are removed a garbage collect and save package will delete the old UStaticMesh and UMaterialInstanceConstant.

We have control over when these new static meshes and materials are created. We were calling FAssetRegistry::AssetCreated for the new meshes and materials but we only called FAssetRegistry::AssetDeleted for the old meshes.

The AssetRegistry retained the materials in it’s cache and presented out of date information regardless of whether the asset was saved or not. The correct information would only be shown if the editor was closed and reopened and the assetregistry was re-scanned.

I didn’t have control of when the materials were being garbage collected but I came up with a method that worked:

TArray< UObject* > SubObjects;
GetObjectsWithOuter(pFoundAsset->GetOutermost(), SubObjects, false, RF_ClassDefaultObject);

TMap AssetData;
for (int i = 0; i < SubObjects.Num(); i++)
{
	AssetData.Add(SubObjects[i]->GetPathName(), new FAssetData(SubObjects[i]));
}


CollectGarbage( GARBAGE_COLLECTION_KEEPFLAGS );

SubObjects.Empty();
GetObjectsWithOuter(pFoundAsset->GetOutermost(), SubObjects, false, RF_ClassDefaultObject);
for (int i = 0; i < SubObjects.Num(); i++)
{
	AssetData.Remove(SubObjects[i]->GetPathName());
}

for (TMap::TIterator AssetDataIter = AssetData.CreateIterator(); AssetDataIter; ++AssetDataIter)
{
 	FAssetRegistryModule::AssetDeleted(AssetDataIter->Value);
}
  1. The code gets the list of objects before garbage collection and stores a FAssetData and a string for the object.

  2. Garbage collection is triggered.

  3. The new list is captured and the new items are removed from the list therefore leaving any old objects in the list.

  4. A new AssetDeleted function was implemented that used a FAssetData pointer and which called FAssetRegistry::RemoveAssetData();

After this the assetregistry cache is updated and accurate information reported.

This meant we could clean up the asset registry after garbage collection without requiring pointers to the original data.

I admit our use case is not exactly the ue4 way but it works :slight_smile:

Is there a cleaner alternative that would work? Is there a way to trigger a assetregistry update of our uasset after a garbage collect and without having to call AssetDeleted or AssetCreated?

Thanks
Kevin

I found a an additional AssetDeleted like this worked better:

void FAssetRegistry::AssetDeleted(const FString &sObjectPath)
{
	FAssetData **AssetData = CachedAssetsByObjectPath.Find(FName(*sObjectPath));
	if (AssetData && *AssetData)
	{
		RemoveAssetData(*AssetData);
	}
}

It does look like there might be a bug with how the directory watcher works in FAssetRegistry::OnDirectoryChanged. We’ve likely never noticed this before as most of our packages only contain a single asset.

In the FFileChangeData::FCA_Modified case, it marks the package for a re-scan without clearing out all the data associated with that package (like FFileChangeData::FCA_Removed does), which could result in it leaving old asset data in the registry.

With regard to your use-case here, I’d be tempted to add a function which takes a UPackage* and has the same behaviour as FFileChangeData::FCA_Removed. You could then pass your package to it, the asset registry would clean up anything it thinks belonged to that package, and then you could use your calls to FAssetRegistry::AssetCreated to re-populate it with the new assets.

I tried out your suggestion.

The problem is our asset is not cleaned out entirely because an editor object always remains.

I implemented the code in fca_removed as part of a “pre package modified” step that causes the asset registry to clear it’s data.

The flow is:

  • pre package modified (assetregistry clears its info)
  • do some creation of objects
  • post package modified (assetregistry rescans the package)

I tried a couple of things for the “post package modified”:

  1. AddAssetPath giving it the package
  2. AddFilesToSearch giving it the path of the package

When I tried it I had the content browser open to the directory the asset is in.
I saw it disapear from the “pre package modify” where the FCA_Removed code kicked in.

I then hoped it would reappear with with the post callback but it didn’t. I tried using the FCA_Added case in the “post package modify” but the files were not checked again.

Any ideas?

Has the new package been saved to disk at that point? If not then FCA_Added would just re-scan the file as it was, and potentially skip it if it didn’t think anything had changed from the initial cache.

AddAssetPath is used to add folders, not asset data, so I’m not surprised that did nothing.

AssetCreated is still your best bet to re-populate the asset registry if your file hasn’t been updated on disk. You’d just need to enumerate your package and add anything you need.

I’ve submitted a fix for the FAssetRegistry::OnDirectoryChanged case (CL# 3087216).

It was a little more involved than my earlier suggestion in order to avoid the Content Browser from getting an asset deleted event for an asset that still existed in the package (as this broke the selection in the UI).

This change also exposes FAssetRegistry::PackageDeleted to let you clear our all the data associated with an in-memory package.