Client Timer In Sync

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.

2 Likes

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

1 Like

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.

2 Likes

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: https://github.com/Quenty/NevermoreEngine/blob/version2/Modules/Shared/TimeSync/TimeSyncService.lua

6 Likes

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.

4 Likes

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!

11 Likes

Thought so.

I was about to say it would be kind of negligible in a scenario of stage timers.

Thanks man.

1 Like

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.

1 Like

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?

1 Like

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.

2 Likes

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
4 Likes

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.

:stuck_out_tongue:

2 Likes

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.

1 Like

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

1 Like

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).

1 Like