Server age countdown behaves like it's client-sided, even though it's a server script

Hey there!

Recently, I’ve been working on a server age UI that counts time since the start of the server. The timer part of it works well, but the issue is that it only works on the client, even though it’s a server script. By this I mean, every time a player joins, they see the timer reset and start counting from 0, even if someone else sees it as 1 hr+.


Code:

-- // Services \\ --
local RS = game:GetService("RunService")

-- // Variables \\ --
local start = os.clock()

-- // Main logic \\ --
RS.Heartbeat:Connect(function()
	local started = os.clock() - start
	
	local min = math.floor(started / 60)

	local sec = math.floor(started % 60)

	local hr = math.floor(min - min%60)/60

	script.Parent.Time.Text = string.format("Server age: %.2d:%.2d:%.2d", hr, min, sec)
end)

I’m well aware that this happens because of local started = os.clock() - start, however, I’m not sure what to replace it with.

1 Like

If you’re displaying this value in a gui, you need to do it from the client. When a client joins, they receive a copy of the contents in StarterGui inside their PlayerGui.

1 Like

Did you even bother reading the post? I’m trying to constantly update the value on the server for everyone to see.

os.clock() returns the amount of time since the current Luau runtime has begun. When a client joins the game and by the time this code runs from a LocalScript, the environment hasn’t been running for long, and will be different between clients. The server is one computing instance, and your goal is to display the server’s age to all clients. You need to keep track of this number on the server and replicate it to clients at an interval. In layman’s terms, at certain time periods, tell clients to represent the server’s age. This is probably best accomplished with the use of RemoteEvents.

I want to also make a quick note about your comment usage. Declaring sections of code with comment headers like services, variables, etc. is partly a waste and doesn’t help readability, since any user should be able to differentiate between clusters of code.

You don’t even need your RunService counting logic, because os.clock() is a continuously counting nominal value and will always return the amount of seconds elapsed since the Lua interpreter started consuming resources.

you can literally just use time()
yes, just that

Also, Heartbeat is only available on the client since it is supposed to run every frame, and the server has no way of knowing that

It doesn’t matter whether OP uses os.clock() or time() in this case since it doesn’t look like he needs precision past minutes. If he needs precision past seconds, he must use os.clock().

The RunService.Heartbeat event works in the server environment too. It makes sense, because it fires after every physics simulation job has finished.

I will say, os.clock is wayy more accurate than time.

For instance,

print(
    os.clock() == os.clock() --> false
)

print(
    time() == time() --> true
)

I have heard that time is planned to be deprecated, but can’t actually confirm, so don’t take my words for it. But for most cases, os.clock is the thing for the job.

I don’t know much of converting time (i’m horrible at math), but:

would this be better?

local hour = math.floor(started / 3600)
local minutes = math.floor(started % 3600)
local seconds = ---? I don't know how to calculate this, just a second. WHY IS STUDIO INSTALLING WHAT

In my reply I stated it doesn’t matter, because he isn’t looking for precision past minutes

tick is going to be deprecated in favor of os.time, time, and os.clock.

1 Like

yea, normally server age doesn’t even need precision past minutes

1 Like

It’s entirely dependent on what level of precision you want. I don’t think it’s an adopted concept that when displaying age you don’t “need” precision past minutes.

If this is true, then yes, please use os.clock()

You need have a value / attribute that kept somewhere maybe in workspace, replicatedstorage, whatever, which keeps the a os.time value of when the server launched! (must be os.time).

Then, you can use that to calculate the time properly on the client.

Something like this could work;

--\\ Client

local ReplicatedStorage = game:GetService("ReplicatedStorage")

local start = ReplicatedStorage:WaitForChild("ServerCreatedTime") and ReplicatedStorage.ServerCreatedTime.Value

game:GetService("RunService").Heartbeat:Connect(function()
    local started = os.time() - start       
 
    local min = math.floor(started / 60)

	local sec = math.floor(started % 60)

	local hr = math.floor(min - min % 60) / 60

	script.Parent.Time.Text = string.format("Server age: %.2d:%.2d:%.2d", hr, min, sec)
end)

Sleitnick has advised against os.clock except in Studio for benchmarking purposes. I’ll see if I can dig up a reference.

Here you go. It’s not super enlightening, but it’s well-worded and from what I would consider a primary source.


Devhub api reference

1 microsecond is WAY too precise for something like server age

But unfortunately that doesn’t say anything about performance or stability consequences if you use it in your game.

This is only one way of doing it, and it’s not preferred. Attributes are intended to pertain to the instances they’re assigned to. In my reply, the best way to handle replication for this would be to use RemoteEvents.

Sorry I don’t know what that means.

Why though…? I’m still confused.

Please don’t do this.

Attributes are intended to pertain to the instances they’re assigned to. Workspace is a container holding all of the game’s view geometry. It does not make sense and is horrid practice to just chuck an attribute under Workspace that relates to something completely different from geometry simply for the sake of easiness and having Roblox handle replication for you.

RemoteEvents were created specifically for networking and communication, and are meant for these kinds of things.

Being in this mindset of relying on attributes for replication introduces code smell, and you wind up running into bugs that take a lot of resources to fix!

Additionally, it would be more resource-conscious if the client asked for the value one time and then handled the counting itself. Continuously polling the server or continuously giving information to clients consumes lots of bandwidth.