diff --git a/include/physics.hpp b/include/physics.hpp index 2193507..92f96be 100644 --- a/include/physics.hpp +++ b/include/physics.hpp @@ -7,6 +7,7 @@ #include #include #include +#include #include #include #include @@ -146,10 +147,10 @@ class ThreadedPhysics { using Command = std::variant; struct PhysicsState { - std::mutex command_mtx; + TracyLockable(std::mutex, command_mtx); std::queue pending_commands; - std::mutex pos_mtx; + TracyLockable(std::mutex, pos_mtx); std::vector masses; // Read by renderer std::unordered_map state_masses; // Read by renderer std::vector springs; // Read by renderer @@ -160,9 +161,11 @@ class ThreadedPhysics { }; private: - PhysicsState state; std::thread physics; +public: + PhysicsState state; + public: ThreadedPhysics() : physics(PhysicsThread, std::ref(state)) {} @@ -180,6 +183,11 @@ private: static auto PhysicsThread(PhysicsState &state) -> void; public: + auto AddMassCmd(const State &_state) -> void; + + auto AddSpringCmd(const State &a, const State &b) -> void; + + auto ClearCmd() -> void; }; // https://en.cppreference.com/w/cpp/utility/variant/visit diff --git a/include/renderer.hpp b/include/renderer.hpp index 2dfbcaa..642d391 100644 --- a/include/renderer.hpp +++ b/include/renderer.hpp @@ -61,25 +61,29 @@ public: } private: - auto AllocateGraphInstancing(const MassSpringSystem &mass_springs) -> void; + auto AllocateGraphInstancing(const std::vector &masses) -> void; - auto - ReallocateGraphInstancingIfNecessary(const MassSpringSystem &mass_springs) + auto ReallocateGraphInstancingIfNecessary(const std::vector &masses) -> void; public: auto UpdateTextureSizes() -> void; - auto DrawMassSprings(const MassSpringSystem &mass_springs, - const State ¤t_state, const State &starting_state, - const std::unordered_set &winning_states, - const std::unordered_set &visited_states) -> void; + auto DrawMassSprings( + const std::vector &masses, + const std::unordered_map &state_masses, + const std::vector &springs, + const std::unordered_map, int> state_springs, + const State ¤t_state, const State &starting_state, + const std::unordered_set &winning_states, + const std::unordered_set &visited_states) -> void; auto DrawKlotski(const State &state, int hov_x, int hov_y, int sel_x, int sel_y, int block_add_x, int block_add_y, const WinCondition win_condition) -> void; - auto DrawMenu(const MassSpringSystem &mass_springs, int current_preset, + auto DrawMenu(const std::vector &masses, + const std::vector &springs, int current_preset, const State ¤t_state, const std::unordered_set &winning_states) -> void; diff --git a/include/state.hpp b/include/state.hpp index dfd3684..511f509 100644 --- a/include/state.hpp +++ b/include/state.hpp @@ -1,7 +1,6 @@ #ifndef __STATE_HPP_ #define __STATE_HPP_ -#include "config.hpp" #include "physics.hpp" #include "presets.hpp" #include "puzzle.hpp" @@ -10,7 +9,7 @@ class StateManager { public: - MassSpringSystem &mass_springs; + ThreadedPhysics &physics; int current_preset; State starting_state; @@ -23,12 +22,12 @@ public: std::unordered_set visited_states; public: - StateManager(MassSpringSystem &_mass_springs) - : mass_springs(_mass_springs), current_preset(0), + StateManager(ThreadedPhysics &_physics) + : physics(_physics), current_preset(0), starting_state(generators[current_preset]()), current_state(starting_state), previous_state(starting_state), edited(false) { - mass_springs.AddMass(MASS, false, current_state); + physics.AddMassCmd(current_state); } StateManager(const StateManager ©) = delete; diff --git a/src/main.cpp b/src/main.cpp index ae311af..405238e 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,3 +1,4 @@ +#include #include #include #include @@ -37,20 +38,27 @@ auto main(int argc, char *argv[]) -> int { // Game setup OrbitCamera3D camera; Renderer renderer(camera); - MassSpringSystem mass_springs; - StateManager state(mass_springs); + ThreadedPhysics physics; + StateManager state(physics); InputHandler input(state, renderer); + std::vector masses; // Read from physics + std::unordered_map state_masses; // Read from physics + std::vector springs; // Read from physics + std::unordered_map, int> + state_springs; // Read from physics + // Game loop - double timestep_accumulator = 0.0; + // double timestep_accumulator = 0.0; while (!WindowShouldClose()) { - timestep_accumulator += GetFrameTime(); + // timestep_accumulator += GetFrameTime(); // Input update state.previous_state = state.current_state; input.HandleInput(); state.UpdateGraph(); // Add state added after user input + /* // Physics update if (timestep_accumulator > TIMESTEP) { // Do not try to catch up if we're falling behind. Frametimes would get @@ -63,21 +71,43 @@ auto main(int argc, char *argv[]) -> int { timestep_accumulator -= TIMESTEP; } + */ + + // Read positions from physics thread + // TODO: We're reading a HUGE amount of SHIT, because the state + // representations (std::string, ~100 Bytes) are copied ~3 times per + // spring: state_masses and state_springs<, + // int> + // TODO: Don't download each frame if nothing changed, check for update + // first + { + std::lock_guard lock(physics.state.pos_mtx); + masses = physics.state.masses; + state_masses = physics.state.state_masses; + springs = physics.state.springs; + state_springs = physics.state.state_springs; + } // Update the camera after the physics, so target lock is smooth - camera.Update(mass_springs.GetMass(state.current_state).position); + if (state_masses.contains(state.current_state)) { + const Mass ¤t_mass = + masses.at(state_masses.at(state.current_state)); + camera.Update(current_mass.position); + } // Rendering renderer.UpdateTextureSizes(); - renderer.DrawMassSprings(mass_springs, state.current_state, - state.starting_state, state.winning_states, - state.visited_states); + if (masses.size() > 0 && state_masses.contains(state.current_state)) { + renderer.DrawMassSprings(masses, state_masses, springs, state_springs, + state.current_state, state.starting_state, + state.winning_states, state.visited_states); + } renderer.DrawKlotski(state.current_state, input.hov_x, input.hov_y, input.sel_x, input.sel_y, input.block_add_x, input.block_add_y, state.CurrentWinCondition()); - renderer.DrawMenu(mass_springs, state.current_preset, state.current_state, - state.winning_states); + renderer.DrawMenu(masses, springs, state.current_preset, + state.current_state, state.winning_states); renderer.DrawTextures(); } diff --git a/src/physics.cpp b/src/physics.cpp index bb2235f..850cc7f 100644 --- a/src/physics.cpp +++ b/src/physics.cpp @@ -344,15 +344,15 @@ auto ThreadedPhysics::PhysicsThread(ThreadedPhysics::PhysicsState &state) 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(); }, + [&](const struct AddMass &am) { mass_springs.AddMass(am.s); }, + [&](const struct AddSpring &as) { mass_springs.AddSpring(as.a, as.b); }, + [&](const struct ClearGraph &cg) { mass_springs.Clear(); }, }; while (state.running) { // Handle queued commands { - std::lock_guard lock(state.command_mtx); + std::lock_guard lock(state.command_mtx); while (!state.pending_commands.empty()) { Command &cmd = state.pending_commands.front(); cmd.visit(visitor); @@ -373,7 +373,7 @@ auto ThreadedPhysics::PhysicsThread(ThreadedPhysics::PhysicsState &state) // Publish the positions for the renderer (copy) { - std::lock_guard lock(state.pos_mtx); + std::lock_guard lock(state.pos_mtx); state.masses = mass_springs.masses; state.state_masses = mass_springs.state_masses; state.springs = mass_springs.springs; @@ -381,3 +381,24 @@ auto ThreadedPhysics::PhysicsThread(ThreadedPhysics::PhysicsState &state) } } } + +auto ThreadedPhysics::AddMassCmd(const State &_state) -> void { + { + std::lock_guard lock(state.command_mtx); + state.pending_commands.push(AddMass{_state}); + } +} + +auto ThreadedPhysics::AddSpringCmd(const State &a, const State &b) -> void { + { + std::lock_guard lock(state.command_mtx); + state.pending_commands.push(AddSpring{a, b}); + } +} + +auto ThreadedPhysics::ClearCmd() -> void { + { + std::lock_guard lock(state.command_mtx); + state.pending_commands.push(ClearGraph{}); + } +} diff --git a/src/renderer.cpp b/src/renderer.cpp index b21155f..725395e 100644 --- a/src/renderer.cpp +++ b/src/renderer.cpp @@ -33,7 +33,7 @@ auto Renderer::UpdateTextureSizes() -> void { menu_target = LoadRenderTexture(width * 2, MENU_HEIGHT); } -auto Renderer::AllocateGraphInstancing(const MassSpringSystem &mass_springs) +auto Renderer::AllocateGraphInstancing(const std::vector &masses) -> void { cube_instance = GenMeshCube(VERTEX_SIZE, VERTEX_SIZE, VERTEX_SIZE); @@ -48,38 +48,40 @@ auto Renderer::AllocateGraphInstancing(const MassSpringSystem &mass_springs) vertex_mat.maps[MATERIAL_MAP_DIFFUSE].color = VERTEX_COLOR; vertex_mat.shader = instancing_shader; - transforms = (Matrix *)MemAlloc(mass_springs.masses.size() * sizeof(Matrix)); - transforms_size = mass_springs.masses.size(); + transforms = (Matrix *)MemAlloc(masses.size() * sizeof(Matrix)); + transforms_size = masses.size(); } auto Renderer::ReallocateGraphInstancingIfNecessary( - const MassSpringSystem &mass_springs) -> void { - if (transforms_size != mass_springs.masses.size()) { - transforms = (Matrix *)MemRealloc(transforms, mass_springs.masses.size() * - sizeof(Matrix)); - transforms_size = mass_springs.masses.size(); + const std::vector &masses) -> void { + if (transforms_size != masses.size()) { + transforms = + (Matrix *)MemRealloc(transforms, masses.size() * sizeof(Matrix)); + transforms_size = masses.size(); } } -auto Renderer::DrawMassSprings(const MassSpringSystem &mass_springs, - const State ¤t_state, - const State &starting_state, - const std::unordered_set &winning_states, - const std::unordered_set &visited_states) - -> void { +auto Renderer::DrawMassSprings( + const std::vector &masses, + const std::unordered_map &state_masses, + const std::vector &springs, + const std::unordered_map, int> state_springs, + const State ¤t_state, const State &starting_state, + const std::unordered_set &winning_states, + const std::unordered_set &visited_states) -> void { ZoneScoped; // Prepare cube instancing { ZoneNamedN(prepare_masses, "PrepareMasses", true); - if (mass_springs.masses.size() < DRAW_VERTICES_LIMIT) { + if (masses.size() < DRAW_VERTICES_LIMIT) { if (transforms == nullptr) { - AllocateGraphInstancing(mass_springs); + AllocateGraphInstancing(masses); } - ReallocateGraphInstancingIfNecessary(mass_springs); + ReallocateGraphInstancingIfNecessary(masses); int i = 0; - for (const auto &mass : mass_springs.masses) { + for (const auto &mass : masses) { transforms[i] = MatrixTranslate(mass.position.x, mass.position.y, mass.position.z); ++i; @@ -96,10 +98,10 @@ auto Renderer::DrawMassSprings(const MassSpringSystem &mass_springs, { ZoneNamedN(draw_springs, "DrawSprings", true); rlBegin(RL_LINES); - for (const auto &spring : mass_springs.springs) { + for (const auto &spring : springs) { // We have to do a lookup of the actual mass object, which is slow :( - const Mass &a = mass_springs.masses.at(spring.mass_a); - const Mass &b = mass_springs.masses.at(spring.mass_b); + const Mass &a = masses.at(spring.mass_a); + const Mass &b = masses.at(spring.mass_b); rlColor4ub(EDGE_COLOR.r, EDGE_COLOR.g, EDGE_COLOR.b, EDGE_COLOR.a); rlVertex3f(a.position.x, a.position.y, a.position.z); rlVertex3f(b.position.x, b.position.y, b.position.z); @@ -110,20 +112,19 @@ auto Renderer::DrawMassSprings(const MassSpringSystem &mass_springs, // Draw masses (instanced) { ZoneNamedN(draw_masses, "DrawMasses", true); - if (mass_springs.masses.size() < DRAW_VERTICES_LIMIT) { + if (masses.size() < DRAW_VERTICES_LIMIT) { // NOTE: I don't know if drawing all this inside a shader would make it // much faster... The amount of data sent to the GPU would be // reduced (just positions instead of matrices), but is this // noticable for < 100000 cubes? - DrawMeshInstanced(cube_instance, vertex_mat, transforms, - mass_springs.masses.size()); + DrawMeshInstanced(cube_instance, vertex_mat, transforms, masses.size()); } } // Mark winning states if (mark_solutions || connect_solutions) { for (const auto &state : winning_states) { - const Mass &winning_mass = mass_springs.GetMass(state); + const Mass &winning_mass = masses.at(state_masses.at(state)); if (mark_solutions) { DrawCube(winning_mass.position, 2 * VERTEX_SIZE, 2 * VERTEX_SIZE, 2 * VERTEX_SIZE, BLUE); @@ -131,26 +132,26 @@ auto Renderer::DrawMassSprings(const MassSpringSystem &mass_springs, if (connect_solutions) { DrawLine3D(winning_mass.position, - mass_springs.GetMass(current_state).position, PURPLE); + masses.at(state_masses.at(current_state)).position, PURPLE); } } } // Mark visited states for (const auto &state : visited_states) { - const Mass &visited_mass = mass_springs.GetMass(state); + const Mass &visited_mass = masses.at(state_masses.at(state)); DrawCube(visited_mass.position, VERTEX_SIZE * 1.5, VERTEX_SIZE * 1.5, VERTEX_SIZE * 1.5, PURPLE); } // Mark starting state - const Mass &starting_mass = mass_springs.GetMass(starting_state); + const Mass &starting_mass = masses.at(state_masses.at(starting_state)); DrawCube(starting_mass.position, VERTEX_SIZE * 2, VERTEX_SIZE * 2, VERTEX_SIZE * 2, ORANGE); // Mark current state - const Mass ¤t_mass = mass_springs.GetMass(current_state); + const Mass ¤t_mass = masses.at(state_masses.at(current_state)); DrawCube(current_mass.position, VERTEX_SIZE * 2, VERTEX_SIZE * 2, VERTEX_SIZE * 2, RED); @@ -250,8 +251,9 @@ auto Renderer::DrawKlotski(const State &state, int hov_x, int hov_y, int sel_x, EndTextureMode(); } -auto Renderer::DrawMenu(const MassSpringSystem &mass_springs, - int current_preset, const State ¤t_state, +auto Renderer::DrawMenu(const std::vector &masses, + const std::vector &springs, int current_preset, + const State ¤t_state, const std::unordered_set &winning_states) -> void { ZoneScoped; @@ -277,8 +279,7 @@ auto Renderer::DrawMenu(const MassSpringSystem &mass_springs, draw_btn(0, 0, std::format("States: {}, Transitions: {}, Winning: {}", - mass_springs.masses.size(), mass_springs.springs.size(), - winning_states.size()), + masses.size(), springs.size(), winning_states.size()), DARKGREEN); draw_btn( 0, 1, diff --git a/src/state.cpp b/src/state.cpp index 4ca7007..a49b663 100644 --- a/src/state.cpp +++ b/src/state.cpp @@ -1,8 +1,8 @@ #include "state.hpp" -#include "config.hpp" #include "presets.hpp" #include "tracy.hpp" +#include #include auto StateManager::LoadPreset(int preset) -> void { @@ -38,30 +38,18 @@ auto StateManager::FillGraph() -> void { std::pair, std::vector>> closure = current_state.Closure(); for (const auto &state : closure.first) { - mass_springs.AddMass(MASS, false, state); + physics.AddMassCmd(state); } for (const auto &[from, to] : closure.second) { - mass_springs.AddSpring(from, to, SPRING_CONSTANT, DAMPENING_CONSTANT, - REST_LENGTH); + physics.AddSpringCmd(from, to); } - std::cout << "Inserted " << mass_springs.masses.size() << " masses and " - << mass_springs.springs.size() << " springs." << std::endl; FindWinningStates(); - std::cout << "Consuming " - << sizeof(decltype(*mass_springs.masses.begin())) * - mass_springs.masses.size() - << " Bytes for masses." << std::endl; - std::cout << "Consuming " - << sizeof(decltype(*mass_springs.springs.begin())) * - mass_springs.springs.size() - << " Bytes for springs." << std::endl; } auto StateManager::UpdateGraph() -> void { if (previous_state != current_state) { - mass_springs.AddMass(MASS, false, current_state); - mass_springs.AddSpring(current_state, previous_state, SPRING_CONSTANT, - DAMPENING_CONSTANT, REST_LENGTH); + physics.AddMassCmd(current_state); + physics.AddSpringCmd(current_state, previous_state); if (win_conditions[current_preset](current_state)) { winning_states.insert(current_state); } @@ -72,8 +60,8 @@ auto StateManager::UpdateGraph() -> void { auto StateManager::ClearGraph() -> void { winning_states.clear(); visited_states.clear(); - mass_springs.Clear(); - mass_springs.AddMass(MASS, false, current_state); + physics.ClearCmd(); + physics.AddMassCmd(current_state); // The previous_state is no longer in the graph previous_state = current_state; @@ -84,14 +72,16 @@ auto StateManager::ClearGraph() -> void { auto StateManager::FindWinningStates() -> void { winning_states.clear(); - for (const auto &[state, mass] : mass_springs.state_masses) { + std::unordered_map state_masses; + { + std::lock_guard lock(physics.state.pos_mtx); + state_masses = physics.state.state_masses; + } + for (const auto &[state, mass] : state_masses) { if (win_conditions[current_preset](state)) { winning_states.insert(state); } } - - std::cout << "Found " << winning_states.size() << " winning states." - << std::endl; } auto StateManager::CurrentGenerator() -> StateGenerator {