C++ as a Simulation Language
Harness the language that gives you power over every byte and every frame.
Abstract
Real time simulation is one of the most demanding workloads in software engineering. Every frame, your program must update thousands—or millions—of small pieces of state, apply mathematical rules, integrate motion, resolve constraints, and produce a coherent result. There is no time for unpredictability, hidden allocations, or runtime surprises.
This is where C++ shines.
Not because it is “fast” in the abstract, but because it gives you control: over memory, over layout, over lifetimes, over cost.
This article introduces the simulation mindset in C++ and lays the foundation for the rest of BeforeTheMesh.
It is the first of a mini series of articles. In the following articles, we will dive deeper into specific aspects of C++ in Graphics and Visualization, and how to use C++ as an engineering tool for simulation. Finally, we will explore advanced techniques and best practices for building high-performance, reliable simulation systems.
1. Why C++ for Simulation?
Simulation code has three core requirements:
- Determinism — the same inputs must produce the same outputs
- Predictability — no hidden allocations, no surprise slow paths
- Performance — millions of operations per frame, often in tight loops
C++ supports these through:
- Value semantics
- Zero cost abstractions
- Explicit memory control
- Inlineable math
- Compile time configuration
Languages with garbage collection or dynamic dispatch as the default struggle here. Simulation needs control, not convenience.
2. The Simulation Mindset
At its core, simulation is a loop:
\[state\left(t+\mathrm{\Delta t}\right)=integrate\left(state\left(t\right),\mathrm{\Delta t}\right)\]Every frame:
- Read the current state
- Apply rules
- Produce the next state
This leads to two fundamental patterns.
Fixed vs Variable Time Step
-
Fixed Δt
- Deterministic
- Stable
- Preferred for precise physics calculations/simulations
-
Variable Δt
- Matches real time
- Can cause instability
- Acceptable for simple animations
Most real engines use a hybrid: fixed simulation step, variable rendering step.
Data Flow
Data Flow and Frame Boundaries
Simulation code benefits from clear boundaries:
- Input state
- Update
- Output state
This keeps the system predictable and debuggable.
3. Value Types and Small Structs
Simulation code is dominated by small, frequently updated pieces of data:
- positions
- velocities
- forces
- transforms
- particle attributes
These should be value types:
/**
* @struct particle
* @brief Represents a single particle in the physics simulation
*
* Each particle has a position in 3D space, a velocity vector,
* and a mass property for physics calculations.
*/
struct particle {
basepoint3<double> position; /// Position in 3D space (x, y, z coordinates)
basevec3<double> velocity; /// Velocity vector (x, y, z components)
double mass; /// Mass of the particle in kilograms
};
Why value types?
- They are cheap to copy
- They live in contiguous memory
- They avoid pointer chasing
- They are cache friendly
- They behave like mathematical objects
Simulation is math. Math is value based.
Copying vs Referencing
Copying a 32 byte struct is cheaper than following a pointer to a heap allocation.
This surprises many developers coming from OOP backgrounds.
4. Memory Layout and Predictability
The CPU loves contiguous memory.
Simulation loves predictable iteration.
These two facts shape how we design data.
Array of Structs (AoS)
std::vector<particle> particles;
Great for:
- simple simulations
- per particle updates
- cache friendly iteration
Struct of Arrays (SoA)
struct Particles {
std::vector<basepoint3<double>> positions;
std::vector<basevec3<double>> velocities;
std::vector<double> masses;
};
Great for:
- SIMD
- GPU uploads
- large scale particle systems
Avoiding Pointer Chasing
Simulation loops often run millions of iterations.
Every pointer dereference is a potential cache miss.
Cache misses cost hundreds of cycles.
Contiguous memory avoids this.
5. Avoiding Hidden Costs
Simulation code must avoid anything that introduces unpredictability.
Virtual Dispatch in Hot Paths
Virtual calls:
- prevent inlining
- add indirection
- break tight loops
Use them for high level architecture, not per particle updates.
Exceptions
Throwing is catastrophic in real time loops.
Even the possibility of throwing can inhibit optimizations.
Prefer:
- return codes
- std::optional
- expected<T> patterns
Allocations Inside Loops
Never allocate inside a simulation step.
- are slow
- fragment memory
- introduce jitter
- break determinism
Preallocate everything.
6. Patterns for Simulation Code
Simulation code benefits from a few recurring patterns.
The Update Loop
for (auto& p : particles) {
// Update velocity based on gravity
p.velocity += gravity * dt;
// Update position based on velocity
p.position += p.velocity * dt;
}
Simple, predictable, fast.
Systems and Pipelines
Break the simulation into stages:
- forces
- integration
- constraints
- collisions
- post processing
Each stage is a pure function over data.
Stateless Functions and Pure Math
Simulation math should be:
- stateless
- deterministic
- free of side effects
This makes it easy to test, debug, and optimize.
7. Practical Example: A Minimal Particle Update
Below is a small example that demonstrates “good C++ for simulation”.
struct particle {
basepoint3<double> position; /// Position in 3D space (x, y, z coordinates)
basevec3<double> velocity; /// Velocity vector (x, y, z components)
double mass; /// Mass of the particle in kilograms
};
void update_particles(std::vector& particles, float dt) {
const basevec3<double> gravity = { 0.0, -9.81, 0.0 };
for (auto& p : particles) {
basevec3<double> acceleration = gravity; // no forces for now
p.velocity += acceleration * dt;
p.position += p.velocity * dt;
}
}
Why is this good?
- No allocations
- No virtual calls
- No exceptions
- Contiguous memory
- Pure math
- Predictable cost
- Easy to SIMD optimize later
This is the foundation of real time simulation.
Closing Thoughts
C++ is not just “fast”.
It is predictable, explicit, and mathematically aligned with the needs of simulation.
This article established the mental model:
- value types
- contiguous memory
- predictable loops
- no hidden costs
- deterministic updates
In the next article, we shift from simulation to graphics, and explore how C++ interacts with GPU pipelines, math types, and memory alignment.
Follow along, and let’s continue to build a foundation for high performance real time systems.