helios Code Style Guide
This document summarizes the coding style and module organization conventions used in the helios project. It is intended to ensure consistency and maintainability across the codebase.
Quick summary
- Use C++23 modules (.ixx for interface/export, .cpp for implementation units) where appropriate.
- Filenames should reflect module and type names (PascalCase for modules/types, e.g.
Window.ixx/Window.cpp). - Namespace hierarchies mirror module paths (e.g.
helios::window). - Getters should be
[[nodiscard]]; methods that do not throw should benoexcept. - Member variables end with
_(e.g.width_).
Modules / file formats
- Interface files (exported modules) live under
include/helios/...and use the.ixxextension (e.g.include/helios/window/Window.ixx).- Structure of an interface file:
- Global module fragment (optional) with
module;and any required#includedirectives for standard/third-party headers. export module helios.<...>.<Name>;importstatements for dependent modules.exported types/namespaces the module exposes.- Doxygen comments for the public API.
- Global module fragment (optional) with
- Structure of an interface file:
- Implementation/module units typically live under
src/helios/...as.cppfiles and are module implementation units:- Start with
module;(global fragment if needed), followed bymodule helios.<...>.<Name>;(withoutexport). importstatements for required modules.
- Start with
- Filename convention: PascalCase for module/type files (e.g.
Window.ixx,Window.cpp). - Module names reflect the path:
helios.window.Windowcorresponds toinclude/helios/window/Window.ixx.
Module facades and partitions
- Module facades are prefixed with an underscore
_(e.g._module.ixxor_windowfacades). They collect or re-export partitions of a subsystem. - A module facade MUST NOT export lower-level modules - modules should only export their own partitions. (Existing rule kept.)
Imports / includes / ordering
- Prefer
importfor internal project modules. Use#includeonly when a module is not available (e.g. external C headers). - Recommended order in a file:
- Global module fragment:
module;+#includedirectives (if needed) - Module declaration:
export module ...;(interface) ormodule ...;(implementation) importstatements- Macros /
#defineconstants (only if unavoidable) namespaceblock with code
- Global module fragment:
Example (interface):
module;
#include <string>
export module helios.window.Window;
import helios.util.Guid;
export namespace helios::window { ... }
Example (implementation):
module;
#include <string>
module helios.window.Window;
import helios.util.Guid;
namespace helios::window { ... }
Namespaces
- Use the nested short notation
helios::sub::...(as used throughout the code), not flat or global namespaces. - Namespaces should reflect module paths.
Class and member conventions
- Type names: PascalCase (e.g.
Window,Guid,SceneNode). - Method and variable names: lowerCamelCase (e.g.
shouldClose(),swapBuffers()). - Private/protected members end with
_(e.g.width_,guid_). - Recommended access order inside a class:
- private
- protected
- public
- Constructors with a single parameter should be
explicit. - Use
overridefor overridden virtual methods. - Virtual destructors in base classes:
virtual ~ClassName() = default;. - Getters that return a value or reference should be
[[nodiscard]]. - Functions that do not throw should be marked
noexcept.
Syntax and formatting
This project follows a consistent C++ syntax and formatting style to improve readability and reduce churn in reviews. Follow these concrete rules:
- Braces: opening brace on the same line as the declaration, for statements and definitions:
if (condition) {notif (condition)
{
}
- Indentation: 4 spaces per indent level. Do not use tabs.
- Spaces: Put a single space after keywords and before parentheses where appropriate:
if (,for (,while (. Use a single space around binary operators:a + b. - Line length: Prefer <= 100 characters. Break long expressions across multiple indented lines (see initializer list example).
- Blank lines: Separate logical sections (e.g. between method definitions, between groups of members) with a single blank line.
- Trailing whitespace: Avoid any trailing whitespace on lines.
- Header/include order in module fragments:
module;, then#includes, thenexport module/moduledeclaration, thenimports. - Comments: Prefer concise
//comments for short notes, and/** ... */Doxygen comments for public API elements.
Code examples (use these as canonical examples):
Module interface (public API example)
// include/helios/window/Window.ixx
module;
#include <string>
export module helios.window.Window;
import helios.util.Guid;
export namespace helios::window {
class Window {
public:
explicit Window(const WindowConfig& cfg);
[[nodiscard]] const util::Guid& guid() const noexcept;
virtual bool show() noexcept = 0;
virtual void swapBuffers() const noexcept = 0;
virtual ~Window() = default;
};
} // namespace helios::window
Implementation (definition / initializer list example)
// src/helios/window/Window.cpp
module;
#include <string>
module helios.window.Window;
import helios.util.Guid;
namespace helios::window {
Window::Window(const WindowConfig& cfg)
: width_(cfg.width),
height_(cfg.height),
title_(cfg.title),
viewport_(cfg.viewport),
guid_(util::Guid::generate()) {
}
[[nodiscard]] const util::Guid& Window::guid() const noexcept {
return guid_;
}
} // namespace helios::window
Brace and spacing examples
// preferred
if (shouldClose()) {
logger_.info("Window requested close");
}
// not preferred
if (shouldClose())
{
logger_.info("Window requested close");
}
Override example
class GlfwWindow : public Window {
public:
bool show() noexcept override;
void swapBuffers() const noexcept override;
};
Logging macro usage
#define HELIOS_LOG_SCOPE "helios::window::Window"
const helios::util::log::Logger& logger_ =
helios::util::log::LogManager::getInstance().registerLogger(HELIOS_LOG_SCOPE);
Splitter / comment for namespace end
} // namespace helios::window
Attributes and qualifiers
- Use
[[nodiscard]]for all value- or status-returning getters. - Use
noexceptfor methods that are guaranteed not to throw (e.g.guid() const noexcept). - Const-correctness is mandatory for methods that do not modify object state.
Logging
- Pattern used in the project:
- Define a macro scope at the top of the interface file (
#define HELIOS_LOG_SCOPE "helios::window::Window"). - Logger instance as a protected member: const helios::util::log::Logger& logger_ = helios::util::log::LogManager::getInstance().registerLogger(HELIOS_LOG_SCOPE);
- Define a macro scope at the top of the interface file (
- Use
LogManager::getInstance()centrally; the scope should always contain the fully qualified module/type package.
Documentation
- Public API must include Doxygen comments (in the interface
.ixx). - Provide a short description, parameters, return values, and notes about side effects or threading when applicable.
Tests, examples, benchmarks
- Tests live under
tests/helios/..., benchmarks underbenchmarks/helios/..., and examples underexamples/. - Tests should import modules the same way production code does and follow the same naming conventions.
Do's and Don'ts (project-specific)
- Do: Use modules (
.ixx) for public-facing APIs. - Do: Export only the actual API from an interface file.
- Do: Keep the member naming suffix
_and use[[nodiscard]]/noexceptwhere appropriate. - Don't: Use global namespaces or unqualified logger scopes.