Inside FGameplayTagQuery
What is FGameplayTagQuery
Quoted from source code comments:
An
FGameplayTagQueryis a logical query that can be run against anFGameplayTagContainer. A query that succeeds is said to “match”. Queries arelogical expressionsthat can test the intersection properties of another tag container (all, any, or none), or the matching state of a set of sub-expressions (all, any, or none). This allows queries to be arbitrarily recursive and very expressive. For instance, if you wanted to test if a given tag container contained tags ((A && B) || (C)) && (!D), you would construct your query in the form ALL( ANY( ALL(A,B), ALL(C) ), NONE(D) )
Why use FGameplayTagQuery
Because when using FGameplayTagQuery for logical matching, the number of tags and matching logic can be arbitrary, and it supports nesting on logic, unlike:
FGameplayTaglimits the use of only 1 tag (although in addition to match itself, it can also be used to match parent tags)FGameplayTagContainerhas only limited matching logic (which is one of AND, OR, NOT, depends on how the code is used)- Before
FGameplayTagRequirementswas added to theFGameplayTagRequirements::TagQuerymember, it had only twoFGameplayTagContainermembers, corresponding to the “AND” and “NOT” matching logic, which is still limited (after the time of TagQuery member added, you can useFGameplayTagRequirements ::ConvertTagFieldsToTagQueryto obtain aQueryobject which is the logical combination of two tag containers)
Implementation of FGameplayTagQuery
data structure
-
TokenStreamVersionversion number, retains data to facilitate subsequent possible implementation changes, corresponding to the enumeration typeEGameplayTagQueryStreamVersion -
TagDictionaryThe tag array after deduplication, which comes from the tags that need to be used in logical expressions -
QueryTokenStreamis a set of metadata that stores theversion number(redundant storage), whether there is a logical expression expression, the logical expression type, the number of tags used, and the index inTagDictionary. It is the key to achieving memory-efficient and fast evaluation. -
UserDescriptionstring, customized description information -
AutoDescriptionstring, automatically generated description information
Generation method
Use C++ to construct query objects
Use the Builder Pattern API to construct logical expressions:
FGameplayTagQuery TagQuery;
const FGameplayTagContainer TagContainerA{};
const FGameplayTagContainer TagContainerB{};
const FGameplayTag TagC{};
TagQuery.Build(FGameplayTagQueryExpression().AllExprMatch()
.AddExpr(FGameplayTagQueryExpression().AnyTagsMatch().AddTags(TagContainerA))
.AddExpr(FGameplayTagQueryExpression().NoExprMatch()
.AddExpr(FGameplayTagQueryExpression().NoTagsMatch().AddTags(TagContainerB))
.AddExpr(FGameplayTagQueryExpression().AnyTagsMatch().AddTag(TagC))), FString{TEXTVIEW("Test Logic")});
// Randomly written logic, it is not recommended to try to understand it
// I use indentation levels to represent nesting levels, and one line in each level defines a logical expression
Brief process of FGameplayTagQuery::Build:
- Write the version number and user description information, and reset those key data
- Write the “version number” and “whether it contains a logical expression” information to elements 0 and 1 of
QueryTokenStream - Parse the
logical expressionand write the enumeration of the expression type inQueryTokenStream, which is of typeEGameplayTagQueryExprType. For “non-nested expression”, write the array number of tags used by it, then add each tag toTagDictionarywhich is deduplicated, and write the index; for “nested expression”, parsing it recursively
To put it simply, the Build process uses depth-first traversal to flatten the logical expressions of the tree structure into an array.
Use the editor to construct the query object
The underlying logic is consistent with the C++ structure, except that because FGameplayTagQueryExpression is not USTRUCT, and InstancedStruct has not yet been born, it is “mirrored” with UObject (personal guess), so there are UEditableGameplayTagQueryExpression and related types to support expression nesting during editing and providing better debugging information, you can refer to FGameplayTagQuery::BuildFromEditableQuery
FGameplayTagQuery::Matches, test the logical expressions
The auxiliary type FQueryEvaluator is used to hold the immutable reference of TagQuery, record the current metadata index and detect whether there are read errors. According to the read expression type, the corresponding logical definition is executed. Every time the token array is read, it will detect whether there is a read error. You can refer to FQueryEvaluator::EvalExpr
Conclusion
I feel that the implementation of FGameplayTagQuery is quite clever and provides powerful matching logic, which is suitable for any needs that require Tag matching functionality. However, the editor logic contains code duplication, is not elegant enough, and the detection of reading errors is not rigorous, so the follow-up, frequent testing is not that necessary. But overall, flaws do not cover up strengths.