[NetCode] Duplicated input in buffer?

Hi guys,

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

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):

  • In CubeInput.cs ->SampleCubeInput–>OnUpdate() use mouse click event instead of pressing button
/*
if (Input.GetKey("a"))
    input.horizontal -= 1;
if (Input.GetKey("d"))
    input.horizontal += 1;
if (Input.GetKey("s"))
    input.vertical -= 1;
if (Input.GetKey("w"))
    input.vertical += 1;
*/

if (Input.GetMouseButtonDown(0))
{
   input.horizontal += 1;
   UnityEngine.Debug.Log(string.Format("{0} @Tick {1}: Click!", World.Name, input.Tick));
}
  • In MoveCubeSystem.cs–>OnUpdate() log when a click event is processed
/*
if (input.horizontal > 0)
    trans.Value.x += deltaTime;
if (input.horizontal < 0)
    trans.Value.x -= deltaTime;
if (input.vertical > 0)
    trans.Value.z += deltaTime;
if (input.vertical < 0)
    trans.Value.z -= deltaTime;
*/

if (input.horizontal > 0)
{
    trans.Value.x += 1;
    UnityEngine.Debug.Log(String.Format("{0} @Tick {1}: Total clicks {2}", World.Name, tick, (int)trans.Value.x));
}

When I start the game in the Editor, most of the times work as I expect (1 Click → Total clicks on server = 1):


(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):
5438808--554514--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? :smile: )

Edit: fix error in code snippet due to copy/paste

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.

2 Likes

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:

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:
5441679--554919--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?

5441679--554916--upload_2020-2-4_12-20-24.png

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.

1 Like

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 :sunglasses::
5442282--554988--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!

Ops, I meant UpdateBefore(typeof(GhostSimulationSystemGroup)) . GhostUpdateSystemGroup is a child of GhostSimulationSystemGroup and GhostSimulationSystemGroup is a child of ClientSImulationSystemGroup.

It doesn’t seem to solve the duplication on the client :confused:

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! :smile: