This chapter discusses using FLTK for your parallel
applications.
General discussion
First let us call a parallel application - a single process
that has several (may be just one) light-weight (l/w) processes,the
processes that use the same address space. Using above termimology we can
say that FLTK has one GUI-l/w-process and several, may be none, extra
l/w-processes that can communicate with each other. The application quits
only after all l/w-processes finish. The general concept of
parallel GUI-application is to keep all time-taking work (such as serious
computations or waiting for a network socket) in extra l/w-processes, let us
call such l/w-process a brain ;-) . A brain can send special
messages to GUI-l/w-process, the GUI-l/w-process queue them and process
them one-by-one, let as say in this situation that a brain requests
an audience. A brain should request an audience when
it wants to do any work that, because of the FLTK design, should be
done from GUI-l/w-process.
Example (Counter.cxx)
#include <FL/Fl.H>
#include <FL/Fl_Double_Window.H>
#include <FL/Fl_Return_Button.H>
#include <FL/Fl_Output.H>
#include <FL/Fl_Brain.H>
#include <stdio.h>
class Counter : public Fl_Output,
public Fl_Brain, public Fl_Secretary {
public:
Counter(int X, int Y, int W, int H, char *L=0)
:Fl_Output(X,Y,W,H,L) {
// Beautify
color(FL_WHITE);
selection_color(FL_GREEN);
labeltype(FL_EMBOSSED_LABEL);
labelfont(FL_COURIER);
labelsize(20);
labelcolor(FL_BLUE);
align(FL_ALIGN_TOP_LEFT);
textsize(20);
textfont(FL_COURIER);
value("0");
};
// This function is called by GUI-thread in response to audience() call
static void update(void *d) {
Counter *c=(Counter *)d;
char message[50];
sprintf(message, "%d", (int)(c->i));
c->value(message); // draw() is called automatically
// In this programm we want to wait for audience to be executed
c->report();
};
static void controller_cb(Fl_Widget *w, void *) {
Fl_Return_Button *r=(Fl_Return_Button *)w;
Counter *c = (Counter*)(w->user_data());
static int relaxed=1;
if(relaxed^=1) {
r->label("Proceed");
r->labelcolor(FL_GREEN);
c->relax();
} else {
r->label("Relax");
r->labelcolor(FL_RED);
c->proceed();
}
r->redraw();
};
protected:
// Child thread code goes here.
void action() {
rank(0); // Choose the lowest priority
for(i=0;;) {
i++;
// This asks fltk to call update() member-function from GUI-thread
audience(update, this);
// Wait for the update() metod
expect();
}
};
private:
short i;
};
int main(int argc, char *argv[])
{
Fl::args(argc, argv);
// It is OK to make toplevel window a local variable
Fl_Double_Window window(255, 115, "Counter");
Counter counter(10, 25, 235, 35, "Counter:");
Fl_Group::current()->resizable(&counter);
Fl_Return_Button controller(65, 75, 125, 35, "Proceed");
controller.labeltype(FL_ENGRAVED_LABEL);
controller.labelfont(FL_TIMES_BOLD);
controller.labelsize(20);
controller.labelcolor(FL_GREEN);
controller.callback(Counter::controller_cb, (void*)(&counter));
// Don't allow user to make window smaller that this size.
window.size_range(255,115);
// That's all. ;-)
window.end();
// argc, argv are passed so that command line parameters
// (geometry, etc.) are
window.show(argc, argv);
return Fl::run();
}
Sreenshot:
It is very simple. When you press on the button "Proceed" it
changes its label to "Relax" and starts to count, when you press
on "Relax" the counter stops and the button changes its label to
"Proceed".
Explanation
In this example the brain waits for the GUI-l/w-process to
process an audience request. But we could change the code so
that the brain just send an audience request and continue its job.
Please note rank(0)
-this is very usefull if
a brain has to make a long-time
computations
(not relax()'ing).
Example (Counter_lock.cxx)
class Counter : public Fl_Output,
public Fl_Brain, public Fl_Lock {
// Please note we inherit Fl_Lock here,not Fl_Secretary
public:
// .....
// This function is called by GUI-thread in response to audience() call
static void update(void *d) {
Counter *c=(Counter *)d;
char message[50];
int i; // local copy!
{ Fl_Guard g(*c); i=c->i; }
sprintf(message, "%d", i);
c->value(message); // draw() is called automatically
// no c->report(); !
};
// .....
// Child thread code goes here.
void action() {
// rank(0); no need in the lowest priority
for(int i=0;;) { // local copy!
i++;
{ Fl_Guard g(*this); this->i=i; }
// This asks fltk to call update() member-function from GUI-thread
audience(update, this);
// no expect();!
// Relax just for 1/50 part of a second.
relax_sec(1./50.);
}
};
// .....
Explanation
Please note that we use a lock here, because a variable i is
accessed by two l/w-processes (GUI-l/w-process and a brain(Counter)).
It is trivial to make a lock.Fl_Guard constructor
locks a lock and deconstructor unlocks, regardless how we quited a block
where we declared an Fl_Guard. In fact this style is mostly used in real applications.
Appendix: description of classes & methods.
This provides a facade for controlling a brain.
Include Files
#include <FL/Fl_Brain.H>
Methods
Does nothing. Please note that it is protected.
Destroys the brain by calling a
shock() method.
This is an pure virtual method that should be defined in
an implementation of brain (Fl_Brain
subclass). This method is called when
proceed()
is called for the first time.
Returns true if the brain was
proceed()'ed,
action() did not return and the brain
wasn't shock()'ed. In the other case it
returns false.
These are the most important methods. They send a request to the main
GUI-l/w-process and it does the following as soon as possible (respectively):
- w->handle(event);
- w->damage(dmask);
- cb(event);
- cb(args);
Makes the calling l/w-process wait for the brain death.
It is safe to call this on !alive()
brain.
These methods makes a calling l/w-process to wait for the brain death
(like follow()), but if the brain
does not finish in n milliseconds
(t seconds) they return false
(if the brain was follow()'ed the return-value is true).
It is safe to call this method on
!alive() brain.
If called for the first time - it starts the brain
(calls action()).
If the brain was relax()'ed
this methos makes it to continue.
Please note that most of Fl_Brain
methods (exept
Fl_Brain(),
~Fl_Brain(),
follow(),
follow_sec(),
alive())
are only valid after
proceed() was called at least onece.
Sets (gets) the priority (rank) of the brain
- 0.0-idle
- 0.0..0.5-idle-normal
- 0.5-normal (default)
- 0.5..1.0-normal-realtime
- 1.0-realtime.
Please note that under Linux
rank()
always returns 0.5 and it
is only possible to decrease brain's
rank().
It is a good idea to rank(1./5.);
a time-taking l/w-process.
Makes the brain to suspend execution until anyone calls
proceed() method. A
brain can call this from it's body, so
relax()'ing itself.
Makes the calling l/w-process to stop for n
milliseconds (t seconds).
Please note that these methods are static so a call like
Fl_Brain::relax_sec(1.0); will work.
Terminates the brain immediatly. No cleanup is done.
All follow()'ing l/w-processes are resumed.
This class provides a facade for controlling a lock. You can use a
lock for mutual-exclusion synchronization. Lock is
always recurcive. Once lock was hire()'d
it should be fire()'d by the same
brain.
Include Files
#include <FL/Fl_Brain.H>
Methods
Creates a lock (system)-object.
Destroys the lock (system)-object.
Locks a lock so that all other brains that call this method
with the same lock are suspended until a brain that hire()'d
a lock fire()'s it.
Releases a lock: makes a first brain that
hire()'d this
lock and was suspended to continue its execution.
Please note that a lock should be fire()'d
by the same brain that hire()'d it.
This is not checked on some systems (ex. Linux) but you should follow this
to keep your application portable.
This is a helper class, but it is very usefull for managing locks.
Include Files
#include <FL/Fl_Brain.H>
Methods
Hire()'s a lock l.
Fire()'s a lock l.
Please note that this method is called automatically when you exit the block.
It is useful to create a static Fl_Lock
object and auto Fl_Guard
{
static Fl_Lock l;
Fl_Guard g(l); // variables are auto by default
//..... syncronized code.
}
It is also usefull to inherit Fl_Lock
class My : public Fl_Lock, Fl_Brain , ..... {
//....
{
Fl_Guard g(this);
// syncronized code.
};
};
This class provides a facade for managing a secretary. A secretary
is mostly usefull for brain<->brain and brain<->GUI-l/w-process communication.
Include Files
#include <FL/Fl_Brain.H>
Methods
Creates a secretary (system)-object.
Destroys the secretary (system)-object.
MAkes a calling brain to wait untill someone
report()'s it
or broadcast()'s.
Makes a calling brain to wait untill someone
report()'s it
or broadcast()'s, in
this case true is returned, a calling brain is
also resumed after n milliseconds or t seconds
pass, in this case false is returned.
Makes a first brain that expect()'d
this secretary and was suspended to continue its execution.
Makes all brain that expect()'d
this secretary and were suspended to continue their execution.
Licence: LGPL. Copyright (c) 1999 by Yaroslav Volovich
(yaroslav_v@mail.ru)
(http://volovich.da.ru)
(27 September 1999)