Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

UE5CoroGAS: "Overlapping ability activations with the same prediction" key when triggering ability from client #30

Closed
JonathanIlk opened this issue Aug 13, 2024 · 11 comments

Comments

@JonathanIlk
Copy link

The Problem

When executing a UUE5CoroGameplayAbility from the client the UUE5CoroGameplayAbility::CoroutineStarting throws an exception "Overlapping ability activations with the same prediction key".
This happens when the ExecuteAbility function contains a co_await and the function resumes execution after waiting.

Executing the same function from the server works as expected.

Implementation

This happens on UE5Coro 1.10.2 and UE5Coro 2 Preview 2. I upgraded my project to UE5Coro 2 to make sure the bug is still happening in the new version, so all examples from now on will be for UE5Coro 2.

Step 1:
Trigger the GameplayAbility by sending a GameplayEvent:

UAbilitySystemBlueprintLibrary::SendGameplayEventToActor(SourceCharacter, Action->ActionEventTag, EventData);

Step 2:
Implement the ExecuteAbility for the UUE5CoroGameplayAbility in the following way:

UE5Coro::GAS::FAbilityCoroutine UCharacterActionAbility::ExecuteAbility(FGameplayAbilitySpecHandle Handle, const FGameplayAbilityActorInfo* ActorInfo, FGameplayAbilityActivationInfo ActivationInfo, const FGameplayEventData* TriggerEventData)
{
	UE_LOG(Interactions, Warning, TEXT("CharacterActionAbility about to wait 1 Second"));
	co_await UE5Coro::Latent::Seconds(1);
	UE_LOG(Interactions, Warning, TEXT("CharacterActionAbility just waited 1 Second"));
}

Step 3:
Start Play in Editor session with Number of Players = 2 and Netmode = Play as Listen Server
Execute the GameplayAbility on the Client.

Expected Result:
Ability is executed on client & server correctly.

Actual Result:
The ability execution breaks with error "Overlapping ability activations with the same prediction key" because of this line in UUE5CoroGameplayAbility::CoroutineStarting:

	checkf(!Activations->Contains(GCurrentPredictionKey),
	       TEXT("Overlapping ability activations with the same prediction key"));

Removing co_await

Removing the co_await from ExecuteAbility fixes this issue. Which makes me think that the cause is somewhere in the logic where the Ability execution resumes after co_await. Maybe the Ability is then treated as fresh ability and sent to the server once again which then complains about duplicate Prediction keys.

P.S.: Thanks for this plugin, I'm trying to write as much of my gameplay logic in C++ as it feels more maintainable to me having my logic in code. Without this plugin it was an absolute mess waiting for all those delegates, with this plugin the code is way cleaner.

@landelare
Copy link
Owner

Thank you for the bug report, and I'm glad you find the plugin useful. I have an idea of what the problem could be (everyone loves multiplayer single-process PIE...), but I'm having trouble reproducing this locally.

I filled in some of the blanks: made an ability that's activated by a tag, added the missing CommitAbility call so that it doesn't fail to activate, etc., but I'm not hitting the checkf.

Could you please provide additional information? Some things that I'd like to know:

  • What version of Unreal are you using?
  • What platform are you using and what compiler?
  • What's your ability's instancing policy?
  • Where and how exactly are you sending that gameplay event? Is it in response to something?
  • What kind of actor is receiving the event (PC, AIC, Pawn, ...)? If the ASC is on another actor, what is that actor?
  • What are the net modes of the actors involved?
  • Is the problem still present if you Play As Client instead of Play As Listen Server?

Please note that implementing ExecuteAbility with a subroutine is undefined behavior.

@JonathanIlk
Copy link
Author

Hey thanks for your quick reply.

In my case the CommitAbility seems to be not needed as the GameplayAbility activates regardless. Maybe there is something wrong in the way I set up my GAS which causes this issue.

What version of Unreal are you using?

Unreal Version: 5.4.2-33871570+++UE5+Release-5.4

What platform are you using and what compiler?

Platform: Windows 10 (22H2) [10.0.19045.4651] (x86_64)
Visual Studio 2022 compiler version 14.41.34119

What's your ability's instancing policy?

NetExecutionPolicy = EGameplayAbilityNetExecutionPolicy::LocalPredicted;
InstancingPolicy = EGameplayAbilityInstancingPolicy::InstancedPerActor;

