A build MCP server is available with four tools:
build_file <path>— compile a single.cpp(path relative to repo root) for fast error-checking after editsbuild— full ninja buildbuild_cmake— regenerate CMake build files whenCMakeLists.txtchangescmake_debug_fast— generate the fast-building Debug configuration: runsBUILD_FOLDER_SUFFIX=fast cmake/build.sh Debug x64 -DGENERATE_DEBUG_INFORMATION=0, createsbuild/Debug-x64-clang-fast/and symlinksbuild/currentto it
build, build_cmake, and build_file will first run cmake_debug_fast if the build/ folder does not yet exist.
Prefer build_file to verify edits to a specific file before running a full build.
-
Do not change formatting style like tabs or other spacing conventions in the files you are editing.
-
Include order
- Standard libraries first
- Then in the order of basic to specific: augs -> game -> view -> application, e.g.:
#include <vector> #include "augs/math/vec2.h" #include "game/cosmos/logic_step.h" #include "view/viewables/viewables_loading_type.h" #include "application/config_json_table.h"
-
Basic cpp footguns
- Do not use
using namespace std; - Use
static_cast<>and the like instead of(type)obj;
- Do not use
-
Prefer initialization with
autoorconst auto.const auto abc = 2;instead ofint abc = 2;- Likewise with classes:
auto object = some_class(2, 3);
-
constwherever possible even at the cost of readability.- In particular,
constevery possible function argument. - But don't
constarguments taken by copy in the function declarations as this is redundant.
- In particular,
-
Use tabs for indentation.
-
Use uncapitalized
snake_caseeverywhere.- Except for template parameters: use CamelCase for them.
template <class T, class... Rest>template <class T, class... Args>for variadic functions, withargs...as the argument name.- Prefer a descriptive name instead
Tif there's one template argument.
- Except for template parameters: use CamelCase for them.
-
Linux kernel indentation style.
- But ALWAYS use brackets after
if/else/forand the like! Too much life has been wasted on the illusion that this line is really a single expression... - Example:
if constexpr(std::is_same_v<T, int>) { // ... } else { // ... }
- In particular don't ever do this atrocity:
} else {
- But ALWAYS use brackets after
-
Put a single space after
if,while,for, etc. Like so:if (expression)if constexpr(expression)do { ... } while (expression);
-
Put a single space between the operator and each operand, e.g.
const auto abc = 2 + 2; -
There isn't really a fixed maximum for the line's length, but keep it around 125 characters.
-
Break the lines like so:
void something::long_function_definition( const int arg1, const double arg2 ) const { } long_function_call( a, b c ); complex_call_with_lambda(other_func(hey_there( a, [](){ return 1337; }, c ))); const auto long_assignment_with_ternary = abc == 2 ? something : else ; // take care to always have the ; a separate line!!! /* For multi-line boolean expressions and assignments, put the semicolon on its own line: */ const bool complex_check = (some_condition && other_condition) || (another_condition && yet_another) ; if (long_clause && other_long_clause && another_long_clause ) { // take care to always have these two characters in a separate line!!! } else { } /* Long initialization: */ pathfinding.rerouting = pathfinding_progress { std::move(*new_rerouting), 0 };
-
If the initial variable value depends on several conditions, you can sometimes employ this trick to make the variable const:
const auto complex_variable_made_const = [&]() { if (foo) { if (bar) { return 2; } return 1; } return 0; }();
I.e. you define a lambda and call it in the same expression (note the
();at the end of the assignment). -
When declaring new structs that are meant to be synchronized through the network or serialized to the disk, write
// GEN INTROSPECTOR struct struct_name:struct arena_mode_ai_state { // GEN INTROSPECTOR struct arena_mode_ai_state float movement_timer_remaining = 0.0f; float movement_duration_secs = 0.0f; ... // END GEN INTROSPECTOR };
Take care to not put function declarations between these comments, only member field definitions.
-
Write comments like this, even if they are single-line:
/* This is a very long comment. */
This makes it easier to later add lines to that comment.
-
Where appropriate, use this if-scoping mechanism for variables:
if (const auto stack_target = cosm[stack_target_id]) { // ... }
This combines dereferencing the handle and checking if it is set in one go, and constrains visibility of
stack_targetonly to the scope where it's needed. -
If there is a very complex if clause, prefer to name the condition with a bool for clarity:
if (const bool something_happened = something && happened) { }
-
Cache the operation results in variables aggressively near the beginning of the function, e.g.:
const auto character_handle = in.cosm[character_id]; if (character_handle) { const auto character_transform = character_handle.get_logic_transform().pos; const auto pos = character_transform.pos; // ... }
instead of writing
in.cosm[character_id].get_logic_transform().pos
every time you need the character's position.
-
Try to avoid repeating yourself. When writing a lot of code, use lambdas generously and then call them in a facade:
auto complex_condition = [&]() { if (something) { return foo; } return bar; }; auto complex_operation = [&]() { // ... }; auto complex_else = [&]() { // ... }; if (complex_condition()) { complex_operation(); } else { complex_else(); }
-
use
a.has_value()instead ofawhen checkingstd::optionalto know it's not a boolean. -
use
a != nullptrinstead ofawhen checking pointers. -
To log values, use the type-safe log functions:
LOG("some value: %x", some_value)where "%x" stands for argument of any type.LOG_NVPS(var1, var2)- Supported format specifiers (via
typesafe_sprintf):%x— generic, works with any type%f— fixed-point float%2f,%4f— fixed-point float with N digits precision%h— hexadecimal
- Standard printf specifiers like
%d,%u,%.2fdo NOT work — they will be printed as literal text.
-
Some of the existing code might not follow the above principles, do not edit it to make it consistent unless explicitly asked.
-
Use
::prefix for all global function calls that are defined in the same file but outside of any namespace. This makes it clear the function is global and not a member of any class or namespace.const auto result = ::my_global_function(arg1, arg2);
-
To create new entities, allocate_new_entity_access access is required; you must declare the function you need there and write a comment that justifies why and how you're going to create new entities.
-
Prefer using vec2 (float) and vec2i (int) for coordinates so e.g.
uint8_t get_cell(int cell_x, int cell_y) const;
would better be written as
uint8_t get_cell(vec2u cell_pos) const;
-
Use vec2/vec2i arithmetic operations instead of operating on x and y separately:
/* Good */ const auto offset = (pos - bound_lt) / cell_size; /* Bad */ const auto offset = vec2i( (pos.x - bound_lt.x) / cell_size, (pos.y - bound_lt.y) / cell_size );
-
Whenever performing 2D vector math, check if there's a function in
augs/math/vec2.h:/* Good: use vec2::length() */ const auto dist = (a - b).length(); /* Bad: manual euclidean distance calculation */ const auto dx = a.x - b.x; const auto dy = a.y - b.y; const auto dist = std::sqrt(dx * dx + dy * dy);
-
You can cast between
vec2(float) andvec2i(int) freely:const auto float_vec = vec2(int_vec); const auto int_vec = vec2i(float_vec);
-
Use
vec2::square(side)for creating square vectors instead ofvec2(side, side):/* Good */ const auto cell_size_vec = vec2::square(cell_size); /* Bad */ const auto cell_size_vec = vec2(cell_size, cell_size);
-
Use
reverse()wrapper fromaugs/templates/reversion_wrapper.hfor reverse iteration:/* Good */ for (const auto& item : reverse(container)) { ... } /* Bad */ for (auto it = container.rbegin(); it != container.rend(); ++it) { ... }
-
Use
callback_result::CONTINUEorcallback_result::ABORTfromaugs/enums/callback_result.hfor callbacks that can continue or abort:for_each_item([&](const auto& item) { if (should_stop) { return callback_result::ABORT; } // process item return callback_result::CONTINUE; });
-
When implementing new functionality in the
augs/folder, always put it in theaugs::namespace. -
Separate generic algorithmic logic (BFS, A*, sorting, etc.) into
augs/algorithm/headers. Game-specific code should call these generic functions with appropriate parameters. This keeps algorithms reusable and testable independent of game code.
float vec2::length()- get the length of a vectorfloat vec2::degrees()- get the angle of a vector in degreesfloat vec2::radians()- get the angle of a vector in radiansvec2 vec2::normalize()- normalize the vectortype vec2::dot(vec2)- dot producttype vec2::cross(vec2)- cross producttype vec2::area()- area (x * y)static vec2 vec2::square(side)- create a square vector (side, side)static vec2 vec2::from_degrees(deg)- create unit vector from degreesvec2 ltrb::lt()- left-top corner (alias for left_top())vec2 ltrb::rb()- right-bottom corner (alias for right_bottom())vec2 ltrb::rt()- right-top corner (alias for right_top())vec2 ltrb::lb()- left-bottom corner (alias for left_bottom())vec2 ltrb::get_center()- center of the rectanglevec2 ltrb::get_size()- size of the rectangle (width, height)bool ltrb::hover(vec2)- check if point is inside the rectanglebool ltrb::hover(ltrb)- check if rectangles overlapT ltrb::w()- width of the rectangleT ltrb::h()- height of the rectangleT ltrb::area()- area of the rectangle (w * h)