[lnkForumImage]
TotalShareware - Download Free Software

Confronta i prezzi di migliaia di prodotti.
Asp Forum
 Home | Login | Register | Search 


 

Forums >

comp.lang.ruby

Updating GUIs

Joe Van Dyk

7/12/2005 5:56:00 PM

Hi,

I'm still fairly new to writing GUIs. A common approach to me is to
write some model classes that have a function called "update". That
function should get called a few times a second, and then the GUI
should get updated reflecting those changes.

What's the best way to do this? Should I have a Ruby thread that
calls the update function, sleeps for 0.2 seconds, and then loops?
How should the GUI get notified of those changes?

For discussion purposes, say I have

class Player
attr_accessor :x_position, :y_position, :player_type
# just a data store
end

class PlayerList
attr_accessor :players
def initialize
@players = [Player.new, Player.new, Player.new]
end
def update
# update goes out and updates the x and y position
end
end

And then the GUI knows about PlayerList and needs to display updated
information on each Player (move a player icon on a map, update
tabular data, that kind of thing).

I could have up to 500 players, and the application isn't supposed to
be terribly CPU intensive, so performance can be an issue.


26 Answers

Lothar Scholz

7/12/2005 6:29:00 PM

0

Hello Joe,

JVD> Hi,

JVD> I'm still fairly new to writing GUIs. A common approach to me is to
JVD> write some model classes that have a function called "update". That
JVD> function should get called a few times a second, and then the GUI
JVD> should get updated reflecting those changes.

JVD> What's the best way to do this? Should I have a Ruby thread that
JVD> calls the update function, sleeps for 0.2 seconds, and then loops?
JVD> How should the GUI get notified of those changes?

Only update/invalidate when you must update and then update/invalidate the
smallest possible rectangle. Thats the normal rule for writing GUI's


--
Best regards, emailto: scholz at scriptolutions dot com
Lothar Scholz http://www.ru...
CTO Scriptolutions Ruby, PHP, Python IDE 's




David Brady

7/12/2005 6:36:00 PM

0

Joe,

I am looking at this very problem. I don't have any good answers yet,
but to avoid simply saying "me too" to your thread... :-)

Have you looked at module Observable? For a large number of updatables,
that might reduce your overhead. The Player class would have to call
notify_observers every time it changed, but the end result is that any
GUI update threads would only need to update things that are specific to
the GUI (like a clock or animations). Player updates would be sent
directly to the GUI from the players when they update.

Ironically, notify_observers calls "update" in the observer. In this
case, the observer would probably want to be the code that controls that
one object in the GUI, not the entire GUI program.

-dB

--
David Brady
ruby-talk@shinybit.com
I'm having a really surreal day... OR AM I?



Joe Van Dyk

7/12/2005 8:10:00 PM

0

Hi,

I think I tried that approach a while ago. It worked pretty well
except with large amounts of Players. If Players get updated 2 times
a second and there's 500 players, that would be at least 1000 update
calls per second.

On 7/12/05, David Brady <ruby_talk@shinybit.com> wrote:
> Joe,
>
> I am looking at this very problem. I don't have any good answers yet,
> but to avoid simply saying "me too" to your thread... :-)
>
> Have you looked at module Observable? For a large number of updatables,
> that might reduce your overhead. The Player class would have to call
> notify_observers every time it changed, but the end result is that any
> GUI update threads would only need to update things that are specific to
> the GUI (like a clock or animations). Player updates would be sent
> directly to the GUI from the players when they update.
>
> Ironically, notify_observers calls "update" in the observer. In this
> case, the observer would probably want to be the code that controls that
> one object in the GUI, not the entire GUI program.
>
> -dB
>
> --
> David Brady
> ruby-talk@shinybit.com
> I'm having a really surreal day... OR AM I?
>
>
>


David Brady

7/12/2005 8:47:00 PM

0

Joe Van Dyk wrote:

>Hi,
>
>I think I tried that approach a while ago. It worked pretty well
>except with large amounts of Players. If Players get updated 2 times
>a second and there's 500 players, that would be at least 1000 update
>calls per second.
>
>

