Compare commits

..

2 Commits

Author SHA1 Message Date
592c4b6cc0 massive state space solver improvement: supercomp took ~10s, now ~40ms
achieved by imposing a 15 block limit on each board and changing the
internal representation from std::string to 4x uint64_t
2026-03-02 05:26:18 +01:00
c7361fe47e fix board goal rendering bug if no target block exists or board has no win condition 2026-03-01 00:30:06 +01:00
16 changed files with 297 additions and 350 deletions

View File

@ -9,8 +9,8 @@ if(POLICY CMP0167)
endif() endif()
option(DISABLE_BACKWARD "Disable backward stacktrace printer" OFF) option(DISABLE_BACKWARD "Disable backward stacktrace printer" OFF)
option(DISABLE_TRACY "Disable the Tracy profiler client" OFF) option(DISABLE_TRACY "Disable the Tracy profiler client" ON)
option(DISABLE_TESTS "Disable building and running tests" OFF) option(DISABLE_TESTS "Disable building and running tests" ON)
# Headers + Sources # Headers + Sources
set(SOURCES set(SOURCES

View File

@ -27,7 +27,6 @@ rec {
config = "x86_64-w64-mingw32"; config = "x86_64-w64-mingw32";
}; };
config.allowUnfree = true; config.allowUnfree = true;
overlays = [];
}; };
# =========================================================================================== # ===========================================================================================
@ -191,15 +190,7 @@ rec {
raylib raylib
raygui raygui
thread-pool thread-pool
boost
# Disable stacktrace since that's platform dependant and won't cross compile to windows
# https://github.com/NixOS/nixpkgs/blob/master/pkgs/development/libraries/boost/generic.nix#L43
(boost.override {
enableShared = false;
extraB2Args = [
"--without-stacktrace"
];
})
]; ];
cmakeFlags = [ cmakeFlags = [
@ -380,4 +371,4 @@ rec {
}; };
} }
); );
} }

View File

@ -5,23 +5,6 @@
#define THREADPOOL // Enable physics threadpool #define THREADPOOL // Enable physics threadpool
#ifdef THREADPOOL
#if defined(_WIN32)
#define NOGDI // All GDI defines and routines
#define NOUSER // All USER defines and routines
#endif
#define BS_THREAD_POOL_NATIVE_EXTENSIONS
// ReSharper disable once CppUnusedIncludeDirective
#include <BS_thread_pool.hpp>
#if defined(_WIN32) // raylib uses these names as function parameters
#undef near
#undef far
#endif
#endif
// TODO: Using the octree from the last frame completely breaks the physics :/
// #define ASYNC_OCTREE
// Gets set by CMake // Gets set by CMake
// #define BACKWARD // Enable pretty stack traces // #define BACKWARD // Enable pretty stack traces
// #define TRACY // Enable tracy profiling support // #define TRACY // Enable tracy profiling support
@ -46,7 +29,7 @@ constexpr float CAMERA_FOV = 90.0;
constexpr float FOV_SPEED = 1.0; constexpr float FOV_SPEED = 1.0;
constexpr float MIN_FOV = 10.0; constexpr float MIN_FOV = 10.0;
constexpr float MAX_FOV = 180.0; constexpr float MAX_FOV = 180.0;
constexpr float CAMERA_DISTANCE = 150.0; constexpr float CAMERA_DISTANCE = 20.0;
constexpr float ZOOM_SPEED = 2.5; constexpr float ZOOM_SPEED = 2.5;
constexpr float MIN_CAMERA_DISTANCE = 2.0; constexpr float MIN_CAMERA_DISTANCE = 2.0;
constexpr float MAX_CAMERA_DISTANCE = 2000.0; constexpr float MAX_CAMERA_DISTANCE = 2000.0;

View File

@ -78,7 +78,7 @@ public:
// Camera // Camera
bool camera_lock = true; bool camera_lock = true;
bool camera_mass_center_lock = true; bool camera_mass_center_lock = false;
bool camera_panning = false; bool camera_panning = false;
bool camera_rotating = false; bool camera_rotating = false;

View File

@ -2,13 +2,49 @@
#define MASS_SPRING_SYSTEM_HPP_ #define MASS_SPRING_SYSTEM_HPP_
#include "octree.hpp" #include "octree.hpp"
#include "util.hpp"
#include "config.hpp" #include "config.hpp"
#include <raylib.h> #include <raylib.h>
#include <raymath.h>
#ifdef THREADPOOL
#if defined(_WIN32)
#define NOGDI // All GDI defines and routines
#define NOUSER // All USER defines and routines
#endif
#define BS_THREAD_POOL_NATIVE_EXTENSIONS
#include <BS_thread_pool.hpp>
#if defined(_WIN32) // raylib uses these names as function parameters
#undef near
#undef far
#endif
#endif
class mass_spring_system class mass_spring_system
{ {
public: public:
class mass
{
public:
Vector3 position = Vector3Zero();
Vector3 previous_position = Vector3Zero(); // for verlet integration
Vector3 velocity = Vector3Zero();
Vector3 force = Vector3Zero();
public:
mass() = delete;
explicit mass(const Vector3 _position)
: position(_position), previous_position(_position) {}
public:
auto clear_force() -> void;
auto calculate_velocity(float delta_time) -> void;
auto calculate_position(float delta_time) -> void;
auto verlet_update(float delta_time) -> void;
};
class spring class spring
{ {
public: public:
@ -18,6 +54,9 @@ public:
public: public:
spring(const size_t _a, const size_t _b) spring(const size_t _a, const size_t _b)
: a(_a), b(_b) {} : a(_a), b(_b) {}
public:
static auto calculate_spring_force(mass& _a, mass& _b) -> void;
}; };
private: private:
@ -26,25 +65,26 @@ private:
#endif #endif
public: public:
static constexpr int SMALL_TASK_BLOCK_SIZE = 256;
static constexpr int LARGE_TASK_BLOCK_SIZE = 256;
octree tree; octree tree;
// This is the main ownership of all the states/masses/springs. // This is the main ownership of all the states/masses/springs.
std::vector<Vector3> positions; std::vector<mass> masses;
std::vector<Vector3> previous_positions; // for verlet integration
std::vector<Vector3> velocities;
std::vector<Vector3> forces;
std::vector<spring> springs; std::vector<spring> springs;
public: public:
mass_spring_system() mass_spring_system()
#ifdef THREADPOOL #ifdef THREADPOOL
: threads(std::thread::hardware_concurrency() - 2, set_mass_springs_pool_thread_name) : threads(std::thread::hardware_concurrency() - 1, set_thread_name)
#endif #endif
{} {
infoln("Using Barnes-Hut + Octree repulsion force calculation.");
#ifdef THREADPOOL
infoln("Thread-pool: {} threads.", threads.get_thread_count());
#else
infoln("Thread-pool: Disabled.");
#endif
}
mass_spring_system(const mass_spring_system& copy) = delete; mass_spring_system(const mass_spring_system& copy) = delete;
auto operator=(const mass_spring_system& copy) -> mass_spring_system& = delete; auto operator=(const mass_spring_system& copy) -> mass_spring_system& = delete;
@ -53,22 +93,20 @@ public:
private: private:
#ifdef THREADPOOL #ifdef THREADPOOL
static auto set_mass_springs_pool_thread_name(size_t idx) -> void; static auto set_thread_name(size_t idx) -> void;
#endif #endif
auto build_octree() -> void;
public: public:
auto clear() -> void; auto clear() -> void;
auto add_mass() -> void; auto add_mass() -> void;
auto add_spring(size_t a, size_t b) -> void; auto add_spring(size_t a, size_t b) -> void;
auto clear_forces() -> void; auto clear_forces() -> void;
auto calculate_spring_force(size_t s) -> void;
auto calculate_spring_forces() -> void; auto calculate_spring_forces() -> void;
auto calculate_repulsion_forces() -> void; auto calculate_repulsion_forces() -> void;
auto integrate_velocity(size_t m, float dt) -> void; auto verlet_update(float delta_time) -> void;
auto integrate_position(size_t m, float dt) -> void;
auto verlet_update(size_t m, float dt) -> void;
auto update(float dt) -> void;
auto center_masses() -> void; auto center_masses() -> void;
}; };

