RPC Details

Remote procedure calls (RPCs) let you call functions on a remote machine. It is similar to calling a normal local function and almost as easy, but there are some important differences to understand.

  • RPCs are always sent from one game object in one peer to another game objects in one or several other peers. The viewID is used internally in uLink to identify the sender and target game object. Therefore, to be able to send an RPC between peers, there has be at least one game object instantiated with a uLinkNetworkView component attached. You can get this game object instantiatied on all connected peers (all clients and the server) dynamically at runtime using uLink.Network.Instantiate() or you can get it by assigning a viewID manually to a game objects in the client scene and assigning the same manual viewID the game object in the server scene.
  • An RPC call can have as many parameters as you like. All parameters will of course be sent over the network. This makes the bandwidth increase with the more parameters you send. Therefore, you should try to minimize your parameters as much as possible if it is an RPC that will be sent often.
  • You need to determine who should receive the RPC that is being sent out. There are several RPC call modes, that cover all common use cases. You can easily invoke the RPC function on everyone, on only the server, everyone but yourself or a specific client/player.

RPC calls are normally used to execute some event on all clients in the game or pass event information specifically between two parties, but you can be creative with it and use it however you want to. For example, a server for a game which only starts after four clients have connected could send an RPC call to all clients as soon as the fourth one connects, thus starting the game. A client could send RPC calls to everyone to signal that he picked up an item. A server could send an RPC to only a particular client to initialize him right after he connects, for example, to give him his player number, spawn location, team color, etc. A client could in turn send an RPC only to the server to specify his starting options, like which color he prefers or what items he has bought.

A network view can only send RPCs and state-sync to other network views with identical uLink.NetworkViewIDs. Therefore it is very important to make sure that view IDs match up correctly. Using uLink.Network.Instantiate() to create the objects will automatically set the same view ID on all created instances of an object. Use this method as often as you can. Read more in the Instantiating Network-Aware Objects manual chapter. It is commonly used for instantiating characters and NPCs.

The other option is to assign manual viewIDs to game objetcs in the scene. Make sure you assign the same viewID to the game object in the client scene and the corresponding game object in the server scene. This is commonly used for static objects like chat windows, doors, elevators and trigger zones.

Using RPCs

There are two steps involved in setting up RPC calls: declaring the function you want to call and then invoking it.

1. Declaring an RPC function

When declaring a function you have to prefix it with an RPC attribute. The following javascript code declares an RPC function.

// All RPC declarations need the RPC attribute.
@RPC
function PrintText(text : String)
{
    Debug.Log(text);
}

Here is the same code in C#.

// All RPC declarations need the RPC attribute.
[RPC]
void PrintText(string text)
{
    Debug.Log(text);
}

2. Invoking the RPC

The following script code invokes that RPC function. This code works in both C# and javascript.

uLink.NetworkView.Get(this).RPC("PrintText", uLink.RPCMode.Others, "Hello world");

The following C#-code invokes the same RPC function. Note that this code only works when your C# class inherits uLink.MonoBehaviour.

networkView.RPC("PrintText", uLink.RPCMode.Others, "Hello world");

The RPC calling code will use the attached uLinkNetworkView component to invoke all PrintText() functions in any scripts attached to this same GameObject on all other clients (and the server) if this code is executed on one of the clients.

The first parameter to uLink.NetworkView.RPC() in the example is the name of the function to be invoked, the second determines who will have this function invoked. In this case we invoke the RPC call on everyone who is currently connected to the server. Clients which connect later will not receive this RPC.

All the following parameters are the parameters that will be passed to the RPC function and be sent across the network. In this case, the String "Hello World" will be sent as a parameter and be passed as the text parameter in the PrintText() function.

As mentioned already, in order for RPC function to work in a script, a uLinkNetworkView component must exist and be attached to the GameObject which contains the script with the RPC sending code. The uLinkNetworkView's state synchronization can be turned off when you only wish to use RPCs. An example of a game object that do not need state syncs is a chat window.

