Been working in games development professionally now for about ~5 years, but have only been doing OOP & haven't worked with multi threading before as I'm mostly a gameplay programmer, I've also been getting back to C++ which I haven't written in a long time. I wrote this small program as an exercise in DOP & multi threading.
Please ignore my "physics" equations if you can call them that, just threw together something.
The code defines entities split up between arrays of types such as Position, Velocity, and Physics. These are minimalistic types, just data containers really.
I've created two jobs, a RandomizeJob which just initializes the entities with random values, and a SimulateMotionJob which takes the intial random values and updates the speed, acceleration and position over time.
Lastly, we first print out the randomized intitial values in the start, and then we print the final simulated value at the end to compare how the entities have changed.
The goal of this was to try and run the job tasks in parallel, and avoid object oriented programming patterns which I'm used to.
I've gotten much feedback concerning how to write modern C++, which is great, don't get me wrong, I need that too. But please try and keep the feedback aimed towards the jobs, threading and data oriented part of this program apart for some obvious misses.
Thanks to everyone who has given me feedback, I truly appreciate it!
Here is the code:
#include <thread>
#include <random>
#include <cmath>
#include <mutex>
#include <algorithm>
#define GRAVITY 9.82f
#define SIM_TIME_SECONDS 10.0f
#define NUM_ENTITIES 500
struct Vector2
{
float x;
float y;
};
struct Position
{
Vector2 pos;
};
struct Velocity
{
float speed;
Vector2 direction;
};
struct Physics
{
float mass;
float acceleration;
};
class RandomizeJob
{
public:
typedef void (*RandomizeJobFinishedCallback)(Position outPositions[], Velocity outVelocities[], Physics outPhysics[]);
RandomizeJob(Position positions[], Velocity velocities[], Physics physics[], RandomizeJobFinishedCallback onFinishedCallback)
{
std::thread positionsThread = std::thread(&RandomizeJob::RandomizePositions, positions);
std::thread velocitiesThread = std::thread(&RandomizeJob::RandomizeVelocities, velocities);
std::thread physicsThread = std::thread(&RandomizeJob::RandomizePhysics, physics);
positionsThread.join();
velocitiesThread.join();
physicsThread.join();
onFinishedCallback(positions, velocities, physics);
}
private:
static std::mt19937 GetRandomGenerator();
static void RandomizePositions(Position positions[]);
static void RandomizeVelocities(Velocity velocities[]);
static void RandomizePhysics(Physics physics[]);
};
std::mt19937 RandomizeJob::GetRandomGenerator()
{
std::random_device randomDevice;
return std::mt19937(randomDevice());
}
void RandomizeJob::RandomizePositions(Position positions[])
{
std::mt19937 generator = GetRandomGenerator();
std::uniform_real_distribution<float> range(-50.0f, 50.0f);
for (size_t i = 0; i < NUM_ENTITIES; i++)
{
positions[i].pos.x = range(generator);
positions[i].pos.y = range(generator);
}
}
void RandomizeJob::RandomizeVelocities(Velocity velocities[])
{
std::mt19937 generator = GetRandomGenerator();
std::uniform_real_distribution<float> speedRange(0.0f, 80.0f);
std::uniform_real_distribution<float> directionRange(-1.0f, 1.0f);
for (size_t i = 0; i < NUM_ENTITIES; i++)
{
velocities[i].speed = speedRange(generator);
const float randomDirX = directionRange(generator);
const float randomDirY = directionRange(generator);
const float magnitude = std::sqrt(randomDirX * randomDirX + randomDirY * randomDirY);
velocities[i].direction.x = randomDirX / magnitude;
velocities[i].direction.y = randomDirY / magnitude;
}
}
void RandomizeJob::RandomizePhysics(Physics physics[])
{
std::mt19937 generator = GetRandomGenerator();
std::uniform_real_distribution<float> massRange(40.0f, 100.0f);
std::uniform_real_distribution<float> accelerationRange(-8.0f, 8.0f);
for (size_t i = 0; i < NUM_ENTITIES; i++)
{
physics[i].mass = massRange(generator);
physics[i].acceleration = accelerationRange(generator);
}
}
class SimulateMotionJob
{
public:
typedef void (*SimulateMotionJobFinishedCallback)(Position resultPositions[], Velocity resultVelocities[], Physics resultPhysics[]);
SimulateMotionJob(Position positions[], Velocity velocities[], Physics physics[], SimulateMotionJobFinishedCallback onFinishedCallback)
{
const std::chrono::high_resolution_clock::time_point start = std::chrono::high_resolution_clock::now();
std::chrono::high_resolution_clock::time_point last = start;
std::chrono::duration<float> totalTime = last - start;
while (totalTime.count() < SIM_TIME_SECONDS)
{
const std::chrono::high_resolution_clock::time_point current = std::chrono::high_resolution_clock::now();
totalTime = current - start;
const float deltaTime = static_cast<std::chrono::duration<float>>(current - last).count();
const int lastSecondInt = static_cast<int>(static_cast<std::chrono::duration<float>>(last - start).count());
const int currentSecondInt = static_cast<int>(totalTime.count());
if (lastSecondInt != currentSecondInt)
{
printf("\n%i", currentSecondInt);
}
std::thread velocitiesThread = std::thread(&SimulateMotionJob::UpdateVelocities, velocities, physics, deltaTime);
std::thread positionsThread = std::thread(&SimulateMotionJob::UpdatePositions, positions, velocities, deltaTime);
velocitiesThread.join();
positionsThread.join();
last = current;
}
onFinishedCallback(positions, velocities, physics);
}
private:
static void UpdateVelocities(Velocity velocities[], Physics physics[], const float deltaTime);
static void UpdatePositions(Position positions[], Velocity velocities[], const float deltaTime);
private:
static std::mutex mutexes[2];
};
std::mutex SimulateMotionJob::mutexes[2];
void SimulateMotionJob::UpdateVelocities(Velocity velocities[], Physics physics[], const float deltaTime)
{
for (size_t i = 0; i < NUM_ENTITIES; i++)
{
const size_t flipFlop = i % 2;
std::lock_guard<std::mutex> lock(mutexes[flipFlop]);
physics[i].acceleration = physics[i].acceleration - GRAVITY * deltaTime;
velocities[i].speed = std::clamp(velocities[i].speed + physics[i].acceleration * deltaTime, 0.0f, std::numeric_limits<float>::max());
}
}
void SimulateMotionJob::UpdatePositions(Position positions[], Velocity velocities[], const float deltaTime)
{
for (size_t i = 0; i < NUM_ENTITIES; i++)
{
const size_t flipFlop = i % 2;
std::lock_guard<std::mutex> lock(mutexes[flipFlop]);
positions[i].pos.x += velocities[i].direction.x * velocities[i].speed * deltaTime;
positions[i].pos.y += velocities[i].direction.y * velocities[i].speed * deltaTime;
}
}
void PrintEntities(Position positions[], Velocity velocities[], Physics physics[])
{
for (size_t i = 0; i < NUM_ENTITIES; i++)
{
printf("\nEntity: %i", i);
printf("\nPos: x=%f, y=%f", positions[i].pos.x, positions[i].pos.y);
printf("\nVelocity: x=%f, y=%f, speed=%f", velocities[i].direction.x, velocities[i].direction.y, velocities[i].speed);
printf("\nPhysics: mass=%f, acceleration=%f", physics[i].mass, physics[i].acceleration);
printf("\n----------------------------");
}
}
int main(int argc, char* argv[])
{
Position positions[NUM_ENTITIES];
Velocity velocities[NUM_ENTITIES];
Physics physics[NUM_ENTITIES];
RandomizeJob randomizeJob(positions, velocities, physics,
[](Position resultPositions[], Velocity resultVelocities[], Physics resultPhysics[])
{
PrintEntities(resultPositions, resultVelocities, resultPhysics);
SimulateMotionJob motionJob(resultPositions, resultVelocities, resultPhysics,
[](Position resultPositions[], Velocity resultVelocities[], Physics resultPhysics[])
{
PrintEntities(resultPositions, resultVelocities, resultPhysics);
});
});
return 0;
}