Where and how exactly are you sending that gameplay event? Is it in response to something?

Im sending the Gameplay Event in response to a user's input action. Each Character has a InteractionsCharacterManager component and each Player additionally has a InteractionsPlayerManager Component.
The PlayerManager handles the input and then calls the CharacterManager to actually perform the CharacterAction where a CharacterAction is basically just a holder for the GameplayTag.

InteractionsPlayerManager.cpp:

void UInteractionsPlayerManager::HandleInputSelectInteraction(const FInputActionValue& InputActionValue)
{
	if (UInteractableContextMenuWidget* Widget = GetInteractionsMenuWidget())
	{
		if(UCharacterAction* Action = Widget->GetHoveredAction())
		{
			CharacterManager->PerformAction(Action, DukesCharacter);
			DukesCharacter->WidgetsController->RemoveHUDWidget(InteractionsMenuWidgetClass);
		}
	}
}

InteractionsCharacterManager.cpp:
This sends the GameplayEvent in a coroutine which enables me to wait until the Action is finished in the caller.

UE5Coro::TCoroutine<bool> UInteractionsCharacterManager::PerformAction(UCharacterAction* Action, ADukesCharacter* SourceCharacter)
{
	if(!Action)
	{
		UE_LOG(LogTemp, Error, TEXT("CharacterActionManager::PerformAction: Action is null!"));
		co_return false;
	}
	if(!SourceCharacter)
	{
		UE_LOG(LogTemp, Error, TEXT("CharacterActionManager::PerformAction: SourceCharacter is null!"));
		co_return false;
	}
	if(!Action->IsActionExecutionPossible(SourceCharacter))
	{
		UE_LOG(LogTemp, Error, TEXT("CharacterActionManager::PerformAction: Character %s tried to execute Action %s, but execution is not possible!"),
			*UKismetSystemLibrary::GetDisplayName(SourceCharacter),
			*UKismetSystemLibrary::GetDisplayName(Action));
		co_return false;
	}
	
	DUKES_LOG(LogTemp, Display, TEXT("CharacterActionManager::Sending Gameplay Event %s to character %s"), *Action->ActionEventTag.ToString(), *SourceCharacter->GetName());
	
	FGameplayEventData EventData;
	EventData.Instigator = SourceCharacter;
	EventData.Target = Action->GetOwner();
	EventData.OptionalObject = Action;

	ActionBeingPerformed = Action;
	UAbilitySystemBlueprintLibrary::SendGameplayEventToActor(SourceCharacter, Action->ActionEventTag, EventData);
	co_await OnPerformFinished;
	co_return true;
}

The GameplayAbility itself marks itself as triggered through GameplayTag by calling this in its constructor:

void UDukesGameplayAbility::InitWithTagTrigger(FGameplayTag TriggerTag)
{
	this->ActionEventTag = TriggerTag;
	FAbilityTriggerData AbilityTriggerData;
	AbilityTriggerData.TriggerTag = TriggerTag;
	AbilityTriggerData.TriggerSource = EGameplayAbilityTriggerSource::GameplayEvent;
	AbilityTriggers = {AbilityTriggerData};
}

What kind of actor is receiving the event (PC, AIC, Pawn, ...)? If the ASC is on another actor, what is that actor?

The Actor receiving the event is a PlayerCharacter. The ASC is placed on the Actor itself.

	AbilitySystemComponent = CreateDefaultSubobject<UCharacterAbilitySystemComponent>(TEXT("AbilitySystemComponent"));
	AbilitySystemComponent->SetIsReplicated(true);
	AbilitySystemComponent->SetReplicationMode(EGameplayEffectReplicationMode::Mixed);

What are the net modes of the actors involved?

Calling GetNetMode() in the Character class:
The NetMode of both PlayerCharacters (his own PC where the execution fails, and the servers PC) for the client is ENetMode::NM_Client while for the Server the NetMode for both is ENetMode::NM_ListenServer.

Is the problem still present if you Play As Client instead of Play As Listen Server?

When starting PIE as client the problem still occurs.
I have also tried starting as standalone in PIE and connecting via the mainmenu of my game. The Problem still occurs.

Please note that implementing ExecuteAbility with a subroutine is undefined behavior.

