Questions about the LÖVE API, installing LÖVE and other support related questions go here.
Forum rules
Rishavs
Party member
Posts: 103
Joined: Sat Oct 17, 2009 5:29 am
Contact:

--------------------------------------------------------------

EDIT:
If you are looking for the threads example, i am putting that in this section so you dont have to go through other questions and discussions.

Here is my default code.
if you run it, you can see how the window is unresponsive for a few seconds while the logic loop runs. (if you have a non potato PC, you may increase the for loop range so the effect is more prominent)

Code: Select all

debug = true
local progress = 0

local scrWidth = love.graphics.getWidth()
local scrHeight = love.graphics.getHeight()

stime = love.timer.getTime()
backend_logic()
ttime = (love.timer.getTime() - stime)
end

function love.draw(dt)

love.graphics.print("FPS: "..tostring(love.timer.getFPS( )), 10, 10)
love.graphics.print("Time taken : "..tostring(round(ttime, 1)) .. " seconds", 10, 30)
end

function love.update(dt)

end

function backend_logic()
local smin = 1
local smax = 5000
for i = smin, smax, 1 do
print("ALL WORK AND NO PLAY MAKES JACK A DULL BOY")
progress = round(i * 100 / smax)
end
end

function round(num, dp)
local mult = 10^(dp or 0)
return (math.floor(num * mult + 0.5)) / mult
end
Now the same code using threads. the main file is now broekn into 2 files: main.lua and backend.lua.
main calls backend as a thread and the backend keeps the main appraised of the current for loop progress via a channel.

main.lua

Code: Select all

debug = true
-- inspect = require "inspect"

local progress = 0
local msg_queue = {}
local scrWidth = love.graphics.getWidth()
local scrHeight = love.graphics.getHeight()

stime = love.timer.getTime()

ttime = (love.timer.getTime() - stime)
end

function love.draw(dt)

love.graphics.print("FPS: "..tostring(love.timer.getFPS( )), 10, 10)
love.graphics.print("Time taken : "..tostring(round(ttime, 1)) .. " seconds", 10, 30)
end

function love.update(dt)

progress = channel:pop() or progress

end

-- thread:getError() will return the same error string now.
end

