Description

Context

The property 'DefaultStartingData' in AbilitySystemComponent can be configured from blueprint to spawn default attribute sets. This happens OnRegister. For runtime spawned actors like pawns, both the server and client will individually construct those attribute set objects and store them in SpawnedAttributes. Eventually, the client will forget its locally created one from SpawnedAttributes and retain the object replicated over from the server.

Problem

When GameplayEffects (GE) are applied on the server immediately on character spawn, the GE can replicated over to the client via ActiveGameplayEffects and

FActiveGameplayEffectsContainer::NetDeltaSerialize 

This can happen while the client is still using its locally created attribute set. ActiveGameplayEffects then contains a state for those attributes.

Currently, OnRep_SpawnedAttributes detects removed attribute sets by object and clears the state for all contained attributes. It doesn't account for the situation where an attribute set is replicated down from the server and replaces an equivalent client-side one. OnRep_SpawnedAttributes then calls 

ActiveGameplayEffects.CleanupAttributeAggregator(Attribute); 

on attributes that are still in play.

Proposal

The call to CleanupAttributeAggregator(Attribute) should be skipped for attributes that are still in play. Any relevant state should be migrated from the temp client attribute set to the attribute set replicated over from the server.

Steps to Reproduce

Given a character blueprint with an AbilitySystemComponent (ASC) in Full replication mode:

  • Prepare a custom AttributeSet class 'MyAttributeSet' with an attribute 'MyAttribute'
  • Override UAttributeSet::PostAttributeChange to print the net role via GetOwningActor()->HasAuthority() and the latest value on any attribute change:
void UMyAttributeSet::PostAttributeChange(const FGameplayAttribute& Attribute, float OldValue, float NewValue)
{
    const FString NetRoleName = GetOwningActor() ? GetOwningActor()->HasAuthority() ? TEXT("Server") : TEXT("Client") : TEXT("Unresolved");
    UE_LOG(LogTemp, Warning, TEXT("[%s] Value of %s changed: %.2f -> %.2f"), *NetRoleName, *Attribute.AttributeName, OldValue, NewValue);
} 
  • Prepare two GameplayEffects GE_Initial, GE_Secondary that modify MyAttribute to add 10 and override 123, respectively.
  • In the character blueprint
    • Configure the ASC's DefaultStartingData to have a MyAttributeSet and a DataTable initializing MyAttribute to 0.0. This is only meant to initialize the attribute set object.
    • On BeginPlay on the server (Has Authority), apply GE_Initial. MyAttribute will then be 10.
    • Implement Debug Key Q and E to do the following:
      • Q: calls a server RPC to apply GE_Secondary to self and stores the handle
      • E: calls a server RPC to remove GE_Secondary by handle
  • Start PIE with dedicated server and client
  • On BeginPlay GE_Initial is applied and the attribute value should be 10 on both server and client.
  • Press Q. This applies GE_Secondary which overrides the attribute value to be 123.
  • Press E. 
  • Observe: This sets the attribute value to be 0.
  • Expected: This removes GE_Secondary which should return the attribute value to be 10.
Callstack

Non-fatal, but this is where the client erronously clears an aggregator for an attribute set:

>    UnrealEditor-GameplayAbilities.dll!UAbilitySystemComponent::OnRep_SpawnedAttributes(const TArray<UAttributeSet *,TSizedDefaultAllocator<32>> & PreviousSpawnedAttributes) Line 3115    C++
     UnrealEditor-GameplayAbilities.dll!UAbilitySystemComponent::execOnRep_SpawnedAttributes(UObject * Context, FFrame & Stack, void * const Z_Param__Result) Line 3135    C++
     UnrealEditor-CoreUObject.dll!UFunction::Invoke(UObject * Obj, FFrame & Stack, void * const Z_Param__Result) Line 7238    C++
     UnrealEditor-CoreUObject.dll!UObject::ProcessEvent(UFunction * Function, void * Parms) Line 2144    C++
     [Inline Frame] UnrealEditor-Engine.dll!EnumHasAnyFlags(ERepParentFlags) Line 38    C++
     UnrealEditor-Engine.dll!FRepLayout::CallRepNotifies(FReceivingRepState * RepState, UObject * Object) Line 4697    C++
     UnrealEditor-Engine.dll!FObjectReplicator::CallRepNotifies(bool bSkipIfChannelHasQueuedBunches) Line 2329    C++
     UnrealEditor-Engine.dll!UActorChannel::ProcessBunch(FInBunch & Bunch) Line 3339    C++
     UnrealEditor-Engine.dll!UActorChannel::ReceivedBunch(FInBunch & Bunch) Line 3092    C++
     UnrealEditor-Engine.dll!UChannel::ReceivedSequencedBunch(FInBunch & Bunch) Line 576    C++
     UnrealEditor-Engine.dll!UChannel::ReceivedNextBunch(FInBunch & Bunch, bool & bOutSkipAck) Line 1039    C++
     UnrealEditor-Engine.dll!UChannel::ReceivedRawBunch(FInBunch & Bunch, bool & bOutSkipAck) Line 687    C++
     UnrealEditor-Engine.dll!UNetConnection::DispatchPacket(FBitReader & Reader, int PacketId, bool & bOutSkipAck, bool & bOutHasBunchErrors) Line 3841    C++
     UnrealEditor-Engine.dll!UNetConnection::ReceivedPacket(FBitReader & Reader, bool bIsReinjectedPacket, bool bDispatchPacket) Line 3217    C++
     UnrealEditor-Engine.dll!UNetConnection::ReceivedRawPacket(void * InData, int Count) Line 2067    C++
     UnrealEditor-OnlineSubsystemUtils.dll!UIpNetDriver::TickDispatch(float DeltaTime) Line 1301    C++
     UnrealEditor-Engine.dll!UNetDriver::InternalTickDispatch(float DeltaSeconds) Line 2059    C++
     [Inline Frame] UnrealEditor-Engine.dll!Invoke(void(UNetDriver::*)(float)) Line 66    C++
     [Inline Frame] UnrealEditor-Engine.dll!UE::Core::Private::Tuple::TTupleBase<TIntegerSequence<unsigned int>>::ApplyAfter(void(UNetDriver::*)(float) &) Line 317    C++
     UnrealEditor-Engine.dll!TBaseUObjectMethodDelegateInstance<0,UNetDriver,void __cdecl(float),FDefaultDelegateUserPolicy>::ExecuteIfSafe(float <Params_0>) Line 667    C++
     [Inline Frame] UnrealEditor-Engine.dll!TMulticastDelegateBase<FDefaultDelegateUserPolicy>::Broadcast(float) Line 257    C++
     UnrealEditor-Engine.dll!TMulticastDelegate<void __cdecl(float),FDefaultDelegateUserPolicy>::Broadcast(float <Params_0>) Line 1079    C++
     UnrealEditor-Engine.dll!UWorld::Tick(ELevelTick TickType, float DeltaSeconds) Line 1328    C++
     UnrealEditor-UnrealEd.dll!UEditorEngine::Tick(float DeltaSeconds, bool bIdleMode) Line 2138    C++
     UnrealEditor-UnrealEd.dll!UUnrealEdEngine::Tick(float DeltaSeconds, bool bIdleMode) Line 550    C++
     UnrealEditor.exe!FEngineLoop::Tick() Line 5849    C++
 

Have Comments or More Details?

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

0
Login to Vote

Fixed
Fix Commit37889013
CreatedOct 23, 2024
ResolvedNov 7, 2024
UpdatedNov 8, 2024
View Jira Issue