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
--# Threads
Threads = 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 list
function Threads:addTaskToList(task)
local t = function() task() end
table.insert(self.taskList, t)
end
-- Create coroutine for all task, run once
function 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)
end
end
-- Task unit
function 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 function
function 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()
end
end
-- 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
end
end
--[[
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/60
myT: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
Usage
Assume 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/60
myT: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)
end
end
...
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.