Cross-server RPCs

In native-Unreal networking, RPCs (Unreal documentation) are functions which either the client or the server use to send messages to each other over a network connection.

Cross-server RPCs facilitate zoning, which is one of the GDK's options for multiserver development.

Zoning is currently (in preview).

In Unreal’s native single-server architecture, your game server holds the canonical state of the whole game world. As there is a single game server, it has complete authority over all server Actors and so it is able to invoke and execute functions on Actors unhindered.

In SpatialOS games, there can be more than one server; each of these servers is known as a “server-worker” instance. (Find out more about server-workers as well as “client-workers” in the glossary.) In a SpatialOS game that runs across many server-worker instances, only one server-worker instance at a time is able to invoke and execute functions on Actors. We refer to that server-worker instance as having “authority” over the Actor (find out more about authority in the glossary.

As Unreal expects there to be only one server, rather than several servers, the GDK has a custom solution to take advantage of the SpatialOS distributed server architecture. This involves handling the scenario where a server-worker attempts to invoke an RPC on an Actor that another server-worker has authority over. This custom solution is the cross-server RPC. The GDK offers cross-server RPC in addition to support for the native RPC types that Unreal provides (Unreal documentation).

When a server-worker instance invokes a cross-server RPC, the SpatialOS Runtime identifies the server-worker instance that has authority over the Actor. The SpatialOS Runtime then passes the execution of that RPC to the server-worker instance that has authority over the Actor, which executes the RPC.

The example diagram below shows a player successfully shooting another player’s hat across a server-worker boundary.

The above diagram shows the following:

  • Server-worker 1 has authority over Player 1 and Server-worker 2 has authority over Player 2.
  • When Player 1 shoots a bullet at Player 2, Server-worker 1 makes the necessary changes to Player 1 (such as reducing the number of bullets Player 1 has). However, Server-worker 1 does not have authority over Player 2, and therefore cannot make any changes to Player 2 (such as reducing Player 2’s health).
  • Server-worker 1 invokes a cross-server RPC which Server-worker 2 executes to make the necessary changes to Player 2 (for example, to reduce Player 2’s health).

Note: You must only use cross-server RPCs when failure to execute the RPC does not cause issues. This is because when a server-worker instance executes a cross-server RPC, the server-worker instance executing the RPC may no-longer have authority over the Actor that the RPC is attempting to change. This can happen because of the time it takes between invoking and executing the cross-server RPC.

How to send a cross-server RPC (using C++)

To set up a cross-server RPC, follow the same instructions as you would for marking up RPCs (Unreal documentation) within Unreal.

  1. Add a CrossServer tag to the UFUNCTION macro of the RPC function that you want to be cross-server on your Actor (MyActor in this example).
	UFUNCTION(CrossServer, Reliable, WithValidation)
	void MyCrossServerRPC();

WithValidation is optional.

  1. Add the related function implementations:
    void MyActor::MyCrossServerRPC_Implementation()
{
// Implementation goes here...
}

You may need to implement the MyCrossServerRPC_Validation() if you used the WithValidation attribute.

  1. Invoke the CrossServer RPC function as you would with any other function.

How to send a cross-server RPC (using Blueprints)

To set up a cross-server RPC in a Blueprint, follow the same instructions as you would for marking up RPCs in Blueprints (Unreal documentation), but from the Replicates drop-down list within the Details panel of your event, select Run on authoritative server (sent from server):

Execution notes

The tables below show where cross-server RPCs are executed based on where they were invoked. (To make it easier to follow, the tables use the same format as the Unreal documentation on RPCs.)

Invoking a cross-server RPC from a server-worker that has authority over an Actor

Actor ownership Cross-server RPC
Client-owned Actor Runs on the server-worker that has authority
Server-owned Actor Runs on the server-worker that has authority
Unowned Actor Runs on the server-worker that has authority

Invoking a cross-server RPC from a client-worker

Only server-worker instances can invoke a cross-server RPC. If a client-worker instance calls a cross-server RPC, the SpatialOS Runtime does not process the call. This is to ensure client-workers don’t hack the game.

TakeDamage cross-server RPC example

You can see an example of how to send cross-server RPCs in the implementation of the AActor class, located in the Actor.h and Actor.cpp files covered in this section. The TakeDamage function has been modified so that when it is called on a server that doesn’t have authority over the Actor, the TakeDamage function sends a cross-server RPC. This ensures that the TakeDamage function is executed on the server that does have authority over the Actor.

Sending derived classes over cross-server RPCs

You can send a derived class over cross-server RPCs by creating separate RPCs for each class derivation. You can see an example of this in the Actor class. The TakeDamage function has been modified to send three different cross-server RPCs if called on a server that is not authoritative over the Actor.

In the Actor.h file, located in .../UnrealEngine/Engine/Source/Runtime/Engine/Classes/GameFramework/Actor.h, you can see the three cross-server RPCs defined, one for the FDamageEvent base class and the other two respectively for the FPointDamageEvent and FRadialDamageEvent derived classes:

UFUNCTION(CrossServer, Reliable, BlueprintCallable, Category = "Spatial")
	virtual void CrossServerTakeDamage(float DamageAmount, FDamageEvent const& DamageEvent, AController* EventInstigator, AActor* DamageCauser);

UFUNCTION(CrossServer, Reliable, BlueprintCallable, Category = "Spatial")
	virtual void CrossServerTakePointDamage(float DamageAmount, FPointDamageEvent const& PointDamageEvent, AController* EventInstigator, AActor* DamageCauser);

UFUNCTION(CrossServer, Reliable, BlueprintCallable, Category = "Spatial")
	virtual void CrossServerTakeRadialDamage(float DamageAmount, FRadialDamageEvent const& RadialDamageEvent, AController* EventInstigator, AActor* DamageCauser);

In the Actor.cpp file, located in .../UnrealEngine/Engine/Source/Runtime/Engine/Private/Actor.cpp, you can see the implementations of the cross-server RPCs, each calling the TakeDamage function with the specific type of damage passed through:

void AActor::CrossServerTakeDamage_Implementation(float DamageAmount, struct FDamageEvent const& DamageEvent, class AController* EventInstigator, AActor* DamageCauser)
{
	TakeDamage(DamageAmount, DamageEvent, EventInstigator, DamageCauser);
}

void AActor::CrossServerTakePointDamage_Implementation(float DamageAmount, struct FPointDamageEvent const& PointDamageEvent, class AController* EventInstigator, AActor* DamageCauser)
{
	TakeDamage(DamageAmount, PointDamageEvent, EventInstigator, DamageCauser);
}

void  AActor::CrossServerTakeRadialDamage_Implementation(float DamageAmount, struct FRadialDamageEvent const& RadialDamageEvent, class AController* EventInstigator, AActor* DamageCauser)
{
	TakeDamage(DamageAmount, RadialDamageEvent, EventInstigator, DamageCauser);
}

You can see that the TakeDamage function checks to see if the server calling the function has authority over the Actor. If it does not then a cross-server RPC is required. The if else if statement then determines which cross-server RPC to send based on the type of damage passed to the TakeDamage function:

bool bNonAuthServer = GetDefault<UGeneralProjectSettings>()->UsesSpatialNetworking() && !HasAuthority() && GetWorld() && GetWorld()->IsServer();
	if (bNonAuthServer)
	{
		if (DamageEvent.IsOfType(FPointDamageEvent::ClassID))
		{
			const FPointDamageEvent& PointDamageEvent = static_cast<const FPointDamageEvent&>(DamageEvent);
			CrossServerTakePointDamage(DamageAmount, PointDamageEvent, EventInstigator, DamageCauser);
			return 0.0f;
		}
		else if (DamageEvent.IsOfType(FRadialDamageEvent::ClassID))
		{
			const FRadialDamageEvent& RadialDamageEvent = static_cast<const FRadialDamageEvent&>(DamageEvent);
			CrossServerTakeRadialDamage(DamageAmount, RadialDamageEvent, EventInstigator, DamageCauser);
			return 0.0f;
		}
		else if (DamageEvent.IsOfType(FDamageEvent::ClassID))
		{
			CrossServerTakeDamage(DamageAmount, DamageEvent, EventInstigator, DamageCauser);
			return 0.0f;
		}
		else
		{
			UE_LOG(LogActor, Warning, TEXT("Calling AActor::TakeDamage on non-auth server with unknown FDamageEvent derivative"));
		}
	}

Unresolved parameters in an RPC

If a server-worker instance receives an RPC with a parameter that it cannot resolve, the GDK outputs a warning to the logs by default. For example, if a server-worker instance receives an RPC with a parameter that specifies an Actor that it does not have authority over.

To disable these warnings, use one of the following instructions (C++ or Blueprint):

How to disable warnings for unresolved parameters in an RPC (using C++)

To disable these warnings on an RPC in C++, add an AllowUnresolvedParameters tag to the UFUNCTION macro of the RPC function.

UFUNCTION(Server, AllowUnresolvedParameters, Reliable)
void MyServerRPC();

How to disable warnings for unresolved parameters in an RPC (using Blueprints)

To disable these warnings on an RPC in a Blueprint:

  1. In the Details panel of the event, select the eye in the upper-right corner.
  2. Select the Show All Advanced Details option and then select Allow Unresolved Parameters to disable warnings for that RPC.

2020-05-12 Page updated with editorial review of cross-server RPC changes
2019-11-07 Page updated with editorial review: added disabling warnings for unresolved parameters in RPCs
2019-06-06 Updated invoking a cross-server RPC from a client worker guidance

Updated about a year ago


Cross-server RPCs


Suggested Edits are limited on API Reference Pages

You can only suggest edits to Markdown body content, but not to the API spec.