Skip to content

AGS Coding Conventions (Cpp)

hit3nkuro edited this page Mar 29, 2022 · 3 revisions

The following are the coding conventions for C++ parts of AGS (this includes engine, script compiler, and limited parts of the editor which are still written in C++ as of 2019).

These definitions are not set in place as absolute rule, but as guidelines that should be followed to promote consistency and uniformity throughout the source code of the AGS run-time engine. They may be reviewed and reconsidered over time, but changes should have a good rationale behind them. In regards to coding style please remember that it primarily aims to make the code consistent and convenient to work with for any average person rather than match someone's personal preference or achieve ultimate perfection.

Formatting

First of all, runtime engine of AGS is an old program and consists of multiple historical "layers" of code built on top of each other. For that reason you may meet a lot of different code styles in there. When writing completely new code unit or doing major refactoring we'd ask you to follow our formatting convention. When doing smaller changes and fixes in the existing code please try to keep formatting consistent with the surrounding code (unless it's too bad) even if it does not match our convention: this is again to make it easier to review and work with the code.

Indentation and whitespace

We use spaces for indentation. Each level of indentation should increase by 4 spaces. There are specific cases where whitespace helps improve readability of code, beyond where it is required. Use common sense and discretion, but the following are some examples of proper whitespace usage.

Horizontal Whitespace

Horizontal whitespace should be used appropriately for indenting lines, and spacing out the code on a line. You should never end a line with trailing whitespace. Whitespace should also be used in the following ways:

  • Surrounding conventional operators, class inheritance, and ternary operators:
int a = 42;
a *= c + d;

class Derived : public Base
{
};

a = (true ? b : c);
  • To separate a C++ reserved keyword from an opening parenthesis:
if (true)
{
}
 
while (false)
{
}
  • After a comma:
    int a, b, c;
 
    function(a, b, c);
  • After a semicolon, if there is more on that line (including comments):
    for (int i = 0; i < 42; ++i)
    {
    }
 
    int id; // used to store the identification number

Vertical Whitespace

Vertical whitespace should be used sparingly to separate functions or other large blocks of code, to help maintain readability. You should not have several blank lines at the start or end of a function, or in-between statements, declarations, and so forth. Use discretion as necessary, but bear in mind that the goal is to make the code readable.

Each file should end with a blank line as some compilers require it or may warn if they do not.

Comments

