FGameplayTagBlueprintPropertyMap does not unregister gameplay tag event callbacks until garbage collection

Summary

FGameplayTagBlueprintPropertyMap provides no public way to unbind from the AbilitySystemComponent it was initialized against. Its only cleanup path, Unregister(), is protected and is invoked solely from the struct’s destructor, i.e. when the owning UObject is finally garbage collected. If the owner is uninitialized and marked as garbage before the next GC purge (which is the normal flow for anim instances), the raw delegates registered on the ASC keep firing into the map, and every gameplay tag count change on the ASC logs:

LogAbilitySystem: Warning: FGameplayTagBlueprintPropertyMap::GameplayTagEventCallback called with corrupted RegisteredOwner or corrupted CachedOwner!This is harmless but extremely spammy in any game that drives weapon AnimBP variables from gameplay tags (the pattern used by Lyra).

Root cause

- Initialize() binds with FOnGameplayEffectTagCountChanged::FDelegate::CreateRaw(this, …) (GameplayEffectTypes.cpp, around line 733), so the binding does not go stale when the owner dies. It must be explicitly removed.

- Unregister() is protected (GameplayEffectTypes.h, around line 1413), so the owning class cannot remove the bindings itself, even though the engine gives it the correct hook: NativeUninitializeAnimation() is called right before the instance is marked garbage.

What we did locally

- Made Unregister() public in our engine branch (access specifier change only).

- Called Unregister() from NativeUninitializeAnimation() in every anim instance class that owns one of these maps. This removes the delegates while the ASC is still alive and eliminates the warnings.

- On re-initialization we also call ApplyCurrentTags() after Initialize(), because UAbilitySystemComponent::RegisterAndCallGameplayTagEvent only executes the delegate when the current tag count is greater than zero, so a reused instance can otherwise keep stale property values for tags that were removed while unbound.

Request

Please expose a supported cleanup path in a future release: either make Unregister() public (as we have done), or have UAnimInstance owners tear the map down automatically on uninitialize. We would appreciate this being taken as an engine change so we can drop our local modification.

[Attachment Removed]

Steps to reproduce (UE 5.6.1)

1. Create a UAnimInstance subclass with an FGameplayTagBlueprintPropertyMap UPROPERTY and call Initialize(this, ASC) against the owning character’s ASC once gameplay is ready.

2. Use an AnimBP of that class as a linked anim layer or as the AnimBP of an equipped weapon’s skeletal mesh.

3. Swap or unequip the weapon. USkeletalMeshComponent::ResetLinkedAnimInstances() calls UninitializeAnimation() and then immediately MarkAsGarbage() on the linked instance (SkeletalMeshComponent.cpp, around line 3580 in 5.6.1). The same window exists when the weapon actor is destroyed.

4. Change any gameplay tag count on the character’s ASC before the next GC pass. Each change executes the still-registered delegate; the TWeakObjectPtr owner checks fail because the owner is marked garbage, and the warning above is logged. The spam continues until GC destroys the anim instance and the struct’s destructor finally runs Unregister().

[Attachment Removed]

Thank you for bringing it to our attention! This is definitely a significant oversight and your solution seems to be the most reasonable path forward. We’ve escalated this into a Jira ticket and want to do due diligence in testing around it to make sure we don’t introduce any new gaps.

[Attachment Removed]

Thanks!

[Attachment Removed]