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