High precision clock syncing tech between clients and server with accuracy of <1ms

[UPDATE]: I have rewritten this module to version 2.0. This new version should be much much more stable, relies on less hacky code to accomplish practically the same result, and will maintain the same precision level even during extremely high latency situations. If you have used the original version of this code in your games, please swap it out with this new one. Updated code is found on the github, as well as the uncopylocked demo place.


(Demo of system, notice how the virtual client and server are synced to within a millisecond; watch full video here: Roblox Client-Server Ultra High Precision Clock Syncing Tech - YouTube)

Have you ever wanted to synchronize events between clients and the server, but found that tick() returns completely different values on different machines, os.time() simply isn’t accurate enough, and simply triggering an event via remote events results in clients receiving events at inconsistent timings? In that case, this system is for you!

Check it out on github: GitHub - Kenji-Shore/Roblox-Client-Server-Time-Sync-Module

This system provides incredibly high accuracy (I have measured a consistent ~1 ms accuracy in normal conditions), so much so that this can even be used for things that demand incredibly high precision, like synchronizing a bullet flying through the air on multiple clients.

How do I know it works?
Proving the accuracy of a clock syncing technology is a very difficult task because there is no perfectly synced clock to act as a baseline for comparison. So, I’ve come up with some clever ways to engineer and perfect this system.

  1. Firstly, I have observed that when you simulate a client and server in Play Solo, tick() is perfectly in sync for both the client and server. I used this knowledge to first extensively study Roblox’s networking behavior, discovering how Roblox fires off signals in larger packages which happen every few frames.
  2. To prove this system’s capabilities in a more realistic scenario, I’ve created a test where I run a virtual client and server (not Play Solo) and I visualize the two clocks in two side by side windows. I’ve recorded a video of this, and you can advance through the video frame by frame to observe the accuracy of the system: Roblox Client-Server Ultra High Precision Clock Syncing Tech - YouTube

Some extra knowledge on Roblox netcode
In a nutshell, Roblox does not fire a signal between a client and server the instant you fire a remoteevent or function; instead, what happens is Roblox waits a few frames and pools together multiple remote firings into one larger package. This is fine, as it prevents network overload and also guarantees packet ordering and delivery, but unfortunately we have no way of knowing exactly when this package gets sent, making it very difficult to make high precision clock syncing. To make a proper high precision synced clock, you must be able to offset this extra delay, which is exactly what I do. There is special logic that does a very good job at guessing what this unknown delay will be every frame on the client and the server.

74 Likes

Exactly what I needed, I need to add the ElapsedTime amount from the client time sent value and the server received time value when the bullet is created on the server, my formula for all my bullets path depend on time so synchronizing works perfectly if the client and server time are in sync, my current system works OK, but this seems like a more engineering solution and well thought, defintly will use it!

So when the bullet Starts, i’ll just do

Bullet.Position = Bullet.FormulaCallBack(TIMEFROMREMOTECALL)

1 Like

Well-made resource!
Even though I don’t have a need for this, this was a very interesting read nonetheless.

I have a question, does all roblox servers have same tick() timezone? for example if i was gonna run tick() on a roblox server located on paris and another on tokyo would they be different or same?

Tick() is different for every machine, and that includes different roblox servers. I have not investigated syncing up roblox servers but I assume it could be done with messagingservice or httpservice. This module is designed for syncing roblox clients to a roblox server.

1 Like

Is there any way that i could use os.time and sync client and server?

os.time can be used to sync a client and server with accuracy of within 1 second. This syncing tech is for purposes that require far greater precision.

I understand how you combat the problem of the package pooling delay, but how does this change the fact that it takes time for signals to reach the listener? If my ping is 100ms, it should take on average 50ms for my event to be fired from client to server, then who knows how long from the server to every individual client. You make the claim that this can do things like synchronizing bullets across multiple clients, but that simply isn’t possible without either A: introducing lag on the client who actually fired the bullet to account for time it takes to reach other clients, or B: inventing faster than light communication systems. Also, in your YouTube video test, the client and server are off by a FULL SECOND, nowhere near the 1 millisecond you advertise.

Another thing to mention is this: Roblox Virtual Server environmens are still not representative of real server environments. You are not running real web requests through Roblox’s servers, all of the content is still hosted on your local machine. Any delay that exists, if there even is one, may be either hardcoded or non-representative of the general populace. I’d love to see this working better on a real client and server but then you’d run into another problem: The time it would take for you to see the server’s data when it sends it back to you is equal to the actual delay between the client and server. There is no way to get this kind of precision because you’re essentially trying to measure with a measurement you made before knowing how to correctly measure.

