API style guide

updated 010413

This is primarily a style guide for published header files. There are also some recommendation applicable to how to do the implementation sources, as some will be published as open source.

Many of these recommendations that are rather uncontroversial and probably are already known to those that read this. Just for completeness...

The permission to expose the memory layout of objects has been partially withdrawn in this version. This is now not allowed.

There is a separate style guide for organising a project in a form that is easy to explain, build and package.

API header text layout

Example:

typedef float           CxReal;

struct CxPair
{
  void                  *data1;
  void                  *data2;
  void                  **aux;
};

typedef unsigned        (*CxIntersectTest)(CxOverlapInfoID info,
                          CxReal tm1[],void *data1,
                          CxReal tm2[],void *data2);

void                    CxObjectParamsGet(CxWorldID world,CxObjectID object);

unsigned                CxObjectUpdate(CxWorldID world,CxObjectID object,
                          CxReal bounds[]);

Indentation

Issues

None?

Examples

/* Indentation GNU style with GNU style comment.
   GNU style is as a rule step 2. */

typedef struct MdBody   MdBody;
typedef struct MdBody   *MdBodyID;

MdBodyID MdBodyCreate()
{
  MdBody *b = (MdBody *) (*MemoryAPI.create)(sizeof (MdBody));

  if (b != 0)
    {
      (*MemoryAPI.incRef)(b);
      {
        b->mass = 0.0f;
        b->density = 1.0;
        MmVec3Set(b->currentPos,0.0f,0.0f,0.0f);
      }
      (*MemoryAPI.decRef)(b);
    }

  return b;
}

/*
    Indentation K&R style with UNIX V7 comment style.
    K&R style is as a rule step 4 (yes, from edition 2).
*/

MdBodyID MdBodyCreate()
{
   MdBody *b = (MdBody *) (*MemoryAPI.create)(sizeof (MdBody));

   if (b != 0) {
       (*MemoryAPI.incRef)(b);
       b->mass = 0.0f;
       b->density = 1.0;
       MmVec3Set(b->currentPos,0.0f,0.0f,0.0f);
       (*MemoryAPI.decRef)(b);
   }

   return b;
}

/**
 * Create a new body.
 * The newly created bdy is allocated using the memory manager API,
 * and its fields are initialised to some default @e neutral values.
 * @return The ID of a newly created, initialised body.
 * @internal 
 * Indentation BSD style (with javadoc style comment).
 * BSD style is as a rule step 8.
 */

MdBodyID
MdBodyCreate()
{
        MdBody         *b = (MdBody *) (*MemoryAPI.create)(sizeof (MdBody));

        if (b == 0)
                abort();
        else
        {
                (*MemoryAPI.incRef)(b);
                b->mass = 0.0f;
                b->density = 1.0;
                MmVec3Set(b->currentPos,0.0f,0.0f,0.0f);
                (*MemoryAPI.decRef)(b);
        }

        return b;
}

Identifier construction

The names of header files should be built in the same way as identifiers. In effect the name of an header file should be the common prefix of the thingies declared in it.

Issues

Examples

/* Global name, so uppercase, also because it begins with
   package prefix. */
struct MuMemoryAPI
{
  void                  *(*create)(size_t bytes,...);
  void                  (*incRef)(void *);
  void                  (*decRef)(void *);
  void                  (*destroy)(void *);
};

/* Arguably this is global, because it is file global.
   Local is local to a function, or a struct. */
static struct MuMemoryAPI  MemoryAPI;

static void MuMemoryNoop(void *)
{
  continue;
}

typedef struct MdBody   MdBody;
typedef struct MdBody   *MdBodyID;

struct MdBody
{
  /* field names are local to the 'struct' and thus
     begin with a lower case letter. */
  float                 mass;
  float                 density;
  MmVector3             currentPos;
};

/* Method name in upper case ("SETMASS"), this is a macro */

#define MdBodySETMASS(b,m)      ((b)->mass = (m))

int main()
{
  MemoryAPI.create = malloc;
  MemoryAPI.destroy = free;
  MemoryAPI.incRef = noop;
  MemoryAPI.decRef = noop;
}

