#Syncing time-sensitive state between predicted Client and Server

1 messages · Page 1 of 1 (latest)

young reef
#

I need some kind of way to sync timestamp between server and client.
In a perfect world I'd have elapsedTime synced between Client and Server, but that's not the case, so I need some other workaround.

What I would want is to store event timestamp which is synced between Client and Server, but with higher priority for predicted value.
So if Client predicted event correctly - it stores current timestamp.
Unless it's a misprediction server will not correct that timestamp (because predicted timestamp might happen during partial tick and be more precise).

At the same time if Client didn't predict event at all, but server did - Client should get timestamp which can be translated to Client's local time.

I believe I can achieve that via NetworkTime singleton somehow, but I can't wrap my head around it.
Any suggestions how that could be done?

neon urchin
#

When we were talking days ago about interpolation time of remote entities on the client, that's a whole different thing

#

When rollback happens, the client sets his tick equal to whatever state just came in from the network, sets the states as well, and then just simply replays inputs until back at the present time

grizzled hemlock
#

what burt said, you should be matching network ticks so it doesn't make sense not to be have server correct

neon urchin
#

you'll likely end up writing your own clock if you're planning on doing this from scratch

#

all you need is a basic update loop clock to tick your fixed network update

young reef
neon urchin
#

None of that matters, or makes sense. You'll need to familiarize yourself with the topic more

#

try drawing it out

grizzled hemlock
#

your simulation happens on a fix tick

neon urchin
#

Your client should only be checking for network messages at the start of a predicted tick

#

so your delta is 0

grizzled hemlock
#

netocde just does partial ticks to make the client predict more accurately but it will tick full the next run

neon urchin
#

"Partial ticks" are also just called running the interpolator

#

using the clock alpha

young reef
grizzled hemlock
#

id say extropolation?

young reef
#

one sec

#

let me give you an example

neon urchin
#

We're hopefully on the same page here. I'm talking about the smoothed out frame updates between simulation ticks

grizzled hemlock
#

The last step on clients can have a lower delta time which we call a partial tick - so it is not quite fixed step on clients. This partial tick is basically reusing the prediction code to do __extrapolation __ so we can render at a higher frequency than the simulation tick rate.

neon urchin
#

Can I have a link for some context?

grizzled hemlock
#

The prediction group will run multiple times on clients, but only once on servers because the full simulation group is fixed step there. The last step on clients can have a lower delta time which we call a partial tick - so it is not quite fixed step on clients. This partial tick is basically reusing the prediction code to do extrapolation so we can render at a higher frequency than the simulation tick rate.
full paragraph

neon urchin
#

they're both ticking at the same rate.

young reef
# neon urchin None of that matters, or makes sense. You'll need to familiarize yourself with t...

So, during partial prediction tick Client predicted an event. And presentation layer of Client instantly presented that event to player.
Unless this is a total missprediction, I want the predicted data to stay, because if presentation of event rollsback - it's going to feel awful to client as it will cause stutter or some other visual lag.

At the same time, if Client didn't predict event at all, I want Server to sync it's own version of timestamp fully.

#

that event might have VFX + audio + maybe some other stuff, which is purely presentation

grizzled hemlock
#

i think you're over thinking what you need to do to make this work

young reef
#

but doesn't really affect logic

neon urchin
#

From what I gathered, the "partial tick" is the interpolation event, it's just still allowing you to sort of "predict" things on those render frames.

#

If you want things to be predicted properly, the logic should generally sit on the actual simulation

#

What exactly are you doing during these partial ticks?

young reef
#

partial tick is just a client only tick between current server tick + whatever delta there is left for current frame on client

neon urchin
#

It has a lower delta time because it's quite literally a render frame in between ticks

young reef
#

yes

neon urchin
#

What type of logic are you executing on these?

#

you said VFX, audio etc but that's still too vague

young reef
#

just prediction of simulation

young reef
grizzled hemlock
#

don't store timestamps, store network ticks

neon urchin
#

That's not the issue

#

it's the unity netcode engine running simulation logic between frames

young reef
grizzled hemlock
#

it doesn't matter if you spawn something on network tick 11 if it's a partial, because it will also spawn on network tick 11 on server

young reef
#

how can I get Client's elapsed time from tick obtained from ghost field?

