This document outlines the coding standards and conventions used in FR-Ocean Engine.
- General Principles
- C++ Language Features
- Naming Conventions
- Code Formatting
- Documentation
- Error Handling
- Performance Guidelines
- Platform Considerations
- Readability First: Code is read more often than written
- KISS: Keep It Simple, Stupid - avoid unnecessary complexity
- DRY: Don't Repeat Yourself - abstract common patterns
- YAGNI: You Aren't Gonna Need It - don't over-engineer
- RAII: Resource Acquisition Is Initialization - manage resources via constructors/destructors
- Single Responsibility: Each class should have one clear purpose
- Open/Closed: Open for extension, closed for modification
- Dependency Inversion: Depend on abstractions, not concretions
- Composition Over Inheritance: Prefer component-based design
- C++17 is the minimum required standard
- Prefer modern C++ features over C-style alternatives
- Avoid compiler-specific extensions when possible
Encouraged:
autofor type deduction (when type is obvious)- Range-based
forloops nullptrinstead ofNULLstd::unique_ptrandstd::shared_ptrconstexprfor compile-time constantsenum classinstead ofenum- Structured bindings (C++17)
std::optionalfor optional values
Use with Caution:
- Templates (prefer simple, well-documented templates)
- Operator overloading (only when natural and intuitive)
- Multiple inheritance (prefer composition)
- Exceptions (currently disabled in engine)
Avoid:
- Raw
new/delete(use smart pointers) - C-style casts (use
static_cast,reinterpret_cast) gotostatements#definefor constants (useconstexpr)using namespacein headers
// Good ✓
class ImageDB {
public:
static SDL_Texture* GetTexture(const std::string& imageName);
private:
inline static std::unordered_map<std::string, SDL_Texture*> textureMap;
};
// Bad ✗
class ImageDB {
public:
static SDL_Texture* get_texture(char* image_name); // No const, C-string
private:
static map<string, SDL_Texture*> texture_map; // using namespace std
};- PascalCase for class and struct names
- Descriptive names that clearly indicate purpose
class Engine { };
class RigidbodyWorld { };
struct ImageDrawRequest { };- PascalCase for public methods
- snake_case for private helper functions (optional)
- Verbs for actions (Get, Set, Add, Remove, Create, Destroy)
class Actor {
public:
std::string GetName();
void AddComponent(const std::string& type);
private:
void processComponents(); // Private helper
};- snake_case for local variables and parameters
- snake_case for member variables
- No Hungarian notation
class SceneDB {
private:
std::vector<uint64_t> actor_id_vec; // Member variable
std::string current_scene_name; // Member variable
inline static uint64_t id_ctr = 0; // Static member
};
void ProcessEvent(const SDL_Event& event) {
int button_id = event.button.button; // Local variable
glm::vec2 mouse_pos = GetMousePosition(); // Local variable
}- UPPER_SNAKE_CASE for constants
- Use
constexprorconstinstead of#define
constexpr int MAX_ACTORS = 1000;
constexpr float DEFAULT_GRAVITY = 9.8f;
const std::string SCENE_PATH = "resources/scenes/";- PascalCase for enum class names
- UPPER_SNAKE_CASE for enum values
enum class INPUT_STATE {
INPUT_STATE_UP,
INPUT_STATE_JUST_BECAME_DOWN,
INPUT_STATE_DOWN,
INPUT_STATE_JUST_BECAME_UP
};- Match class name:
ImageDB.hpp/ImageDB.cpp - Use
.hppfor C++ headers,.cppfor sources - Use
.honly for C headers
- 4 spaces for indentation (no tabs)
- Configure your editor to convert tabs to spaces
- K&R style for braces (opening brace on same line)
- Always use braces for control structures, even single-line
// Good ✓
if (condition) {
DoSomething();
}
for (int i = 0; i < 10; i++) {
Process(i);
}
// Bad ✗
if (condition)
DoSomething(); // Missing braces
if (condition) { DoSomething(); } // One-liner with braces- Aim for 100 characters per line
- Break long lines for readability
// Good ✓
static void QueueImageDrawEx(const std::string& imageName, float x, float y,
float rotationDegrees, float scaleX, float scaleY,
float pivotX, float pivotY,
float r, float g, float b, float a,
float sortingOrder);
// Okay (if unavoidable)
static void QueueImageDrawEx(const std::string& imageName, float x, float y, float rotationDegrees, float scaleX, float scaleY, float pivotX, float pivotY, float r, float g, float b, float a, float sortingOrder); // 237 chars- Space after keywords:
if (,for (,while ( - No space before function call parentheses:
Function(arg) - Space around operators:
a + b,x = 5 - No space inside parentheses:
(value)not( value )
// Good ✓
if (x > 0) {
int result = Calculate(x, y);
value = result + 10;
}
// Bad ✗
if(x>0){
int result=Calculate( x , y );
value=result+10;
}- Attach
*and&to the type, not the variable
// Good ✓
SDL_Texture* texture;
const std::string& name;
Actor* InstantiateActor(const std::string& temp);
// Bad ✗
SDL_Texture *texture;
const std::string &name;
Actor *InstantiateActor(const std::string &temp);// 1. Header comment
//
// ClassName.hpp
// FR-Ocean Engine
//
// Brief description of what this file does.
//
// Created by Author on Date.
//
// 2. Include guard
#ifndef ClassName_hpp
#define ClassName_hpp
// 3. System includes
#include <string>
#include <vector>
// 4. Third-party includes
#include "SDL2/SDL.h"
#include "glm/glm.hpp"
// 5. Project includes
#include "OtherClass.hpp"
// 6. Forward declarations
class ForwardDeclaredClass;
// 7. Class definition
class ClassName {
public:
// Public interface
private:
// Private members
};
#endif /* ClassName_hpp */All public APIs must have Doxygen-style documentation:
/**
* @brief Brief one-line description.
*
* Detailed description of what this function does, how it works,
* and any important notes or caveats.
*
* @param param1 Description of first parameter
* @param param2 Description of second parameter
* @return Description of return value
*
* @throws std::runtime_error if something fails
*
* @note Additional notes or warnings
* @see RelatedFunction() for related functionality
*
* @example
* ```cpp
* ClassName obj;
* obj.Function(arg1, arg2);
* ```
*/
ReturnType Function(Type1 param1, Type2 param2);- Explain why, not what (the code explains what)
- Use
//for single-line comments - Use
/* */for multi-line comments - Comment complex algorithms and non-obvious logic
// Good ✓ - Explains WHY
// Sort by z-order first, then submission order for stable sorting
std::stable_sort(queue.begin(), queue.end(), ...);
// Bad ✗ - States the obvious
// Sort the queue
std::stable_sort(queue.begin(), queue.end(), ...);// TODO(author): Description of what needs to be done
// FIXME(author): Description of what needs fixing
// HACK(author): Description of workaround- Use
assert()for debug-only checks - Use static assertions for compile-time checks
#include <cassert>
void Process(Actor* actor) {
assert(actor != nullptr && "Actor must not be null");
// ...
}
static_assert(sizeof(int) == 4, "int must be 4 bytes");- Log errors to console with
std::cerr - Use
Debug.LogError()for Lua-visible errors - Fail gracefully when possible (don't crash)
if (!file.is_open()) {
std::cerr << "Error: Failed to open file: " << filename << std::endl;
return false; // or return default value
}- Exceptions are currently disabled in the engine
- Use return codes or
std::optionalfor error cases - Document error conditions in function comments
// Returns texture or nullptr if loading fails
static SDL_Texture* GetTexture(const std::string& imageName);
// Returns optional actor (nullopt if not found)
static std::optional<Actor*> FindActor(const std::string& name);- Use RAII for automatic resource cleanup
- Prefer
std::unique_ptrfor ownership - Use
std::shared_ptrsparingly (only when shared ownership needed) - Avoid unnecessary copies (use
const&for parameters)
// Good ✓
std::unique_ptr<Actor> actor = std::make_unique<Actor>();
void Process(const std::string& name) { // Pass by const reference
// ...
}
// Bad ✗
Actor* actor = new Actor(); // Manual memory management
void Process(std::string name) { // Unnecessary copy
// ...
}- Premature optimization is the root of all evil - profile first!
- Use
inlinefor small, frequently called functions - Prefer
reserve()for vectors with known size - Use
emplace_back()instead ofpush_back()for complex types - Cache function results when appropriate
// Reserve capacity to avoid reallocations
std::vector<uint64_t> actor_ids;
actor_ids.reserve(1000);
// Emplace instead of push_back (avoids copy)
actors.emplace_back(std::make_unique<Actor>());
// Cache expensive lookups
auto cached_component = actor->GetComponent("Rigidbody");
// Use cached_component instead of repeated GetComponent calls- O(1) for critical path operations
- O(n) acceptable for per-frame iteration
- O(n log n) okay for sorting (once per frame)
- O(n²) avoid in hot paths (consider spatial partitioning)
// Good ✓ - O(1) lookup with unordered_map
std::unordered_map<std::string, SDL_Texture*> textureCache;
auto it = textureCache.find(name);
// Bad ✗ - O(n) lookup with vector
std::vector<std::pair<std::string, SDL_Texture*>> textureCache;
auto it = std::find_if(textureCache.begin(), textureCache.end(), ...);- Use SDL2 for cross-platform functionality (window, events, graphics)
- Avoid platform-specific APIs when possible
- Use
#ifdefsparingly, only when necessary
#ifdef _WIN32
#include <windows.h>
// Windows-specific code
#elif defined(__APPLE__)
#include <TargetConditionals.h>
// macOS-specific code
#elif defined(__linux__)
// Linux-specific code
#endif- Use forward slashes
/in paths (works on all platforms) - Use
std::filesystemfor path manipulation (C++17)
#include <filesystem>
namespace fs = std::filesystem;
fs::path scene_path = fs::path("resources") / "scenes" / "level1.scene";- Be aware of endianness when reading/writing binary files
- Use network byte order for serialization
- Build with warnings enabled (
-Wall -Wextraor/W4) - Treat warnings as errors (
-Werroror/WX) - Fix all warnings before committing
# CMake example
if(MSVC)
add_compile_options(/W4 /WX)
else()
add_compile_options(-Wall -Wextra -Werror)
endif()- PascalCase for component tables
- snake_case for local variables
- PascalCase for lifecycle methods (OnStart, OnUpdate)
PlayerController = {
speed = 5.0,
is_grounded = false
}
function PlayerController:OnStart()
local rigidbody = self.actor:GetComponent("Rigidbody")
end- Use 4 spaces for indentation
- Always use
localfor variables (avoid global pollution) - Comment complex logic
-- Good ✓
function Component:OnUpdate()
local velocity = self.rigidbody:GetVelocity()
if Input.GetKey("left") then
velocity.x = -self.speed
end
self.rigidbody:SetVelocity(velocity)
end
-- Bad ✗
function Component:OnUpdate()
velocity=self.rigidbody:GetVelocity() -- No local, no spacing
if Input.GetKey("left") then velocity.x=-self.speed end -- One-liner
self.rigidbody:SetVelocity(velocity)
end- IDE: Visual Studio Code, Visual Studio, CLion, Xcode
- Formatter: clang-format (config provided in repo)
- Linter: clang-tidy
- Debugger: GDB (Linux), LLDB (macOS), MSVC Debugger (Windows)
- Profiler: Valgrind, Instruments, Visual Studio Profiler
Save as .clang-format in project root:
BasedOnStyle: Google
IndentWidth: 4
ColumnLimit: 100
PointerAlignment: LeftBefore submitting code, verify:
- Code follows naming conventions
- All public APIs are documented
- No compiler warnings
- No memory leaks (checked with Valgrind or similar)
- Code is readable and self-documenting
- Complex logic has explanatory comments
- Performance is acceptable (profile if unsure)
- Works on all supported platforms
- Follows RAII principles
- Uses appropriate data structures (complexity)
Last Updated: 2025-10-28 Engine Version: 1.0 Author: Jack Gaffney