Timer Module - Manage timers and their states at no cost!

Hello all,
I’ll be releasing a Timer module I’ve worked on.
This module was originally intended to be used for my game, but this has some uses so I decided to release it publicly. It’s meant to easily manage timers, as well as their current states. You can easily pause, kill, resume and play a timer with this.


Usecases

The main functionality the timer module offers you is:

  • Accurate delay
  • Detailed information about the timer

With this module, it’s especially easy to check for instance how much time the timer that’s running has left. Take this code for example:

local MyTimer = Timer.new(5)
MyTimer:Start()
wait(1)
print(MyTimer:GetRemaining()) -- Approximately 4 seconds.
print(MyTimer:GetState()) -- Running

Not only does it tell you how much time the timer has left, but also what state it is currently in.
If you get the state of that timer after 5 seconds had passed, it would return Dead.
What’s especially useful is that you can set callbacks for different states - you can set a callback for when the timer starts running, for when it finishes, when it dies, or when it gets stopped (with the :Stop() method).


This module is greatly optimized by using a sorted array.
It sorts all timers in the queue by their remaining time left, the one with the least time left being at the end of the array. A newly created timer is inserted into the queue through a binary search algorithm.
It only updates the remaining time left of the last timer in the queue, and when that timer dies it goes on to the next one.

The documentation has a lot more to say about this than I do :slightly_smiling_face:

Documentation

Home · Pyseph/Timer Wiki · GitHub

Source Code

https://github.com/PysephRBX/Timer/blob/main/Source.lua

Example usage
local Timer = require(script.Parent.TimerModule)

local MyTimer = Timer.new(5) -- creates a timer with a 5 second lifespan
MyTimer:OnStopped(warn) -- warns when the timer stops

print(MyTimer:GetLength()) -- 5
print(MyTimer:GetState()) -- Paused
MyTimer:Start()
print(MyTimer:IsRunning()) -- true
wait(1)
print(MyTimer:GetRemaining()) -- approx. 4 seconds
73 Likes

The module is not for sale, also can you include the source code :smiley:

1 Like

Really sorry about that; I’ve never really released any open-source modules, it should work now.
I’ll edit in the source here.

3 Likes

This seems very interesting, perhaps you should add a place for users to test this module too, would help a ton.

1 Like

I put a download to a test place (Timer.rbxl)

PS: I’ve taken the [Open Source] piece out of the thread’s title and replaced it with a short little “tag line” as per our discouragement of such tags in the Community Resources category and to fill for missing character minimums. All resources here have the implication of, and must be FOSS (free and open source software). This was not an edit made by the OP and may not reflect how they would normally write a title.

[Update 1.0.1]

  • Huge improvements to the module, thanks to CntKillMe!
    (Somewhat) short explanation of what’s changed:
    This module has been modified and has been greatly optimized by using a sorted array.
    It sorts all timers in the queue by their remaining time left, the one with the least time left being at the end of the array. A newly created timer is inserted into the queue through a binary search algorithm.
    It only updates the remaining time left of the last timer in the queue, and when that timer dies it goes on to the next one.
    the :GetRemaining() function calculates how long it has left to live through checking how long it has lived for, and running some simple arithmetic operations from there.
    The script for :GetRemaining() is just this in the source (FrozenRemaining is for when it’s paused)!:
return self.FrozenRemaining or self.Length - (os.clock() - self.Started)

Hello, I appreciate this module but I encountered an issue.

local ServerStorage = game:GetService("ServerStorage")
local TimerModule = require(ServerStorage:WaitForChild("Timer"))

local t1 = TimerModule.new(5)
local t2 = TimerModule.new(4)
local t3 = TimerModule.new(6)
t1:Start()
t2:Start()
t3:Start()

Errors with:
09:48:36.684 - ServerStorage.Timer:175: attempt to index nil with ‘GetRemaining’
09:48:36.685 - Stack Begin
09:48:36.686 - Script ‘ServerStorage.Timer’, Line 175 - function FindBestSpot
09:48:36.686 - Script ‘ServerStorage.Timer’, Line 188 - function AddToArray
09:48:36.686 - Script ‘ServerStorage.Timer’, Line 214 - function Start
09:48:36.687 - Script ‘ServerScriptService.Script’, Line 8
09:48:36.687 - Stack End

