1
0
Fork 0
mirror of https://github.com/OpenMW/openmw.git synced 2025-05-01 01:41:24 +00:00

Merge branch 'cs_optimize_saving' into 'master'

Fix editor saving performance

See merge request OpenMW/openmw!2741
This commit is contained in:
psi29a 2023-02-17 13:30:20 +00:00
commit d846a9fc15
8 changed files with 117 additions and 60 deletions

View file

@ -6,12 +6,40 @@
#include <QTimer> #include <QTimer>
#include <components/debug/debuglog.hpp>
#include <apps/opencs/model/doc/messages.hpp> #include <apps/opencs/model/doc/messages.hpp>
#include "../world/universalid.hpp" #include "../world/universalid.hpp"
#include "stage.hpp" #include "stage.hpp"
namespace CSMDoc
{
namespace
{
std::string_view operationToString(State value)
{
switch (value)
{
case State_Saving:
return "Saving";
case State_Merging:
return "Merging";
case State_Verifying:
return "Verifying";
case State_Searching:
return "Searching";
case State_Loading:
return "Loading";
default:
break;
}
return "Unknown";
}
}
}
void CSMDoc::Operation::prepareStages() void CSMDoc::Operation::prepareStages()
{ {
mCurrentStage = mStages.begin(); mCurrentStage = mStages.begin();
@ -27,7 +55,7 @@ void CSMDoc::Operation::prepareStages()
} }
} }
CSMDoc::Operation::Operation(int type, bool ordered, bool finalAlways) CSMDoc::Operation::Operation(State type, bool ordered, bool finalAlways)
: mType(type) : mType(type)
, mStages(std::vector<std::pair<Stage*, int>>()) , mStages(std::vector<std::pair<Stage*, int>>())
, mCurrentStage(mStages.begin()) , mCurrentStage(mStages.begin())
@ -61,6 +89,7 @@ void CSMDoc::Operation::run()
} }
mPrepared = false; mPrepared = false;
mStart = std::chrono::steady_clock::now();
mTimer->start(0); mTimer->start(0);
} }
@ -140,7 +169,17 @@ void CSMDoc::Operation::executeStage()
emit reportMessage(*iter, mType); emit reportMessage(*iter, mType);
if (mCurrentStage == mStages.end()) if (mCurrentStage == mStages.end())
{
if (mStart.has_value())
{
const auto duration = std::chrono::steady_clock::now() - *mStart;
Log(Debug::Verbose) << operationToString(mType) << " operation is completed in "
<< std::chrono::duration_cast<std::chrono::duration<double>>(duration).count() << 's';
mStart.reset();
}
operationDone(); operationDone();
}
} }
void CSMDoc::Operation::operationDone() void CSMDoc::Operation::operationDone()

View file