RPC calling options

There is a number of variations of the uLink.NetworkView.RPC() method, each with a different set of specified options. Beyond the string name of the RPC to be called, and the list of custom RPC arguments at the end, various options to control how the RPC call behaves can be specified.

A uLink.NetworkFlags parameter can be used to configure how the RPC call is sent over the network. With this reliability, buffering, encryption, time-stamping and RPC argument type safety can be controlled. By default all options are turned on. Encryption can only be used if it has been activated in advance. Encryption in uLink can be activated at any time per player or per server. Read more in the  Security and Encryption manual chapter.

The receivers can be dictated by a uLink.RPCMode parameter. By using different RPC modes, it is possible to send this to others, all, server, owner, etc. Check out the API documentation for a list of all options. In uLink it is perfectly possible to send an RPC to yourself. In this sense, uLink considers the uLink.Network.player to be a connected target. Sending to all will include sending to itself. Sending from the server to uLink.NetworkPlayer.server will result in the same behavior.

A Type parameter can be used to restrict the RPC to be invoked only on a specific uLink.MonoBehaviour type, and not on other script types, even if they have implemented the same RPC. There are variations of this where an instance of a uLink.MonoBehaviour object can be supplied from which the script type is taken.

Finally, there is an option to send the RPC only to a specific uLink.NetworkPlayer, which can be either a client or a server.

RPC extra parameters

When an RPC is received, you can get hold of extra information regarding the call. For example, you might want to know who sent the RPC, if it was received over an encrypted session or the time when the message was sent. This is achieved by adding extra optional parameters to the end of your RPC specification.

The uLink.NetworkMessageInfo structure

The uLink.NetworkMessageInfo structure contains all kinds of meta-info regarding an RPC call. Just add it to the end of your RPC declaration. For example, the PrintText() RPC shown above would be declared like this in javascript.

@RPC
function PrintText(text : String, info : uLink.NetworkMessageInfo)
{
    Debug.Log(text + " from " + info.sender);
}

To see all details about the info available, refer to the API documentation for uLink.NetworkMessageInfo.

uLink.BitStream

In addition to this, you can also get hold of a binary stream representing any remaining parameters left out of the RPC specification on the receiving end. This is very handy when you want to send RPC:s in a dynamic way, so that the number of parameters or even the types of the parameters change at runtime.

Let's say we rewrite the PrintText() RPC above so that the first parameter is an integer that defines the type of the second argument. It would look like this in C#.

[RPC]
void PrintText(int myTypeCode, uLink.BitStream stream)
{
    switch (myTypeCode)
    {
        case 0:
            int intValue;
            stream.Read(out intValue);
            Debug.Log("Integer value " + intValue);
            break;
        case 1:
            string stringValue;
            stream.Read(out stringValue);
            Debug.Log("String value " + stringValue);
            break;
        // ... etc
    }
}

Of course, you can use both uLink.BitStream and uLink.NetworkMessageInfo at the same time, if you wish, as shown here in C#.

[RPC]
void PrintText(int myTypeCode, uLink.BitStream stream, uLink.NetworkMessageInfo info)
{
    // ... use both the BitStream and NetworkMessageInfo objects.
}

For more info on the uLink.BitStream class, see the API documentation.

RPC data types

uLink supports a variety of data types and serializes these types automatically.

Read more about data types in the manual sections Serialization and Data Types.

RPC buffer

RPC calls can also be buffered. Buffered RPC calls are stored in the server. They are delivered in order and executed in each new connecting client. This is a very powerful tool to make it easy for clients to join existing game sessions a bit later. All object instantiations using uLink.Network.Instantiate() are buffered automatically. Instantiations are actually RPC calls under the hood. The effect of this, for new clients, is a correct order of object instantiations right after the connection to the game server has finished.

