Application programmers' guide to ace displays

This is a prelimininary discussion document. New displays will be written according to this spec and old ones will gradually be converted.

We're discussing the window handling, the data structures, their names and their memory handling.

The primary ideas about an ace-display are :

  1. The window display the data of a specific object.
  2. All data calculated about the object is held in a datastructure.
  3. This data structure is attached to the window using graphAssociators.
  4. The memory of the datastructure has to be re-claimed if the window dies.
  5. The graph has to be cleared if the data-structure is destroyed.
    This may occur, if the handle upon which the datastructure was allocated is destroyed.
This is a model of a fictitious display for another Acedb Map :
      +--------------------------+
      |O| AMAP:dj9812          |X|
      +--------------------------+        +-----------------------------------+
      |          =               |        | struct AceMapStruct               |
      |           =_ _  gene     |------> | {                                 |
      |           = - =  unc-1   |        |   <standard struct-members>       |
      | dj9812    =_  =          |        |   <specific struct-members>       |
      |           _-  _          |        | }                                 |
      |           |   -          |        +-----------------------------------+
      |           -   =          |
      +--------------------------+

The way the window and the data-structure is created and destroyed needs standardization, some of that can hopefully be provided by a set of wrapper functions in the display-Package.

The layout of the data-structure and naming conventions :

Let's assume that the name for this display is "AceMap", another display along the lines of FMAP or GMAP.

Magic identifiers and associators

Every graph has an associator attached : an associator lets you register a void* upon a unique identifier. This unique identifier is another pointer-type, and we will use the address of a static string-constant. The unique address of that string is guaranted by the compiler, which during link-time will fit the constant string within the address-space of the program.
When the window is created and it's data-structure filled with appropriate information, the graphAssociator for the active graph will be registered to return the address of the data-structure if it is presented with the string's magic address on that window again.

The magic associator string for our example display would be declared like this :
static magic_t GRAPH2AceMap_ASSOC = "GRAPH2AceMap";

It is because C has very poor type-checking and no generic data-types, that the graphAss-package uses void* as the address to the window's data-structure.
The pointer returned by graphAssFind (upon the active graph) has to be type-cast from void* to the type of AceMapStruct. Pointer type-casts are always a dangerous thing to do, because the dereferencing of structures is just a very crude memory look-up in C.

Therefore, one of the <standard struct-members> is a magic identifier, which is used to verify that the void* pointer is what we expect. After that we know that the type-cast was safe and we can derefence the void* pointer based on the struct definition of AceMapStruct.

The magic identifier is again a unique pointer, generated by the compiler as the address to a static string constant. The magic id is declared in the following way :

static magic_t AceMap_MAGIC = "AceMap";
One of the standard ace-display struct members is ->magic which is assigned to &AceMap_MAGIC when the data-structure is created and filled with data.

Memory handling

Above we mentioned two goals about the memory handling of ace-displays. The graph should be destroyable by the usual graphDestroy() menu-action or a window-managers "CLOSE" signal and upon dying the memory of the data-strcuture needs to be reclaimed cleanly. On the other hand we want to be able to create an AceMapStruct and destroy it, which should clear up the graph.

The function creating the graph needs to create the data-structure and graphAssociate it with itself. The graph's destructor function needs to disassociate the data-structure, with the dying graph and clear its memory.

The function creating the data-structure needs to allocate the memory of the struct upon a given handle, register the finalisation function, assign it's magic identifier, assign the graph to be the one we've just opened and fill in all the data.

The window is created by the standard DisplayFunc, whose type is defined in acedb.h (should move to display.h) as

typedef BOOL (*DisplayFunc)(KEY key, KEY from, BOOL isOldDisplay) ;

A window that has a display-function of this type can be registered and configured using the wspec/displays.wrm configuration file.

The structure of the AceMap display module so far is :

/*******************************************/

static magic_t GRAPH2AceMap_ASSOC = "GRAPH2AceMap";
static magic_t AceMap_MAGIC = "AceMap";