grizzled hemlock
#

elapsed time is just network tick * fixed delta time

neon urchin
#

I don't think you should try to make your own solution. There's probably a way around this

grizzled hemlock
#

(personally i'd probably just avoid spawning stuff like this in a partial tick?)

neon urchin
#

I would avoid doing pretty much anything in a partial tick, if you have that control

#

player rotation is pretty much the only thing you should be extrapolating on these frames

young reef
young reef
#

I talk about netcode syntax

neon urchin
#

how? It needs to be known

young reef
#

well, if I am writing a library - I don't

neon urchin
#

w a t

grizzled hemlock
#

you can read it from the network component?

young reef
#

which one?

neon urchin
#

that's sorta what I was implying

grizzled hemlock
#

ClientServerTickRate

young reef
#

oh, that one

#

but does it exist on client though?

grizzled hemlock
young reef
#

It says it doesn't by default

grizzled hemlock
#
            tickRate.ResolveDefaults();```
#

which is really just

#

SimulationTickRate = 60;

young reef
#

ok so, if it doesn't exist - 60

grizzled hemlock
#

though i recommend having somewhere in your project setting up this component

#

because you really want to drop some of this stuff down in editor

young reef
#

btw, can it be changed in runtime?

grizzled hemlock
#

to avoid really large prediction loops due to editor overhead

grizzled hemlock
#

you'd have to somehow change client/server to sync it at same time to change

young reef
#

oh

#

So I guess best way is to create it via OnCreate

#

which means I can check for it in OnStartRunning

grizzled hemlock
#

i load it in a subscene

#

unity checks it (multiple times) every update in NetcodeServerRateManager

        {
            m_ClientSeverTickRateQuery.TryGetSingleton<ClientServerTickRate>(out var tickRate);
            tickRate.ResolveDefaults();```
#

just to be as versitile as possible

young reef
#

I see

grizzled hemlock
#

anyway i agree with Burt that what you want to do is already going to be handled in some way

#

just go check megacity and see how they predict their audio events etc

#

or even old asteroid demo

young reef
#

ok, so.
The way I see it. I can sync networkTick in ghost field. While sync time delta of partial tick in non-ghost field.

neon urchin
#

There's gotta be a way to check if you're inside of a partial tick

#

and run conditionals based off of that

young reef
#

well, maybe partial tick is not really a must, but I want to be versatile and support it just in case

neon urchin
#

why must you run vfx on a partial tick?

#

and audio etc?

young reef
#

for lowest possible latency

neon urchin
#

nobody is going to notice.

#

I can promise you.

young reef
#

maybe

neon urchin
#

This is how everyone does it

#

sim logic goes on fixed ticks

#

interpolation goes in between

#

netcode internal workings is a pointless conversation

neon urchin
#

It's like I'm talking to a help desk robot

grizzled hemlock
#

it's like i'm talking to someone who hasn't used netcode for entities

neon urchin
#

damn straight

#

@young reefDid that fix your issue?

#

If you absolutely want it on the partial tick, we could probably come up with something

young reef
#

I haven't done anything yet

young reef
#

tick * fixedDelta would give me simulation elapsed

#

not Client world

#

on which presentation relies

neon urchin
#

subPredictTargetTick down at the bottom looks like it might do something

#

InterpolationTickFraction
Might just be the related to the lagged non predicted state view

young reef
#

but does not relevant to actual elapsed time, tracked by World

neon urchin
#

the total elapsed time is probably very relevent to the actual tickrate system

#

and you can probably compute the overflow using that and the tickrate

young reef
#

that's what my question is

neon urchin
#
That because the needs for the client to keep the predicted tick in sync with the server...```
young reef
#

I talk about elapsed in presentation

neon urchin
#

prediction you mean?

#

I'm pretty sure this is predicted time

young reef
#

no

#

For systems updating in the PresentationSystemGroup or InitializationSystemGroup, or in general outside the SimulationSystemGroup, the reported timing are the one normally reported by the application loop.

neon urchin
#

yeah this is a lot of extra work for something so little

#

good luck man

tepid ibex
#

Can I ask: What is the exact use-case?

  • Specifically, is the client or server creating (i.e. raising) this timestamp?
  • And what timeline do you want to measure?
  • And do you want to measure said timeline in realtime, or in game simulation time?
  • Ideally, please provide a gameplay example.

In general, you typically want to refer to gameplay timing state with a combination of:

[GhostField] public PlayingState CurrentState; 
[GhostField] public NetworkTick CurrentStateEndServerTick; 
// OR
[GhostField] public NetworkTick CurrentStateStartTick;

And fetch the delta against the NetworkTime.ServerTick singleton. This way, you:

  • Only need to replicate this Ghost if the game starts, pauses, resumes, or ends.
  • Can handle partial ticks elegantly too.

If you need even higher precision than a tick, you do what some of these advanced racing games do with calculating exact finish line times via comparing the distance over the finish line against the translation delta of the car on that tick (vs the previous tick).

young reef
#

this is also relevant to other hybrid stuff that is not data, but instance based

#

I do get the whole idea about syncing between NetworkTick, but I'm actually struggling to figure the required API

#

for example, I sync start tick in a ghost field. Client receives it. Reads it. But what elapsed time should it use to launch audio clip at?

#

I'd say tick sync is fine (for now). At very least I already figured a way for client to keep predicted exact time if ghost is valid.

tepid ibex
#

So you want the server to begin playing this audio? Then the clients inputs to be compared against known "beat" timings similar to something like Guitar Hero?

young reef
#

nah, it's nothing like guitar hero.
I just need to sync audio start time between server and client

tepid ibex
#

For what purpose?

young reef
#

so for example if client joined mid some long audio - client will hear audio at aprox same moment as other clients

grizzled hemlock
#

do you have an example?

young reef
#

Client joins during cutscene with a monologue of some sort

tepid ibex
#

This is very doable. Essentially, on the server you say that AudioTrack01 started exactly on ServerTick T.

young reef
#

I get the concept, I just can't figure how to get Client's elapsed time from NetworkTick

#

😅

tepid ibex
#

Ah okay. Sec.

#
// Inside your System.OnUpdate (which is a PredictedSimulationSystemGroup system): 
var networkTime = SystemAPI.GetSingleton<NetworkTime>();
var clientServerTickRate = SystemAPI.GetSingleton<ClientServerTickRate>(); // As tertle says, handle if this is not present.
var dt = 1.0 / clientServerTickRate.SimulationTickRate;
// EDIT: Fixed!
var secondsSinceAudioStarted = networkTime.ServerTick.TicksSince(audioComponent.AudioStartServerTick) * dt;
if(networkTime.IsPartialTick)
   secondsSinceAudioStarted += networkTime.PartialTickFraction * dt;

// Plug that into your audio source/clip. I can't remember the exact thing to make audio sources fast forward, sec...
#

You can "queue" up some stuff like cutscenes by saying "start n ServerTicks into the future". That'll ensure that all clients have replicated that GhostField before they need to hook up to it.

young reef
#

Client world might exist minutes before it's connected

tepid ibex
#

ServerTick handles all of that. It's a synced timeline.

young reef
#

oooh

#

I get it, I don't need elapsed, I only need delta

tepid ibex
#

If ClientA joins the server first and plays for hours, then ClientB will join and receive a ServerTick of like 60,000.

#

Yeah, exactly.

young reef
#

all right, thanks

#

Hopefully that's all I need

tepid ibex
#

np 🙂 audioSource.time = secondsSinceAudioStarted; should work. You probably want to write some logic to only set this once, then modify it if it gets X ticks out of whack (e.g. due to time corrections).

#

Important note: The server is the authority on time. Thus, the client will constantly be subtly correcting its own time to ensure it's on the exact timeline dictated by the server. Thus, you can expect secondsSinceAudioStarted to diverge from realtime, over time, especially if the client has a lag spike or similar event. You can (and should) test all of this with high ping and with lag spike simulations.

In future, I want to provide some heuristic-based auto-detection of these kinds of issues, so you can correct them after you recover.

young reef
#

that would be correct?

tepid ibex
#

Oh yeah, sorry, forgot about NetworkTick having unusual syntax. Use NetworkTick.TicksSince. I.e. networkTime.ServerTick.TicksSince(startTick) lol.

young reef
#

yeah

tepid ibex
#

@young reef Just ran across this old thread. Did this approach work out for you?