Doing lengthy calculations from a GUI

Well-written GUI programs often give the deceiving impression that many things are done in parallel. This is not the case.  In fact all events are processed sequentially, one after the other. The impression of concurrency is caused by the short amout of time which is usually needed to handle a single event.

Things become different when lengthy calculations need to be done. If no precautions are taken, the GUI ``freezes'' during the time that such a calculation is performed. If this is not acceptable, some scheme must be used to break up the calculation in parts and allow events to be handled in between.

Within Ggi there are two mechanisms to achieve this.

The most `orthodox' approach would be registering a timer handler using ScheduleTimer(), with a time interval of zero, and then letting this handler perform the partial calculations, one after the other. Mainloop() would then handle any intervening events, at most one of every event type for each call to the timer handler. Though not difficult, programming such a handler may be experienced as counter-intuitive, especially when the calculation is just a simple (nested) loop.

The second mechanism is provided by the function GgiHandleEvents(), which can be called whenever the application decides to allow the handling of intervening events. Please note that this can cause recursion on a number of levels, including application-specific event handlers. Any handlers which can be called recursively must be programmed to take care of the recursion.

The following example shows the use of the second mechanism. It is derived from GUI example 2. Differences are indicated in red. In this example a hundred million cosines are calculated. This is done from within the keyword handler work() which is activated by the keyword WORK=. Every fifty thousand calls to cos(), GgiHandleEvents() is called to allow the handling of intervening events. One of these events could be setting the keyword WORK= to NO. If this happens, work() is called recursively. In this recursive call the static variable worktodo is set to FALSE, which eventually causes the first invocation to execute the break statement and return.

Example program:

/* example5.c -XT */
   
#include "stddef.h"
#include "math.h"       /* for the cos function */
#include "gipsyc.h"
#include "cmain.h"
#include "init.h"
#include "finis.h"
#include "userfio.h"
#include "ggi.h"
   
/*
 *   Keyword handler for QUIT=
 */
static void quit(ident id, char *key, int code, void *arg)
{
   bool finished=toflog(FALSE);
 
   (void)userflog(&finished, 1, 2, key, " ");
   if (tobool(finished)) finis_c();
}
   
/*
 *   Keyword handler for both NUMBER= and GETAL=
 */
static void number(ident id, char *key, int code, void *arg)
{
   float result;
   
   if (userfreal(&result, 1, 2, key, " ")==1)
      anyoutf(0, "The number %f was given for %s", result, key);
}
  
/*
 *   Keyword handler for both DRINK= and FOOD=
 *   Note that the argument 'items' was specified by the registration calls.
 */
static void order(ident id, char *key, int code, void *items)
{
   fint result;
      
   if (userfint(&result, 1, 2, key, " ")==1)
      anyoutf(0, "You ordered %s", ((char**)items)[result]);
}  
 
/*
 *   Keyword handler for WORK=
 */
static void work(ident id, char *key, int code, void *items)
{
   static bool worktodo=toflog(FALSE);
   int count=0, i, j;
 
   (void)userflog(&worktodo, 1, 2, key, " ");
   worktodo = tobool(worktodo);
   if (worktodo) {
      for (j=0; j<2000; j++) {
         volatile float x=1.111;
         for(i=0; i<50000; i++) {
            (void)cos(x);                     /* pretend doing useful work... */
            count++;
         }
         if (GgiHandleEvents()) {
            if (!worktodo) break;             /* abort calculation */
         }
      }
      if (worktodo) wkeyf("%sNO", key);       /* if necessary, reset button */
      anyoutf(0, "Calculated %d cosines", count);
   }
}
   
/*
 *   Main program
 */
MAIN_PROGRAM_ENTRY
{
   static char *drinks[]={"Water","Milk","Beer","Wine","Sherry","Whisky",NULL};
   static char *foods[]={"Beans","Peas","Lettuce","Potatoes","Meat",NULL};
  
   ident button, text1, text2, gauge, menu1, menu2; /* declare elements */
   ident workbt;
   
   init_c();
   
/*
 *    Specify explicit layout and postponed realization.
 */
   GgiAutoLayout(FALSE);      /* layout  done explicitly by program */
   GgiPostponeRealize(TRUE);  /* show after all elements have been positioned */
            
/*
 *    Create elements.
 */
   button = GgiButton("QUIT=", "Terminate program");
   workbt = GgiButton("WORK=", "Work hard...");
   text1 = GgiTextField("NUMBER=", "Type a number in this field", 15);
   text2 = GgiTextField("GETAL=",  "Typ een getal\nin dit veld",  15);
   gauge = GgiGauge("NUMBER=", "Produce numbers from -1 to +1", 200, -1.0, 1.0);
   menu1 = GgiMenu("DRINK=","Order your drink", drinks);
   menu2 = GgiMenu("FOOD=","Order your food", foods);
   
/*
 *    Modify some elements' properties.
 */
   (void)GgiSetLabel(text1, "Number", 60); /* change default keyword label */
   (void)GgiSetLabel(text2, "Getal", 60);  /* change default keyword label */
   (void)GgiSetLabel(gauge, " ", 1);       /* suppress gauge's keyword label */
   
/*
 *    Position the elements.
 */
   GgiSetPosition(button, 0, NULL,   0, NULL);              /* top left */
   GgiSetPosition(workbt, 0, button, 0, NULL);              /* right to button */
   GgiSetPosition(text1,  0, NULL,  10, button);            /* below button */
   GgiSetPosition(text2,  0, NULL,   0, text1);             /* below text1 */
   GgiSetPosition(gauge,  0, text1, 10, button);            /* right of text1 */
   GgiSetPosition(menu2, -GgiWidth(menu2), gauge, 0, NULL); /* above gauge */
   GgiSetPosition(menu1, -GgiWidth(menu1)-GgiWidth(menu2)-10, menu2, 0, NULL);
                                                            /* left of menu2 */
  
/* 
 *    Show what has been created until now.
 */
   GgiRealize();
   
/* 
 *    Register keyword callbacks.
 */
   (void)ScheduleKeyevent(quit,   "QUIT=",   KEYCHANGE, NULL);
   (void)ScheduleKeyevent(number, "NUMBER=", KEYCHANGE, NULL);
   (void)ScheduleKeyevent(number, "GETAL=",  KEYCHANGE, NULL);
   (void)ScheduleKeyevent(order,  "DRINK=",  KEYCHANGE, drinks);
   (void)ScheduleKeyevent(order,  "FOOD=",   KEYCHANGE, foods);
   (void)ScheduleKeyevent(work,   "WORK=",   KEYCHANGE, NULL);
  
/*
 *    Put things into action.
 */
   MainLoop();
}  

Programming GIPSY Maintained by J. P. Terlouw