Tek-Tips is the largest IT community on the Internet today!

Members share and learn making Tek-Tips Forums the best source of peer-reviewed technical information on the Internet!

  • Congratulations Mike Lewis on being selected by the Tek-Tips community for having the most helpful posts in the forums last week. Way to Go!

Background processes in Tk

Status
Not open for further replies.

AMiSM

Technical User
Jan 26, 2006
128
US
Greetings!
Let's say you want to write a video game, with Tk as your interface, say an rts, not turn-based. There are going to be things happening in the background in addition to getting user input. How do you code, say, the AI, or the sprite animations in a canvas widget while the program also handles random user input?
OR...Let's say you have to make a GUI for a program that is monitoring chemical processes and operating valves while handling random user input all at the same time?
In short, is there any way to code with Tk so that is something other than completely event-driven, or is Tk strictly event-driven?
How is Tetris done with Tk?
 
A quick Google search for perl/tk games returned this result for tktetris (as well as many other Tk scripts)


Perl is, by default, a single-threaded program. It can run one "program" at a time, executing command after command in order. If this is what you mean by event-driven, this is how Perl is.

There is a Perl module called threads, which can enable Perl to become multi-threaded.

To say something first about using threads: Tk seems to have its own way of handling threads, which I haven't found documented anywhere yet. But if you use Tk and then spawn off a new thread, your program crashes. So, spawn all your background threads first before loading the Tk libraries.

One problem with threads is that, each thread is like its own program. They don't normally share variables among one another. There's another module, threads::shared, that can be used to share variables. A recommended approach is to share arrays, so that the various threads can push or unshift events onto this array, and the other threads can read from it to get the information.

For a quick example:
Code:
use threads;
use threads::shared;

# share an array
our @messenger : shared;

# spawn an independent thread
my $child = threads->create (sub {
   # this thread will wait 20 seconds
   # then tell the main thread to turn
   # the MainWindow to have a blue background
   sleep 20;

   push (@messenger, "turn background blue");
});
$child->detach; # detach the thread, we don't care about a return value

use Tk;

my $mw = MainWindow->new;

# the main thread (the run running Tk) should
# constantly check @messenger for new messages
# from the other threads.
while (1) {
   select (undef,undef,undef,0.01); # sleep a very short time
   $mw->update; # refresh the Tk windows

   if (@messenger) {
      # get the next task
      my $task = shift(@messenger);

      if ($task =~ /turn background blue/i) {
         $mw->configure (-background => 'blue');
      }
   }
}

That's one approach to doing it. You'd have to come up with your own kind of "protocol" for how to parse messages from @messenger. Another idea is to have the threads put "literal" Perl code into the array (such that $variable has the $ sign in it, rather than interpolating a real $variable), and have the main thread eval it.

It won't be easy if you're not familiar with Perl and how these things work.
 
I thought of threads before, but there just has to be a cleaner and easier way.
I looked through the tetris source. (I don't know why it was a dead link when I came across that before.) I can't see immediately how the blocks move without waiting for input. As you'd expect, it's just subs after "MainLoop;".
 
Probably what it eventually does is, when you start a new game there's probably a loop somewhere that calls $mw->update every so often while moving the blocks between events.

I guess it depends on what you're intending to do. A game like Tetris or Breakout or Pac-Man is easy to do using just the one thread. It just has to move all the widgets around, then update the main window to show the changes. If you're making a really big game (such as an RTS as you mentioned) where each team could have hundreds of units, which all have their individual "AI's" (as far as, when an enemy gets near they all walk over to it and fight automatically without being commanded to), you may need one or more threads just for the sake of speed.
 
Maybe Win32::GUI is a better way to go.
 
...looks like the same issues.
 
Yeah, Win32::GUI doesn't solve the problem that Tk has, both modules require an update between events to refresh the GUI windows.

Here's something else you can look at: go to command prompt and type widget and hit enter. It brings up a Perl Tk window that demonstrates the various widgets. Scroll down under "Simulations" and click "Balls bouncing around in a cavity".

This is a little Tk script with a bunch of colored balls and when you hit start, they start moving and bouncing off walls and everything. View the source of that to find out how they do it. Basically they have a subroutine to move the balls around on the canvas and they update the main window.

Here's the bit of relevant code from it:
Code:
sub move_one_ball {

    # Move one ball, belonging to one simulation, one clock tick.

    my ($ball_obj, $speed_ratio) = @_;

    my($ball, $canv, $minx, $miny, $maxx, $maxy);
    my($ballx, $bally, $deltax, $deltay);

    $speed_ratio = 1.0 unless defined $speed_ratio;
    $ball = $ball_obj->{'canvas_ID'};
    $canv = $ball_obj->{'canvas'};
    $ballx = $ball_obj->{'pos'}[0];
    $bally = $ball_obj->{'pos'}[1];

    $minx = $ball_obj->{'size'} / 2.0;
    $maxx = $ball_obj->{'canvas'}->cget(-width) - $minx;

    $miny = $ball_obj->{'size'} / 2.0;
    $maxy = $ball_obj->{'canvas'}->cget(-height) - $miny;

    if ($ballx > $maxx || $ballx < $minx) {
        $ball_obj->{'vel'}[0] = -1.0 * $ball_obj->{'vel'}[0];
    }
    if ($bally > $maxy || $bally < $miny) {
        $ball_obj->{'vel'}[1] = -1.0 * $ball_obj->{'vel'}[1];
    }

    $deltax = $ball_obj->{'vel'}[0] * $speed_ratio;
    $deltay = $ball_obj->{'vel'}[1] * $speed_ratio;

    $canv->move($ball, $deltax, $deltay);
    $ball_obj->{'pos'}[0] = $ballx + $deltax;
    $ball_obj->{'pos'}[1] = $bally + $deltay;

    return $ball_obj;

} # end move_one_ball

sub move_all_balls {

    # Move all the balls belong to one simulation instance one clock tick.

    my($class, $canvas, $speed_ratio) = @_;

    foreach (@{$BALLS{Ball->get_canvas_hash($canvas)}->{'BALLS'}}) {
        $_->move_one_ball($speed_ratio);
        DoOneEvent(DONT_WAIT);		# be kind and process XEvents if they arise
    }

} # end move_all_balls

(note: DoOneEvent is the same as $mw->update).
 
Cool! I didn't know the 'widget' thing was there. That inspires a bit of hope. The only question I have is, how does the script call the subroutine while still remaining open to input? I gotta look over this a bit...
 
DoOneEvent (or $mw->update) basically allows the GUI to "update", in that new changes to the GUI itself are visible, and all the pending events are handled. So, if you had your GUI update every 2 seconds (which would be really annoying), if you clicked on a button during those 2 seconds, it wouldn't react to you until update or DoOneEvent was called, and then the Tk window would receive that event that you clicked a button.

So, DoOneEvent handles all the GUI input for ya, as well as visually updating the GUI.
 
Got it! Check it out:

use Tk;

$mw = new MainWindow();
$txt=$mw->Text(-height=>4,-width=>50)->pack();

$mw -> repeat ( 2000 => \&beep );

MainLoop;

sub beep {
print "\a";
}
 
Status
Not open for further replies.

Part and Inventory Search

Sponsor

Back
Top