Could you elaborate on this a little more? A subroutine in C++ is just a normal function right? In my case im implementing ExecuteAbility with a coroutine, since I'm returning UE5Coro::GAS::FAbilityCoroutine and using co_await/co_return which turns the subroutine into a coroutine?

@landelare
Copy link
Owner

Thank you for the additional information, I've managed to reproduce the issue.

Could you elaborate on this a little more? A subroutine in C++ is just a normal function right?

Yes. You're almost certainly doing this correctly, but since you mentioned that "Removing the co_await from ExecuteAbility fixes this issue." I thought I'd highlight this as a potential pitfall when working with UE5CoroGAS.

@landelare
Copy link
Owner

@JonathanIlk I have an attempted bugfix on the next branch. Although I couldn't break it in my test environment using various combinations of instancing policies, single-process/multi-process, PIE/standalone, etc., I'm not confident that it fixes your entire original issue.

Could you please test how it behaves in your project?

I've added some logging to UUE5CoroGameplayAbility. I'd like to know if there are any leaks in Activations being logged (the numbers should eventually return to 0, or the number of your permanently-active ability coroutines, if you have any). Other than this, I'm worried about abilities not ending, abilities not resuming after co_awaits, and new checks being hit.

@JonathanIlk
Copy link
Author

Hey, thanks for the quick reply and the even quicker bugfix.

It seems like the issue with the duplicate PredictionKey is fixed now. However there is another issue where the Coroutine execution after a co_await sometimes does not work on the server.
Here are my logs:

[0529.48][609]LogUE5CoroGAS: Warning: [UE5CoroGAS Temp] Start [4/3]
[0529.48][609]Interactions: Warning: Client-HarvestPlantActionAbility_0:CharacterActionAbility about to wait 1 Second
[0529.65][616]LogUE5CoroGAS: Warning: [UE5CoroGAS Temp] Start [4/0]
[0529.65][616]Interactions: Warning: Server-HarvestPlantActionAbility_0:CharacterActionAbility about to wait 1 Second
[0530.49][649]Interactions: Warning: Client-HarvestPlantActionAbility_0:CharacterActionAbility just waited 1 Second
[0530.49][649]LogUE5CoroGAS: Warning: [UE5CoroGAS Temp] End [4/3]
[0530.49][649]LogUE5CoroGAS: Warning: [UE5CoroGAS Temp] Activations 2->1
[0530.63][655]LogUE5CoroGAS: Warning: [UE5CoroGAS Temp] End [4/0]
[0530.63][655]LogUE5CoroGAS: Warning: [UE5CoroGAS Temp] Activations 1->0
[0530.63][655]LogUE5CoroGAS: Warning: [UE5CoroGAS Temp] End [4/0]
[0530.63][655]LogUE5CoroGAS: Warning: [UE5CoroGAS Temp] Activations 0->0

[0567.17][226]LogUE5CoroGAS: Warning: [UE5CoroGAS Temp] Start [6/5]
[0567.17][226]Interactions: Warning: Client-HarvestPlantActionAbility_0:CharacterActionAbility about to wait 1 Second
[0567.31][231]LogUE5CoroGAS: Warning: [UE5CoroGAS Temp] Start [6/0]
[0567.31][231]Interactions: Warning: Server-HarvestPlantActionAbility_0:CharacterActionAbility about to wait 1 Second
[0568.20][264]Interactions: Warning: Client-HarvestPlantActionAbility_0:CharacterActionAbility just waited 1 Second
[0568.20][264]LogUE5CoroGAS: Warning: [UE5CoroGAS Temp] End [6/5]
[0568.20][264]LogUE5CoroGAS: Warning: [UE5CoroGAS Temp] Activations 2->1
[0568.29][267]Interactions: Warning: Server-HarvestPlantActionAbility_0:CharacterActionAbility just waited 1 Second
[0568.29][267]LogUE5CoroGAS: Warning: [UE5CoroGAS Temp] End [6/0]
[0568.29][267]LogUE5CoroGAS: Warning: [UE5CoroGAS Temp] Activations 1->0

