Engine design¶
Folder structure¶
Use lowercase filenames
Don’t use spaces, use underscores.
Source code¶
connector/ «project root»
├── .clang-format «Clang Format configuration»
├── .clang-tidy «Clang Tidy configuration»
├── .git-blame-ignore-revs «git ignore revisions»
├── .gitignore «git ignore»
├── .readthedocs.yml «Read The Docs configuration»
├── CHANGELOG.rst
├── CMakeLists.txt
├── CODE_OF_CONDUCT.md
├── CONTRIBUTING.md
├── LICENSE.md
├── README.rst
├── .github/ «GitHub templates and action configurations»
├── assets/
│ ├── models/
│ └── textures/ «textures»
├── benchmarks/
├── cmake/ «CMake helpers»
├── configuration/
├── documentation/
│ ├── CMakeLists.txt «CMake file for the documentation»
│ ├── cmake/ «documentation cmake helpers»
│ └── source/ «documentation source code»
├── example/ «example application»
├── include/ «header files»
├── shaders/
├── src/ «source code»
├── tests/
├── third_party/ «third party dependencies»
└── vma-dumps/
Application¶
vulkan-renderer/ «application root»
├── inexor-vulkan-renderer.exe «executable»
├── ...
├── assets/
├── shaders/
└── ...
Dependency management¶
In general we try to keep the number of dependencies at a minimum.
Dependencies are downloaded directly by CMake.
Criteria for library selection¶
If we really need a new library, it should be selected based on the following considerations:
Are you sure we need this library? Can’t we solve the problem with C++ standard library or some other library we are already using?
The library must have an open source license which is accepted by us (see Licenses).
It must be in active development.
It must have a good documentation.
A sufficient number of participants must have contributed so we can be sure it is reviewed.
Coding style¶
The easiest way to get the right format is to use the provided clang-format file in the root directory.
Other styles which cannot be applied automatically are listed below:
Use
#pragma once
as include guardsOwn headers are included with quotes
- Includes are ordered as follows
Own headers
empty line
Third Party Libraries
empty line
System Libraries
Use C++17 namespace style
namespace inexor::vulkan-renderer
No
using <namespace>
For default member initialization use brace instead of equal initialization
Prefer American English over British English
Use spaces to indent
Use Linux line ends (ln) in your commits
Use
///
for multiline documentation instead of/**/
Naming convention¶
Open the .clang-tidy
file and search for readability-identifier-naming
to get the naming convention used by this project.
Error handling¶
Use exceptions for error handling, as proposed by the C++ core guidelines.
More information about why to use exceptions can be found here.
Get methods¶
Name: Don’t use prefix
get_
. Give the get method the same name as the resource it returns.For complex types (
std::vector
,std::string
), return a const reference.Don’t
const
the return type for simple types (int
,float
), because this prevents move semantics to be applied.For simple types (
int
,float
), just copy the return value.Mark get methods as
[[nodiscard]]
in the header file only.Mark get methods as
const
, so they don’t change members.Do not add documentation for get methods, since it is self-explanatory.
Keep get methods directly in the header file.
Do not add
inline
since get methods in header files are always inlined.The get method should not run any other code, like checking if the value is actually valid. Since we are using RAII, the value to return must be in a valid state anyways.
Use operator overloading sparingly. Prefer get methods instead.
Examples:
[[nodiscard]] const glm::vec3& position() const {
return m_position;
}
[[nodiscard]] float aspect_ratio() const {
return m_aspect_ratio;
}
Removed clang-tidy checks¶
- bugprone-narrowing-conversions
Same as
cppcoreguidelines-narrowing-conversions
- cppcoreguidelines-avoid-magic-numbers
Alias of
readability-magic-numbers
- cppcoreguidelines-c-copy-assignment-signature
Alias of
misc-unconventional-assign-operator
- cppcoreguidelines-non-private-member-variables-in-classes
Alias of
misc-non-private-member-variables-in-classes
- cppcoreguidelines-pro-bounds-array-to-pointer-decay
Not suitable for this project.
- google-readability-todo
We do not care about any TODO assignments or related issues.
- hicpp-explicit-conversions
Alias of
google-explicit-constructor
- hicpp-move-const-arg
Alias of
performance-move-const-arg
- hicpp-no-array-decay
Alias of
cppcoreguidelines-pro-bounds-array-to-pointer-decay
- hicpp-uppercase-literal-suffix
Alias of
readability-uppercase-literal-suffix
- llvm-header-guard
#pragma once
is used.- modernize-use-trailing-return-type
Trailing return types are not used.
- readability-magic-numbers
Too many places where it would be useless to introduce a constexpr value.
- readability-uppercase-literal-suffix
Just a style preference.
Code design¶
Literature¶
The following books inspired Inexor’s code design:
Bjarne Stroustrup: The C++ Programming Language (4th Edition)
Scott Meyers: Effective C++: 55 Specific Ways to Improve Your Programs and Designs, Third Edition
Nicolai M. Josuttis: C++ Move Semantics - The Complete Guide
Nicolai M. Josuttis: C++ Templates - The Complete Guide, 2nd Edition
Robert C. Martin: Clean Code: A Handbook of Agile Software Craftsmanship
Robert C. Martin: The Clean Coder: A Code of Conduct for Professional Programmers
General considerations¶
Organize the code in components.
Split declarations and definitions, if possible.
Make appropriate use of the standard library.
Avoid data redundancy in the engine. Do not keep memory copied unnecessarily.
Do not duplicate code. Find an appropriate abstraction which accounts for the scenario.
Try to keep dependencies between components at minimum because single components (e.g. classes) should be as recyclable as possible.
Use spdlog instead of
printf
orstd::cout
for console output.Use
assert
to validate parameters or necessary resources during development (debug mode).Document the code using doxygen comments. Code without documentation is almost useless.
Make sure the code is platform-independent. For now, we will support Windows and Linux but not Mac OS.
Use Vulkan memory allocator library for Vulkan-specific memory allocations like buffers.
Do not allocate memory manually. Use modern ++ features like smart pointers or STL containers instead.
Don’t use the singleton pattern as it makes thread safety and refactoring difficult.
Don’t use call-by-value for returning values from a function call.
Don’t use macros for code generation or as a replacement for enumerations.
C++ core guidelines¶
The C++ code guidelines are a set of rules to use for modern C++ projects created by the C++ community.
In the following section, we will list up the entries which are of considerable interest for the Inexor project.
There will be some gaps in the number as we skipped some of the less importer ones.
Also the code guidelines have gaps by default (blank space for new rules).
Philosophy¶
Interfaces¶
Functions and class methods¶
F.1: “Package” meaningful operations as carefully named functions
F.4: If a function may have to be evaluated at compile time, declare it constexpr
F.7: For general use, take T* or T& arguments rather than smart pointers
F.15: Prefer simple and conventional ways of passing information
F.16: For “in” parameters, pass cheaply-copied types by value and others by reference to const
F.18: For “will-move-from” parameters, pass by X&& and std::move the parameter
F.20: For “out” output values, prefer return values to output parameters
F.21: To return multiple “out” values, prefer returning a struct or tuple
F.26: Use a unique_ptr<T> to transfer ownership where a pointer is needed
F.43: Never (directly or indirectly) return a pointer or a reference to a local object
F.51: Where there is a choice, prefer default arguments over overloading
Classes¶
C.2: Use class if the class has an invariant; use struct if the data members can vary independently
C.3: Represent the distinction between an interface and an implementation using a class
C.4: Make a function a member only if it needs direct access to the representation of a class
C.7: Don’t define a class or enum and declare a variable of its type in the same statement
C.8: Use class rather than struct if any member is non-public
Enumerations¶
Resource management¶
R.2: In interfaces, use raw pointers to denote individual objects (only)
R.5: Prefer scoped objects, don’t heap-allocate unnecessarily
R.12: Immediately give the result of an explicit resource allocation to a manager object
R.13: Perform at most one explicit resource allocation in a single expression statement
Classes¶
C.30: Define a destructor if a class needs an explicit action at object destruction
C.31: All resources acquired by a class must be released by the class’s destructor
C.35: A base class destructor should be either public and virtual, or protected and non-virtual
C.41: A constructor should create a fully initialized object
C.42: If a constructor cannot construct a valid object, throw an exception
C.43: Ensure that a copyable (value type) class has a default constructor
C.44: Prefer default constructors to be simple and non-throwing
C.46: By default, declare single-argument constructors explicit
C.47: Define and initialize member variables in the order of member declaration
C.64: A move operation should move and leave its source in a valid state
C.80: Use =default if you have to be explicit about using the default semantics
C.81: Use =delete when you want to disable default behavior (without wanting an alternative)
C.82: Don’t call virtual functions in constructors and destructors
C.90: Rely on constructors and assignment operators, not memset and memcpy
Follow rule of 0 and rule of 5¶
Performance¶
Design patterns¶
Check out Refactoring Guru to learn more about software design patterns.
Don’t use the singleton pattern as it makes thread safety and refactoring difficult.
Use the builder pattern for composition of complicated data structures.
An example of a builder pattern would be the descriptor builder.
Regressions¶
If something used to work but it’s broken after a certain commit, it’s not just some random bug.
It’s probably an issue which was introduced by the code change which was submitted.
It’s important for us to keep working features in a stable state.
You can use
git bisect
for tracing of the commit which introduced the bug.