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!