wip: wait until data is ready and has been consumed (couples physics and rendering thread again)

This commit is contained in:
2026-02-24 13:16:00 +01:00
parent 74065b7ea2
commit e7d4170b77
6 changed files with 37 additions and 22 deletions

View File

@ -2,6 +2,7 @@
#define __PHYSICS_HPP_
#include <atomic>
#include <condition_variable>
#include <mutex>
#include <queue>
#include <raylib.h>
@ -150,7 +151,11 @@ class ThreadedPhysics {
TracyLockable(std::mutex, command_mtx);
std::queue<Command> pending_commands;
TracyLockable(std::mutex, pos_mtx);
TracyLockable(std::mutex, data_mtx);
std::condition_variable_any data_ready_cnd;
std::condition_variable_any data_consumed_cnd;
bool data_ready = false;
bool data_consumed = true;
std::vector<Mass> masses; // Read by renderer
std::unordered_map<State, int> state_masses; // Read by renderer
std::vector<Spring> springs; // Read by renderer

View File

@ -61,10 +61,9 @@ public:
}
private:
auto AllocateGraphInstancing(const std::vector<Mass> &masses) -> void;
auto AllocateGraphInstancing(std::size_t size) -> void;
auto ReallocateGraphInstancingIfNecessary(const std::vector<Mass> &masses)
-> void;
auto ReallocateGraphInstancingIfNecessary(std::size_t size) -> void;
public:
auto UpdateTextureSizes() -> void;

View File

@ -81,12 +81,19 @@ auto main(int argc, char *argv[]) -> int {
// TODO: Don't download each frame if nothing changed, check for update
// first
{
std::lock_guard<LockableBase(std::mutex)> lock(physics.state.pos_mtx);
std::unique_lock<LockableBase(std::mutex)> lock(physics.state.data_mtx);
physics.state.data_ready_cnd.wait(
lock, [&] { return physics.state.data_ready; });
masses = physics.state.masses;
state_masses = physics.state.state_masses;
springs = physics.state.springs;
state_springs = physics.state.state_springs;
physics.state.data_ready = false;
physics.state.data_consumed = true;
}
physics.state.data_consumed_cnd.notify_one();
// Update the camera after the physics, so target lock is smooth
if (state_masses.contains(state.current_state)) {

View File

@ -349,7 +349,7 @@ auto ThreadedPhysics::PhysicsThread(ThreadedPhysics::PhysicsState &state)
[&](const struct ClearGraph &cg) { mass_springs.Clear(); },
};
while (state.running) {
while (state.running.load()) {
// Handle queued commands
{
std::lock_guard<LockableBase(std::mutex)> lock(state.command_mtx);
@ -366,7 +366,6 @@ auto ThreadedPhysics::PhysicsThread(ThreadedPhysics::PhysicsState &state)
}
// Physics update
// TODO: I need this thread to run at a constant rate
mass_springs.ClearForces();
mass_springs.CalculateSpringForces();
mass_springs.CalculateRepulsionForces();
@ -380,12 +379,20 @@ auto ThreadedPhysics::PhysicsThread(ThreadedPhysics::PhysicsState &state)
// Publish the positions for the renderer (copy)
{
std::lock_guard<LockableBase(std::mutex)> lock(state.pos_mtx);
std::unique_lock<LockableBase(std::mutex)> lock(state.data_mtx);
state.data_consumed_cnd.wait(lock, [&] { return state.data_consumed; });
state.masses = mass_springs.masses;
state.state_masses = mass_springs.state_masses;
state.springs = mass_springs.springs;
state.state_springs = mass_springs.state_springs;
state.data_ready = true;
state.data_consumed = false;
}
// Notify the rendering thread that new data is available
state.data_ready_cnd.notify_one();
}
}

View File

@ -33,8 +33,7 @@ auto Renderer::UpdateTextureSizes() -> void {
menu_target = LoadRenderTexture(width * 2, MENU_HEIGHT);
}
auto Renderer::AllocateGraphInstancing(const std::vector<Mass> &masses)
-> void {
auto Renderer::AllocateGraphInstancing(std::size_t size) -> void {
cube_instance = GenMeshCube(VERTEX_SIZE, VERTEX_SIZE, VERTEX_SIZE);
instancing_shader = LoadShader("shader/instancing_vertex.glsl",
@ -48,16 +47,14 @@ auto Renderer::AllocateGraphInstancing(const std::vector<Mass> &masses)
vertex_mat.maps[MATERIAL_MAP_DIFFUSE].color = VERTEX_COLOR;
vertex_mat.shader = instancing_shader;
transforms = (Matrix *)MemAlloc(masses.size() * sizeof(Matrix));
transforms_size = masses.size();
transforms = (Matrix *)MemAlloc(size * sizeof(Matrix));
transforms_size = size;
}
auto Renderer::ReallocateGraphInstancingIfNecessary(
const std::vector<Mass> &masses) -> void {
if (transforms_size != masses.size()) {
transforms =
(Matrix *)MemRealloc(transforms, masses.size() * sizeof(Matrix));
transforms_size = masses.size();
auto Renderer::ReallocateGraphInstancingIfNecessary(std::size_t size) -> void {
if (transforms_size != size) {
transforms = (Matrix *)MemRealloc(transforms, size * sizeof(Matrix));
transforms_size = size;
}
}
@ -76,9 +73,9 @@ auto Renderer::DrawMassSprings(
ZoneNamedN(prepare_masses, "PrepareMasses", true);
if (masses.size() < DRAW_VERTICES_LIMIT) {
if (transforms == nullptr) {
AllocateGraphInstancing(masses);
AllocateGraphInstancing(masses.size());
}
ReallocateGraphInstancingIfNecessary(masses);
ReallocateGraphInstancingIfNecessary(masses.size());
int i = 0;
for (const auto &mass : masses) {

View File

@ -43,7 +43,7 @@ auto StateManager::FillGraph() -> void {
// TODO: We have only dispatched the commands, the states won't be downloaded
// when calling this... Make FindWinningStates() another command?
// Or recalculate whenever masses.size() changes?
FindWinningStates();
// FindWinningStates();
}
auto StateManager::UpdateGraph() -> void {
@ -74,7 +74,7 @@ auto StateManager::FindWinningStates() -> void {
winning_states.clear();
std::unordered_map<State, int> state_masses;
{
std::lock_guard<LockableBase(std::mutex)> lock(physics.state.pos_mtx);
std::lock_guard<LockableBase(std::mutex)> lock(physics.state.data_mtx);
state_masses = physics.state.state_masses;
}
for (const auto &[state, mass] : state_masses) {