@ -1,12 +1,15 @@
#ifndef CSM_DOC_OPERATION_H #ifndef CSM_DOC_OPERATION_H
#define CSM_DOC_OPERATION_H #define CSM_DOC_OPERATION_H
#include <chrono>
#include <optional>
#include <utility> #include <utility>
#include <vector> #include <vector>
#include <QObject> #include <QObject>
#include "messages.hpp" #include "messages.hpp"
#include "state.hpp"
class QTimer; class QTimer;
@ -18,7 +21,7 @@ namespace CSMDoc
{ {
Q_OBJECT Q_OBJECT
int mType; State mType;
std::vector<std::pair<Stage*, int>> mStages; // stage, number of steps std::vector<std::pair<Stage*, int>> mStages; // stage, number of steps
std::vector<std::pair<Stage*, int>>::iterator mCurrentStage; std::vector<std::pair<Stage*, int>>::iterator mCurrentStage;
int mCurrentStep; int mCurrentStep;
@ -31,11 +34,12 @@ namespace CSMDoc
QTimer* mTimer; QTimer* mTimer;
bool mPrepared; bool mPrepared;
Message::Severity mDefaultSeverity; Message::Severity mDefaultSeverity;
std::optional<std::chrono::steady_clock::time_point> mStart;
void prepareStages(); void prepareStages();
public: public:
Operation(int type, bool ordered, bool finalAlways = false); Operation(State type, bool ordered, bool finalAlways = false);
///< \param ordered Stages must be executed in the given order. ///< \param ordered Stages must be executed in the given order.
/// \param finalAlways Execute last stage even if an error occurred during earlier stages. /// \param finalAlways Execute last stage even if an error occurred during earlier stages.

View file

@ -125,6 +125,7 @@ CSMDoc::WriteDialogueCollectionStage::WriteDialogueCollectionStage(Document& doc
int CSMDoc::WriteDialogueCollectionStage::setup() int CSMDoc::WriteDialogueCollectionStage::setup()
{ {
mInfosByTopic = mInfos.getInfosByTopic();
return mTopics.getSize(); return mTopics.getSize();
} }
@ -145,14 +146,17 @@ void CSMDoc::WriteDialogueCollectionStage::perform(int stage, Messages& messages
// Test, if we need to save anything associated info records. // Test, if we need to save anything associated info records.
bool infoModified = false; bool infoModified = false;
const auto infos = mInfos.getTopicInfos(topic.get().mId); const auto topicInfos = mInfosByTopic.find(topic.get().mId);
for (const auto& record : infos) if (topicInfos != mInfosByTopic.end())
{ {
if (record->isModified() || record->mState == CSMWorld::RecordBase::State_Deleted) for (const auto& record : topicInfos->second)
{ {
infoModified = true; if (record->isModified() || record->mState == CSMWorld::RecordBase::State_Deleted)
break; {
infoModified = true;
break;
}
} }
} }
@ -173,31 +177,36 @@ void CSMDoc::WriteDialogueCollectionStage::perform(int stage, Messages& messages
} }
// write modified selected info records // write modified selected info records
for (auto iter = infos.begin(); iter != infos.end(); ++iter) if (topicInfos != mInfosByTopic.end())
{ {
const CSMWorld::Record<CSMWorld::Info>& record = **iter; const std::vector<const CSMWorld::Record<CSMWorld::Info>*>& infos = topicInfos->second;
if (record.isModified() || record.mState == CSMWorld::RecordBase::State_Deleted) for (auto iter = infos.begin(); iter != infos.end(); ++iter)
{ {
ESM::DialInfo info = record.get(); const CSMWorld::Record<CSMWorld::Info>& record = **iter;
info.mId = record.get().mOriginalId;
info.mPrev = ESM::RefId::sEmpty; if (record.isModified() || record.mState == CSMWorld::RecordBase::State_Deleted)
if (iter != infos.begin())
{ {
const auto prev = std::prev(iter); ESM::DialInfo info = record.get();
info.mPrev = (*prev)->get().mOriginalId; info.mId = record.get().mOriginalId;
info.mPrev = ESM::RefId::sEmpty;
if (iter != infos.begin())
{
const auto prev = std::prev(iter);
info.mPrev = (*prev)->get().mOriginalId;
}
const auto next = std::next(iter);
info.mNext = ESM::RefId::sEmpty;
if (next != infos.end())
info.mNext = (*next)->get().mOriginalId;
writer.startRecord(info.sRecordId);
info.save(writer, record.mState == CSMWorld::RecordBase::State_Deleted);
writer.endRecord(info.sRecordId);
} }
const auto next = std::next(iter);
info.mNext = ESM::RefId::sEmpty;
if (next != infos.end())
info.mNext = (*next)->get().mOriginalId;
writer.startRecord(info.sRecordId);
info.save(writer, record.mState == CSMWorld::RecordBase::State_Deleted);
writer.endRecord(info.sRecordId);
} }
} }
} }

View file

@ -4,8 +4,10 @@
#include "stage.hpp" #include "stage.hpp"
#include <deque> #include <deque>
#include <unordered_map>
#include "../world/idcollection.hpp" #include "../world/idcollection.hpp"
#include "../world/infocollection.hpp"
#include "../world/record.hpp" #include "../world/record.hpp"
#include "../world/scope.hpp" #include "../world/scope.hpp"
@ -118,6 +120,7 @@ namespace CSMDoc
SavingState& mState; SavingState& mState;
const CSMWorld::IdCollection<ESM::Dialogue>& mTopics; const CSMWorld::IdCollection<ESM::Dialogue>& mTopics;
CSMWorld::InfoCollection& mInfos; CSMWorld::InfoCollection& mInfos;
CSMWorld::InfosRecordPtrByTopic mInfosByTopic;
public: public:
WriteDialogueCollectionStage(Document& document, SavingState& state, bool journal); WriteDialogueCollectionStage(Document& document, SavingState& state, bool journal);

View file

@ -32,7 +32,7 @@ CSMTools::JournalCheckStage::JournalCheckStage(
int CSMTools::JournalCheckStage::setup() int CSMTools::JournalCheckStage::setup()
{ {
mIgnoreBaseRecords = CSMPrefs::get()["Reports"]["ignore-base-records"].isTrue(); mIgnoreBaseRecords = CSMPrefs::get()["Reports"]["ignore-base-records"].isTrue();
mInfosByTopic = mJournalInfos.getInfosByTopic();
return mJournals.getSize(); return mJournals.getSize();
} }
@ -50,38 +50,39 @@ void CSMTools::JournalCheckStage::perform(int stage, CSMDoc::Messages& messages)
int totalInfoCount = 0; int totalInfoCount = 0;
std::set<int> questIndices; std::set<int> questIndices;
for (const CSMWorld::Record<CSMWorld::Info>* record : mJournalInfos.getTopicInfos(journal.mId)) if (const auto infos = mInfosByTopic.find(journal.mId); infos != mInfosByTopic.end())
{ {
if (record->isDeleted()) for (const CSMWorld::Record<CSMWorld::Info>* record : infos->second)
continue;
const CSMWorld::Info& journalInfo = record->get();
totalInfoCount += 1;
if (journalInfo.mQuestStatus == ESM::DialInfo::QS_Name)
{ {
statusNamedCount += 1; if (record->isDeleted())
} continue;
// Skip "Base" records (setting!) const CSMWorld::Info& journalInfo = record->get();
if (mIgnoreBaseRecords && record->mState == CSMWorld::RecordBase::State_BaseOnly)
continue;
if (journalInfo.mResponse.empty()) totalInfoCount += 1;
{
CSMWorld::UniversalId id(CSMWorld::UniversalId::Type_JournalInfo, journalInfo.mId);
messages.add(id, "Missing journal entry text", "", CSMDoc::Message::Severity_Warning);
}
std::pair<std::set<int>::iterator, bool> result = questIndices.insert(journalInfo.mData.mJournalIndex); if (journalInfo.mQuestStatus == ESM::DialInfo::QS_Name)
{
statusNamedCount += 1;
}
// Duplicate index // Skip "Base" records (setting!)
if (!result.second) if (mIgnoreBaseRecords && record->mState == CSMWorld::RecordBase::State_BaseOnly)
{ continue;
CSMWorld::UniversalId id(CSMWorld::UniversalId::Type_JournalInfo, journalInfo.mId);
messages.add(id, "Duplicated quest index " + std::to_string(journalInfo.mData.mJournalIndex), "", if (journalInfo.mResponse.empty())
CSMDoc::Message::Severity_Error); {
CSMWorld::UniversalId id(CSMWorld::UniversalId::Type_JournalInfo, journalInfo.mId);
messages.add(id, "Missing journal entry text", "", CSMDoc::Message::Severity_Warning);
}
// Duplicate index
if (!questIndices.insert(journalInfo.mData.mJournalIndex).second)
{
CSMWorld::UniversalId id(CSMWorld::UniversalId::Type_JournalInfo, journalInfo.mId);
messages.add(id, "Duplicated quest index " + std::to_string(journalInfo.mData.mJournalIndex), "",
CSMDoc::Message::Severity_Error);
}
} }
} }

View file

@ -2,6 +2,7 @@
#define CSM_TOOLS_JOURNALCHECK_H #define CSM_TOOLS_JOURNALCHECK_H
#include "../world/idcollection.hpp" #include "../world/idcollection.hpp"
#include "../world/infocollection.hpp"
#include "../doc/stage.hpp" #include "../doc/stage.hpp"
@ -39,6 +40,7 @@ namespace CSMTools
const CSMWorld::IdCollection<ESM::Dialogue>& mJournals; const CSMWorld::IdCollection<ESM::Dialogue>& mJournals;
const CSMWorld::InfoCollection& mJournalInfos; const CSMWorld::InfoCollection& mJournalInfos;
bool mIgnoreBaseRecords; bool mIgnoreBaseRecords;
CSMWorld::InfosRecordPtrByTopic mInfosByTopic;
}; };
} }

