We’re hitting an ensure on CurrentActorInfo in GetAvatarActorFromActorInfo during client join/rejoin in our multiplayer game (listen server host). The root cause is a replication timing window during FastArrayDeltaSerialize of FGameplayAbilitySpecContainer.
When a client joins, all ability specs are added to the ActivatableAbilities array, and then PostReplicatedAdd fires sequentially for each spec. During PostReplicatedAdd for spec A, our game code broadcasts a delegate from OnGiveAbility. A UI widget handles that delegate and calls GetGameplayAbilityFromSpecHandle for a different ability (spec B) whose spec is already in the array but whose PostReplicatedAdd hasn’t fired yet, so no instance has been created.
GetGameplayAbilityFromSpecHandle in AbilitySystemBlueprintLibrary.cpp calls GetPrimaryInstance, gets null, and falls back to returning AbilitySpec->Ability (the CDO) with bIsInstance set to false.
[Image Removed]
We’ve fixed the Blueprint to check bIsInstance, but the broader question is about the CDO fallback itself.
Given that non-instanced abilities are deprecated (multiple spots in GameplayAbility.cpp explicitly says so and ensures against it), is there still a valid use case for GetGameplayAbilityFromSpecHandle returning the CDO? For InstancedPerActor and InstancedPerExecution abilities, calling any function that touches CurrentActorInfo, CurrentActivationInfo, or other instance state on the CDO will always be wrong.
Would it be appropriate to change the fallback to return null?
if (!AbilityInstance)
{
// AbilityInstance = AbilitySpec->Ability; // <- Fix - Remove this line
bIsInstance = false;
}
This would make the function return null for instanced abilities that don’t have an instance yet, which is consistent with how the other early-return paths already work (invalid ASC and invalid handle both return null). Callers already need to handle null from those paths.
[Attachment Removed]