Ah. You're not trying to figure out how to setup an update structure.
You're trying to figure out how to pay for an 1KHz update rate. :-)

Eliminating function calls may save you some time, but the biggest thing
you can do to save time is to reduce needless duplication of drawing
routines. Don't take my word for it; check the profiler. My bet is
that the graphics routines take the most time.

If you have 500 discrete widgets on the GUI, one for each player, try to
make sure they each only update themselves when updated. Don't redraw
the entire UI on each cycle. This works if, for example, Player is a
thing that plays songs and has its own little display.

If each Player is drawn to the same area, for example if you have 500
sprites each running around the field doing their own thing, you'll want
to poll all the players then update the GUI area all at once. A classic
game programming technique is to divide each frame into separate
components: get input, process input/physics/collisions, move objects,
render. render() is done to an off-screen buffer, then blitted to the
display. The end result is that the poky 2D GUI drawing routines are
limited to the blit at the end of the frame.

-dB

--
David Brady
ruby-talk@shinybit.com
I'm having a really surreal day... OR AM I?



Joe Van Dyk

7/12/2005 9:18:00 PM

0

On 7/12/05, David Brady <ruby_talk@shinybit.com> wrote:
> Joe Van Dyk wrote:
>
> >Hi,
> >
> >I think I tried that approach a while ago. It worked pretty well
> >except with large amounts of Players. If Players get updated 2 times
> >a second and there's 500 players, that would be at least 1000 update
> >calls per second.
> >
> >
>
> Ah. You're not trying to figure out how to setup an update structure.
> You're trying to figure out how to pay for an 1KHz update rate. :-)
>
> Eliminating function calls may save you some time, but the biggest thing
> you can do to save time is to reduce needless duplication of drawing
> routines. Don't take my word for it; check the profiler. My bet is
> that the graphics routines take the most time.
>
> If you have 500 discrete widgets on the GUI, one for each player, try to
> make sure they each only update themselves when updated. Don't redraw
> the entire UI on each cycle. This works if, for example, Player is a
> thing that plays songs and has its own little display.
>
> If each Player is drawn to the same area, for example if you have 500
> sprites each running around the field doing their own thing, you'll want
> to poll all the players then update the GUI area all at once. A classic
> game programming technique is to divide each frame into separate
> components: get input, process input/physics/collisions, move objects,
> render. render() is done to an off-screen buffer, then blitted to the
> display. The end result is that the poky 2D GUI drawing routines are
> limited to the blit at the end of the frame.

Thanks for the help.

I'm doing this on a TkCanvas. Each Player has a different polygon
(each polygon has about 30 points on it or so). The polygons have to
move around on the canvas and rotate depending on which direction they
are going. So, I'm calling a rotate function for every point on every
polygon. I think that's a source of the slowness. I could post the
code for that section if needed. Are there any techniques to make
that part faster? I was thinking that converting the section of the
code that does the math on every point to C would help a lot, but
perhaps I'm doing it completely the wrong way.

# rotate function
def rotate(deg, x, y, c_x = 0, c_y = 0)
rad = (deg * Math::PI)/180.0
s_rad = Math::sin(rad)
c_rad = Math::cos(rad)
x -= c_x
y -= c_y
[c_x + (x * c_rad - y * s_rad), c_y + (x * s_rad + y * c_rad)]
end

# Gets used like this.
# coords returns an array of polygon points.
# center is the center of the polygon (an array like [x,y])
poly.coords(coords.collect{|x, y| rotate(deg, x, y, *center)})

Should I try to convert the rotate function to C and then see if it's
fast enough? I'm still making a gazillion function calls (say, 300
polygons and 30 points each and update twice a second, that's 18,000
function calls a second). No wonder it's slow. :( Perhaps using
TkCanvas was a mistake for this.


Joe Van Dyk

7/12/2005 9:19:00 PM

0

On 7/12/05, Joe Van Dyk <joevandyk@gmail.com> wrote:
> On 7/12/05, David Brady <ruby_talk@shinybit.com> wrote:
> > Joe Van Dyk wrote:
> >
> > >Hi,
> > >
> > >I think I tried that approach a while ago. It worked pretty well
> > >except with large amounts of Players. If Players get updated 2 times
> > >a second and there's 500 players, that would be at least 1000 update
> > >calls per second.
> > >
> > >
> >
> > Ah. You're not trying to figure out how to setup an update structure.
> > You're trying to figure out how to pay for an 1KHz update rate. :-)
> >
> > Eliminating function calls may save you some time, but the biggest thing
> > you can do to save time is to reduce needless duplication of drawing
> > routines. Don't take my word for it; check the profiler. My bet is
> > that the graphics routines take the most time.
> >
> > If you have 500 discrete widgets on the GUI, one for each player, try to
> > make sure they each only update themselves when updated. Don't redraw
> > the entire UI on each cycle. This works if, for example, Player is a
> > thing that plays songs and has its own little display.
> >
> > If each Player is drawn to the same area, for example if you have 500
> > sprites each running around the field doing their own thing, you'll want
> > to poll all the players then update the GUI area all at once. A classic
> > game programming technique is to divide each frame into separate
> > components: get input, process input/physics/collisions, move objects,
> > render. render() is done to an off-screen buffer, then blitted to the
> > display. The end result is that the poky 2D GUI drawing routines are
> > limited to the blit at the end of the frame.
>
> Thanks for the help.
>
> I'm doing this on a TkCanvas. Each Player has a different polygon
> (each polygon has about 30 points on it or so). The polygons have to
> move around on the canvas and rotate depending on which direction they
> are going. So, I'm calling a rotate function for every point on every
> polygon. I think that's a source of the slowness. I could post the
> code for that section if needed. Are there any techniques to make
> that part faster? I was thinking that converting the section of the
> code that does the math on every point to C would help a lot, but
> perhaps I'm doing it completely the wrong way.
>
> # rotate function
> def rotate(deg, x, y, c_x = 0, c_y = 0)
> rad = (deg * Math::PI)/180.0
> s_rad = Math::sin(rad)
> c_rad = Math::cos(rad)
> x -= c_x
> y -= c_y
> [c_x + (x * c_rad - y * s_rad), c_y + (x * s_rad + y * c_rad)]
> end
>
> # Gets used like this.
> # coords returns an array of polygon points.
> # center is the center of the polygon (an array like [x,y])
> poly.coords(coords.collect{|x, y| rotate(deg, x, y, *center)})
>
> Should I try to convert the rotate function to C and then see if it's
> fast enough? I'm still making a gazillion function calls (say, 300
> polygons and 30 points each and update twice a second, that's 18,000
> function calls a second). No wonder it's slow. :( Perhaps using
> TkCanvas was a mistake for this.

Also, is there a way to "freeze" the canvas while I'm making these
updates? I thought there was one for gnomecanvas.


Lothar Scholz

7/12/2005 9:36:00 PM

0

Hello Joe,

JVD> I'm doing this on a TkCanvas.

If you use TkCanvas it is very unlikely that the speed problem is
caused by the update call. This part is very optimized in TK.

JVD> Should I try to convert the rotate function to C and then see if it's
JVD> fast enough? I'm still making a gazillion function calls (say, 300
JVD> polygons and 30 points each and update twice a second, that's 18,000
JVD> function calls a second). No wonder it's slow. :( Perhaps using
JVD> TkCanvas was a mistake for this.

Have you isolated your rotation code and calculated the values without
passing the parameters to TkCanvas. How fast is this ?

The only TK related problem is to pass 18000 floating point numbers to
the canvas widget. Don't know how heavyweight the RubyTK layer is but
it means converting them to strings and back to floating points. So
maybe this parameter setting is the reason for the problem, but you
must measure it not trying to guess it.

I've seen more complicated TkCanvas scenarios 7 years ago when
CPU speed was 400 MHz and TK did not show a problem.

