Search Unity

  1. Unity 6 Preview is now available. To find out what's new, have a look at our Unity 6 Preview blog post.
    Dismiss Notice
  2. Unity is excited to announce that we will be collaborating with TheXPlace for a summer game jam from June 13 - June 19. Learn more.
    Dismiss Notice

[NetCode] Duplicated input in buffer?

Discussion in 'NetCode for ECS' started by paolo_simone_enovia, Feb 3, 2020.

  1. paolo_simone_enovia

    paolo_simone_enovia

    Joined:
    Jan 17, 2020
    Posts:
    7
    Hi guys,

    I'm kinda new to Unity (and game development in general), so I could use some help :)

    I'm experimenting with NetCode and I have encountered an (at least apparently) strange behaviour where some times a single input event from client is processed multiple times on server.

    In order to recreate the "bug" starting from the NetCube example in the latest version of unity-multiplayer (0.0.4):

    1. In CubeInput.cs ->SampleCubeInput-->OnUpdate() use mouse click event instead of pressing button
      Code (CSharp):
      1. /*
      2. if (Input.GetKey("a"))
      3.     input.horizontal -= 1;
      4. if (Input.GetKey("d"))
      5.     input.horizontal += 1;
      6. if (Input.GetKey("s"))
      7.     input.vertical -= 1;
      8. if (Input.GetKey("w"))
      9.     input.vertical += 1;
      10. */
      11.  
      12. if (Input.GetMouseButtonDown(0))
      13. {
      14.    input.horizontal += 1;
      15.    UnityEngine.Debug.Log(string.Format("{0} @Tick {1}: Click!", World.Name, input.Tick));
      16. }
    2. In MoveCubeSystem.cs-->OnUpdate() log when a click event is processed
      Code (CSharp):
      1. /*
      2. if (input.horizontal > 0)
      3.     trans.Value.x += deltaTime;
      4. if (input.horizontal < 0)
      5.     trans.Value.x -= deltaTime;
      6. if (input.vertical > 0)
      7.     trans.Value.z += deltaTime;
      8. if (input.vertical < 0)
      9.     trans.Value.z -= deltaTime;
      10. */
      11.  
      12. if (input.horizontal > 0)
      13. {
      14.     trans.Value.x += 1;
      15.     UnityEngine.Debug.Log(String.Format("{0} @Tick {1}: Total clicks {2}", World.Name, tick, (int)trans.Value.x));
      16. }

    When I start the game in the Editor, most of the times work as I expect (1 Click --> Total clicks on server = 1):
    upload_2020-2-3_18-34-53.png
    (btw I assumed that the ClientWorld run the OnUpdate() so many time because of simulating multiple ticks due to prediction... is it correct?)

    But sometimes it's like the input is processed multiple times, even if the input event is registered only once (1 Click --> Total clicks on server = 2):
    upload_2020-2-3_18-43-29.png
    Seems like the same input is taken at different simulation ticks

    What is wrong? The NetCode library? The Editor? My code? (My question? :D )

    Edit: fix error in code snippet due to copy/paste
     
    Last edited: Feb 4, 2020
  2. timjohansson

    timjohansson

    Unity Technologies

    Joined:
    Jul 13, 2016
    Posts:
    473
    Yes, that is correct

    The server will keep running the game loop at a fixed frequency. When processing a player it will get the input for the tick it is processing. If that did not arrive yet the server will duplicate the most recent input it has - which in this case happens to be the tick when you clicked.
    There is an issue with adjusting the time synchronization that makes this happen more frequently than it should. Even after that is fixed you need to be aware of the fact that events can be duplicated though, and make sure to check last applied tick or something if you want to avoid it. We might add more control over how commands are duplicated when no new input has arrived in the future, but it is not on our high priority list right now.
     
    Sarkahn and paolo_simone_enovia like this.
  3. paolo_simone_enovia

    paolo_simone_enovia

    Joined:
    Jan 17, 2020
    Posts:
    7
    Oh I see... Thank you for the very fast and helpful reply!

    It makes sense, but after fiddling around a bit I couldn't figure how to do that (again, noob here, sorry).
    My first idea was to use the
    input.tick
    (which at first I completely missed, whops):

    In MoveCubeSystem.cs-->OnUpdate() add the
    input.tick
    to the console log:
    Code (CSharp):
    1. UnityEngine.Debug.Log(String.Format("{0} @Tick {1} | InputTick {2}: Total clicks {3}", World.Name, tick, input.tick, (int)trans.Value.x));
    The outcome is the following:
    upload_2020-2-4_12-21-7.png

    On the client it is evident that the input at tick 299 is used also in predicting tick 300, but in the server it seems that input.tick is always the same as PredictingTick, so I don't know how to check if the input is duplicated.

    So this bring me to asking which is the best way to check last applied tick?
     

    Attached Files:

  4. timjohansson

    timjohansson

    Unity Technologies

    Joined:
    Jul 13, 2016
    Posts:
    473
    The way to check for duplicated inputs is to compare CommandData.Tick to the requested tick like you are doing. You can also tweak the logic to store which tick the click if for or remembering last applied input if you want to allow duplicated inputs to be applied once.

    I'm not sure what the duplicated inputs on the client is - that should not happen. The only way I can think of for that to happen is if you sample the input after the prediction system is reading it - in other words there is a missing [UpdateBefore(typeof(GhosUpdateSystemGroup))] on the input sampling system. That in combination with the client not always increasing the server tick every frame since it is running with a dynamic timestep could make it look like it is sometimes duplicating commands.
     
    paolo_simone_enovia likes this.
  5. paolo_simone_enovia

    paolo_simone_enovia

    Joined:
    Jan 17, 2020
    Posts:
    7
    Ok, I was misguided by the fact that CommandData.Tick on the server is always the same as the server Tick (instead of the tick when the command was sampled), I guess it is overwritten by the framework during the duplication.

    Thanks to your suggestion I added a new field clientTick to CubeInput, to store the tick separately... then I found out that the same approach is used in DOTSSample in the field renderTick of the class UserCommand :cool::
    upload_2020-2-4_14-52-49.png

    The tick stored in the new variable remains the same when the input is duplicated on different server ticks, which makes sense and allows to build custom logic.

    Mmm, for this tests I'm using the vanilla NetCube example from unity-multiplayer repository.
    The SampleCubeInput system is in
    [UpdateInGroup(typeof(ClientSimulationSystemGroup))]
    , and if I simply add the tag
    [UpdateBefore(typeof(GhostUpdateSystemGroup))]
    it throws a warning because they are different groups.
    From the documentation (I know it's a work in progress) is not clear if GhostUpdateSystemGroup has an explicit order dependency with ClientSimulationSystemGroup... Should I move SampleCubeInput to another group?

    Thanks again for the support, It's been extremely helpful!
     
  6. timjohansson

    timjohansson

    Unity Technologies

    Joined:
    Jul 13, 2016
    Posts:
    473
    Ops, I meant UpdateBefore(typeof(GhostSimulationSystemGroup)) . GhostUpdateSystemGroup is a child of GhostSimulationSystemGroup and GhostSimulationSystemGroup is a child of ClientSImulationSystemGroup.
     
  7. paolo_simone_enovia

    paolo_simone_enovia

    Joined:
    Jan 17, 2020
    Posts:
    7
    It doesn't seem to solve the duplication on the client :/

    For me it's not a deal breaker, since it should be handled with the same logic as server side, but let me know if you want to investigate deeper and need more information.

    Keep up the awesome work! :D