implement threaded physics (decoupled from rendering thread) - not yet integrated

This commit is contained in:
2026-02-24 01:02:53 +01:00
parent 3e87bbb6a5
commit 39c0b58f3f
4 changed files with 113 additions and 14 deletions

View File

@ -1,7 +1,7 @@
cmake_minimum_required(VERSION 3.25) cmake_minimum_required(VERSION 3.25)
project(MassSprings) project(MassSprings)
set(CMAKE_CXX_STANDARD 23) set(CMAKE_CXX_STANDARD 26)
set(CMAKE_EXPORT_COMPILE_COMMANDS ON) set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
find_package(raylib REQUIRED) find_package(raylib REQUIRED)

View File

@ -1,9 +1,14 @@
#ifndef __PHYSICS_HPP_ #ifndef __PHYSICS_HPP_
#define __PHYSICS_HPP_ #define __PHYSICS_HPP_
#include <atomic>
#include <mutex>
#include <queue>
#include <raylib.h> #include <raylib.h>
#include <raymath.h> #include <raymath.h>
#include <thread>
#include <unordered_map> #include <unordered_map>
#include <variant>
#include <vector> #include <vector>
#include "config.hpp" #include "config.hpp"
@ -105,14 +110,13 @@ private:
#endif #endif
public: public:
auto AddMass(float mass, bool fixed, const State &state) -> void; auto AddMass(const State &state) -> void;
auto GetMass(const State &state) -> Mass &; auto GetMass(const State &state) -> Mass &;
auto GetMass(const State &state) const -> const Mass &; auto GetMass(const State &state) const -> const Mass &;
auto AddSpring(const State &massA, const State &massB, float spring_constant, auto AddSpring(const State &massA, const State &massB) -> void;
float dampening_constant, float rest_length) -> void;
auto Clear() -> void; auto Clear() -> void;
@ -129,4 +133,58 @@ public:
#endif #endif
}; };
class ThreadedPhysics {
struct AddMass {
State s;
};
struct AddSpring {
State a;
State b;
};
struct ClearGraph {};
using Command = std::variant<AddMass, AddSpring, ClearGraph>;
struct PhysicsState {
std::mutex command_mtx;
std::queue<Command> pending_commands;
std::mutex pos_mtx;
std::vector<Mass> masses; // Read by renderer
std::unordered_map<State, int> state_masses; // Read by renderer
std::vector<Spring> springs; // Read by renderer
std::unordered_map<std::pair<State, State>, int>
state_springs; // Read by renderer
std::atomic<bool> running{true};
};
private:
PhysicsState state;
std::thread physics;
public:
ThreadedPhysics() : physics(PhysicsThread, std::ref(state)) {}
ThreadedPhysics(const ThreadedPhysics &copy) = delete;
ThreadedPhysics &operator=(const ThreadedPhysics &copy) = delete;
ThreadedPhysics(ThreadedPhysics &&move) = delete;
ThreadedPhysics &operator=(ThreadedPhysics &&move) = delete;
~ThreadedPhysics() {
state.running = false;
physics.join();
}
private:
static auto PhysicsThread(PhysicsState &state) -> void;
public:
};
// https://en.cppreference.com/w/cpp/utility/variant/visit
template <class... Ts> struct overloads : Ts... {
using Ts::operator()...;
};
#endif #endif

View File

@ -1,3 +1,7 @@
#include <raylib.h>
#include <raymath.h>
#include <tracy/Tracy.hpp>
#include "config.hpp" #include "config.hpp"
#include "input.hpp" #include "input.hpp"
#include "physics.hpp" #include "physics.hpp"
@ -5,10 +9,6 @@
#include "state.hpp" #include "state.hpp"
#include "tracy.hpp" #include "tracy.hpp"
#include <raylib.h>
#include <raymath.h>
#include <tracy/Tracy.hpp>
// TODO: Klotski state file loading // TODO: Klotski state file loading
// - File should contain a single state per line, multiple lines possible // - File should contain a single state per line, multiple lines possible
// - If a file is loaded, the presets should be replaced with the states // - If a file is loaded, the presets should be replaced with the states

View File

@ -5,6 +5,7 @@
#include <algorithm> #include <algorithm>
#include <cfloat> #include <cfloat>
#include <cstddef> #include <cstddef>
#include <mutex>
#include <raylib.h> #include <raylib.h>
#include <raymath.h> #include <raymath.h>
#include <tracy/Tracy.hpp> #include <tracy/Tracy.hpp>
@ -69,8 +70,7 @@ auto Spring::CalculateSpringForce(Mass &_mass_a, Mass &_mass_b) const -> void {
_mass_b.force = Vector3Add(_mass_b.force, force_b); _mass_b.force = Vector3Add(_mass_b.force, force_b);
} }
auto MassSpringSystem::AddMass(float mass, bool fixed, const State &state) auto MassSpringSystem::AddMass(const State &state) -> void {
-> void {
if (!state_masses.contains(state)) { if (!state_masses.contains(state)) {
masses.emplace_back(Vector3Zero()); masses.emplace_back(Vector3Zero());
std::size_t idx = masses.size() - 1; std::size_t idx = masses.size() - 1;
@ -86,9 +86,7 @@ auto MassSpringSystem::GetMass(const State &state) const -> const Mass & {
return masses.at(state_masses.at(state)); return masses.at(state_masses.at(state));
} }
auto MassSpringSystem::AddSpring(const State &state_a, const State &state_b, auto MassSpringSystem::AddSpring(const State &state_a, const State &state_b)
float spring_constant,
float dampening_constant, float rest_length)
-> void { -> void {
std::pair<State, State> key = std::make_pair(state_a, state_b); std::pair<State, State> key = std::make_pair(state_a, state_b);
if (!state_springs.contains(key)) { if (!state_springs.contains(key)) {
@ -340,3 +338,46 @@ auto MassSpringSystem::InvalidateGrid() -> void {
last_springs_count = 0; last_springs_count = 0;
} }
#endif #endif
auto ThreadedPhysics::PhysicsThread(ThreadedPhysics::PhysicsState &state)
-> void {
MassSpringSystem mass_springs;
const auto visitor = overloads{
[&](const AddMass &am) { mass_springs.AddMass(am.s); },
[&](const AddSpring &as) { mass_springs.AddSpring(as.a, as.b); },
[&](const ClearGraph &cg) { mass_springs.Clear(); },
};
while (state.running) {
// Handle queued commands
{
std::lock_guard<std::mutex> lock(state.command_mtx);
while (!state.pending_commands.empty()) {
Command &cmd = state.pending_commands.front();
cmd.visit(visitor);
state.pending_commands.pop();
}
}
if (mass_springs.masses.empty()) {
std::this_thread::sleep_for(std::chrono::milliseconds(1));
continue;
}
// Physics update
mass_springs.ClearForces();
mass_springs.CalculateSpringForces();
mass_springs.CalculateRepulsionForces();
mass_springs.VerletUpdate(TIMESTEP * SIM_SPEED);
// Publish the positions for the renderer (copy)
{
std::lock_guard<std::mutex> lock(state.pos_mtx);
state.masses = mass_springs.masses;
state.state_masses = mass_springs.state_masses;
state.springs = mass_springs.springs;
state.state_springs = mass_springs.state_springs;
}
}
}