#### Howdy, Stranger!

It looks like you're new here. If you want to get involved, click one of these buttons!

# A Threads class with coroutine

edited June 2016 Posts: 113

# 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, 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()`.

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.

### Class code

The class code

``````--# Threads

self.time = os.clock()
self.timeTick = 0.01
self.img = image(100,100)
end

local t = function() task() end
end

-- Create coroutine for all task, run once
for id = 1, n do
-- local f = function () self.taskList[id]() end
local f = function () self:taskUnit(id) end
local co = coroutine.create(f)
-- Record all tasks' status,  now should be suspended
end
end

-- self:switchPoint(id)

end

-- Switch point, put this function into the task function
-- 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
-- self:visual(id)
self.time = os.clock()
coroutine.yield()
end
end

-- Put this function into draw()
if n == 0 then return end
for i = 1, n do
-- Record all the tasks' status
if not status then
return
end
end
end

--[[
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)
print("id: "..id)
vt[id]()
end
--]]
``````

The test code:

``````--# Main
function setup()

myT.timeTick = 1/60

--]]

myT:job()

end

function draw()
background(0)

myT:dispatch()

fill(244, 27, 27, 255)

sysInfo()
end

function tt ()
while true do
-- print("tt: "..os.clock())
end
end

function oo ()
while true do
-- print("oo: "..os.clock())
end
end

function mf ()
local k = 0
for i=1,10000000 do
k = k + i
-- print("mf: "..k)
-- If the time >= timeTick then pause
end
end

function pk ()
local k = 0
for i=1,10000000 do
k = k + i
-- print("pk: "..k)
-- If the time >= timeTick then pause
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.timeTick = 1/60

myT:job()
end

function draw()
background(0)

myT:dispatch()
end

...
-- Show some information
...
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
end
end
...

for i=1,200000 do
for j= 1, 300000 do
...
-- Put the switchPoint() here
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.

Tagged:

• Mod
Posts: 9,112

I didn't look at any of your code, I just read the problem discription. One way I get around the blank screen is to set some variable to true in startup. In draw(), if that variable is still true, I display what ever messages to say I'm downloading something. Once the download is done, I set that variable to false and run the normal code. But it's nice you're learning about coroutines and threads.

• edited June 2016 Posts: 113

@dave1707 Thanks for you suggestion, maybe my problem description is not so exact. I will modify it.

This is the scene: when we use socket to download several files from network, without coroutine, it have to run one after another, so we have to wait, but with coroutine, we can save time.

It is a sample in book `Programming in Lua`, the chapter 9. In its example, using socket to download 4 files, without coroutine need 15 seconds, with coroutine need 6 seconds. I do not know if it is different in `Codea`. I will try it.

• Mod
Posts: 9,112

@binaryblues I've never tried to download a lot of large files, so I never tried loading them all at the same time. I've done several small files one after the other that didn't take a very long time. Maybe I'll find a large file somewhere and try downloading it multiple times at the same time.

• Mod
Posts: 9,112

@binaryblues Here's an example of something I use. Running this the way it is will run forever because I don't have a file in the `url` to download. If you put a large file to download in the `url`, it will eventually end once it downloads it 4 times.

``````function setup()
url="http://www."
ang=0
rectMode(CENTER)
http.request(url,response1)
http.request(url,response2)
http.request(url,response3)
http.request(url,response4)
end

end

end

end

end

function draw()
background(40, 40, 50)
fill(255)
ang=ang+10
pushMatrix()
translate(WIDTH/2,HEIGHT/2)
rotate(ang)
rect(0,0,50,50)
popMatrix()
else
end
end
``````
• Mod
Posts: 2,020

@dave1707 `http.request` is asynchronous anyway, so you wouldn't need to put it on a coroutine thread.

• Posts: 113

@dave1707 @yojimbo2000 Yes, the http.request() is asynchronous, I dont know clear if the socket is asynchronous, I will delete the scene, thanks!