MdBodyID MdBodyCreate()
{
  MdBody *b = (MdBody *) (*MemoryAPI.create)(sizeof (MdBody));

  (*MemoryAPI.incRef)(b);
  {
    b->mass = 0.0f;
    b->density = 1.0;
    MmVec3Set(b->currentPos,0.0f,0.0f,0.0f);
  }
  (*MemoryAPI.decRef)(b);

  return b;
}

void MdBodyDestroy(MdBodyID b)
{
  (*MemoryAPI.destroy)((void *) b);
}

Pragmatics

Issues

Examples

Simple examples:

/*
  It is arguable, but the order of parameters here is 'to','from','n'
  because MemoryCopyDouble(void *to,void *from [, 8]) is useful, and
  MemoryZeroPage(void *to [,pageOfZeros,4096]) is too, thus we conclude
  that 'n' is more specific than 'from' which is more specific than
  'to'. As 'to' is the memory object being operated upon, so it should
  come first indeed; in effect "MemoryCopy" is a misnomer and the
  function should be called "MemorySet".
*/

void                    MemoryCopy(void *to,void *from,unsigned n);

To provide accessors/mutators for an object we usually do normal functions. This can be implemented with a C++ header called Thing.hxx like:

#ifndef THING_HXX
#define THING_HXX
class Thing
{
  float                 p1;
public:
  float                 m1;
  void                  f1();
};
#endif

and a C++ wrapper source called ThingAPI.cxx like:

#include "Thing.hxx"

extern "C" float ThingGetM1(const ThingID b)             { return b->m1; }
extern "C" void  ThingSetM1(ThingID b,const float v)     { b->m1 = v; }
extern "C" void  ThingF1(ThingID b)                      { b->f1(); }

Here there is a function call overhead on every access to m1. This can be obviated by splitting the C++ header in two parts; one a C header called ThingPub.h like:

#ifndef THINGPUB_H
#define THINGPUB_H
struct ThingPub
{
  float                 m1;
};
#endif

and another still called Thing.hxx like:

#ifndef THING_HXX
#define THING_HXX

#include <ThingPub.h>

class Thing: ThingPub
{
  float                 p1;
public:
  void                  f1();
};
#endif

At this point one can deliver as API the ThingPub.h header above and the public C API header called Thing.h like:

#ifndef THING_H
#define THING_H

/*
  Including this allows the user to use directly the
  fields of the 'struct' if necessary
*/
#include <ThingPub.h>

typedef struct ThingPub Thing;

/*
  Otherwise the user can use the inline functions or
  macros if s/he is wise.
*/
#if (__GNUC__) || (_MSC_VER)
  static __inline float ThingGetM1(const ThingID t)           { return t->m1; }
  static __inline void ThingSetM1(ThingID t,const float v)    { t->m1 = v; }
#else
# define ThingGetM1(t)                                ((t)->m1)
# define ThingSetM1(t,v)                              ((void) ((t)->m1 = (v)))#endif

#endif

Semantics (or what?)

Issues

Examples

Some simple issues:

#if (MeArchSIMD)
  typedef float         MeVector3[4];
#else
  typedef float         MeVector3[3];
#endif

typedef struct MdBody   *MdBodyID;

#define MdBodyMAX       800

static struct MdBody    MdBodies[800];

/* Converting an ID to a pointer is easy */
#define MdBodyIDTOPTR(b) (b)

void                    MdBodyMove(MdBodyID b,MeVector3 pos);
void                    MdBodyMoveN(MdBodyID b[],MeVector3 pos[],
                          unsigned n);

Let's now change the memory layout of out pool of bodies, and use as object handle an integer ID:

/*
  Undocumented internal representation.
*/
#define MkBodyCLUSTLOG  2
#define MkBodyCLUSTMASK 3

struct MkBodyClust
{
  float                 mass[1<>MkBodyCLUSTLOG].free[b&MkBodyCLUSTMASK]);

    return MkBodies[b>>MkBodyCLUSTLOG].mass[b&MkBodyCLUSTMASK];
  }
