Coding style
When making changes or additions that you plan on contributing to bsf, you must follow the same coding style as the rest of the codebase. This document tries to list the most important aspects of the style.
Spacing
- Use tabs instead of spaces for indentation
- Always put a space after a semicolon (e.g.
for(i32 i = 0; i < 5; i++)). - Always put a space between operators (e.g.
5 + 2). - Separate meaningful parts of the code with empty lines
- Always put a blank line after a block
{ } - No single line should be longer than column 124 (set up a guideline in your editor)
Brackets
- Both opening and closing brackets should be on their own line
- The exceptions are empty methods and small methods defined directly in class declarations
- Conditionals or loops that have just one line should omit the brackets
Naming
- Methods and functions must use Capital case with first letter as uppercase (e.g.
void MyMethod(); int GetValue();). - Local variables must be in camelCase with first letter as lowercase (e.g.
i32 myVar; const T squaredLength;). - Public fields in classes/structs must use Capital case with first letter as uppercase (e.g.
i32 X; bool IsCompleted;). - Class/struct/enum names must be in PascalCase with first letter as uppercase (e.g.
class MyClass). - Non-public class/struct fields should be prefixed with
m, followed by a capital first letter (e.g.i32 mClassMemberField;). - Non-constant global variables and methods should be prefixed with
g, followed by a capital first letter (e.g.const Application& GetApplication();). - Constant variables (both global and class members) should be prefixed with
k, followed by a capital first letter (e.g.static const Vector3 kZero;). - Macros should be written in all caps with underscores separating the words, and prefixed with
B3D_(e.g.#define B3D_LOG). - Static non-constant variables should be prefixed with
s, followed by a capital first letter (e.g.bool sIsInitialized;). - Everything needs to be part of the
b3dnamespace- Types used primarily on the render thread should be part of the
b3d::rendernamespace - ECS fragments should be part of the
b3d::ecsnamespace
- Types used primarily on the render thread should be part of the
- Use
Basesuffix to mark base classes unless a more generalized name is more appropriate (e.g.MeshBase) - Prefix templated base classes/structs with a
Tunless a more generalized name is more appropriate (e.g.TAsyncOp<...>) - Use
Hprefix for component and resource handles (e.g.HMyComponent)
Class interface
- Carefully construct class interfaces, keeping only relevant methods public and everything else private/protected/internal
- Public fields should be avoided (except for simple structures) and getter/setter methods should be provided instead
- If a class needs to communicate with another class within a sub-system, but the functionality isn't something a normal user is meant to use, provide "internal" methods. Those may be (depending on what is more convenient):
- Private methods that are exposed to the other class using the
friendsyntax - Public methods that are surrounded with
@name Internal @{ @}documentation block.
- Private methods that are exposed to the other class using the
- Class entries should be listed in this order:
- typedefs
- nested classes/structures
- public methods
- internal methods
- public fields
- internal fields
- protected methods
- private methods
- protected fields
- private fields
- Each major class should belong to its own .h file, prefixed with
B3Dfollowed by the class name- If helper types are required, they can belong to the same file, but prefer to list them after the main class when possible
Function/method interface
- If method doesn't modify data, always mark it as
const(getters especially) (e.g.i32 GetValue() const;) - Always pass non-primitive parameters by reference unless
nullis a valid value in which case use a pointer (e.g.void MyMethod(i32 a, const SomeStruct& b)) - Reference/pointer parameters that won't be modified by a function should always be
const(e.g.bool ProcessData(const SomeStruct& input, SomeStruct& outData))
Implementation
- Avoid the use of
autoexcept for:- Very long type names (like iterators), in which case include indication of the type in variable name (e.g.
auto iter = ...;) - Variables where type can be otherwise deduced (e.g. casts,
auto var = (u32)otherVar;)
- Very long type names (like iterators), in which case include indication of the type in variable name (e.g.
- Use built-in typedefs for standard library containers (e.g.
Vector) and shared pointers (SPtr). - Don't allocate memory using
new/deleteormalloc/free, instead usebsfallocators - No code warnings under default compiler warning settings are allowed. Fix all your warnings or if absolutely not possible isolate that bit of code and disable that specific warning (but only in that bit of code).
- Never use
using namespacein a header
Documentation
- Documentation must be in JavaDoc format
- Write meaningful documentation
- Avoid writing documentation that provides information that can be deduced from class/method name and instead try to provide the reader with deeper understanding. For example
/** Gets value */ GetValue();is pointless documentation - Avoid giving away implementation details in documentation. Implementation should be allowed to change, and as long as the interface remains the same, the documentation should remain the same
- Avoid writing documentation that provides information that can be deduced from class/method name and instead try to provide the reader with deeper understanding. For example
- Document every class/structure/enum with a short description on what their purpose is
- Optionally provide a use-case example
- Document every enum member
- Document struct fields unless they have getter/setter methods that are already documented
- Getter/Setter methods should have the same documentation (e.g. document the setter, then use
@copydocon getter) - Documentation for private class fields is optional
- Document every method, both public and private
- The only exceptions being very trivial methods whose use can be deduced from their name
- You are allowed to omit documentation for method parameters & return value if it is clear from the general method description what they do
- If you document one parameter you must document them all, as well as the return value
- Document implementations
- Prefer writing self-documenting code as opposed to writing comments
- Give variables meaningful names
- If an expression is too complex break it down using temporary variables using meaningful names
- If it is still unclear, write comments
- Carefully document complex mathematical expressions
- Add a comment what the expression does or a hyperlink to expression source
- If simplified or re-arranged, provide reference expression in comments, as well as explanation on how the current expression was derived (or a link to one)
- Prefer writing self-documenting code as opposed to writing comments
- Use
@addtogroupto group classes into relevant sections (e.g. a class doing something physics-related should be in the "Physics" group)- Classes/structs/functions/enums that aren't meant to be used by normal users should be placed in a separate group to avoid polluting the user-facing documentation (e.g. user-facing physics functionality should be in the "Physics" group, while non-user-facing should be in "Physics_Internal" group)
- Use
@name Internalto mark public methods inside a class that they are not meant for normal use (i.e. they might be used for internal communication within a sub-system only) - Documentation should produce no Doxygen warnings when it is generated
- If it is not obvious for which thread is a class/method meant to be used on, note it in the documentation (use "render thread" or "simulation thread" terminology)