Lag Compensation / Simulation back in time

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?
Timestamping ?
Features · lidgren/lidgren-network-gen3 Wiki · GitHub ?

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

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:

    public int ClosestFrameId
    {
        get
        {
            if (Time.frameCount < 10)
                return 0;
            //How far back in time are they?
            int tripTime = NetworkTransport.GetCurrentRTT(Server.singleton.hostId, connectionId, out Server.singleton.error) / 2;
            float distance = -1;
            for (int i = 0; i < Server.singleton.FrameQueue.Count; i++)
            {
                if (distance > -1 && (Time.time - (tripTime * 1000f)) - Server.singleton.FrameQueue[i].Time > distance)
                {
                    return Server.singleton.FrameQueue[i - 1].Count;
                }
                distance = (Time.time - (tripTime * 1000f)) - Server.singleton.FrameQueue[i].Time;
            }
            return Server.singleton.FrameQueue[Server.singleton.FrameQueue.Count - 1].Count;
        }
    }

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:

    public Dictionary<int, SimulationFrameData> FrameData = new Dictionary<int, SimulationFrameData>();


    void Update()
    {
        if (FrameData.Count >= ServerSettings.singleton.FrameHistory)
            FrameData.Remove(Time.frameCount - ServerSettings.singleton.FrameHistory + 1);

        FrameData.Add(Time.frameCount, new SimulationFrameData()
        {
            Position = transform.position,
            Rotation = transform.rotation
        });
    }
1 Like

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?

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:

        int estimatedFrameId = ConnectedClientsDic[connectionId].ClosestFrameId;
        Action action = () =>
        {
           
        };
        SimulationHelper.Simulate(estimatedFrameId, action);

I estimate at what frame the clients are seeing.
Then I simulate that frame. Currently like this:

    public static void Simulate(int frameId, Action action)
    {
        for (int i = 0; i < SimulationObjects.Count; i++)
        {
            SimulationObjects[i].SetStateTransform(frameId);
        }

        action.Invoke();

        for (int i = 0; i < SimulationObjects.Count; i++)
        {
            SimulationObjects[i].ResetStateTransform();
        }
    }

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?

Update:
Instead of RTT, I guess this is the way to go:
https://docs.unity3d.com/ScriptReference/Networking.NetworkTransport.GetRemoteDelayTimeMS.html
https://docs.unity3d.com/ScriptReference/Networking.NetworkTransport.GetNetworkTimestamp.html
I did not know they were a thing.

I could include that in my messages and then get the exact™ delay.

As I understood, RTT returns the ping with a CPU ms delay. Correct me if I’m wrong.

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.

1 Like

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.

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.

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.

2 Likes

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

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.

2 Likes

Interesting read, thanks. BTW, here is another thread that is related to the subject of lag compensation and time stamp usage.

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.

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.

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.

Here is how I solved it:
http://twoten.io/index.php/game-networking/a-guide-to-lag-compensation-in-unity/

1 Like

Nice job, thanks for post.

Inspired by yours :wink:

1 Like

You’ve done even better) Keep going ;):wink:

1 Like