#else
# define MkBodyGETMASS(b,m) \
  (0.0f + MkBodies[(b) >> MkBodyCLUSTLOG].mass[(b) & MkBodyCLUSTMASK])
#endif

extern MeFloat          MkBodyGetMass(const MkBodyID b);

Part of the implementation may then look like:

#include "MkBody.h"

MkBodyClust             *MkBodies;
static unsigned         MkBodiesMax = 0;

void MkBodyInit(const unsigned bodies)
{
  MkBodies = (MkBodyClust *) MemoryCreate(bodies>>MkBodyCLUSTLOG
                                          * sizeof (struct MkBodyClust));

  if (MkBodies == 0)
    return;

  MkBodiesMax = bodies;

  {
    register unsigned i;

    for (i = 0; i < MkBodiesMax; i++)
      MkBodies[i>>MkBodyCLUSTLOG].free[i&MkBodyCLUSTMASK] = 1;
  }
}

MkBodyID MkBodyCreate()
{
  register unsigned i;

  for (i = 0; i < MkBodiesMax; i++)
  {
    register char *const f
      = &MkBodies[i>>MkBodyCLUSTLOG].free[i&MkBodyCLUSTMASK];

    if (*f != 0)
    { 
      *f = 0;
      return i;
    }
  }

  return (MkBodyID) -1;
}

MeFloat MkBodyGetMass(const MkBodyID b)
{
  assert (b < MkBodiesMax
          && !MkBodies[b>>MkBodyCLUSTLOG].free[b&MkBodyCLUSTMASK]);
  return MkBodies[b>>MkBodyCLUSTLOG].mass[b&MkBodyCLUSTMASK];
}

MeFloat MkBodyGetMass(MkBodyID b)
{
  assert (b < MkBodiesMax
          && !MkBodies[b>>MkBodyCLUSTLOG].free[b&MkBodyCLUSTMASK]);
  return MkBodies[b>>MkBodyCLUSTLOG].mass[b&MkBodyCLUSTMASK];
}

void MkBodyDestroy(MkBodyID b)
{
  assert (b < MkBodiesMax
          && !MkBodies[b>>MkBodyCLUSTLOG].free[b&MkBodyCLUSTMASK]);
  MkBodies[b>>MkBodyCLUSTLOG].free[b&MkBodyCLUSTMASK] = 1;
}

void MkBodyExit()
{
  assert (MkBodies != 0 && MkBodiesMax != 0);
  MemoryDestroy(MkBodies);
  MkBodiesMax = 0;
}

Now let's consider a C++ implementation snippet that uses inheritance like:

class Base
{
  float                 p1;
public:
  float                 m1;
  void                  f1();
};

class DerivedA: Base
{
  int                   p2;
public:
  int                   m2;
  void                  f2();
};

class DerivedB: Base
{
  char                  p3;
public:
  char                  m3;
  void                  f3();
};

We can then produce a flattened out C API as in:

typedef struct Base     *BaseID;
float                   BaseGetM1(BaseID b);
float                   BaseSetM1(BaseID b,float v);
void                    BaseF1(BaseID b);

typedef struct Derived1ID Derived1ID;
float                   Derived1GetM1(Derived1ID b);
float                   Derived1SetM1(Derived1ID b,float v);
int                     Derived1GetM2(Derived1ID b);
int                     Derived1SetM2(Derived1ID b,int v);
void                    Derived1F1(Derived1ID b);
void                    Derived1F2(Derived1ID b);

typedef struct Derived2ID Derived2ID;
float                   Derived2GetM1(Derived2ID b);
float                   Derived2SetM1(Derived2ID b,float v);
char                    Derived2GetM3(Derived2ID b);
char                    Derived2SetM3(Derived2ID b,char v);
void                    Derived2F1(Derived2ID b);
void                    Derived2F3(Derived2ID b);

The alternative is to use Qua functions:

typedef struct Base     *BaseID;
float                   BaseGetM1(BaseID b);
float                   BaseSetM1(BaseID b,float v);
void                    BaseF1(BaseID b);

