Say there is a thread that looks something like this:
local working
local function x()
working = coroutine.wrap(function()
while true do
wait(0.1)
print("working")
end
end)
working()
end
wait(5)
coroutine.kill(working)
working = nil
The solution should work where multiple versions of x() are called in “parallel” and consecutively after the previous is finished
Ideally I want the solution to still allow yielding from inside the thread (as is done in this example through wait(0.1))
I guess something like this could work but it seems a little messy
local exists = {}
local function myWait(t)
wait(t)
if not exists[coroutine.running()] then
coroutine.yield()
end
end
local function myKill(thread)
exists[thread] = nil
if coroutine.status(thread) == "running" then
coroutine.yield(thread)
end
end
local function myWrap(f)
local thread = coroutine.wrap(f)
exists[thread] = true
return thread
end
local working
local function x()
working = myWrap(function()
while true do
myWait(0.1)
print("working")
end
end)
working()
end
x()
wait(5)
myKill(working)
working = nil
This assumes the coroutine yield change though too
You should implement a state variable, and have the inner loop check if the state is “dead”. Alternatively, you could use coroutine.yield instead of wait, and you could pass a state variable to your generator so that it gets returned by coroutine.yield. You should handle calls to wait() outside of the generator anyway, in my opinion.
This is an interesting point of discussion, because you can’t actually “kill” a coroutine unless it yields at some point, because in order for a method like “coroutine.kill” to be called you’d have to yield to the thread which handles calling that method, either with coroutine.yield or some other method that yields (e.g., wait()).
local threads = {}
local function x()
local curr
curr = coroutine.wrap(function()
while true do
wait(0.1)
if not threads[curr] then
coroutine.yield() -- kill thread // assumes roblox thread fixed
end
print("working")
end
end)
threads[curr] = true
curr()
return curr
end
local working = x()
wait(5)
threads[working] = nil
Also if wait is to be handled externally would it be like this?
-- assumes same interval for all just for brief example
local running = {}
local id = 0
local function createStepper(f)
id = id+1
running[id] = f
return id
end
local function removeStepper(k)
running[k] = nil
end
coroutine.wrap(function()
while true do
wait(0.1)
for _,f in next, running do
f()
end
end
end)()
local function step()
print("working")
end
local working = createStepper(step)
wait(5)
removeStepper(working)
Btw
So in my 2nd post (before you posted)
local function myKill(thread)
exists[thread] = nil
if coroutine.status(thread) == "running" then
coroutine.yield(thread)
end
end
Is checking the status pointless? As in will it never return “running” because it will never be ran in parallel?
local mGnr = coroutine.wrap(function(input)
while input~=‘kill’ do
print ‘running’
input = coroutine.yield()
end
end)
local t0 = tick()
while true do
wait(0.1)
if tick()-t0>5 then
mGnr ‘kill’
else
mGnr()
end
end
No, I think it should work fine since you’re calling wait().
Okay, but then you can just make multiple generators and keep track of when they were started. That’s just how I would do it, although I guess there are multiple ways to skin a cat. In general, I feel that it’s good practice to avoid a large number of concurrent loops, although ROBLOX’s task scheduler takes care of it all in the end.
local c = coroutine.create(function()
wait(5)
end)
print(coroutine.status(c))
coroutine.resume(c)
print(coroutine.status(c))
wait(5)
print(coroutine.status(c))
For first two it returns suspended and last it returns dead, when would it ever return running?
Also I think I was misusing wrap in some of my code above where I should have been using create (to get the thread) and then resume (instead of calling the function)
What do you mean by multiple generators though?
I’m having a hard time understanding how your code would scale without turning into my stepper with one universal loop example
If you don’t care about errors propagating from the coroutine to the main thread, then coroutine.wrap is fine; otherwise, use coroutine.create/resume, which calls the function in protected mode (like pcall).
As for when it would return running, presumably the only time that would happen is if you’re calling coroutine.status from inside that coroutine.
Well, I don’t see why there’s a problem with one universal loop. You could store the threads in a more specific manner if it suits you. For example, you could collect a list of threads into another thread which operates on their combined output, then pass that to a “universal loop”.
I assume the reason there’s not already a coroutine.kill method is that nobody really ever uses coroutines in that way.
I just don’t think it’d ever be a good idea to give every single thread its own loop using wait(). Personally I want more control over things like what order my threads run in, and how I handle their combined output. Also, if I only have one loop, then I can control the refresh rate with only one variable. I feel like a general coroutine.kill method should never really be necessary, but do correct me if I’m wrong.
Coroutine “killing” is generally a bad idea. Depending on the implementation the coroutine could be doing anything at the moment it’s “killed”. Not to mention state and gc cleanup that also needs to happen (which I’m pretty sure simply yielding it will cause it to memleak). You’re better off having a failsafe inside the coroutine to jump out of the loop and let it die on its own.
local function foo()
while true do
wait(0.1)
print("working")
end
end
local function kill(thread, f)
local env = getfenv(f)
function env:__index(k)
if type(env[k]) == "function" and coroutine.running() == thread then
return function()
coroutine.yield()
error("Killed " .. tostring(thread), 0)
end
else
return env[k]
end
end
setfenv(f, setmetatable({}, env))
coroutine.resume(thread)
end
local thread = coroutine.create(foo)
coroutine.resume(thread)
print(coroutine.status(thread)) --> suspended
kill(thread, foo)
wait(0.1)
print(coroutine.status(thread)) --> dead
You should then be able to build your own manager and check if the thread running is blacklisted with a dictionary, instead of just coroutine.running() == thread.
It’s a workaround to inject the killing code, it won’t kill if you don’t reference global, that’s why you should have it’s environment set by your coroutine manager, or implement a function that does the same job within the function.
Your code would either crash or die if it doesn’t reference a global anywhere, most functions don’t localize everything, the thread can only be killed when it’s running, if you don’t want to add checks while it’s running, your coroutine manager should set the environment before it localizes.