Develop modular, clean and document your work.
- No spaghetti code
- No code duplication
- No overengineering
- Refactor your work
- Use comments
- Other stuff, like e.g. Sauerbraten Uniques
Inexor C++ code uses four spaces for indendation. No tabs. The CMakeFiles use two spaces for indendation. No tabs. We never use tabs.
There should be a new line at the end of files, but no empty lines.
Lines should not have any trailing whitespace, that is any whitespace just before the new line.
Detecting whitespace errors is pretty hard, but git provides a few tools to help us detect those.
# Mark empty lines at the ends of files, trailing spaces # and tabs used for indentation $ git config core.whitespace blank-at-eof,blank-at-eol,tab-in-indent # Mark them RED $ git config color.diff.whitespace "red red" # Always output colored diffs $ git config color.diff always
And here is how you can inspect your code:
# Show the history of commits with their associated, colored diffs $ git log -p --color # Check for errors in the working tree $ git diff --color # Check for errors in the stage $ git diff --color --cached # After rebasing your branch, check your entire branch for space errors $ git diff master
This way you won't be able to detect tabs in the middle of lines and bad indentation (using 3 spaces where 4 should be used). Bad indentation though is clearly visible to the eye and should just be taken care of by looking.
Namespaces: Separation of Code and Code
Generally, the code should be divided into sensible units. These units should have a somewhat sensible API and often provide their own namespace. Each unit must be documented sensibly.
The root of the Inexor core code is the inexor/ dir in the repo. It's namespace is
The root should mostly contain modules; not actual sources.
Modules are sets of types, functions and classes that are sort of working together to the same end. Modules normally reside inside the source root, but if it really makes sense, modules may be nested.
The name of the folder and the name of the namespace should be the same:
inexor/graphics <-> inexor::graphics
Each module must provide a readme.md file, describing the purpose and the contents of that module and they should provide a header with the same name as the module, which can be used to include the module as a whole (normally by including all other headers in the module).
inexor $ ls graphics/ readme.md graphics.h ...
The util Module
There is a module called util; this is somewhat special; In some cases you will create a rather generic utility, which does not really belong to the current module, but that functionality is still so small, that it does not make sense to create a new module for it.
In such cases the functionality might be added to the util module.
An example of such a case is the uuidgen function.
There are, of course, two kinds of files: source files and
headers. These should always be paired, with the header
containing declarations and API documentation and the source
file containing implementations.
Header and source always share the same basename; for the extensions ".cpp" and ".h" must be used.
inexor $ ls graphics/ readme.md graphics.h skybox.cpp skybox.h ...
These source files should normally not exceed 1000 lines of code. Often it is beneficial if one source pair contains a single class or so. If a file represents a class, it should generally be called the same as the class itself; in this case, PascalCase should be used.
inexor $ ls graphics/ readme.md graphics.h skybox.cpp skybox.h CellRenderer.cpp CellRenderer.h ...
For source pairs that do not represent a single class or struct, snake_case names should be used. In such cases it might be beneficial to have a separate namespace for the file (classes/structs are sort of namespaces).
skybox.cpp, skybox.h -> inexor::graphics::skybox
char *pointer; instead of
Includes within Inexor should be relative to source:
#include "rpc/rpc.h" #include "net/MessageConnect.h" using namespace inexor::rpc;
Large parts of the Inexor codebase are not using strings but only char arrays (limited to 260 chars). For new code it is preferred to use std::string, however this might not be possible in large parts of Inexor. Here are the basics how Sauer deals with strings:
typedef char string in tools.h
defformatstring(stringname) ("Example %s %d and %u", char *a, int b, uint c);
string _stringname_and formats the second expression
a = "Values", b = -2, c = 5will lead to
"Example Values -2 and 5"
formatstring(stringname) (see above);
does not define
formatting works as above
Large parts of the codebase are using standard types (maps, vector, list) which are implemented in shared/tools.h. In some parts of Inexor, standard types can be used, but for the most, we are going to run into name collisions.
Vectors are extended arrays, which provide numerous abilities. Sauerbraten's vectors are not those of the vector-class in c++.
Since vectors (including the std:: ones) provide a continuous memory segment, large bits of the codebase use vectors as string builders.
Create them with
Add a variable with
The new Entry will be at the last place.
Use an entry with
Remove an entry with
This will also resort the vector (the next entry will take i's position).
Clear the vector with
or (if you do not want to delete its contents)
Length receive the amount of entries in that vector with
int len = vectorname.length();
bool hasi = vectorname.inrange(i);tells you whether
vectorname[i]is actually a thing. This could not be the case if e.g. i is below zero or higher than the amount of entries.
Sauer provides it's own kind of loop short cuts; these are macros which, basically all wrap a for loop.
for (int R=0; V < X; V++)
There is also a variant with hardcoded iterators; these are often used to nest loops.
loopi(X) -> for (int i=0; i < X; i++) loopj(X) -> for (int j=0; j < X; j++) loopk(X) ... loopl(X)
Similar constructs also exist for looping backwards:
loopirev(X) -> for (int i=X; i-- >= 0;)
There are some similar named but quite different loops; this one loops over all indices "i" of an Inexor vector "V":
This is a bit tricks; from the constructs above you would expect that this loops until V with a iterator variable v.
These loop constructs are a bit unsafe, because they always cast to int and they hide variable declaration.
In many cases it's safer to use C++11's foreach, iterators or plain old loops.