• Posts: 978

@binaryblues it seems like you got satisfactory solutions from the people who have replied, so I'm glad.

If you are still interested in any feedback on your code, I did run it and play with it a little, and there are a couple things I could say.

• First off I think it's a great idea, and useful for more than newcomers. `coroutine` is not in the included documentation, and it's slippery to understand even if you do find reference for it. A class that simplifies it would be great.
• I think you could cut down on the obligations you require of users: they have to initialize the class, then load all their tasks, then and only then call `job()`, make sure to include the appropriate breakpoints in their functions, and make sure to call the threads object in every `draw()` cycle.
• If I understand correctly, you can only add tasks to a threads object before you call `job()`? It would be great if you could dynamically add tasks at any time.
• I may be wrong, but it seems to me in theory you could reduce the obligations to three: initialize the object, put the breakpoints where needed in the functions, and call the threads object every `draw()` cycle. You don't need to explicitly send the functions to the threading object, because `coroutine` remembers where to resume from, and if the breakpoints are wrappers for `coroutine` calls, they will remember where to resume from also. Or am I misunderstanding?
• If you no longer need to explicitly send functions to the threader, then in theory you no longer need to call `job`, and perhaps that alone would enable dynamically adding tasks?

Again, I think this is a noble effort, and I know it's been a long time, but if you wanted to revisit it I think it would be well worth doing.

• edited April 1 Posts: 123

@UberGoober

Using your feedback I was able to simplify this into a class that is responsible for a single thread. Then in the parent where one would call a Thread they can deal with managing multiple threads. In my case I also use an inversion of control technique to manage the draw loop:

``````Thread = class()

self.time = os.clock()
self.timeTick = tick or 0.016
self.id = name
end

-- Switch point, put this function into the task function
if (os.clock() - self.time) >= self.timeTick then
self.time = os.clock()
coroutine.yield()
end
end

-- Put this function into draw()
if not status then
self.status = "Finished"
end
end

``````

And then using IOC and a mapped queue for drawing, you can dynamically add a thread anytime.

``````function draw()

goto done
else
end
end
::done::
end

--drawing
for i = 1, #drawQueue.queue do
if drawQueue.queue[i] == nil then
print('draw nil')
goto done
else
drawQueue.queue[i]()
end
::done::
end
end

``````
``````Queue = class()

function Queue:init()
self.queue = {}
self.map = {}
end

function Queue:enqueue(name, func)
self.queue[#self.queue + 1] = func
self.map[name] = #self.queue
self.map[#self.queue] = name
end

function Queue:unqueue(name)
local index = self.map[name]
for i = index, #self.queue do
self.map[i] = self.map[i + 1]
if self.map[i] then
self.map[self.map[i]] = i
end
self.queue[i] = self.queue[i + 1]
end
self.map[name] = nil
end

``````
• edited April 1 Posts: 978

Wow this looks really interesting. I wrote my comments back in 2017 when I was actively using coroutines in a game, so I had some experience. I barely remember what my own words above mean now. I’m very glad if I was helpful and this looks like a great thing to use.

Is there any chance you could make a demo project showing it in action? I think I get it in theory but it would be nice to see it in practice.

• Posts: 123

Yeah check out my thread here - I’ll add my project in a zip.

• Posts: 57

@skar could you provide a simple example of using thread to play a sound?

• Posts: 123

@krdavis based on what I understand about Codea, it’s already doing `sound()` and `music()` in threads behind the curtain. I could be wrong.

But either way, there is no data processing function that you can hook into to thread the sound/music. Wherever you call the functions will be when the sound is played, draw loops continue on their own.

The way this Thread works is by having a callback `switchPoint()` that you use inside a large data processing function, the callback checks to see how much time has passed, if more than the allotted time has passed the threads pauses and lets Codea loop the draw. From what I can tell there’s no way and no reason to use this for sounds.

• Posts: 57

@skar thanks for the info, I will take it into account while working on my project.