function get_progress()
return msg_queue[#msg_queue] or "starting..."
end

function round(num, dp)
local mult = 10^(dp or 0)
return (math.floor(num * mult + 0.5)) / mult
end
and backend.lua

Code: Select all

local smin = 1
local smax = 1000

for i = smin, smax, 1 do
print(i .. " > ALL WORK AND NO PLAY MAKES JACK A DULL BOY")
channel:push((i * 100 / smax))
end

function round(num, dp)
local mult = 10^(dp or 0)
return (math.floor(num * mult + 0.5)) / mult
end
Finally, we run the same code using coroutines

Code: Select all

debug = true
local progress = 0

local scrWidth = love.graphics.getWidth()
local scrHeight = love.graphics.getHeight()

stime = love.timer.getTime()
co = coroutine.create(backend_logic)

ttime = (love.timer.getTime() - stime)
end

function love.draw(dt)

love.graphics.print("FPS: "..tostring(love.timer.getFPS( )), 10, 10)
love.graphics.print("Time taken : "..tostring(round(ttime, 1)) .. " seconds", 10, 30)
end

function love.update(dt)
_, progress = coroutine.resume(co)
end
end

function backend_logic()
local smin = 1
local smax = 5000
for i = smin, smax, 1 do
print(i .. " > ALL WORK AND NO PLAY MAKES JACK A DULL BOY")
local prg = round(i * 100 / smax)
coroutine.yield(prg)
end
ttime = (love.timer.getTime() - stime)
return 100
end

function round(num, dp)
local mult = 10^(dp or 0)
return (math.floor(num * mult + 0.5)) / mult
end
some basic takeaway:
threads is just as fast as normal blocking operation. However, it feels like threads are meant for long running operations like physics. creating a new thread for every short lived operation may not be ideal. i still need to research this more.

coroutines is definitely slower. the lag becomes more apparent when you increase the for loop range (for small ranges the lag is negligible).

Anyway, if i find any more ways of doing non blocking code (I might try async and some other parallel/concurrent programming libs next) i'll update my thread.

--------------------------------------------------------------

hi

I am trying to use threads but I fear I dont understand the implementation.
So, i created a simple example that you can help me with.

I have a simple function which takes a bit of time to finish. while it runs, the rendering is stopped. I instead want the rendering to go on and show me a progress indicator for it.

This is the code that i am using for it:

Code: Select all

debug = true
local progress = 0

local scrWidth = love.graphics.getWidth()
local scrHeight = love.graphics.getHeight()

stime = love.timer.getTime()
-- backend_logic()
ttime = (love.timer.getTime() - stime)
end

function love.draw(dt)

love.graphics.print("FPS: "..tostring(love.timer.getFPS( )), 10, 10)
love.graphics.print("Time taken : "..tostring(round(ttime, 1)) .. " seconds", 10, 30)
end

function love.update(dt)

end

function backend_logic()
local smin = 1
local smax = 10000
for i = smin, smax, 1 do
print("ALL WORK AND NO PLAY MAKES JACK A DULL BOY")
progress = round(i * 100 / smax)
end
end

function round(num, dp)
local mult = 10^(dp or 0)
return (math.floor(num * mult + 0.5)) / mult
end
How can i implement threads so that the backend_logic is run in its own thread without blocking the main thread?
the "progress" variable is a shared one. it is updated every tick in the child thread, but its value should be shown on the main thread.

EDIT:
this is my try.

Code: Select all

function love.load(arg)
stime = love.timer.getTime()

progress = channel:pop()
-- backend_logic()
ttime = (love.timer.getTime() - stime)
end

Code: Select all

function backend_logic()
local prg = 0
local smin = 1
local smax = 1000
for i = smin, smax, 1 do
print("ALL WORK AND NO PLAY MAKES JACK A DULL BOY")
prg = round(i * 100 / smax)
channel:push( prg )
end
end

It doesnt works. :/
I get "Error: main.lua:9: bad argument #1 to 'newThread' (Data expected, got no value)" error. Halp.
Last edited by Rishavs on Mon Sep 12, 2016 7:09 am, edited 1 time in total.

s-ol
Party member
Posts: 1077
Joined: Mon Sep 15, 2014 7:41 pm
Location: Cologne, Germany
Contact:

read this again. you need to give it a filename, and even if it took a function, it would need to be newThread( backend_logic ) not newThread( backend_logic() ) for the function to even 'reach' the newThread call.

also you will need to use a named channel to communicate to your thread unless you pass the unnamed channel to it via arguments in :start() perhaps.

s-ol.nu /blog  -  p.s-ol.be /st8.lua  -  g.s-ol.be /gtglg /curcur

Code: Select all

print( type(love) )
if false then
baby:hurt(me)
end

Rishavs
Party member
Posts: 103
Joined: Sat Oct 17, 2009 5:29 am
Contact:

Hmmm

I thought i could do it all in the main file only using

Can you check my try in the first post please?
If i reuse the same code but put in a different file, will it work?

Or do i remove the function call and have the raw code in the new file?

0x72
Citizen
Posts: 51
Joined: Thu Jun 18, 2015 9:02 am

Consider this code, I hope it'll make the thing clear:

Code: Select all

-- using codesting, but as you can see just a filename would be much cleaner
local backend_logic_str = [[

love.timer = require('love.timer') -- you won't have it out of box in new thread

channel = love.thread.getChannel( 'backend_logic_channel' ) -- named channel - same as in the main thread

function backend_logic()
local prg = 0
local smin = 1
local smax = 1000
for i = smin, smax, 1 do
print("ALL WORK AND NO PLAY MAKES JACK A DULL BOY")
channel:push( i )
love.timer.sleep(0.5) -- this thread locks but it won't lock the main thread
end
end

backend_logic() -- the function won't run itself without some help, could also be an anonymus, immediately
-- invoked function ( (function() --[[...]] end)() ), o no function at all (just naked
-- code) if you will
]]

end

function love.update(dt)
io.write('.') -- just some dots so we see when love.update() runs
io.flush()
while true do -- in a loop so we pop all of them in one update(), - just in case the other thread runs faster then the main loop
local msg = channel:pop()
if msg then -- the other thread have just pushed message, nice!
print("MSG: ", msg)
else -- channel queue's empty, we can move on
break
end
end
end

-- edit --
minor thing: you used round, while most likely you wanted math.floor

Rishavs
Party member
Posts: 103
Joined: Sat Oct 17, 2009 5:29 am
Contact:

thanks to you all, i got my code to work.

Still have 1 question though:
If I try to use a function call in my thread file, the function name isnt recognized.

Code: Select all

local smin = 1
local smax = 1000
for i = smin, smax, 1 do
print(i .. " > ALL WORK AND NO PLAY MAKES JACK A DULL BOY")
channel:push(round(i * 100 / smax))
end

function round(num, dp)
local mult = 10^(dp or 0)
return (math.floor(num * mult + 0.5)) / mult
end
gives me:
backend.lua:9: attempt to call global 'round' (a nil value)"

ps. adding my main and thread files for anyone coming in after me.
main.lua

Code: Select all

debug = true
local progress = 0
local msg_queue = {}
local scrWidth = love.graphics.getWidth()
local scrHeight = love.graphics.getHeight()

stime = love.timer.getTime()

-- backend_logic()
ttime = (love.timer.getTime() - stime)
end

function love.draw(dt)

love.graphics.print("FPS: "..tostring(love.timer.getFPS( )), 10, 10)
love.graphics.print("Time taken : "..tostring(round(ttime, 1)) .. " seconds", 10, 30)
end

function love.update(dt)

v = channel:pop()
if v then
table.insert(msg_queue, v)
end
-- print(msg_queue[10])
end

-- thread:getError() will return the same error string now.
end

function get_progress()
return msg_queue[#msg_queue] or "starting..."
end

function round(num, dp)
local mult = 10^(dp or 0)
return (math.floor(num * mult + 0.5)) / mult
end
and backend.lua

Code: Select all

local smin = 1
local smax = 1000

for i = smin, smax, 1 do
print(i .. " > ALL WORK AND NO PLAY MAKES JACK A DULL BOY")
channel:push(i * 100 / smax)
end

function round(num, dp)
local mult = 10^(dp or 0)
return (math.floor(num * mult + 0.5)) / mult
end

Nixola
Inner party member
Posts: 1940
Joined: Tue Dec 06, 2011 7:11 pm
Location: Italy

The code in the first

Code: Select all

 tag and the last one is different. In the first one, you're calling the round function which will only be defined after that for loop runs; so you'd need to define the function before the loop.
lf = love.filesystem
ls = love.sound
la = love.audio
lp = love.physics
li = love.image
lg = love.graphics

Rishavs
Party member
Posts: 103
Joined: Sat Oct 17, 2009 5:29 am
Contact:

Ok. so when i put the round function before the for loop in my thread file, it works.
But this is the first time i have seen anything like it. In my other scripts I can use function calls above the function definitions.
I think i am missing something very basic to lua.

KayleMaster
Party member
Posts: 212
Joined: Mon Aug 29, 2016 8:51 am

Lua threads are separate environments and so they cannot access the variables and functions of the main thread, and communication between threads is limited.
They are to be used for for loops, io loading/saving, multiplayer, physics etc.

Rishavs
Party member
Posts: 103
Joined: Sat Oct 17, 2009 5:29 am
Contact:

But I also put the function in the thread file as well.
the function is only recognized if i place it before the function call in the script.

If i put the function below the place where it is called, then i get an exception.
This means I will have to always put functions on top, and raw code below in a script if i were to use it in a thread.

sucks.

0x72
Citizen
Posts: 51
Joined: Thu Jun 18, 2015 9:02 am

My bet is you mean something like that:

Code: Select all

function love.load() -- update/draw/whatever
other_later_defined_function() -- valid call
end

--  other_later_defined_function() -- this would be an error

function other_later_defined_function() -- definition
print('foobar')
end

It might not be obvious at glance but here the valid call actually will happen after the definition.

Code: Select all

foo = 'foo'