Help with this Thread Handler

I’m unsure if I’m overdoing it but I made a module that should give me freedom to handle some threads for my combat game. When I try closing/canceling the thread it just doesn’t close it.

The reason why I made this was to handle threads in a way that can make them cancelable easily instead of having issues in every combat script I make later. The MutilateThread just closes threads and removes it from the table, and while it does get removed from the table, the thread doesn’t cancel.

local ThreadModule = {}
local Threads: {[string]: thread} = {}

local function HandleThread(Thread: thread, ...): () -> ()
	local args = {...}
	
	return coroutine.create(function()
		Thread(table.unpack(args))
	end)
end

--Adds threads into the table and make it functional
function ThreadModule.RegisterThread(ThreadName: string, Thread: thread, ...)
	assert(typeof(ThreadName) == "string", "Argument 1 needs String, received "..typeof(ThreadName))
	assert(typeof(Thread) == "thread", "Argument 2 needs Thread, received "..typeof(Thread))
	
	if Threads[ThreadName] ~= nil then
		ThreadModule.MutilateThread(ThreadName)
	else
		Threads[ThreadName] = {HandleThread(Thread, ...)}
	end
end

--calls the thread as a function
function ThreadModule.CallThread(ThreadName: string): () -> any?
	local FoundThread = Threads[ThreadName]
	
	if FoundThread then
		for _, Thread in FoundThread do
			if coroutine.status(Thread) == "dead" or coroutine.status(Thread) == "suspended" then
				ThreadModule.MutilateThread(ThreadName)
				return
			end
			coroutine.resume(Thread)
		end
	end
end

--cancels and deletes the thread from the table
function ThreadModule.MutilateThread(ThreadName: string)
	local FoundThread = Threads[ThreadName]
	
	if FoundThread then
		for _, Thread in FoundThread do
			task.cancel(Thread)
			Threads[ThreadName] = nil
		end
	else
		warn("THREAD WAS NOT FOUND!")
	end
end

function ThreadModule.CheckThreadHealth(ThreadName: string): string
	local FoundThread = Threads[ThreadName]
	
	if FoundThread then
		for _, v in FoundThread do
			return coroutine.status(v)
		end
	end
end

return ThreadModule

I am quite sure for the threads to stop running they have to yield at some point.
Either they call a yielding function in the roblox library or they use task.wait.

But its also possible that mutilatethread is probably made wrong since it makes no sense at all.

You could use a maid as these are already made to handle connections, etc. You can just modify one to handle threads. Here’s one from Quenty.

What doesn’t make sense about that function (other than the name)

I can try to use maid or janitor to handle it

I’m not sure it makes a difference but they might be handled differently under the hood, try coroutine.close() instead of task.cancel()

nvm I was thinking perhaps the thing in Threads might not be a table and didn’t see {HandleThread(Thread, ...)}

Functions not yielding is the only thing I can think of, so just make sure you use task.wait in the functions so there are places roblox can stop them.

I fixed your code, it had some issues.

You were trying to terminate threads forcefully… you can’t do that in LUAU
You had a function HandleThread that wrapped a thread in another coroutine, which was unnecessary.
In the ThreadModule.RegisterThread function, you were storing a table of thread handles for each thread name, which made the code more complex than needed. You can directly store the coroutine handle.
The ThreadModule.CallThread function was not checking the status of the thread properly, which might lead to issue
Your code was missing the handling of a "stop" (idk how its called again i forgot the name) signal to exit threads gracefully.

local ThreadModule = {}
local Threads = {}

function ThreadModule.RegisterThread(ThreadName, Thread, ...)
    assert(type(ThreadName) == "string", "Argument 1 needs to be a string")
    assert(type(Thread) == "function", "Argument 2 needs to be a function")

    if Threads[ThreadName] ~= nil then
        ThreadModule.MutilateThread(ThreadName)
    else
        Threads[ThreadName] = coroutine.create(Thread, ...)
    end
end

function ThreadModule.CallThread(ThreadName)
    local FoundThread = Threads[ThreadName]

    if FoundThread then
        local status = coroutine.status(FoundThread)
        if status == "suspended" or status == "normal" then
            local success, error_message = coroutine.resume(FoundThread)
            if not success then
                warn("Error in thread: " .. error_message)
                ThreadModule.MutilateThread(ThreadName)
            end
        else
            ThreadModule.MutilateThread(ThreadName)
        end
    end
end

function ThreadModule.MutilateThread(ThreadName)
    local FoundThread = Threads[ThreadName]

    if FoundThread then
        coroutine.resume(FoundThread, "cancel")
        Threads[ThreadName] = nil
    else
        warn("THREAD WAS NOT FOUND!")
    end
end

function ThreadModule.CheckThreadHealth(ThreadName)
    local FoundThread = Threads[ThreadName]

    if FoundThread then
        return coroutine.status(FoundThread)
    end
end

return ThreadModule

This is kind of unrelated to the issue, but I want to point out that this is not multi-threaded. Coroutines aren’t really another thread, they only serve as ways of having one bit of code not fully “block” the other.

coroutine.wrap(function()
    while true do
        wait(1) -- As soon as the script yields, the engine sees this as an opportunity to focus on something else
        print("HI!")
    end
end)()

while true do
    -- ... like printing "Hello!"
    print("Hello!")
    -- Another yield! Time to focus on something else for a little...
    wait(1)
end

If you want a really bad analogy, it’s sort of like trying to serve multiple types of pizza to several guests. In your code, it’s like having only one box open at a time and switching which box is open whenever someone asks for a slice, but in true multithreading, it’s like having multiple of the boxes open at the same time for anybody to take a slice of any type of pizza.

If you are looking for true multithreading, you should look into Parallel Luau:

The main problem I am having with this whole module is the MutilateThread function. The main problem with it is it doesn’t stop the thread/function.

So the thread just switches back and forth “every” heartbeat?

Not every Heartbeat, rather every time your code “yields”. I’m sure you know what it means, when you call task.wait or use some sort of service that depends on some external service like HttpService.

Every time you code calls a yielding function, the engine sees this as an opportunity to do something else. After all, your code isn’t doing anything when it’s yielding. This could be to carry out other important tasks like, if it’s been long enough, render to the screen or simulate physics. This is why while true do end freezes the game, there’s simply never a yielding function that’s called to let the engine do anything other than run that loop.

In this case, every time some part of your code yields, the engine will see it as an opportunity to execute the coroutines that haven’t been run for some time (AKA when whatever function they called to go into a yielding state returns). I made a simple diagram to try and explain:

(Just assume that Blue called task.wait() without arguments, the engine will resume it as soon as possible assuming there isn’t anything else it needs to do.)

1 Like

I found a different approach to this problem, sorry for no feedback! This is amazing information you’ve given me!

This topic was automatically closed 14 days after the last reply. New replies are no longer allowed.