Making a system where yielding functions can be cancelled within an array

My goal is to create an array of functions that are placed in via a function and using some OOP, I can have a “Terminate” function that ends the function from running.

Problem is, I cannot always determine what is inside the function for my use case and want to make it flexible for any kind of function where it will cancel immediately. The only thing I can guarantee is that each function will yield, whether through wait function or using API that yields normally.

The way the coroutines work I guess is that I cannot cancel functions at all without flags. I am not sure how to implement them if there are no other possibilities if I might not use pre-created function code like is given in the code.

I am ideally wanting this to be simple to use so that it can act like a module without any headaches on just making a process work.

Code
local processes = {}

function processes.Terminate(self)
	if coroutine.isyieldable(self.Process) and coroutine.yield(self.Process) then
		processes[table.find(processes, self)] = nil -- linear search
		print("Process terminated")
	end
end

function processes.New(f, ...)
	local newProcess = coroutine.create(f)
	local process = {Process = newProcess, Started = time()}
	processes[#processes + 1] = process
	coroutine.resume(newProcess, ...)
	return setmetatable(process, {
		__index = processes
	})
end

local newProcess = processes.New(function()
	wait(2)
	print("2 seconds has passed!") -- the goal is that this won't print
	return
end)
wait(1)
newProcess:Terminate()

-- probably fits better as a module but ignore that for now

Seems like what you want are Promises? If you ask me, I’d prefer using something that’s already written over trying to fumble my way around a custom implementation like this which I probably wouldn’t understand - the same case may be applicable to you as well.

Promises are cancellable, though you’d need to chain Promises to achieve the effect you’re looking for as opposed to putting everything inside an executor. It’s certainly out there but I’m not sure if Promises would be appropriate for your use case, since one hasn’t been provided.

2 Likes

I want to create individual tasks where time is of the essence, meaning I must have it stop immediately even during an ongoing yield to return information including time data.

I realize maybe a loop is better for this and can change my approach or check out Promises.

It seems to me that promises cannot solve your problem, because the cancellation of a promise does not end the thread, but rather launches an event (onCancel). Promises are only a form of abstraction of the coroutines.

To achieve your goal, you can have the Teminate method resume your coroutines with a flag so that the thread ends immediately.

Yielding with Wait is more difficult because it is the engine that is in control and you do not know when your thread will be resumed so, again, the use of flags is the only way.

I am afraid there is no other way to solve your problem without the use of flags. I am certainly not an expert and I could be wrong.

Incidentally, in the Terminate method you can mark the end time of the thread. This way your Process instance will keep the exact start and end times

local processes = {}

processes.Processes = {}

function processes.New(f, ...)
	local newProcess = coroutine.create(f)
	local process = {Process = newProcess, Started = time()}
	processes.Processes[process] = newProcess
	coroutine.resume(newProcess, ...)
	return setmetatable(process, {
		__index = processes
	})
end

function processes.Terminate(self)
	coroutine.resume(self.Process, false)
	processes.Processes[self] = nil
	self.Finished = time()
	print("Process terminated")
	return self
end

wait(5) -- wait for the game to finish loading

print("newProcess")
local newProcess
newProcess = processes.New(function()
	if coroutine.yield(newProcess.Process) then return end -- yielding at some point
	
	print("No printed!") -- the goal is that this won't print
	return
end)
wait(1)
newProcess:Terminate()

print("newProcess2")
local newProcess2
newProcess2 = processes.New(function()
	wait(2)
	if processes.Processes[newProcess2] then return end -- ending with a flag
	
	print("2 seconds has passed!") -- the goal is that this won't print
	return
end)
wait(1)
newProcess:Terminate()
1 Like