RPC (Remote Procedure Calls)
Please note that this feature is still under development and breaking changes may occur in the future.
RPC serves as a mean to perform a directed reliable one-time action. What does it mean?
- directed – an action can be scheduled by the server instance to be run by all players or by a player to be run on the server; the action is run only on the target and not on the instance that schedules the RPC,
- reliable – the action is guaranteed to execute as long as the connection is not severed,
- one-time – RPCs are neither scheduled nor executed again on clients during reconciliation.
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
orref
modifiers, - it is an instance (non-
static
) method, - it is a concrete method without
virtual
,abstract
oroverride
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. implementing IObservable
directly or indirectly and being part of a game object with the ElympicsBehaviour
component attached). As RPCs are tick-aligned, a call must be performed inside:
ElympicsUpdate
method after implementingIUpdatable
,Initialize
method after implementingIInitializable
, or- physics callbacks.
An RPC 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 [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.
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
The purpose of server-to-players RPCs 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.
Remember that client instances cannot modify the game state by themselves as all the changes would be overwritten by a server-authoritative snapshot.
That's why server-to-players RPCs (which are executed on clients) are not meant to modify the game state and only run some special effects.
If you want to modify the state, 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);
}
{...}
}
Bots are run as a part of the server so they don't execute server-to-players RPCs which are meant to trigger audiovisual effects. Such effects would not be visible/audible on the headless server instance.
Player-to-server calls
Player-to-server RPC is a user-friendly way of sending one-time inputs (e.g. sending your nickname, choosing a team or marking yourself as ready). Using the traditional input mechanism would require additional boilerplate logic to ensure the data is reliably received only once.
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. Keep this in mind – with prediction enabled, RPC calling logic may be based on non-confirmed state and cannot be "fixed" (i.e. RPCs cannot be unscheduled or have their source tick number changed).
Player-to-server RPCs are executed on the server so it is safe to modify the game state with them.
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
}
{...}
}
Player-to-server RPCs scheduled by bots are executed directly without a delay as bots are a part of the server.