It’s not immediately synced. It’s synced after the remote event is first fired. I can’t say I’ve ever had any experience with his module, but that’s what the code looks like.
What I like to do is:
Instead of having a value ticking down, I just store the tick() in a NumberValue and then have the client do the math locally to find the time remaining
Also, sometimes the difference between the SaveClock:GetTime() and the Time Stamp (Time Stamp is MasterClock:GetTime() before firing the client) is negative. What do I do in that case?
Again, I’ve never used that module before, so I have no idea. You’d have to ask @Quenty, sorry.
It takes a round-trip for TimeSyncManager to synchronize. This is the player’s ping. You may use TimeSyncManager:IsSynced()
to determine if it’s synced.
In general, just use TimeSyncManager:GetTime() and it’ll work.
Sometimes the difference between slave and master will be negative. This is expected behavior (if the client (i.e. slave clock), is an hour ahead (in the event of TimeZones), you’ll get a negative offset)). Note that before it’s synced, it may offset by -1. I’ve updated the code so this won’t occur any longer.
https://github.com/Quenty/NevermoreEngine/blob/version2/Modules/Time/TimeSyncManager.lua
Edit: See TimeSyncService: NevermoreEngine/Modules/Shared/TimeSync/TimeSyncService.lua at version2 · Quenty/NevermoreEngine · GitHub
Whats the difference between this and just reading a number value on the serve called “Current Time” or whatever from the client?
That method also gets it somewhat precise, correct?
That method is as precise as your internet. Quenty’s method is always precise, since it doesn’t rely on the server every time you want to check the time.
This uses significantly less bandwidth and still maintains high frequency. When you read from a NumberValue on the server you get a low and uneven update rate. This, especially on 60 FPS animations, will lead to time-differences being 0 and division by zero. This is bad.
For @ninja900500’s case of doing stage timers, it’s not too big of a deal. You can probably get away with a number value that just counts down on the server. It’s what I did for a few years. However, if you’re trying to synchronize bullets or other high frequency time sensitive events you need something like this.
Edit: Sniped by Kampfkarren!
Thought so.
I was about to say it would be kind of negligible in a scenario of stage timers.
Thanks man.
Quenty’s module is most likely a solution, but since the thread doesn’t have a marked solution here’s what I do:
When a timer needs to start, I fire all clients telling them how much time is left. From there, I let the client do the countdown itself. However, to try and get it closer to the server’s timer, every two seconds or so I would send another signal to each client telling them, again, how much time is left. Once the client receives the signal, compare how much time the client thinks is left to how much time the server told it was left. If the client thinks there’s less time left than the server just said, then don’t listen to the server and stick with what the client has. If the server says there’s less time left than the client thought before getting the signal, jump down to how much time the server said.
By doing what I said above, the timer on the client will stick as close to the correct time as possible. Some signals will come faster than others, which is why you always stick with the lower amount of time left.
When I call my client with the new stage countdown time I do something like this.
local Diff = SlaveClock:GetTime()-Args.MasterClockTime
CountdownTime = Args.CountdownTime-Diff
I use diff to compensate for the time it took the client to receive the event. However, at times, the client gets negative or very extreme values for “Diff”. Any idea why and how to fix it?
The clock system already accounts for ping time. Just use :GetTime() to get the accounted for time. If you want to print out ping for some reason you can write out _pneWayDelay.
The :GetTime() from the one sent from the server to to the client via remote events or the :GetTime() right here on the client?
I’m not quite sure how I can incorporate this for a counting down timer thats in sync between client and server, thats why.
Time is shared between the client and the server. So :GetTime() returns basically the same value between both.
So you just send the client the start time of your clock, and it’s length. Here’s some sample code.
You cannot just copy this code in, but it should give you some indication of how to program it.
--- Shows time countdown using TimeSyncManager
-- @classmod TimerTextLabel
-- @author Quenty
local require = require(game:GetService("ReplicatedStorage"):WaitForChild("Nevermore"))
local BasicPane = require("BasicPane")
local TimeSyncManager = require("TimeSyncManager")
local TimerTextLabel = setmetatable({}, BasicPane)
TimerTextLabel.__index = TimerTextLabel
TimerTextLabel.ClassName = "TimerTextLabel"
function TimerTextLabel.new(textLabel)
local self = setmetatable(BasicPane.new(textLabel), TimerTextLabel)
self._length = 0
self._startOffset = 0
self.Gui.Visible = false
self._maid:GiveTask(self.VisibleChanged:Connect(function(isVisible, doNotAnimate, visibleMaid)
self.Gui.Visible = isVisible
if isVisible then
-- Setup animation
local alive = true
spawn(function()
while alive do
self:_update()
wait(0.1)
end
end)
visibleMaid:GiveTask(function()
alive = false
end)
end
end))
self:_update()
return self
end
function TimerTextLabel:WithStartOffset(startOffset)
self._startOffset = startOffset or error("No startOffset")
return self
end
function TimerTextLabel:WithLength(length)
self._length = length or error("No length")
self:_update()
return self
end
function TimerTextLabel:WithStartTimeValue(startTimeValue)
self._startTimeValue = startTimeValue or error("No startTimeValue")
self._maid:GiveTask(self._startTimeValue.Changed:Connect(function()
self:_update()
end))
self:_update()
return self
end
function TimerTextLabel:_formatTime(time)
if time <= 0 then
return "0:00"
elseif time > 60*60 then
return string.format("%.2d:%.2d:%.2d", time/(60*60), time/60%60, time%60)
else
return string.format("%.1d:%.2d", time/60%60, time%60)
end
end
function TimerTextLabel:_update()
if not self._startTimeValue then
self.Gui.Text = "0:00"
return
end
local timePassed = TimeSyncManager:GetTime() - (self._startTimeValue.Value + self._startOffset)
local timeLeft = self._length - timePassed
self.Gui.Text = self:_formatTime(timeLeft)
end
return TimerTextLabel
So how do I fix the case of players first joining and they need the countdown updated instantly for them when the clock is not sync when the player first joins?
I notice that sometimes my friend’s clock is off sync at around 5 seconds when he joins.
The clock automatically synchronizes itself, as stated above. You can just wait. If your clock is structured like mine above, it’ll auto-correct. This is nice, since it accounts for ping, et cetera.
Honestly, it’s a lot easier to just use a NumberValue and update it every 0.1 seconds on the server, if this is too confusing.
There was an issue about how the client does not pick up change on server values if the change happens too fast and the connection is low.
So when my stage changed and I had a client Changed event, it didn’t pick up the change, since changing certain changes (loading in heavy maps n stuff) dropped the connection
FYI there is no such thing as a perfectly synced time across all devices using a certain service, anywhere in the world. You can fetch time and adjust it based on round-trip time, but that’s about the best you can do.
The only difference between tick() and os.time() is that os.time() is in UTC timezone and tick() is in your local timezone. They can both be completely wrong or inaccurate if your system time isn’t set correctly, and they will definitely not do anything except a timezone correction. Their values don’t attempt to sync with Roblox servers or anything like that – you’ll have to estimate that offset yourself and use it.
Pretty much the only thing you can assume somewhat safely is that os.time() will be similar (within a few seconds threshold) across all Roblox servers (not clients, clients are the wild west because the user owns the time configuration there).