Download

OnRep is not firing on Client side when running a Dedicated Server (4.26.2)

Hey,

I’m running two player client in PIE with a simulated dedicated server from PIE. Every locally controlled Pawn creates a replicated actor on the Server.

So I have two of that Actor on the server, but only one for each client, and the server is the owner of those actors.

Those actors have a replicated member which gets changed on the server, however, its OnRep only fires on the server.

.h stuff

UFUNCTION(BlueprintCallable, Server, Reliable)
void Server_SetTestMemberAuth(const int NewTest);
UPROPERTY(Transient, ReplicatedUsing=OnRep_Test)
int Test;
UFUNCTION()
void OnRep_Test() const;

.cpp stuff

void AGrid::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const
{
	Super::GetLifetimeReplicatedProps(OutLifetimeProps);

	DOREPLIFETIME(AGrid, Test);
}
void AGrid::Server_SetTestMemberAuth_Implementation(const int NewTest)
{
	if (!HasAuthority())
	{
		return;
	}

	Test = NewTest;
	OnRep_Test();
}
void AGrid::OnRep_Test() const
{
	if (HasAuthority())
	{
		UE_LOG(LogTemp, Warning, TEXT("OnRep_Test: Server"));
	}
	else
	{
		UE_LOG(LogTemp, Warning, TEXT("OnRep_Test: Client"));
	}
}

I’ve tried the exact same setup in Blueprints, updating a replicated test int member from a server RPC, and that RepNotify runs on both clients and the server.

What am I missing here?

Do you do this by hand or you let Unreal handle this? Also does AGrid have replication enabled?

This is how I create the AGrid actor on the server:

UFUNCTION(Server, Reliable)
void Server_CreateGridAuth();
void ATowerDefensePawn::BeginPlay()
{
	Super::BeginPlay();

	if (IsLocallyControlled())
	{
		Server_CreateGridAuth();	
	}
}
void ATowerDefensePawn::Server_CreateGridAuth_Implementation()
{
	if (HasAuthority())
	{
		UWorld* World = GetWorld();
		if (World)
		{
			FActorSpawnParameters SpawnParameters;
			SpawnParameters.Owner = this;
			SpawnParameters.SpawnCollisionHandlingOverride = ESpawnActorCollisionHandlingMethod::AlwaysSpawn;
			Grid = Cast<AGrid>(World->SpawnActor(GridClassToSpawn, &FTransform::Identity, SpawnParameters));
		}
	}
}

This is the constructor of my Grid, the Actor that I spawn:

AGrid::AGrid(const FObjectInitializer& ObjectInitializer)
: Super(ObjectInitializer)
{	
	GridVisualizer = CreateDefaultSubobject<UGridVisualizerComponent>(TEXT("Grid Visualizer"));
	if (GridVisualizer)
	{
		RootComponent = GridVisualizer;
	}

	bReplicates = true;
	bAlwaysRelevant = true;

	NumColumns = 50;
	NumRows = 50;
	CellSize = 64;

	Selection.BottomLeftCell = INT_MIN;
	Selection.TopRightCell = INT_MIN;
}

EDIT: Also, whether or not it replicates, or its relevancy is not touched during runtime, or changed in the blueprint, this is the only place I’ve touched it. If I just double check the blueprint, relevancy and replicates boxes are ticked as they should be.

A few points:

The “if (HasAuthority())” checks in the _Implementation function are not needed. That will only ever execute on the authority, unless you are calling MyFunction_Implementation directly (which you shouldn’t be).

You have a race condition in BeginPlay. Using IsLocallyControlled() there depends on the Controller and it’s properties replicating before the pawn calls BeginPlay - which you can’t garauntee will occur 100% of the time. Anything that is dependant on the state/existence of another actor needs to be driven via OnRep callbacks. Handling this in AController::SetPawn() would be the best place. Better yet, I would just have the server spawn these things itself - I’m not sure why the client would specifically request it in this case.

The last point is that the OnRep will only fire if the server actually replicates something, and by default only if the received value is different from the local one. If you are setting ‘Test’ to the same value, or if the client has manipulated it locally, the OnRep will not fire.

Hey Jamsh, thanks for the pointers, I have a few things I’d like to get back to you on.

The HasAuthority in the server functions will get removed, I thought they werent necessary, but I didnt know, so I didnt take a chance on it, thanks for clarifying it.

The reason Im telling the server to spawn it from my client is because I only want the owning client to get a Grid actor. Think of it like a PlayerController, all PlayerControllers exist on the Server, but only the PlayerController that is relevant for you exists on your Client, thats the behaviour Im after. However, I’m not at all sure that is what I have achieved, Im still quite new to networking even if I’ve worked in games for some time now.

I am calling

void AGrid::Server_SetTestMemberAuth_Implementation(const int NewTest)
{
	if (!HasAuthority())
	{
		return;
	}

	Test = NewTest;
	OnRep_Test();
}

every three seconds with a randomized int from Blueprint, and its OnRep only runs on the Server, and not on the Client. What confuses me so much is that I’ve done this exact Test implementation in the BP instead, and directly called that one instead of my C++ one from the same place with the same value, and it’s RepNotify runs on both client and server.

So if you only want a grid to be “relevant” to each client, the way to do that would be to make the grid actor only relevant to the owner (bRelevantOnlyOwner or something like that) - then spawn it server-side using the PlayerController as the owner. It doesn’t really matter who requests the server spawn something, that spawned actor will always be relevant and replicated to all players unless it’s setup not to be.

Why the OnRep isn’t firing I’m not sure, there’s no reason it shouldn’t be. I would check that the actor you are spawning is actually being spawned on the Server, and not individually by each client (if it’s spawned client-side, then the client does have authority).

Thanks for the pointers, I’ll look into making the spawning a bit better, hopefully that’s where my issue lies.