Understanding love.run — Is there a annotated version?
Understanding love.run — Is there a annotated version?
I have added the love.run function to the top of my main.lua file so that I can deepen my understanding of love2d and possibly alter the standard game loop, but I don't understand what some parts of love.run are doing. What I am hoping to find is a line-by-line dissection of love.run to understand how it works (or a commented version). I would appreciate if someone could point me to something like that, if it exists. I have tried searching for it to no avail.
- Positive07
- Party member
- Posts: 1014
- Joined: Sun Aug 12, 2012 4:34 pm
- Location: Argentina
Re: Understanding love.run — Is there a annotated version?
Like the one in the wiki love.run or the source code? It doesn't do much, and it's readable so it should be enough
for i, person in ipairs(everybody) do
[tab]if not person.obey then person:setObey(true) end
end
love.system.openURL(github.com/pablomayobre)
[tab]if not person.obey then person:setObey(true) end
end
love.system.openURL(github.com/pablomayobre)
Re: Understanding love.run — Is there a annotated version?
Yes, the wiki. I can read the words but I was hoping for an explanation of what it's doing and the underlying reason in the context of the way game loops run.
- Positive07
- Party member
- Posts: 1014
- Joined: Sun Aug 12, 2012 4:34 pm
- Location: Argentina
Re: Understanding love.run — Is there a annotated version?
Notes:
Well love.run is the last step of the main function. Basically there are three functions:
So first it sets the random seed for love.math.random (love.math.randomSeed) so that you always get different values every time you run LÖVE (If you passed the same value every time you started LÖVE love.math.random would generate the very same numbers across sessions).
Then it calls love.load (if it exists) which you probably defined in your main.lua file (which was previously required in love.init) and passes the global arg table where the command line arguments used to execute the game can be found. If I executed the game from the command line with the command "love game.love --fused "argument whatever" the table would look something like:
Then it sets dt to 0 which will later be changed and passed to update.
Then it has got an infinite loop, this is because you need to keep on executing all the time until you quit the game.
The first step of the loop is to pump the event queue (love.event.pump), this is a stack with all the events that happened since the last frame. You then iterate through the stack using the love.event.poll iterator, which returns an event and 6 arguments (for example "mousepressed", 63, 61, 2, false would mean that there was a mousepressed event at x=63 and y=61, with the right button and it wasn't a touch)
If the event is registered in the love.handlers table then it calls the love.handlers[event] function with the 6 arguments. This love.handlers functions are just function that then delegate to the more known callbacks. So for example there is love.handlers.mousepressed which checks if love.mousepressed exists and calls it passing all the arguments it received, but if love.mousepressed doesn't exist then the event is ignored
Also if the event was a quit event it directly checks if there is a love.quit callback and if that function returns a falsy value (false or nil) then quits the game by returning from the love.run function which breaks the loop and terminates the game.
Then the timer is stepped with love.timer.step and dt is changed to love.timer.getDelta which is then passed to love.update if love.update exists (otherwise we don't call it because that would throw an error)
Then we check that graphics are active (love.graphics.isActive) so we can know if we can draw to the screen or not, and if we can then we clear the window (love.graphics.clear) with the current background color (love.graphics.getBackgroundColor), reset all the transformations we made in past frames with love.graphics.translate, love.graphics.rotate and love.graphics.scale, this is done with love.graphics.origin.
Then if love.draw exists we call it, and after that we call love.graphics.present which actually displays everything we drew in the screen.
After that there is a sleep (love.timer.sleep), this is done so that LÖVE doesn't consume a lot of CPU, basically the sleep tells the operating system to do it's stuff since we have already completed our stuff for now, when the sleep ends we request the CPU to continue executing the game, this repeats every frame.
The sleep is not that big, but it's enough to lower the load LÖVE has on the CPU.
Then the loop runs again... and so on until the love.event.quit is fired
Source code:
love.handlers
main function
love.boot
love.init
love.run
- Reaaaaally long explanation
- Read it with love.run by it's side so you can follow up with the code
- If needed be you can check the sources, I didn't put the links directly on the text but they are at the end
Well love.run is the last step of the main function. Basically there are three functions:
- love.boot: Initializes the filesystem, mounts the .love file, checks if conf.lua or main.lua exists and if they don't shows the no game screen.
- love.init: Requires conf.lua, executes love.conf, sets up the window, the event handlers, the modules, and the console, then requires main.lua
- love.run: The one we are interested in...
So first it sets the random seed for love.math.random (love.math.randomSeed) so that you always get different values every time you run LÖVE (If you passed the same value every time you started LÖVE love.math.random would generate the very same numbers across sessions).
Then it calls love.load (if it exists) which you probably defined in your main.lua file (which was previously required in love.init) and passes the global arg table where the command line arguments used to execute the game can be found. If I executed the game from the command line with the command "love game.love --fused "argument whatever" the table would look something like:
Code: Select all
{
[0] = love
[1] = game.love
[2] = --fused
[3] = argument whatever
}
Then it has got an infinite loop, this is because you need to keep on executing all the time until you quit the game.
The first step of the loop is to pump the event queue (love.event.pump), this is a stack with all the events that happened since the last frame. You then iterate through the stack using the love.event.poll iterator, which returns an event and 6 arguments (for example "mousepressed", 63, 61, 2, false would mean that there was a mousepressed event at x=63 and y=61, with the right button and it wasn't a touch)
If the event is registered in the love.handlers table then it calls the love.handlers[event] function with the 6 arguments. This love.handlers functions are just function that then delegate to the more known callbacks. So for example there is love.handlers.mousepressed which checks if love.mousepressed exists and calls it passing all the arguments it received, but if love.mousepressed doesn't exist then the event is ignored
Also if the event was a quit event it directly checks if there is a love.quit callback and if that function returns a falsy value (false or nil) then quits the game by returning from the love.run function which breaks the loop and terminates the game.
Then the timer is stepped with love.timer.step and dt is changed to love.timer.getDelta which is then passed to love.update if love.update exists (otherwise we don't call it because that would throw an error)
Then we check that graphics are active (love.graphics.isActive) so we can know if we can draw to the screen or not, and if we can then we clear the window (love.graphics.clear) with the current background color (love.graphics.getBackgroundColor), reset all the transformations we made in past frames with love.graphics.translate, love.graphics.rotate and love.graphics.scale, this is done with love.graphics.origin.
Then if love.draw exists we call it, and after that we call love.graphics.present which actually displays everything we drew in the screen.
After that there is a sleep (love.timer.sleep), this is done so that LÖVE doesn't consume a lot of CPU, basically the sleep tells the operating system to do it's stuff since we have already completed our stuff for now, when the sleep ends we request the CPU to continue executing the game, this repeats every frame.
The sleep is not that big, but it's enough to lower the load LÖVE has on the CPU.
Then the loop runs again... and so on until the love.event.quit is fired
Source code:
love.handlers
main function
love.boot
love.init
love.run
for i, person in ipairs(everybody) do
[tab]if not person.obey then person:setObey(true) end
end
love.system.openURL(github.com/pablomayobre)
[tab]if not person.obey then person:setObey(true) end
end
love.system.openURL(github.com/pablomayobre)
- zorg
- Party member
- Posts: 3465
- Joined: Thu Dec 13, 2012 2:55 pm
- Location: Absurdistan, Hungary
- Contact:
Re: Understanding love.run — Is there a annotated version?
To add to the above:
The arg table:
One other thing is that the event polling doesn't guarantee the order of events that were recorded since the last frame; this is usually not a big deal, since users usually aren't faster than the game loop, but just consider the fact that if they were, the order of a keypressed and keyreleased event will be arbitrary, which may screw things up, depending on your code.
EDIT: I still don't know why i wrote this before, me just testing this now showed it to be completely consistent.
Also, if you really want to experiment, modifying the love.timer.sleep call is a nice place to start; i have found that for my system, a slightly different value gives better performance with basically no side-effects.
Finally, just to show you what's possible, here's my own love.run that i use in my own projects, usually:
(Not saying it's flawless though)
My code practically has 3 rates it runs at (as opposed to the default love.run, which only has one);
the tick rate for updating,
the frame rate for drawing, and
the atomic rate, which i usually only use for audio (unless i offload that to a dedicated thread completely... though input could also be something i could do there)
Also also, with the above, i need to have vsync off, because that would kill the atomic rate's granularity. (meaning it would be slow)
The arg table:
Code: Select all
-2 : <path to the löve executable>
-1 : the string "embedded boot.lua" (I don't know whether this can be anything else...)
0 : nil
1 : <path to the folder/.love file main.lua is in>
2 and up : additional arguments, including --fused.
EDIT: I still don't know why i wrote this before, me just testing this now showed it to be completely consistent.
Also, if you really want to experiment, modifying the love.timer.sleep call is a nice place to start; i have found that for my system, a slightly different value gives better performance with basically no side-effects.
Finally, just to show you what's possible, here's my own love.run that i use in my own projects, usually:
(Not saying it's flawless though)
Code: Select all
love.run = function()
-- Should be self-explanatory.
if love.math then
love.math.setRandomSeed(os.time())
end
-- Should be self-explanatory.
if love.load then love.load(arg) end
-- We don't want the first frame's dt to include time taken by love.load.
if love.timer then love.timer.step() end
local dt = 0.0 -- delta time
local tr = 1/100 -- tick rate
local fr = 1/75 -- frame rate
local da = 0.0 -- draw accumulator
local ua = 0.0 -- update accumulator
-- Main loop time.
while true do
-- Process events.
if love.event then
love.event.pump()
for name, a,b,c,d,e,f in love.event.poll() do
if name == "quit" then
if not love.quit or not love.quit() then
return a
end
end
love.handlers[name](a,b,c,d,e,f)
end
end
-- Update dt, as we'll be passing it to update
if love.timer then
love.timer.step()
dt = love.timer.getDelta()
da = da + dt
ua = ua + dt
end
-- Call audio
if love.atomic then love.atomic(dt) end
-- Call update
if ua > tr then
if love.update then
love.update(tr) -- will pass 0 if love.timer is disabled
end
ua = ua % tr
end
-- Call draw
if da > fr then
if love.graphics and love.graphics.isActive() then
love.graphics.clear(love.graphics.getBackgroundColor())
love.graphics.origin()
if love.draw then love.draw() end -- no interpolation
love.graphics.present()
end
da = da % fr
end
-- Optimal sleep time, anything higher does not go below 0.40 % cpu
-- utilization; 0.001 results in 0.72 %, so this is an improvement.
if love.timer then love.timer.sleep(0.002) end
end
end
the tick rate for updating,
the frame rate for drawing, and
the atomic rate, which i usually only use for audio (unless i offload that to a dedicated thread completely... though input could also be something i could do there)
Also also, with the above, i need to have vsync off, because that would kill the atomic rate's granularity. (meaning it would be slow)
Last edited by zorg on Fri Apr 07, 2017 7:21 am, edited 1 time in total.
Me and my stuff True Neutral Aspirant. Why, yes, i do indeed enjoy sarcastically correcting others when they make the most blatant of spelling mistakes. No bullying or trolling the innocent tho.
Re: Understanding love.run — Is there a annotated version?
Wonderful! Thank you both for taking the time to write out these explanations. I learned a lot.
- Positive07
- Party member
- Posts: 1014
- Joined: Sun Aug 12, 2012 4:34 pm
- Location: Argentina
Re: Understanding love.run — Is there a annotated version?
Interesting love.run zorg! I would really like to implement a threaded-static-frame-rate one I had the idea once and should be easy to work with but is a pain to implement and getting it right hahaha
About the nil you put in the arg table [0] index, I sometimes get love.exe there, others I get nil (when actually fusing the game)
About the nil you put in the arg table [0] index, I sometimes get love.exe there, others I get nil (when actually fusing the game)
for i, person in ipairs(everybody) do
[tab]if not person.obey then person:setObey(true) end
end
love.system.openURL(github.com/pablomayobre)
[tab]if not person.obey then person:setObey(true) end
end
love.system.openURL(github.com/pablomayobre)
- zorg
- Party member
- Posts: 3465
- Joined: Thu Dec 13, 2012 2:55 pm
- Location: Absurdistan, Hungary
- Contact:
Re: Understanding love.run — Is there a annotated version?
Yeah, as your own post stated, it may be like that when fused; i tried to use --fused when executing from cmd, but the order stayed the same for me; maybe i did something wrong.Positive07 wrote: ↑Thu Feb 23, 2017 5:07 pmAbout the nil you put in the arg table [0] index, I sometimes get love.exe there, others I get nil (when actually fusing the game)
Would be good to know what influences that though.
Me and my stuff True Neutral Aspirant. Why, yes, i do indeed enjoy sarcastically correcting others when they make the most blatant of spelling mistakes. No bullying or trolling the innocent tho.
Who is online
Users browsing this forum: Ahrefs [Bot] and 5 guests