Skip to content

Project Structure & Coding Conventions

Manuel M. edited this page Feb 24, 2015 · 3 revisions

Note: Code samples on this page do not represent any valid code present in the project's sources, it is all made up.

General Naming Conventions

  • Prefer camelCase wherever possible: helloThisIsACamel
  • When incorporating an abbreviation in a name, force it to be camelCase: JsonLoader instead of JSONLoader
  • Prefix names with the global namespace prefix if necessary (e.g. preprocessor #defines)

Project Structure

Supported Architectures

Currently the project is 64 bit only. Support for 32 bit Architectures is not planned.

Directories

+- code                // contains all source code
  +- app               // some project
    +- PCH.h           // precompiled header file
    +- something.h     // header file
    +- implementation  // contains implementation details
      +- something.cpp // the main translation unit including something.h
  +- engine            // another project
    +- ...             // more files...
+- docs                // contains misc documents, documentation, ideas, etc.
  +- readme.txt        // simple read-me
+- ???

C++ Source Code

All source code must reside in a subdirectory below code/. Any kind of source file located directly inside the code/ folder is forbidden.

Include Directives

They're used to include other files, typically header files (.h or .hpp).

Whenever you #include a file that is not in the same directory as the current file, use angle brackets <> instead of quites "" for the file path. See the rules for how the two different forms of #include behave for different compilers:

In short: The quoted form searches locally first, then in all directories specified by some /I or -I option to the compiler. Since we basically never refer to our included files in the local form, we might as well just use the angle-bracket form.

I.e. this is the recommended way:

#include <krEngine/renderin/window.h> // Use this form for all regular headers.
#include "localFile.inl"              // Use this form for special files only.

Include Guards

Include guards guard against multiple inclusions of the same file for each compilation unit (.cpp).

All our target compilers support pragma once so we'll use that. There is no need for the old #ifdef ABC #def ABC #endif convention used in ancient C and C++. All header files should usually begin with #pragma once:

#pragma once

namespace kr
{
  struct Something {};
}

Prefix/Precompiled Headers

We use a manually maintained prefix header located at code/krEngine/pch.h. Our build system uses cotire which automatically include the prefix header for every translation unit. In project that do not use cotire, your translation units (the .cpp files) usually looked like this:

// Include precompiled header
#include "krEngine/stdafx.h"

// Include other headers.
#include "krEngine/rendering/window.h"
// ... more includes ...

kr::Window::Window()
{
}
// ... more code ...

In our project, this is no longer necessary! Since cotire takes care of it automatically, never include the prefix header manually!

Bracing & Indentation

We are using a variant of the Allman Style for indentation and bracing. In this style, every opening brace stands on a new line for itself, aligining properly with its closing couterpart. Indentation is done with 2 whitespaces, not tabs. if, while and for statements are allways followed by braces, even if only one statement is executed. Always prefer clearity and security of code over length of code.


static char * concat (char *s1, char *s2)
{
  while (x == y)
  {
    something();
    somethingelse();
  }

  if (theThing())
  {
    finalthing();
  }
}

Namespaces

The Krepel Namespace is called kr. This is the main, global namespace and all code should reside in there.

int main(int argc, const char *argv[])
{
    kr::String message("Hello world.");
    KR_assert(message.GetData() != nullptr, "...");
    kr::Log("Message: %s, %d", message.GetData(), KR_COMPILETIME_MIN(1, 2));
    return 0;
}

Other namespaces may only be defined within the kr namespace:

// Good:
namespace kr {
  namespace internal {
    struct ScopeExit { /* ... */ }; // some code that is not supposed to be used directly by the user.
  }
}
// Bad:
namespace kr {}
namespace internal {} // Should be inside `kr`

Variables

Even though the ezEngine uses a light-weight version of the Hungarian Notation, we do not use it. Variable names are always in lower-camelCase.

void doSomething(const ezVec3& startPosition) // start is lower-case
{
  ezVec3 endPosition(0, 1, 0);
  auto rayDirection = endPosition- startPosition;
}

Use descriptive names for your variables, such as direction instead of d.

Documentation

In order to be able to generate a source code documentation later on, we are documenting stuff in header files using doxygen commands, which /// for each line of documentation.

/// \brief Calculates the factorial of \a n
/// \example factorial(5); // yields 720
int factorial(int n);

C/C++ Preprocessor

ALL preprocessor #defines, with no exceptions, are given the prefix KR_ and are continued with UPPER_CASE (not camelCase or PascalCase).

#define KR_EMPTY_PUBLIC_CONSTRUCTOR(TheTypeName) public: TheTypeName() {}
#define KR_VERSION_MAJOR 0
#define KR_VERSION_MINOR 1

Indentation

Preprocessor statements are formatted like source code, thus nested statements are indented with two spaces:

#ifdef WIN32
  #ifdef KR_EXPORT
    #define KR_API __declspec(dllexport)
  #else
    #define KR_API __declspec(dllimport)
  #endif
#else
  #define KR_EXPORT
#endif

Multi-Line Statements

Multi-line statements are formatted like normal source code but whith the trailing backslashes aligned on the same column:

// 2: align backslashes in one column
#define KR_UNIT_TEST(TheGroup, TheName)    \
class TheGroup##TheName : public IUnitTest \
{                                          \
public:                                    \
  void run() override final;               \
};

CMake

Naming

Everything that can be named must have the kr_ or KR_ prefix, unless it is a temporary variable limited to the current scope. This should assure clarity of the origin of variables and the likes.

Since the CMake parser is not case-sensitive, we use lower-cased words separated by underscores for callables i.e. macros or functions: kr_log, kr_library, kr_add_source

Logging and Errors

For logging, use the function kr_log, which takes 2 arguments: verbosity and message. A higher value of verbosity means the message is less likely to be printed, controlled by the global variable KR_VERBOSITY, which is a cached string and can be set by the use when configuring CMake. The default value for KR_VERBOSITY is 0, so if you want to log a message that is always show, use kr_log(0 "hello world").

The logging output can be indented by calling kr_indent_log_prefix(" "), which will indent the log prefix by two spaces. By default, the log prefix is set to "[kr] ", which allows us to quickly identity our own log messages. Note: the indentation will only last for the current scope. This behavior can be exploited to group several log messages together, similar to EZ_LOG_BLOCK.

To report errors, that will not stop CMake entirely, use kr_error("my error message"). This is equivalent to regular CMake: message(SEND_ERROR "my error message").

To abort CMake parsing entirely, use kr_fatal("my error message"). This is equivalent to regular CMake message(FATAL_ERROR "my error message") and is used when our general config detects an unsupported platform.

General

Development

Game Design

Clone this wiki locally