Description

UAbilitySystemComponent::DestroyActiveState() is called when the component is unregistered (OnUnregister) and when its destroyed (OnComponentDestroyed). It performs state cleanup such as canceling abilities and cleaning up ability instances.

The function sets a bool bDestroyActiveStateInitiated to true to ensure the cleanup is only done once. This is never set to false again.

This causes problems when the owning actor goes through a lifecycle where it will exit and re-enter play. Specifically, this causes problems when:

  • Actor reuse: An actor and its components are unregistered because their outer level is hidden, but then shown again so the same actor and components are reregistered. This scenario happens with world partition cell load/unload and level instances (LoadLevelInstance, then toggling ShouldBeVisible).
  • Actor seamless travel persistence: When seamless server-traveling actors can be flagged as persisting across seamless travel. This makes an actor from the previous world persist in the new world and also triggers the actor's OnUnregister and OnRegister.

In both these cases the AbilitySystemComponent re-enters play but when removed again, DestroyActiveState() is skipped so abilities are not canceled and cleaned up.

Expected: DestroyActiveState() should perform cleanup whenever the actor is removed from play, not only the first time.

Steps to Reproduce

See attached repro project:

  • In engine code, temporarily make bDestroyActiveStateInitiated in AbilitySystemComponent protected instead of private
  • Open ASCMainMap and play in PIE. This map will toggle a sublevel containing an ASC on and off. See log that bDestroyActiveStateInitiated is true all the time. As a consequence, DestroyActiveState() is skipped.
  • Open ASCSeamlessTravelTest and play in PIE. This map will seamless travel to itself and have a map actor with an ASC persist each time. Observe that its bDestroyActiveStateInitiated remains true when it is registered to the new world. As a consequence, DestroyActiveState() is skipped.

Expected: DestroyActiveState() should not be skipped even when the ASC re-enters play.

Have Comments or More Details?

There's no existing public thread on this issue, so head over to Questions & Answers just mention UE-199290 in the post.

0
Login to Vote

Fixed
ComponentUE - Gameplay - Gameplay Ability System
Affects Versions5.3
Target Fix5.4
Fix Commit29311191
Main Commit29311229
CreatedOct 30, 2023
ResolvedNov 1, 2023
UpdatedDec 1, 2023
View Jira Issue