Search Unity

Breaking the ClientRPC-Command Paradigm

Discussion in 'Multiplayer' started by KraidleyAran, Dec 11, 2016.

  1. KraidleyAran

    KraidleyAran

    Joined:
    Feb 15, 2014
    Posts:
    4
    Networking in Unity is pretty simple. As someone who tried to write their own Unity / Gamemaker in XNA, I can say that they've really made it easy for you to do things. Not only that, but there are numerous posts on the subject, talking about how to make everything work together. Most of the answers to posts I've ready are pretty similar to this:


    private void SetFired(bool fired)
    {
    if (isServer)
    {
    RpcSetFired(fired)​
    }
    else
    {
    CmdSetFired(fired)
    }​
    }

    [Command]
    void CmdSetFired(bool fired)
    {
    RpcSetFired(fired)​
    }

    [ClientRpc]
    void RpcSetFired(bool fired)
    {
    Fired = fired;​
    }

    This is actual former code I was using. This worked for me for the longest time UNTIL I decided to write my own Network Manager, that inherited from Unity's. It wasn't until I decided to create my own lobby system using Unity's matchmaking services that this code started producing lag on the client side. Literally, anything using this paradigm had some feature of lag - This code in particular was to let the server know that the player fired (not sure why i wanted to do that to start with... but that's neither here nor there), but I had a function just like it to produce a projectile, which was having the same problem. Same thing with flipping a sprite over the network - the client always lagged. I know it wasn't general lag, because movement itself was unaffected - which uses Unity's Network Transformer. The sprite flipping I'm doing isn't covered in the Animator (as far as I am aware? Now that I think about it though.....I'll research that after writing this), so the only way for me to do it was using the ClientRpc-Command Paradigm.

    After sleeping on it for a bit, drinking some coffee, and partaking in other various activties to spark my brain activity, I had this crazy idea. What if.... ClientRpcs were not needed at all? The point of the Command is for the Client to tell the Server "hey, i'm doing this" and for the server to say "okay, I'll let everyone else know YOU are doing that." The ClientRpc is for the Server to say "Hey all clients, you're doing this." and they do. So why would you have to write a Command to tell the Server you're going to do something, THEN tell it to tell the Clients?

    I haven't seen a post that says this, and would love to hear some opinions on this, but if we're to take the example above, here's how I re-wrote the code, and everything functioned perfectly on both sides:

    private void SetFired(bool fired)
    {
    Fired = fired;
    CmdSetFired(fired);​
    }

    [Command]
    void CmdSetFired(bool fired)
    {
    Fired = fired;​
    }

    No ClientRPC. I tried only with the Command, but without local set, the other clients never get the message. I applied this fix to EVERY piece of functionality that had lag attached to it,- each of which were running the original ClientRPC-Command paradigm, and it they all started functioning better. Another thing I noticed was that I was having some weird disconnect problems before applying this fix - i haven't seen a single disconnect since.

    Let me know your opinions. Is there something I'm missing here? Am I ahead of the game or have i just been reading the wrong posts? googling the wrong things?
     
  2. Lohoris2

    Lohoris2

    Joined:
    Aug 20, 2013
    Posts:
    85
    I assume Fired is a SyncVar, otherwise the other clients will never know when someone fired.

    That being said, well, this is what happens in your specific case.
    There are other cases where you may want to do things differently.

    In programming, there are always multiple ways of achieving the same goals. This is true for every aspect of programming.
     
  3. KraidleyAran

    KraidleyAran

    Joined:
    Feb 15, 2014
    Posts:
    4
    Nope, Fired is only passed in by a local function. SyncVars are for Server use only because they force the value on to the client.

    I agree there are several solutions for every problem, but the solution I'm providing is more efficient. That's why I'm wondering if perhaps there's a piece i'm missing? If not, then if people are having similar unsolved lag issues, fragmenting packets or changing message sizes may only be a temporary solution. My solution literally cuts the network traffic in half.


    EDIT: I mis-read your post. They don't need to know neccesairily if the user fired, but the general question your posing is what I was missing. Does this actually sync to the clients? -> As in, I'm going to find out if this does do that, but it's not needed for what I'm doing I don't think, but it will be needed for other pieces.
     
  4. Lohoris2

    Lohoris2

    Joined:
    Aug 20, 2013
    Posts:
    85
    You tried this with only one client, right?
    In your code you never warn the other clients about what the first client did.
     
  5. KraidleyAran

    KraidleyAran

    Joined:
    Feb 15, 2014
    Posts:
    4

    Actually, it does. As I said in the op, I wrote some code to do a flip sprite. here's the old code that I did:


    public void FlipSpriteX(bool flip)
    {
    if (isServer)
    {
    RpcFlipSpriteX(flip)​
    }
    else
    {
    CmdFlipSpriteX(flip)​
    }​
    }
    [ClientRpc]
    {
    SpriteRenderer.flipX = flip;​
    }​
    [Command]
    void CmdFlipSpriteX(bool flip)
    {
    RpcFlipSpriteX(bool flip)​
    }

    This was lagging. The player would eventually turn on the client, but it was super laggy. The server turned it perfectly fine though.

    Here's my new code:

    public void FlipSpriteX(bool flip)
    {
    SpriteRenderer.flipX = flip;
    CmdFlipSpriteX(flip);​
    }

    [Command]
    void CmdFlipSpriteX(bool flip)
    {
    SpriteRenderer.flipX = flip;​
    }

    This stopped the lag problem, and players on both client and server and can see each other change direction without a problem. So every client is receiving an update from the Command. But they set themselves first so that it happens on the Client first, then it tells the Server via Command, which then tells everyone else

    Also, I'm not using any SyncVars at all for any of my Player objects. Everything is now done through Commands only - not a single Rpc exists on my object anymore but plays exactly the same.
     
  6. thegreatzebadiah

    thegreatzebadiah

    Joined:
    Nov 22, 2012
    Posts:
    836
    @Lohoris2 is right. You're either only testing with one server and one client or there's other code we're not seeing here.

    Even in the simple two player test, how does the client know when the server wants to flip the sprite (or fire or w/e). Commands don't go from server to client, so I don't see how this could work.
     
    Lohoris2 likes this.
  7. KraidleyAran

    KraidleyAran

    Joined:
    Feb 15, 2014
    Posts:
    4
    It doesn't. I've been tinkering and testing, and just ran into an issue where the host no longer updates on the client because there's no RPC command. Man, I was super excited too....

    I still think putting the Rpc inside the Command is a bad idea, and caused my lag issue originally. But I don't really know of another way for now. Ugh, back to the drawing board.
     
  8. thegreatzebadiah

    thegreatzebadiah

    Joined:
    Nov 22, 2012
    Posts:
    836
    I send out RPCs inside Commands all the time without any issues. I think you may see some additional lag in the editor due to the extra profiling / logging done on the host but it should be smooth as butter in a build.
     
    Lohoris2 likes this.
  9. thegreatzebadiah

    thegreatzebadiah

    Joined:
    Nov 22, 2012
    Posts:
    836
    Oh one more thing I just noticed: In your original command / rpc setup the owner is going to receive their own message back from the server. So you probably want to throw a "if (!hasAuthority)" check in your RpcSetFired() / RpcFlipSpriteX() or w/e.
     
    Lohoris2 likes this.