English
Coding Guidelines
This page describes the basic patterns used in the engine code. Formatting and naming rules are in the C++ code style.
RAII (Resource Acquisition Is Initialization)
Description
Use the RAII idiom to manage resources. A resource is acquired when an object is created and released automatically when the object is destroyed.
This applies to memory, files, threads, mutexes, OS handles, SDL/HID objects, graphics resources, and any other objects that require explicit release.
WARNING
If a resource type is used only once in the whole engine, a dedicated RAII wrapper is not required. Manual resource management is allowed in such cases, as long as it does not hurt readability and maintenance.
When to use
Use RAII when working with:
- memory
- files
- threads
- mutexes
- graphics APIs
Advantages
- ✅ Resources are released automatically
- ✅ Safe on early return from a function
- ✅ Fewer memory and resource leaks
- ✅ The code is simpler and easier to maintain
Grouping functionality (Class vs Namespace)
Description
Group related functionality into a namespace or a class instead of adding many global functions.
The choice depends on whether there is state.
- Use a namespace if the functions are independent, store no data, and form a set of utilities
- Use a class if the object has state, manages resources, or describes a separate entity, manager, service, etc
If a class is a service or an engine component, it should contain all the logic related to it. This reduces coupling and makes the interface clear.
WARNING
The exception is special classes that split responsibility between several subsystems. For example, CGamepadService manages devices, while user input is handled by the separate CInput system.
NOTE
A namespace must always have a name. Unnamed namespaces (namespace { ... }) are prohibited. A helper function used in a single .cpp file is declared as static.
When to use
Use a namespace for
- math functions
- utilities
- conversion functions
- serialization
- stateless algorithms
Use a class for
- services
- managers
- game entities
- objects with a lifetime
- classes that own resources
- objects that store state
Advantages
- ✅ Related code is kept in one place
- ✅ The number of global functions is minimal
- ✅ Functionality is easier to maintain and extend
- ✅ It is clear who is responsible for a particular operation
Disadvantages
- ❌ Excessive encapsulation sometimes makes small functions harder to reuse
Unified interfaces
Description
When adding an alternative implementation of an existing system, use a common interface.
For example, if the engine supports raster and vector images, both implementations should provide one interface for loading, releasing, and accessing the resource.
This isolates the differences between implementations and avoids if, switch, and #ifdef scattered across the code.
WARNING
If the implementations differ too much to build a clear common interface, separate APIs are allowed. Do not create an abstraction for its own sake.
When to use
- alternative implementations of one subsystem
- different resource formats
- platform-dependent implementations
- interchangeable services
Advantages
- ✅ The code does not depend on a specific implementation
- ✅ Fewer branches (
if,switch,#ifdef)
Isolating platform-dependent code
Description
Do not use platform-dependent APIs, data types, and header files in game, render, and other platform-independent modules.
All platform-dependent code lives in the corresponding Platform implementations (Windows, Linux, macOS, etc.) and provides one interface for the rest of the engine.
This minimizes the use of #ifdef, simplifies maintenance, and makes adding new platforms easier.
WARNING
#ifdef is allowed only inside platform modules, or when there is no reasonable way to move the difference behind a common interface. The condition uses one of the macros IXR_WINDOWS, IXR_LINUX, IXR_APPLE_SERIES, IXR_BSD_SERIES.
When to use
- file system access
- window creation
- thread management
- system calls
- interaction with the OS
Advantages
- ✅ Game code does not depend on a specific platform
- ✅ New platforms are easier to support
- ✅ Far fewer
#ifdef - ✅ Platform logic is kept in one place
Disadvantages
- ❌ A separate implementation has to be maintained for each platform
Using internal types
Description
Use the internal IXR types and aliases instead of standard library types or platform-dependent types.
This keeps the implementation under central control, simplifies support for different platforms, and keeps the code consistent across the project.
For example:
std::string→xr_stringstd::string_view→xr_string_viewstd::unordered_map→xr_hash_mapstd::unordered_set→xr_hash_setstd::vector→xr_vectorstd::unique_ptr→xr_unique_ptrstd::uint32_t→u32
WARNING
If the type has no internal analog, the standard library type may be used.
NOTE
Code that may run before the engine's internal allocators are initialized (for example, at application startup or during exception handling) should use standard library containers (std::*). IXR internal types may not be available yet at that point.
When to use
- STL containers
- string types
- fixed-width integer types
- smart pointers
- other types that have internal analogs
Advantages
- ✅ One code style across the project
- ✅ The implementation of internal types can be changed in one place
- ✅ The project is easier to port and maintain