I’m making a timer that counts down when a RemoteEvent is fired, but it creates a timer that overlaps with a timer that’s already running. My first idea was using coroutines and yielding any timer that is still happening.
Unfortunately, for some reason that stops me from replacing the timer thread variable with a new thread and running it.
local function updateTime(seconds: number)
if timerThread ~= nil then
print("There's already a timer thread, yielding")
coroutine.yield(timerThread)
timerThread = nil
end
timerThread = coroutine.wrap(function(Time)
print("Starting new timer thread")
for second = seconds, 0, -1 do
local minutes = second/60%60
local seconds = second%60
timeText.Text = string.format("%02i:%02i", minutes, seconds)
switchTick:Play()
task.wait(1)
end
end)
timerThread(seconds)
end
I tried using RunService and disconnecting old connections, but that still made timers overlap with one another. I tried using booleans and toggled it to stop old connections, but that also completely stopped the timer from even working, so I’m out of options. I’d appreciate any help with this
This isn’t valid, first of all coroutine.wrap() returns a function not a thread and second of all the coroutine.yield() function expects 0 arguments, it simply yields the execution of the current coroutine.
local Thread
local function CreateThread()
if coroutine.running() == Thread then coroutine.yield() end
Thread = coroutine.create(function() CreateThread() end)
coroutine.resume(Thread)
end
CreateThread()
Does “overlaps with timer that’s already running” just mean that you want to be able to cancel the existing timer and replace it with a new one, but you get mult timers trying to change the timeText.Text and play the animation/sound/or whatever?
Anyway, I can’t remember ever trying to make a countdown timer in Lua that was more than a simple wait(1) with some text formatting. I played around a bit with this idea that you are welcome to use/experiment with. Maybe it will spark some ideas. Doubtful it is the right way to go about this as-is, and it’s a bit slap-dash, so apologies for that. It uses Heartbeat for the timer (in it’s own coroutine) and another coroutine with an update function. It can be paused and resumed or reset to a new countdown.
local RunService = game:GetService("RunService")
local ONE_SECOND = 1
local function useAsCallback(t)
local minutes = math.floor(t/60)
local seconds = t%60
print(minutes, ":", seconds)
--timeText.Text = string.format("%02i:%02i", minutes, seconds)
--switchTick:Play()
end
-- Timer Class
-----------------------------------
local Timer = {}
Timer.__index = Timer
function Timer.new(seconds)
local self = {
_accumulator = 0,
secondsInitial = seconds,
secondsRemaining = seconds,
running = false,
heartBeat = nil,
heartBeatco = nil,
update = nil,
updateCallbackFn = nil,
}
setmetatable(self, Timer)
return self
end
function Timer:Cleanup()
self:Pause()
self._accumulator = 0
self:destroyHeartBeatTimer()
self:destroyUpdateCoroutine()
end
function Timer:Pause()
if self.running then
self.running = false
end
end
function Timer:Reset(t)
self:Pause()
if t and type(t) == "number" then
self.secondsInitial = t
end
self.secondsRemaining = self.secondsInitial
self:Start()
end
function Timer:Start()
if self.running then
return
end
if self.secondsRemaining <= 0 then
self.secondsRemaining = self.secondsInitial
end
if self.heartBeatco == nil then
self:createHeartBeatTimer()
end
self.running = true
coroutine.resume(self.update, self)
end
function Timer:attachCallbackFn(fn)
self.updateCallbackFn = fn
end
function Timer:createUpdateCoroutine()
self:Pause()
self:destroyUpdateCoroutine()
self.update = coroutine.create(function()
while true do
if self.updateCallbackFn then
self.updateCallbackFn(self.secondsRemaining)
end
if self.secondsRemaining <= 0 then
self.running = false
task.defer(self:Cleanup())
end
coroutine.yield()
end
end)
end
function Timer:destroyUpdateCoroutine()
if self.update then
self:Pause()
coroutine.close(self.update)
end
end
function Timer:createHeartBeatTimer()
self:Cleanup()
self:createUpdateCoroutine()
self.heartBeatco = coroutine.create(function()
self.heartBeat = RunService.Heartbeat:Connect(function(step)
if self.running == true then
self._accumulator += step
while self._accumulator >= ONE_SECOND and self.secondsRemaining > 0 do
self._accumulator -= ONE_SECOND
self.secondsRemaining -= ONE_SECOND
coroutine.resume(self.update, self)
end
end
end)
end)
coroutine.resume(self.heartBeatco)
end
function Timer:destroyHeartBeatTimer()
self:Pause()
if self.heartBeat then
self.heartBeat:Disconnect()
self.heartBeat = nil
end
if self.heartBeatco then
coroutine.close(self.heartBeatco)
self.heartBeatco = nil
end
end
Test
-- test
wait(4)
print("create timer with t=15 seconds")
local T = Timer.new(15)
T:attachCallbackFn(useAsCallback)
wait(2)
print("start countdown")
T:Start()
wait(5)
print("pause")
T:Pause()
wait(5)
print("resume")
T:Start()
wait(2)
print("Start while already started--shouldn't break")
T:Start()
wait(5)
print("reset with t=15")
T:Reset(15)
wait(10)
print("reset again with t=10")
T:Reset(10)
wait(12)
print("start again with existing values")
T:Start()
wait(10)
print("destroy the Timer object")
T:Cleanup()
T = nil
print("end")
This is probably a result of how I coded it, but it still overlaps with a timer instead of replacing the existing timer.
local function updateTime(seconds: number)
if coroutine.running() == timerThread then
print("Yielding")
coroutine.yield()
end
timerThread = coroutine.create(function()
updateTime()
end)
coroutine.resume(timerThread)
print("Starting new timer thread")
for second = seconds, 0, -1 do
local minutes = second/60%60
local seconds = second%60
timeText.Text = string.format("%02i:%02i", minutes, seconds)
switchTick:Play()
task.wait(1)
end
end
Here’s a video I should have put in the first place of the problem:
This was the one that fully worked, thank you so much!
Code for anyone having similar problems:
-- Services
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local Timer = require(ReplicatedStorage:WaitForChild("Timer")) -- Timer module
-- Timer variables
local newTimer = Timer.new(3600)
-- Functions
local function callbackThread(t)
local minutes = math.floor(t/60)
local seconds = t%60
timeText.Text = minutes..":"..string.format("%02i", seconds)
switchTick:Play()
end
local function updateTime(seconds: number)
newTimer:attachCallbackFn(callbackThread)
newTimer:Start()
newTimer:Reset(seconds)
end
Once again, thank you, I can finally stop stressing over this small problem.