The RPC buffer can be used for any custom made RPC in a game. It is very convenient when programming the process of a player joining an existing game session. A common scenario is that every player who joins a game should first take some time to choose a team, a vehicle, equipment and so on. In this case you could send a custom RPC to everyone when the player joins, and set the RPC to be buffered. Then, whenever a new player joins the game later, the RPCs will automatically be sent to the new player and executed.

In addition, uLink has some functionality to remove RPCs from the RPC buffer. This is useful when, for example, a player disconnects or gets a time out because of inactivity or network problems. The buffer in the server should then be cleared of any RPCs for this client so that new players will never see old timed-out avatars and their equipment.

In the code example below, notice the line with uLink.Network.RemoveRPCs(). It removes all buffered RPCs for a specific player. This code is run on the server.

function uLink_OnPlayerDisconnected(player : uLink.NetworkPlayer)
{
    uLink.Network.DestroyPlayerObjects(player);
    uLink.Network.RemoveRPCs(player);
}

The exact usage of the RPC buffer is up to the game developer. This is a powerful tool that makes the initialization of new clients very easy.

When a newly connected client has received everything in the RPC buffer uLink will send the message uLink.Network.uLink_OnPreBufferedRPCs(), before the RPCs are executed. You can write some code to catch this message if it is important in the game client to handle all buffered RPCs in a controlled manner. The code example below, written in C#, catches this message and does not execute any buffered RPCs at all, because the DontExecuteOnConnected() is called on every buffered RPC. Manual execution can be handy if you want to execute the RPC buffer at a later time in the client or if you want to exclude some buffered RPCs. Only use this code example for debugging or if you need to examine every buffered RPC for some reason in a specific game situation. Remember that the uLink internal RPC for instantiating objects will be included in the list.

void uLink_OnPreBufferedRPCs(uLink.NetworkBufferedRPC[] bufferedArray)
{
   Debug.Log("Message uLink_OnPreBufferedRPCs was detected!");
   foreach (uLink.NetworkBufferedRPC rpc in bufferedArray)
   {
      if (rpc.isInstantiate)
      {
         Debug.Log("Got bufferd instantiate");
      }
      else
      {
          Debug.Log("Found RPC with name = " + rpc.rpcName);
          rpc.DontExecuteOnConnected();
          //Write more logic here for doing manual execution later...
      }
   }
}    

Reliable or unreliable

RPCs are sent reliably and in-order by default. This reduces bug risks when RPCs otherwise could disappear or could arrive in a bad order that would increase the risk of exceptions and hard-to-find bugs.

There are situations when game developers want to send unreliable RPCs. For example, the server can send an RPC to a client to play a sound or show a particle effect. If these RPCs should disappear or arrive a bit late, there would be no real harm done, and the game would still be running correctly. Unreliable RPCs can be convenient to use, as they reduce bandwidth and save server resources, by avoiding internal resends and handling acknowledgements.

The following example shows how to send an unreliable RPC in C#.

networkView.UnreliableRPC("PrintText", ulink.RPCMode.Others, "Hello world");

RPCs and authoritative server

There are a few things to keep in mind regarding RPCs when you run the uLink server as authoritative.

It is no longer possible to send an RPC with uLink.RPCmode.All or uLink.RPCmode.Others from a client. The reason for this is security. If a client would be able to send an RPC to the server and force it to relay this RPC to all other clients it could be used for hacking the game. The only one capable of using the RPC broadcast modes is the server. Clients connected to an authoritative server can only send RPCs to the server.

If some information must be sent from one client to another, that information has to be sent as an RPC from client A to the server and the server then has to send a new RPC to client B.

Coroutine RPCs

uLink supports coroutine RPCs, just like Unity's network. They are a good extra tool in the toolbox for a network programmer beacause you can delay the execution of code. Coroutines are documented in the Unity reference guide at Unity - Coroutines.

Here is a C# example of a coroutine RPC:

[RPC]
IEnumerator MyRPC(string data)
{
    Debug.Log("Got " + data);
    yield return new WaitForSeconds(3);
    Debug.Log("After 3 sec.");
}