[0697.89][286]LogUE5CoroGAS: Warning: [UE5CoroGAS Temp] Start [8/7]
[0697.89][286]Interactions: Warning: Client-HarvestPlantActionAbility_0:CharacterActionAbility about to wait 1 Second
[0698.05][291]LogUE5CoroGAS: Warning: [UE5CoroGAS Temp] Start [8/0]
[0698.05][291]Interactions: Warning: Server-HarvestPlantActionAbility_0:CharacterActionAbility about to wait 1 Second
[0698.90][314]Interactions: Warning: Client-HarvestPlantActionAbility_0:CharacterActionAbility just waited 1 Second
[0698.90][314]LogUE5CoroGAS: Warning: [UE5CoroGAS Temp] End [8/7]
[0698.90][314]LogUE5CoroGAS: Warning: [UE5CoroGAS Temp] Activations 2->1
[0699.03][318]LogUE5CoroGAS: Warning: [UE5CoroGAS Temp] End [8/0]
[0699.03][318]LogUE5CoroGAS: Warning: [UE5CoroGAS Temp] Activations 1->0
[0699.03][318]LogUE5CoroGAS: Warning: [UE5CoroGAS Temp] End [8/0]
[0699.03][318]LogUE5CoroGAS: Warning: [UE5CoroGAS Temp] Activations 0->0

My ExecuteAbility looks like this:

UE5Coro::GAS::FAbilityCoroutine UCharacterActionAbility::ExecuteAbility(FGameplayAbilitySpecHandle Handle, const FGameplayAbilityActorInfo* ActorInfo, FGameplayAbilityActivationInfo ActivationInfo, const FGameplayEventData* TriggerEventData)
{
	CommitAbility(Handle, ActorInfo, ActivationInfo);
	DUKES_LOG(Interactions, Warning, TEXT("CharacterActionAbility about to wait 1 Second"));
	co_await UE5Coro::Latent::Seconds(1);
	DUKES_LOG(Interactions, Warning, TEXT("CharacterActionAbility just waited 1 Second"));
	co_return;
}

As you can see for the first and third attempt the second DUKES_LOG is missing on the server.
However, for the second attempt it worked correctly.
The occurence of this seems to be very random. In some cases I start a PIE session and this error occurs right away with the first activation (as in the log at the top). Sometimes it works correctly 10 times and then the error occurs.
The thing in common for all error attempts seems to be that the Ability with the predictionkey.base = 0 is ended twice.

@landelare
Copy link
Owner

Could you please include LogAbilitySystem messages in the log? Ideally at Verbose level.

I can produce similar "mismatched" ability activations using pure BP (Event ActivateAbilityFromEvent -> CommitAbility -> Print String -> Task Wait Delay -> Print String -> End Ability). If you're seeing the same thing, then this is just UE5CoroGAS working as intended and exposing engine behavior.

@JonathanIlk
Copy link
Author

JonathanIlk commented Aug 16, 2024

Logs with log LogAbilitySystem VeryVerbose:

[0389.26][151]LogUE5CoroGAS: Warning: [UE5CoroGAS Temp] Start [10/9]
[0389.26][151]Dukes_Interactions: Warning: Client-HarvestPlantActionAbility_0:CharacterActionAbility about to wait 1 Second
[0389.26][151]LogAbilitySystem: PlayerCharacter_C_1: Activated [29] HarvestPlantActionAbility_0. Level: 1. PredictionKey: [10/9].
[0389.46][156]LogUE5CoroGAS: Warning: [UE5CoroGAS Temp] Start [10/0]
[0389.46][156]Dukes_Interactions: Warning: Server-HarvestPlantActionAbility_0:CharacterActionAbility about to wait 1 Second
[0389.46][156]LogAbilitySystem: PlayerCharacter_C_1: Activated [29] HarvestPlantActionAbility_0. Level: 1. PredictionKey: [10/0].
[0389.61][159]LogAbilitySystem: Verbose: PlayerCharacter_C_1: Server Confirmed [29] Default__HarvestPlantActionAbility. PredictionKey: [10/0]
[0389.66][160]LogAbilitySystem: Verbose: FReplicatedPredictionKeyItem::OnRep [10/0]
[0390.30][173]Dukes_Interactions: Warning: Client-HarvestPlantActionAbility_0:CharacterActionAbility just waited 1 Second
[0390.30][173]LogAbilitySystem: PlayerCharacter_C_1: Ended [29] HarvestPlantActionAbility_0. Level: 1. WasCancelled: 0.
[0390.30][173]LogUE5CoroGAS: Warning: [UE5CoroGAS Temp] End [10/9]
[0390.30][173]LogUE5CoroGAS: Warning: [UE5CoroGAS Temp] Activations 2->1
[0390.42][176]Dukes_Interactions: Warning: Server-HarvestPlantActionAbility_0:CharacterActionAbility just waited 1 Second
[0390.42][176]LogAbilitySystem: PlayerCharacter_C_1: Ended [29] HarvestPlantActionAbility_0. Level: 1. WasCancelled: 0.
[0390.42][176]LogUE5CoroGAS: Warning: [UE5CoroGAS Temp] End [10/0]