typedef struct AceMapStruct
{
  /* standard display-struct members */
  magic_t *magic;		/* == &AceMap_MAGIC */
  STORE_HANDLE handle;
  Graph graph;
  KEY originKey;


  /* AceMap display struct members */
  Array exampleArray;
  char *exampleString;

} *AceMap;

/*******************************************/

static AceMap currentAceMap (char *caller)
     /* return AceMap structure associated with the active graph */
{
  AceMap amap;

  if (!graphAssFind (&GRAPH2AceMap_ASSOC,&amap))
    messcrash("%s() could not find AceMap on graph", caller);
  if (!amap)
    messcrash("%s() received NULL AceMap pointer", caller);
  if (amap->magic != &AceMap_MAGIC)
    messcrash("%s() received non-magic AceMap pointer", caller);
  
  return amap;
} /* currentAceMap */

/*******************************************/

static void aceMapFinalise (void *vp)
     /* Finalisation function for AceMap type */
{
  AceMap amap = (AceMap)vp;
  Graph old = graphActive ();

  if (graphActivate (amap->graph))
    {
      graphDestroy ();	       /* let the graph-destructor do the rest */
      graphActivate (old);
    }
  else
    handleDestroy (amap->handle);

  return;
} /* aceMapFinalise */

static AceMap aceMapCreate (KEY key, KEY from)
     /* Constructor function for AceMap type */
{
  AceMap amap = NULL;

  if (aceMapPossibleFor(key))
    {
      amap = (AceMap)messalloc(sizeof(struct AceMapStruct));
      blockSetFinalise (amap, aceMapFinalise);
      amap->magic = &AceMap_MAGIC;
      amap->handle = handleCreate();
      amap->graph = 0;
      amap->originKey = key;
      

      /* now the AceMap specifics */
      amap->exampleArray = arrayHandleCreate(10, amap->handle);
      amap->exampleString = strnew ("example", amap->handle);

    }

  return amap;
} /* aceMapCreate */


static void aceMapDestroy (void)
     /* graph-destructor for AceMap display */
{
  AceMap amap = currentAceMap ("aceMapDestroy");

  amap->graph = 0;		/* graph is dying already */
  messfree (amap);
} /* aceMapDestroy */


static void aceMapRefresh (void)
{
  AceMap amap = currentAceMap("aceMapRefresh");

  aceMapDraw (amap, 0);

  return;
} /* aceMapRefresh */


static void aceMapDraw (AceMap amap, KEY from)
{
  /* display object amap->originKey, but center on object 'from' */

  return;
} /* aceMapRefresh */


BOOL aceMapDisplay (KEY key, KEY from, BOOL isOldGraph)
     /* graph-constructor for AceMap display */
{
  AceMap amap;

  if (isOldGraph &&
      /* there is a window of type "AceMap already" */
      graphAssFind(&GRAPH2AceMap_ASSOC, &amap) &&
      amap ->magic == &AceMap_MAGIC &&
      amap->originKey == key)
    {
      /* That existing graph is already showing object we're asking for
	 just redraw for new position based on the 'from'-object */
      aceMapDraw (amap, from);
      return TRUE;
    }

  amap = aceMapCreate (key, from);

  if (!amap)
    {
      /* can't create an AceMap, no data or similar problem
       *  so we show the object in the text display instead */
      display (key, 0, TREE);
      return FALSE;
    }


  /* open new display of type "AceMap" */
  if (!displayCreate ("AceMap"))
    return FALSE;
  
  graphRetitle (name(key));
  graphRegister (RESIZE, aceMapRefresh) ;
  graphRegister (DESTROY, aceMapDestroy) ;

  graphAssociate (&GRAPH2AceMap_ASSOC, amap);
  amap->graph = graphActive();
  
  aceMapDraw (amap, from);

  return TRUE;
} /* aceMapDisplay */
      
/*******************************************/