PyGTK/Smooth Animation with PyGTK: Difference between revisions

From OLPC
Jump to navigation Jump to search
No edit summary
 
(10 intermediate revisions by 5 users not shown)
Line 1: Line 1:
{{Developers}}
{{Translations}}
This page describes different ways to get smooth animation in PyGTK, with various pros and cons.
This page describes different ways to get smooth animation in PyGTK, with various pros and cons.


Line 9: Line 11:
== PyGTK Events ==
== PyGTK Events ==


The PyGTK event loop is the means by which PyGTK events like mouse clicks, key presses and notifications are dispatched.
The PyGTK event queue is the means by which PyGTK events like mouse clicks, key presses and notifications are dispatched. The event queue is a list of events that need to be executed. Events can be generated by user input, or by other events such as an expose event generated by a window being resized.


The general process when a PyGTK application starts up is:
The typical PyGTK application sequence looks like this:


* Create one or more GTK windows / widgets.
* Create GTK windows and widgets.
* Connect events to functions.
* Connect events to functions.
* Call gtk.main().
* Call gtk.main().


The gtk.main() function simply processes the event loop, dispatching events to the appropriate handlers, until the quit signal is received at which point it returns and the application usually exits. Note that in Sugar Activities, gtk.main() is called by the sugar-activity launcher script.
The gtk.main() function simply processes the event queue, dispatching events to the connected functions, until the gtk.main_quit() is called, at which point it returns and the application usually exits. Note that in Sugar Activities, gtk.main() is called for you by the sugar-activity launcher script.


This system is good for GUI applications where you typically want the program to be idle until some external circumstance such as user input occurs. But for realtime applications, you generally want some piece of code executing all the time. Fortunately, there are several ways to achieve background processing in PyGTK.
This event-driven system is good for GUI applications which typically want to be idle until some external event (such as user input) occurs. But for realtime applications, you generally want some piece of code executing all the time. Fortunately, there are several ways to achieve this kind of regular processing in PyGTK.


== Timer Events ==
== Timer Events ==
Line 25: Line 27:
Using gobject.timeout_add(ms, fn) you can cause PyGTK to call a specific function every N milliseconds. This is most people's first approach to smooth animation.
Using gobject.timeout_add(ms, fn) you can cause PyGTK to call a specific function every N milliseconds. This is most people's first approach to smooth animation.


One problem with timer events is that the timer only starts counting again *after* the function returns. So, if your function takes 20ms to execute, it will be called 70ms after it it was first called. Therefore, the timing becomes inconsistent unless your function always takes exactly the same amount of time (and the cost of your function must be accounted for when choosing the timeout).
One problem with timer events is that the timer only starts counting again *after* the function returns. So, if the timer is set to 30ms and your function takes 20ms to execute, it will be called 50ms after it was first called. Therefore, the timing becomes inconsistent unless your function always takes exactly the same amount of time (and the cost of your function must be accounted for when choosing the timeout).


A second problem is that if the millisecond count not enough for all other events to be processed between timer events, you can starve the event queue in such a way that other events will never happen. This can cause the program to stutter, the GUI to fail to update, and numerous other problems.
A second problem is that if the millisecond count not enough for all other events to be processed between timer events, you can starve the event queue in such a way that other events will never happen. This can cause the program to stutter, the GUI to fail to update, and numerous other problems.
Line 41: Line 43:
# Generate an expose event.
# Generate an expose event.
self.queue_draw()
self.queue_draw()
return True


== Idle Events ==
== Idle Events ==
Line 61: Line 64:
# Generate an expose event.
# Generate an expose event.
self.queue_draw()
self.queue_draw()
return True


== Threads ==
== Threads ==
Line 66: Line 70:
Yet another option for animation is to create a secondary thread which repeatedly calls queue_draw or some other function to update graphics.
Yet another option for animation is to create a secondary thread which repeatedly calls queue_draw or some other function to update graphics.


This works fairly well, but in my experience the event queue tends to fill up leading to jerky animation and an unresponsive application. Since the XO only has a single processor, multithreading adds additional overhead to the system, and you will have to deal with extra GTK and Python synchronization overhead as well.
This is how PyGame works, but in my experience the event queue tends to fill up leading to jerky animation and an unresponsive application.

Since the XO only has a single processor, multithreading adds additional overhead to the system, and you will have to deal with extra GTK and Python synchronization overhead as well.


==== Example ====
==== Example ====
Line 75: Line 81:
class MyActivity(Activity)
class MyActivity(Activity)
def __init__(self):
def __init__(self):
thr = threading.Thread(function=self.mainloop)
thr = threading.Thread(target=self.mainloop)
thr.start()
thr.start()
Line 117: Line 123:


For an example of a game written entirely in PyGTK using these techniques in practice, see the [[ThreeDPong]] activity.
For an example of a game written entirely in PyGTK using these techniques in practice, see the [[ThreeDPong]] activity.

[[Category:Developers]]

