From f606fc6fc1c0de540833bf3a0b0d2157ceee5747 Mon Sep 17 00:00:00 2001 From: Yousef Khadadeh Date: Sun, 27 Jul 2025 16:30:45 +0300 Subject: [PATCH] Improved undo --- include/Helpers/Parallel.hpp | 7 +- src/CollabList.hpp | 31 ++++++-- src/DrawingProgram/BrushTool.cpp | 6 +- src/DrawingProgram/DrawingProgram.cpp | 78 +++++-------------- src/DrawingProgram/DrawingProgram.hpp | 3 +- .../DrawingProgramSelection.cpp | 50 ++++++++---- .../DrawingProgramSelection.hpp | 1 + src/DrawingProgram/EllipseDrawTool.cpp | 4 +- src/DrawingProgram/EraserTool.cpp | 6 +- src/DrawingProgram/RectDrawTool.cpp | 4 +- src/DrawingProgram/RectDrawTool.hpp | 1 - src/DrawingProgram/TextBoxTool.cpp | 4 +- 12 files changed, 96 insertions(+), 99 deletions(-) diff --git a/include/Helpers/Parallel.hpp b/include/Helpers/Parallel.hpp index 8b178b9..c70fb08 100644 --- a/include/Helpers/Parallel.hpp +++ b/include/Helpers/Parallel.hpp @@ -5,10 +5,13 @@ #include #endif -template void parallel_loop_container(const ContainerType& c, std::function func) { +template void parallel_loop_container(const ContainerType& c, std::function func, bool forceSingleThread = false) { #ifdef __EMSCRIPTEN__ std::for_each(c.begin(), c.end(), func); #else - std::for_each(std::execution::par_unseq, c.begin(), c.end(), func); + 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/CollabList.hpp b/src/CollabList.hpp index 2068430..feaa5d9 100644 --- a/src/CollabList.hpp +++ b/src/CollabList.hpp @@ -56,7 +56,7 @@ template class CollabList { complete_client_pos_refresh(false); } - IDType client_insert(uint64_t pos, const T& item) { + ObjectInfoPtr client_insert(uint64_t pos, const T& item) { auto newObj(std::make_shared()); newObj->syncIgnore = true; newObj->id = getNewIDFunc(); @@ -74,16 +74,35 @@ template class CollabList { if(updateCallback) updateCallback(); - return newObj->id; + return newObj; } - void client_erase(const T& item, IDType& erasedID) { + + IDType client_insert(const ObjectInfoPtr& objInfoToInsert) { + objInfoToInsert->syncIgnore = true; + objInfoToInsert->id = getNewIDFunc(); + objInfoToInsert->pos = std::min(static_cast(clientSideList.size()), objInfoToInsert->pos); + objInfoToInsert->obj->collabListInfo = objInfoToInsert; + + clientSideList.insert(clientSideList.begin() + objInfoToInsert->pos, objInfoToInsert); + idToObjectMap.emplace(objInfoToInsert->id, objInfoToInsert); + + client_pos_refresh_after_insert(objInfoToInsert->pos); + + if(clientInsertCallback) + clientInsertCallback(objInfoToInsert); + if(updateCallback) + updateCallback(); + + return objInfoToInsert->id; + } + + void client_erase(const ObjectInfoPtr& objToErase) { bool erasedSomething = false; std::erase_if(clientSideList, [&](const auto& obj) { - if(obj->obj == item) { + if(obj->obj == objToErase->obj) { if(clientEraseCallback) clientEraseCallback(obj); - erasedID = obj->id; - idToObjectMap.erase(erasedID); + idToObjectMap.erase(objToErase->id); obj->syncIgnore = true; obj->obj->collabListInfo.reset(); erasedSomething = true; diff --git a/src/DrawingProgram/BrushTool.cpp b/src/DrawingProgram/BrushTool.cpp index 4ab8bf5..ee76866 100644 --- a/src/DrawingProgram/BrushTool.cpp +++ b/src/DrawingProgram/BrushTool.cpp @@ -106,12 +106,12 @@ void BrushTool::tool_update() { controls.intermediateItem->coords = drawP.world.drawData.cam.c; controls.intermediateItem->lastUpdateTime = std::chrono::steady_clock::now(); uint64_t placement = drawP.components.client_list().size(); - drawP.components.client_insert(placement, controls.intermediateItem); + auto objAdd = drawP.components.client_insert(placement, controls.intermediateItem); controls.intermediateItem->temp_update(drawP); controls.intermediateItem->client_send_place(drawP); // NOTE: The data isnt finallized at this point, so a BVH isn't generated for an object that you undo while youre drawing (this isnt just for brush strokes) // The fix for now is to generate a BVH for an object when you redo it - drawP.add_undo_place_component(placement, controls.intermediateItem); + drawP.add_undo_place_component(objAdd); controls.addedTemporaryPoint = false; } else { @@ -165,8 +165,6 @@ void BrushTool::gui_toolbox() { t.gui.text_label_centered("Brush"); t.gui.checkbox_field("hasroundcaps", "Round Caps", &controls.hasRoundCaps); t.gui.slider_scalar_field("relwidth", "Size", &drawP.controls.relativeWidth, 3.0f, 40.0f); - //t.gui.checkbox_field("drawminrel", "Drawing Min Relative To Size", &controls.drawingMinimumRelativeToSize); - //t.gui.checkbox_field("midwayinterp", "Midway Interpolation", &controls.midwayInterpolation); t.gui.pop_id(); } diff --git a/src/DrawingProgram/DrawingProgram.cpp b/src/DrawingProgram/DrawingProgram.cpp index daf430e..d3919dd 100644 --- a/src/DrawingProgram/DrawingProgram.cpp +++ b/src/DrawingProgram/DrawingProgram.cpp @@ -53,6 +53,7 @@ DrawingProgram::DrawingProgram(World& initWorld): }; components.clientEraseCallback = [&](const CollabListType::ObjectInfoPtr& c) { compCache.erase_component(c); + selection.erase_component(c); delayedUpdateComponents.erase(c->obj); updateableComponents.erase(c->obj); }; @@ -140,18 +141,12 @@ void DrawingProgram::init_client_callbacks() { message(a); std::vector didntExistPreviously = components.server_insert_ordered_vector(sortedPlacedComponents); - if(sortedPlacedComponents.size() >= DrawingProgramCache::MINIMUM_COMPONENTS_TO_START_REBUILD) { - parallel_loop_container(sortedPlacedComponents, [&](const auto& c){ - c->obj->final_update(*this, false); - }); + bool isSingleThread = sortedPlacedComponents.size() < DrawingProgramCache::MINIMUM_COMPONENTS_TO_START_REBUILD; + parallel_loop_container(sortedPlacedComponents, [&](const auto& c){ + c->obj->final_update(*this, isSingleThread); + }, isSingleThread); + if(!isSingleThread) force_rebuild_cache(); - } - else { - for(size_t i = 0; i < sortedPlacedComponents.size(); i++) { - if(didntExistPreviously[i]) - sortedPlacedComponents[i]->obj->final_update(*this); - } - } }); world.con.client_add_recv_callback(CLIENT_ERASE_SINGLE_COMPONENT, [&](cereal::PortableBinaryInputArchive& message) { ServerClientID idToErase; @@ -443,9 +438,9 @@ void DrawingProgram::add_file_to_canvas_by_path_execute(const std::string& fileP img->d.imageID = imageID; img->final_update(*this); uint64_t placement = components.client_list().size(); - components.client_insert(placement, img); + auto objAdd = components.client_insert(placement, img); img->client_send_place(*this); - add_undo_place_component(placement, img); + add_undo_place_component(objAdd); } void DrawingProgram::add_file_to_canvas_by_data(const std::string& fileName, std::string_view fileBuffer, Vector2f dropPos) { @@ -466,9 +461,9 @@ void DrawingProgram::add_file_to_canvas_by_data(const std::string& fileName, std img->d.imageID = imageID; img->final_update(*this); uint64_t placement = components.client_list().size(); - components.client_insert(placement, img); + auto objAdd = components.client_insert(placement, img); img->client_send_place(*this); - add_undo_place_component(placement, img); + add_undo_place_component(objAdd); } void DrawingProgram::reset_tools() { @@ -498,61 +493,26 @@ void DrawingProgram::initialize_draw_data(cereal::PortableBinaryInputArchive& a) compCache.test_rebuild(components.client_list(), true); } -void DrawingProgram::add_undo_place_component(uint64_t placement, const std::shared_ptr& comp) { +void DrawingProgram::add_undo_place_component(const CollabListType::ObjectInfoPtr& objToUndo) { world.undo.push(UndoManager::UndoRedoPair{ - [&, comp]() { - ServerClientID compID; - comp->client_send_erase(*this); - components.client_erase(comp, compID); + [&, objToUndo]() { + objToUndo->obj->client_send_erase(*this); + components.client_erase(objToUndo); reset_tools(); return true; }, - [&, comp, placement]() { - if(comp->collabListInfo.lock()) + [&, objToUndo]() { + if(objToUndo->obj->collabListInfo.lock()) return false; - components.client_insert(placement, comp); - comp->final_update(*this); // Method to recover after an undo that happened while drawing a component - comp->client_send_place(*this); + components.client_insert(objToUndo); + objToUndo->obj->final_update(*this); // Method to recover after an undo that happened while drawing a component + objToUndo->obj->client_send_place(*this); reset_tools(); return true; } }); } -void DrawingProgram::add_undo_place_components(uint64_t placement, const std::vector>& comps) { - world.undo.push(UndoManager::UndoRedoPair{ - [&, comps]() { - bool toRet = true; - std::unordered_set idsToErase; - for(auto& c : comps) { - if(!c->collabListInfo.lock()) - toRet = false; - else { - idsToErase.emplace(c->collabListInfo.lock()->id); - ServerClientID compID; - components.client_erase(c, compID); - } - } - DrawComponent::client_send_erase_set(*this, idsToErase); - reset_tools(); - return toRet; - }, - [&, comps, placement]() { - for(auto& c : comps) - if(c->collabListInfo.lock()) - return false; - for(auto& c : comps | std::views::reverse) { - components.client_insert(placement, c); - c->final_update(*this); // Method to recover after an undo that happened while drawing a component - c->client_send_place(*this); - } - reset_tools(); - return true; - } - }); - -} - ClientPortionID DrawingProgram::get_max_id(ServerPortionID serverID) { ClientPortionID maxClientID = 0; auto& cList = components.client_list(); diff --git a/src/DrawingProgram/DrawingProgram.hpp b/src/DrawingProgram/DrawingProgram.hpp index da6277b..897603b 100644 --- a/src/DrawingProgram/DrawingProgram.hpp +++ b/src/DrawingProgram/DrawingProgram.hpp @@ -76,8 +76,7 @@ class DrawingProgram { void draw_drag_circle(SkCanvas* canvas, const Vector2f& pos, const SkColor4f& c, const DrawData& drawData, float radiusMultiplier = 1.0f); - void add_undo_place_component(uint64_t placement, const std::shared_ptr& comp); - void add_undo_place_components(uint64_t placement, const std::vector>& comps); + void add_undo_place_component(const CollabListType::ObjectInfoPtr& objToUndo); BrushTool brushTool; EraserTool eraserTool; diff --git a/src/DrawingProgram/DrawingProgramSelection.cpp b/src/DrawingProgram/DrawingProgramSelection.cpp index db9a5ae..6798633 100644 --- a/src/DrawingProgram/DrawingProgramSelection.cpp +++ b/src/DrawingProgram/DrawingProgramSelection.cpp @@ -18,6 +18,13 @@ const std::unordered_set& DrawingProgramSelection return selectedSet; } +void DrawingProgramSelection::erase_component(const CollabListType::ObjectInfoPtr& objToCheck) { + if(is_selected(objToCheck)) { + cache.erase_component(objToCheck); + selectedSet.erase(objToCheck); + } +} + void DrawingProgramSelection::add_from_cam_coord_collider_to_selection(const SCollision::ColliderCollection& cC) { auto cCWorld = drawP.world.drawData.cam.c.collider_to_world, SCollision::ColliderCollection>(cC); @@ -32,7 +39,7 @@ void DrawingProgramSelection::add_from_cam_coord_collider_to_selection(const SCo } }; drawP.compCache.traverse_bvh_erase_function(cCWorld.bounds, [&](const auto& bvhNode, auto& comps) { - if(bvhNode && + if(bvhNode && (bvhNode->coords.inverseScale << 9) < drawP.world.drawData.cam.c.inverseScale && SCollision::collide(cC, drawP.world.drawData.cam.c.to_space(bvhNode->bounds.min)) && SCollision::collide(cC, drawP.world.drawData.cam.c.to_space(bvhNode->bounds.max)) && SCollision::collide(cC, drawP.world.drawData.cam.c.to_space(bvhNode->bounds.top_right())) && @@ -78,10 +85,24 @@ void DrawingProgramSelection::deselect_all() { for(auto& transformedObj : a) transformsFrom.emplace_back(transformedObj, transformedObj->obj->coords); + selectedSet.clear(); + cache.clear(); + transformOpHappening = TransformOperation::NONE; + + bool isSingleThread = a.size() < DrawingProgramCache::MINIMUM_COMPONENTS_TO_START_REBUILD; parallel_loop_container(a, [&](auto& obj) { obj->obj->coords = selectionTransformCoords.other_coord_space_from_this_space(obj->obj->coords); - obj->obj->final_update(drawP, false); - }); + obj->obj->finalize_update(drawP, false); + }, isSingleThread); + if(!isSingleThread) + drawP.force_rebuild_cache(); + else { + for(auto& obj : a) + drawP.compCache.add_component(obj); + } + + selectionTransformCoords = CoordSpaceHelper(); + std::vector> transformsTo; std::vector> transformsToSend; for(auto& transformedObj : a) { @@ -101,17 +122,19 @@ void DrawingProgramSelection::deselect_all() { return false; } + bool isSingleThread = a.size() < DrawingProgramCache::MINIMUM_COMPONENTS_TO_START_REBUILD; parallel_loop_container(transformsFrom, [&](auto& p) { auto& [comp, coords] = p; comp->obj->coords = coords; - comp->obj->finalize_update(drawP, false); - }); + comp->obj->finalize_update(drawP, isSingleThread); + }, isSingleThread); DrawComponent::client_send_transform_many(drawP, transformsToSend); drawP.reset_tools(); - drawP.force_rebuild_cache(); + if(!isSingleThread) + drawP.force_rebuild_cache(); return true; }, @@ -125,28 +148,23 @@ void DrawingProgramSelection::deselect_all() { return false; } + bool isSingleThread = a.size() < DrawingProgramCache::MINIMUM_COMPONENTS_TO_START_REBUILD; parallel_loop_container(transformsTo, [&](auto& p) { auto& [comp, coords] = p; comp->obj->coords = coords; - comp->obj->finalize_update(drawP, false); - }); + comp->obj->finalize_update(drawP, isSingleThread); + }, isSingleThread); DrawComponent::client_send_transform_many(drawP, transformsToSend); drawP.reset_tools(); - drawP.force_rebuild_cache(); + if(!isSingleThread) + drawP.force_rebuild_cache(); return true; } }); - - - selectedSet.clear(); - cache.clear(); - selectionTransformCoords = CoordSpaceHelper(); - drawP.force_rebuild_cache(); - transformOpHappening = TransformOperation::NONE; } } diff --git a/src/DrawingProgram/DrawingProgramSelection.hpp b/src/DrawingProgram/DrawingProgramSelection.hpp index 288c7d0..15561ed 100644 --- a/src/DrawingProgram/DrawingProgramSelection.hpp +++ b/src/DrawingProgram/DrawingProgramSelection.hpp @@ -13,6 +13,7 @@ class DrawingProgramSelection { void draw_gui(SkCanvas* canvas, const DrawData& drawData); bool is_something_selected(); bool mouse_collided_with_selection(); + void erase_component(const CollabListType::ObjectInfoPtr& objToCheck); bool is_selected(const CollabListType::ObjectInfoPtr& objToCheck); const std::unordered_set& get_selected_set(); private: diff --git a/src/DrawingProgram/EllipseDrawTool.cpp b/src/DrawingProgram/EllipseDrawTool.cpp index 9dadcd8..84e4e10 100644 --- a/src/DrawingProgram/EllipseDrawTool.cpp +++ b/src/DrawingProgram/EllipseDrawTool.cpp @@ -109,9 +109,9 @@ void EllipseDrawTool::tool_update() { controls.intermediateItem->coords = drawP.world.drawData.cam.c; controls.intermediateItem->lastUpdateTime = std::chrono::steady_clock::now(); uint64_t placement = drawP.components.client_list().size(); - drawP.components.client_insert(placement, controls.intermediateItem); + auto objAdd = drawP.components.client_insert(placement, controls.intermediateItem); controls.intermediateItem->client_send_place(drawP); - drawP.add_undo_place_component(placement, controls.intermediateItem); + drawP.add_undo_place_component(objAdd); controls.intermediateItem->temp_update(drawP); controls.drawStage = 1; } diff --git a/src/DrawingProgram/EraserTool.cpp b/src/DrawingProgram/EraserTool.cpp index f2cde6b..49f36b3 100644 --- a/src/DrawingProgram/EraserTool.cpp +++ b/src/DrawingProgram/EraserTool.cpp @@ -24,10 +24,10 @@ void EraserTool::reset_tool() { if(!erasedComponents.empty()) { DrawingProgramCache::move_components_from_bvh_nodes_to_set(erasedComponents, erasedBVHNodes); - std::unordered_set idsToErase; + std::unordered_set idsToErase1; for(auto& c : erasedComponents) - idsToErase.emplace(c->id); - DrawComponent::client_send_erase_set(drawP, idsToErase); + idsToErase1.emplace(c->id); + DrawComponent::client_send_erase_set(drawP, idsToErase1); drawP.components.client_erase_set(erasedComponents); drawP.world.undo.push(UndoManager::UndoRedoPair{ diff --git a/src/DrawingProgram/RectDrawTool.cpp b/src/DrawingProgram/RectDrawTool.cpp index d1acdb3..eb71581 100644 --- a/src/DrawingProgram/RectDrawTool.cpp +++ b/src/DrawingProgram/RectDrawTool.cpp @@ -109,10 +109,10 @@ void RectDrawTool::tool_update() { controls.intermediateItem->coords = drawP.world.drawData.cam.c; controls.intermediateItem->lastUpdateTime = std::chrono::steady_clock::now(); uint64_t placement = drawP.components.client_list().size(); - controls.intermediateID = drawP.components.client_insert(placement, controls.intermediateItem); + auto objAdd = drawP.components.client_insert(placement, controls.intermediateItem); controls.intermediateItem->temp_update(drawP); controls.intermediateItem->client_send_place(drawP); - drawP.add_undo_place_component(placement, controls.intermediateItem); + drawP.add_undo_place_component(objAdd); controls.drawStage = 1; } break; diff --git a/src/DrawingProgram/RectDrawTool.hpp b/src/DrawingProgram/RectDrawTool.hpp index b61cfd8..43a8161 100644 --- a/src/DrawingProgram/RectDrawTool.hpp +++ b/src/DrawingProgram/RectDrawTool.hpp @@ -28,7 +28,6 @@ class RectDrawTool { int fillStrokeMode = 1; int drawStage = 0; std::shared_ptr intermediateItem; - ServerClientID intermediateID; } controls; DrawingProgram& drawP; diff --git a/src/DrawingProgram/TextBoxTool.cpp b/src/DrawingProgram/TextBoxTool.cpp index 87ef56a..987e43b 100644 --- a/src/DrawingProgram/TextBoxTool.cpp +++ b/src/DrawingProgram/TextBoxTool.cpp @@ -227,9 +227,9 @@ void TextBoxTool::tool_update() { controls.intermediateItem->coords = drawP.world.drawData.cam.c; controls.intermediateItem->lastUpdateTime = std::chrono::steady_clock::now(); uint64_t placement = drawP.components.client_list().size(); - drawP.components.client_insert(placement, controls.intermediateItem); + auto objAdd = drawP.components.client_insert(placement, controls.intermediateItem); controls.intermediateItem->client_send_place(drawP); - drawP.add_undo_place_component(placement, controls.intermediateItem); + drawP.add_undo_place_component(objAdd); controls.drawStage = 1; } break;