From c0dfad23b355dda2d2f530001868fb1eff98ba0e Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Sat, 14 Mar 2015 12:00:24 +0100 Subject: [PATCH] Fixed editor operation multi-threading (Fixes #923) --- apps/opencs/CMakeLists.txt | 2 +- apps/opencs/model/doc/document.cpp | 3 +- apps/opencs/model/doc/document.hpp | 4 +- apps/opencs/model/doc/operation.cpp | 25 +++++---- apps/opencs/model/doc/operation.hpp | 13 +++-- apps/opencs/model/doc/operationholder.cpp | 65 +++++++++++++++++++++++ apps/opencs/model/doc/operationholder.hpp | 56 +++++++++++++++++++ apps/opencs/model/doc/runner.cpp | 4 +- apps/opencs/model/doc/runner.hpp | 4 +- apps/opencs/model/tools/tools.cpp | 62 +++++++++++---------- apps/opencs/model/tools/tools.hpp | 11 ++-- 11 files changed, 195 insertions(+), 54 deletions(-) create mode 100644 apps/opencs/model/doc/operationholder.cpp create mode 100644 apps/opencs/model/doc/operationholder.hpp diff --git a/apps/opencs/CMakeLists.txt b/apps/opencs/CMakeLists.txt index 7eacfd7ef..e618d2961 100644 --- a/apps/opencs/CMakeLists.txt +++ b/apps/opencs/CMakeLists.txt @@ -5,7 +5,7 @@ set (OPENCS_SRC main.cpp opencs_units (. editor) opencs_units (model/doc - document operation saving documentmanager loader runner + document operation saving documentmanager loader runner operationholder ) opencs_units_noqt (model/doc diff --git a/apps/opencs/model/doc/document.cpp b/apps/opencs/model/doc/document.cpp index e688a9474..7d27143b4 100644 --- a/apps/opencs/model/doc/document.cpp +++ b/apps/opencs/model/doc/document.cpp @@ -2254,7 +2254,8 @@ CSMDoc::Document::Document (const Files::ConfigurationManager& configuration, mTools (*this), mResDir(resDir), mProjectPath ((configuration.getUserDataPath() / "projects") / (savePath.filename().string() + ".project")), - mSaving (*this, mProjectPath, encoding), + mSavingOperation (*this, mProjectPath, encoding), + mSaving (&mSavingOperation), mRunner (mProjectPath), mPhysics(boost::shared_ptr()) { if (mContentFiles.empty()) diff --git a/apps/opencs/model/doc/document.hpp b/apps/opencs/model/doc/document.hpp index f3aef6db6..a0905e5ad 100644 --- a/apps/opencs/model/doc/document.hpp +++ b/apps/opencs/model/doc/document.hpp @@ -20,6 +20,7 @@ #include "saving.hpp" #include "blacklist.hpp" #include "runner.hpp" +#include "operationholder.hpp" class QAbstractItemModel; @@ -59,7 +60,8 @@ namespace CSMDoc CSMWorld::Data mData; CSMTools::Tools mTools; boost::filesystem::path mProjectPath; - Saving mSaving; + Saving mSavingOperation; + OperationHolder mSaving; boost::filesystem::path mResDir; Blacklist mBlacklist; Runner mRunner; diff --git a/apps/opencs/model/doc/operation.cpp b/apps/opencs/model/doc/operation.cpp index e728050f4..3f1674f50 100644 --- a/apps/opencs/model/doc/operation.cpp +++ b/apps/opencs/model/doc/operation.cpp @@ -29,9 +29,9 @@ void CSMDoc::Operation::prepareStages() CSMDoc::Operation::Operation (int type, bool ordered, bool finalAlways) : mType (type), mStages(std::vector >()), mCurrentStage(mStages.begin()), mCurrentStep(0), mCurrentStepTotal(0), mTotalSteps(0), mOrdered (ordered), - mFinalAlways (finalAlways), mError(false) + mFinalAlways (finalAlways), mError(false), mConnected (false) { - connect (this, SIGNAL (finished()), this, SLOT (operationDone())); + mTimer = new QTimer (this); } CSMDoc::Operation::~Operation() @@ -42,15 +42,17 @@ CSMDoc::Operation::~Operation() void CSMDoc::Operation::run() { + mTimer->stop(); + + if (!mConnected) + { + connect (mTimer, SIGNAL (timeout()), this, SLOT (executeStage())); + mConnected = true; + } + prepareStages(); - QTimer timer; - - timer.connect (&timer, SIGNAL (timeout()), this, SLOT (executeStage())); - - timer.start (0); - - exec(); + mTimer->start (0); } void CSMDoc::Operation::appendStage (Stage *stage) @@ -65,7 +67,7 @@ bool CSMDoc::Operation::hasError() const void CSMDoc::Operation::abort() { - if (!isRunning()) + if (!mTimer->isActive()) return; mError = true; @@ -116,10 +118,11 @@ void CSMDoc::Operation::executeStage() emit reportMessage (iter->mId, iter->mMessage, iter->mHint, mType); if (mCurrentStage==mStages.end()) - exit(); + operationDone(); } void CSMDoc::Operation::operationDone() { + mTimer->stop(); emit done (mType, mError); } diff --git a/apps/opencs/model/doc/operation.hpp b/apps/opencs/model/doc/operation.hpp index 3c9467754..a6217fe2d 100644 --- a/apps/opencs/model/doc/operation.hpp +++ b/apps/opencs/model/doc/operation.hpp @@ -3,7 +3,8 @@ #include -#include +#include +#include namespace CSMWorld { @@ -14,7 +15,7 @@ namespace CSMDoc { class Stage; - class Operation : public QThread + class Operation : public QObject { Q_OBJECT @@ -27,6 +28,8 @@ namespace CSMDoc int mOrdered; bool mFinalAlways; bool mError; + bool mConnected; + QTimer *mTimer; void prepareStages(); @@ -38,8 +41,6 @@ namespace CSMDoc virtual ~Operation(); - virtual void run(); - void appendStage (Stage *stage); ///< The ownership of \a stage is transferred to *this. /// @@ -60,6 +61,8 @@ namespace CSMDoc void abort(); + void run(); + private slots: void executeStage(); @@ -68,4 +71,4 @@ namespace CSMDoc }; } -#endif \ No newline at end of file +#endif diff --git a/apps/opencs/model/doc/operationholder.cpp b/apps/opencs/model/doc/operationholder.cpp new file mode 100644 index 000000000..d79e14023 --- /dev/null +++ b/apps/opencs/model/doc/operationholder.cpp @@ -0,0 +1,65 @@ + +#include "operationholder.hpp" + +#include "operation.hpp" + +CSMDoc::OperationHolder::OperationHolder (Operation *operation) : mRunning (false) +{ + if (operation) + setOperation (operation); +} + +void CSMDoc::OperationHolder::setOperation (Operation *operation) +{ + mOperation = operation; + mOperation->moveToThread (&mThread); + + connect ( + mOperation, SIGNAL (progress (int, int, int)), + this, SIGNAL (progress (int, int, int))); + + connect ( + mOperation, SIGNAL (reportMessage (const CSMWorld::UniversalId&, const std::string&, const std::string&, int)), + this, SIGNAL (reportMessage (const CSMWorld::UniversalId&, const std::string&, const std::string&, int))); + + connect ( + mOperation, SIGNAL (done (int, bool)), + this, SLOT (doneSlot (int, bool))); + + connect (this, SIGNAL (abortSignal()), mOperation, SLOT (abort())); + + connect (&mThread, SIGNAL (started()), mOperation, SLOT (run())); +} + +bool CSMDoc::OperationHolder::isRunning() const +{ + return mRunning; +} + +void CSMDoc::OperationHolder::start() +{ + mRunning = true; + mThread.start(); +} + +void CSMDoc::OperationHolder::abort() +{ + mRunning = false; + emit abortSignal(); +} + +void CSMDoc::OperationHolder::abortAndWait() +{ + if (mRunning) + { + mThread.quit(); + mThread.wait(); + } +} + +void CSMDoc::OperationHolder::doneSlot (int type, bool failed) +{ + mRunning = false; + mThread.quit(); + emit done (type, failed); +} diff --git a/apps/opencs/model/doc/operationholder.hpp b/apps/opencs/model/doc/operationholder.hpp new file mode 100644 index 000000000..6fe6df053 --- /dev/null +++ b/apps/opencs/model/doc/operationholder.hpp @@ -0,0 +1,56 @@ +#ifndef CSM_DOC_OPERATIONHOLDER_H +#define CSM_DOC_OPERATIONHOLDER_H + +#include +#include + +namespace CSMWorld +{ + class UniversalId; +} + +namespace CSMDoc +{ + class Operation; + + class OperationHolder : public QObject + { + Q_OBJECT + + QThread mThread; + Operation *mOperation; + bool mRunning; + + public: + + OperationHolder (Operation *operation = 0); + + void setOperation (Operation *operation); + + bool isRunning() const; + + void start(); + + void abort(); + + // Abort and wait until thread has finished. + void abortAndWait(); + + private slots: + + void doneSlot (int type, bool failed); + + signals: + + void progress (int current, int max, int type); + + void reportMessage (const CSMWorld::UniversalId& id, const std::string& message, + const std::string& hint, int type); + + void done (int type, bool failed); + + void abortSignal(); + }; +} + +#endif diff --git a/apps/opencs/model/doc/runner.cpp b/apps/opencs/model/doc/runner.cpp index d679c1890..14fe0cda8 100644 --- a/apps/opencs/model/doc/runner.cpp +++ b/apps/opencs/model/doc/runner.cpp @@ -6,7 +6,7 @@ #include #include -#include "operation.hpp" +#include "operationholder.hpp" CSMDoc::Runner::Runner (const boost::filesystem::path& projectPath) : mRunning (false), mStartup (0), mProjectPath (projectPath) @@ -145,7 +145,7 @@ void CSMDoc::Runner::readyReadStandardOutput() } -CSMDoc::SaveWatcher::SaveWatcher (Runner *runner, Operation *operation) +CSMDoc::SaveWatcher::SaveWatcher (Runner *runner, OperationHolder *operation) : QObject (runner), mRunner (runner) { connect (operation, SIGNAL (done (int, bool)), this, SLOT (saveDone (int, bool))); diff --git a/apps/opencs/model/doc/runner.hpp b/apps/opencs/model/doc/runner.hpp index 38b52a73b..517122492 100644 --- a/apps/opencs/model/doc/runner.hpp +++ b/apps/opencs/model/doc/runner.hpp @@ -16,6 +16,8 @@ class QTemporaryFile; namespace CSMDoc { + class OperationHolder; + class Runner : public QObject { Q_OBJECT @@ -74,7 +76,7 @@ namespace CSMDoc public: /// *this attaches itself to runner - SaveWatcher (Runner *runner, Operation *operation); + SaveWatcher (Runner *runner, OperationHolder *operation); private slots: diff --git a/apps/opencs/model/tools/tools.cpp b/apps/opencs/model/tools/tools.cpp index 2139f890f..170ea8ccd 100644 --- a/apps/opencs/model/tools/tools.cpp +++ b/apps/opencs/model/tools/tools.cpp @@ -26,30 +26,30 @@ #include "referencecheck.hpp" #include "startscriptcheck.hpp" -CSMDoc::Operation *CSMTools::Tools::get (int type) +CSMDoc::OperationHolder *CSMTools::Tools::get (int type) { switch (type) { - case CSMDoc::State_Verifying: return mVerifier; + case CSMDoc::State_Verifying: return &mVerifier; } return 0; } -const CSMDoc::Operation *CSMTools::Tools::get (int type) const +const CSMDoc::OperationHolder *CSMTools::Tools::get (int type) const { return const_cast (this)->get (type); } -CSMDoc::Operation *CSMTools::Tools::getVerifier() +CSMDoc::OperationHolder *CSMTools::Tools::getVerifier() { - if (!mVerifier) + if (!mVerifierOperation) { - mVerifier = new CSMDoc::Operation (CSMDoc::State_Verifying, false); + mVerifierOperation = new CSMDoc::Operation (CSMDoc::State_Verifying, false); - connect (mVerifier, SIGNAL (progress (int, int, int)), this, SIGNAL (progress (int, int, int))); - connect (mVerifier, SIGNAL (done (int, bool)), this, SIGNAL (done (int, bool))); - connect (mVerifier, + connect (&mVerifier, SIGNAL (progress (int, int, int)), this, SIGNAL (progress (int, int, int))); + connect (&mVerifier, SIGNAL (done (int, bool)), this, SIGNAL (done (int, bool))); + connect (&mVerifier, SIGNAL (reportMessage (const CSMWorld::UniversalId&, const std::string&, const std::string&, int)), this, SLOT (verifierMessage (const CSMWorld::UniversalId&, const std::string&, const std::string&, int))); @@ -60,46 +60,48 @@ CSMDoc::Operation *CSMTools::Tools::getVerifier() mandatoryIds.push_back ("Month"); mandatoryIds.push_back ("PCRace"); - mVerifier->appendStage (new MandatoryIdStage (mData.getGlobals(), + mVerifierOperation->appendStage (new MandatoryIdStage (mData.getGlobals(), CSMWorld::UniversalId (CSMWorld::UniversalId::Type_Globals), mandatoryIds)); - mVerifier->appendStage (new SkillCheckStage (mData.getSkills())); + mVerifierOperation->appendStage (new SkillCheckStage (mData.getSkills())); - mVerifier->appendStage (new ClassCheckStage (mData.getClasses())); + mVerifierOperation->appendStage (new ClassCheckStage (mData.getClasses())); - mVerifier->appendStage (new FactionCheckStage (mData.getFactions())); + mVerifierOperation->appendStage (new FactionCheckStage (mData.getFactions())); - mVerifier->appendStage (new RaceCheckStage (mData.getRaces())); + mVerifierOperation->appendStage (new RaceCheckStage (mData.getRaces())); - mVerifier->appendStage (new SoundCheckStage (mData.getSounds())); + mVerifierOperation->appendStage (new SoundCheckStage (mData.getSounds())); - mVerifier->appendStage (new RegionCheckStage (mData.getRegions())); + mVerifierOperation->appendStage (new RegionCheckStage (mData.getRegions())); - mVerifier->appendStage (new BirthsignCheckStage (mData.getBirthsigns())); + mVerifierOperation->appendStage (new BirthsignCheckStage (mData.getBirthsigns())); - mVerifier->appendStage (new SpellCheckStage (mData.getSpells())); + mVerifierOperation->appendStage (new SpellCheckStage (mData.getSpells())); - mVerifier->appendStage (new ReferenceableCheckStage (mData.getReferenceables().getDataSet(), mData.getRaces(), mData.getClasses(), mData.getFactions())); + mVerifierOperation->appendStage (new ReferenceableCheckStage (mData.getReferenceables().getDataSet(), mData.getRaces(), mData.getClasses(), mData.getFactions())); - mVerifier->appendStage (new ReferenceCheckStage(mData.getReferences(), mData.getReferenceables(), mData.getCells(), mData.getFactions())); + mVerifierOperation->appendStage (new ReferenceCheckStage(mData.getReferences(), mData.getReferenceables(), mData.getCells(), mData.getFactions())); - mVerifier->appendStage (new ScriptCheckStage (mDocument)); + mVerifierOperation->appendStage (new ScriptCheckStage (mDocument)); - mVerifier->appendStage (new StartScriptCheckStage (mData.getStartScripts(), mData.getScripts())); + mVerifierOperation->appendStage (new StartScriptCheckStage (mData.getStartScripts(), mData.getScripts())); - mVerifier->appendStage( + mVerifierOperation->appendStage( new BodyPartCheckStage( mData.getBodyParts(), mData.getResources( CSMWorld::UniversalId( CSMWorld::UniversalId::Type_Meshes )), mData.getRaces() )); + + mVerifier.setOperation (mVerifierOperation); } - return mVerifier; + return &mVerifier; } CSMTools::Tools::Tools (CSMDoc::Document& document) -: mDocument (document), mData (document.getData()), mVerifier (0), mNextReportNumber (0) +: mDocument (document), mData (document.getData()), mVerifierOperation (0), mNextReportNumber (0) { // index 0: load error log mReports.insert (std::make_pair (mNextReportNumber++, new ReportModel)); @@ -108,7 +110,11 @@ CSMTools::Tools::Tools (CSMDoc::Document& document) CSMTools::Tools::~Tools() { - delete mVerifier; + if (mVerifierOperation) + { + mVerifier.abortAndWait(); + delete mVerifierOperation; + } for (std::map::iterator iter (mReports.begin()); iter!=mReports.end(); ++iter) delete iter->second; @@ -126,7 +132,7 @@ CSMWorld::UniversalId CSMTools::Tools::runVerifier() void CSMTools::Tools::abortOperation (int type) { - if (CSMDoc::Operation *operation = get (type)) + if (CSMDoc::OperationHolder *operation = get (type)) operation->abort(); } @@ -141,7 +147,7 @@ int CSMTools::Tools::getRunningOperations() const int result = 0; for (int i=0; sOperations[i]!=-1; ++i) - if (const CSMDoc::Operation *operation = get (sOperations[i])) + if (const CSMDoc::OperationHolder *operation = get (sOperations[i])) if (operation->isRunning()) result |= sOperations[i]; diff --git a/apps/opencs/model/tools/tools.hpp b/apps/opencs/model/tools/tools.hpp index 5125a3638..b8ded8a83 100644 --- a/apps/opencs/model/tools/tools.hpp +++ b/apps/opencs/model/tools/tools.hpp @@ -5,6 +5,8 @@ #include +#include "../doc/operationholder.hpp" + namespace CSMWorld { class Data; @@ -27,7 +29,8 @@ namespace CSMTools CSMDoc::Document& mDocument; CSMWorld::Data& mData; - CSMDoc::Operation *mVerifier; + CSMDoc::Operation *mVerifierOperation; + CSMDoc::OperationHolder mVerifier; std::map mReports; int mNextReportNumber; std::map mActiveReports; // type, report number @@ -36,12 +39,12 @@ namespace CSMTools Tools (const Tools&); Tools& operator= (const Tools&); - CSMDoc::Operation *getVerifier(); + CSMDoc::OperationHolder *getVerifier(); - CSMDoc::Operation *get (int type); + CSMDoc::OperationHolder *get (int type); ///< Returns a 0-pointer, if operation hasn't been used yet. - const CSMDoc::Operation *get (int type) const; + const CSMDoc::OperationHolder *get (int type) const; ///< Returns a 0-pointer, if operation hasn't been used yet. public: