From b43eb29465014eec2a7a8e195925a025392fc346 Mon Sep 17 00:00:00 2001
From: elsid <elsid.mail@gmail.com>
Date: Sun, 13 Feb 2022 20:13:14 +0100
Subject: [PATCH 1/2] Log duration of writing save game file

---
 apps/openmw/mwstate/statemanagerimp.cpp | 7 +++++++
 1 file changed, 7 insertions(+)

diff --git a/apps/openmw/mwstate/statemanagerimp.cpp b/apps/openmw/mwstate/statemanagerimp.cpp
index 3b459ec2ce..634dcdab53 100644
--- a/apps/openmw/mwstate/statemanagerimp.cpp
+++ b/apps/openmw/mwstate/statemanagerimp.cpp
@@ -188,6 +188,8 @@ void MWState::StateManager::saveGame (const std::string& description, const Slot
 
     try
     {
+        const auto start = std::chrono::steady_clock::now();
+
         if (!character)
         {
             MWWorld::ConstPtr player = MWMechanics::getPlayer();
@@ -302,6 +304,11 @@ void MWState::StateManager::saveGame (const std::string& description, const Slot
 
         Settings::Manager::setString ("character", "Saves",
             slot->mPath.parent_path().filename().string());
+
+        const auto finish = std::chrono::steady_clock::now();
+
+        Log(Debug::Info) << '\'' << description << "' is saved in "
+            << std::chrono::duration_cast<std::chrono::duration<float, std::milli>>(finish - start).count() << "ms";
     }
     catch (const std::exception& e)
     {

From a4d7b7251122d1b31194ffb69053ef5343c71c7c Mon Sep 17 00:00:00 2001
From: elsid <elsid.mail@gmail.com>
Date: Sun, 13 Feb 2022 20:45:05 +0100
Subject: [PATCH 2/2] Write png image of the global map for save asynchronously

Write global map to the save file last to give more time for async job to
finish.
---
 apps/openmw/mwbase/windowmanager.hpp    |  2 +
 apps/openmw/mwgui/mapwindow.cpp         |  8 +++-
 apps/openmw/mwgui/mapwindow.hpp         |  4 +-
 apps/openmw/mwgui/windowmanagerimp.cpp  |  5 +++
 apps/openmw/mwgui/windowmanagerimp.hpp  |  2 +
 apps/openmw/mwrender/globalmap.cpp      | 59 +++++++++++++++++++------
 apps/openmw/mwrender/globalmap.hpp      |  5 +++
 apps/openmw/mwstate/statemanagerimp.cpp |  8 ++--
 8 files changed, 74 insertions(+), 19 deletions(-)

diff --git a/apps/openmw/mwbase/windowmanager.hpp b/apps/openmw/mwbase/windowmanager.hpp
index 3044e13e9b..eeb2d6a56b 100644
--- a/apps/openmw/mwbase/windowmanager.hpp
+++ b/apps/openmw/mwbase/windowmanager.hpp
@@ -358,6 +358,8 @@ namespace MWBase
 
             virtual void onDeleteCustomData(const MWWorld::Ptr& ptr) = 0;
             virtual void forceLootMode(const MWWorld::Ptr& ptr) = 0;
+
+            virtual void asyncPrepareSaveMap() = 0;
     };
 }
 
diff --git a/apps/openmw/mwgui/mapwindow.cpp b/apps/openmw/mwgui/mapwindow.cpp
index 2aa1b51950..b5f281d333 100644
--- a/apps/openmw/mwgui/mapwindow.cpp
+++ b/apps/openmw/mwgui/mapwindow.cpp
@@ -759,7 +759,7 @@ namespace MWGui
         , mGlobal(Settings::Manager::getBool("global", "Map"))
         , mEventBoxGlobal(nullptr)
         , mEventBoxLocal(nullptr)
-        , mGlobalMapRender(new MWRender::GlobalMap(localMapRender->getRoot(), workQueue))
+        , mGlobalMapRender(std::make_unique<MWRender::GlobalMap>(localMapRender->getRoot(), workQueue))
         , mEditNoteDialog()
     {
         static bool registered = false;
@@ -1028,7 +1028,6 @@ namespace MWGui
 
     MapWindow::~MapWindow()
     {
-        delete mGlobalMapRender;
     }
 
     void MapWindow::setCellName(const std::string& cellName)
@@ -1357,6 +1356,11 @@ namespace MWGui
         marker->eventMouseWheel += MyGUI::newDelegate(this, &MapWindow::onMapZoomed);
     }
 
+    void MapWindow::asyncPrepareSaveMap()
+    {
+        mGlobalMapRender->asyncWritePng();
+    }
+
     // -------------------------------------------------------------------
 
     EditNoteDialog::EditNoteDialog()
diff --git a/apps/openmw/mwgui/mapwindow.hpp b/apps/openmw/mwgui/mapwindow.hpp
index 9f6ea1339d..c50a92dac3 100644
--- a/apps/openmw/mwgui/mapwindow.hpp
+++ b/apps/openmw/mwgui/mapwindow.hpp
@@ -260,6 +260,8 @@ namespace MWGui
         void write (ESM::ESMWriter& writer, Loading::Listener& progress);
         void readRecord (ESM::ESMReader& reader, uint32_t type);
 
+        void asyncPrepareSaveMap();
+
     private:
         void onDragStart(MyGUI::Widget* _sender, int _left, int _top, MyGUI::MouseButton _id);
         void onMouseDrag(MyGUI::Widget* _sender, int _left, int _top, MyGUI::MouseButton _id);
@@ -304,7 +306,7 @@ namespace MWGui
         MyGUI::Button* mEventBoxLocal;
 
         float mGlobalMapZoom = 1.0f;
-        MWRender::GlobalMap* mGlobalMapRender;
+        std::unique_ptr<MWRender::GlobalMap> mGlobalMapRender;
 
         struct MapMarkerType
         {
diff --git a/apps/openmw/mwgui/windowmanagerimp.cpp b/apps/openmw/mwgui/windowmanagerimp.cpp
index c2f7785da3..04e48fb280 100644
--- a/apps/openmw/mwgui/windowmanagerimp.cpp
+++ b/apps/openmw/mwgui/windowmanagerimp.cpp
@@ -2263,4 +2263,9 @@ namespace MWGui
         for(auto* window : mWindows)
             window->onDeleteCustomData(ptr);
     }
+
+    void WindowManager::asyncPrepareSaveMap()
+    {
+        mMap->asyncPrepareSaveMap();
+    }
 }
diff --git a/apps/openmw/mwgui/windowmanagerimp.hpp b/apps/openmw/mwgui/windowmanagerimp.hpp
index ef19329cdf..11d10ab45e 100644
--- a/apps/openmw/mwgui/windowmanagerimp.hpp
+++ b/apps/openmw/mwgui/windowmanagerimp.hpp
@@ -392,6 +392,8 @@ namespace MWGui
     void onDeleteCustomData(const MWWorld::Ptr& ptr) override;
     void forceLootMode(const MWWorld::Ptr& ptr) override;
 
+    void asyncPrepareSaveMap() override;
+
   private:
     unsigned int mOldUpdateMask; unsigned int mOldCullMask;
 
diff --git a/apps/openmw/mwrender/globalmap.cpp b/apps/openmw/mwrender/globalmap.cpp
index 5da79ec037..3d0a066451 100644
--- a/apps/openmw/mwrender/globalmap.cpp
+++ b/apps/openmw/mwrender/globalmap.cpp
@@ -93,6 +93,26 @@ namespace
         MWRender::GlobalMap* mParent;
     };
 