View file

@ -82,11 +82,10 @@ void CSMWorld::InfoCollection::load(
} }
} }
std::vector<CSMWorld::Record<CSMWorld::Info>*> CSMWorld::InfoCollection::getTopicInfos(const ESM::RefId& topic) const CSMWorld::InfosRecordPtrByTopic CSMWorld::InfoCollection::getInfosByTopic() const
{ {
std::vector<CSMWorld::Record<CSMWorld::Info>*> result; InfosRecordPtrByTopic result;
for (const std::unique_ptr<Record<Info>>& record : getRecords()) for (const std::unique_ptr<Record<Info>>& record : getRecords())
if (record->mBase.mTopicId == topic) result[record->mBase.mTopicId].push_back(record.get());
result.push_back(record.get());
return result; return result;
} }

View file

@ -7,7 +7,6 @@
#include "collection.hpp" #include "collection.hpp"
#include "info.hpp" #include "info.hpp"
#include "record.hpp"
namespace ESM namespace ESM
{ {
@ -18,6 +17,7 @@ namespace ESM
namespace CSMWorld namespace CSMWorld
{ {
using InfosByTopic = std::unordered_map<ESM::RefId, std::vector<ESM::RefId>>; using InfosByTopic = std::unordered_map<ESM::RefId, std::vector<ESM::RefId>>;
using InfosRecordPtrByTopic = std::unordered_map<ESM::RefId, std::vector<const Record<Info>*>>;
class InfoCollection : public Collection<Info, IdAccessor<Info>> class InfoCollection : public Collection<Info, IdAccessor<Info>>
{ {
@ -27,7 +27,7 @@ namespace CSMWorld
public: public:
void load(ESM::ESMReader& reader, bool base, const ESM::Dialogue& dialogue, InfosByTopic& infosByTopic); void load(ESM::ESMReader& reader, bool base, const ESM::Dialogue& dialogue, InfosByTopic& infosByTopic);
std::vector<Record<Info>*> getTopicInfos(const ESM::RefId& topic) const; InfosRecordPtrByTopic getInfosByTopic() const;
}; };
} }