typedef struct Derived1ID Derived1ID;
BaseID                  Derived1QuaBase(Derived1ID d1);
int                     Derived1GetM2(Derived1ID d1);
int                     Derived1SetM2(Derived1ID d1,int v);
void                    Derived1F2(Derived1ID d1);

typedef struct Derived2ID Derived2ID;
BaseID                  Derived2QuaBase(Derived2ID d2);
char                    Derived2GetM3(Derived2ID d2);
char                    Derived2SetM3(Derived2ID d2,char v);
void                    Derived2F3(Derived2ID d2);

A Qua function takes as parameter a pointer to a type logically derived from a base type, and returns a pointer to the base type (portion of the logically derived object). Thus it allows to take advantage of polymorphism from the C side of things.

There are three plausible implementations of the Qua functions, one in the C++ implementation of the class code:

extern "C" BaseID Derived1QuaBase(Derived1ID d1)  { return d1; }
extern "C" BaseID Derived2QuaBase(Derived2ID d2)  { return d2; }

The other two can be done in the API C header files:

#if (__GNUC__) || (_MSC_VER)
  static __inline BaseID Derived1QuaBase(Derived1ID d1) { return (BaseID) d1; }
  static __inline BaseID Derived2QuaBase(Derived2ID d2) { return (BaseID) d2; }
#else
  /* No type checking unfortunately */
# define Derived1QuaBase(d1) ((BaseID) (d1))
# define Derived2QuaBase(d2) ((BaseID) (d2))
#endif

Appendix: API mail, news and web style

Issues

None?

Examples

I *strongly* suggest that the _identifier_ that names a variable be
indicated as "ident", while the variable itself be indicated as
'ident'. However it is true that normally ``everything goes''.

One can then say that the the value of 'ident' should be $sqrt(log(n))$, 
while one can also say that the variable called "ident" probably is a
_local_ variable given the rule ``local names begin with a lowercase
letter like "a"''

Version history

These are notes on the most significant differences between different versions and revisions of this document.

Version 4, 2001/04/13
    Added the notion of revealing object handle implementation types as discussed on the API mailing list long ago.
  • Removed the demented demand that all object handles be implemented as pointers even if they are not pointers.
  • Changed delete to destroy in the example code for consistency.
Version 3, 2000/06/02
  • Revised according to Manny's suggestions.
  • Put the section on formatting text for email, news and comments to the end as an appendix.
  • Added explanation of what is a Qua function.
Version 3, 2000/05/17
  • Added pointers to the Doxygen documentation and to the guide on how to write JavaDoc style comments.
  • Some cosmetic/typo fixes.
Version 3, 2000/02/23
  • Object handles are opaque IDs, which are usually pointers, and sometimes integers. Object attributes are accessed via accessor/mutator functions or macros.
  • In explicitly authorised cases like KEA 1, the memory layout of an object can be documented, but accessor/mutator functions or macros/inlines must still be provided, and their use encouraged.
Version 3, 2000/02/15
  • Standard operations names have now been defined.
  • A note added to make sure that header file names have the same style as identifiers.
  • The Issues sections has been moved to before rather than after the Examples sections.
  • The names of prefixes for dynamics and collision have now been defined.
  • Added as issues the prefixes for cloth and particles.
  • Added recommendation for Doxygen with JavaDoc style comments.
Version 2, 2000/01/27
  • Object handles are pointers, and the memory layout of objects as a rule should not be documented, and only accessor/mutator functions or macros should be used.
  • In explicitly authorised cases like KEA 1, the memory layout of an object can be documented, but accessor/mutator functions or macros/inlines must still be provided, and their use encouraged.
  • More advice on defining macro bodies.
  • Added naming of common operations as an issue.
Version 2, 2000/01/21
  • Object handles are pointers, and the memory layout of objects can be documented and used, and/or accessor/mutator functions or macros can be used.
  • Member function names should be like PkgTypeOpField.
Version 1
  • Object handles are opaque, and the memory layout of objects must be too, and all object properties must have accessor/mutator functions or macros.
  • Member function names should look like PkgTypeFieldOp.