Love CPU usage

General discussion about LÖVE, Lua, game development, puns, and unicorns.
MrFariator
Party member
Posts: 512
Joined: Wed Oct 05, 2016 11:53 am

Re: Love CPU usage

Post by MrFariator »

Just to humor you, I grabbed my old Macbook from 2009 (specs, running 10.11.6 (El Capitan)), and your attached .love only consumed 8% CPU at startup, and 5.6~5.8% CPU after a little while.

Maybe you have some power saving settings on, and GPU is not properly firing up? Dunno, at this point it might sound like your Macbook Pro specifically is at fault somehow.
User avatar
slime
Solid Snayke
Posts: 3132
Joined: Mon Aug 23, 2010 6:45 am
Location: Nova Scotia, Canada
Contact:

Re: Love CPU usage

Post by slime »

ChicoGameDev wrote: Sat Aug 29, 2020 7:29 pm I'm currently on a Macbook Pro and even an empty application with vsync off and just the sleep the CPU is up to ~103% here is the love file for testing purpose.
There was a bug in the version of OpenAL Soft shipped with the original love 11.3 download for macOS which caused increased CPU usage on a separate thread. A fixed version of love for macOS was reuploaded a while ago which doesn't have that issue - the fixed version is labelled "11.3b" in its version description. It's worth checking to make sure you're using that newer 11.3 version, I think.
User avatar
ChicoGameDev
Citizen
Posts: 70
Joined: Thu Feb 14, 2019 6:02 pm
Location: Switzerland
Contact:

Re: Love CPU usage

Post by ChicoGameDev »

Oh yeah

Thank you so much Slime you are amazing !

That was it I downloaded again and I'm now at 8% CPU usage. That's truly amazing ! Glad I asked :D

Thanks everybody !


Regards,
Lionel Leeser

Luven : https://github.com/chicogamedev/Luven

--

Always keep Game Dev as a passion.
User avatar
pgimeno
Party member
Posts: 3549
Joined: Sun Oct 18, 2015 2:58 pm

Re: Love CPU usage

Post by pgimeno »

As a side note, I prefer avoiding modification of love.run because it tends to change a lot between major versions, so when the version changes it can be a pain to update, and a major pain to keep the program working with both versions at a time.

But then a frame limiter should best be implemented at the end of love.draw, not on love.update.
User avatar
ChicoGameDev
Citizen
Posts: 70
Joined: Thu Feb 14, 2019 6:02 pm
Location: Switzerland
Contact:

Re: Love CPU usage

Post by ChicoGameDev »

That's true.

Originally it was in the love.draw but when I was reading it again for making this post I thought I've done something wrong :D.

Thanks for confirming and I've just put it back into the love.draw.

Regards
Lionel Leeser

Luven : https://github.com/chicogamedev/Luven

--

Always keep Game Dev as a passion.
RNavega
Party member
Posts: 249
Joined: Sun Aug 16, 2020 1:28 pm

Re: Love CPU usage

Post by RNavega »

If you can guarantee that you won't overwrite or nillify love.draw or love.update, then you don't need to keep checking their presence at every cycle. These table lookups should be pretty fast already, but if you want to be pedantic you can cache them.

This is a love.run() for v11.3:

Code: Select all

local FRAME_TIME = 1.0 / 30.0 -- 30.0 = desired FPS

-- Custom love.run with frame limiting.
function love.run()
    if not love.timer or not love.event or not love.graphics then
        error('Modules not enabled')
    end

    if love.load then love.load(love.arg.parseGameArguments(arg), arg) end

    -- Main loop.
    return function()
        local timerStep = love.timer.step
        local timerSleep = love.timer.sleep
        local eventPump = love.event.pump
        local eventPoll = love.event.poll
        local eventHandlers = love.handlers
        local graphicsActive = love.graphics.isActive
        local graphicsPresent = love.graphics.present
        local loveDraw = love.draw
        local loveUpdate = love.update
        local dt1 = 0.0
        local dt2 = 0.0
        local sleepTime = 0.0

        while true do
            eventPump()
            for name, a,b,c,d,e,f in eventPoll() do
                if name == "quit" then
                    if not love.quit or not love.quit() then
                        return a or 0
                    end
                end
                eventHandlers[name](a,b,c,d,e,f)
            end

            -- Call update and draw.
            dt1 = timerStep()
            loveUpdate(dt1+dt2)

            if graphicsActive() then
                loveDraw()
                graphicsPresent()
            end

            dt2 = timerStep()
            sleepTime = FRAME_TIME - (dt1+dt2)
            if sleepTime > 0.0 then
               timerSleep(sleepTime)
            end
        end
    end
end
That infinite 'while' loop is based on the fact that the boot code keeps running the function returned by love.run() until that function returns a non-nil value, as seen here: https://github.com/love2d/love/blob/mas ... #L801-L805
User avatar
pgimeno
Party member
Posts: 3549
Joined: Sun Oct 18, 2015 2:58 pm

Re: Love CPU usage

Post by pgimeno »

If you want your program to be compatible with love.js, the function returned by love.run should only process one frame and return, as the docs say.
RNavega
Party member
Posts: 249
Joined: Sun Aug 16, 2020 1:28 pm

Re: Love CPU usage

Post by RNavega »

Thats important to know, thank you for the pointer. I'm personally only interested in the desktop and Android platforms.
I searched and the discussion behind changing love.run() from a loop to a function is in here, pretty interesting:
- https://github.com/love2d/love/issues/1273
- https://github.com/love2d/love/commit/7 ... 7aa4b4fbf4