[0390.42][176]LogUE5CoroGAS: Warning: [UE5CoroGAS Temp] Activations 1->0
[0391.62][204]LogUE5CoroGAS: Warning: [UE5CoroGAS Temp] Start [12/11]
[0391.62][204]Dukes_Interactions: Warning: Client-HarvestPlantActionAbility_0:CharacterActionAbility about to wait 1 Second
[0391.62][204]LogAbilitySystem: PlayerCharacter_C_1: Activated [29] HarvestPlantActionAbility_0. Level: 1. PredictionKey: [12/11].
[0391.83][208]LogUE5CoroGAS: Warning: [UE5CoroGAS Temp] Start [12/0]
[0391.83][208]Dukes_Interactions: Warning: Server-HarvestPlantActionAbility_0:CharacterActionAbility about to wait 1 Second
[0391.83][208]LogAbilitySystem: PlayerCharacter_C_1: Activated [29] HarvestPlantActionAbility_0. Level: 1. PredictionKey: [12/0].
[0391.94][210]LogAbilitySystem: Verbose: PlayerCharacter_C_1: Server Confirmed [29] Default__HarvestPlantActionAbility. PredictionKey: [12/0]
[0391.94][210]LogAbilitySystem: Verbose: FReplicatedPredictionKeyItem::OnRep [12/0]
[0392.65][225]Dukes_Interactions: Warning: Client-HarvestPlantActionAbility_0:CharacterActionAbility just waited 1 Second
[0392.65][225]LogAbilitySystem: PlayerCharacter_C_1: Ended [29] HarvestPlantActionAbility_0. Level: 1. WasCancelled: 0.
[0392.65][225]LogUE5CoroGAS: Warning: [UE5CoroGAS Temp] End [12/11]
[0392.65][225]LogUE5CoroGAS: Warning: [UE5CoroGAS Temp] Activations 2->1
[0392.81][229]LogAbilitySystem: PlayerCharacter_C_1: Ended [29] HarvestPlantActionAbility_0. Level: 1. WasCancelled: 0.
[0392.81][229]LogUE5CoroGAS: Warning: [UE5CoroGAS Temp] End [12/0]
[0392.81][229]LogUE5CoroGAS: Warning: [UE5CoroGAS Temp] Activations 1->0
[0392.81][229]LogAbilitySystem: Verbose: IsEndAbilityValid returning false on Ability HarvestPlantActionAbility_0 due to EndAbility being called multiple times
[0392.81][229]LogUE5CoroGAS: Warning: [UE5CoroGAS Temp] End [12/0]
[0392.81][229]LogUE5CoroGAS: Warning: [UE5CoroGAS Temp] Activations 0->0

If this behavior also exists when using GAS without UE5Coro, I would consider this fixed. Can't expect this library to fix Engine bugs. Thanks for your quick help!

@landelare
Copy link
Owner

If this behavior also exists when using GAS without UE5Coro

Just to confirm, what happens in your project if you use a pure BP GA with the same replication/instancing/etc. policies? My message above describes the BP event graph that I was using for testing.

UE5CoroGAS won't behave 100% identically to a pure GA, e.g., it guarantees that your coroutines' local variables will be destroyed properly, instead of just throwing away the BP mid-execution.

@JonathanIlk
Copy link
Author

JonathanIlk commented Aug 18, 2024

grafik

Yup, same issue. Sometimes it works correctly sometimes it does not. (Screenshot shows 2 executions)

grafik

Did you find a configuration where this works as expected?

@landelare
Copy link
Owner

Yup, same issue. Sometimes it works correctly sometimes it does not.

Thank you for confirming.

Did you find a configuration where this works as expected?

Per-execution instancing seems to work better, although I haven't exhaustively tested if it avoided this specific issue.

@landelare
Copy link
Owner

The fix has shipped in 1.10.3 and 2.0.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants