From be2ba1e28594094f3ad3e22711de1b527dded172 Mon Sep 17 00:00:00 2001 From: Yousef Khadadeh Date: Fri, 2 Jan 2026 17:11:32 +0300 Subject: [PATCH] Started work on new undo system --- CMakeLists.txt | 1 + .../NetworkingObjects/NetObjManager.cpp | 6 + .../NetworkingObjects/NetObjManager.hpp | 4 + .../NetworkingObjects/NetObjOwnerPtr.impl.hpp | 5 + src/World.cpp | 10 +- src/World.hpp | 4 +- src/WorldUndoManager.cpp | 107 ++++++++++++++++++ src/WorldUndoManager.hpp | 49 ++++++++ 8 files changed, 182 insertions(+), 4 deletions(-) create mode 100644 src/WorldUndoManager.cpp create mode 100644 src/WorldUndoManager.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 47d6a94..7143597 100755 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -49,6 +49,7 @@ set(sources "src/main.cpp" "src/FileHelpers.cpp" "src/VersionConstants.cpp" "src/ScaleUpCanvas.cpp" + "src/WorldUndoManager.cpp" "src/Bookmarks/BookmarkListItem.cpp" "src/Bookmarks/BookmarkManager.cpp" "src/ResourceDisplay/ResourceDisplay.cpp" diff --git a/include/Helpers/NetworkingObjects/NetObjManager.cpp b/include/Helpers/NetworkingObjects/NetObjManager.cpp index 0d774a0..74af80a 100644 --- a/include/Helpers/NetworkingObjects/NetObjManager.cpp +++ b/include/Helpers/NetworkingObjects/NetObjManager.cpp @@ -43,6 +43,12 @@ namespace NetworkingObjects { bool NetObjManager::is_connected() const { return (server && !server->is_disconnected()) || (client && !client->is_disconnected()); } + void NetObjManager::set_netid_reassign_callback(const std::function& newNetIDReassignCallback) { + netIDReassignCallback = newNetIDReassignCallback; + } + void NetObjManager::set_netobj_destroy_callback(const std::function& newDestroyCallback) { + destroyCallback = newDestroyCallback; + } void NetObjManager::send_multi_update_messsage(std::function captureSendBlock, SendUpdateType updateType, const std::shared_ptr& specificClient) { if(is_connected()) { std::shared_ptr d = std::make_shared(); diff --git a/include/Helpers/NetworkingObjects/NetObjManager.hpp b/include/Helpers/NetworkingObjects/NetObjManager.hpp index c96dbfb..0c72c84 100644 --- a/include/Helpers/NetworkingObjects/NetObjManager.hpp +++ b/include/Helpers/NetworkingObjects/NetObjManager.hpp @@ -27,6 +27,8 @@ namespace NetworkingObjects { void disconnect(); bool is_server() const; bool is_connected() const; + void set_netid_reassign_callback(const std::function& newNetIDReassignCallback); + void set_netobj_destroy_callback(const std::function& newDestroyCallback); template NetObjOwnerPtr read_create_message(cereal::PortableBinaryInputArchive& a, const std::shared_ptr& clientReceivedFrom) { NetObjID id; a(id); @@ -173,5 +175,7 @@ namespace NetworkingObjects { std::unordered_map objectData; NetObjManagerTypeList typeList; NetTypeIDType nextTypeID; + std::function netIDReassignCallback; + std::function destroyCallback; }; } diff --git a/include/Helpers/NetworkingObjects/NetObjOwnerPtr.impl.hpp b/include/Helpers/NetworkingObjects/NetObjOwnerPtr.impl.hpp index 36f85b3..722d8ff 100644 --- a/include/Helpers/NetworkingObjects/NetObjOwnerPtr.impl.hpp +++ b/include/Helpers/NetworkingObjects/NetObjOwnerPtr.impl.hpp @@ -34,6 +34,8 @@ namespace NetworkingObjects { template NetObjOwnerPtr::~NetObjOwnerPtr() { if(objMan && rawPtr) { + if(objMan->destroyCallback) + objMan->destroyCallback(id); auto it = objMan->objectData.find(id); delete static_cast(it->second.p); objMan->objectData.erase(it); @@ -49,10 +51,13 @@ namespace NetworkingObjects { NetObjManager::SingleObjectData objData = it->second; objData.p = rawPtr; objMan->objectData.erase(it); + NetworkingObjects::NetObjID oldID = id; id = NetObjID::random_gen(); objMan->objectData.emplace(id, objData); if constexpr(hasReassignNetObjIds) rawPtr->reassign_netobj_ids_call(); + if(objMan->netIDReassignCallback) + objMan->netIDReassignCallback(oldID, id); } } diff --git a/src/World.cpp b/src/World.cpp index 66f7bd6..43af720 100644 --- a/src/World.cpp +++ b/src/World.cpp @@ -38,6 +38,7 @@ World::World(MainProgram& initMain, OpenWorldInfo& worldInfo): netObjMan(!worldInfo.isClient), main(initMain), + undo(*this), rMan(*this), drawProg(*this), bMan(*this), @@ -45,6 +46,12 @@ World::World(MainProgram& initMain, OpenWorldInfo& worldInfo): canvasTheme(*this) { init_net_obj_type_list(); + netObjMan.set_netobj_destroy_callback([&undo = undo](const NetworkingObjects::NetObjID& idToDestroy) { + undo.remove_by_netid(idToDestroy); + }); + netObjMan.set_netid_reassign_callback([&undo = undo](const NetworkingObjects::NetObjID& oldID, const NetworkingObjects::NetObjID& newID) { + undo.reassign_netid(oldID, newID); + }); netSource = worldInfo.netSource; @@ -474,8 +481,7 @@ void World::scale_up(const WorldScalar& scaleUpAmount) { // This means that the scale up message must be send BEFORE the World::scale_up function is called on our end // if this client is the one responsible for the scale up drawProg.scale_up(scaleUpAmount); - - undo.clear(); // Do last to make sure that any undo generated while scaling up is removed, as it contains outdated scale info + undo.scale_up(scaleUpAmount); } void World::draw(SkCanvas* canvas) { diff --git a/src/World.hpp b/src/World.hpp index 260e70b..e706cbd 100644 --- a/src/World.hpp +++ b/src/World.hpp @@ -2,7 +2,7 @@ #include #include #include "Helpers/NetworkingObjects/NetObjUnorderedSet.hpp" -#include "UndoManager.hpp" +#include "WorldUndoManager.hpp" #include "Bookmarks/BookmarkManager.hpp" #include "ResourceManager.hpp" #include "DrawingProgram/DrawingProgram.hpp" @@ -42,7 +42,7 @@ class World { MainProgram& main; DrawData drawData; - UndoManager undo; + WorldUndoManager undo; ResourceManager rMan; DrawingProgram drawProg; BookmarkManager bMan; diff --git a/src/WorldUndoManager.cpp b/src/WorldUndoManager.cpp new file mode 100644 index 0000000..815ad4e --- /dev/null +++ b/src/WorldUndoManager.cpp @@ -0,0 +1,107 @@ +#include "WorldUndoManager.hpp" + +void WorldUndoAction::scale_up(const WorldScalar& scaleAmount) {} +WorldUndoAction::~WorldUndoAction() {} + +WorldUndoManager::WorldUndoManager(World& initWorld): + world(initWorld) +{} + +bool WorldUndoManager::can_undo() { + return !undoQueue.empty(); +} + +bool WorldUndoManager::can_redo() { + return !redoQueue.empty(); +} + +void WorldUndoManager::push(std::unique_ptr undoAction) { + push_undo(std::move(undoAction)); + redoQueue.clear(); +} + +void WorldUndoManager::push_undo(std::unique_ptr undoAction) { + if(undoQueue.size() == UNDO_QUEUE_LIMIT) + undoQueue.pop_front(); + undoQueue.emplace_back(std::move(undoAction)); +} + +void WorldUndoManager::push_redo(std::unique_ptr undoAction) { + if(redoQueue.size() == UNDO_QUEUE_LIMIT) + redoQueue.pop_front(); + redoQueue.emplace_back(std::move(undoAction)); +} + +void WorldUndoManager::undo() { + if(undoQueue.empty()) + return; + if(!undoQueue.back()->undo(*this)) + clear(); + else { + push_redo(std::move(undoQueue.back())); + undoQueue.pop_back(); + } +} + +void WorldUndoManager::redo() { + if(redoQueue.empty()) + return; + if(!redoQueue.back()->redo(*this)) + clear(); + else { + push_undo(std::move(redoQueue.back())); + redoQueue.pop_back(); + } +} + +void WorldUndoManager::clear() { + redoQueue.clear(); + undoQueue.clear(); +} + +void WorldUndoManager::scale_up(const WorldScalar& scaleAmount) { + for(auto& u : undoQueue) + u->scale_up(scaleAmount); + for(auto& r : redoQueue) + r->scale_up(scaleAmount); +} + +void WorldUndoManager::reassign_netid(const NetworkingObjects::NetObjID& oldNetObjID, const NetworkingObjects::NetObjID& newNetObjID) { + auto netIDtoUndoIDIterator = netIDToUndoID.find(oldNetObjID); + if(netIDtoUndoIDIterator != netIDToUndoID.end()) { + UndoObjectID undoID = netIDtoUndoIDIterator->second; + netIDToUndoID.erase(netIDtoUndoIDIterator); + netIDToUndoID.emplace(newNetObjID, undoID); + undoIDToNetID[undoID] = newNetObjID; + } +} + +void WorldUndoManager::remove_by_netid(const NetworkingObjects::NetObjID& netObjID) { + auto netIDtoUndoIDIterator = netIDToUndoID.find(netObjID); + if(netIDtoUndoIDIterator != netIDToUndoID.end()) { + undoIDToNetID.erase(netIDtoUndoIDIterator->second); + netIDToUndoID.erase(netIDtoUndoIDIterator); + } +} + +void WorldUndoManager::remove_by_undoid(UndoObjectID undoID) { + auto undoIDtoNetIDIterator = undoIDToNetID.find(undoID); + if(undoIDtoNetIDIterator != undoIDToNetID.end()) { + netIDToUndoID.erase(undoIDtoNetIDIterator->second); + undoIDToNetID.erase(undoID); + } +} + +WorldUndoManager::UndoObjectID WorldUndoManager::register_new_netobj(const NetworkingObjects::NetObjID& netObjID) { + ++lastUndoObjectID; + undoIDToNetID[lastUndoObjectID] = netObjID; + netIDToUndoID[netObjID] = lastUndoObjectID; + return lastUndoObjectID; +} + +std::optional WorldUndoManager::get_netid_from_undoid(UndoObjectID undoID) { + auto it = undoIDToNetID.find(undoID); + if(it == undoIDToNetID.end()) + return std::nullopt; + return it->second; +} diff --git a/src/WorldUndoManager.hpp b/src/WorldUndoManager.hpp new file mode 100644 index 0000000..bb42e54 --- /dev/null +++ b/src/WorldUndoManager.hpp @@ -0,0 +1,49 @@ +#pragma once +#include +#include +#include +#include + +class World; +class WorldUndoManager; + +class WorldUndoAction { + public: + virtual bool undo(WorldUndoManager& undoMan) = 0; + virtual bool redo(WorldUndoManager& undoMan) = 0; + virtual void scale_up(const WorldScalar& scaleAmount); + virtual ~WorldUndoAction(); +}; + +class WorldUndoManager { + public: + typedef uint64_t UndoObjectID; + WorldUndoManager(World& initWorld); + + void push(std::unique_ptr undoAction); + void undo(); + void redo(); + void clear(); + bool can_undo(); + bool can_redo(); + void scale_up(const WorldScalar& scaleAmount); + + void reassign_netid(const NetworkingObjects::NetObjID& oldNetObjID, const NetworkingObjects::NetObjID& newNetObjID); + void remove_by_netid(const NetworkingObjects::NetObjID& netObjID); + void remove_by_undoid(UndoObjectID undoID); + UndoObjectID register_new_netobj(const NetworkingObjects::NetObjID& netObjID); + std::optional get_netid_from_undoid(UndoObjectID undoID); + + World& world; + private: + void push_undo(std::unique_ptr undoAction); + void push_redo(std::unique_ptr undoAction); + constexpr static size_t UNDO_QUEUE_LIMIT = 500; + + std::unordered_map undoIDToNetID; + std::unordered_map netIDToUndoID; + std::deque> undoQueue; + std::deque> redoQueue; + + UndoObjectID lastUndoObjectID = 0; +};