A Threads class with coroutine
Introduction
Problem description
When I tried to run some functions like terrain generating. Because it need a long time to finish, so I have to face a black screen without any information appear. This is because of the mechanism of Codea
:
when
setup()
is running, thedraw()
won't run, whensetup()
is finished, it will run thedraw()
. But these functions must run once, so they need to be put intosetup()
.
When I use these functions in setup()
and I want to see some information drawing on the screen, how can I do?
I found coroutine
can deal with this problem, then I began to learn it.
Solution
I am learning and writing a Threads
class. Using it we can solve the problem.
Threads class
Class code
The class code
--# ThreadsThreads = class()function Threads:init() self.threads = {} self.taskList = {} self.time = os.clock() self.timeTick = 0.01 self.taskID = 1 self.taskStatus = {} self.taskVT = {} self.img = image(100,100)end-- Add task function into the task listfunction Threads:addTaskToList(task) local t = function() task() end table.insert(self.taskList, t)end-- Create coroutine for all task, run oncefunction Threads:job() local n = #self.taskList for id = 1, n do -- local f = function () self.taskList[id]() end local f = function () self:taskUnit(id) end local co = coroutine.create(f) table.insert(self.threads, co) -- Record all tasks' status, now should be suspended self.taskStatus[id] = coroutine.status(co) endend-- Task unitfunction Threads:taskUnit(id) self.taskID = id self.taskList[id]() -- self:switchPoint(id) -- self.taskStatus[id] = "Finished" end-- Switch point, put this function into the task functionfunction Threads:switchPoint(id) -- Switch task,when its timetick is used and task have not finished if (os.clock() - self.time) >= self.timeTick then -- Debug info, do not put the print into the task function print("hello: No."..id.." is "..self.taskStatus[id]) -- self:visual(id) -- reset task time self.time = os.clock() -- Pause the task coroutine.yield() endend-- Put this function into draw()function Threads:dispatch() local n = #self.threads if n == 0 then return end for i = 1, n do -- Record the current task self.taskID = i local status = coroutine.resume(self.threads[i]) -- Record all the tasks' status self.taskStatus[i] = coroutine.status(self.threads[i]) -- If task finished, then remove it from self.threads, and return if not status then self.taskStatus[i] = "Finished" table.remove(self.threads, i) -- table.remove(self.taskList,i) return end endend --[[function Threads:visual(id) local n = #self.taskList local vt = {} background(18, 16, 16, 255) setContext(self.img) pushStyle() strokeWidth(1) fill(255, 211, 0, 255) -- if self.taskID == 1 then fill(241, 7, 7, 255) else fill(255, 211, 0, 255) end local w,h = self.img.width/n, self.img.height/n local x,y = 0,0 for i = 1, n do vt[i] = function () rect(100+x+(i-1)*w,100+y+(i-1)*h,w,h) end end popStyle() setContext() -- sprite(self.img,300,300) -- vt[self.taskID]() print("id: "..id) vt[id]()end--]]``` The test code:
--# Main function setup() print("Thread testing ...")
myT = Threads()myT.timeTick = 1/60myT:addTaskToList(tt)myT:addTaskToList(oo)myT:addTaskToList(mf)myT:addTaskToList(pk)--[[ myT.taskList[2]()--]]myT:job()print(unpack(myT.taskList))
end
function draw() background(0)
myT:dispatch()fill(244, 27, 27, 255)print("2: "..myT.taskStatus[1])print("length: "..#myT.taskList)sysInfo()
end
-- The task functions
function tt () while true do -- print("tt: "..os.clock()) myT:switchPoint(myT.taskID) end end
function oo () while true do -- print("oo: "..os.clock()) myT:switchPoint(myT.taskID) end end
function mf () local k = 0 for i=1,10000000 do k = k + i -- print("mf: "..k) -- If the time >= timeTick then pause myT:switchPoint(myT.taskID) end end
function pk () local k = 0 for i=1,10000000 do k = k + i -- print("pk: "..k) -- If the time >= timeTick then pause myT:switchPoint(myT.taskID) end end
### UsageAssume we have a function `createTerrain()` to generate the terrain in `setup()`, and it will take a long time, we can do like below:
function setup()
myT = Threads()myT.timeTick = 1/60myT:addTaskToList(createTerrain)myT:job()
end
function draw() background(0)
myT:dispatch()drawLoadingScreen()
end
-- This is the loading screen function drawLoadingScreen() ... -- Show some information text("Creating the terrain, please wait...") ... end
-- The function will run for a long time function createTerrain() ... for i=1,10000 do for j= 1, 100000 do ... -- Put the switchPoint() here myT:switchPoint(myT.taskID) end end ...
for i=1,200000 do for j= 1, 300000 do ... -- Put the switchPoint() here myT:switchPoint(myT.taskID) endend...
end
If the function is a method of a class, like `Map:createTerrain()`, we can load it like below:
myT:addTaskToList(function () Map:createTerrain() end)
OK, it is all.BTW. This is for the newbie, if you are experienced, please ignore it. :)