"Could not find graph with registry graph key" error still appears after following previous post's advice

This question was created in reference to: [Some MetaSounds ‘randomly’ missing from Frontend [Content removed]

Our game has missing sounds accompanied by a spam of this error in the log:

LogMetaSound: Error: Could not find graph with registry graph key ‘External_None.[insert-a-bunch-of-numbers], /Path/To/Metasound/MS_GenericName’

We are getting this issue even after trying the suggested fixes in the linked ticket, specifically:

  • resaving affected Metasounds and patches that it references
  • turning on cvar au.MetaSound.DisableAsyncGraphRegistration 1

This issue is also really hard to reproduce. It happens for players randomly. Restarting the game always fixes it.

Our Metasound source MS_GenericName has a few Metasound patches that it references. MS_GenericName was never moved outside of its folder, but it was potentially renamed. We have resaved it since.

There were two Metasound patches that were moved around in the editor and they had redirectors. I fixed up the redirectors but I have no idea if that had any effect. I always have to wait for a full-scale playtest to find out if the issue is still there.

I have two questions so far:

  1. Are there any other insights you can provide into how to fix this issue, or why this issue happens?
  2. In your opinion, can you confirm or deny if fixing up redirectors for Metasound patches will fix the issue?
    1. The original answer mentions that “When MetaSounds have dependencies between them (e.g. a patch is used within a source), they have some UPROPERTY fields which describe those dependencies” and that sometimes those dependency UPROPERTYs get messed up, so I’m curious about where that happens.
    2. So far I only went through FRegistryContainerImpl::BuildAndRegisterGraphFromDocument code to see if there is any point at which graph registration fails. The only suspicious thing is the AssetPath might be different from what we need, but that shouldn’t happen for the Metasound source, it should only happen for Metasound patches that got moved around in our case. Since the log is complaining about Metasound source and not Metasound patch, I’m not sure if that’s the source of the issue. Any insights for this are appreciated.

Thank you.

Hi Anyaleo,

Sorry this issue is still persisting for you.

Since turning on `au.MetaSound.DisableAsyncGraphRegistration 1` did not solve the issue, then it is much more likely that it is an issue with a missing reference.

The log is complaining that while it was creating the MetaSound “MS_FS_asphalty_gritty_jog”, that it didn’t find the asset “MS_boots_concrete_clean_jog_wet”. It then fails to create “MS_FS_asphalty_gritty_jog” because it needs to be able to create all it’s subgraphs in order to create the final graph. But it couldn’t find the subgraph “MS_boots_concrete_clean_jog_wet” because it wasn’t in the node registry.

The link between MS_FS_asphalty_gritty_jog and MS_boots_concrete_clean_jog_wet should be stored on this UPROPERTY of the MS_FS_asphalty_gritty_jog_wet asset.

TSet<TObjectPtr<UObject>> UMetaSoundSource::ReferencedAssetClassObjects

At this point, evidence suggests that MS_FS_asphalty_gritty_jog_wet is missing from that array. If you’re handy with a debugger, try breaking in FMetasoundAssetBase::UpdateAndRegisterForExecution(…). Inside that function there is a call to FMetasoundAssetBase::RegisterAssetDependencies(…). You should be able to see MS_FS_asphalty_gritty_jog causing MS_boots_concrete_clean_jog_wet to get registered as a dependency. If not, it means that the dependency is missing from ReferencedAssetClassObjects. You could also look into adding a couple simple lines of debug logging in RegisterAssetDependencies(…) to simply log what assets are being registered as dependencies to what parent.

`MetasoundAssetBase.cpp

void FMetasoundAssetBase::RegisterAssetDependencies(const Metasound::Frontend::FMetaSoundAssetRegistrationOptions& InRegistrationOptions)
{
using namespace Metasound::Frontend;

TArray<FMetasoundAssetBase*> References = GetReferencedAssets();
for (FMetasoundAssetBase* Reference : References)
{
// Log references for debugging
UE_LOG(LogMetaSound, Verbose, TEXT(“Parent metasound %s registering dependency %s”), *GetOwningAssetName(), *Reference->GetOwningAssetName());

if (InRegistrationOptions.bForceReregister || !Reference->IsRegistered())
{
Reference->UpdateAndRegisterForExecution(InRegistrationOptions);
}
}
}`

If those references are missing from that array, then redirectors won’t be able to do their job when fixing up files during a uasset move. Redirectors DO work if the reference was there in the first place and then the file gets moved in the editor.

What surprises me is that a resave did not fix the issue. There is a step where we fix up all those references in the editor. If you want to try the resave approach again, you can open up both assets (the source & the patch that it fails to find) and then resave both only after they’ve both been opened in the metasound editor.

> "Since the log is complaining about Metasound source and not Metasound patch, I’m not sure if that’s the source of the issue. Any insights for this are appreciated. "

I alluded to this earlier in my response, but the issue is that the source cannot find the patch. So while it does complain about the source, it’s mainly complaining about the source not being able to find the patch. The real issue is to understand why the patch isn’t being registered as a dependency of the source.

I hope this helps.

The call to InitResources() registers the MetaSound, and if a MetaSound never has it’s InitResource method called, it or any metasound within it, may not be registered with the metasound node registry.

From the looks of it, the `DefaultSoundWave` doesn’t get a call to `InitResources()` inside `UCustomSoundNodeWavePlayer::LoadAsset(…)`. Try adding a call to InitResources there.

When you do seamless travel, you say the metasound graphs unregister. I assume this isn’t something you’re doing on purpose, but that it’s the metasound assets getting GC’d. When a MetaSound gets GC’d, it unregisters itself.

It sounds like somewhere you need to make sure that the top level UMetaSoundSource being played is referenced in an object that is going to survive seamless travel. In general, most of this referencing is handled for you as long as you’re either using the existing sound cue or audio component. If you have a custom sound cue node, you’ll need to make sure the metasound is being set as a UPROPERTY somewhere. But what about your AudioComponent? Is that set to be persistent or tied to an object that will survive seamless travel?

Thanks for all the digging Anyaleo,

I’ll work on seeing if we can get a repro and a fix for 5.7. Thanks for your work on this. I can see pretty easily how seamless travel would cause this issue.

My assumption before was that this issue was happening with sounds you wanted to play over the duration of a world transition. For those scenarios we simply tell folks to tie it to the sounds to something that is intentionally going to persist across the transition. Instead, best I can tell these are just sounds that occur often and the registration order issue gets triggered because they happen during seamless travel.

Curious thing is that we haven’t hit this issue internally before on any of our content. I’m not sure if that’s because we aren’t using seamless travel or because of something else in your setup. The call to register a sound is generally triggered the first time you play the sound. Thinking through some short term fixes for y’all:

  • Check when you’re calling `USoundBase::InitResources()` from your custom USoundCue nodes. If this is done during “PostLoad()” or tied to UObject loading in general, you might be able to move it to somewhere in ParseNodes(). There may not be any wiggle room here. I wouldn’t be surprised if you’re already doing this and it’s just the case that footsteps happen all the time.
  • We might be able to detect that the metasound registration failed and simply try again. I’ll have to dig into the code a bit more to see if there is a reasonable spot to do this. My first hunch would be in UMetaSoundSource::CreateSoundGenerator() and to trigger re-registration if the `TSharedPtr<IGraph>` is invalid. But I’ll need to do some experimenting to figure that out.
  • Depending on how memory intensive your project is, you could get by with removing the unregistration call, or simply having a USubsystem which forces all metasounds to be persistent across transitions. MetaSound UObjects are generally in the 10’s of kb, and the USoundWaves associated with them utilize the audio stream cache to keep compressed audio memory in check.

The long term fix for this registration kerfuffle is likely going to be a simple ref-count on those registration calls, or capturing some additional data to determine which UObject instance triggered the register/unregister calls.

Thank you for such a detailed breakdown of the problem! I understand a lot more about what’s happening on the backend now.

I should have mentioned the referenced post is from a different project, I’m not associated with them. Our log only spams a singular error with “Could not find graph with registry graph key”. I’m not getting the other errors or warnings that the previous post was getting. But your breakdown of their log was still very helpful in understanding the situation.

So, here is what I found with the debugger:

  1. We reference our Metasounds from a sound cue -- specifically, we have a custom Wave Player that inherits from a regular USoundNodeWavePlayer
  2. Whenever we call FMetasoundAssetBase::RegisterAssetDependencies, this is what the call stack looks like:

[Image Removed]The call to USoundNodeWavePlayer::LoadAsset comes from our UCustomSoundNodeWavePlayer, and this is what the LoadAsset function looks like:

`void UCustomSoundNodeWavePlayer::LoadAsset(bool bAddToRoot)
{
Super::LoadAsset(bAddToRoot); // this path registeres asset dependancies

// and THIS path ensures we are always referencing a metasound if there is no metasound in the sound wave field
if (GetSoundWave() == nullptr)
{
USoundWave* DefaultSoundWave = GetDefault()->DefaultSoundWave.LoadSynchronous();
SetSoundWave(DefaultSoundWave);
}
}`So, we register Metasound references first. If we find out that the sound cue node doesn’t have a sound wave, then we set the sound wave to be a Metasound.

This part of the code was very suspicious. We only call UMetaSoundSource::InitResources if we have a valid SoundWave. If we don’t have a valid SoundWave, we set it after it’s too late to call InitResources and register Metasound asset dependencies.

But I’m not sure why this wouldn’t break all of the time if that was the issue?

Another thing: this comment in USoundNodeWavePlayer when it InitsResources says there is another place where Metasound and its dependent graphs are registered?

`USoundNodeWavePlayer.cpp (in ParseNodes)

// Don’t init resources when running cook, as this can trigger
// registration of a MetaSound and its dependent graphs.
// Those will instead be registered when the MetaSound itself is cooked (FMetasoundAssetBase::CookMetaSound)
// in a way that does not deal with runtime data like this function does
if (!IsRunningCookCommandlet())
{
SoundWave->InitResources();
}`This comment says the Metasound will be registered when it’s cooked. But then there is a WITH_EDITORONLY_DATA around the CookMetaSound function so I’m not sure how relevant it is to our problem.

Do you think the sound is not playing in our case because of this interaction between Metasounds and sound cues? We just resaved all of our sound cues to try to prevent this problem, so we’ll see if that does anything.

That didn’t work, but we found more information about what’s going wrong.

  1. The issue only happens after you seamless travel between maps.
  2. When the issue happens, au.debug.soundcues 1 shows a massive list of sound cues that is not getting cleaned up, even when no sounds are playing. I suspect the Metasound doesn’t properly reach the “On Finished” node and remains active, which makes the sound cue remain active, too.

We are looking into whether there is something going wrong with registering/unregistering the graphs. When we seamless travel, we immediately unregister the Metasound graphs. We didn’t have the logs for registering when we found this issue, so we don’t know if the graphs ever got registered again.

We are playing Metasounds from inside a sound cue, so I wonder if the initialization process is different and something is going wrong during seamless travel.

Our Metasound is a UPROPERTY on a sound cue. But our audio components and sound cues aren’t being referenced by anything that’s expected to survive seamless travel.

We found out from the last testing that this is what happens with registration:

  1. Old Metasound (from before seamless travel) is queued up to be garbage collected
  2. New Metasound (from after seamless travel) gets re-registered
  3. Old Metasound gets GC’d, but because it’s the same as the New Metasound, the New Metasound gets unregistered too. And we no longer have a Metasound to play

So it sounds like referencing the Metasound on an object that’s set to survive seamless travel is an option. We are a little hesitant with this solution because we are planning on using more Metasounds in the future, and it would be a little janky to always remember to add them to a specific object that survives seamless travel. Do you have any other advice for us at this time?

Thank you for helping us out Phil, this was incredibly helpful. We are going to try calling InitResources() on ParseNodes() and see how that goes.

In terms of our setup, it happens to us because we have a period of time where the Metasound graph is not referenced by anything after seamless travel. The object that references the Metasound comes from the server, and sometimes it takes too long to replicate that object to the client. During that time when the old object got cleaned up and the new object is not replicated yet, nothing references the Metasound so it gets marked for GC. GC doesn’t happen immediately, the new object gets replicated and registers the Metasound, and then GC finally happens and cleans up the Metasound graph. It doesn’t happen always, but I hope that helps with your repro.

Thanks again anyaleo!