View File

@ -31,20 +31,22 @@ public:
public: public:
octree() = default; octree() = default;
// octree(const octree& copy) = delete; octree(const octree& copy) = delete;
// auto operator=(const octree& copy) -> octree& = delete; auto operator=(const octree& copy) -> octree& = delete;
// octree(octree&& move) = delete; octree(octree&& move) = delete;
// auto operator=(octree&& move) -> octree& = delete; auto operator=(octree&& move) -> octree& = delete;
public: public:
auto create_empty_leaf(const Vector3& box_min, const Vector3& box_max) -> int;
[[nodiscard]] auto get_octant(int node_idx, const Vector3& pos) const -> int; [[nodiscard]] auto get_octant(int node_idx, const Vector3& pos) const -> int;
[[nodiscard]] auto get_child_bounds(int node_idx, int octant) const [[nodiscard]] auto get_child_bounds(int node_idx, int octant) const
-> std::pair<Vector3, Vector3>; -> std::pair<Vector3, Vector3>;
auto create_empty_leaf(const Vector3& box_min, const Vector3& box_max) -> int;
auto insert(int node_idx, int mass_id, const Vector3& pos, float mass, int depth) -> void; auto insert(int node_idx, int mass_id, const Vector3& pos, float mass, int depth) -> void;
static auto build_octree(octree& t, const std::vector<Vector3>& positions) -> void;
[[nodiscard]] auto calculate_force(int node_idx, const Vector3& pos) const -> Vector3; [[nodiscard]] auto calculate_force(int node_idx, const Vector3& pos) const -> Vector3;
}; };
#endif #endif

View File

@ -1,8 +1,6 @@
#ifndef PHYSICS_HPP_ #ifndef PHYSICS_HPP_
#define PHYSICS_HPP_ #define PHYSICS_HPP_
#include "config.hpp"
#include <atomic> #include <atomic>
#include <condition_variable> #include <condition_variable>
#include <mutex> #include <mutex>
@ -82,16 +80,15 @@ public:
} }
private: private:
#ifdef ASYNC_OCTREE
static auto set_octree_pool_thread_name(size_t idx) -> void;
#endif
static auto physics_thread(physics_state& state) -> void; static auto physics_thread(physics_state& state) -> void;
public: public:
auto clear_cmd() -> void;
auto add_mass_cmd() -> void; auto add_mass_cmd() -> void;
auto add_spring_cmd(size_t a, size_t b) -> void; auto add_spring_cmd(size_t a, size_t b) -> void;
auto clear_cmd() -> void;
auto add_mass_springs_cmd(size_t num_masses, const std::vector<std::pair<size_t, size_t>>& springs) -> void; auto add_mass_springs_cmd(size_t num_masses, const std::vector<std::pair<size_t, size_t>>& springs) -> void;
}; };

View File

