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.
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[]);
/**
,
tags embedded in them start with @
) if they are
documentation blocks, rather than mere comments, for use with
Doxygen.
None?
/* 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; }
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.
Prefix | Library |
---|---|
Me |
Utility libraries. |
Mdt |
MathEngine Dynamics Toolkit (aka KEA) |
Mcd |
MathEngine Collision Detection (aka Collision Lite) |
BodySetPosition
rather than
BodyPositionSet
.
typedef
for the
corresponding struct
name, with the same name.
void PkgInit(....)
void PkgUpdate(....)
,void PkgTerm()
PkgTypeID PkgCreateType(....)
void PkgDestroyType(PkgTypeID)
FieldType PkgTypeGetField(PkgTypeID)
void PkgTypeSetField(PkgTypeID,FieldType)
Mey
the prefix for MayFly?Mcg
OK?Mps
OK?/* 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); }
do {
and
} while (0)
sequences.
void
.
struct
definitions, which may then appear in the
API headers.
struct
definition and another
(which includes the first) with the macro that is actually the
published API.
Qua
functions.
#ifndef
, #define
, #endif
sequence against multiple inclusion.
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
const
should be used whenever meaningful.MdBodyIDtoPtr
, but this can as a rule only be
done in an implementation and platform dependent way, so it
must be protected by an #if
or #ifdef
.
GetField
) or mutator (e.g.
SetField
) functions, or macros/inlines (e.g.
GETFIELD
and SETFIELD
).
typedef
s, in
particular vector types, as we want to make sure that all
vectors and vector elements are integrally aligned on four
floats (128 bit) boundaries, at least optionally, for
machines that support 4-way SIMD code.
struct
s, all fields for the same
record contiguous in memory) or parallel (a
struct
of arrays, all values of the same field
contiguous in memory).
Qua
function
or inline that turns an ID to an instance of a derived class
into an ID to the instance of the base class embedded in it.
Qua
functions
or macros conver a derived class object ID to a base class
object ID, or both?
Qua
functions
always be functions to have type checking or can they be
macros?
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
_xxx_
for italics,
*xxx*
for bold, for example) for ASCII text are
recommended. Also "xxx"
for quoting text as
strings, 'xxx'
for referring to the programmatic
entity called "xxx", and $x$
for the mathematical
entity called "x". I also use ``
and
''
to indicate quoting in the natural language
sense, not the computer sense.
None?
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"''
These are notes on the most significant differences between different versions and revisions of this document.
delete
to destroy
in the
example code for consistency.Qua
function.
PkgTypeOpField
.
PkgTypeFieldOp
.