Coroutine.kill implementation

That doesn’t satisfy the

1 Like

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.

1 Like

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.

5 Likes

Are non-yielding blocks of code split up by the task scheduler?

This message says it will be auto gc-ed:
http://lua-users.org/lists/lua-l/2005-08/msg00552.html

1 Like

Roblox uses their own thread scheduler so I can’t guarantee anything.

3 Likes

O ok
What about first question though?

Again, I’m not sure how they handle task scheduling. They shouldn’t behave that way, but it’s entirely possible they might.

1 Like

Here’s a way I used to kill a thread:

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.

10 Likes

What hapens when you don’t reference a global within the thread though?

E.g: add

local print = print
local wait = wait

to the beginning of the script (I always do it for speed)

2 Likes

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.

Since it requires referencing a global why would I want to use it over one of the previous solutions?

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.

What do you mean by set the environment before it localizes?
The stack is different from the static globals

this is kind of late but i thought it was very interesting
after experimenting in this post
i noticed that script:Destroy() basically acts like coroutine.kill()~it GCs and everything
so is this the ultimate coroutine.kill()? and could it possibly be implemented into Roblox then since script:Destroy() already does it?
here is the test place I used: corouitnekill.rbxl (14.2 KB)

@AMD_chan I know this is very late lol but I also noticed that if you replace the wait(5) in the kill script with coroutine.yield() instead, the GC cleans up table t before the script is destroyed (since the script main thread has no references and isn’t running it gets GCed)

2 Likes

I remember playing around with this behavior a while back and facing some issues regarding the consistency at which the Lua threads get killed on :Destroy(); back then simply parenting the script to nil would disconnect all its connections and mess with some other stuff while leaving its main thread running, so I wouldn’t rely on the behavior.

if you can remember some of the things you did to break this behavior, I’d very much like to see

Edit: If you :Destroy() and set to nil inside of the script, the script will still run, but I think thats a special case because the (or a) script thread is still running

Edit 2: if a modulescript is destroyed from an outside script then the code will still run : (

1 Like

I think this could help you. @FieryEvent already said this but I think you were looking for this though.

shared.FunTables = {}
shared.CreateKillableFun = function(foo)
    local index = tostring(foo)..#shared.FunTables
    local thread = coroutine.create(foo)
    coroutine.resume(thread)
    shared.FunTables[index] = {foo,thread}
    return index,shared.FunTables[index][1]
end

shared.kill = function(Index, f)
    local env = getfenv(f)
    local thread = shared.FunTables[Index][2]
    function env:__index(k)
        if type(env[k]) == "function" and coroutine.running() == thread then
            return function()
                coroutine.yield()
            end
        else
            return env[k]
        end
    end
    setfenv(f, setmetatable({}, env))
    coroutine.resume(thread)
end-

this can go in the second script im guessing you can tell how to use it

local index,Funck = shared.CreateKillableFun(function()
    print("On")
    wait(1)
    print("Stopped?")
end)

wait(.1)
shared.kill(index, Funck)

It lets you stop a running function from outside the function.

2 Likes

Hi guys, I used the code offer by @FieryEvent (very thanks) to kill all the events “wait()” en LocalScript, but crash the memory :frowning: , What can I do to not crashing…?

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    

My LocalScript execute 2 animations, and the crashing occurs when press the key “x” several times:

UIS.InputBegan:Connect(function(tecla,isTyping)
if tecla.KeyCode == Enum.KeyCode.X then		

	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()
					print("Killed " .. tostring(thread), 0)
				end
			else
				return env[k]
			end
		end
		setfenv(f, setmetatable({}, env))
		coroutine.resume(thread)
	end

	giro = true


	function animacion()
		solte = false
		Track1.Stopped:wait()

		if solte == false then

			Track2 = game.Players.LocalPlayer.Character.Humanoid:LoadAnimation(script.Holding)
			Track2:Play()			
			print("play track2")

			--********************************Inicio Remolino*******************************
			local function remolino()
				local remolino = script.remolino:Clone()
				print("Crea remolino")
				remolino.Parent = Character.UpperTorso
				remolino.CFrame = Character.UpperTorso.CFrame * CFrame.Angles(0.4,0,0) --porque? se crea chueco??

				remolino.Transparency = 1
				wait(0.5)
				remolino.Transparency = 0.5
				local remolino = Character.UpperTorso:FindFirstChild("remolino")

				spawn(function()
					print("antes de girar")
					while giro do
						print("girando")
							remolino.CFrame = remolino.CFrame * CFrame.Angles(0,0.5,0)
							wait()
					end
				end)
				spawn(function()
					while UIS:IsKeyDown(Enum.KeyCode.X) do
						print("apareciendo")
						remolino.Transparency -= 0.05
						wait()
					end	
				end)

			end	
			--********************************Fin Remolino**********************************

			--********************************Inicio Aura*******************************
			local function aura()

				local aura = game.ReplicatedStorage:WaitForChild("aura"):Clone()
				print("aura creada", aura)	

				local weld = Instance.new("Weld")
				weld.Parent = Character.HumanoidRootPart
				weld.Part0 = Character.HumanoidRootPart
				aura.CanCollide = false
				aura.Anchored = false
				aura.Parent = Character.HumanoidRootPart
				weld.Part1 = Character.HumanoidRootPart.aura
			end
			--********************************Fin Aura**********************************

			--********************************Inicio ShakeCamera**********************************
			local function shake()
				wait(0.7)

				while  UIS:IsKeyDown(Enum.KeyCode.X)  do

					local x = math.random(-10,20)/50
					local y = math.random(-10,20)/50
					local z = math.random(-10,20)/50

					Hum.CameraOffset = Vector3.new(x,y,z)
					wait()

				end
				Hum.CameraOffset = Vector3.new(0,0,0)				
			end
			--********************************Fin ShakeCamera**********************************

			spawn(aura)
			spawn(remolino)
			spawn(shake)
		end
	end
	local thread = coroutine.create(animacion)
	coroutine.resume(thread)
	local con
	con = tecla.Changed:Connect(function(prop)
		if prop == "UserInputState" then
			
			if Track2 then
				Track2:Stop()
				print("1 detuve Track2",Track2)
			end
			kill(thread, animacion)
 end
 end)
 end
 end)	

 UIS.InputEnded:Connect(function(tecla,isTyping)
 if tecla.KeyCode == Enum.KeyCode.X then
	solte = true
	print("solte",solte)

	if Track2 then
		Track2:Stop()
		print("2 detuve Track2",Track2)
	end
	--******************************** Inicio Desvanece y borra Remolino**************************************

	local remolino = Character.UpperTorso:FindFirstChild("remolino")
	if remolino then
		spawn(function()
			while true do 

				remolino.Transparency += 0.1
				wait()
			end							
		end)
		wait(1)	
		remolino:Destroy()
		giro = false
	--******************************** Fin Desvanece y borra Remolino**************************************
	--******************************** Inicio borra Aura**************************************
		if Character.HumanoidRootPart:FindFirstChild("aura") then
			Character.HumanoidRootPart.aura:Destroy()
			Character.HumanoidRootPart.Weld:Destroy()
		end
	--******************************** Fin borra Aura**************************************
 end
 end 
 end)