RPC

Synchronizing an object’s properties, state and location is important for ensuring an accurate and natural multiplayer experience. However, actions taken by a player also need to be synchronized.

Polling values to see if they change to indicate a particular action is certainly possible, but most of the time it is preferable to relay the action to other players so they can call a relevant function.

Remote Procedure Calls (RPCs) are messages sent between players that contain a function signature and optional arguments. These can call methods directly on objects in other players’ worlds.

Strix facilitates this with a set of RPC related functions.

Registration

Note

Registration of an RPC should take place on both the owner and replica GameObject.

In order to turn a method into an RPC, you only need to mark it with a StrixRpc attribute.

[StrixRpc]
void MyRPCMethod()
{
    // ...
}

Only methods on classes derived from StrixBehaviour can be RPCs. When you mark a method with the StrixRpc attribute, it assigns the method a procedure code which is used to identify the method when making a call. You can specify this code directly, using the ProcedureCode property of the attribute. Otherwise, a hash of the method’s name is used.

Sending RPCs

Strix provides multiple methods for sending RPCs. These differ in the members that RPCs are sent to. RPCs sent to a particular member will be called on the object the RPC is registered to, on that client’s instance.

There are several types of targets you can specify when calling an RPC:

  1. Call RPC on specific room member

void Rpc(UID to, string rpcName, params object[] args)
void Rpc(UID to, string rpcName, RpcSuccessEventHandler successHandler, FailureEventHandler failureHandler, params object[] args)
  1. Call RPC on this member

void Rpc(string rpcName, params object[] args)
void Rpc(string rpcName, RpcSuccessEventHandler successHandler, FailureEventHandler failureHandler, params object[] args)
  1. Call RPC only on the current owner of the room

void RpcToRoomOwner(string rpcName, params object[] args)
void RpcToRoomOwner(string rpcName, RpcSuccessEventHandler successHandler, FailureEventHandler failureHandler, params object[] args)
  1. Call RPC on all the members, excluding the current one

void RpcToOtherMembers(string rpcName, params object[] args)
void RpcToOtherMembers(string rpcName, RpcSuccessEventHandler successHandler, FailureEventHandler failureHandler, params object[] args)
  1. Call RPC on all room members including self

void RpcToAll(string rpcName, params object[] args)
void RpcToAll(string rpcName, RpcSuccessEventHandler successHandler, FailureEventHandler failureHandler, params object[] args)

The name of the RPC to call can be retrieved with nameof:

nameof(MyRPCMethod);

RPC Arguments

All Send RPC functions can take an array of arguments.

Note

When sending an RPC, Strix does not check that the argument array is valid for the specific function signature. Be very careful regarding correct argument signatures, or else receiving the RPCs may fail.

The array for args represents the list of arguments for a given function, in order. Even if the function only takes one argument, the argument must be inside an array.

Strix can serialize some limited types for these arguments.

Serializable Types

Primitive types

  • bool

  • char

  • byte

  • sbyte

  • short

  • ushort

  • int

  • uint

  • long

  • ulong

  • float

  • double

  • byte[]

  • string

  • DateTime

  • Enums

Containers

  • Arrays

  • List<>

  • LinkedList<>

  • HashSet<>

  • Dictionary<,>

  • All collections inherited from ICollection or ICollection<>

  • All collections inherited from IDictionary or IDictionary<,>

Note

All elements (including keys for dictionaries) must also be of a serializable type listed on this subsection.

Unity types

  • Vector2

  • Vector3

  • Vector4

  • Vector2Int

  • Vector3Int

  • Quaternion

  • Matrix4x4

  • Color

  • Color32

  • Rect

  • RectInt

  • Bounds

  • LayerMask

Custom classes

You can also serialize custom classes if you register them using ObjectFactory.Instance.Register(). The types of all the fields and properties of these classes should also be serializable. This means they should either be one of the types listed above or should be registered manually beforehand as well.

RPC Examples

Suppose you have a structure in your code describing the damage dealt to a character:

struct DamageInfo
{
    public int Points;
    public Vector3 Direction;
}

If you want to use it as a parameter in an RPC method, first you should register it before you use it. This can be done just once and a good place for it would be somewhere at start of the game:

public class SomeGameManager : MonoBehaviour
{
    public void Start()
    {
        ObjectFactory.Instance.Register(typeof(DamageInfo));
    }
}

Make sure this line is executed on both sending client and receiving clients before you call the RPC with the argument of this type.

After that, you can use it like this:

public class Character : StrixBehaviour
{
    [StrixRpc]
    public void MakeDamage(DamageInfo damageInfo)
    {

    }
}
gameObject.GetComponent<Character>().Rpc(
    nameof(Character.MakeDamage),
    new DamageInfo {
        Points = 10,
        Direction = (transform.position - gameObject.transform.position).normalized
    }
);

RpcContext

You can also get metainformation about the RPC you’ve received. For that, you need to add a StrixRpcContext as the last argument of the method. Take heed: only the method signature changes, the calls stay the same. Provide all the arguments except the context itself when sending the RPC. The context will be supplied automatically by the SDK when the RPC is called. From it, you can extract information about the room member and client who sent you the RPC.

[StrixRpc]
public void MakeDamage(DamageInfo damageInfo, StrixRpcContext strixRpcContext)
{
    Debug.Log("Received " + damageInfo.Points + " damage from " + strixRpcContext.sender.GetName());
}

Warning Logs

During RPC usage, you can get various warning messages. Here is a list of what those messages mean, what could cause them, and how you can try to fix them.

  • Can’t use RPC when not joined to room

    Possible causes: You either haven’t joined any room yet, or you were in a room but got disconnected from it.

    How to fix: You should join or rejoin a room and try using RPC again. Also, check that your logic is functioning correctly after you leave a room.

  • Can’t use RPC for %target% because it does not have StrixReplicator attached

    Possible causes: There is no Strix replicator component on the object you are calling an RPC function on.

    How to fix: Ensure the object you are calling the RPC on has a Strix replicator.

  • Can’t use RPC for %target% because networkInstanceId is not assigned yet.

    Possible causes: The Strix replicator is not syncing on the object you are calling an RPC function on.

    How to fix: Ensure a Strix replicator is attached and syncing before performing RPCs.

  • StrixReplicator not found for instanceId: %instanceId%

    Possible causes: The replicator has been deleted unexpectedly.

    How to fix: Ensure the replicator is not deleted.

  • RPC method not found for object type: %objectType%

    Possible causes: The RPC may not have been registered on this client. Additionally, the object the RPC is sent to may not be the correct one.

    How to fix: Ensure all clients register the RPCs they will send or receive. Ensure the object you will invoke on is synced.

  • RPC method not found: %rpcHash%

    Possible causes: The RPC may not have been registered on this client.

    How to fix: Ensure all clients register the RPCs they will send or receive.

  • RPC Component not found for type %Type%

    Possible causes: The particular script component calling the method may have been deleted.

    How to fix: Ensure the script will not be deleted before an RPC call is made.

  • Failed to invoke Rpc: %name%. %message%

    Possible causes: An error occurred during argument deserialization, or when invoking the RPC.

    How to fix: Check your arguments are correctly serialized and are deserializable. Ensure the callee is valid.