I’m working with a disco light system. I want to be able to start a disco effect – say, lights changing colors – and stop it at any time. Also, effects must be able to run in parallel. In a module, I might have something like this:
local Module = {}
function Module.Start()
spawn(function()
while true do
-- set light color
RunService.Heartbeat:Wait()
end
end)
end
function Module.Stop()
-- stop the function wherever it is in the current frame
end
return Module
I could throw in some variable Running and check for Running to be true every repetition of the loop. If it’s false, break the loop. But that could quickly get messy with a more complicated disco effect.
How could I do this? Is this a job for coroutines? (They’ve always gone over my head.)
I mean technically speaking, even tho you may assume that a variable would get messy with a more complicated system. I still recommend just
local Module = {}
Module.Running = false
function Module.Start()
Module.Running = true
spawn(function()
while Module.Running do
-- set light color
RunService.Heartbeat:Wait()
end
end)
end
function Module.Stop()
Module.Running = false
-- stop the function wherever it is in the current frame
end
return Module
As coroutines may have a yield, I don’t see an actual way of killing a coroutine, but I don’t think they’re the best route for this type of system.
This is a little dependent on the situation. There is no coroutine.kill, but I don’t think coroutines are your solution here anyway. A much better approach to this particular example would be something along these lines:
local Module = {}
local RunService = game:GetService("RunService")
local Connection = nil
function Module.Start()
-- check not already running
Connection = RunService.Heartbeat:Connect(function(dt)
-- set light color
end)
end
function Module.Stop()
-- check running
Connection:Disconnect()
Connection = nil
end
return Module
Some more info on why you may wish to avoid using spawn here.
Darn, a coroutine.kill would be nice. I’ll certainly take both of your posts into consideration for each of the effects individually.
To add a little more, what I’m essentially trying to do is replicate the functionality of disabling/enabling a script. Given this script
while true do
print("a")
wait()
print("b")
wait()
end
I could enable/disable this script at will and it might print “a” last or it might print “b” last depending on when I disable it. Is there no general implementation to do the same thing in lua? Or do I have to use some sort of Module.Running or case-specific implementation like you’ve shown?
You should take the “co” in coroutine to mean “cooperative”. As such, you can’t just forever terminate a coroutine from the outside. The code in the coroutine and the code wanting to terminate it have to cooperate on deciding a time for the coroutine to “terminate”.
Since the Lua thread scheduler isn’t exposed the easiest way to do this is have a variable or other piece of state that both look at, and then when that state gets set the coroutine breaks whatever loops it’s in.
You can’t instantly kill a thread, the best you can do is to yield a coroutine and then delete all references to that coroutine once you decided you don’t need it anymore. Lua’s garbage collection will collect it once it notices that it isn’t active anymore and that’s the end of the line.
module.cor = nil
module.cancel = false
function module.Start()
module.cor = coroutine.create(function()
while not module.cancel do
--other code
game:GetService("RunService").Heartbeat:Wait()
end
--yielding is not required since the body got executed and the coroutine is declared dead.
--if however there's more code after this and you want to kill it right here you will need to add: coroutine.yield() here and it will yield the curoutine. Making it not run anything, then destroy the reference.
end)
module.cor.resume()
end
function module.Stop()
module.cancel = true
game:GetService("RunService").Heartbeat:Wait() --wait for the frame to end
module.cor = nil
end
There is no way to kill a running coroutine, they are automatically dead once they code inside of them has been ran completely, once dead they also cannot be restarted, the main purpose of a coroutine is to create a new thread so that loops can run in parallel while being in the same script. while there is no formal way to do this you could write your own class using module scripts, and it wouldn’t be too hard, for example:
--Inside a module script
local CoroutineClass = {}
CoroutineClass.__index = CoroutineClass
function CoroutineClass:New(Function)
local NewObj = {}
setmetatable(NewObj,self)
NewObj.Flag = true
NewObj.Function = Function
NewObj.Coroutine = NewObj:CreateCoroutine()
return NewObj
end
function CoroutineClass:CreateCoroutine()
return coroutine.create(function()
while self.Flag do
self.Function()
wait()
end
end)
end
function CoroutineClass:Start()
--chechking if the coroutine is already running and if it is return
if coroutine.status(self.Coroutine) == "running" then return end
--checking if a coroutine has ran before already and if it has then we must create a new one
if coroutine.status(self.Coroutine) == "dead" then
self.Coroutine = self:CreateCoroutine()
end
self.Flag = true
coroutine.resume(self.Coroutine)
end
function CoroutineClass:Kill()
self.Flag = false
end
return CoroutineClass
--inside of a local script / script
local CoroutineClass = --require the coroutine module
local function DoSomething()
--[[
only put here what
you want to ran inside the loop
and not the loop itself
--]]
print("Go")
wait()
print("No")
end
local Coroutine = CoroutineClass:New(DoSomething)
Coroutine:Start()--use this line here to start/restart the coroutine
wait(5)
print("Coroutine Killed")
Coroutine:Kill()--use this line here to kill the coroutine
wait(5)
print("Coroutine Restarted")
Coroutine:Start()