A Threads class with coroutine

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:app

when setup() is running, the draw() won't run, when setup() is finished, it will run the draw(). But these functions must run once, so they need to be put into setup().this

When I use these functions in setup() and I want to see some information drawing on the screen, how can I do?spa

I found coroutine can deal with this problem, then I began to learn it.code

Solution

I am learning and writing a Threads class. Using it we can solve the problem.orm

Threads class

Class code

The class codeip

--# 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:rem

--# 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:it

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:io

myT:addTaskToList(function () Map:createTerrain() end)

OK, it is all.table

BTW. This is for the newbie, if you are experienced, please ignore it. :)

相關文章
相關標籤/搜索