It looks like you're new here. If you want to get involved, click one of these buttons!
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.
I am learning and writing a Threads
class. Using it we can solve the problem.
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
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.
Comments
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.
@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 inCodea
. I will try it.@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.
@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 theurl
, it will eventually end once it downloads it 4 times.@dave1707
http.request
is asynchronous anyway, so you wouldn't need to put it on a coroutine thread.@dave1707 @yojimbo2000 Yes, the http.request() is asynchronous, I dont know clear if the socket is asynchronous, I will delete the scene, thanks!
@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.
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.job()
, make sure to include the appropriate breakpoints in their functions, and make sure to call the threads object in everydraw()
cycle.job()
? It would be great if you could dynamically add tasks at any time.draw()
cycle. You don't need to explicitly send the functions to the threading object, becausecoroutine
remembers where to resume from, and if the breakpoints are wrappers forcoroutine
calls, they will remember where to resume from also. Or am I misunderstanding?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.
@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:
And then using IOC and a mapped queue for drawing, you can dynamically add a thread anytime.
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.
Yeah check out my thread here - I’ll add my project in a zip.
@skar could you provide a simple example of using thread to play a sound?
@krdavis based on what I understand about Codea, it’s already doing
sound()
andmusic()
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.@skar thanks for the info, I will take it into account while working on my project.