+    std::vector<char> writePng(const osg::Image& overlayImage)
+    {
+        std::ostringstream ostream;
+        osgDB::ReaderWriter* readerwriter = osgDB::Registry::instance()->getReaderWriterForExtension("png");
+        if (!readerwriter)
+        {
+            Log(Debug::Error) << "Error: Can't write map overlay: no png readerwriter found";
+            return std::vector<char>();
+        }
+
+        osgDB::ReaderWriter::WriteResult result = readerwriter->writeImage(overlayImage, ostream);
+        if (!result.success())
+        {
+            Log(Debug::Warning) << "Error: Can't write map overlay: " << result.message() << " code " << result.status();
+            return std::vector<char>();
+        }
+
+        std::string data = ostream.str();
+        return std::vector<char>(data.begin(), data.end());
+    }
 }
 
 namespace MWRender
@@ -221,6 +241,20 @@ namespace MWRender
         osg::ref_ptr<osg::Texture2D> mOverlayTexture;
     };
 
+    struct GlobalMap::WritePng final : public SceneUtil::WorkItem
+    {
+        osg::ref_ptr<const osg::Image> mOverlayImage;
+        std::vector<char> mImageData;
+
+        explicit WritePng(osg::ref_ptr<const osg::Image> overlayImage)
+            : mOverlayImage(std::move(overlayImage)) {}
+
+        void doWork() override
+        {
+            mImageData = writePng(*mOverlayImage);
+        }
+    };
+
     GlobalMap::GlobalMap(osg::Group* root, SceneUtil::WorkQueue* workQueue)
         : mRoot(root)
         , mWorkQueue(workQueue)
@@ -400,23 +434,15 @@ namespace MWRender
         map.mBounds.mMinY = mMinY;
         map.mBounds.mMaxY = mMaxY;
 
