Description

Gameplay Ability System

Standalone:

Removing an attribute set and then removing an active gameplay effect that has a modifier for an attribute from that set results in a crash. Currently, one must remove the gameplay effect before removing an attribute set.

Related server-client problem:

Removing a gameplay effect and then removing an attribute set at the same time on the server does not guarantee the same order of operations on the client, since the replication order of SpawnedAttributes and ActiveGameplayEffects is not guaranteed. When SpawnedAttributes replicates to a client before ActiveGameplayEffects this results in an attribute set being removed before the gameplay effect - this results in the same crash as in the standalone case.

Replication order is never guaranteed so this poses a problem for server-side code that calls RemoveSpawnedAttribute and RemoveActiveGameplayEffect at roughly the same time.

Steps to Reproduce

Repro project instructions

In the repro project I've done what's described below and the following hotkeys are bound:

  • 1 = adds attribute set on server
  • 2 = adds gameplay effect on server
  • 3 = remove gameplay effect on server
  • 4 = remove attribute set on server

1 2 3 4 works as intended. 1 2 4 3 causes the crash.

Standalone scenario

  • Create an attribute set MyTempAttributeSet with an attribute MyTempAttribute
  • Create a gameplay effect GE_MyTempEffect that adds +5 MyTempAttribute in its Modifiers. Make its DurationPolicy infinite.
  • Make a hotkey to construct a UMyTempAttributeSet and add it to AbilitySystemComponent by calling ASC->AddSpawnedAttribute. Also make a hotkey to remove that attribute set.
  • Make a hotkey to apply GE_MyTempEffect to self. Also make a hotkey to remove it.

Start PIE as standalone. Try the following order of hotkeys:

  1. Add the attribute set
  2. Add the gameplay effect
  3. Remove the gameplay effect
  4. Remove the attribute set

Observe that everything works as intended. Then try:

  1. Add the attribute set
  2. Add the gameplay effect
  3. Remove the attribute set
  4. Remove the gameplay effect

Observe that a crash happens at step 4, as the gameplay effect removal logic assumes that the attribute set exists.

Second scenario

A bit harder to repro, but this is the source of the user reported crash. When server-code removes a gameplay effect and attribute set in the same tick and in the correct order (first GE, then attribute set) - this can happen out of order on clients since replication order is not guaranteed. To repro this, make all hotkeys execute their logic on server only by introducing server RPCs. In the repro project this is set up.

The two replicated attributes of AbilitySystemComponent that are relevant are ActiveGameplayEffects and SpawnedAttributes.

Callstack

