pygame eating up my cpu!

Started by
8 comments, last by Oluseyi 15 years, 3 months ago
Here is what i would think would be the almost canonical pygame game loop:

import pygame
from pygame.locals import *

pygame.init()
screen = pygame.display.set_mode((400, 480))
clock = pygame.time.Clock()

while True:
    time = clock.tick(60)
    pygame.event.pump()
    p = pygame.key.get_pressed()
    if p[K_ESCAPE]:
        sys.exit(0)
    paint_area = pygame.Rect(0, 0, 100, 100)
    screen.fill((255, 0, 0), paint_area)
    pygame.display.update(paint_area)    



Unfortunately this guy eats about 99% of my cpu at all times. If I can't fix the loop is there any way to program 2d games in python with good cpu efficiency? [Edited by - caleb_yau on December 20, 2008 12:09:13 PM]
Advertisement
It could be that your computer has poor timer, and your requested resolution is higher than that.

For example, worst-case could be 18.2Hz, but you're requesting 60Hz. This could (although it shouldn't) lead to timer waiting for 0 ms to avoid overshooting by too much.

Another possibility is that your rendering is taking long due to lack of proper hardware optimization.

The tick() function actually operates on old and current time. If the body if your loop takes longer than 16.7ms, and if it accounts for the declared 10ms timer granularity of SDL_Delay, then it will only allow for 6.7ms for body of the loop, anything larger would result in no wait time, since tick() would determine it's late already.

Reduce the frame rate to 30 or even 15 and see if the problem persists. If that fixes the problem, then the SDL_Delay which is used internally by pygame isn't usable for this purpose.

Also, print out the result of tick() function. Documentation isn't clear, but it may return the time it actually waited.
I believe SDL_Delay works by maxing out your CPU.

I know this because I was attempting to take a screen shot of an SDL application I had compiled from the sources and the screen shot was always of AFTER the app had ended. It was a simple draw one thing to the screen, delay for fives seconds, and close.

What I believe happened is my computer could not even register the print screen command while SDL_Delay was running.

If there's any kind of system sleep function, see if that does a better job.

This would be an example of where portability decides to bite you in the ass.

EDIT:
Take this with a grain of salt. I wasn't too scientific about the whole thing and it's been a while. Still, might want to check it out.
Looking at the code I think it made have something to do with the line:
paint_area = pygame.Rect(0, 0, 100, 100)

your creating a new rect for each frame and since your frame rate is 60. your creating 60 rects a second. It may not be the main issue, but you may want to try creating the rect outside of the loop and see how that effects cpu use.
Fixed! Splinter of Chaos' advice worked. Well first getticks did print its wait time, which was a steady 16 ms. I ended up using a combination of the time.sleep function from the time module and python.time.get_ticks(). So it ends up being a pretty os ambigous game still. Now python hardly shows up on top! The tick() solution is in like every pygame tutorial and many games on the pygame website suffer poorly probably because of it. I feel like I discovered some ultimate secret to python game programming!

import pygameimport timefrom pygame.locals import *pygame.init()screen = pygame.display.set_mode((400, 480))clock = pygame.time.Clock()old_time = pygame.time.get_ticks()while True:    new_time = pygame.time.get_ticks()    waited = new_time - old_time    old_time = new_time    if waited < 60:      time.sleep(1.0 / (60 - waited))    print 'waited: ', waited    pygame.event.pump()    p = pygame.key.get_pressed()    if p[K_ESCAPE]:        sys.exit(0)    paint_area = pygame.Rect(0, 0, 100, 100)    screen.fill((255, 0, 0), paint_area)    pygame.display.update(paint_area) 


Thanks alot!
Quote:Original post by caleb_yau
Here is what i would think would be the almost canonical pygame game loop...

Nope. I'm not a fan of artificially slowing down your application, though you can limit the number of display refreshes.

