C++ as an Engineering Tool
Build systems that scale, perform well, and make their costs visible.
Abstract
Simulation and graphics put constant pressure on software, but the deeper challenge is building
This article introduces the engineering mindset behind real-time engines. We will take the Particle Playground—already
capable of simulation and rendering—and reshape it into a
At this point, the Playground is no longer just a demo. It is beginning to resemble a small engine, and the next step is to make that structure intentional.
1. Why Engineering Matters in C++
C++ gives you a great deal of control, but that control needs structure:
- raw pointers everywhere
- global state
- resource leaks
- tangled modules
- unpredictable lifetimes
- fragile initialization sequences
Real time engines address this by relying on:
RAII handles module boundaries explicit ownership target based build systems fail soft error handling
We will explore those patterns by evolving the Playground step by step, beginning with the most important idea: tying resource ownership to object lifetime.
2. RAII — The Foundation of C++ Engineering
RAII (Resource Acquisition Is Initialization) is the idea that:
This applies to:
- GPU buffers
- shader programs
- windows
- input contexts
- timers
- file handles
- memory blocks
Instead of:
particle* particles = new particle[1000]; // manual ownership: easy to leak if cleanup is missed
delete [] particles; // cleanup must be paired explicitly
A better approach is to wrap that ownership in a small template class that manages a fixed array of objects:
template <typename T>
class ppg_array {
T* m_data;
size_t m_size;
public:
ppg_array() : m_data(nullptr), m_size(0) {}
ppg_array(size_t size) : m_data(new T[size]), m_size(size) {}
~ppg_array() { delete[] m_data; } // releases owned storage automatically
void resize(size_t new_size) {
delete[] m_data;
m_data = new T[new_size];
m_size = new_size;
}
size_t size() const { return m_size; }
T& operator[](size_t index) { return m_data[index]; }
const T& operator[](size_t index) const { return m_data[index]; }
T* data() { return m_data; }
const T* data() const { return m_data; }
// provide begin() and end() methods for range-based for loops
T* begin() { return m_data; }
T* end() { return m_data + m_size; } // one-past-the-end pointer used by range-based iteration
const T* begin() const { return m_data; }
const T* end() const { return m_data + m_size; }
};
The usage becomes straightforward:
ppg_array particles(1000); // storage is owned by the array object
We no longer need to call
The Playground uses a slightly different layout in practice, one that better supports GPU updates while the application runs: particle positions, velocities, and colors are stored in separate arrays.
struct particle_system {
ppg_array positions;
ppg_array velocities;
ppg_array colors;
};
RAII helps provide:
- deterministic cleanup for owned resources
- fewer missed release paths
- less reliance on global shutdown functions
- fewer ownership questions during debugging
RAII therefore becomes more than a convenience; it becomes a core mechanism for reliable engine architecture. Once lifetimes are controlled, the next question is what the rest of the code is allowed to see.
3. Handle-Based Architecture
Passing raw OpenGL object names throughout the codebase can quickly become difficult to manage:
- they can leak when deletion paths are missed
- they expose implementation details
- they encourage global state
- they make lifetime unclear
A common alternative is to expose
using Handle = uint32_t;
Handles:
- hide implementation
- allow indirection
- support pooling
- enable debugging
- support resource replacement and hot reload
- make resource management uniform
A Resource Management System can then map handles to actual RAII objects, such as:
Buffer& getBuffer(BufferHandle);
Program& getProgram(ProgramHandle);
This gives the architecture room to scale. Handles, however, work best when the codebase is divided into clear modules with well-defined responsibilities.
4. Module Boundaries — The Engine’s Skeleton
For the Playground, we can split the engine into the following modules:
Minimal utilities:
- timer (implemented in btm-framework)
- ppg_array
- handle management in ppg_handle_manager
Together, these utilities give the project its foundational building blocks.
All math types:
- vector2, vector3, vector4
- matrix4
- transform
- camera (coming from btm-framework)
- shaders (coming from btm-framework)
Pure, header only, and dependency free.
Pure CPU simulation:
- particle
- particle system
- step_simulation driven by elapsed frame time
It does not depend on graphics, platform code, or OpenGL.
GPU side rendering:
- RAII wrappers for buffers, shaders, programs
- particle_renderer
- handle based resource management
This module depends on math and core, but not on simulation.
Window + context:
- Window
- Input
- Platform abstraction
- our own backend (btm-framework)
Because this module has no dependencies on the others, it completes a structure that keeps the engine understandable before additional runtime behavior is introduced.
5. Error Handling — Fail Soft, Not Hard
Unhandled exceptions can be a poor fit for real time loops:
- they can unwind across latency sensitive code paths
- they may add binary, ABI, or platform constraints
- they complicate control flow
For this reason, many engines prefer
Result<ProgramHandle> createProgram(...);
Where:
ok == true → valid handleok == false → diagnostic error information
The result is explicit, predictable control flow that supports the broader goal of making ownership and responsibility visible.
6. Resource Lifetime — The Engine’s Contract
Every module must define:
who owns what who cleans up who depends on whom
Examples:
- Renderer owns GPU buffers and shader programs
- Platform owns the window and context
- Simulation owns particle data
- Camera owns its view/projection state
- Resource Manager owns all GPU resources
Modules should not depend on another module’s internal details.
That contract is what helps engines remain maintainable over time, and it prepares the full Playground structure to bring these ideas together.
7. The Particle Playground — Engine Edition
In Article 3, the Playground moves from a working demo toward a compact modular engine architecture.
theparticleplayground_3/
core/
math/
graphics/
platform/
// Create the platform window and rendering context.
platform ppg_platform;
btm_window_handle pFrame = ppg_platform.create_window(800, 600, "The Particle Playground - part 3");
// std::cout << "Article 3 - C++ as an Engineering Tool" << std::endl;
// std::cout << "Article 4 - Patterns for Simulation and Graphics" << std::endl;
// Initialize simulation and rendering systems.
simulation sim(1000);
particle_renderer g_particle_renderer;
g_particle_renderer.init(sim.positions(), sim.colors());
// Configure the camera used for rendering.
btm::gl_camera camera(btm::fvec3(0, 0, 20), btm::fvec3(0, 0, 0), btm::fvec3(0, 1, 0));
camera.set_fov(btm::dtr(15.f));
btm::start_timer();
btm::get_elapsed_time();
// run the program loop
while (ppg_platform.application_active()) {
float fElapsed = (float)btm::get_elapsed_time();
sim.step_simulation(fElapsed);
render(camera, g_particle_renderer, sim.positions());
}
stop_timer();
return 0;
The resulting loop is:
- clean
- modular
- predictable
- scalable
At this stage, the Playground looks less like a one-off sample and more like a small engine whose architecture can continue to grow.
8. Why This Architecture Matters
This modular, RAII driven, handle based architecture enables:
future features (instancing, batching, compute shaders)debugging tools (resource tracking, hot reload)platform abstraction (custom backends)scalability (multiple render passes, multiple systems)clean separation (simulation independent from rendering)
It mirrors production-engine patterns in a smaller, more approachable form, while preparing the project for the next article’s focus on data flow and integration.
9. Preparing for Article 4
With the engineering foundations in place, Article 4 will show:
- how simulation and graphics form a
pipeline - how data flows through the engine
- how batching and transforms work
- how to build a simple component like structure
- how to integrate everything into a cohesive system
Article 3 establishes the structure. Article 4 builds on that foundation with patterns that connect the pieces, allowing the engine to grow from separate modules into a coordinated runtime system.
Closing Thoughts
C++ is not only a language for performance; it is also a language for architecture. RAII, handles, module boundaries, and explicit ownership give us practical techniques for building reliable real-time engine systems.
This article established the engineering mindset:
- RAII as the foundation
- handles as the interface
- modules as the structure
- resource managers as the backbone
- target based CMake as the build system
- explicit error handling as the safety net
With these tools in place, the Particle Playground becomes more than a collection of rendering and simulation code. It becomes a clean, compact engine foundation, ready for the pipeline, batching, and integration work that comes next.