Generally, line comments (//) should be preferred over block level comments (/* */) as block comments cannot be nested.

Braces

"Curly braces", { and }, should occupy a line by themselves (trailing comments are acceptable also). They should be at one level of indentation lower than the code they contain. This helps as a vertical guide (in addition to the indentation) in finding where a particular block of code starts or ends.

{ // opening brace
    // this code is inside the braces
} // closing brace

Curly braces may be omitted after conditional or loop statements if there is only one line of code in the following block. In case of combined if/else statement only omit curvy braces if each of the blocks consist of one line.

if (a == 0)
    DoSomething();

if (a == 0)
    DoSomething();
else
    DoSomethingElse();

for (int i = 0; i < 10; ++i)
    NextIteration(i);

// but...
if (a == 0)
{
    DoOnlyOneStatement();
}
else
{
    DoMultiplePart1();
    DoMultiplePart2();
}

In case of nested conditions or loops we also permit omitting braces but only if there are no more than two levels of nesting.

if (do_processing)
    for (int i = 0; i < 10; ++i)
        Process(i);

Please use discretion when omitting curvy braces and try to not overdo, especially if surrounded by complex code with lots of nesting.

Naming Conventions

The user types: classes, structs, enums and typedefs, - as well as global entities such as namespaces, global constants and global variables should be named using upper CamelCase notation. It is also suggested to begin global variables with 'g_' or 'gl_' prefix.

class AnimatedButton;
struct Rect;

typedef std::unordered_map<String, Object> MapOfObjects;

Bitmap *gl_VirtualScreen;

When creating aliases (typedefs) for standard containers and smart pointers you may add some kind of simple prefix indicating the aliased type, for example:

typedef std::shared_ptr<Bitmap> PBitmap;

typedef std::vector<int> VInts;

Enum's constants (not enum type itself) while being in CamelCase should be prefixed with letter 'k'. Constant names are also allowed to have two or more parts separated by underscores ('_'). Constants within same enum might share a common first word indicating their relation, for example:

enum TypeOfAction
{
    kAction_Default,
    kAction_Special,
    kAction_Other
};

Class methods and public data fields should also follow CamelCase notation. Private class fields should be named using lower camelCase and prefixed with an underscore ('_') to distinguish single-worded members from local variables (see further):

class MyClass
{
public:
    MyClass();
    ~MyClass();
    void PublicMethod();

    int PublicField;

private:
    void PrivateMethod();

    int _privateField;
};

Local variables and function arguments should be named in all lowercase with underscores separating parts of name when necessary (but not as a prefix).

void SomeFunction(int func_arg1, int func_arg2)
{
    int local_var = 0;
}

Names should be as descriptive as possible, without being overly verbose. Functions particularly should indicate what they actually do, so a name such as "GetID()" would be preferred over simply naming the function "ID()".

Namespaces

By convention, a namespace does not increase the indentation level. The namespace itself should have leading and trailing blank lines to help make this distinction clearer. For added clarity, the closing brace of a namespace should have a trailing comment with the name of the namespace itself.

namespace Namespace
{
// code here, indentation level still at 0

} // Namespace

Function Declarations and Definitions

Function declarations should typically not span more than one line, unless the parameter list inhibits readability. Function declarations should include parameter names, that match the parameter names of the function definition. If a parameter is nameless, a comment should notate what the parameter is for. When defining a function, the same general idea applies, that the return type, function name, and parameter list, should typically not span multiple lines. In the event that the parameter list should need to expand to a new line, it is preferred that the first parameter of the new line be aligned to match the indentation of the first parameter of the function. If this is not reasonably feasible (for example, if the function name is very long this may be problematic), then it is preferred to have each parameter on a separate line, with 4 spaces of indentation, preceding the opening brace:

void DoThis(int param1, int param2, int param3)
{
}
 
int DoThat(int param1, int param2, int param3, int param4,
           int param5, int param6, int param7, int param8)
{
}
 
char* DoTheOtherThingThatHasALongName( // opening parenthesis should always be on the same line as function name
     char *c,
     int len,
     bool destroy) // closing parenthesis should be on the same line as the last parameter
{
}

Constructor initialization lists

When writing a class constructor consider perform simple member initializations (that do not presume any side-effects) into initialization list. The initialization list should be idented by 1 level further (4 spaces), have one member initialized per line and ':' and ',' separators placed at the beginning of each line.

MyClass::MyClass()
    : BaseClass()
    , _privateField(0)
    , _anotherPrivateField(10)
{
    _calculatedPrivateField = CalculateValues();
}

This rule may be ignored for trivial structs with just a few members (in which case you may also do inlined constructor).

SimpleStruct() : ID(0) {}

Function Parameter Ordering

Generally, function parameters will be listed in order of input parameters, in-out parameters, and then output parameters. This will help provided added clarity to your functions. You should always document the intent and purpose of your parameters.

Switch Statements

In the case of switch statements (pun very much intended!), the labels for each case should not be indented from the switch block itself, but the lines for that case should be. Any break or return statements should be at the same level of indentation as the lines inside each case. The default case should always be provided, even if it should never be reached (if this is the case, consider using assert or simply adding a comment). Fall-through from one case to another should be avoided unless there is no other code for that case. If not allowing fall-through would require duplication of code or cause other issues, the fall-through should be specifically noted to prevent suspicions in possible mistake:

switch (var)
{
case 1:
    DoSomething();
    break;
case 2:
case 3:
    DoSomethingElse();
    // fall-through intended
case 4:
    DoSomethingCompletelyDifferent();
    break;
default:
    // do nothing
    break;
}

Loops

If a loop statement is left empty, it should be clear that this is the intention. In this case, the opening and closing braces may appear on the same line as the loop statement (typically with no space in-between). Alternately, you could use plain ';' or 'continue;' instead. This is to prevent confusion and promote code readability:

for (int i = 0; i < 42; ++i) {}
for (int i = 0; i < 42; ++i);
while (UpdateGame()) continue;

Pointers and References

For pointers and references, the preferred convention is to have the asterisk or ampersand beside the name. Having the asterisk or ampersand beside the type is also accepted, but may cause confusion if declaring more than one pointer or reference. Having whitespace on both sides of the asterisk or ampersand should be avoided.

Bitmap *ds;
void Func(const char *name, int &result);

Boolean Expressions

Boolean expressions should always be written in an invariant fashion. That is, whether the compiler reads the operators left-to-right or right-to-left, the result should always be the same. Usage of parenthesis is encouraged to prevent variance or confusion.

If you are writing a boolean expression that needs to span multiple lines to preserve readability, be consistent in where you split the lines at. It is preferred in this case that each line end with an operator (&& or ||).

Templates

When defining a template function or class, the template keyword, angle brackets, and template parameters should occupy their own line, at the same indentation level as the following definition. There should not be a space between the template keyword and the opening angle bracket. Generic parameters should prefer the verbiage "typename" vs "class".

template
void MyTemplateFunction()
{
}