This wikipedia article says Android uses preemptive multitasking like the desktop platforms, so hopefully it should be ok with a loop-based love.run() main loop.
User avatar
pgimeno
Party member
Posts: 3549
Joined: Sun Oct 18, 2015 2:58 pm

Re: Love CPU usage

Post by pgimeno »

Apologies in advance for drifting the topic even further. I've been examining your main loop in more detail.
RNavega wrote: Tue Sep 01, 2020 6:21 am

Code: Select all

        while true do
            eventPump()
            for name, a,b,c,d,e,f in eventPoll() do
                if name == "quit" then
                    if not love.quit or not love.quit() then
                        return a or 0
                    end
                end
                eventHandlers[name](a,b,c,d,e,f)
            end

            -- Call update and draw.
            dt1 = timerStep()
            loveUpdate(dt1+dt2)

            if graphicsActive() then
                loveDraw()
                graphicsPresent()
            end

            dt2 = timerStep()
            sleepTime = FRAME_TIME - (dt1+dt2)
            if sleepTime > 0.0 then
               timerSleep(sleepTime)
            end
        end
    end
This is the order in which that code does things:

Code: Select all

|--------------------------------------------------------|
|          |        |         |         |        |
process    measure  process   process   display  sleep
event      time     update    draw      frame    (*)
callbacks           callback  callback

(*) sleeps {target frame time - (update + draw + display)}
That order does not make much sense to me. At the very least, the event callbacks should be included in the measurement; there's no reason to exclude them. Then, it only works with vsync disabled; therefore it can't be used as a frame rate limiter [edit: oops, I was wrong there, sorry]. The actual frame rate will also be variable, not fixed, as the time when the frame is displayed (graphicsPresent) depends on the duration of the update and draw callbacks, even though the average frame time will be correct (assuming the input events take negligible time, or that the first time measurement is taken before them).

The correct order in my opinion is this:

Code: Select all

|------------------------------------------------------|
|        |          |         |         |      |
measure  process    process   process   sleep  display
time     event      update    draw      (*)    frame
         callbacks  callback  callback

(*) sleeps {target frame time - (input + update + draw + display)}
The only problem is that it can possibly introduce a little more latency, but now it can be used both for frame rate limiting and to produce a truly fixed frame rate, depending on vsync.

Anyway, you get the same effect with the code that Chicco posted, assuming it's at the end of love.draw.
RNavega
Party member
Posts: 249
Joined: Sun Aug 16, 2020 1:28 pm

Re: Love CPU usage

Post by RNavega »

The goal is getting a main loop that's as generous as possible to the CPU so you have low usage/no busy-spinning, it should do a game tick and then sleep to give all the remaining time back to the OS.

The loop I posted I based on the first one in the deWiTTERS game loop article. To get my head around it I made this diagram:

Image

(With "m" standing for "measure time"/love.timer.step(), and "s?" standing for "sleep if you have time to spare")

One of the love.timer.step() calls (m1) needs to go right before the love.update() callback, because 'dt' must be guaranteed to be the time since the last call to it.
This loop has a vulnerability: if the system can't keep up with the desired framerate because the game is too heavy, then the game will slow down. It only caps/limits the framerate if it's too fast, it doesn't skip frames if it's falling behind.
However, that's okay to me, all of the LÖVE games I tested are very lightweight and don't go above 10% CPU use on my low-end machine.

My thinking for putting the sleep after love.graphics.present() is that, if you need to sleep at all, it should happen right after you've just swapped buffers and have new content on the screen, because present() might stall a bit if using VSync and you want love.timer.step() to include that time.

But after what you said, I looked in the LÖVE source and noticed that the vsync setting is sent to SDL as an integer (same meaning as this). If you want to cap the framerate at half the refresh rate (30 FPS on a 60hz monitor, or 72 FPS on a 144Hz monitor) you're allowed to put a...

Code: Select all

  t.window.vsync = 2
...in conf.lua. The CPU usage reduces a lot and the framerate it produces is more consistent than when manually sleeping like I was doing. The mainloop reduces to:

Code: Select all

    -- Main loop.
    return function()
        local timerStep = love.timer.step
        local timerSleep = love.timer.sleep
        local eventPump = love.event.pump
        local eventPoll = love.event.poll
        local eventHandlers = love.handlers
        local loveUpdate = love.update
        local loveDraw = love.draw
        local graphicsPresent = love.graphics.present
   
        timerStep()
        
        while true do            
            eventPump()
            for name, a,b,c,d,e,f in eventPoll() do
                if name == "quit" then
                    if not love.quit or not love.quit() then
                        return a or 0
                    end
                end
                eventHandlers[name](a,b,c,d,e,f)
            end

            -- Call update and draw.
            loveUpdate(timerStep())
            loveDraw()
            -- Testing love.graphics.isActive() is only needed on mobile platforms (iOS/Android)
            -- or if you call love.window.close() while the program is still running.
            graphicsPresent()
        end
    end
So VSync = 2 might be the default in your low-CPU game, and if the user wants a faster rate then they can lower that from 2 to 1 or to 0 on your options screen or whatever interface you have for that, with 2 being "FPS = half refresh rate", 1 being "FPS = refresh rate" and 0 being "unrestricted upates, no waiting" (love.graphics.present() returns immediately rather than waiting).
In all cases you rely on love.update's 'dt' to advance the speeds-per-second of your game.
Post Reply

Who is online

Users browsing this forum: Semrush [Bot] and 95 guests