Overall, I’m very skeptical of this, and I’m totally open to being proven wrong, but I don’t see anything in your explanation that contradicts what I’ve stated here. This is the age old you can’t measure the one way speed of light problem.

  1. Obviously I don’t know EXACTLY how long it took for the message to travel back and forth, but what I can do is observe the round trip time of the message (since I bounce it from the server to the client and back), average that value over several frames, and divide it by two to approximate the time for one way travel. This approximate value is more than sufficient to provide reasonably accurate results in normal conditions.
  2. You are introducing arbitrary constraints in your problem solving. No, I don’t need to introduce lag on the firing client, and no I do not have faster than light communication. A bullet takes time to physically travel, and you can use this fact to create an effectively zero-delay situation. If a bullet takes 200 milliseconds to travel from the gun to its target, as long as the time for the “bullet fire” signal to travel from the firing client to the server and then to the receiving client is UNDER 200 milliseconds, you can do the following: Upon firing the bullet, the client sends this information, PLUS the globally synced time, to the receiving client. The receiving client then sees the time that the bullet was fired, calculates how long it would take for the bullet to hit them, and then executes the “bullet hit target” code once that time is reached. Boom, now the firing client and receiving client see the same action at the same time. Bullet sync achieved.

Obviously, if you are shooting at a wall directly in front of you, there is not enough time to perfectly sync the action. I never claimed that was possible.


Look at the synced time in the simulated client and the simulated server.
Synced time on client:
image
Synced time on server:
image

These times that were printed are written in seconds and go to the 4th decimal. As you can see, the difference between the synced time on the client and server is 0.0008 seconds, or when converted to milliseconds, 0.8 milliseconds. Just as “advertised”.

Not only that, but in this paused frame of the video, you can see the same accuracy of the system by comparing the position of the blue block on the server window (on the right), to the position of the red block on the client window. On the client window, there is a red block (which is positioned according to the synced time), and a blue block (which is what Roblox’s default replication system produces). You can visualize how long it takes for Roblox’s replication to match up a moving part’s position by comparing the position of the red and blue block.

1 Like

My only concern with the way the code is implemented is that firing a RemoteFunction 60 times a second puts unnecessary network stress on the server and clients. How would this perform in 50+ player servers with a large number of RemoteEvents firing every second?

1 Like

Probably not well? If you’re trying to create a competitive game with 50+ player servers on Roblox infrastructure, then I don’t know what to tell you. Hell, 50 player servers on their own is more than enough to really stress out the network and create suboptimal playing experiences.

Take a 10 player server. That’s 600 RemoteFunction invocations a second from the server. This seems like way too much network traffic just to sync client/server clocks.

What matters isn’t the frequency of your remote calls, what matters is the bandwidth being consumed by the data being transmitted. The amount of data I send per call is basically negligible. If you are serious about accurately networked online games, you’re bound to be firing remotes containing replication data every frame; so I do not think this will be much of a problem.

Also, incidentally, by the very nature of how Roblox designed their networking, which is what I had to work around for figuring out an accurate synced time, firing many remotes at once is not substantially more expensive because the calls get grouped together in larger pools. So, if you are already firing a remote every frame per client to replicate player data, then the synced clock signal is just gonna be added onto that other call. So basically negligible, in theory.

3 Likes

I’d love to see this working in a game. You should also have a look at Quenty’s clock synchronization module https://github.com/Quenty/NevermoreEngine/tree/version2/Modules/Shared/TimeSync which probably is “good enough” if you’re willing to relax the < 1 ms requirement. It’s based on IEEE 1588 https://www.nist.gov/el/intelligent-systems-division-73500/introduction-ieee-1588. The synchronization is performed similarly to yours, except a remote call is made around once a second (configurable IIRC).

1 Like

I have a suggestion: Instead of using remote invocation calls why not just use a number value in the workspace/replicated storage?

thank you for this, os.time wasn’t syncing at all and i was so annoyed. this fixed the problem instantly. thank you for saving my time

1 Like

What are you using it for? Just curious.

More bandwith probably. Hypothetically, if you were to rely on the instance to replicate, data about where this instance is, do i have a reference to it? etc. must be processed both ways internally. Whereas a remote is a more explicit method of communication, where you’d imagine less ‘tag’ data is sent along

I use the module in my virtual piano game specifically due to all three reasons you list. The basic framework involves notes being fired as remotes containing their information (i.e. press/release, volume, etc.) from a client to the server, which then fires the info to other clients for playing/visualization. Depending on the volume and speed notes are played at, however, notes intended to be played as rolled chords/arpeggios/grace notes/etc. can get weirdly pooled in the remote scheduling, which turns what should be discrete notes into a group that appears to be played at the same time; I term this behavior “note snapping” (imagine a bus stop, etc.).

To get around this (for the most part), I timestamp notes on the client they were played and delay playing/visualizing them on receiving clients using a pseudo-scheduler tied to RenderStepped to replicate how they were played by the sender as closely as possible. The accuracy and precision of the timestamps provided by the module are basically perfect for achieving this, so I’m really grateful that it exists. I was at the end of my rope trying to get the implementation to work since tick() differed across system time zones and both os.time() and TimeSyncManager lacked the accuracy and/or precision to be used in RenderStepped scheduling.

3 Likes

That’s awesome. Have you tested this with multiple devices in the same room, like your pc and a phone on different accounts? You’re going to get the best results in simulated servers in studio, but do check its performance in live servers.

1 Like