wxPython fast and slow

I

iu2

Hi guys,

Can you please help me with this:
I wrote a little game in wxPython where I move small panels (showing
bitmaps (100 x 100)) on the screen.

This is similar to the code I wrote:

for i in xrange(1, steps + 1):
my_panel.SetPosition(...)
time.sleep(0.1 / steps)

I use PyScripter (Windows) for writing the application. While
PyScripter is active, the panels move quickly. But when I exit
PyScripter and execute the script from the explorer or the command
line, the panels move very slowly.

Do you have any idea of what is going wrong?
Thank you very much

iu2
 
M

Mike Driscoll

Hi guys,

Can you please help me with this:
I wrote a little game in wxPython where I move small panels (showing
bitmaps (100 x 100)) on the screen.

This is similar to the code I wrote:

for i in xrange(1, steps + 1):
    my_panel.SetPosition(...)
    time.sleep(0.1 / steps)

I use PyScripter (Windows) for writing the application. While
PyScripter is active, the panels move quickly. But when I exit
PyScripter and execute the script from the explorer or the command
line, the panels move very slowly.

Do you have any idea of what is going wrong?
Thank you very much

iu2

Can you post a sample application so we can try to figure out what's
wrong? You might also cross-post this to the wxPython mailing list.
They might know.

Mike
 
L

Lie Ryan

iu2 said:
Do you have any idea of what is going wrong?


I think this might be related to the OS's process prioritization,
focused Windows would get more priority than background window.
 
I

iu2

Can you post a sample application so we can try to figure out what's
wrong? You might also cross-post this to thewxPythonmailing list.
They might know.

Mike- Hide quoted text -

- Show quoted text -

Hi, thanks for your reply

Here is a sample application:

----------------------------------------------------------

import wx
import time

class My_frame(wx.Frame):
def __init__(self):
wx.Frame.__init__(self, None, -1, 'Moving panel')
self.surface = p = wx.Panel(self, size=(300, 130))
self.square = wx.Panel(p, -1, size=(100, 100), pos=(0, 30))
self.square.Bind(wx.EVT_PAINT, self.on_paint_square)

btn_move = wx.Button(p, -1, 'Move panel', pos=(0, 0))
self.Bind(wx.EVT_BUTTON, self.move_panel, btn_move)

self.Fit()

def move_panel(self, evt):
def gen():
for x in range(200):
yield x
for x in range(200, 0, -1):
yield x
for x in gen():
self.square.SetPosition((x, 30))
time.sleep(0.005)


def on_paint_square(self, evt):
square = evt.GetEventObject()
dc = wx.BufferedPaintDC(square)
dc.Pen = wx.Pen('blakc', 2)
dc.Brush = wx.Brush('light blue')
dc.DrawRectangle(0, 0, *square.GetSize())

app = wx.PySimpleApp()
My_frame().Show()
app.MainLoop()

----------------------------------------------------------

Press the button and the panel moves to the right and then back to the
left.
While PyScripter is running the panel moves at a certain speed. You
can run the application from the Windows explorer with the same speed.
You don't need to run it from PyScripter.
When PyScripter is closed, the application runs much less quickly.
I experienced this on two PC-s.
Maybe this behavior is not even related to wxPython but to the sleep
command. I don't know...

Thanks
iu2
 
C

Carl Banks

Hi, thanks for your reply

Here is a sample application:

----------------------------------------------------------

import wx
import time

class My_frame(wx.Frame):
    def __init__(self):
        wx.Frame.__init__(self, None, -1, 'Moving panel')
        self.surface = p = wx.Panel(self, size=(300, 130))
        self.square = wx.Panel(p, -1, size=(100, 100), pos=(0, 30))
        self.square.Bind(wx.EVT_PAINT, self.on_paint_square)

        btn_move = wx.Button(p, -1, 'Move panel', pos=(0, 0))
        self.Bind(wx.EVT_BUTTON, self.move_panel, btn_move)

        self.Fit()

    def move_panel(self, evt):
        def gen():
            for x in range(200):
                yield x
            for x in range(200, 0, -1):
                yield x
        for x in gen():
            self.square.SetPosition((x, 30))
            time.sleep(0.005)

    def on_paint_square(self, evt):
        square = evt.GetEventObject()
        dc = wx.BufferedPaintDC(square)
        dc.Pen = wx.Pen('blakc', 2)
        dc.Brush = wx.Brush('light blue')
        dc.DrawRectangle(0, 0, *square.GetSize())

