Oh noes ! Replication !

When you think you’re starting to understand Replication and getting things to work you get a big fat “Nope” :stuck_out_tongue: I have been reading a lot of stuff about replication, especially the unreal engine 3 replication documentation (it’s more complete, although some of it is not applicable to UE 4, but the core is the same). I was hoping you could help me clarify some doubts that i have right now and that i will have while i try to learn it:

  • I noticed that each client has a copy with authority of every replicated actor, but i though that copy was only for storing the latest values from the server before updating the proxy version. Today i noticed that a client also runs Server functions, so can you clarify exactly what really happens when you call a Server function? Does he execute it on both the “real” server and “copy” server?

  • What is the right way of handling the replication workflow? I have been using the following (Equip weapon pseudo code example):



void OnPressedEquipKey()
{
     AWeapon * WeaponToEquip = GetWeaponToEquip();

     ServerEquipWeapon(WeaponToEquip);
}

void ServerEquipWeapon(AWeapon * Weapon)
{
    StartEquippingWeapon(Weapon);

    ClientStartEquippingWeapon(Weapon); // Multicast to make clients do the animation also and update the current weapon
}

void ClientEquipWeapon(AWeapon * Weapon)
{
    StartEquippingWeapon(Weapon);
}

void StartEquippingWeapon(AWeapon * Weapon)
{
     PendingWeapon = Weapon;
     PendingWeapon ->AttachRootComponentTo(Mesh, "RightHand", EAttachLocation::SnapToTarget);

     float Duration = 0.1f;

	if (EquipAnimMontage != NULL)
		Duration = FMath::Max<float>(PlayAnimMontage(EquipAnimMontage ), 0.1f);

	GetWorldTimerManager().SetTimer(this, &AMyCharacter::FinishedEquippingWeapons, Duration);
}

void FinishedEquippingWeapons()
{
      CurrentWeapon = PendingWeapon;
      PendingWeapon = NULL;
}


Is this the right way? I’ve been using this Server function executes a function then calls the Client function that executes the function again and i haven’t used repnotify for anything. Are there conventions to properly replicate things? Because i’m getting the feeling that i’m not doing things the way it was designed by Epic.

C++ Code For You!

Examples From My Own Code Base

Here are two examples from my own code base of how I replicate things that dont have built in replication support like CharacterMovement->Velocity does.

These are things you have to use repnotify for so that every client will do the action that has to be performed locally.

These two examples involve gravity, which you have to set locally.

This is all occurring a class that extends ACharacter


**Two Approaches**

I use two different approaches in the code below.

In one case I rely on the value itself to always be dirtied and updated properly, a bit risky sometimes, depending on your situation.

In the other case I am flipping a bool that serves to guarantee replication will occur, and sending along the actual relevant data at the same time.

I'd recommend you try this latter approach first!

Notation

Of the various notations that I use in function definitions, the only required ones are _Validate and _Implementation in the CPP file.

I use R_ to tell myself that a variable is being replicated so I can clearly see why it appears in certain places in my .cpp files.


**Server Rep**

The server has to call the OnRep function manually because it does not participate in replication updates since it is the one sending them.

To simplify my code base I just have the Server run the same code the clients are running as it's vars have already been updated.

You'd want to avoid running certain code on a dedicated server that is purely graphical in nature.

You can avoid running code on dedicated server by wrapping it with



```


if(GEngine->GetNetMode(GetWorld()) != NM_DedicatedServer)
{
   //code to run on non-dedicated servers
}


```



Virtual OnRep

I always make OnRep functions virtual so that I can override just the OnRep part in subclasses, gaining full use of the replication structure I set up in the base class without having to re-write any of it in subclasses

:slight_smile:

The net code structure I am showing here has worked great for me in real multiplayer games with up to 3 people involved who are all simultaneously using my multiplayer in-game editor to co-create the world together! In a packaged game!

Enjoy!

Rama

.H



//~~~ Physics Gravity ~~~
UFUNCTION(reliable, server, WithValidation)
void SERVER_SetPhysicsGravityActive(bool MakeActive);

