Skip to main content

RPC (Remote Procedure Calls)

RPC serves as a mean to perform a directed reliable one-time action. What does it mean?

General usage

RPC-enabled methods have to be marked with the [ElympicsRpc] attribute. Any method is suitable as long as it meets the following criteria:

  • it belongs to a class inheriting from ElympicsMonoBehaviour
  • it has void return type
  • its parameter list consists only of primitive types and strings, without in, out or ref modifiers
  • it is an instance (non-static) method
  • it is a concrete method without virtual, abstract or override modifiers
  • the class it belongs to is not a part of a custom assembly; only the default scripting assembly (Assembly-CSharp.dll) is scanned by Elympics

Such a method can be called only from a synchronized object (i.e. component implementing IObservable directly or indirectly and being attached to a game object with the ElympicsBehaviour component). In games with prediction enabled, RPCs are tick-aligned, so they can only be performed inside:

  • IUpdatable.ElympicsUpdate method
  • IInitializable.Initialize method
  • a physics callback

When ElympicsConfig.Prediction is set to false, player-to-server RPCs can be performed at any time. Please note that this setting will cause the client to never run ElympicsUpdate, making state from the server the only source of game state progression.

info

An RPC-enabled method call looks like any other plain method call, but Elympics modifies the compiled code so the called method is not run immediately. Instead, a message containing all passed arguments is created and then sent to the target instance. Only there the method body is finally executed.

The direction of RPC is decided by passing ElympicsRpcDirection.ServerToPlayers or ElympicsRpcDirection.PlayerToServer to the constructor of [ElympicsRpc] attribute marking the method. Additionally, RPC methods should be scheduled only after performing if (Elympics.IsServer) or if (Elympics.IsClient || Elympics.IsBot) check, respectively.

Negative checks

It may be tempting to use if (!Elympics.IsServer) instead of if (Elympics.IsClient || Elympics.IsBot), however you should not do that because for example both Elympics.IsBot and Elympics.IsServer are set to true for bot instances.
If you don't split the checks, you can use the Elympics.IsClientOrBot convenience property.

RPC actions are sent to the target at the end of a tick. Received RPCs are executed at the beginning of a tick, before running ElympicsUpdate or after applying the authoritative state on clients with prediction disabled. You can find more details in our Game loop article. Keep in mind that several ticks may pass between scheduling and executing an action as the information has to be transmitted over the network. However, we make sure no RPC executes before its associated tick.

Server-to-players calls

Server-to-players RPCs are invoked by the server and executed on its clients. Their purpose is to run audiovisual effects not affecting the game state. Such an RPC serves as an alternative to the ElympicsVar.ValueChanged callback which is unreliable and may be run multiple times during reconciliation.

Do not change state in server-to-players RPCs

Remember that client instances cannot modify the state of unpredictable objects by themselves as all the changes would be overwritten by a server-authoritative snapshot.
That's why server-to-players RPCs are not meant to modify the game state and only run some special effects.
If you want to modify the state of an unpredictable object, just do it directly on the server instance, without using RPCs.

The following code example demonstrates how to use a server-to-players RPC.

public class PlayerHealthController : ElympicsMonoBehaviour
{
[SerializeField] private PlayerData playerData;
private readonly ElympicsInt _health = new(100);
private readonly ElympicsBool _dead = new();

public void OnCollisionEnter(Collision other)
{
var bullet = other.transform.GetComponentInParent<Bullet>();
if (bullet == null)
return;

_health.Value -= 20;
if (_health.Value < 0)
{
_dead.Value = true;
if (Elympics.IsServer) // ensuring the RPC is scheduled by the server instance to execute on all client instances
AddKillLogEntry(bullet.Owner.Nickname, playerData.Nickname); // scheduling the RPC
}
}

[ElympicsRpc(ElympicsRpcDirection.ServerToPlayers)] // specifying the direction
private void AddKillLogEntry(string killer, string victim)
{
// the contents of this method will be run on client instances
killLog.AddEntry(killer, victim);
}

{...}
}
Using server-to-players RPCs with bots

Bots are run as a part of the server so they don't execute server-to-players RPCs.

Player-to-server calls

Player-to-server RPCs are invoked by a client and executed on the server. They provide a user-friendly way of sending one-time inputs (e.g. sending your nickname, choosing a playable character). Using the traditional input mechanism would require additional boilerplate logic to ensure the data is reliably received only once.

RPCs instead of inputs

Note that RPCs should not be used to replace all the inputs as they are costly to send over the network and may introduce unwanted lag when used every tick.

RPCs are not scheduled during reconciliation.

When prediction is enabled, RPC calling logic on a client may be based on non-confirmed predicted state and cannot be "fixed" by reconciliation, because RPCs cannot be unscheduled or have their source tick number changed.

Since player-to-server RPCs are executed on the server, it is possible to modify the game state with them.

Security of RPC-based inputs

Currently, there is no mechanism preventing one of the players from sending player-to-server RPCs on behalf of another player. That means that for the time being RPCs are insecure and therefore should not be used for handling game logic e.g. in paid competitions.
Necessary security measures are to be introduced in the near future.

These RPCs may only be called after performing if (Elympics.IsClient || Elympics.IsBot) check. The check may be split into separate if (Elympics.IsClient) and if (Elympics.IsBot) checks, as presented in the example below.

public class PlayerData : ElympicsMonoBehaviour, IInitializable
{
private readonly ElympicsString _nickname = new();
public string Nickname => _nickname.Value;

public void Initialize()
{
if (Elympics.IsClient) // ensuring the RPC is scheduled by a client instance to execute on the server instance
SetNickname(PlayerPrefs.GetString("Nickname")); // scheduling the RPC
if (Elympics.IsBot) // ensuring the RPC is scheduled by a bot to execute inside the server instance
SetNickname("Bot"); // scheduling the RPC
}

[ElympicsRpc(ElympicsRpcDirection.PlayerToServer)]
private void SetNickname(string nickname)
{
// the contents of this method will be run on the server
_nickname.Value = nickname; // the variable will be changed on the server and then propagated to players in a snapshot
}

{...}
}
Using player-to-server RPCs with bots

Player-to-server RPCs scheduled by bots are executed directly without a delay as bots are a part of the server.