From 4c4c767eef7b68910cfdec9b239e990aec05757c Mon Sep 17 00:00:00 2001 From: Yousef Khadadeh Date: Fri, 29 May 2026 23:14:56 +0300 Subject: [PATCH] Parallelize eraser --- include/Helpers/Parallel.hpp | 11 ++++++++ .../CanvasComponentContainer.cpp | 4 +-- src/CanvasComponents/MeshCanvasComponent.cpp | 2 +- src/DrawingProgram/Tools/EraserTool.cpp | 26 ++++++++++++------- src/DrawingProgram/Tools/EraserTool.hpp | 6 ++++- 5 files changed, 36 insertions(+), 13 deletions(-) diff --git a/include/Helpers/Parallel.hpp b/include/Helpers/Parallel.hpp index 32b46bf..fa12318 100644 --- a/include/Helpers/Parallel.hpp +++ b/include/Helpers/Parallel.hpp @@ -33,3 +33,14 @@ template void parallel_loop_container(const ContainerTy std::for_each(std::execution::par_unseq, c.begin(), c.end(), func); #endif } + +template void parallel_loop_container_mutable(ContainerType& c, std::function func, bool forceSingleThread = false) { +#if defined(__EMSCRIPTEN__) || defined(__ANDROID__) + std::for_each(c.begin(), c.end(), func); +#else + if(forceSingleThread) + std::for_each(c.begin(), c.end(), func); + else + std::for_each(std::execution::par_unseq, c.begin(), c.end(), func); +#endif +} diff --git a/src/CanvasComponents/CanvasComponentContainer.cpp b/src/CanvasComponents/CanvasComponentContainer.cpp index 30feb16..71cedb5 100644 --- a/src/CanvasComponents/CanvasComponentContainer.cpp +++ b/src/CanvasComponents/CanvasComponentContainer.cpp @@ -150,8 +150,8 @@ void CanvasComponentContainer::commit_transform_dont_invalidate_cache() { // camCoordsScaleToCheckAgainst is different from camCoords.inverseScale if we locked the eraser's scale. CanvasComponentEraseDetailResult CanvasComponentContainer::collides_with_erase_detail(const CoordSpaceHelper& camCoords, const WorldScalar& camCoordsScaleToCheckAgainst, const SkPath& checkAgainstCam) const { - constexpr static int COMP_MAX_SHIFT_BEFORE_STOP_ERASE = 3; - constexpr static int COMP_COMPLETE_ERASE_SHIFT = 13; + constexpr static int COMP_MAX_SHIFT_BEFORE_STOP_ERASE = 1; + constexpr static int COMP_COMPLETE_ERASE_SHIFT = 14; if(!worldAABB.has_value()) return CanvasComponentEraseDetailResult::NO_CHANGE; if((camCoordsScaleToCheckAgainst << COMP_MAX_SHIFT_BEFORE_STOP_ERASE) < coords.inverseScale) // Object is too large, just dismiss the collision diff --git a/src/CanvasComponents/MeshCanvasComponent.cpp b/src/CanvasComponents/MeshCanvasComponent.cpp index 05f9dd7..f04934d 100644 --- a/src/CanvasComponents/MeshCanvasComponent.cpp +++ b/src/CanvasComponents/MeshCanvasComponent.cpp @@ -292,7 +292,7 @@ std::vector MeshCanvasComponent::attempt_split(Drawin if(clippingSubjects.size() <= 1) return {}; - ClipperD clipper; + ClipperD clipper(4); PolyTreeD solutionTree; clipper.AddSubject(clippingSubjects); diff --git a/src/DrawingProgram/Tools/EraserTool.cpp b/src/DrawingProgram/Tools/EraserTool.cpp index d49ff7f..fad9ae9 100644 --- a/src/DrawingProgram/Tools/EraserTool.cpp +++ b/src/DrawingProgram/Tools/EraserTool.cpp @@ -30,6 +30,7 @@ #include "../../GUIStuff/ElementHelpers/RadioButtonHelpers.hpp" #include "../../GUIStuff/ElementHelpers/CheckBoxHelpers.hpp" #include "Helpers/NetworkingObjects/NetObjOrderedList.hpp" +#include "Helpers/Parallel.hpp" EraserTool::EraserTool(DrawingProgram& initDrawP): DrawingProgramToolBase(initDrawP) @@ -139,7 +140,7 @@ void EraserTool::erase_on_path() { if(drawP.layerMan.component_passes_layer_selector(c, drawP.controls.layerSelector)) { auto it = updatedComponents.find(c); if(it != updatedComponents.end()) { - c->obj->get_comp().set_data_from(*it->second->obj); + c->obj->get_comp().set_data_from(*it->second.copyData->obj); updatedComponents.erase(it); } erasedComponents.emplace(c); @@ -173,7 +174,7 @@ void EraserTool::erase_on_path() { case CanvasComponentEraseDetailResult::REMOVED: auto it = updatedComponents.find(c); if(it != updatedComponents.end()) { - c->obj->get_comp().set_data_from(*it->second->obj); + c->obj->get_comp().set_data_from(*it->second.copyData->obj); updatedComponents.erase(it); } else @@ -215,15 +216,19 @@ void EraserTool::erase_component(CanvasComponentContainer::ObjInfo* erasedComp) void EraserTool::commit_erase() { bool placedNewObject = false; std::unordered_map>> newObjectsToPlace; + + parallel_loop_container_mutable(updatedComponents, [&](auto& compPair) { + compPair.second.splitComps = compPair.first->obj->get_comp().attempt_split(drawP); + }); + std::erase_if(updatedComponents, [&] (auto& p) { auto& [comp, oldData] = p; - std::vector splitComps = comp->obj->get_comp().attempt_split(drawP); - if(!splitComps.empty()) { - for(CanvasComponentContainer* c : splitComps) + if(!oldData.splitComps.empty()) { + for(CanvasComponentContainer* c : oldData.splitComps) newObjectsToPlace[comp->obj->parentLayer].emplace_back(std::next(comp->obj->objInfo), c); placedNewObject = true; erasedComponents.emplace(comp); - comp->obj->get_comp().set_data_from(*oldData->obj); + comp->obj->get_comp().set_data_from(*oldData.copyData->obj); return true; } return false; @@ -240,18 +245,21 @@ void EraserTool::commit_erase() { if(objectsToErase) drawP.layerMan.erase_component_container(erasedComponents, !placedNewObject); bool first = !objectsToErase && !placedNewObject; - for(auto& [comp, oldData] : updatedComponents) { + parallel_loop_container(updatedComponents, [&](auto& compPair) { + auto& [comp, oldData] = compPair; comp->obj->get_comp().simplify_paths(); comp->obj->normalize_object_coordinates(); + }); + for(auto& [comp, oldData] : updatedComponents) { comp->obj->commit_update_dont_invalidate_cache(drawP); drawP.send_transforms_for({comp}); comp->obj->send_comp_update(drawP, true); if(first) { first = false; - drawP.world.undo.push(std::make_unique(std::move(oldData->obj), oldData->coords, drawP.world.undo.get_undoid_from_netid(comp->obj.get_net_id()))); + drawP.world.undo.push(std::make_unique(std::move(oldData.copyData->obj), oldData.copyData->coords, drawP.world.undo.get_undoid_from_netid(comp->obj.get_net_id()))); } else - drawP.world.undo.push_on_last(std::make_unique(std::move(oldData->obj), oldData->coords, drawP.world.undo.get_undoid_from_netid(comp->obj.get_net_id()))); + drawP.world.undo.push_on_last(std::make_unique(std::move(oldData.copyData->obj), oldData.copyData->coords, drawP.world.undo.get_undoid_from_netid(comp->obj.get_net_id()))); } }, NetworkingObjects::NetObjManager::SendUpdateType::SEND_TO_ALL, nullptr); erasedComponents.clear(); diff --git a/src/DrawingProgram/Tools/EraserTool.hpp b/src/DrawingProgram/Tools/EraserTool.hpp index 3c21ec6..1c14416 100644 --- a/src/DrawingProgram/Tools/EraserTool.hpp +++ b/src/DrawingProgram/Tools/EraserTool.hpp @@ -40,7 +40,11 @@ class EraserTool : public DrawingProgramToolBase { virtual void input_mouse_motion_callback(const InputManager::MouseMotionCallbackArgs& motion) override; virtual void input_pen_axis_callback(const InputManager::PenAxisCallbackArgs& axis) override; - std::unordered_map> updatedComponents; + struct UpdatedComponentData { + std::unique_ptr copyData; + std::vector splitComps; + }; + std::unordered_map updatedComponents; std::unordered_set erasedComponents; // Pointers will be erased from this set if theyre erased in the main list (done by callback) private: SkPath erasePath;