Appendix D. Fathammer Code Conventions & Guidelines

The following appendix presents Fathammer's coding conventions and guidelines. Most of them are recommended for all X-Forge developers and some are stylistic issues.


Fathammer Code Conventions & Guidelines
=======================================


Table of Contents


1 Naming and Types
1.1 Types
1.2 Cases
1.3 Prefixes
1.4 File Names and Directories
2 Code Guidelines
2.1 Using Constants and Consts
2.2 Code Formatting
2.3 Pitfalls To Avoid
2.4 Other Advices


------------------------------------------------------------------------------


1 Naming and Types


1.1 Types


Primitive types have alias names:
        INT8, INT16, INT32, INT64, 
        UINT8, UINT16, UINT32, UINT64, 
        FLOAT32, FLOAT64, FIXED, REAL,
        INT, UINT, CHAR, CHAR8, CHAR16
Mapping to actual types varies with platform but here is an example:
        typedef signed char INT8;
        typedef signed short INT16;
        typedef signed long INT32;
        typedef signed long long INT64;
        typedef unsigned char UINT8;
        typedef unsigned short UINT16;
        typedef unsigned long UINT32;
        typedef unsigned long long UINT64;
        typedef float FLOAT32;
        typedef double FLOAT64;
        typedef Fixed FIXED;
        typedef FIXED REAL;
        typedef INT32 INT;
        typedef UINT32 UINT;
        typedef char CHAR8;
        typedef short CHAR16;
        typedef CHAR8 CHAR;
                or
        typedef CHAR16 CHAR; (depending on platform)

Plain INT is considered to be the fast integer on all platforms. INT is
guaranteed to be at least 16 bits (like standard 'int' in C).

For boolean values use type of INT with values 0 and 1 when the size is
not an issue (e.g. a return value of method). If size is an issue and speed
is not that relevant, use UINT8 or bitfields like UINT8 b:1; in a struct
if you have other smaller members to fill the space too.


1.2 Cases


Classes and structs are written in lower case except first letter of each
word is written in upper case.
        Example: ClassesLikeThis
Variables and methods are named like classes but first letter is always
in lower case.
        Example: variablesLikeThis
        Example: INT methodsLikeThis(UINT32 aFlags)
Constants and typedefs are written in full uppercase separating words
with underscores.
        Example: CONSTANTS_LIKE_THIS


1.3 Prefixes


Prefix of core, engine and util classes are "XFc", "XFe" and "XFu"
respectively.
        Examples: class XFcFile, XFeNode, XFuXMPlayer
Member variables are prefixed with small m.
        Example: INT mActive;
Method/function arguments are prefixed with small a.
        Example: void method(UINT32 aFlags)
Prefix for global symbols (includes functions and data) is "xfc",
"xfe" or "xfu" like with class prefixes.
        Examples:
        extern CHAR *xfcProgName;  (platform specific)
        extern "C" void xfcClearFramebuf(...args...);


1.4 File Names and Directories


File name for each class is the same as class name with extensions
'.cpp' and '.h'.
Example: file names for class XFcFile are "XFcFile.cpp" and "XFcFile.h".

Some classes have both common and platform specific code, in that case
filenames are like "XFcFile.cpp", "XFcFile.h" and "platform_XFcFile.cpp".
The "platform_XFcFile.cpp" is located naturally in the corresponding
directory under platform/.

Platform specific parts of software are separated to their own directories
(epoc, wince, linux, ...). These directories should lie under the specific
project's "platform"-directory. Build environment for each platform by the
platform name should be in "build" directory which is in same level with the
"source" directory. For library projects, headers should be separated to
"include" directory which is in the same level with "source". If part of
files are public (seen by other than in-house people, e.g. most of xfcore
headers), then the internal part of files are separated to an "internal"
subdirectory.
Example of part of directory structure:
        (REP means the path to local copy of code repository)
        REP/project/include
        REP/project/source
        REP/project/source/platform/linux
        REP/project/build
        REP/project/build/linux
        REP/project/documentation
In the example case REP/project/include is added to the project build
environment as include path.

In case of the actual X-Forge product the directory structure has also
subprojects showing as xfcore, xfengine and xfutil directories under the
main level source, include, build and documentation -directories.

Source includes must be written like
        #include <xfcore/internal/XFcFoo.h>
