ShooterGame Controller = NULL Race Condition

I’ve been trying to implement a death camera within ShooterGame’s AShooterPlayerController::PawnPendingDestroy and have noticed that sometimes my death camera code runs and sometimes it does not.

In ShooterCharacter::OnDeath a call is made to DetachFromControllerPendingDestroy() that I believe is causing a race condition preventing AShooterPlayerController::PawnPendingDestroy from being called on the client.

I believe the server’s call to DetachFromControllerPendingDestroy() removes the character’s controller (controller = NULL) which is replicated to the client before the client’s own call to DetachFromControllerPendingDestroy(). Moments later when the client passes through DetachFromControllerPendingDestroy(), the controller is NULL and the call to Controller->PawnPendingDestroy(this) is never made.

Is it possible that there is a race condition on controller = null?

Not sure if there is an easy fix for this, any suggestions?

AShooterCharacter::OnDeath()



void AShooterCharacter::OnDeath(float KillingDamage, struct FDamageEvent const& DamageEvent, class APawn* PawnInstigator, class AActor* DamageCauser)
{
	if (bIsDying)
	{
		return;
	}

	bReplicateMovement = false;
	bTearOff = true;
	bIsDying = true;

	if (Role == ROLE_Authority)
	{
		ReplicateHit(KillingDamage, DamageEvent, PawnInstigator, DamageCauser, true);	

		// play the force feedback effect on the client player controller
		APlayerController* PC = Cast<APlayerController>(Controller);
		if (PC && DamageEvent.DamageTypeClass)
		{
			UShooterDamageType *DamageType = Cast<UShooterDamageType>(DamageEvent.DamageTypeClass->GetDefaultObject());
			if (DamageType && DamageType->KilledForceFeedback)
			{
				PC->ClientPlayForceFeedback(DamageType->KilledForceFeedback, false, "Damage");
			}
		}
	}

	// cannot use IsLocallyControlled here, because even local client's controller may be NULL here
	if (GetNetMode() != NM_DedicatedServer && DeathSound && Mesh1P && Mesh1P->IsVisible())
	{
		UGameplayStatics::PlaySoundAtLocation(this, DeathSound, GetActorLocation());
	}

	// remove all weapons
	DestroyInventory();
	
	// switch back to 3rd person view
	UpdatePawnMeshes();

	DetachFromControllerPendingDestroy();
	StopAllAnimMontages();

	if (LowHealthWarningPlayer && LowHealthWarningPlayer->IsPlaying())
	{
		LowHealthWarningPlayer->Stop();
	}

	if (RunLoopAC)
	{
		RunLoopAC->Stop();
	}

	// disable collisions on capsule
	CapsuleComponent->SetCollisionEnabled(ECollisionEnabled::NoCollision);
	CapsuleComponent->SetCollisionResponseToAllChannels(ECR_Ignore);

	if (Mesh)
	{
		static FName CollisionProfileName(TEXT("Ragdoll"));
		Mesh->SetCollisionProfileName(CollisionProfileName);
	}
	SetActorEnableCollision(true);

	// Death anim
	float DeathAnimDuration = PlayAnimMontage(DeathAnim);

	// Ragdoll
	if (DeathAnimDuration > 0.f)
	{
		GetWorldTimerManager().SetTimer(this, &AShooterCharacter::SetRagdollPhysics, FMath::Min(0.1f, DeathAnimDuration), false);
	}
	else
	{
		SetRagdollPhysics();
	}
}


APawn::DetachFromControllerPendingDestroy()



void APawn::DetachFromControllerPendingDestroy()
{
	if ( Controller != NULL && Controller->GetPawn() == this )
	{
		AController* OldController = Controller;
		Controller->PawnPendingDestroy(this);
		if (Controller != NULL)
		{
			Controller->UnPossess();
			Controller = NULL;
		}
	}
}


AShooterPlayerController:PawnPendingDestroy()



void AShooterPlayerController::PawnPendingDestroy(APawn* P)
{
	FVector CameraLocation = P->GetActorLocation() + FVector(0, 0, 300.0f);
	FRotator CameraRotation(-90.0f, 0.0f, 0.0f);
	FindDeathCameraSpot(CameraLocation, CameraRotation);

	Super::PawnPendingDestroy(P);

	ClientSetSpectatorCamera(CameraLocation, CameraRotation);
}