--
Best regards, emailto: scholz at scriptolutions dot com
Lothar Scholz http://www.ru...
CTO Scriptolutions Ruby, PHP, Python IDE 's




Joe Van Dyk

7/12/2005 10:21:00 PM

0

On 7/12/05, Lothar Scholz <mailinglists@scriptolutions.com> wrote:
> Hello Joe,
>
> JVD> I'm doing this on a TkCanvas.
>
> If you use TkCanvas it is very unlikely that the speed problem is
> caused by the update call. This part is very optimized in TK.
>
> JVD> Should I try to convert the rotate function to C and then see if it's
> JVD> fast enough? I'm still making a gazillion function calls (say, 300
> JVD> polygons and 30 points each and update twice a second, that's 18,000
> JVD> function calls a second). No wonder it's slow. :( Perhaps using
> JVD> TkCanvas was a mistake for this.
>
> Have you isolated your rotation code and calculated the values without
> passing the parameters to TkCanvas. How fast is this ?
>
> The only TK related problem is to pass 18000 floating point numbers to
> the canvas widget. Don't know how heavyweight the RubyTK layer is but
> it means converting them to strings and back to floating points. So
> maybe this parameter setting is the reason for the problem, but you
> must measure it not trying to guess it.
>
> I've seen more complicated TkCanvas scenarios 7 years ago when
> CPU speed was 400 MHz and TK did not show a problem.

This is taking me about a third of a second to do. Seems a bit high
if I want to do this at least twice a second and leave room on the
machine to do other things:


# rotate function
def rotate(deg, x, y, c_x = 0, c_y = 0)
rad = (deg * Math::PI)/180.0
s_rad = Math::sin(rad)
c_rad = Math::cos(rad)
x -= c_x
y -= c_y
[c_x + (x * c_rad - y * s_rad), c_y + (x * s_rad + y * c_rad)]
end

class Player
attr_accessor :x, :y, :polygon, :heading
def initalize
@x = @y = @heading = 0
@polygon = []
end
end

players = []
500.times do
p = Player.new
p.x = rand 1000000
p.y = rand 1000000
p.heading = rand 360
p.polygon = [] # Why do I need this here? If it's not, p.polygon is nil. :(
40.times do
p.polygon << [rand(10), rand(10)]
end
players << p
end

start_time = Time.now
players.each do |player|
player.polygon = player.polygon.collect do |x,y|
rotate(player.heading, x, y, player.x, player.y)
end
end
end_time = Time.now
puts end_time - start_time


So what's the next step when trying to optimize this? Convert the
rotate function to C? Or can I use a different algorithm?


Joe Van Dyk

7/12/2005 11:56:00 PM

0

On 7/12/05, Joe Van Dyk <joevandyk@gmail.com> wrote:
> On 7/12/05, Lothar Scholz <mailinglists@scriptolutions.com> wrote:
> > Hello Joe,
> >
> > JVD> I'm doing this on a TkCanvas.
> >
> > If you use TkCanvas it is very unlikely that the speed problem is
> > caused by the update call. This part is very optimized in TK.
> >
> > JVD> Should I try to convert the rotate function to C and then see if it's
> > JVD> fast enough? I'm still making a gazillion function calls (say, 300
> > JVD> polygons and 30 points each and update twice a second, that's 18,000
> > JVD> function calls a second). No wonder it's slow. :( Perhaps using
> > JVD> TkCanvas was a mistake for this.
> >
> > Have you isolated your rotation code and calculated the values without
> > passing the parameters to TkCanvas. How fast is this ?
> >
> > The only TK related problem is to pass 18000 floating point numbers to
> > the canvas widget. Don't know how heavyweight the RubyTK layer is but
> > it means converting them to strings and back to floating points. So
> > maybe this parameter setting is the reason for the problem, but you
> > must measure it not trying to guess it.
> >
> > I've seen more complicated TkCanvas scenarios 7 years ago when
> > CPU speed was 400 MHz and TK did not show a problem.
>
> This is taking me about a third of a second to do. Seems a bit high
> if I want to do this at least twice a second and leave room on the
> machine to do other things:
>
>
> # rotate function
> def rotate(deg, x, y, c_x = 0, c_y = 0)
> rad = (deg * Math::PI)/180.0
> s_rad = Math::sin(rad)
> c_rad = Math::cos(rad)
> x -= c_x
> y -= c_y
> [c_x + (x * c_rad - y * s_rad), c_y + (x * s_rad + y * c_rad)]
> end
>
> class Player
> attr_accessor :x, :y, :polygon, :heading
> def initalize
> @x = @y = @heading = 0
> @polygon = []
> end
> end
>
> players = []
> 500.times do
> p = Player.new
> p.x = rand 1000000
> p.y = rand 1000000
> p.heading = rand 360
> p.polygon = [] # Why do I need this here? If it's not, p.polygon is nil. :(
> 40.times do
> p.polygon << [rand(10), rand(10)]
> end
> players << p
> end
>
> start_time = Time.now
> players.each do |player|
> player.polygon = player.polygon.collect do |x,y|
> rotate(player.heading, x, y, player.x, player.y)
> end
> end
> end_time = Time.now
> puts end_time - start_time
>
>
> So what's the next step when trying to optimize this? Convert the
> rotate function to C? Or can I use a different algorithm?

