GameplayTagQuery displays an outdated 'AutoDescription' after GameplayTag redirects have been applied (until the query is edited and confirmed again)

Hi, I wanted to ask about a problem with GameplayTagQueries in combination with GameplayTag redirects. The problem is basically that the cached ‘AutoDescription’ in the query does not get updated automatically when GameplayTag redirects are used and it still displays the old tags instead. This isn’t a major issue, but can be quite confusing, so I was asked to look into it.

I’ve managed to find a fix for it on our side. I still wanted to bring it up for awareness… and to see if there is a better way than how I approached it.

I’ve compared the serialization of FGameplayTagContainer with that of FGameplayTagQuery. At first glance it seems fairly obvious what the difference is. FGameplayTagContainer::Serialize contains code for replacing redirected tags whereas FGameplayTagQuery::Serialize does not. But it is not so easy. FGameplayTagQuery::Serialize is not getting used at all, as far as I can tell, because TStructOpsTypeTraits<FGameplayTagQuery> does NOT specify ‘WithSerializer’. Instead, it seems the tags inside the query are serialized via ‘normal’ property serialization and the tags themselves handle the redirects in FGameplayTag::PostSerialize.

To be able to detect when the ‘AutoDescription’ inside the query needs to be rebuilt, I needed change the serialization quite a bit:

  • FGameplayTagQuery needs to serialize the tags inside FGameplayTagQuery::Serialize, so the tags don’t handle the redirects on their own.
  • Inside FGameplayTagQuery::Serialize we now ask the GameplayTagsManager to do the redirects for the tags inside the query and if any redirect got applied, also rebuild the AutoDescription
  • For backwards compatibility I needed to introduce a custom serialization version. In older versions it would fallback to serializing tagged properties.

Our new serialize function now looks like this:

bool FGameplayTagQuery::Serialize(FArchive& Ar, UStruct* DefaultsStruct, const void* Defaults)
{
    Ar.UsingCustomVersion(FGameplayTagQueryObjectVersion::GUID);
    if (Ar.CustomVer(FGameplayTagQueryObjectVersion::GUID) < FGameplayTagQueryObjectVersion::UpdateAutoDescriptionAfterRedirect)
    {
        DefaultsStruct->SerializeTaggedProperties(Ar, reinterpret_cast<uint8*>(this), DefaultsStruct, static_cast<uint8*>(const_cast<void*>(Defaults)));

#if WITH_EDITOR
        // Only do redirects for real loads, not for duplicates or recompiles
        if (Ar.IsLoading() && Ar.IsPersistent() && !(Ar.GetPortFlags() & PPF_Duplicate) && !(Ar.GetPortFlags() & PPF_DuplicateForPIE))
        {
            // When 'TagDictionary' gets serialized as a tagged property, the tags themselves already take care of applying redirects via PostSerialize().
            // We don't have any way to detect if any redirects got applies this way, so we need to assume 'AutoDescription' is outdated and needs rebuilding.
            RebuildAutoDescription();
        }
#endif
    }
    else
    {
        Ar << TokenStreamVersion;
        Ar << TagDictionary;
        Ar << QueryTokenStream;

        Ar << UserDescription;
        Ar << AutoDescription;

        // Only do redirects for real loads, not for duplicates or recompiles
        if (Ar.IsLoading() && Ar.IsPersistent() && !(Ar.GetPortFlags() & PPF_Duplicate) && !(Ar.GetPortFlags() & PPF_DuplicateForPIE))
        {
            // The manager takes care of applying redirects and also triggers a rebuild of the AutoDescription if any redirects were found.
            UGameplayTagsManager::Get().GameplayTagQueryLoaded(*this, Ar.GetSerializedProperty());
        }
    }

    return true;
}

Some surrounding changes were also needed for setting everything up. Let us know if there is a better way. Thanks!

Steps to Reproduce

  • create a blueprint class that has a variable of type GameplayTagQuery
  • configure the GameplayTagQuery with some tags
  • rename a GameplayTag that was used in the query
  • restart the editor
  • open the previously created blueprint
  • observe that the AutoDescription of the GameplayTagQuery still displays the old tag before renaming

Hello [mention removed]​,

Thanks for the report. I was able to reproduce the behavior you described following your repro steps. The issue occurs in UE 5.6 and also persists in a recent UE5-Main source build at CL 49551242.

This appears to be an engine-side issue since it should be expected for the AutoDescription to be refreshed after GameplayTag redirects are applied on load.

I’m going to report this internally so the engine team can review it and I’m going to check whether there’s a simpler workaround that could be used in the meantime.

I’ll follow up soon with more information.

Best,

Francisco

Thanks a lot :heart:

Hello [mention removed]​,

Just a small update while the internal bug report is being prepared.

The approach you described looks solid and would be a robust engine solution. However, the serialization change would need to be handled carefully with respect to existing assets.

In the meantime, I tested a lighter workaround that rebuilds the AutoDescription field in FGameplayTagQuery::PostSerialize. This does not require cusotm versioning. This workaround requires the following changes.

In GameplayTagContainer.h:

// Inside FGameplayTagQuery declaration, enable PostSerialize
 
GAMEPLAYTAGS_API void PostSerialize(const FArchive& Ar);
 
...
 
template<>
struct TStructOpsTypeTraits<FGameplayTagQuery> : public TStructOpsTypeTraitsBase2<FGameplayTagQuery>
{
	enum
	{
		WithCopy = true,
		WithPostSerialize = true,
	};
};
 

In GameplayTagContainer.cpp:

void FGameplayTagQuery::PostSerialize(const FArchive& Ar)
{
#if WITH_EDITOR
	if (Ar.IsLoading()
		&& Ar.IsPersistent()
		&& !(Ar.GetPortFlags() & PPF_Duplicate)
		&& !(Ar.GetPortFlags() & PPF_DuplicateForPIE)
		&& !Ar.IsCooking())
	{
		if (UserDescription.IsEmpty())
		{
			if (UEditableGameplayTagQuery* EditableQuery = CreateEditableQuery())
			{
				BuildFromEditableQuery(*EditableQuery);
			}
		}
	}
#endif
}

This worked as expected in a recent UE5-Main source build.

I’m including both approaches in the internal report so the engine team can evaluate and decide on the most appropiate long term fix. I’ll follow up once the report is ready.

Best,

Francisco

That is indeed a bit simpler :+1:. A minor downside is that this rebuilds the AutoDescription more often, even if no tag redirect needs to be applied. But I doubt causes any noticeable overhead in practice.

Hello [mention removed]​,

Yes, with that workaround the AutoDescription rebuild would occur whenever PostSerialize runs for the asset during an editor load but the impact should remain minimal.

I’ve now registered the issue internally so the engine team can review it and determine the most appropriate fix. Once it becomes public, you’ll be able to track it here: Unreal Engine Issues and Bug Tracker (UE\-361557)

In the meantime, you may continue using the proposed workaround or your own solution.

If you need anything else regarding this case, please let me know. Otherwise, I’ll go ahead and close this case.

Best,

Francisco

Thanks a lot for the help! Yes, we can close it.