from pygame import *running = Truefps = 60DISPLAY_REFRESH = USEREVENTtime.set_timer(DISPLAY_REFRESH, int(1000.0/fps))while running:    for evt in event.get():        if evt.type == KEYDOWN and evt.key == ESCAPE:            running = False        elif evt.type == DISPLAY_REFRESH:            paint_area = pygame.Rect(0, 0, 100, 100)            screen.fill((255, 0, 0), paint_area)            pygame.display.update(paint_area)    # do your non-rendering game loop computation here    # to reduce CPU usage, call this guy:    time.wait(0)


Quote:Unfortunately this guy eats about 99% of my cpu at all times.

That doesn't necessarily mean anything. It's running flat out, so it's using its full allocated CPU timeslice. Giving up any remainder of the timeslice is the key to lowering CPU usage, which is done by sleeping - or, in the case of PyGame, calling time.wait(). We pass zero because the time specified is actually a lower bound - your process will sleep at least that many milliseconds. calling Sleep(0) (underlying system API) just releases the rest of your current timeslice.
Whoa much better and cleaner Oluseyi. I like wait a lot already! Unfortunately that means that sleeping is everybody's secret ... :)
Quote:Original post by caleb_yau
Whoa much better and cleaner Oluseyi. I like wait a lot already! Unfortunately that means that sleeping is everybody's secret ... :)

Yes. I also make heavy use of custom events in my PyGame programming, instead of manually juggling time intervals. Another advantage of this approach is that you can enter a function that takes over the event loop, allowing for different behavior - ESCAPE might exit that function/mode rather than the application. (Generally, avoid calling sys.exit(), as it really just raises an exception, SystemExit, that can spill onto the command line.)

Here's an example of the modal nested event loops approach. main() calls menu(), which establishes an event loop and then either exits or calls puzzle() depending on user input. puzzle() sets up its own event loop - note that they all use the same event queue, but take over polling it so that the effect of the same event can vary with game mode. This is easier than trying to shoehorn all that logic into a single loop and select based on game state. In effect, each of the functions menu(), puzzle(), etc is the game state.

def PostQuitEvent():    pygame.event.post(pygame.event.Event(pygame.QUIT))def main():    screen = pygame.display.set_mode((800, 450))    menu(screen)def menu(screen):    ...    running = True    selected = 0    while running:        for evt in pygame.event.get():            if evt.type == pygame.QUIT:                running = False            elif evt.type == pygame.KEYDOWN:                if evt.key == pygame.K_ESCAPE:                    PostQuitEvent()                elif evt.key == pygame.K_RETURN:                    if selected == (NUM_MENU_OPTIONS - 1):                        PostQuitEvent()                    else:                        puzzle(screen, 0)                elif evt.key == pygame.K_UP or evt.key == pygame.K_DOWN:                    selected += 1                    select %= NUM_MENU_OPTIONS        ...def puzzle(screen, p):    ...    running = True    while running:        for evt in pygame.event.get():            if evt.type == pygame.QUIT:                running = False            elif evt.type == pygame.KEYDOWN:                if evt.key == pygame.K_ESCAPE:                    PostQuitEvent()        ...if __name__ == '__main__':    main()

This is definetly going to clean up my game loop a lot, and probably speed it up a little too. What's this supposed to do though?:


if __name__ == '__main__':
main()

if im not mistaken __name__ will be the name of the module, and only will only be '__main__' when its run interactively. Won't this almost never run in that case?
Quote:Original post by caleb_yau
What's this supposed to do though?:
if __name__ == '__main__':    main()

if im not mistaken __name__ will be the name of the module, and only will only be '__main__' when its run interactively. Won't this almost never run in that case?

Not when it's run interactively. When the module is the executing module, as opposed to be loaded via import. (You can't run a file interactively, after all.) This little test is very useful: it allows you to implement unit tests for a module designed for import, for example, or to import a module written as an application and take advantage of its types in other code.

This topic is closed to new replies.

Advertisement