Understanding love.run — Is there a annotated version?

General discussion about LÖVE, Lua, game development, puns, and unicorns.
Post Reply
sdeban
Prole
Posts: 7
Joined: Sun Feb 05, 2017 5:13 pm

Understanding love.run — Is there a annotated version?

Post by sdeban » Wed Feb 22, 2017 3:05 pm

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.

User avatar
Positive07
Party member
Posts: 1005
Joined: Sun Aug 12, 2012 4:34 pm
Location: Argentina

Re: Understanding love.run — Is there a annotated version?

Post by Positive07 » Wed Feb 22, 2017 4:19 pm

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/Positive07)

sdeban
Prole
Posts: 7
Joined: Sun Feb 05, 2017 5:13 pm

Re: Understanding love.run — Is there a annotated version?

Post by sdeban » Thu Feb 23, 2017 2:16 am

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.

User avatar
Positive07
Party member
Posts: 1005
Joined: Sun Aug 12, 2012 4:34 pm
Location: Argentina

Re: Understanding love.run — Is there a annotated version?

Post by Positive07 » Thu Feb 23, 2017 3:54 am

Notes:
  • 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
Functions:
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...
love.run:
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 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
for i, person in ipairs(everybody) do
[tab]if not person.obey then person:setObey(true) end
end
love.system.openURL(Github.com/Positive07)

User avatar
zorg
Party member
Posts: 2555
Joined: Thu Dec 13, 2012 2:55 pm
Location: Absurdistan, Hungary
Contact:

Re: Understanding love.run — Is there a annotated version?

Post by zorg » Thu Feb 23, 2017 6:49 am

To add to the above:
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.
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)

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
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)
Last edited by zorg on Fri Apr 07, 2017 7:21 am, edited 1 time in total.
Me and my stuff :3True 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.

sdeban
Prole
Posts: 7
Joined: Sun Feb 05, 2017 5:13 pm

Re: Understanding love.run — Is there a annotated version?

Post by sdeban » Thu Feb 23, 2017 2:32 pm

Wonderful! Thank you both for taking the time to write out these explanations. I learned a lot.

User avatar
Positive07
Party member
Posts: 1005
Joined: Sun Aug 12, 2012 4:34 pm
Location: Argentina

Re: Understanding love.run — Is there a annotated version?

Post by Positive07 » Thu Feb 23, 2017 5:07 pm

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)
for i, person in ipairs(everybody) do
[tab]if not person.obey then person:setObey(true) end
end
love.system.openURL(Github.com/Positive07)

User avatar
zorg
Party member
Posts: 2555
Joined: Thu Dec 13, 2012 2:55 pm
Location: Absurdistan, Hungary
Contact:

Re: Understanding love.run — Is there a annotated version?

Post by zorg » Thu Feb 23, 2017 9:06 pm

Positive07 wrote:
Thu Feb 23, 2017 5:07 pm
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)
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. :P
Would be good to know what influences that though.
Me and my stuff :3True 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.

Post Reply

Who is online

Users browsing this forum: Exabot [Bot] and 167 guests