Step 5

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:

  1. Read the current state
  2. Apply rules
  3. 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.