-        std::ostringstream ostream;
-        osgDB::ReaderWriter* readerwriter = osgDB::Registry::instance()->getReaderWriterForExtension("png");
-        if (!readerwriter)
+        if (mWritePng != nullptr)
         {
-            Log(Debug::Error) << "Error: Can't write map overlay: no png readerwriter found";
+            mWritePng->waitTillDone();
+            map.mImageData = std::move(mWritePng->mImageData);
+            mWritePng = nullptr;
             return;
         }
 
-        osgDB::ReaderWriter::WriteResult result = readerwriter->writeImage(*mOverlayImage, ostream);
-        if (!result.success())
-        {
-            Log(Debug::Warning) << "Error: Can't write map overlay: " << result.message() << " code " << result.status();
-            return;
-        }
-
-        std::string data = ostream.str();
-        map.mImageData = std::vector<char>(data.begin(), data.end());
+        map.mImageData = writePng(*mOverlayImage);
     }
 
     struct Box
@@ -606,4 +632,11 @@ namespace MWRender
         cam->removeChildren(0, cam->getNumChildren());
         mRoot->removeChild(cam);
     }
+
+    void GlobalMap::asyncWritePng()
+    {
+        // Use deep copy to avoid any sychronization
+        mWritePng = new WritePng(new osg::Image(*mOverlayImage, osg::CopyOp::DEEP_COPY_ALL));
+        mWorkQueue->addWorkItem(mWritePng, /*front=*/true);
+    }
 }
diff --git a/apps/openmw/mwrender/globalmap.hpp b/apps/openmw/mwrender/globalmap.hpp
index fd8a8d1016..28531f14df 100644
--- a/apps/openmw/mwrender/globalmap.hpp
+++ b/apps/openmw/mwrender/globalmap.hpp
@@ -72,7 +72,11 @@ namespace MWRender
 
         void ensureLoaded();
 
+        void asyncWritePng();
+
     private:
+        struct WritePng;
+
         /**
          * Request rendering a 2d quad onto mOverlayTexture.
          * x, y, width and height are the destination coordinates (top-left coordinate origin)
@@ -121,6 +125,7 @@ namespace MWRender
 
         osg::ref_ptr<SceneUtil::WorkQueue> mWorkQueue;
         osg::ref_ptr<CreateMapWorkItem> mWorkItem;
+        osg::ref_ptr<WritePng> mWritePng;
 
         int mWidth;
         int mHeight;
diff --git a/apps/openmw/mwstate/statemanagerimp.cpp b/apps/openmw/mwstate/statemanagerimp.cpp
index 634dcdab53..c8cd53a331 100644
--- a/apps/openmw/mwstate/statemanagerimp.cpp
+++ b/apps/openmw/mwstate/statemanagerimp.cpp
@@ -190,6 +190,8 @@ void MWState::StateManager::saveGame (const std::string& description, const Slot
     {
         const auto start = std::chrono::steady_clock::now();
 
+        MWBase::Environment::get().getWindowManager()->asyncPrepareSaveMap();
+
         if (!character)
         {
             MWWorld::ConstPtr player = MWMechanics::getPlayer();
@@ -257,9 +259,9 @@ void MWState::StateManager::saveGame (const std::string& description, const Slot
                 +MWBase::Environment::get().getWorld()->countSavedGameRecords()
                 +MWBase::Environment::get().getScriptManager()->getGlobalScripts().countSavedGameRecords()
                 +MWBase::Environment::get().getDialogueManager()->countSavedGameRecords()
-                +MWBase::Environment::get().getWindowManager()->countSavedGameRecords()
                 +MWBase::Environment::get().getMechanicsManager()->countSavedGameRecords()
-                +MWBase::Environment::get().getInputManager()->countSavedGameRecords();
+                +MWBase::Environment::get().getInputManager()->countSavedGameRecords()
+                +MWBase::Environment::get().getWindowManager()->countSavedGameRecords();
         writer.setRecordCount (recordCount);
 
         writer.save (stream);
@@ -282,9 +284,9 @@ void MWState::StateManager::saveGame (const std::string& description, const Slot
         MWBase::Environment::get().getLuaManager()->write(writer, listener);
         MWBase::Environment::get().getWorld()->write (writer, listener);
         MWBase::Environment::get().getScriptManager()->getGlobalScripts().write (writer, listener);
-        MWBase::Environment::get().getWindowManager()->write(writer, listener);
         MWBase::Environment::get().getMechanicsManager()->write(writer, listener);
         MWBase::Environment::get().getInputManager()->write(writer, listener);
+        MWBase::Environment::get().getWindowManager()->write(writer, listener);
 
         // Ensure we have written the number of records that was estimated
         if (writer.getRecordCount() != recordCount+1) // 1 extra for TES3 record