Coroutine used as timer

Hi,
I am using a Timer I found on the Education Roblox website. However there seems to be some problem in it in the Timer Module, I just can not figure it out myself, as this is the only part which is not getting explained

Although I consider myself already a good developer, this is a level above me.

The problem is, the function: timeUp() is running every time one additional time on top.
this function is running when the timer is finished.
on this website you can download the whole project: https://education.roblox.com/en-us/resources/battle-royale/cleanup-and-reset

this is the MatchManager:

local MatchManager = {}

-- Services
local ServerStorage = game:GetService("ServerStorage")
local ReplicatedStorage = game:GetService("ReplicatedStorage")

-- Module Scripts
local moduleScripts = ServerStorage:WaitForChild("ModuleScripts")
local playerManager = require(moduleScripts:WaitForChild("PlayerManager"))
local gameSettings = require(moduleScripts:WaitForChild("GameSettings"))
local displayManager = require(moduleScripts:WaitForChild("DisplayManager"))
local timer = require(moduleScripts:WaitForChild("Timer"))

-- Events
local events = ServerStorage:WaitForChild("Events")
local matchStart = events:WaitForChild("MatchStart")
local matchEnd = events:WaitForChild("MatchEnd")

-- Values
local displayValues = ReplicatedStorage:WaitForChild("DisplayValues")
local timeLeft = displayValues:WaitForChild("TimeLeft")

-- Creates a new timer object to be used to keep track of match time. 
local myTimer = timer.new()

-- Local Functions
local function stopTimer()
	myTimer:stop()
end

local function timeUp()
	print("Times is up")
	matchEnd:Fire(gameSettings.endStates.TimerUp)
end

local function startTimer()
	myTimer:start(gameSettings.matchDuration)
	myTimer.finished:Connect(timeUp)	

	while myTimer:isRunning() do
		-- Adding +1 makes sure the timer display ends at 1 instead of 0. 
		timeLeft.Value = (math.floor(myTimer:getTimeLeft() + 1))
		-- By not setting the time for wait, it offers more accurate looping  
		wait()
	end
end

-- Module Functions
function MatchManager.prepareGame()
	playerManager.sendPlayersToMatch()
	matchStart:Fire()
end

function MatchManager.getEndStatus(endState)
	local messageToReturn

	if endState == gameSettings.endStates.FoundWinner then
		local winnerName = playerManager.getWinnerName()
		messageToReturn = "Winner is : " .. winnerName
	elseif endState == gameSettings.endStates.TimerUp then
		messageToReturn = "Time ran out!"
	else
		messageToReturn = "Error found"
	end

	return messageToReturn
end

function MatchManager.cleanupMatch()
	playerManager.removeAllWeapons()
end

function MatchManager.resetMatch()
	playerManager.resetPlayers()
end

matchStart.Event:Connect(startTimer)
matchEnd.Event:Connect(stopTimer)

return MatchManager

and I think in this modulescript lies the problem - because the

local Timer = {}
Timer.__index = Timer

function Timer.new()
	local self = setmetatable({}, Timer)

	self._finishedEvent = Instance.new("BindableEvent")
	self.finished = self._finishedEvent.Event
	
	self._running = false
	self._startTime = nil
	self._duration = nil
	
	return self
end

function Timer:start(duration)
	if not self._running then
		local timerThread = coroutine.wrap(function()
			self._running = true
			self._duration = duration
			self._startTime = tick()
			while self._running and tick() - self._startTime < duration do
				wait()
			end
			local completed = self._running
			self._running = false
			self._startTime = nil
			self._duration = nil
			self._finishedEvent:Fire(completed)
		end)
		timerThread()
	else
		warn("Warning: timer could not start again as it is already running.")
	end
end

function Timer:getTimeLeft()
	if self._running then
		local now = tick()
		local timeLeft = self._startTime + self._duration - now
		if timeLeft < 0 then
			timeLeft = 0
		end
		return timeLeft
	else
		warn("Warning: could not get remaining time, timer is not running.")
	end
end

function Timer:isRunning()
	return self._running
end

function Timer:stop()
	self._running = false
end

return Timerlocal Timer = {}
Timer.__index = Timer

function Timer.new()
	local self = setmetatable({}, Timer)

	self._finishedEvent = Instance.new("BindableEvent")
	self.finished = self._finishedEvent.Event
	
	self._running = false
	self._startTime = nil
	self._duration = nil
	
	return self
end

function Timer:start(duration)
	if not self._running then
		local timerThread = coroutine.wrap(function()
			self._running = true
			self._duration = duration
			self._startTime = tick()
			while self._running and tick() - self._startTime < duration do
				wait()
			end
			local completed = self._running
			self._running = false
			self._startTime = nil
			self._duration = nil
			self._finishedEvent:Fire(completed)
		end)
		timerThread()
	else
		warn("Warning: timer could not start again as it is already running.")
	end
end

function Timer:getTimeLeft()
	if self._running then
		local now = tick()
		local timeLeft = self._startTime + self._duration - now
		if timeLeft < 0 then
			timeLeft = 0
		end
		return timeLeft
	else
		warn("Warning: could not get remaining time, timer is not running.")
	end
end

function Timer:isRunning()
	return self._running
end

function Timer:stop()
	self._running = false
end

return Timer

I hope someone can shine a new light on it…

1 Like

The link doesn’t work - and there is no function timeUp() in the code you’ve shown us?

Hi,
I added the matchmanger function…

why the link is not working, I don’t know… but you get there via:

than → content
and in the tutorial series it is the: Coding Project: Battle Royale
the finished project you can than download under: Cleanup and Reset

this is the link to the complete Roblox file:
BattleRoyal Lesson 7

okay,
I suprisingly just found the problem myself…
it was this line of code:

myTimer.finished:Connect(timeUp)

every time it is called in the startTimer() function - I guess an additional :Connect entry is added in the background.

Now, I will report this fault script in a different part of the forum

my solution - I added a new function which is called only ONCE

local function initTimer()
	myTimer.finished:Connect(timeUp)
end