On a very fast machine, the above code ran in 0.14 seconds. On a
pentium1 machine, it ran in 2.04 seconds.

When I used the following C function to do the rotation, it pretty
much halved the execution time to 0.06 seconds on the fast machine and
0.94 seconds on the Pentium1 machine.

I'm pretty new to C extensions, is there anything else I can do to
improve the speed? It would seem that moving the code that loops
around the Players and collects the polygon points to C would help
much more, but I don't know how to do that yet in C.

static VALUE rotate(VALUE self, /* Module */
VALUE _degree, /* Rotate Degree */
VALUE _x, VALUE _y, /* Point */
VALUE _c_x, VALUE _c_y) /* Center to rotate around */
{
double degree = NUM2DBL(_degree);
long x = NUM2LONG(_x);
long y = NUM2LONG(_y);
long c_x = NUM2LONG(_c_x);
long c_y = NUM2LONG(_c_y);

double rad = (degree * M_PI) / 180.0;
double s_rad = sin(rad);
double c_rad = cos(rad);
x -= c_x;
y -= c_y;

VALUE return_array = rb_ary_new();
rb_ary_push(return_array, rb_float_new(c_x + (x * c_rad - y * s_rad)));
rb_ary_push(return_array, rb_float_new(c_y + (x * s_rad + y * c_rad)));
return return_array;
}


Clifford Heath

7/13/2005 12:21:00 AM

0

Joe Van Dyk wrote:
> What's the best way to do this? Should I have a Ruby thread that
> calls the update function, sleeps for 0.2 seconds, and then loops?
> How should the GUI get notified of those changes?

I designed the OpenUI cross-platform GUI tool. We maintained a
draw-list to which any changed GUI object was added. The actual
draw was delayed. The delay was implemented by processing all
queued and incoming events (we controlled the main event loop),
then waiting for a (customisable) delay, usually 0.1-0.2 seconds,
before processing the drawlist. 0.1 seconds is about right, small
enough that a human won't notice the delay but long enough to
ensure that everything that reasonably can happen before redraw
has happened.

Redraw does need to be forced if you want to achieve the impression
of rapid progress (flickering through lists of files being scanned,
etc).

This is an excellent way to minimise redraw activity. It gives
automatic type-ahead - a user can even learn to enter keyboard
data into a popup window and Ok it *without the popup even
appearing*... On a slower computer of course - OpenUI was initially
developed to run on a Windows 3.1 system with only 4M of RAM :-).
The NASDAQ exchange ran on 486's with Windows 3.1 and only 8Mb
of RAM for many years - this was an OpenUI app that was receiving
and displaying sustained UDP broadcast bids and offers at an
average of several per second, maximum rate about ten per second,
with a very low lost-packet rate (there was no recovery from lost
packets). Pretty impressive, even by today's standards.

Clifford Heath.