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.
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.
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:
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.)