It seems like a problem with the FindBestSpot function where it is trying to use TimerArrays[0]

Hey! apparently, I somehow didn’t push last update properly.
Could you try again, and check if this was addressed?
Thanks!

The module works perfectly now, thank you!

1 Like

I’ve been toying with this for a few days and despite everything and even trying your test code and thoroughly reading the documentation, I couldn’t get this to work.

Here’s my code, not sure what I’m doing wrong.

function handler:togglePlayClock(...)	
	local pClock = timerModule.new(30)
	
	pClock:Start()
	
	pClock:OnRunning(function()
		print("while running called")
		if pClock:IsRunning() then
			print("timer running")
			network:fireAllClients("updateInterface", "playClock", pClock:GetRemaining())
		end	
	end)
	
	pClock:OnStopped(function()
		print("lol it's stopped")
		
		return true
	end)
end

Hey, try setting the OnRunning callback before calling the method Start. I’m at school, but I’d assume your OnRunning callback isnt being ran because your timers state is already in the Running state - hence it doesn’t get called.

The different state callbacks such as OnRunning, Ondied etc get called immediately after, if not simultaneously as the module changes your timers state.

[Update 1.0.2]

  • Fixed issues with callbacks when starting to play a timer
  • Timer default state is now Paused (previously Running)
4 Likes

[Update 1.0.3]

  • Addressed a bug where :GetRemaining() would return values slightly under 0 (e.g -0.06) - the module now makes sure the value returned is always 0 or above.
1 Like

[Update 1.0.4]

  • Edited how callbacks function; OnStopped is now called only when you cancel the Timer object through the :Stop() Method. Previously, OnStopped would get called no matter how the timer died - you should use OnDied for that.

There seems to be a bug? :Wait() and :OnDied dont seem to work :frowning:
Heres my code:

local timer = require(game:GetService("ReplicatedStorage").Modules:WaitForChild("Timer"))
local Timer = timer.new(5)
Timer:Start()
Timer:OnDied(function() print("Done") end)

I’ve tried it with other functions like :GetRemaining and whatnot (it prints 0), but the OnDied function never runs, same case with Timer:Wait() making it yield the script infinitely

Sorry for the trouble. I’ll look into it when I’m home.

[Update 1.0.5]

  • Addressed issues of @Sweet_Smoothies
  • Updated IncrementTime to work properly
  • Added alias for IncrementTime - IncrementRemaining
  • Added Reset method: resets the time left the TimerObject has, resetting it back to the timer’s starter length.
  • Updated docs
3 Likes

I may have found a bug. OnFinished will not fire while another timer is running. In this example t1 and t2 will trigger OnFinished simultaneously.

local Timer2 = require(workspace.Timer)
local t1 = Timer2.new(3)
local t2 = Timer2.new(5)
local t3 = Timer2.new(7)

t1:OnFinished(function()
	print("t1 Finished")
end)
t2:OnFinished(function()
	print("t2 Finished")
end)
t3:OnFinished(function()
	print("t3 Finished")
end)

t1:Start()
t2:Start() 
t3:Start()
2 Likes

I found the solution.

local CenterIdx = math.ceil((LeftIdx + RightIdx) / 2)

This line originally used math.floor but needs a math.ceil. Otherwise when a second timer is added to TimerArrays, CenterIdx will still be equal ‘0’.

--// Thank you CntKillMe
local function FindBestSpot(CompletionTime)
	local LeftIdx = 0
	local RightIdx = #TimerArrays

	if RightIdx == 0 then
		return 1
	end
	while (LeftIdx <= RightIdx) do
		local CenterIdx = math.ceil((LeftIdx + RightIdx) / 2)
		local CenterTimer = TimerArrays[CenterIdx]

		if CenterTimer == nil then
			return RightIdx + 1
		end
		if (CompletionTime > CenterTimer:GetRemaining()) then
			RightIdx = CenterIdx - 1
		elseif (CompletionTime < CenterTimer:GetRemaining()) then
			LeftIdx = CenterIdx + 1
		else
			LeftIdx = CenterIdx
			break
		end
	end

	return LeftIdx	
end