app = wx.PySimpleApp()
My_frame().Show()
app.MainLoop()

----------------------------------------------------------

Press the button and the panel moves to the right and then back to the
left.
While PyScripter is running the panel moves at a certain speed. You
can run the application from the Windows explorer with the same speed.
You don't need to run it from PyScripter.
When PyScripter is closed, the application runs much less quickly.
I experienced this on two PC-s.
Maybe this behavior is not even related to wxPython but to the sleep
command. I don't know...

It's not a good idea to call time.sleep inside a loop inside an event
handler, which is what you are doing here.

wx has a mechanism to call some sort of callback at timed intervals.
(I don't know what it is but I know it has one.) Instead of animating
the box move inside the button callback, have it request that a
callback be called after x seconds pass, and draw a single frame
inside that callback. Then invoke it again until you're done drawing.

Here's a rough idea of what that might look like:



class My_frame(wx.Frame):

# ... among other things ...

def move_panel(self,evt):
self.square_pos = 0
wx.set_timed_callback_of_some_sort(
self.draw_frame,0.005)

def draw_frame(self):
self.square.SetPosition((self.square_pos, 30))
self.square_pos += 1
if self.square_pos < 200:
wx.set_timed_callback_of_some_sort(
self.draw_frame,0.005)


As for why it works fine in PyScripter but not when started from
Explorer, well, let's just say that when you abuse callbacks like you
did, you shouldn't expect reasonable behavior.


Carl Banks
 
M

Mike Driscoll

Hi, thanks for your reply

Here is a sample application:

----------------------------------------------------------

import wx
import time

class My_frame(wx.Frame):
    def __init__(self):
        wx.Frame.__init__(self, None, -1, 'Moving panel')
        self.surface = p = wx.Panel(self, size=(300, 130))
        self.square = wx.Panel(p, -1, size=(100, 100), pos=(0, 30))
        self.square.Bind(wx.EVT_PAINT, self.on_paint_square)

        btn_move = wx.Button(p, -1, 'Move panel', pos=(0, 0))
        self.Bind(wx.EVT_BUTTON, self.move_panel, btn_move)

        self.Fit()

    def move_panel(self, evt):
        def gen():
            for x in range(200):
                yield x
            for x in range(200, 0, -1):
                yield x
        for x in gen():
            self.square.SetPosition((x, 30))
            time.sleep(0.005)

    def on_paint_square(self, evt):
        square = evt.GetEventObject()
        dc = wx.BufferedPaintDC(square)
        dc.Pen = wx.Pen('blakc', 2)
        dc.Brush = wx.Brush('light blue')
        dc.DrawRectangle(0, 0, *square.GetSize())

app = wx.PySimpleApp()
My_frame().Show()
app.MainLoop()

----------------------------------------------------------

Press the button and the panel moves to the right and then back to the
left.
While PyScripter is running the panel moves at a certain speed. You
can run the application from the Windows explorer with the same speed.
You don't need to run it from PyScripter.
When PyScripter is closed, the application runs much less quickly.
I experienced this on two PC-s.
Maybe this behavior is not even related to wxPython but to the sleep
command. I don't know...

Thanks
iu2

You probably want to use wx.Timer rather than using time.sleep. You
could use wx.Sleep() too. I don't really understand why you're doing
what you're doing though. Did you re-post to the wxPython group?

Mike
 
I

iu2

It's not a good idea to call time.sleep inside a loop inside an event
handler, which is what you are doing here.

wx has a mechanism to call some sort of callback at timed intervals.
(I don't know what it is but I know it has one.)  Instead of animating
the box move inside the button callback, have it request that a
callback be called after x seconds pass, and draw a single frame
inside that callback.  Then invoke it again until you're done drawing.

Here's a rough idea of what that might look like:

class My_frame(wx.Frame):

    # ... among other things ...

    def move_panel(self,evt):
        self.square_pos = 0
        wx.set_timed_callback_of_some_sort(
            self.draw_frame,0.005)

    def draw_frame(self):
        self.square.SetPosition((self.square_pos, 30))
        self.square_pos += 1
        if self.square_pos < 200:
            wx.set_timed_callback_of_some_sort(
               self.draw_frame,0.005)

As for why it works fine in PyScripter but not when started from
Explorer, well, let's just say that when you abuse callbacks like you
did, you shouldn't expect reasonable behavior.

Carl Banks

Hi,

Here is the timer version. It works even more slowly, even with
PyScripter active:

--------------------------------------------------------------
import wx
import time

class My_frame(wx.Frame):
def __init__(self):
wx.Frame.__init__(self, None, -1, 'Moving panel')
self.surface = p = wx.Panel(self, size=(300, 130))
self.square = wx.Panel(p, -1, size=(100, 100), pos=(0, 30))
self.square.Bind(wx.EVT_PAINT, self.on_paint_square)

btn_move = wx.Button(p, -1, 'Move panel', pos=(0, 0))
self.Bind(wx.EVT_BUTTON, self.move_panel, btn_move)

self.Fit()

def on_paint_square(self, evt):
square = evt.GetEventObject()
dc = wx.BufferedPaintDC(square)
dc.Pen = wx.Pen('blakc', 2)
dc.Brush = wx.Brush('light blue')
dc.DrawRectangle(0, 0, *square.GetSize())

def move_panel(self, evt):
def gen():
for x in range(200):
yield x
for x in range(200, 0, -1):
yield x
self.track = gen()
self.timer = wx.Timer(self)
self.Bind(wx.EVT_TIMER, self.on_timer_square, self.timer)
self.timer.Start(milliseconds=1)

def on_timer_square(self, evt):
try:
x = self.track.next()
self.square.SetPosition((x, 30))
except StopIteration:
self.timer.Stop()

app = wx.PySimpleApp()
My_frame().Show()
app.MainLoop()
-----------------------------------------------------------------

I actually tried this one first. Due to the slow speed I changed to
looping inside the event.
I don't understand why it takes so long to move that square with
wx.Timer set to 1 ms interval. Perhaps its minimum interval is
actually 10 ms (as in Windows) so 100 steps really take 1 second.
But in that case, I really want to move the square in a tight loop
inside the wx.EVT_BUTTON event.

So allow me to rephrase my question:
Is there a way to move that square quickly and smoothly? Should 400
one-pixel moves should take so long on a 2.8 GHz core duo?

There is certainly something wrong in the code I wrote which I need
your help to figure out.
Can it be related to recurring paint events? If so how should I change
the code?

Thanks
 
I

iu2

Here is a too-fast version you could fiddle around with:

import wx

class My_frame(wx.Frame):
     def __init__(self):
         wx.Frame.__init__(self, None, -1, 'Moving panel')
         self.surface = p = wx.Panel(self, size=(300, 130))
         self.square = wx.Panel(p, -1, size=(100, 100), pos=(0, 30))
         self.square.Bind(wx.EVT_PAINT, self.on_paint_square)

         btn_move = wx.Button(p, -1, 'Move panel', pos=(0, 0))
         self.Bind(wx.EVT_BUTTON, self.startcalls, btn_move)
         self.Fit()

     def startcalls(self, evt=None):
         def gen():
             for x in range(200):
                 yield x
             for x in range(200, 0, -1):
                 yield x
         self.track = gen().next
         wx.CallAfter(self.stepcall)

     def stepcall(self, evt=None):
         try:
             x = self.track()
         except StopIteration:
             pass
         else:
             self.square.SetPosition((x, 30))
             wx.CallAfter(self.stepcall)

     def on_paint_square(self, evt):
         square = evt.GetEventObject()
         dc = wx.BufferedPaintDC(square)
         dc.Pen = wx.Pen('black', 2)
         dc.Brush = wx.Brush('light blue')
         dc.DrawRectangle(0, 0, *square.GetSize())

if __name__ == '__main__':
     app = wx.PySimpleApp()
     My_frame().Show()
     app.MainLoop()

Indeed, but I don't think the CallAfter is necessary. I could just as
well remove the time.sleep in the original code. I could also make a
tight loop to replace time.sleep
for i in range(1000000): pass
and tune it to fit the speed I need.

I haven't mention this, but I actually want something to be the same
speed on different PC-s. So a timer seems to fit in.

I just can't make it work.
Using wx.Timer is too slow.
Using time.sleep is fast with PyScripter active, and slow when it is
closed.
 
D

David Bolen

iu2 said:
Indeed, but I don't think the CallAfter is necessary. I could just as
well remove the time.sleep in the original code. I could also make a
tight loop to replace time.sleep
for i in range(1000000): pass
and tune it to fit the speed I need.

Except that CallAfter passed control back through the event loop which is
crucial for your GUI to appear responsive in other ways.
I haven't mention this, but I actually want something to be the same
speed on different PC-s. So a timer seems to fit in.

Then even a time.sleep() or plain loop isn't sufficient since each may
have additional latencies depending on load. You will probably need
to query a system clock of some type to verify when your interval has
passed.
I just can't make it work.
Using wx.Timer is too slow.
Using time.sleep is fast with PyScripter active, and slow when it is
closed.

I have to admit to thinking that perhaps you're trying to operate too
quickly if you need better resolution than wx.Timer. Most screen
operations don't have to appear that frequently to still appear
smooth, but that's your call.

Of course, even wx.Timer may be subject to other latencies if the
system or your application is busy with other events, so it depends
on how critical precise your timing needs to be.

You might also try an idle event, implementing your own timer (using
whatever call gives you the best resolution on your platform), and
just ignoring idle events that occur more frequently than the timing
you want. Just remember to always request a new event. You could do
the same thing with CallAfter as well, just reschedule a new one if
the current one is faster than your preferred interval.

-- David
 
C

Carl Banks

Hi,

Here is the timer version. It works even more slowly, even with
PyScripter active:

--------------------------------------------------------------
import wx
import time

class My_frame(wx.Frame):
    def __init__(self):
        wx.Frame.__init__(self, None, -1, 'Moving panel')
        self.surface = p = wx.Panel(self, size=(300, 130))
        self.square = wx.Panel(p, -1, size=(100, 100), pos=(0, 30))
        self.square.Bind(wx.EVT_PAINT, self.on_paint_square)

        btn_move = wx.Button(p, -1, 'Move panel', pos=(0, 0))
        self.Bind(wx.EVT_BUTTON, self.move_panel, btn_move)

        self.Fit()

    def on_paint_square(self, evt):
        square = evt.GetEventObject()
        dc = wx.BufferedPaintDC(square)
        dc.Pen = wx.Pen('blakc', 2)
        dc.Brush = wx.Brush('light blue')
        dc.DrawRectangle(0, 0, *square.GetSize())

    def move_panel(self, evt):
        def gen():
            for x in range(200):
                yield x
            for x in range(200, 0, -1):
                yield x
        self.track = gen()
        self.timer = wx.Timer(self)
        self.Bind(wx.EVT_TIMER, self.on_timer_square, self.timer)
        self.timer.Start(milliseconds=1)

    def on_timer_square(self, evt):
        try:
            x = self.track.next()
            self.square.SetPosition((x, 30))
        except StopIteration:
            self.timer.Stop()

app = wx.PySimpleApp()
My_frame().Show()
app.MainLoop()
-----------------------------------------------------------------

I actually tried this one first. Due to the slow speed I changed to
looping inside the event.
I don't understand why it takes so long to move that square with
wx.Timer set to 1 ms interval. Perhaps its minimum interval is
actually 10 ms (as in Windows) so 100 steps really take 1 second.
But in that case, I really want to move the square in a tight loop
inside the wx.EVT_BUTTON event.

No, you don't *ever* want to do this. GUI frameworks are designed for
event handlers to execute quickly. It won't work reliably to hijack
an event handler. It might work ok sometimes, but other times it'll
freeze the application while it's executing, perhaps even the whole
windowing system. Some toolkits might even queue the graphics
commands and not draw anything until the event handler returns. Just
don't do it.

So allow me to rephrase my question:
Is there a way to move that square quickly and smoothly? Should 400
one-pixel moves should take so long on a 2.8 GHz core duo?

Not really. The issue isn't core speed; other factors limit the
maximum output rate.

Your monitor probably has a maximum update rate of 60-80 Hz range,
which means regardless of how fast your CPU is, you can't see updates
faster than this. Which means that it'd be impossible to move your
square to move one pixel at time 100 pixels and finish in 1 second and
see every frame.

There are other things in the system that contribute to it, too. The
real issue is that a typical desktop system isn't designed to push
very high frame rates. Your system might be able to draw 1000 squares
50 times a second, but it doesn't mean it can draw 500 squares at 100
frames per second, or even one square at that speed.

There is certainly something wrong in the code I wrote which I need
your help to figure out.
Can it be related to recurring paint events? If so how should I change
the code?

What's wrong, I think, is your understanding of "smooth" animation.
It's not very important to use small increments of space, only small
increments of time.

In other words, you don't have to draw that square in 1 pixel
increments for it to look smooth, you only have to draw it at at a
reasonably fast frame rate (say 25 Hz for starters), and draw it where
it's supposed to be at the right time.

I recommend you write a function that calculates the square's position
as a function of time, and whenever you get a timer event, calculate
the elapsed time, figure out where the square should be and draw it
there. Something like this:


def calc_position(t):
if t < 0.2:
x = t/1000
elif t < 0.4:
x = 200 - (t-0.2)/1000
else:
raise ValueError('animation over')

def move_panel(self,evt):
self.timer = wx.Timer(self)
self.Bind(wx.EVT_TIMER, self.on_timer_square,
self.timer)
self.timer.Start(milliseconds=40) # 25 Hz

def on_timer_square(self, evt):
try:
# I'm not sure how to get elapsed time since timer is
started
x = self.calc_position(self.timer._GetElapsedTime())
except ValueError:
self.timer.Stop()
else:
self.square.SetPosition((x, 30))


(For very advanced graphics, small increments in space do come into
play for fast-moving objects; however, such cases are usually handled
by blurring the object in question. For instance they'd draw the
square to a memory buffer 5 times, then average them to get the
blurred version, and draw that.)



Carl Banks
 
I

iu2

Then even a time.sleep() or plain loop isn't sufficient since each may
have additional latencies depending on load.  You will probably need
to query a system clock of some type to verify when your interval has
passed.
You might also try an idle event, implementing your own timer (using
whatever call gives you the best resolution on your platform), and
just ignoring idle events that occur more frequently than the timing
you want.  Just remember to always request a new event.  You could do
the same thing with CallAfter as well, just reschedule a new one if
the current one is faster than your preferred interval.

-- David

Thanks, I need to give it a try.
Except that CallAfter passed control back through the event loop which is
crucial for your GUI to appear responsive in other ways.

A question about CallAfter: As I understand, this function is intended
to be used from within threads, where it queues the operation to be
performed in the GUI queue.
But in this example there are no other threads except for the GUI
thread itself, from which CallAfter is called.
How does it work in this situation? Does it queue the opreation for
some idle time or does it perform it right away?
I thought that it probably performs it immediately in this case so it
is exactly like calling the operation directly.

And another question, if I may, I used to make tight loops in windows
API, planting inside them a command that processes messages from the
GUI queue and returns when no more messages exists. Something like
this:

loop {
operations
process_gui_messages
}

The loop ran quickly and the GUI remained responsive during the loop.
I did it on window API using a function I defined similar to this one:

void ProcessMessages()
{
while (PeekMessage(....)) {
TranslateMessage(..);
DispatchMessage(..);
}
}

This technique is not good for long loops, where the user may activate
other long GUI opreations during the tight loop and make a mess.
But it carries out the job well where during the time of the loop the
user may only access to certain features, such as pressing a button to
cancel the operation, operating the menu to exit the program, etc.
This scheme saves some state-machine code that is required when using
event-based programming.

Does wxPython have something like ProcessMessages?

Thanks
 
D

David Bolen

iu2 said:
A question about CallAfter: As I understand, this function is intended
to be used from within threads, where it queues the operation to be
performed in the GUI queue.

I agree with the second half of the sentence but not the first.
CallAfter is intended to queue up a delayed call (via the GUI queue),
but it can be used anywhere you wish that behavior. Yes, it's also
one of the very few functions that can be called from a thread other
than the GUI thread, but it works just as well from the GUI thread.

Or to quote its docstring:

Call the specified function after the current and pending event
handlers have been completed. This is also good for making GUI
method calls from non-GUI threads. Any extra positional or
keyword args are passed on to the callable when it is called.
How does it work in this situation? Does it queue the opreation for
some idle time or does it perform it right away?

You can actually see the source in _core.py in your wx installation.
It always executes via a wx.PostEvent call.
And another question, if I may, I used to make tight loops in windows
API, planting inside them a command that processes messages from the
GUI queue and returns when no more messages exists. Something like
this:

loop {
operations
process_gui_messages
}

The loop ran quickly and the GUI remained responsive during the loop.
I did it on window API using a function I defined similar to this one:

I don't think there's much difference in the above and doing your
operations during one of the events. In both cases "operations" is
going to block any further event processing so cannot be lengthy or
the GUI will feel unresponsive. "Lengthy" varies but I'd certainly
put it in the neighborhood of small fractions of a second.

Your original code took almost 2 seconds for the "operations" part
(before getting back to processing GUI messages through the main
loop), which certainly seems too long.
void ProcessMessages()
{
while (PeekMessage(....)) {
TranslateMessage(..);
DispatchMessage(..);
}
}

Not quite positive, but if you're talking about implementing this as a
nested dispatch loop (e.g., called from within an existing event), you
can do that via wxYield. Of course, as with any nested event loop
processing, you have to be aware of possible reentrancy issues.
This technique is not good for long loops, where the user may activate
other long GUI opreations during the tight loop and make a mess.
But it carries out the job well where during the time of the loop the
user may only access to certain features, such as pressing a button to
cancel the operation, operating the menu to exit the program, etc.
This scheme saves some state-machine code that is required when using
event-based programming.

Maybe - for my own part, I'm not completely convinced and tend to far
prefer avoiding nested event loop dispatching. There are some times
when it might be unavoidable, but I tend to find it indicative that I
might want to re-examine what I am doing.

It seems to me that as long as you have to keep the "operations" step
of your loop small enough, you have to be able to divide it up. So
you'll need some state no matter what to be able to work through each
stage of the overall "operations" in between calls to process the GUI.

At that point, whether it's a local variable within the scope of the
looping code, or just some instance variables in the object handling
the event loop seems about the same amount of state management.

For example, in your original code you could probably consider the
generator and/or 'x' your local state. But the current step in the
movement could just as easily be an instance variable.
Does wxPython have something like ProcessMessages?

If you just mean a way to process pending messages wxYield may be
sufficient.

If you want to take over the primary dispatch loop for the application,
normally that has been handed off to wxWidgets via wxApp.MainLoop. However,
I believe you can build your own main dispatch loop if you want, as there
are functions in wxApp like ProcessPendingEvents, Pending, Dispatch and
so on. You may need to explicitly continue to support Idle events in
your own loop if desired.

If you need to get into more details, it's probably better dealt with
on the wxPython mailing list.

-- David
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,770
Messages
2,569,584
Members
45,075
Latest member
MakersCBDBloodSupport

Latest Threads

Top