instead of just #include <XFcFoo.h> - that is, include path should
include only the root of the include directory.

Remember that all file names are case sensitive and separator is
always forward slash ('/').
        Examples:
        #include <internal/xfcfoo.h>            BAD
        #include <xfccore.h>                    BAD
        #include <util\xfuxmplayer.h>           BAD
        #include <xfcore/XFcCore.h>             GOOD
        #include <xfcore/internal/XFcFoo.h>     GOOD


2 Code Guidelines


2.1 Using Constants and Consts


Enumerations are preferred over static constants which are preferred
over defines. Do not pollute namespace with too general names for
the enumeration itself.

Constant flag names must be prefixed according to context. For example
with function enumerateDisplayFormats, some of the flags could be
EDF_PORTRAIT and EDF_LANDSCAPE. In core platform classes also the
general prefix should be used. Example:
        enum XFCGLVERTEXFLAGS
        {
            XFCGLVF_XYZ = 1,
            XFCGLVF_RHW = 2,
            // ...
        };

Use consts where it is clearly advantageous or necessary for correct
semantics for calling code (e.g. passing const variables as arguments).


2.2 Code Formatting


Indentation unit size is 4 spaces. Do not use tab characters. Source files
should look the same with any tab size since tabs are not used. It is
recommended that you also configure your editor to use tab size of 8 spaces
instead of 4 to avoid some indentation mistakes.
Configuring Emacs (add to your .emacs):
        (setq indent-tabs-mode nil)
        (defun fixed-c-mode-common-hook ()
          ""
          (c-set-style "stroustrup")
          (setq indent-tabs-mode nil)
          (c-set-offset 'case-label '+)
          (setq c-basic-offset 4))
        (add-hook 'c-mode-common-hook (function fixed-c-mode-common-hook))
        (global-set-key "\C-m" 'newline-and-indent)
Configuring Microsoft Visual C++ / eMbedded Visual C++:
        Tools->Options->Tabs:
        File type = C/C++
        Tab size = 8
        Indent size = 4
        (*) Insert spaces
        Auto indent = Smart
Configuring Metrowerks CodeWarrior:
        Edit->Preferences->Editor->Font & Tabs
        Tab Size: 4
        (*) Tab Inserts Spaces        
Keep the length of source lines under 120 characters.

Note that preprocessor lines (start with '#') in our public header files in
"need" of splitting with '\' are an exception to this. When the headers are
checked out from CVS on Windows for the distribution package and these
headers are used on Linux, gcc refuses to compile the splitted lines because
there's extra garbage on the line after '\' character since Windows enter
is CRLF and on Unix it is just LF. So splitting lines with '\' is *forbidden*
on distributed headers and source, so those lines are allowed to be longer
if it is necessary. For other code (internal headers and source)
'\'-splitting is ok since CVS handles the CRLF/LF conversion issues
transparently while developing.

Insert two empty lines before starting a function. Add empty lines in code
blocks to express logical units of operations.

All operators are surrounded by spaces.
Examples: x += 3; y = 0; z ^= 0xf;

All following rules concerning pointers and asterisks are applied to
references with ampersands aswell.

With pointers align the asterisk with variable name.
Example: char *x, y; instead of char* x, y;
Example: void XFuClass::test(UINT8 *aSomething, UINT32 &anotherThing)

If a method returns a pointer asterisk should be surrounded by spaces.
Example: INT * XFuTest::doSomething() { ... }

Asterisk should have a space infront when using pointer typecasts.
Example: test = (UINT8 *)malloc(1024);

Add a space after commas.
Example: INT a, b;

One space is added after if/for/switch/etc before opening parenthesis.
Example: while (1)

Braces are placed in a separate row. In case of very simple if-statement
the code can be placed just after the condition.
Examples:
        if (height < 0) return;
        if (x < 0 && y < 0 &&
            (flags & EDF_LANDSCAPE))
        {
        }
        else if (flags & EDF_PORTRAIT)
        {
        }

In nested for-loops each loop must define a new block.
Example:
        for (;;)
        {
            for (;;)
            {
            }
        }  

Do not use variable declaration inside for-statements. If you declare a
variable inside a for and use the same variable again later it generates
an error with gcc.
Examples:
        for (INT i = 0; ...)            BAD
        INT i;                          GOOD
        for (i = 0; ...)

Code for cases in a switch statement is indented. When doing a switch
for enumeration be sure to include a case for every member of the
enumeration or the default case. Supplying a default case (even an empty
one) is always recommended.
Example:
        switch (state)
        {
            case RUNNING:
                ++frame;
                break;
            case IDLE:
                // fall through
            default:
                break;
        }

If you choose to initialize class members using an initializer list in a
constructor, define the initializer list in same order than the class members
are introduced in the class definition. Declaring fields in wrong order
generates warning messages with gcc.


2.3 Pitfalls To Avoid


- Don't construct objects in-place as arguments to a method call, since gcc
  refuses to compile those.
  Example:
        setUpVector(XFcVector3(0, 1, 0));       BAD
        XFcVector3 up(0, 1, 0);                 GOOD
	setUpVector(up);
- Do not rely on sizeof() for structures to be the exact same amount compared
  to manually calculated count of bytes. Also member alignment of structures
  may vary. (Relates to the next one)
- Don't save/load contents of structures directly to/from a file, 
  instead handle each member separately. This is especially true for
  classes.
- Align structure members by the size of the field. For example, on some
  platforms uneven memory accesses of 2 bytes are silently rounded to even
  addresses!
- Try to keep stack usage as low as possible. Some supported platforms 
  have very limited stack size compared to other platforms.
- Class constructors are not allowed to fail. If construction an object 
  can fail e.g. due to unsuccessful memory allocation, instance creation
  must be done by creating a static create()-method in the class returning
  a pointer to constructed object and the actual constructor must be
  declared non-public. 
  Example:
          class XFuExample
          {
          private:
              void *mPrivateData;
              XFuExample();
          public:
              static XFuExample * create();
          };
          
          XFuExample::XFuExample()
          {
              mPrivateData = NULL;
          }
          
          XFuExample * XFuExample::create()
          {
              XFuExample *data = new XFuExample;
              if (data != NULL)
              {
                  data->mPrivateData = (void *)new INT8[0x242];
                  if (data->mPrivateData == NULL)
                  {
                      delete data;
                      return NULL;
                  }
              }
              return data;
          }


2.4 Other Advices


Multi-line /* */-style comments are recommended to be written by aligning
a column of asterisks on each line.
Example: /* this is
          * a multi-line comment
          */

Final code should not have commented out code rows. In some specific cases
commented out code can be left with text describing the reason for comment.
Examples:
        //r = (UINT8)((x * 0.9f) / 255.0f);                     BAD
        
        // slower but more accurate way                         GOOD
        // r = (UINT8)((x * 0.9f) / 255.0f);

Do not use multiple inheritance. Plain interface use is ok (Java-way).
Exceptions are possible with common agreement.

Commit only compilable code to the repository. List of files to compile
are usually built just by adding "all files in the dir" (either manually
or automatically) and it's not nice if one has to hand-pick several source
files off from the list which aren't yet in compilable state. If you need
version controlling for sources in pre-compilable/working phase please
create and use a personal subdirectory for your stuff in the
users-subdirectory in repository.
Exception: Changes to API depending on platform specific implementation may
break compilation on some platforms. If you can't fix all platforms, please
note developers maintaining other platforms about what should be changed and
how.

Code should compile without warnings on all supported platforms and
compilers. Below is a list of some mistakes which cause warnings on some
platforms and/or compilers:
- Do not leave unused variables to the code base.
- Do not delete[] void pointers. Include a typecast in those cases.
  Example: delete[] (UINT32 *)data;
- When explicitly using an assignment instead of equivalency comparison
  in if-statements, add extra parentheses.
  Example: if ((a = value & 0xf))
- For debug code with stdlib printf statements: add extra typecasts if
  the argument types are not the exact C standard primitive types.
  Examples: int a; fprintf(stderr, "%d\n", a);
            INT32 b; fprintf(stderr, "%d\n", (long)b);

Following recommendation is to ease creation of project build environments:
If some application project has a small side utility (e.g. creation of a
data file), do not put the utility source to the same directory with the
actual application but instead create a subdirectory for the utility.

Enclose all header files in #ifndef/#define/#endif block. 
Example: for file xfcore/XFcFoo.h the content should be as follows:
        (after the header doxygen comment block)
        #ifndef XFCFOO_H_INCLUDED
        #define XFCFOO_H_INCLUDED
        /* ... */
        #endif // !XFCFOO_H_INCLUDED