Latest revision as of 22:21, 5 July 2011

  english | español HowTo [ID# 257709]  +/-  

This page describes different ways to get smooth animation in PyGTK, with various pros and cons.

Battery life

It's important to understand that adding constant animation (say an animating background) will prevent the laptop from entering suspend mode and will drastically reduce battery life, making your application less popular in the real world.

Therefore, you should *only* use animation in activities that absolutely require it, and for the shortest amount of time possible.

PyGTK Events

The PyGTK event queue is the means by which PyGTK events like mouse clicks, key presses and notifications are dispatched. The event queue is a list of events that need to be executed. Events can be generated by user input, or by other events such as an expose event generated by a window being resized.

The typical PyGTK application sequence looks like this:

  • Create GTK windows and widgets.
  • Connect events to functions.
  • Call gtk.main().

The gtk.main() function simply processes the event queue, dispatching events to the connected functions, until the gtk.main_quit() is called, at which point it returns and the application usually exits. Note that in Sugar Activities, gtk.main() is called for you by the sugar-activity launcher script.

This event-driven system is good for GUI applications which typically want to be idle until some external event (such as user input) occurs. But for realtime applications, you generally want some piece of code executing all the time. Fortunately, there are several ways to achieve this kind of regular processing in PyGTK.

Timer Events

Using gobject.timeout_add(ms, fn) you can cause PyGTK to call a specific function every N milliseconds. This is most people's first approach to smooth animation.

One problem with timer events is that the timer only starts counting again *after* the function returns. So, if the timer is set to 30ms and your function takes 20ms to execute, it will be called 50ms after it was first called. Therefore, the timing becomes inconsistent unless your function always takes exactly the same amount of time (and the cost of your function must be accounted for when choosing the timeout).

A second problem is that if the millisecond count not enough for all other events to be processed between timer events, you can starve the event queue in such a way that other events will never happen. This can cause the program to stutter, the GUI to fail to update, and numerous other problems.

Note that when the animation has finished, you can stop the timer event by returning False from your handler function.

Example

 class MyActivity(Activity)
   def __init__(self):
     # Add a 20fps (50ms) animation timer.
     gobject.timeout_add(50, self.timer_cb)
 
   def timer_cb(self):
     # Generate an expose event.
     self.queue_draw()
     return True

Idle Events

Using gobject.idle_add(fn) you can set up a function to be called whenever PyGTK is idle, that is when there are no other events to process.

This can be an improvement over timer events since it ensures that the event queue will not be starved, and other events like input and GUI updates will execute before your function is called.

The downside is that other events (like mouse movement) take priority over your idle event, or vice versa, which leads to animation stuttering when lots of external events happen, or else delays in processing external events while animation is running.

Like timer events, you can stop the idle event from being generated by returning False from your handler function.

Example

 class MyActivity(Activity)
   def __init__(self):
     gobject.idle_add(50, self.idle_cb)
 
   def idle_cb(self):
     # Generate an expose event.
     self.queue_draw()
     return True

Threads

Yet another option for animation is to create a secondary thread which repeatedly calls queue_draw or some other function to update graphics.

This is how PyGame works, but in my experience the event queue tends to fill up leading to jerky animation and an unresponsive application.

Since the XO only has a single processor, multithreading adds additional overhead to the system, and you will have to deal with extra GTK and Python synchronization overhead as well.

Example

 # Do not forget this!  Without it, you will get random crashes.  It must be called before gtk.main().
 gtk.gdk.threads_init()
 
 class MyActivity(Activity)
   def __init__(self):
     thr = threading.Thread(target=self.mainloop)
     thr.start()
 
   def mainloop(self):
     while True:
       # Generate an expose event.
       self.queue_draw()
       # Allow the main thread to process for a while.
       time.sleep(10)

Taking over the Event loop

The last method, and the only one I have found that is reliable enough for games, is to take over the PyGTK event loop. This is what SDL does internally, and is the pattern by which most Windows games operate as well.

The gtk.main() function supports being called recursively, and it has a gtk.main_iteration() function which processes a single event and returns. The final piece of the puzzle is gtk.events_pending(), which returns True if any events are pending.

This method is the most reliable, since you know that you will get all the available time, but also that all GTK events will be processed as soon as possible.

Example

 class MyActivity(Activity)
   def __init__(self):
     # Start up the main loop just after application initialization.
     self.timeout_add(20, self.mainloop)
 
   def mainloop(self):
     while True:
       # Process all pending events.
       while gtk.events_pending():
         gtk.main_iteration(False)
       # Generate an expose event (could just draw here as well).
       self.queue_draw()

Expose events versus Immediate drawing

In the above examples, we have called self.queue_draw() to cause an event to be generated which will update the entire screen. In a real activity, you would just want to refresh whatever widget is animating.

However, another option is to just execute drawing commands (using cairo, gtk.Drawable, etc) in your main loop. There is no penalty to doing this, so you might want to consider it in game-like applications where rendering logic is mixed with update code.

Real world example

For an example of a game written entirely in PyGTK using these techniques in practice, see the ThreeDPong activity.