Search Unity

  1. Megacity Metro Demo now available. Download now.
    Dismiss Notice
  2. Unity support for visionOS is now available. Learn more in our blog post.
    Dismiss Notice

Lag Compensation / Simulation back in time

Discussion in 'Multiplayer' started by TwoTen, Jul 31, 2017.

  1. TwoTen

    TwoTen

    Joined:
    May 25, 2016
    Posts:
    1,168
    Hello!
    What's the most accurate way to get the latency between a client and a server?
    Or alternatively, what are the best approaches to sync time? That way you can include the time in the message.

    Anyone know the details of how others achieve it?
    http://www.raknet.net/raknet/manual/timestamping.html ?
    https://github.com/lidgren/lidgren-network-gen3/wiki/Features#utility-features ?


    What I need to achieve:
    I am working on Lag Compensation. So what I need is an approximation of how many seconds ago a specific message was sent.

    Anyone have experience with this?

    SOLVED, HERE IS HOW I APPROACHED IT IN DETAIL:
    https://twotenpvp.github.io/lag-compensation-in-unity.html
     
    Last edited: Oct 22, 2017
  2. TwoTen

    TwoTen

    Joined:
    May 25, 2016
    Posts:
    1,168
    This is what I am currently doing. Basically, the lag compensation will be for players. So what I will do is this: Every frame every player position is saved in a queue.
    And if someone sends a queue request. I basically process how far back they are. So my current solution is to get the Id (FrameCount) of the frame that we want to simulate on.

    This is what I've come up with:
    Code (CSharp):
    1.  
    2.     public int ClosestFrameId
    3.     {
    4.         get
    5.         {
    6.             if (Time.frameCount < 10)
    7.                 return 0;
    8.             //How far back in time are they?
    9.             int tripTime = NetworkTransport.GetCurrentRTT(Server.singleton.hostId, connectionId, out Server.singleton.error) / 2;
    10.             float distance = -1;
    11.             for (int i = 0; i < Server.singleton.FrameQueue.Count; i++)
    12.             {
    13.                 if (distance > -1 && (Time.time - (tripTime * 1000f)) - Server.singleton.FrameQueue[i].Time > distance)
    14.                 {
    15.                     return Server.singleton.FrameQueue[i - 1].Count;
    16.                 }
    17.                 distance = (Time.time - (tripTime * 1000f)) - Server.singleton.FrameQueue[i].Time;
    18.             }
    19.             return Server.singleton.FrameQueue[Server.singleton.FrameQueue.Count - 1].Count;
    20.         }
    21.     }
    22.  
    Is this a good way to do it? Or how would you guys approach this?


    NOTE: I am not using the Queue data structure, dumb of me to name it that haha.
    I am using a List (not linked). Adding to the end each frame. And removing the first if frames in queue is > ConfigurableValue.

    This should have Time complexity of o(1) if I remember correctly.
    EDIT:
    After thinking about it for more than half a second. Removing at index 0 has a Time complexity of o(n). Which is a bummer. But I need to be able to iterate though them.


    To then actually simulate it. I am currently concidering a method in a helper. Where you can pass in a list of Action's. (Optimization).

    I will then set all the states needed. Invoke all the Actions (These can be raycasts etc). And then I will set the state back once the simulation is done.

    Is this even a good approach? I know this post originaly had a more basic question. But It's kinda evolved. Hope you guys don't mind haha

    For my objects that require their states saved. I am doing this:
    Code (CSharp):
    1.     public Dictionary<int, SimulationFrameData> FrameData = new Dictionary<int, SimulationFrameData>();
    2.  
    3.  
    4.     void Update()
    5.     {
    6.         if (FrameData.Count >= ServerSettings.singleton.FrameHistory)
    7.             FrameData.Remove(Time.frameCount - ServerSettings.singleton.FrameHistory + 1);
    8.  
    9.         FrameData.Add(Time.frameCount, new SimulationFrameData()
    10.         {
    11.             Position = transform.position,
    12.             Rotation = transform.rotation
    13.         });
    14.     }
     
    Last edited: Jul 31, 2017
    Deleted User likes this.
  3. robochase

    robochase

    Joined:
    Mar 1, 2014
    Posts:
    244
    i don't quite understand what you're trying to do. you're saving position & rotation snapshots of a player, and then what, exactly? trying to match up which particular snapshot a different message took place on, so you can reconcile what happened?
     
  4. TwoTen

    TwoTen

    Joined:
    May 25, 2016
    Posts:
    1,168
    Let me give an example of what I am currently using it for:
    When the server recieves a shoot message from client it first checks ammo n all of that good stuff. Then I do this:
    Code (CSharp):
    1.         int estimatedFrameId = ConnectedClientsDic[connectionId].ClosestFrameId;
    2.         Action action = () =>
    3.         {
    4.            
    5.         };
    6.         SimulationHelper.Simulate(estimatedFrameId, action);
    I estimate at what frame the clients are seeing.
    Then I simulate that frame. Currently like this:
    Code (CSharp):
    1.     public static void Simulate(int frameId, Action action)
    2.     {
    3.         for (int i = 0; i < SimulationObjects.Count; i++)
    4.         {
    5.             SimulationObjects[i].SetStateTransform(frameId);
    6.         }
    7.  
    8.         action.Invoke();
    9.  
    10.         for (int i = 0; i < SimulationObjects.Count; i++)
    11.         {
    12.             SimulationObjects[i].ResetStateTransform();
    13.         }
    14.     }
    So in my action I can run my raycasts etc. And then all the objects have a few snapshots that gets saved every frame.
    However, I am wondering. Will this be accurate? Is the NetworkTransport.GetRTT accurate? Is this a good way of doing it? How is it "usually" done?
     
  5. TwoTen

    TwoTen

    Joined:
    May 25, 2016
    Posts:
    1,168
  6. Deleted User

    Deleted User

    Guest

    As I understood, RTT returns the ping with a CPU ms delay. Correct me if I'm wrong.
     
    Last edited by a moderator: Aug 1, 2017
  7. TwoTen

    TwoTen

    Joined:
    May 25, 2016
    Posts:
    1,168
    Yes, I believe that's with the sendtime of the ack included.

    so the GetRemoteDelay is EXACTLY what I need. So with the message that has to simulate eveyrthing. I can just Include that timestamp and then calculate how many frames back I need to go. Then set all objects to their pos that frame. And run my raycasts etc.
     
    Last edited: Aug 1, 2017
    Deleted User likes this.
  8. TwoTen

    TwoTen

    Joined:
    May 25, 2016
    Posts:
    1,168
    My final solution:
    Measuring the delay they have is going to be inconsistent and It will go wrong. What I did instead:
    Everytime a the server sends a message to the other clients saying x player has moved. It also includes a frameId. When the client then shoots. It tells the server what frameId that player is on. The server then puts the time back to that exact frameId when they last got their update. And everything should match up.
     
  9. TwoTen

    TwoTen

    Joined:
    May 25, 2016
    Posts:
    1,168
    This is my progress with my methods posted above:


    Note: I added a manual coroutine here to make the game state go back to normal after a LONG time. Normally. The simulation happends within one frame. So you wont ever see / notice it.
     
  10. robochase

    robochase

    Joined:
    Mar 1, 2014
    Posts:
    244
    i'm just wondering about this because like from the client's perspective, they might only be getting server updates 15 times per second, but the client might be running at 60fps. so they very likely would be shooting a player sometime between 'server frames'.

    it seems like you'd need the client to tell the server 'hey i shot a guy at serverFrame:7+12' and the server would then jump back to frame 7, simulate forward 12 local frames, and then do the raycast. if you're using the physics engine for anything, you might run into trouble with determinism here. food for thought.
     
    larku and TwoTen like this.
  11. TwoTen

    TwoTen

    Joined:
    May 25, 2016
    Posts:
    1,168
    I am using the physics engine yes. And I find it be deterministic enough.
    As for the local frames. That is intresting indeed. A normalized value between 0 and 1 that tells the server how far to the next server frame we are. Or something like that.

    Right now I send all pos updates batched in one message. So I could then include to the server like 0.5. That would mean the clients have half the lerp left. or 0, that would indicate that we just started the server frame.


    Update:
    This is prolly the formula to use
    Command Execution Time = Current Server Time - Packet Latency - Client View Interpolation

    We run the simulation on the current time - the delay - how far they were in their interpolation
    Source: https://developer.valvesoftware.com/wiki/Source_Multiplayer_Networking#Lag_compensation
     
    Last edited: Aug 2, 2017
  12. TwoTen

    TwoTen

    Joined:
    May 25, 2016
    Posts:
    1,168
    What I decided to do is to send the server how far into the lerp we are (value between 0 and 1). When it's then time for the simulation I do this on the server:
    Set every object to (Vector3.Lerp(previousServerFrame, theServerFrameTheyProvided, theirLerpState). That way I can even estimate how far between a server frame they are.

    So if I send updates every 10 seconds. That means all objects have to do a lerp to 100% in those 10 seconds. If I then shoot 5 seconds after I got my last update. My value will be 0.5. I send that to the server. And the server then sets the objects between the previous frame and the frame provided and then runs the simulation. (Simulation just includes refreshing all colliders, running raycasts or similar things, move everything back and then update the colliders again)

    EDIT:
    After testing my method. It appears to be working pretty much too good to be true. I had one player just moving to the left all the time. And he sent 1.1 position updates a second (0.90909090909 seconds between each update, this is because I only keep once second of history data). And whenever a simulation request came in to the server. I debug logged the position that we estimated it to be at (With the between frame approximation and everything).
    So on there server (Where I don't apply any lerp). He just teleported a few meters once every second ish. But for the client. he was smoothly moving. When I then sent the sim request I logged the players position on the client end. And it matched up EXACTLY with the servers approximation. I tested under poor Network Conditions as well and it works great. If anyone is interested in my methodologies or anything related. Just hit me up. It was not very hard to get working and works fantastically.
     
    Last edited: Aug 2, 2017
    robochase and moco2k like this.
  13. moco2k

    moco2k

    Joined:
    Apr 29, 2015
    Posts:
    294
    Interesting read, thanks. BTW, here is another thread that is related to the subject of lag compensation and time stamp usage.
     
    Last edited: Aug 2, 2017
  14. TwoTen

    TwoTen

    Joined:
    May 25, 2016
    Posts:
    1,168
    Well, as you can see above. I am no longer trying to guess what frame they are at. I am providing that straight to the server. This gives me 100% accuracy (Since there is no guessing) and it also makes it a lot easier to actually do.
     
  15. moco2k

    moco2k

    Joined:
    Apr 29, 2015
    Posts:
    294
    Sure thing. Just thought it's a good reference to have in here since it also deals and started with the traditional timestamp-based approach.
     
  16. TwoTen

    TwoTen

    Joined:
    May 25, 2016
    Posts:
    1,168
    Yes, might be great for future readers. I am thinking of making a guide similar to the one about Lerping where I describe how to get this kind of lag compensation to work. I just gotta prepare some flowcharts etc for it.
     
  17. TwoTen

    TwoTen

    Joined:
    May 25, 2016
    Posts:
    1,168
  18. Deleted User

    Deleted User

    Guest

    Last edited by a moderator: Aug 4, 2017
  19. TwoTen

    TwoTen

    Joined:
    May 25, 2016
    Posts:
    1,168
    Inspired by yours ;)
     
    Deleted User likes this.
  20. Deleted User

    Deleted User

    Guest

    You've done even better) Keep going ;);)
     
    Last edited by a moderator: Aug 4, 2017
    TwoTen likes this.
  21. TwoTen

    TwoTen

    Joined:
    May 25, 2016
    Posts:
    1,168
    Last edited: Aug 8, 2017
    moco2k and thegreatzebadiah like this.
  22. Zincord

    Zincord

    Joined:
    Dec 21, 2013
    Posts:
    14
  23. TwoTen

    TwoTen

    Joined:
    May 25, 2016
    Posts:
    1,168
  24. Zincord

    Zincord

    Joined:
    Dec 21, 2013
    Posts:
    14