UPROPERTY(ReplicatedUsing=OnRep_SetPhysicsGravity)
bool R_PhysicsGravityActive;

UFUNCTION()
virtual void OnRep_SetPhysicsGravity();


//~~~ Gravity Scale ~~~
	
UFUNCTION(reliable, server, WithValidation)
void SERVER_SetGravityScale(float TheGravityScale);

UPROPERTY(ReplicatedUsing=OnRep_SetGravityScale)
bool DoRep_GravityScale;

UPROPERTY(Replicated)
float R_GravityScale;

UFUNCTION()
virtual void OnRep_SetGravityScale();



CPP



```


//~~~ Physics Gravity ~~~
bool AJoyCharacterBase::**SERVER_SetPhysicsGravityActive**_Validate(bool MakeActive)
{
	return true;
}
void AJoyCharacterBase::**SERVER_SetPhysicsGravityActive**_Implementation(bool MakeActive)
{
	//Rep
	R_PhysicsGravityActive = MakeActive;
	OnRep_SetPhysicsGravity();
}

void AJoyCharacterBase::OnRep_**SetPhysicsGravity**()
{
	if(Mesh)
	{
		Mesh->SetEnableGravity(R_PhysicsGravityActive);
	}
}


//~~~ Gravity Scale ~~~
bool AJoyCharacterBase::**SERVER_SetGravityScale**_Validate(float TheGravityScale)
{
	return true;
}
void AJoyCharacterBase::**SERVER_SetGravityScale**_Implementation(float TheGravityScale)
{
	//Rep
	DoRep_GravityScale 	= !DoRep_GravityScale;
	R_GravityScale 		= TheGravityScale;
	  
	//Server
	OnRep_SetGravityScale();
}

void AJoyCharacterBase::OnRep_**SetGravityScale**()
{
	if(!UVictoryCore::VIsValid(CharacterMovement)) return;
	//~~~~~~~~~~~~~~~~~~~~~~~~~~~~
	
	CharacterMovement->GravityScale = R_GravityScale;
	CharacterMovement->Velocity = FVector::ZeroVector;
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//		Replication List
void AJoyCharacterBase::GetLifetimeReplicatedProps( TArray< FLifetimeProperty > & OutLifetimeProps ) const
{
	Super::GetLifetimeReplicatedProps( OutLifetimeProps );
 
	//Physics Gravity
	DOREPLIFETIME(AJoyCharacterBase, R_PhysicsGravityActive);
	
	//Gravity Scale, Adjustments in In-Game Editor
	DOREPLIFETIME(AJoyCharacterBase, R_GravityScale);
	DOREPLIFETIME(AJoyCharacterBase, DoRep_GravityScale);
}


```

Thank you for the answer Rama :slight_smile: I understand how repnotify works and for the example you presented makes perfect sense to use it, but for more complex operations, like the equip logic i showed, i don’t know if it is the best approach. Can you tell me what are the disadvantages of using Client functions over repnotifiy functions? Also if you can clarify me about the Server function logic also running on the client that would help a lot because that is what is making me more confused. This also happens on Client functions, if i have a dedicated server with 2 clients (Client A and B) with 2 characters (Character A and B) and i run a Client function with a GEngine->AddOnScreenDebugMessage, that log will appear 3 times for each client (for the ROLE_Authority, ROLE_SimulatedProxy and ROLE_AutonomousProxy versions of the client). Say for example Client A using Character A has equipped a weapon and we call ServerEquipWeapon which also calls ClientStartEquippingWeapon(Weapon); so shouldn’t it be like:

  • Server runs StartEquippingWeapon(Weapon) from ServerEquipWeapon for it’s Character A version with the permission ROLE_Authority and updates it
  • Client A runs the Client function for it’s Character A version with the permission ROLE_AutonomousProxy and updates it
  • Client B runs the Client function for it’s Character A version with the permission ROLE_SimulatedProxy and updates it

So each client should only run that Client function once and not 3 times, right?

Thanks