UAbilitySystemComponent::SetNumericAttribute_Internal(const FGameplayAttribute &,float &) AbilitySystemComponent.cpp:342
FActiveGameplayEffectsContainer::InternalUpdateNumericalAttribute(FGameplayAttribute,float,const FGameplayEffectModCallbackData *,bool) GameplayEffect.cpp:2814
FActiveGameplayEffectsContainer::OnAttributeAggregatorDirty(FAggregator *,FGameplayAttribute,bool) GameplayEffect.cpp:2405
UAbilitySystemComponent::OnAttributeAggregatorDirty(FAggregator *,FGameplayAttribute,bool) AbilitySystemComponent.cpp:1785
[Inlined] Invoke(void (UAbilitySystemComponent::*)(FAggregator *, FGameplayAttribute, bool),UAbilitySystemComponent *&,FAggregator *&,const FGameplayAttribute &,const bool &) Invoke.h:66
[Inlined] UE::Core::Private::Tuple::TTupleBase<TIntegerSequence<unsigned int,0,1>,FGameplayAttribute,bool>::ApplyAfter(void (UAbilitySystemComponent::*&)(FAggregator *, FGameplayAttribute, bool),UAbilitySystemComponent *&,FAggregator *&) Tuple.h:321
TBaseUObjectMethodDelegateInstance<0,UAbilitySystemComponent,void __cdecl(FAggregator *),FDefaultDelegateUserPolicy,FGameplayAttribute,bool>::ExecuteIfSafe(FAggregator *) DelegateInstancesImpl.h:550
[Inlined] TMulticastDelegateBase<FDefaultDelegateUserPolicy>::Broadcast(FAggregator *) MulticastDelegateBase.h:204
TMulticastDelegate<void __cdecl(FAggregator *),FDefaultDelegateUserPolicy>::Broadcast(FAggregator *) DelegateSignatureImpl.inl:988
FAggregator::BroadcastOnDirty() GameplayEffectAggregator.cpp:605
[Inlined] FAggregator::RemoveAggregatorMod(FActiveGameplayEffectHandle) GameplayEffectAggregator.cpp:476
FActiveGameplayEffectsContainer::RemoveActiveGameplayEffectGrantedTagsAndModifiers(const FActiveGameplayEffect &,bool) GameplayEffect.cpp:3730
FActiveGameplayEffectsContainer::InternalOnActiveGameplayEffectRemoved(FActiveGameplayEffect &,bool,const FGameplayEffectRemovalInfo &) GameplayEffect.cpp:3703
FActiveGameplayEffect::PreReplicatedRemove(const FActiveGameplayEffectsContainer &) GameplayEffect.cpp:1835
FFastArraySerializer::TFastArraySerializeHelper<FActiveGameplayEffect,FActiveGameplayEffectsContainer>::PostReceiveCleanup<TMap<int,TMap<int,FGuidReferences,FDefaultSetAllocator,TDefaultMapHashableKeyFuncs<int,FGuidReferences,0> >,FDefaultSetAllocator,TDefaultMapHashableKeyFuncs<int,TMap<int,FGuidReferences,FDefaultSetAllocator,TDefaultMapHashableKeyFuncs<int,FGuidReferences,0> >,0> > >(FFastArraySerializer::FFastArraySerializerHeader &,TArray<int,TSizedInlineAllocator<8,32,TSizedDefaultAllocator<32> > > &,TArray<int,TSizedInlineAllocator<8,32,TSizedDefaultAllocator<32> > > &,TMap<int,TMap<int,FGuidReferences,FDefaultSetAllocator,TDefaultMapHashableKeyFuncs<int,FGuidReferences,0> >,FDefaultSetAllocator,TDefaultMapHashableKeyFuncs<int,TMap<int,FGuidReferences,FDefaultSetAllocator,TDefaultMapHashableKeyFuncs<int,FGuidReferences,0> >,0> > &) FastArraySerializer.h:1132
FFastArraySerializer::FastArrayDeltaSerialize_DeltaSerializeStructs<FActiveGameplayEffect,FActiveGameplayEffectsContainer>(TArray<FActiveGameplayEffect,TSizedDefaultAllocator<32> > &,FNetDeltaSerializeInfo &,FActiveGameplayEffectsContainer &) FastArraySerializer.h:1814
FFastArraySerializer::FastArrayDeltaSerialize<FActiveGameplayEffect,FActiveGameplayEffectsContainer>(TArray<FActiveGameplayEffect,TSizedDefaultAllocator<32> > &,FNetDeltaSerializeInfo &,FActiveGameplayEffectsContainer &) FastArraySerializer.h:1191
FActiveGameplayEffectsContainer::NetDeltaSerialize(FNetDeltaSerializeInfo &) GameplayEffect.cpp:4134
FRepLayout::ReceiveCustomDeltaProperty(FReceivingRepState *__restrict,FNetDeltaSerializeInfo &,FStructProperty *) RepLayout.cpp:4612
[Inlined] FNetSerializeCB::ReceiveCustomDeltaProperty(const FRepLayout &,FReceivingRepState *,FNetDeltaSerializeInfo &,FStructProperty *) DataReplication.cpp:262
FObjectReplicator::ReceivedBunch(FNetBitReader &,const FReplicationFlags &,const bool,bool &) DataReplication.cpp:1136
UActorChannel::ProcessBunch(FInBunch &) DataChannel.cpp:3125
UActorChannel::ReceivedBunch(FInBunch &) DataChannel.cpp:2965
UChannel::ReceivedSequencedBunch(FInBunch &) DataChannel.cpp:558
UChannel::ReceivedNextBunch(FInBunch &,bool &) DataChannel.cpp:1022
UChannel::ReceivedRawBunch(FInBunch &,bool &) DataChannel.cpp:670
UNetConnection::ReceivedPacket(FBitReader &,bool) NetConnection.cpp:3530
UNetConnection::ReceivedRawPacket(void *,int) NetConnection.cpp:1881
UIpNetDriver::TickDispatch(float) IpNetDriver.cpp:1294
UNetDriver::InternalTickDispatch(float) NetDriver.cpp:1539
[Inlined] Invoke(void (UNetDriver::*)(float),UNetDriver *&,float &) Invoke.h:66
[Inlined] UE::Core::Private::Tuple::TTupleBase<TIntegerSequence<unsigned int> >::ApplyAfter(void (UNetDriver::*&)(float),UNetDriver *&,float &) Tuple.h:321
TBaseUObjectMethodDelegateInstance<0,UNetDriver,void __cdecl(float),FDefaultDelegateUserPolicy>::ExecuteIfSafe(float) DelegateInstancesImpl.h:550
[Inlined] TMulticastDelegateBase<FDefaultDelegateUserPolicy>::Broadcast(float) MulticastDelegateBase.h:204
TMulticastDelegate<void __cdecl(float),FDefaultDelegateUserPolicy>::Broadcast(float) DelegateSignatureImpl.inl:988
UWorld::Tick(ELevelTick,float) LevelTick.cpp:1352
UEditorEngine::Tick(float,bool) EditorEngine.cpp:1903
UUnrealEdEngine::Tick(float,bool) UnrealEdEngine.cpp:516
FEngineLoop::Tick() LaunchEngineLoop.cpp:5806
[Inlined] EngineTick() Launch.cpp:61
GuardedMain(const wchar_t *) Launch.cpp:195
LaunchWindowsStartup(HINSTANCE__ *,HINSTANCE__ *,char *,int,const wchar_t *) LaunchWindows.cpp:233
WinMain(HINSTANCE__ *,HINSTANCE__ *,char *,int) LaunchWindows.cpp:284
[Inlined] invoke_main() 0x00007ff67dedd9a6
__scrt_common_main_seh() 0x00007ff67dedd985
<unknown> 0x00007ff845c47614
<unknown> 0x00007ff8479626b1

Have Comments or More Details?

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

0
Login to Vote

Fixed
ComponentUE - Gameplay - Gameplay Ability System
Affects Versions5.25.3
Target Fix5.4
Fix Commit28130133
Main Commit28130157
CreatedAug 23, 2023
ResolvedSep 22, 2023
UpdatedOct 2, 2023