@ -140,13 +140,6 @@ inline auto ansi_reset() -> std::string
} }
// std::println doesn't work with mingw // std::println doesn't work with mingw
template <typename... Args>
auto traceln(std::format_string<Args...> fmt, Args&&... args) -> void
{
std::cout << std::format("[{}TRACE{}]: ", ansi_bold_fg(fg_cyan), ansi_reset()) << std::format(
fmt, std::forward<Args>(args)...) << std::endl;
}
template <typename... Args> template <typename... Args>
auto infoln(std::format_string<Args...> fmt, Args&&... args) -> void auto infoln(std::format_string<Args...> fmt, Args&&... args) -> void
{ {

View File

@ -278,7 +278,7 @@ auto input_handler::toggle_camera_projection() const -> void
auto input_handler::move_block_nor() -> void auto input_handler::move_block_nor() -> void
{ {
const puzzle& current = state.get_current_state(); const puzzle& current = state.get_current_state();
const std::optional<puzzle>& next = current.try_move_block_at(sel_x, sel_y, nor); const std::optional<puzzle>& next = current.try_move_block_at_fast(sel_x, sel_y, nor);
if (!next) { if (!next) {
return; return;
} }
@ -290,7 +290,7 @@ auto input_handler::move_block_nor() -> void
auto input_handler::move_block_wes() -> void auto input_handler::move_block_wes() -> void
{ {
const puzzle& current = state.get_current_state(); const puzzle& current = state.get_current_state();
const std::optional<puzzle>& next = current.try_move_block_at(sel_x, sel_y, wes); const std::optional<puzzle>& next = current.try_move_block_at_fast(sel_x, sel_y, wes);
if (!next) { if (!next) {
return; return;
} }
@ -302,7 +302,7 @@ auto input_handler::move_block_wes() -> void
auto input_handler::move_block_sou() -> void auto input_handler::move_block_sou() -> void
{ {
const puzzle& current = state.get_current_state(); const puzzle& current = state.get_current_state();
const std::optional<puzzle>& next = current.try_move_block_at(sel_x, sel_y, sou); const std::optional<puzzle>& next = current.try_move_block_at_fast(sel_x, sel_y, sou);
if (!next) { if (!next) {
return; return;
} }
@ -314,7 +314,7 @@ auto input_handler::move_block_sou() -> void
auto input_handler::move_block_eas() -> void auto input_handler::move_block_eas() -> void
{ {
const puzzle& current = state.get_current_state(); const puzzle& current = state.get_current_state();
const std::optional<puzzle>& next = current.try_move_block_at(sel_x, sel_y, eas); const std::optional<puzzle>& next = current.try_move_block_at_fast(sel_x, sel_y, eas);
if (!next) { if (!next) {
return; return;
} }

View File

@ -14,21 +14,34 @@
#include <tracy/Tracy.hpp> #include <tracy/Tracy.hpp>
#endif #endif
// TODO: Add state space generation time to debug overlay // TODO: Add some popups (my split between input.cpp/gui.cpp makes this ugly)
// - Clear graph: Notify that this will clear the visited states and move
// history
// - Reset state: Notify that this will reset the move count
// TODO: Implement state discovery/enumeration // TODO: Reduce memory usage
// - Find all possible initial board states (single one for each possible statespace). // - The memory model of the puzzle board is terrible (bitboards?)
// Currently wer're just finding all states given the initial state
// - Would allow to generate random puzzles with a certain move count
// TODO: Move selection accordingly when undoing moves (need to diff two states and get the moved blocks) // TODO: Improve solver
// - Move discovery is terrible
// - Instead of trying each direction for each block, determine the
// possible moves more efficiently (requires a different memory model)
// - Implement state discovery/enumeration
// - Find all possible initial board states (single one for each
// possible statespace). Currently wer're just finding all states
// given the initial state
// - Would allow to generate random puzzles with a certain move count
// TODO: Move selection accordingly when undoing moves (need to diff two states
// and get the moved blocks)
// TODO: Click states in the graph to display them in the board // TODO: Click states in the graph to display them in the board
// NOTE: Tracy uses a huge amount of memory. For longer testing disable Tracy.
// For profiling explore_state_space // For profiling explore_state_space
auto main2(int argc, char* argv[]) -> int auto main2(int argc, char* argv[]) -> int
{ {
// Supercompo
const puzzle p = puzzle( const puzzle p = puzzle(
"S:[4x5] G:[1,3] M:[F] B:[{_ 2X2 _ _} {1x1 _ _ 1x1} {1x2 2x1 _ 1x2} {_ 2x1 _ _} {1x1 2x1 _ 1x1}]"); "S:[4x5] G:[1,3] M:[F] B:[{_ 2X2 _ _} {1x1 _ _ 1x1} {1x2 2x1 _ 1x2} {_ 2x1 _ _} {1x1 2x1 _ 1x1}]");
@ -60,12 +73,6 @@ auto main(int argc, char* argv[]) -> int
infoln("Tracy adapter disabled."); infoln("Tracy adapter disabled.");
#endif #endif
#ifdef THREADPOOL
infoln("Thread-pool enabled.");
#else
infoln("Thread-pool disabled.");
#endif
// RayLib window setup // RayLib window setup
SetTraceLogLevel(LOG_ERROR); SetTraceLogLevel(LOG_ERROR);
SetConfigFlags(FLAG_VSYNC_HINT); SetConfigFlags(FLAG_VSYNC_HINT);
@ -143,8 +150,8 @@ auto main(int argc, char* argv[]) -> int
// Update the camera after the physics, so target lock is smooth // Update the camera after the physics, so target lock is smooth
size_t current_index = state.get_current_index(); size_t current_index = state.get_current_index();
if (masses.size() > current_index) { if (masses.size() > current_index) {
const Vector3& current_mass = masses[current_index]; const mass_spring_system::mass& current_mass = mass_spring_system::mass(masses.at(current_index));
camera.update(current_mass, mass_center, input.camera_lock, input.camera_mass_center_lock); camera.update(current_mass.position, mass_center, input.camera_lock, input.camera_mass_center_lock);
} }
// Rendering // Rendering

View File

@ -1,77 +1,67 @@
#include "mass_spring_system.hpp" #include "mass_spring_system.hpp"
#include "config.hpp" #include "config.hpp"
#include "util.hpp"
#include <cfloat> #include <cfloat>
#include <cstring>
#ifdef TRACY #ifdef TRACY
#include <tracy/Tracy.hpp> #include <tracy/Tracy.hpp>
#endif #endif
auto mass_spring_system::calculate_spring_force(const size_t s) -> void auto mass_spring_system::mass::clear_force() -> void
{ {
const spring _s = springs[s]; force = Vector3Zero();
const Vector3 a_pos = positions[_s.a];
const Vector3 b_pos = positions[_s.b];
const Vector3 a_vel = velocities[_s.a];
const Vector3 b_vel = velocities[_s.b];
const Vector3 delta_pos = a_pos - b_pos;
const Vector3 delta_vel = a_vel - b_vel;
const float sq_len = Vector3DotProduct(delta_pos, delta_pos);
const float inv_len = 1.0f / sqrt(sq_len);
const float len = sq_len * inv_len;
const float hooke = SPRING_CONSTANT * (len - REST_LENGTH);
const float dampening = DAMPENING_CONSTANT * Vector3DotProduct(delta_vel, delta_pos) * inv_len;
const Vector3 a_force = Vector3Scale(delta_pos, -(hooke + dampening) * inv_len);
const Vector3 b_force = a_force * -1.0f;
forces[_s.a] += a_force;
forces[_s.b] += b_force;
} }
auto mass_spring_system::integrate_velocity(const size_t m, const float dt) -> void auto mass_spring_system::mass::calculate_velocity(const float delta_time) -> void
{ {
const Vector3 acc = forces[m] / MASS; const Vector3 acceleration = Vector3Scale(force, 1.0 / MASS);
velocities[m] += acc * dt; const Vector3 temp = Vector3Scale(acceleration, delta_time);
velocity = Vector3Add(velocity, temp);
} }
auto mass_spring_system::integrate_position(const size_t m, const float dt) -> void auto mass_spring_system::mass::calculate_position(const float delta_time) -> void
{ {
previous_positions[m] = positions[m]; previous_position = position;
positions[m] += velocities[m] * dt;
const Vector3 temp = Vector3Scale(velocity, delta_time);
position = Vector3Add(position, temp);
} }
auto mass_spring_system::verlet_update(const size_t m, const float dt) -> void auto mass_spring_system::mass::verlet_update(const float delta_time) -> void
{ {
const Vector3 acc = (forces[m] / MASS) * dt * dt; const Vector3 acceleration = Vector3Scale(force, 1.0 / MASS);
const Vector3 pos = positions[m]; const Vector3 temp_position = position;
Vector3 delta_pos = pos - previous_positions[m]; Vector3 displacement = Vector3Subtract(position, previous_position);
delta_pos *= 1.0 - VERLET_DAMPENING; // Minimal dampening const Vector3 accel_term = Vector3Scale(acceleration, delta_time * delta_time);
positions[m] += delta_pos + acc; // Minimal dampening
previous_positions[m] = pos; displacement = Vector3Scale(displacement, 1.0 - VERLET_DAMPENING);
position = Vector3Add(Vector3Add(position, displacement), accel_term);
previous_position = temp_position;
} }
#ifdef THREADPOOL auto mass_spring_system::spring::calculate_spring_force(mass& _a, mass& _b) -> void
auto mass_spring_system::set_mass_springs_pool_thread_name(size_t idx) -> void
{ {
BS::this_thread::set_os_thread_name(std::format("repulsion-{}", idx)); // TODO: Use a bungee force here instead of springs, since we already have global repulsion?
traceln("Using thread \"{}\"", BS::this_thread::get_os_thread_name().value_or("INVALID NAME")); const Vector3 delta_position = Vector3Subtract(_a.position, _b.position);
const float current_length = Vector3Length(delta_position);
const Vector3 delta_velocity = Vector3Subtract(_a.velocity, _b.velocity);
const float hooke = SPRING_CONSTANT * (current_length - REST_LENGTH);
const float dampening = DAMPENING_CONSTANT * Vector3DotProduct(delta_velocity, delta_position) / current_length;
const Vector3 force_a = Vector3Scale(delta_position, -(hooke + dampening) / current_length);
const Vector3 force_b = Vector3Scale(force_a, -1.0);
_a.force = Vector3Add(_a.force, force_a);
_b.force = Vector3Add(_b.force, force_b);
} }
#endif
auto mass_spring_system::clear() -> void auto mass_spring_system::clear() -> void
{ {
positions.clear(); masses.clear();
previous_positions.clear();
velocities.clear();
forces.clear();
springs.clear(); springs.clear();
tree.nodes.clear(); tree.nodes.clear();
} }
@ -87,17 +77,14 @@ auto mass_spring_system::add_mass() -> void
// }; // };
// position = Vector3Scale(Vector3Normalize(position), REST_LENGTH * 2.0); // position = Vector3Scale(Vector3Normalize(position), REST_LENGTH * 2.0);
positions.emplace_back(Vector3Zero()); masses.emplace_back(Vector3Zero());
previous_positions.emplace_back(Vector3Zero());
velocities.emplace_back(Vector3Zero());
forces.emplace_back(Vector3Zero());
} }
auto mass_spring_system::add_spring(size_t a, size_t b) -> void auto mass_spring_system::add_spring(size_t a, size_t b) -> void
{ {
// Update masses to be located along a random walk when adding the springs // Update masses to be located along a random walk when adding the springs
const Vector3& mass_a = positions[a]; const mass& mass_a = masses.at(a);
const Vector3& mass_b = positions[b]; mass& mass_b = masses.at(b);
Vector3 offset{ Vector3 offset{
static_cast<float>(GetRandomValue(-100, 100)), static_cast<float>(GetRandomValue(-100, 100)), static_cast<float>(GetRandomValue(-100, 100)), static_cast<float>(GetRandomValue(-100, 100)),
@ -107,7 +94,7 @@ auto mass_spring_system::add_spring(size_t a, size_t b) -> void
// If the offset moves the mass closer to the current center of mass, flip it // If the offset moves the mass closer to the current center of mass, flip it
if (!tree.nodes.empty()) { if (!tree.nodes.empty()) {
const Vector3 mass_center_direction = Vector3Subtract(positions[a], tree.nodes[0].mass_center); const Vector3 mass_center_direction = Vector3Subtract(mass_a.position, tree.nodes.at(0).mass_center);
const float mass_center_distance = Vector3Length(mass_center_direction); const float mass_center_distance = Vector3Length(mass_center_direction);
if (mass_center_distance > 0 && Vector3DotProduct(offset, mass_center_direction) < 0.0f) { if (mass_center_distance > 0 && Vector3DotProduct(offset, mass_center_direction) < 0.0f) {
@ -115,8 +102,8 @@ auto mass_spring_system::add_spring(size_t a, size_t b) -> void
} }
} }
positions[b] = mass_a + offset; mass_b.position = mass_a.position + offset;
previous_positions[b] = mass_b; mass_b.previous_position = mass_b.position;
// infoln("Adding spring: ({}, {}, {})->({}, {}, {})", mass_a.position.x, mass_a.position.y, // infoln("Adding spring: ({}, {}, {})->({}, {}, {})", mass_a.position.x, mass_a.position.y,
// mass_a.position.z, // mass_a.position.z,
@ -131,7 +118,9 @@ auto mass_spring_system::clear_forces() -> void
ZoneScoped; ZoneScoped;
#endif #endif
memset(forces.data(), 0, forces.size() * sizeof(Vector3)); for (auto& m : masses) {
m.clear_force();
}
} }
auto mass_spring_system::calculate_spring_forces() -> void auto mass_spring_system::calculate_spring_forces() -> void
@ -140,18 +129,56 @@ auto mass_spring_system::calculate_spring_forces() -> void
ZoneScoped; ZoneScoped;
#endif #endif
const auto solve_spring_force = [&](const int i) for (const auto s : springs) {
{ mass& a = masses.at(s.a);
calculate_spring_force(i); mass& b = masses.at(s.b);
}; spring::calculate_spring_force(a, b);
#ifdef THREADPOOL
threads.submit_loop(0, springs.size(), solve_spring_force, SMALL_TASK_BLOCK_SIZE).wait();
#else
for (size_t i = 0; i < springs.size(); ++i) {
solve_spring_force(i);
} }
}
#ifdef THREADPOOL
auto mass_spring_system::set_thread_name(size_t idx) -> void
{
BS::this_thread::set_os_thread_name(std::format("bh-worker-{}", idx));
}
#endif
auto mass_spring_system::build_octree() -> void
{
#ifdef TRACY
ZoneScoped;
#endif #endif
tree.nodes.clear();
tree.nodes.reserve(masses.size() * 2);
// Compute bounding box around all masses
Vector3 min{FLT_MAX, FLT_MAX, FLT_MAX};
Vector3 max{-FLT_MAX, -FLT_MAX, -FLT_MAX};
for (const auto& m : masses) {
min.x = std::min(min.x, m.position.x);
max.x = std::max(max.x, m.position.x);
min.y = std::min(min.y, m.position.y);
max.y = std::max(max.y, m.position.y);
min.z = std::min(min.z, m.position.z);
max.z = std::max(max.z, m.position.z);
}
// Pad the bounding box
constexpr float pad = 1.0;
min = Vector3Subtract(min, Vector3Scale(Vector3One(), pad));
max = Vector3Add(max, Vector3Scale(Vector3One(), pad));
// Make it cubic (so subdivisions are balanced)
const float max_extent = std::max({max.x - min.x, max.y - min.y, max.z - min.z});
max = Vector3Add(min, Vector3Scale(Vector3One(), max_extent));
// Root node spans the entire area
const int root = tree.create_empty_leaf(min, max);
for (size_t i = 0; i < masses.size(); ++i) {
tree.insert(root, static_cast<int>(i), masses[i].position, MASS, 0);
}
} }
auto mass_spring_system::calculate_repulsion_forces() -> void auto mass_spring_system::calculate_repulsion_forces() -> void
@ -160,60 +187,45 @@ auto mass_spring_system::calculate_repulsion_forces() -> void
ZoneScoped; ZoneScoped;
#endif #endif
const auto solve_octree = [&](const int i) build_octree();
auto solve_octree = [&](const int i)
{ {
const Vector3 force = tree.calculate_force(0, positions[i]); const Vector3 force = tree.calculate_force(0, masses[i].position);
forces[i] += force; masses[i].force = Vector3Add(masses[i].force, force);
}; };
// Calculate forces using Barnes-Hut // Calculate forces using Barnes-Hut
#ifdef THREADPOOL #ifdef THREADPOOL
threads.submit_loop(0, positions.size(), solve_octree, LARGE_TASK_BLOCK_SIZE).wait(); const BS::multi_future<void> loop_future = threads.submit_loop(0, masses.size(), solve_octree, 256);
loop_future.wait();
#else #else
for (size_t i = 0; i < positions.size(); ++i) { for (size_t i = 0; i < masses.size(); ++i) {
solve_octree(i); solve_octree(i);
} }
#endif #endif
} }
auto mass_spring_system::update(const float dt) -> void auto mass_spring_system::verlet_update(const float delta_time) -> void
{ {
#ifdef TRACY #ifdef TRACY
ZoneScoped; ZoneScoped;
#endif #endif
const auto update = [&](const int i) for (auto& m : masses) {
{ m.verlet_update(delta_time);
verlet_update(i, dt);
};
#ifdef THREADPOOL
threads.submit_loop(0, positions.size(), update, SMALL_TASK_BLOCK_SIZE).wait();
#else
for (size_t i = 0; i < positions.size(); ++i) {
update(i);
} }
#endif
} }
auto mass_spring_system::center_masses() -> void auto mass_spring_system::center_masses() -> void
{ {
Vector3 mean = Vector3Zero(); Vector3 mean = Vector3Zero();
for (const Vector3& pos : positions) { for (const auto& m : masses) {
mean += pos; mean += m.position;
} }
mean /= static_cast<float>(positions.size()); mean /= static_cast<float>(masses.size());
const auto center_mass = [&](const int i) for (auto& m : masses) {
{ m.position -= mean;
positions[i] -= mean;
};
#ifdef THREADPOOL
threads.submit_loop(0, positions.size(), center_mass, SMALL_TASK_BLOCK_SIZE).wait();
#else
for (size_t i = 0; i < positions.size(); ++i) {
center_mass(i);
} }
#endif
} }

View File

@ -1,7 +1,7 @@
#include "octree.hpp" #include "octree.hpp"
#include "config.hpp" #include "config.hpp"
#include "util.hpp"
#include <cfloat>
#include <raymath.h> #include <raymath.h>
#ifdef TRACY #ifdef TRACY
@ -19,6 +19,16 @@ auto octree::node::child_count() const -> int
return child_count; return child_count;
} }
auto octree::create_empty_leaf(const Vector3& box_min, const Vector3& box_max) -> int
{
node n;
n.box_min = box_min;
n.box_max = box_max;
nodes.emplace_back(n);
return static_cast<int>(nodes.size() - 1);
}
auto octree::get_octant(const int node_idx, const Vector3& pos) const -> int auto octree::get_octant(const int node_idx, const Vector3& pos) const -> int
{ {
const node& n = nodes[node_idx]; const node& n = nodes[node_idx];
@ -65,16 +75,6 @@ auto octree::get_child_bounds(const int node_idx, const int octant) const -> std
return std::make_pair(min, max); return std::make_pair(min, max);
} }
auto octree::create_empty_leaf(const Vector3& box_min, const Vector3& box_max) -> int
{
node n;
n.box_min = box_min;
n.box_max = box_max;
nodes.emplace_back(n);
return static_cast<int>(nodes.size() - 1);
}
auto octree::insert(const int node_idx, const int mass_id, const Vector3& pos, const float mass, auto octree::insert(const int node_idx, const int mass_id, const Vector3& pos, const float mass,
const int depth) -> void const int depth) -> void
{ {
@ -153,44 +153,6 @@ auto octree::insert(const int node_idx, const int mass_id, const Vector3& pos, c
nodes[node_idx].mass_total = new_mass; nodes[node_idx].mass_total = new_mass;
} }
auto octree::build_octree(octree& t, const std::vector<Vector3>& positions) -> void
{
#ifdef TRACY
ZoneScoped;
#endif
t.nodes.clear();
t.nodes.reserve(positions.size() * 2);
// Compute bounding box around all masses
Vector3 min{FLT_MAX, FLT_MAX, FLT_MAX};
Vector3 max{-FLT_MAX, -FLT_MAX, -FLT_MAX};
for (const auto& [x, y, z] : positions) {
min.x = std::min(min.x, x);
max.x = std::max(max.x, x);
min.y = std::min(min.y, y);
max.y = std::max(max.y, y);
min.z = std::min(min.z, z);
max.z = std::max(max.z, z);
}
// Pad the bounding box
constexpr float pad = 1.0;
min = Vector3Subtract(min, Vector3Scale(Vector3One(), pad));
max = Vector3Add(max, Vector3Scale(Vector3One(), pad));
// Make it cubic (so subdivisions are balanced)
const float max_extent = std::max({max.x - min.x, max.y - min.y, max.z - min.z});
max = Vector3Add(min, Vector3Scale(Vector3One(), max_extent));
// Root node spans the entire area
const int root = t.create_empty_leaf(min, max);
for (size_t i = 0; i < positions.size(); ++i) {
t.insert(root, static_cast<int>(i), positions[i], MASS, 0);
}
}
auto octree::calculate_force(const int node_idx, const Vector3& pos) const -> Vector3 auto octree::calculate_force(const int node_idx, const Vector3& pos) const -> Vector3
{ {
if (node_idx < 0) { if (node_idx < 0) {

View File

@ -542,7 +542,7 @@ auto puzzle::try_get_invalid_reason() const -> std::optional<std::string>
const auto [w, h, gx, gy, r, g] = unpack_meta(); const auto [w, h, gx, gy, r, g] = unpack_meta();
traceln("Validating puzzle \"{}\"", string_repr()); infoln("Validating puzzle {}", string_repr());
const std::optional<block>& b = try_get_target_block(); const std::optional<block>& b = try_get_target_block();
if (get_goal() && !b) { if (get_goal() && !b) {

View File

@ -6,7 +6,7 @@
#include <rlgl.h> #include <rlgl.h>
#ifdef TRACY #ifdef TRACY
#include <tracy/Tracy.hpp> #include <tracy/Tracy.hpp>
#endif #endif
auto renderer::update_texture_sizes() -> void auto renderer::update_texture_sizes() -> void
@ -29,9 +29,9 @@ auto renderer::update_texture_sizes() -> void
auto renderer::draw_mass_springs(const std::vector<Vector3>& masses) -> void auto renderer::draw_mass_springs(const std::vector<Vector3>& masses) -> void
{ {
#ifdef TRACY #ifdef TRACY
ZoneScoped; ZoneScoped;
#endif #endif
if (masses.size() != state.get_state_count()) { if (masses.size() != state.get_state_count()) {
// Because the physics run in a different thread, it might need time to catch up // Because the physics run in a different thread, it might need time to catch up
@ -40,16 +40,16 @@ auto renderer::draw_mass_springs(const std::vector<Vector3>& masses) -> void
// Prepare connection batching // Prepare connection batching
{ {
#ifdef TRACY #ifdef TRACY
ZoneNamedN(prepare_connections, "PrepareConnectionsBatching", true); ZoneNamedN(prepare_masses, "PrepareConnectionsBatching", true);
#endif #endif
connections.clear(); connections.clear();
connections.reserve(state.get_target_count()); connections.reserve(state.get_target_count());
if (input.connect_solutions) { if (input.connect_solutions) {
for (const size_t& _state : state.get_winning_indices()) { for (const size_t& _state : state.get_winning_indices()) {
const Vector3& current_mass = masses[state.get_current_index()]; const Vector3& current_mass = masses.at(state.get_current_index());
const Vector3& winning_mass = masses[_state]; const Vector3& winning_mass = masses.at(_state);
connections.emplace_back(current_mass, winning_mass); connections.emplace_back(current_mass, winning_mass);
DrawLine3D(current_mass, winning_mass, Fade(TARGET_BLOCK_COLOR, 0.5)); DrawLine3D(current_mass, winning_mass, Fade(TARGET_BLOCK_COLOR, 0.5));
} }
@ -58,9 +58,9 @@ auto renderer::draw_mass_springs(const std::vector<Vector3>& masses) -> void
// Prepare cube instancing // Prepare cube instancing
{ {
#ifdef TRACY #ifdef TRACY
ZoneNamedN(prepare_masses, "PrepareMassInstancing", true); ZoneNamedN(prepare_masses, "PrepareMassInstancing", true);
#endif #endif
if (masses.size() < DRAW_VERTICES_LIMIT) { if (masses.size() < DRAW_VERTICES_LIMIT) {
// Don't have to reserve, capacity is already set to DRAW_VERTICES_LIMIT in constructor // Don't have to reserve, capacity is already set to DRAW_VERTICES_LIMIT in constructor
@ -73,10 +73,12 @@ auto renderer::draw_mass_springs(const std::vector<Vector3>& masses) -> void
// Normal vertex // Normal vertex
Color c = VERTEX_COLOR; Color c = VERTEX_COLOR;
if ((input.mark_solutions || input.mark_path) && state.get_winning_indices().contains(mass)) { if ((input.mark_solutions || input.mark_path) &&
state.get_winning_indices().contains(mass)) {
// Winning vertex // Winning vertex
c = VERTEX_TARGET_COLOR; c = VERTEX_TARGET_COLOR;
} else if ((input.mark_solutions || input.mark_path) && state.get_path_indices().contains(mass)) { } else if ((input.mark_solutions || input.mark_path) &&
state.get_path_indices().contains(mass)) {
// Path vertex // Path vertex
c = VERTEX_PATH_COLOR; c = VERTEX_PATH_COLOR;
} else if (mass == state.get_starting_index()) { } else if (mass == state.get_starting_index()) {
@ -102,15 +104,15 @@ auto renderer::draw_mass_springs(const std::vector<Vector3>& masses) -> void
// Draw springs (batched) // Draw springs (batched)
{ {
#ifdef TRACY #ifdef TRACY
ZoneNamedN(draw_springs, "DrawSprings", true); ZoneNamedN(draw_springs, "DrawSprings", true);
#endif #endif
rlBegin(RL_LINES); rlBegin(RL_LINES);
for (const auto& [from, to] : state.get_links()) { for (const auto& [from, to] : state.get_links()) {
if (masses.size() > from && masses.size() > to) { if (masses.size() > from && masses.size() > to) {
const auto& [ax, ay, az] = masses[from]; const auto& [ax, ay, az] = masses.at(from);
const auto& [bx, by, bz] = masses[to]; const auto& [bx, by, bz] = masses.at(to);
rlColor4ub(EDGE_COLOR.r, EDGE_COLOR.g, EDGE_COLOR.b, EDGE_COLOR.a); rlColor4ub(EDGE_COLOR.r, EDGE_COLOR.g, EDGE_COLOR.b, EDGE_COLOR.a);
rlVertex3f(ax, ay, az); rlVertex3f(ax, ay, az);
rlVertex3f(bx, by, bz); rlVertex3f(bx, by, bz);
@ -121,16 +123,17 @@ auto renderer::draw_mass_springs(const std::vector<Vector3>& masses) -> void
// Draw masses (instanced) // Draw masses (instanced)
{ {
#ifdef TRACY #ifdef TRACY
ZoneNamedN(draw_masses, "DrawMasses", true); ZoneNamedN(draw_masses, "DrawMasses", true);
#endif #endif
if (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 // 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 // much faster... The amount of data sent to the GPU would be
// reduced (just positions instead of matrices), but is this // reduced (just positions instead of matrices), but is this
// noticable for < 100000 cubes? // noticable for < 100000 cubes?
DrawMeshInstanced(cube_instance, vertex_mat, transforms.data(), masses.size()); // NOLINT(*-narrowing-conversions) DrawMeshInstanced(cube_instance, vertex_mat, transforms.data(),
masses.size()); // NOLINT(*-narrowing-conversions)
} }
} }
@ -149,8 +152,9 @@ auto renderer::draw_mass_springs(const std::vector<Vector3>& masses) -> void
// Mark current state // Mark current state
const size_t current_index = state.get_current_index(); const size_t current_index = state.get_current_index();
if (masses.size() > current_index) { if (masses.size() > current_index) {
const Vector3& current_mass = masses[current_index]; const Vector3& current_mass = masses.at(current_index);
DrawCube(current_mass, VERTEX_SIZE * 2, VERTEX_SIZE * 2, VERTEX_SIZE * 2, VERTEX_CURRENT_COLOR); DrawCube(current_mass, VERTEX_SIZE * 2, VERTEX_SIZE * 2, VERTEX_SIZE * 2,
VERTEX_CURRENT_COLOR);
} }
EndMode3D(); EndMode3D();
@ -159,9 +163,9 @@ auto renderer::draw_mass_springs(const std::vector<Vector3>& masses) -> void
auto renderer::draw_klotski() const -> void auto renderer::draw_klotski() const -> void
{ {
#ifdef TRACY #ifdef TRACY
ZoneScoped; ZoneScoped;
#endif #endif
BeginTextureMode(klotski_target); BeginTextureMode(klotski_target);
ClearBackground(RAYWHITE); ClearBackground(RAYWHITE);
@ -173,9 +177,9 @@ auto renderer::draw_klotski() const -> void
auto renderer::draw_menu() const -> void auto renderer::draw_menu() const -> void
{ {
#ifdef TRACY #ifdef TRACY
ZoneScoped; ZoneScoped;
#endif #endif
BeginTextureMode(menu_target); BeginTextureMode(menu_target);
ClearBackground(RAYWHITE); ClearBackground(RAYWHITE);
@ -190,28 +194,32 @@ auto renderer::draw_textures(const int fps, const int ups, const size_t mass_cou
{ {
BeginDrawing(); BeginDrawing();
DrawTextureRec(menu_target.texture, Rectangle(0, 0, menu_target.texture.width, -menu_target.texture.height), DrawTextureRec(menu_target.texture,
Rectangle(0, 0, menu_target.texture.width, -menu_target.texture.height),
Vector2(0, 0), WHITE); Vector2(0, 0), WHITE);
DrawTextureRec(klotski_target.texture, DrawTextureRec(klotski_target.texture,
Rectangle(0, 0, klotski_target.texture.width, -klotski_target.texture.height), Rectangle(0, 0, klotski_target.texture.width, -klotski_target.texture.height),
Vector2(0, MENU_HEIGHT), WHITE); Vector2(0, MENU_HEIGHT), WHITE);
DrawTextureRec(render_target.texture, Rectangle(0, 0, render_target.texture.width, -render_target.texture.height), DrawTextureRec(render_target.texture,
Rectangle(0, 0, render_target.texture.width, -render_target.texture.height),
Vector2(GetScreenWidth() / 2.0f, MENU_HEIGHT), WHITE); Vector2(GetScreenWidth() / 2.0f, MENU_HEIGHT), WHITE);
// Draw borders // Draw borders
DrawRectangleLinesEx(Rectangle(0, 0, GetScreenWidth(), MENU_HEIGHT), 1.0f, BLACK); DrawRectangleLinesEx(Rectangle(0, 0, GetScreenWidth(), MENU_HEIGHT), 1.0f, BLACK);
DrawRectangleLinesEx(Rectangle(0, MENU_HEIGHT, GetScreenWidth() / 2.0f, GetScreenHeight() - MENU_HEIGHT), 1.0f, DrawRectangleLinesEx(
BLACK); Rectangle(0, MENU_HEIGHT, GetScreenWidth() / 2.0f, GetScreenHeight() - MENU_HEIGHT), 1.0f,
BLACK);
DrawRectangleLinesEx(Rectangle(GetScreenWidth() / 2.0f, MENU_HEIGHT, GetScreenWidth() / 2.0f, DrawRectangleLinesEx(Rectangle(GetScreenWidth() / 2.0f, MENU_HEIGHT, GetScreenWidth() / 2.0f,
GetScreenHeight() - MENU_HEIGHT), 1.0f, BLACK); GetScreenHeight() - MENU_HEIGHT),
1.0f, BLACK);
gui.draw(fps, ups, mass_count, spring_count); gui.draw(fps, ups, mass_count, spring_count);
EndDrawing(); EndDrawing();
} }
auto renderer::render(const std::vector<Vector3>& masses, const int fps, const int ups, const size_t mass_count, auto renderer::render(const std::vector<Vector3>& masses, const int fps, const int ups,
const size_t spring_count) -> void const size_t mass_count, const size_t spring_count) -> void
{ {
update_texture_sizes(); update_texture_sizes();
@ -219,4 +227,4 @@ auto renderer::render(const std::vector<Vector3>& masses, const int fps, const i
draw_klotski(); draw_klotski();
draw_menu(); draw_menu();
draw_textures(fps, ups, mass_count, spring_count); draw_textures(fps, ups, mass_count, spring_count);
} }

View File

@ -12,7 +12,7 @@
auto state_manager::synced_try_insert_state(const puzzle& state) -> size_t auto state_manager::synced_try_insert_state(const puzzle& state) -> size_t
{ {
if (state_indices.contains(state)) { if (state_indices.contains(state)) {
return state_indices[state]; return state_indices.at(state);
} }
const size_t index = state_pool.size(); const size_t index = state_pool.size();
@ -148,7 +148,7 @@ auto state_manager::append_preset_file(const std::string& preset_name) -> bool
auto state_manager::load_preset(const size_t preset) -> void auto state_manager::load_preset(const size_t preset) -> void
{ {
clear_graph_and_add_current(preset_states[preset]); clear_graph_and_add_current(preset_states.at(preset));
current_preset = preset; current_preset = preset;
edited = false; edited = false;
} }
@ -270,8 +270,8 @@ auto state_manager::goto_most_distant_state() -> void
int max_distance = 0; int max_distance = 0;
size_t max_distance_index = 0; size_t max_distance_index = 0;
for (size_t i = 0; i < node_target_distances.distances.size(); ++i) { for (size_t i = 0; i < node_target_distances.distances.size(); ++i) {
if (node_target_distances.distances[i] > max_distance) { if (node_target_distances.distances.at(i) > max_distance) {
max_distance = node_target_distances.distances[i]; max_distance = node_target_distances.distances.at(i);
max_distance_index = i; max_distance_index = i;
} }
} }
@ -285,7 +285,7 @@ auto state_manager::goto_closest_target_state() -> void
return; return;
} }
update_current_state(get_state(node_target_distances.nearest_targets[current_state_index])); update_current_state(get_state(node_target_distances.nearest_targets.at(current_state_index)));
} }
auto state_manager::populate_graph() -> void auto state_manager::populate_graph() -> void
@ -306,9 +306,9 @@ auto state_manager::populate_graph() -> void
const auto& [states, _links] = s.explore_state_space(); const auto& [states, _links] = s.explore_state_space();
synced_insert_statespace(states, _links); synced_insert_statespace(states, _links);
current_state_index = state_indices[p]; current_state_index = state_indices.at(p);
previous_state_index = current_state_index; previous_state_index = current_state_index;
starting_state_index = state_indices[s]; starting_state_index = state_indices.at(s);
// Search for cool stuff // Search for cool stuff
populate_winning_indices(); populate_winning_indices();
@ -393,7 +393,7 @@ auto state_manager::get_starting_index() const -> size_t
auto state_manager::get_state(const size_t index) const -> const puzzle& auto state_manager::get_state(const size_t index) const -> const puzzle&
{ {
return state_pool[index]; return state_pool.at(index);
} }
auto state_manager::get_current_state() const -> const puzzle& auto state_manager::get_current_state() const -> const puzzle&
@ -468,7 +468,7 @@ auto state_manager::get_preset_count() const -> size_t
auto state_manager::get_current_preset_comment() const -> const std::string& auto state_manager::get_current_preset_comment() const -> const std::string&
{ {
return preset_comments[current_preset]; return preset_comments.at(current_preset);
} }
auto state_manager::has_history() const -> bool auto state_manager::has_history() const -> bool

View File

@ -1,7 +1,6 @@
#include "threaded_physics.hpp" #include "threaded_physics.hpp"
#include "config.hpp" #include "config.hpp"
#include "mass_spring_system.hpp" #include "mass_spring_system.hpp"
#include "util.hpp"
#include <chrono> #include <chrono>
#include <raylib.h> #include <raylib.h>
@ -13,30 +12,16 @@
#include <tracy/Tracy.hpp> #include <tracy/Tracy.hpp>
#endif #endif
#ifdef ASYNC_OCTREE
auto threaded_physics::set_octree_pool_thread_name(size_t idx) -> void
{
BS::this_thread::set_os_thread_name(std::format("octree-{}", idx));
traceln("Using thread \"{}\"", BS::this_thread::get_os_thread_name().value_or("INVALID NAME"));
}
#endif
auto threaded_physics::physics_thread(physics_state& state) -> void auto threaded_physics::physics_thread(physics_state& state) -> void
{ {
mass_spring_system mass_springs; #ifdef THREADPOOL
#ifdef ASYNC_OCTREE
BS::this_thread::set_os_thread_name("physics"); BS::this_thread::set_os_thread_name("physics");
BS::thread_pool<> octree_thread(1, set_octree_pool_thread_name);
std::future<void> octree_future;
octree tree_buffer;
size_t last_mass_count = 0;
infoln("Using asynchronous octree builder.");
#endif #endif
mass_spring_system mass_springs;
const auto visitor = overloads{ const auto visitor = overloads{
[&](const struct add_mass&) [&](const struct add_mass& am)
{ {
mass_springs.add_mass(); mass_springs.add_mass();
}, },
@ -44,7 +29,7 @@ auto threaded_physics::physics_thread(physics_state& state) -> void
{ {
mass_springs.add_spring(as.a, as.b); mass_springs.add_spring(as.a, as.b);
}, },
[&](const struct clear_graph&) [&](const struct clear_graph& cg)
{ {
mass_springs.clear(); mass_springs.clear();
}, },
@ -81,58 +66,26 @@ auto threaded_physics::physics_thread(physics_state& state) -> void
} }
} }
if (mass_springs.positions.empty()) { if (mass_springs.masses.empty()) {
std::this_thread::sleep_for(std::chrono::milliseconds(1)); std::this_thread::sleep_for(std::chrono::milliseconds(1));
continue; continue;
} }
// Physics update // Physics update
if (physics_accumulator.count() > TIMESTEP) { if (physics_accumulator.count() > TIMESTEP) {
#ifdef ASYNC_OCTREE
// Snapshot the positions so mass_springs is not mutating the vector while the octree is building
std::vector<Vector3> positions = mass_springs.positions;
// Start building the octree for the next physics update.
// Move the snapshot into the closure so it doesn't get captured by reference (don't use [&])
octree_future = octree_thread.submit_task([&tree_buffer, positions = std::move(positions)]()
{
octree::build_octree(tree_buffer, positions);
});
// Rebuild the tree synchronously if we changed the number of masses to not use
// an empty tree from the last frame in the frame where the graph was generated
if (last_mass_count != mass_springs.positions.size()) {
traceln("Rebuilding octree synchronously because graph size changed");
octree::build_octree(mass_springs.tree, mass_springs.positions);
last_mass_count = mass_springs.positions.size();
}
#else
octree::build_octree(mass_springs.tree, mass_springs.positions);
#endif
mass_springs.clear_forces(); mass_springs.clear_forces();
mass_springs.calculate_spring_forces(); mass_springs.calculate_spring_forces();
mass_springs.calculate_repulsion_forces(); mass_springs.calculate_repulsion_forces();
mass_springs.update(TIMESTEP * SIM_SPEED); mass_springs.verlet_update(TIMESTEP * SIM_SPEED);
// This is only helpful if we're drawing a grid at (0, 0, 0). Otherwise, it's just // This is only helpful if we're drawing a grid at (0, 0, 0). Otherwise, it's just
// expensive and yields no benefit since we can lock the camera to the center of mass // expensive and yields no benefit since we can lock the camera to the center of mass
// cheaply. // cheaply. mass_springs.center_masses();
// mass_springs.center_masses();
++loop_iterations; ++loop_iterations;
physics_accumulator -= std::chrono::duration<double>(TIMESTEP); physics_accumulator -= std::chrono::duration<double>(TIMESTEP);
} }
#ifdef ASYNC_OCTREE
// Wait for the octree to be built
if (octree_future.valid()) {
octree_future.wait();
octree_future = std::future<void>{};
std::swap(mass_springs.tree, tree_buffer);
}
#endif
// Publish the positions for the renderer (copy) // Publish the positions for the renderer (copy)
#ifdef TRACY #ifdef TRACY
FrameMarkStart("PhysicsThreadProduceLock"); FrameMarkStart("PhysicsThreadProduceLock");
@ -161,16 +114,16 @@ auto threaded_physics::physics_thread(physics_state& state) -> void
if (mass_springs.tree.nodes.empty()) { if (mass_springs.tree.nodes.empty()) {
state.mass_center = Vector3Zero(); state.mass_center = Vector3Zero();
} else { } else {
state.mass_center = mass_springs.tree.nodes[0].mass_center; state.mass_center = mass_springs.tree.nodes.at(0).mass_center;
} }
state.masses.clear(); state.masses.clear();
state.masses.reserve(mass_springs.positions.size()); state.masses.reserve(mass_springs.masses.size());
for (const Vector3& pos : mass_springs.positions) { for (const auto& mass : mass_springs.masses) {
state.masses.emplace_back(pos); state.masses.emplace_back(mass.position);
} }
state.mass_count = mass_springs.positions.size(); state.mass_count = mass_springs.masses.size();
state.spring_count = mass_springs.springs.size(); state.spring_count = mass_springs.springs.size();
state.data_ready = true; state.data_ready = true;
@ -178,22 +131,11 @@ auto threaded_physics::physics_thread(physics_state& state) -> void
} }
// Notify the rendering thread that new data is available // Notify the rendering thread that new data is available
state.data_ready_cnd.notify_all(); state.data_ready_cnd.notify_all();
#ifdef TRACY #ifdef TRACY
FrameMarkEnd("PhysicsThreadProduceLock"); FrameMarkEnd("PhysicsThread"); FrameMarkEnd("PhysicsThreadProduceLock");
#endif
}
}
auto threaded_physics::clear_cmd() -> void FrameMarkEnd("PhysicsThread");
{
{
#ifdef TRACY
std::lock_guard<LockableBase(std::mutex)> lock(state.command_mtx);
#else
std::lock_guard<std::mutex> lock(state.command_mtx);
#endif #endif
state.pending_commands.emplace(clear_graph{});
} }
} }
@ -221,6 +163,18 @@ auto threaded_physics::add_spring_cmd(const size_t a, const size_t b) -> void
} }
} }
auto threaded_physics::clear_cmd() -> void
{
{
#ifdef TRACY
std::lock_guard<LockableBase(std::mutex)> lock(state.command_mtx);
#else
std::lock_guard<std::mutex> lock(state.command_mtx);
#endif
state.pending_commands.emplace(clear_graph{});
}
}
auto threaded_physics::add_mass_springs_cmd(const size_t num_masses, auto threaded_physics::add_mass_springs_cmd(const size_t num_masses,
const std::vector<std::pair<size_t, size_t>>& springs) -> void const std::vector<std::pair<size_t, size_t>>& springs) -> void
{ {