Would this really be gauranteed? If the packet (where the flipped bool was true) is lost, it won’t be resent since it’s assumed that the state it contained is now outdated, and a new packet is generated instead with more recent state. But in this new packet, the flipped bool is false again and hence the state could end up not being replicated and the simulation is now desynced.

I investigated the matter further and it appears that because it’s the same GEngine the prints are being shared between the clients and the server, so in reality it appears that it only executes 3 times. I changed a little bit the workflow i was using to do the following:



void OnPressedEquipKey()
{
     AWeapon * WeaponToEquip = GetWeaponToEquip();

     ServerEquipWeapon(WeaponToEquip);
}

void ServerEquipWeapon(AWeapon * Weapon)
{
    StartEquippingWeapon(Weapon);
}

void ClientEquipWeapon(AWeapon * Weapon)
{
    StartEquippingWeapon(Weapon);
}

void StartEquippingWeapon(AWeapon * Weapon)
{
     PendingWeapon = Weapon;
     PendingWeapon ->AttachRootComponentTo(Mesh, "RightHand", EAttachLocation::SnapToTarget);

     float Duration = 0.1f;

	if (EquipAnimMontage != NULL)
		Duration = FMath::Max<float>(PlayAnimMontage(EquipAnimMontage ), 0.1f);

	GetWorldTimerManager().SetTimer(this, &AMyCharacter::FinishedEquippingWeapons, Duration);

     if(Role == ROLE_Authority)
        ClientEquipWeapon(Weapon); // Multicast to make clients do the animation also and update the current weapon
}

void FinishedEquippingWeapons()
{
      CurrentWeapon = PendingWeapon;
      PendingWeapon = NULL;
}


Basically i removed ClientStartEquippingWeapon(Weapon); from ServerEquipWeapon and added the if(Role == ROLE_Authority) ClientStartEquippingWeapon(Weapon); in StartEquippingWeapon. The if statement is to check if it’s running StartEquippingWeapon(Weapon); from ServerEquipWeapon and not from ClientEquipWeapon, but this doesn’t work, because apparently Client functions execute with ROLE_Authority so it will create an infinite loop until it crashes. What other type of workflow can i use to make this work?

If the bool is being flipped by a server function marked with Reliable then I presume that will work even if there is packet loss. I’ve never had an issue even in real multiplayer tests I"ve been doing since the Beta.

But if you are really concerned you could consider my other favored model which is to increment a uint8 instead of using a bool.

From my tests a bool and uint8 are actually the same size, so its not a big deal to rep the uint8 instead of a bool!

sizeof(bool) == sizeof(uint8) in my tests so far :slight_smile:

The reason I like incrementing the uint8 is cause if the server function gets called twice somehow it will still rep anyway, and I doubt there’d be 255 increments before 1 got thru to the client :slight_smile:

Rama

Do you know if the replicated variables that come from the server are all updated first and only after that will it start calling the repnotifies? Or does he call the repnotify immediately after updating the replicated variable and does that one by one? You said “In one case I rely on the value itself to always be dirtied and updated properly, a bit risky sometimes, depending on your situation.” is that because he does the latter one and you don’t know the order of updating or because of network saturation?

As far as I know repnotifies are only fired once the function that changes their values completes. For this reason I’ve not encountered any issues with the order of variable modification, ie, the DoRep bool vs the actual replicated variable data, they just have to be modified within the same server function call.

I’m asking this because i have the function StartEquippingWeapon that sets the variable bIsEquipping to true and then sets right after the class variable WeaponRight to the weapon i want to equip, but when the repnotify OnRep_IsEquipping triggers the Weapon variable is still NULL and hasn’t been updated yet. I don’t want to repnotify the weapon because i can dual wield and the equip runs a different animation.

It appears that the problem happens because i’m doing the running the function from PostInitializeComponents, i tried also on BeginPlay and it has the same problem. If i execute the function 0.5f seconds later with a SetTimer it seems to work fine. From what point in the character workflow can i do things to replicate?

Edit: If i set the Weapon variable netpriority to a big value it works so i guess it’s just replicating the Weapon after the bIsEquipping.