*DO NOT USE* High precision clock syncing tech between clients and server with accuracy of <1ms

I understood what replication pressure did in this specific module, I was wondering where else you used it and how you determine the Threshold constants—because it sounds like when there are many scripts using this pattern, Threshold becomes correlated with replication priority (and this leads to similar behavior as UDP with multiple priority levels).

Dang I’d hoped you structure your code in a unique way by forcing non-traditional heartbeat code in heartbeat

Replication pressure, a term I just arbitrarily named, is only used to determine when to shut off changes to the timing offsets. Just saying “now is a bad time to be calculating the synced time”, basically.

I actually do shove everything, and I mean everything, into a heartbeat update loop. I do not make use of wait()s and resuming Lua threads at all, which is probably a stupid thing but I am very stubborn like that. The biggest pain points are for UI animation, where I have to keep track of a bunch of ticks() for when animations start and stop. I have a handy easing functions module, so I can do stuff like animThingy = easing.Quad.Out(tick() - startTime)

1 Like

Is this particular to you or is there a pattern I can read more about? I basically do everything w/ coroutines (except for physics and spring animations—if you’re using Quad, why not Roblox’s tween service for efficiency?) but I want to learn more about the benefits of your style.

Oh, I think we might have a misunderstanding. I actually don’t think there is any benefit to the way I structure my code. Not performance, not intuitiveness, not organization. I just do what I do because I do ¯\_(ツ)_/¯

1 Like

Fwiw I spend more time refactoring than writing new code :sweat_smile:

2 Likes

Line 95 should subtract self.ReceiveTick not currentTick. It’s also slightly more accurate to calculate tick() directly in the remote event call. You can observe the former (and have some trouble observing the latter because Luau is so fast :heart_eyes: ) by seeing on the client in Studio Play mode that math.abs(module:GetTime()-tick()) is smaller. The difference with the line 95 bug is especially noticeable if you stably artificially lower the frame rate:

local targ=10
local t0=0
_G'bindtorenderstep'(-2^32,function()
	t0=os.clock()
end)
_G.stepped:bind(function()--do it on stepped not heartbeat so no race condition with module:Heartbeat
	repeat until os.clock()-t0>1/targ
end)

Also, tick() is going to be deprecated, and the equivalent is os.clock(), so should probably switch to that.


Have you ever run into issues with the time being able to decrease (due to resyncing)?—seems like this might have sneaky bugs. Why did you decide not to eventually stop resyncing (to solve the previous question’s problem)?

Also, do you get <1ms in your games? Maybe my networking just sucks XD

This module is really awesome

For anyone lately interested in a formal comparison of where this should be used, I simulated two five-amperage sin function platforms with TimeSync and this, clear and opaque studded platform respectively:

Edit I: Clarification: The far platforms are default replication. Note the clock models “lead” because lack passive replication delay.

Video:


(Note the glass platform actually often leading in this case; Quenty’s is slightly overshooting here, which is counter-intuitive.)


Measurements:


TimeSync:
Send 0.7kb/s
Receive 0kb/s


Fluffmiceter’s:
Send 2.1kb/s
Receive 2.0kb/s


(this is over the receive .1kb/s background radiation)


Heed:

Personally, I do not believe you should be concerned about this. When a popular game like Natural Disaster Survival can average 70kb/s send and 140kb/s receive, the in-topic discussions concerning large playercounts making this inapplicable is wrong from my understanding. The old 50kb/s recommendation was per player, and has far been exceeded in just a few years. I take advantage of this in my own games, smoothly running 100kb/s receive and near equal send with all device support.

tl;dr: they are useful and support diverse usecases, but with one Fluff clock you can have slow platforms but also superior visualization of exceptionally fast bullet replication, beyond the scope of TimeSync which AFAIK is the runner up.

4 Likes

I needed some high precision clock syncing between every client and the server, and this is perfect!
It requires close to no setup and is pretty much a drop-in replacement for os.clock() or tick().

Can highly recommend!
Thank you for releasing this great module for free!

2 Likes

Thank you so much! I initially wanted to use Quenty’s module, but I did not want to import the entire Nevermore framework to only use one module. This is all I wanted. By the way, I gave you a star on GitHub to support your work.

2 Likes

Exactly how I intend people to use it! Most of my logic is timed based off of tick(), so I intentionally wrote this so it can be a simple replacement, no additional logic needed.

1 Like

I’m confused as to what is going on in your visualization. The whole point of time syncing is to synchronize an event or movement on multiple clients and the server, but your demonstration is only showing what one client sees.

1 Like

Instead of calculating how many things are added to workspace every frame you can use Stats to check the Physics and game Data receive and send rate in kbps: Stats | Roblox Creator Documentation

4 Likes

I was not aware of that. That is really helpful!

Works awesome for projectiles! Thanks so much!

Some people, including me, are asking for a license, for legal reasons. Could you please add one to the GitHub?

This is a really useful module (and very clever implementation), but I think it’s time for it go soon with the addition of workspace:GetServerTimeNow() (coming soon based on the release notes).

4 Likes

Wow, that is awesome! It is flattering to think that Roblox recognizes the same problem that I saw and sought to provide an in-engine solution to it. As said in that post’s replies, the naive approach of taking round trip time of a remote and dividing by two is highly inaccurate because of the behavior of the packet buffer as it processes outgoing and incoming remotes. This is the exact thing that my module tries to account for, meaning that if Roblox engineers acknowledge this same issue, then it is very likely that their solution has a high level of accuracy :open_mouth:

6 Likes

I am late here but just so I understand correctly. You are syncing the client and server clock and you are able to estimate a replication delay with some calculations. With this delay you can offset the server part (or if you trust the server, interpolate the client back). What I am interested to know is how ‘getservertimenow()‘ will substitute your module. Is it because the time elapse is in milliseconds instead of seconds as in tick()

That documentation is incorrect. The function is, as far as I’m concerned, supposed to return seconds.

How will the new function be any better than tick() or os.time(). They both return seconds and asynchronized also I believe meaning that server and client wont be synched.

1 Like