Search Unity

Server using RPCMode.Server to send to itself (Workaround)

Discussion in 'Multiplayer' started by GibTreaty, Dec 23, 2014.

  1. GibTreaty

    GibTreaty

    Joined:
    Aug 25, 2010
    Posts:
    792
    So I ran into what seems like a common problem. If you are the host and you are sending a message to yourself...
    Code (csharp):
    1. networkView.RPC("FunctionName", RPCMode.Server);
    ...the function, "FunctionName", won't be called.

    Although if you used...
    Code (csharp):
    1. networkView.RPC("FunctionName", RPCMode.All);
    ...the server would receive it just fine and call the function named "FunctionName". The problem is now EVERYONE will receive that call when you may only want the server to receive it.

    What I don't understand is why RPCMode.All can be used by the server to send to the server (and everyone) but RPCMode.Server can't be used by the server to send to the server. It makes absolutely no sense.


    The Workaround
    - http://pastebin.com/bJQRbm1k

    Code (csharp):
    1. using UnityEngine;
    2. using System;
    3. using System.Reflection;
    4.  
    5. public static class NetworkExtensions {
    6.    /// <summary>
    7.    /// If the server is using RPCMode.Server to call itself it will call 'function', otherwise NetworkView.RPC will be used.
    8.    /// </summary>
    9.    public static void RPC(this NetworkView networkView, Action function, RPCMode mode, params object[] args) {
    10.      if(function == null) return;
    11.  
    12.      if(mode == RPCMode.Server && Network.isServer)
    13.        function();
    14.      else
    15.        networkView.RPC(function.Method.Name, mode, args);
    16.    }
    17.  
    18.    /// <summary>
    19.    /// If the server is using RPCMode.Server to call itself it will manually find the method to call, otherwise NetworkView.RPC will be used.
    20.    /// </summary>
    21.    public static void RPCDirect(this NetworkView networkView, string name, RPCMode mode, params object[] args) {
    22.      if(mode == RPCMode.Server && Network.isServer) {
    23.        object obj = null;
    24.        MethodInfo function = null;
    25.  
    26.        foreach(MonoBehaviour a in networkView.GetComponents<MonoBehaviour>()) {
    27.          Type type = a.GetType();
    28.  
    29.          foreach(MethodInfo method in type.GetMembers(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)) {
    30.            if(Attribute.IsDefined(method, typeof(RPC)) && method.Name == name) {
    31.              ParameterInfo[] parameters = method.GetParameters();
    32.              object[] arguments = (object[])args.Clone();
    33.  
    34.              Type networkMessageInfoType = typeof(NetworkMessageInfo);
    35.  
    36.              if(parameters.Length > 0 && parameters[parameters.Length - 1].ParameterType == networkMessageInfoType)
    37.                if(!(arguments.Length > 0 && args[arguments.Length - 1].GetType() == networkMessageInfoType)) {
    38.                  Array.Resize<object>(ref arguments, arguments.Length + 1);
    39.                  arguments[parameters.Length - 1] = new NetworkMessageInfo();
    40.                }
    41.  
    42.              if(parameters.Length == arguments.Length) {
    43.                bool allTypesCorrect = true;
    44.  
    45.                for(int i = 0; i < parameters.Length; i++)
    46.                  if(parameters[i].ParameterType != arguments[i].GetType()) {
    47.                    allTypesCorrect = false;
    48.                    break;
    49.                  }
    50.  
    51.                if(allTypesCorrect) {
    52.                  obj = a;
    53.                  function = method;
    54.                  args = arguments;
    55.                  break;
    56.                }
    57.              }
    58.            }
    59.          }
    60.  
    61.          if(function != null) break;
    62.        }
    63.  
    64.        if(obj != null && function != null)
    65.          function.Invoke(obj, args);
    66.        else
    67.          Debug.LogError("Method (" + name + ") not found.", networkView);
    68.      }
    69.      else
    70.        networkView.RPC(name, mode, args);
    71.    }
    72. }

    It can be used the same way as NetworkView.RPC, just a different name.
    Code (csharp):
    1. networkView.RPC(ActualFunction, RPCMode.Server);
    2. networkView.RPCDirect("FunctionName", RPCMode.Server);
    They just simply check to see if you are the server and are trying to send the RPC to yourself, if so it'll call the function directly. The method RPCDirect is named that otherwise it will conflict with NetworkView.RPC.

    Note: RPCDirect will not automatically account for the NetworkMessageInfo parameter (if it is present in the function) as I cannot set the properties inside of it. http://puu.sh/dFTRz/fff19459a9.png

    Use case: Let's say you've instantiated a new character on the network. It doesn't matter whether you are the server or client. You then want to send a message to the server to give you a weapon to start out with. Because it's an authoritative server you would want the server to handle giving out the weapons. But you would also want the server to handle giving out weapons to itself, but not have to use an "if" check to see if you are the server sending the RPC message to yourself.
     
    Last edited: Dec 23, 2014
  2. appels

    appels

    Joined:
    Jun 25, 2010
    Posts:
    2,687
    I think it's quite logic. It's silly to send something over the network when the destination = sender.
     
  3. GibTreaty

    GibTreaty

    Joined:
    Aug 25, 2010
    Posts:
    792
    It's not about sending it over the network at this point, it's about consistency and not having to reinvent the wheel when calling a similar function. NetworkView.RPC should be able to decide whether or not to send the server message through the network or whether it should just call it outright. In my case the function that the RPC should be calling has a NetworkMessageInfo parameter whose properties seem to only be set internally.

    Code (csharp):
    1. [RPC]
    2. public void OnPlayerStart(NetworkMessageInfo info) {
    3.    //Give weapon to info.sender
    4. }
    It'd be nice to only have to code one function to do this, not multiple ones that do the same thing in the end.

    Code (csharp):
    1. [RPC]
    2. public void OnPlayerStart(NetworkMessageInfo info) {
    3.    //Give weapon to info.sender
    4. }
    5.  
    6. public void OnServerPlayerStart(){
    7.    //Give weapon to server player
    8. }
    Making two functions may seem like nothing at first, but you may have to do this many times for other features of your game which can take up a lot of time. There's also the part where you have to write an "if" check for each and every single one, which breaks the coding rule where you are supposed to keep things simple and don't repeat yourself.
     
  4. GibTreaty

    GibTreaty

    Joined:
    Aug 25, 2010
    Posts:
    792
    RPCDirect now checks if the NetworkMessageInfo is a parameter and assigns a new NetworkMessageInfo to it which seems to still be usable. NetworkMessageInfo.sender and Network.player will both return 0 if they are both the server.
     
  5. DryBloodOrangeSoda

    DryBloodOrangeSoda

    Joined:
    Jan 22, 2015
    Posts:
    3
    I agree with GibTreaty, its simplier (and sometimes even better!) to just have one code path.
     
  6. zoran404

    zoran404

    Joined:
    Jan 11, 2015
    Posts:
    520
    GibTreaty, just let the RPC call the local function, no need to rewrite everything -_-
     
  7. GibTreaty

    GibTreaty

    Joined:
    Aug 25, 2010
    Posts:
    792
    I think you've missed the point. The RPC can't call the local function if you're the server sending the message to yourself. If you have a function that makes an RPC call to the server and any player is supposed to be able to use it, it then breaks the flow when the server can't use it. Having a built-in solution would solve this problem and you'd never have to worry about again.

     
    Last edited: Jan 23, 2015
  8. Moonstorm

    Moonstorm

    Joined:
    Jul 26, 2012
    Posts:
    23
    This is a very sexy solution. Nice work, definitely gonna be using this.

    Also, something I found VERY annoying about Unity's networking regarding RPCs -- DO NOT USE RPCMode.All

    If you have say, a method A which RPCs method B and then a separate method C, here's what can happen

    RPC.All (method A)
    RPC.All (method C)

    Server performs it as: A, B, C
    but Clients receive it as:
    B, A, C


    My work around for this is to simply never use RPCMode.All. Rather, I call the method locally on the server and then use RPCMode.Others. I will use your trick here to create a wrapper RPC function that will do this for me.
     
    GibTreaty likes this.