From 9784881921f3fb8d9459c6e776b71287a415c841 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mika=C3=ABl=20Capelle?= Date: Wed, 18 Nov 2020 19:23:00 +0100 Subject: [PATCH 01/11] Update following addition of IPluginGame::listSaves(). --- src/runner/gamefeatureswrappers.cpp | 23 +++++------------------ src/runner/gamefeatureswrappers.h | 4 +--- src/runner/proxypluginwrappers.cpp | 23 +++++++++++++++++------ src/runner/proxypluginwrappers.h | 3 +-- src/runner/pythonrunner.cpp | 15 +++++++++------ src/runner/uibasewrappers.h | 12 +++++++++--- 6 files changed, 42 insertions(+), 38 deletions(-) diff --git a/src/runner/gamefeatureswrappers.cpp b/src/runner/gamefeatureswrappers.cpp index 2bf4b47..af17ba8 100644 --- a/src/runner/gamefeatureswrappers.cpp +++ b/src/runner/gamefeatureswrappers.cpp @@ -126,15 +126,9 @@ std::vector ModDataContentWrapper::getContentsFor(std::shared_ptr(this, m_SaveGames[file], "getSaveGameInfo", file); -} - -SaveGameInfoWrapper::MissingAssets SaveGameInfoWrapper::getMissingAssets(QString const & file) const -{ - return basicWrapperFunctionImplementation(this, "getMissingAssets", file); + return basicWrapperFunctionImplementation(this, "getMissingAssets", boost::ref(save)); } MOBase::ISaveGameInfoWidget* SaveGameInfoWrapper::getSaveGameWidget(QWidget* parent) const @@ -142,10 +136,6 @@ MOBase::ISaveGameInfoWidget* SaveGameInfoWrapper::getSaveGameWidget(QWidget* par return basicWrapperFunctionImplementation(this, m_SaveGameWidget, "getSaveGameWidget", parent); } -bool SaveGameInfoWrapper::hasScriptExtenderSave(QString const & file) const -{ - return basicWrapperFunctionImplementation(this, "hasScriptExtenderSave", file); -} /// end SaveGameInfo Wrapper ///////////////////////////// /// ScriptExtender Wrapper @@ -316,7 +306,7 @@ void registerGameFeaturesPythonConverters() .def("getContentsFor", bpy::pure_virtual(&ModDataContent::getContentsFor), bpy::arg("filetree")) ; - bpy::class_("Content", + bpy::class_("Content", bpy::init>((bpy::arg("id"), "name", "icon", bpy::arg("filter_only") = false))) .add_property("id", &ModDataContent::Content::id) .add_property("name", &ModDataContent::Content::name) @@ -327,12 +317,9 @@ void registerGameFeaturesPythonConverters() } bpy::class_("SaveGameInfo") - .def("getSaveGameInfo", bpy::pure_virtual(&SaveGameInfo::getSaveGameInfo), bpy::return_value_policy(), - bpy::arg("filepath")) - .def("getMissingAssets", bpy::pure_virtual(&SaveGameInfo::getMissingAssets), bpy::arg("filepath")) - .def("getSaveGameWidget", bpy::pure_virtual(&SaveGameInfo::getSaveGameWidget), bpy::return_value_policy(), + .def("getMissingAssets", bpy::pure_virtual(&SaveGameInfo::getMissingAssets), bpy::arg("save")) + .def("getSaveGameWidget", bpy::pure_virtual(&SaveGameInfo::getSaveGameWidget), bpy::return_value_policy(), bpy::arg("parent"), "[optional]") - .def("hasScriptExtenderSave", bpy::pure_virtual(&SaveGameInfo::hasScriptExtenderSave), bpy::arg("filepath")) ; bpy::class_("ScriptExtender") diff --git a/src/runner/gamefeatureswrappers.h b/src/runner/gamefeatureswrappers.h index 606b560..3b1e99b 100644 --- a/src/runner/gamefeatureswrappers.h +++ b/src/runner/gamefeatureswrappers.h @@ -106,10 +106,8 @@ public: static constexpr const char* className = "SaveGameInfoWrapper"; using boost::python::wrapper::get_override; - virtual MOBase::ISaveGame const *getSaveGameInfo(QString const &file) const override; - virtual MissingAssets getMissingAssets(QString const &file) const override; + virtual MissingAssets getMissingAssets(MOBase::ISaveGame const& save) const override; virtual MOBase::ISaveGameInfoWidget *getSaveGameWidget(QWidget *parent = 0) const override; - virtual bool hasScriptExtenderSave(QString const &file) const override; private: // We need to keep the python objects alive: diff --git a/src/runner/proxypluginwrappers.cpp b/src/runner/proxypluginwrappers.cpp index 2a1efd3..f4a8657 100644 --- a/src/runner/proxypluginwrappers.cpp +++ b/src/runner/proxypluginwrappers.cpp @@ -5,6 +5,7 @@ #include #include "pythonwrapperutilities.h" +#include "uibasewrappers.h" #include #include @@ -147,14 +148,24 @@ void IPluginGameWrapper::initializeProfile(const QDir & directory, ProfileSettin basicWrapperFunctionImplementation(this, "initializeProfile", directory, settings); } -QString IPluginGameWrapper::savegameExtension() const +std::vector> IPluginGameWrapper::listSaves(QDir folder) const { - return basicWrapperFunctionImplementation(this, "savegameExtension"); -} + boost::python::object saves; + auto cppSaves = basicWrapperFunctionImplementation> >(this, saves, "listSaves", folder); -QString IPluginGameWrapper::savegameSEExtension() const -{ - return basicWrapperFunctionImplementation(this, "savegameSEExtension"); + { + GILock lock; + + for (std::size_t i = 0; i < cppSaves.size(); ++i) { + if (auto* p = dynamic_cast(cppSaves[i].get())) { + p->m_PySave = saves[i]; + } + } + + saves = {}; + } + + return cppSaves; } bool IPluginGameWrapper::isInstalled() const diff --git a/src/runner/proxypluginwrappers.h b/src/runner/proxypluginwrappers.h index 892314d..908c4e9 100644 --- a/src/runner/proxypluginwrappers.h +++ b/src/runner/proxypluginwrappers.h @@ -104,8 +104,7 @@ public: virtual void detectGame() override; virtual QString gameName() const override; virtual void initializeProfile(const QDir &directory, ProfileSettings settings) const override; - virtual QString savegameExtension() const override; - virtual QString savegameSEExtension() const override; + virtual std::vector> listSaves(QDir folder) const override; virtual bool isInstalled() const override; virtual QIcon gameIcon() const override; virtual QDir gameDirectory() const override; diff --git a/src/runner/pythonrunner.cpp b/src/runner/pythonrunner.cpp index d482124..9366843 100644 --- a/src/runner/pythonrunner.cpp +++ b/src/runner/pythonrunner.cpp @@ -92,6 +92,9 @@ BOOST_PYTHON_MODULE(mobase) bpy::register_ptr_to_python>(); bpy::register_ptr_to_python>(); bpy::implicitly_convertible, std::shared_ptr>(); + bpy::register_ptr_to_python>(); + bpy::register_ptr_to_python>(); + bpy::implicitly_convertible, std::shared_ptr>(); // Containers: utils::register_sequence_container>(); @@ -105,7 +108,8 @@ BOOST_PYTHON_MODULE(mobase) utils::register_sequence_container>(); utils::register_sequence_container>(); utils::register_sequence_container>(); // Required for QVariant since this is QVariantList. - utils::register_sequence_container>>(); + utils::register_sequence_container>>(); + utils::register_sequence_container>>(); utils::register_sequence_container>(); utils::register_sequence_container>(); @@ -250,12 +254,12 @@ BOOST_PYTHON_MODULE(mobase) .def("process", &ExecutableForcedLoadSetting::process) ; - bpy::class_, ISaveGameWrapper*, boost::noncopyable>("ISaveGame") - .def("getFilename", bpy::pure_virtual(&ISaveGame::getFilename)) + bpy::class_, std::shared_ptr, boost::noncopyable>("ISaveGame") + .def("getFilepath", bpy::pure_virtual(&ISaveGame::getFilepath)) .def("getCreationTime", bpy::pure_virtual(&ISaveGame::getCreationTime)) + .def("getName", bpy::pure_virtual(&ISaveGame::getName)) .def("getSaveGroupIdentifier", bpy::pure_virtual(&ISaveGame::getSaveGroupIdentifier)) .def("allFiles", bpy::pure_virtual(&ISaveGame::allFiles)) - .def("hasScriptExtenderFile", bpy::pure_virtual(&ISaveGame::hasScriptExtenderFile)) ; // See Q_DELEGATE for more details. @@ -917,8 +921,7 @@ BOOST_PYTHON_MODULE(mobase) .def("detectGame", bpy::pure_virtual(&MOBase::IPluginGame::detectGame)) .def("gameName", bpy::pure_virtual(&MOBase::IPluginGame::gameName)) .def("initializeProfile", bpy::pure_virtual(&MOBase::IPluginGame::initializeProfile), (bpy::arg("directory"), "settings")) - .def("savegameExtension", bpy::pure_virtual(&MOBase::IPluginGame::savegameExtension)) - .def("savegameSEExtension", bpy::pure_virtual(&MOBase::IPluginGame::savegameSEExtension)) + .def("listSaves", bpy::pure_virtual(&MOBase::IPluginGame::listSaves), bpy::arg("folder")) .def("isInstalled", bpy::pure_virtual(&MOBase::IPluginGame::isInstalled)) .def("gameIcon", bpy::pure_virtual(&MOBase::IPluginGame::gameIcon)) .def("gameDirectory", bpy::pure_virtual(&MOBase::IPluginGame::gameDirectory)) diff --git a/src/runner/uibasewrappers.h b/src/runner/uibasewrappers.h index 448575d..ace9f0f 100644 --- a/src/runner/uibasewrappers.h +++ b/src/runner/uibasewrappers.h @@ -41,11 +41,17 @@ public: static constexpr const char* className = "ISaveGameWrapper"; using boost::python::wrapper::get_override; - virtual QString getFilename() const override { return basicWrapperFunctionImplementation(this, "getFilename"); }; + virtual QString getFilepath() const override { return basicWrapperFunctionImplementation(this, "getFilepath"); }; virtual QDateTime getCreationTime() const override { return basicWrapperFunctionImplementation(this, "getCreationTime"); }; + virtual QString getName() const override { return basicWrapperFunctionImplementation(this, "getName"); }; virtual QString getSaveGroupIdentifier() const override { return basicWrapperFunctionImplementation(this, "getSaveGroupIdentifier"); }; virtual QStringList allFiles() const override { return basicWrapperFunctionImplementation(this, "allFiles"); }; - virtual bool hasScriptExtenderFile() const override { return basicWrapperFunctionImplementation(this, "hasScriptExtenderFile"); }; + +protected: + + friend class IPluginGameWrapper; + mutable boost::python::object m_PySave; + }; // This needs a wrapper but currently I have no idea how to expose this properly to python: @@ -58,7 +64,7 @@ public: // Bring the constructor: using ISaveGameInfoWidget::ISaveGameInfoWidget; - virtual void setSave(QString const& save) override { basicWrapperFunctionImplementation(this, "setSave", save); }; + virtual void setSave(MOBase::ISaveGame const& save) override { basicWrapperFunctionImplementation(this, "setSave", boost::ref(save)); }; }; #endif // UIBASEWRAPPERS_H From 6e088406c89f996bbc2bbf8fa22b846e2dc1963c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mika=C3=ABl=20Capelle?= Date: Wed, 18 Nov 2020 19:26:59 +0100 Subject: [PATCH 02/11] Update following removal of IPlugin::isActive(). --- src/pythonrunner_en.ts | 86 ------------------------------------------ 1 file changed, 86 deletions(-) diff --git a/src/pythonrunner_en.ts b/src/pythonrunner_en.ts index babd5b5..28e31af 100644 --- a/src/pythonrunner_en.ts +++ b/src/pythonrunner_en.ts @@ -1,92 +1,6 @@ - - ProxyPython - - - Python Initialization failed - - - - - On a previous start the Python Plugin failed to initialize. -Either the value in Settings->Plugins->ProxyPython->plugin_dir is set incorrectly or it is empty and auto-detection doesn't work for whatever reason. -Do you want to try initializing python again (at the risk of another crash)? -Suggestion: Select "no", and click the warning sign for further help. Afterwards you have to re-enable the python plugin. - - - - - Proxy Plugin to allow plugins written in python to be loaded - - - - - Python not installed or not found - - - - - Python version is incompatible - - - - - Invalid python path - - - - - Initializing Python failed - - - - - Python auto-detection failed - - - - - ModOrganizer path contains a semicolon - - - - - invalid problem key %1 - - - - - Some MO plugins require the python interpreter to be installed. These plugins will not even show up in settings-&gt;plugins.<br>If you want to use those plugins, please install the 32-bit version of Python 2.7.x from <a href="%1">%1</a>.<br>This is only required to use some extended functionality in MO, you do not need Python to play the game. - - - - - Your installed python version has a different version than 2.7. Some MO plugins may not work.<br>If you have multiple versions of python installed you may have to configure the path to 2.7 (32 bit) in the settings dialog.<br>This is only required to use some extended functionality in MO, you do not need Python to play the game. - - - - - Please set python_dir in Settings->Plugins->ProxyPython to the path of your python 2.7 (32 bit) installation. - - - - - The auto-detection of the python path failed. I don't know why this would happen but you can try to fix it by setting python_dir in Settings->Plugins->ProxyPython to the path of your python 2.7 (32 bit) installation. - - - - - Sorry, I don't know any details. Most likely your python installation is not supported. - - - - - The path to Mod Organizer (%1) contains a semicolon. <br>While this is legal on NTFS drives there is a lot of software that doesn't handle it correctly.<br>Unfortunately MO depends on libraries that seem to fall into that group.<br>As a result the python plugin can't be loaded.<br>The only solution I can offer is to remove the semicolon / move MO to a path without a semicolon. - - - QObject From 31e4f724ed9c2e9ddf056df50972cfe5bbb31f92 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mika=C3=ABl=20Capelle?= Date: Wed, 18 Nov 2020 20:31:59 +0100 Subject: [PATCH 03/11] Clean listSaves() bindings. --- src/runner/proxypluginwrappers.cpp | 19 +++---------------- 1 file changed, 3 insertions(+), 16 deletions(-) diff --git a/src/runner/proxypluginwrappers.cpp b/src/runner/proxypluginwrappers.cpp index f4a8657..f7f4f8f 100644 --- a/src/runner/proxypluginwrappers.cpp +++ b/src/runner/proxypluginwrappers.cpp @@ -150,22 +150,9 @@ void IPluginGameWrapper::initializeProfile(const QDir & directory, ProfileSettin std::vector> IPluginGameWrapper::listSaves(QDir folder) const { - boost::python::object saves; - auto cppSaves = basicWrapperFunctionImplementation> >(this, saves, "listSaves", folder); - - { - GILock lock; - - for (std::size_t i = 0; i < cppSaves.size(); ++i) { - if (auto* p = dynamic_cast(cppSaves[i].get())) { - p->m_PySave = saves[i]; - } - } - - saves = {}; - } - - return cppSaves; + // Why do I not need to hold python references here? Is it because those are wrapped + // in shared_ptr? + return basicWrapperFunctionImplementation>>(this, "listSaves", folder); } bool IPluginGameWrapper::isInstalled() const From 7dbc139b1060a9e70fb2483d77db6c932fa5c9fc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mika=C3=ABl=20Capelle?= Date: Thu, 19 Nov 2020 20:40:05 +0100 Subject: [PATCH 04/11] Fix crash when destructing Python save games due to the GIL lock. --- src/proxy/proxypython.cpp | 2 + src/runner/CMakeLists.txt | 26 ++++++++ src/runner/pythonrunner.cpp | 12 +++- src/runner/pythonrunner.h | 2 + src/runner/shared_ptr_converter.h | 99 +++++++++++++++++++++++++++++++ src/runner/uibasewrappers.h | 2 - 6 files changed, 140 insertions(+), 3 deletions(-) create mode 100644 src/runner/shared_ptr_converter.h diff --git a/src/proxy/proxypython.cpp b/src/proxy/proxypython.cpp index da98fa2..47691ed 100644 --- a/src/proxy/proxypython.cpp +++ b/src/proxy/proxypython.cpp @@ -104,6 +104,8 @@ ProxyPython::ProxyPython() ProxyPython::~ProxyPython() { + delete m_Runner; + if (!m_TempRunnerFile.isEmpty()) { ::FreeLibrary(m_RunnerLib); QFile(m_TempRunnerFile).remove(); diff --git a/src/runner/CMakeLists.txt b/src/runner/CMakeLists.txt index cac7f40..6c4a506 100644 --- a/src/runner/CMakeLists.txt +++ b/src/runner/CMakeLists.txt @@ -23,3 +23,29 @@ endif() requires_project(game_features) requires_library(python) + +add_filter(NAME src/converters GROUPS + converters + pythonutils + shared_ptr_converter + tuple_helper + variant_helper +) + +add_filter(NAME src/runner GROUPS + pythonrunner + pylogger +) + +add_filter(NAME src/utils GROUPS + error + gilock + sipapiaccess +) + +add_filter(NAME src/wrappers GROUPS + gamefeatureswrappers + proxypluginwrappers + pythonwrappersutilities + uibasewrappers +) diff --git a/src/runner/pythonrunner.cpp b/src/runner/pythonrunner.cpp index 9366843..feda706 100644 --- a/src/runner/pythonrunner.cpp +++ b/src/runner/pythonrunner.cpp @@ -39,6 +39,7 @@ #include "tuple_helper.h" #include "variant_helper.h" #include "converters.h" +#include "shared_ptr_converter.h" #include "pylogger.h" using namespace MOBase; @@ -86,6 +87,7 @@ BOOST_PYTHON_MODULE(mobase) utils::register_qflags_converter(); // Pointers: + utils::shared_ptr_from_python>(); bpy::register_ptr_to_python>(); bpy::register_ptr_to_python>(); bpy::implicitly_convertible, std::shared_ptr>(); @@ -254,7 +256,7 @@ BOOST_PYTHON_MODULE(mobase) .def("process", &ExecutableForcedLoadSetting::process) ; - bpy::class_, std::shared_ptr, boost::noncopyable>("ISaveGame") + bpy::class_, boost::noncopyable>("ISaveGame") .def("getFilepath", bpy::pure_virtual(&ISaveGame::getFilepath)) .def("getCreationTime", bpy::pure_virtual(&ISaveGame::getCreationTime)) .def("getName", bpy::pure_virtual(&ISaveGame::getName)) @@ -1101,6 +1103,8 @@ class PythonRunner : public IPythonRunner public: PythonRunner(); + ~PythonRunner(); + bool initPython(const QString& pythonDir); QList instantiate(const QString& pluginName); bool isPythonInstalled() const; @@ -1151,6 +1155,12 @@ PythonRunner::PythonRunner() m_PythonHome = new wchar_t[MAX_PATH + 1]; } +PythonRunner::~PythonRunner() { + // We need the GIL lock when destroying Python objects. + GILock lock; + m_PythonObjects.clear(); +} + static const char *argv0 = "ModOrganizer.exe"; struct PrintWrapper diff --git a/src/runner/pythonrunner.h b/src/runner/pythonrunner.h index 73de994..c494ae1 100644 --- a/src/runner/pythonrunner.h +++ b/src/runner/pythonrunner.h @@ -13,6 +13,8 @@ public: virtual QList instantiate(const QString &pluginName) = 0; virtual bool isPythonInstalled() const = 0; virtual bool isPythonVersionSupported() const = 0; + + virtual ~IPythonRunner() { } }; diff --git a/src/runner/shared_ptr_converter.h b/src/runner/shared_ptr_converter.h new file mode 100644 index 0000000..e49d683 --- /dev/null +++ b/src/runner/shared_ptr_converter.h @@ -0,0 +1,99 @@ +#ifndef PYTHONRUNNER_SHARED_PTR_CONVERTER_H +#define PYTHONRUNNER_SHARED_PTR_CONVERTER_H + +#include + +#include "error.h" +#include "gilock.h" + +namespace utils { + + // Shared pointers are handled in a special way by Boost.Python since they hold + // the wrapped Python object and only release it when the ref counter of the shared + // ptr drops to 0 using shared_ptr_deleter. + // + // Unfortunately for us, this will happen outside of the Python proxy for some objects + // and thus without the GIL lock, making everything crash, so we need a custom deleter + // that holds the GIL while releasing the lock. + // + // Note that this is only useful for Python -> C++ conversion, and without this, Boost + // will automatically wrapped the pointer. The C++ -> Python conversion is handled + // separately by boost::python::register_ptr_to_python. + + template + struct shared_ptr_from_python; + + namespace details { + + struct shared_ptr_deleter_with_gil_lock : boost::python::converter::shared_ptr_deleter { + + using shared_ptr_deleter::shared_ptr_deleter; + + void operator()(void const* o) { + GILock lock; + shared_ptr_deleter::operator()(o); + } + + }; + + template + struct shared_ptr_void; + + template + struct shared_ptr_void> { using type = std::shared_ptr; }; + + template + struct shared_ptr_void> { using type = boost::shared_ptr; }; + + template + using shared_ptr_void_t = typename shared_ptr_void::type; + + } + + template + struct shared_ptr_from_python + { + using T = typename SharedPtr::element_type; + + shared_ptr_from_python() + { + using namespace boost::python; + converter::registry::insert(&convertible, &construct, type_id() +#ifndef BOOST_PYTHON_NO_PY_SIGNATURES + , &converter::expected_from_python_type_direct::get_pytype +#endif + ); + } + + private: + static void* convertible(PyObject* p) + { + if (p == Py_None) + return p; + + return boost::python::converter::get_lvalue_from_python(p, boost::python::converter::registered::converters); + } + + static void construct(PyObject* source, boost::python::converter::rvalue_from_python_stage1_data* data) + { + using namespace boost::python; + void* const storage = ((converter::rvalue_from_python_storage*)data)->storage.bytes; + // Deal with the "None" case. + if (data->convertible == source) + new (storage) SharedPtr(); + else + { + details::shared_ptr_void_t hold_convertible_ref_count( + (void*)0, details::shared_ptr_deleter_with_gil_lock(handle<>(borrowed(source)))); + // use aliasing constructor + new (storage) SharedPtr(hold_convertible_ref_count, + static_cast(data->convertible)); + } + + data->convertible = storage; + } + }; + +} + +#endif \ No newline at end of file diff --git a/src/runner/uibasewrappers.h b/src/runner/uibasewrappers.h index ace9f0f..95e878a 100644 --- a/src/runner/uibasewrappers.h +++ b/src/runner/uibasewrappers.h @@ -50,8 +50,6 @@ public: protected: friend class IPluginGameWrapper; - mutable boost::python::object m_PySave; - }; // This needs a wrapper but currently I have no idea how to expose this properly to python: From 5b84588732c8b40a8655a9de70c8cff617408ca1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mika=C3=ABl=20Capelle?= Date: Thu, 19 Nov 2020 20:41:24 +0100 Subject: [PATCH 05/11] Add comment to reference Boost.Python issue. --- src/runner/shared_ptr_converter.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/runner/shared_ptr_converter.h b/src/runner/shared_ptr_converter.h index e49d683..8c3cf83 100644 --- a/src/runner/shared_ptr_converter.h +++ b/src/runner/shared_ptr_converter.h @@ -19,6 +19,8 @@ namespace utils { // Note that this is only useful for Python -> C++ conversion, and without this, Boost // will automatically wrapped the pointer. The C++ -> Python conversion is handled // separately by boost::python::register_ptr_to_python. + // + // This is an open Boost.Python problem: https://github.com/boostorg/python/pull/11 template struct shared_ptr_from_python; From 234586823ddd2d63ab8fbc44c51da1931e495acd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mika=C3=ABl=20Capelle?= Date: Thu, 19 Nov 2020 21:21:58 +0100 Subject: [PATCH 06/11] Return shared_ptr in IPlugin::requirements(). --- src/runner/CMakeLists.txt | 2 +- src/runner/proxypluginwrappers.cpp | 8 ++++---- src/runner/proxypluginwrappers.h | 6 ++---- src/runner/pythonrunner.cpp | 26 +++++++++++++------------- 4 files changed, 20 insertions(+), 22 deletions(-) diff --git a/src/runner/CMakeLists.txt b/src/runner/CMakeLists.txt index 6c4a506..6515a8e 100644 --- a/src/runner/CMakeLists.txt +++ b/src/runner/CMakeLists.txt @@ -46,6 +46,6 @@ add_filter(NAME src/utils GROUPS add_filter(NAME src/wrappers GROUPS gamefeatureswrappers proxypluginwrappers - pythonwrappersutilities + pythonwrapperutilities uibasewrappers ) diff --git a/src/runner/proxypluginwrappers.cpp b/src/runner/proxypluginwrappers.cpp index f7f4f8f..3d6e99e 100644 --- a/src/runner/proxypluginwrappers.cpp +++ b/src/runner/proxypluginwrappers.cpp @@ -72,11 +72,11 @@ QList class_name::settings() const \ QString class_name::localizedName_Default() const { return IPlugin::localizedName(); } \ QString class_name::master_Default() const { return IPlugin::master(); } \ BOOST_PP_EXPR_IF(include_requirements, \ - QList class_name::requirements() const { \ - return basicWrapperFunctionImplementationWithDefault>( \ - this, &class_name::requirements_Default, m_Requirements, "requirements"); \ + std::vector> class_name::requirements() const { \ + return basicWrapperFunctionImplementationWithDefault>>( \ + this, &class_name::requirements_Default, "requirements"); \ } \ - QList class_name::requirements_Default() const { return IPlugin::requirements(); }) + std::vector> class_name::requirements_Default() const { return IPlugin::requirements(); }) #define COMMON_I_PLUGIN_WRAPPER_DEFINITIONS(class_name) COMMON_I_PLUGIN_WRAPPER_DEFINITIONS_IMPL(class_name, 1) diff --git a/src/runner/proxypluginwrappers.h b/src/runner/proxypluginwrappers.h index 908c4e9..1d71f2e 100644 --- a/src/runner/proxypluginwrappers.h +++ b/src/runner/proxypluginwrappers.h @@ -19,8 +19,6 @@ // The wrapper for IPluginGame cannot override requirements() since it's final, // so we need to be able to exclude the declarations. #define COMMON_I_PLUGIN_WRAPPER_DECLARATIONS_IMPL(include_requirements) \ - BOOST_PP_EXPR_IF(include_requirements, \ - private: mutable boost::python::object m_Requirements; ) \ public: \ virtual bool init(MOBase::IOrganizer *moInfo) override; \ virtual QString name() const override; \ @@ -33,8 +31,8 @@ virtual QList settings() const override; \ QString localizedName_Default() const; \ QString master_Default() const; \ BOOST_PP_EXPR_IF(include_requirements, \ - virtual QList requirements() const override; \ - QList requirements_Default() const;) + virtual std::vector> requirements() const override; \ + std::vector> requirements_Default() const;) #define COMMON_I_PLUGIN_WRAPPER_DECLARATIONS COMMON_I_PLUGIN_WRAPPER_DECLARATIONS_IMPL(1) diff --git a/src/runner/pythonrunner.cpp b/src/runner/pythonrunner.cpp index feda706..07f6d89 100644 --- a/src/runner/pythonrunner.cpp +++ b/src/runner/pythonrunner.cpp @@ -87,16 +87,18 @@ BOOST_PYTHON_MODULE(mobase) utils::register_qflags_converter(); // Pointers: - utils::shared_ptr_from_python>(); bpy::register_ptr_to_python>(); bpy::register_ptr_to_python>(); bpy::implicitly_convertible, std::shared_ptr>(); bpy::register_ptr_to_python>(); bpy::register_ptr_to_python>(); bpy::implicitly_convertible, std::shared_ptr>(); - bpy::register_ptr_to_python>(); + + utils::shared_ptr_from_python>(); bpy::register_ptr_to_python>(); - bpy::implicitly_convertible, std::shared_ptr>(); + + utils::shared_ptr_from_python>(); + bpy::register_ptr_to_python>(); // Containers: utils::register_sequence_container>(); @@ -105,13 +107,13 @@ BOOST_PYTHON_MODULE(mobase) utils::register_sequence_container>(); utils::register_sequence_container>(); utils::register_sequence_container>(); - utils::register_sequence_container>(); utils::register_sequence_container(); utils::register_sequence_container>(); utils::register_sequence_container>(); utils::register_sequence_container>(); // Required for QVariant since this is QVariantList. utils::register_sequence_container>>(); utils::register_sequence_container>>(); + utils::register_sequence_container>>(); utils::register_sequence_container>(); utils::register_sequence_container>(); @@ -274,7 +276,7 @@ BOOST_PYTHON_MODULE(mobase) // Plugin requirements: auto iPluginRequirementClass = bpy::class_< - IPluginRequirementWrapper, bpy::bases<>, IPluginRequirementWrapper*, boost::noncopyable>("IPluginRequirement"); + IPluginRequirementWrapper, bpy::bases<>, boost::noncopyable>("IPluginRequirement"); { bpy::scope scope = iPluginRequirementClass; @@ -292,26 +294,24 @@ BOOST_PYTHON_MODULE(mobase) // pluginDependency .def("pluginDependency", +[](QStringList const& pluginNames) { return PluginRequirementFactory::pluginDependency(pluginNames); - }, bpy::return_value_policy(), bpy::arg("plugins")) + }, bpy::arg("plugins")) .def("pluginDependency", +[](QString const& pluginName) { return PluginRequirementFactory::pluginDependency(pluginName); - }, bpy::return_value_policy(), bpy::arg("plugin")) + }, bpy::arg("plugin")) .staticmethod("pluginDependency") // gameDependency .def("gameDependency", +[](QStringList const& gameNames) { return PluginRequirementFactory::gameDependency(gameNames); - }, bpy::return_value_policy(), bpy::arg("games")) + }, bpy::arg("games")) .def("gameDependency", +[](QString const& gameNames) { return PluginRequirementFactory::gameDependency(gameNames); - }, bpy::return_value_policy(), bpy::arg("game")) + }, bpy::arg("game")) .staticmethod("gameDependency") // diagnose - .def("diagnose", &PluginRequirementFactory::diagnose, - bpy::return_value_policy(), bpy::arg("diagnose")) + .def("diagnose", &PluginRequirementFactory::diagnose, bpy::arg("diagnose")) .staticmethod("diagnose") // basic - .def("basic", &PluginRequirementFactory::basic, - bpy::return_value_policy(), (bpy::arg("checker"), "description")) + .def("basic", &PluginRequirementFactory::basic, (bpy::arg("checker"), "description")) .staticmethod("basic"); bpy::class_("FileInfo", bpy::init<>()) From aacf3654d862ad84bb7a49be2aa35afe6f9cd8c5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mika=C3=ABl=20Capelle?= Date: Fri, 20 Nov 2020 21:42:07 +0100 Subject: [PATCH 07/11] Add default argument to GuessedString constructor to match C++ implementation. --- src/runner/pythonrunner.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/runner/pythonrunner.cpp b/src/runner/pythonrunner.cpp index 07f6d89..b5cf1d2 100644 --- a/src/runner/pythonrunner.cpp +++ b/src/runner/pythonrunner.cpp @@ -712,7 +712,7 @@ BOOST_PYTHON_MODULE(mobase) bpy::class_, boost::noncopyable>("GuessedString") .def(bpy::init<>()) - .def(bpy::init((bpy::arg("value"), "quality"))) + .def(bpy::init((bpy::arg("value"), bpy::arg("quality") = EGuessQuality::GUESS_USER))) .def("update", static_cast& (GuessedValue::*)(const QString&)>(&GuessedValue::update), bpy::return_self<>(), bpy::arg("value")) From ccef4be7418ef2a7584848fb8795caefcf21298b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mika=C3=ABl=20Capelle?= Date: Sat, 21 Nov 2020 12:25:43 +0100 Subject: [PATCH 08/11] Replace ScriptExtender saveGameAttachmentExtensions with savegameExtension. --- src/runner/gamefeatureswrappers.cpp | 6 +++--- src/runner/gamefeatureswrappers.h | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/runner/gamefeatureswrappers.cpp b/src/runner/gamefeatureswrappers.cpp index af17ba8..f83dd2a 100644 --- a/src/runner/gamefeatureswrappers.cpp +++ b/src/runner/gamefeatureswrappers.cpp @@ -160,9 +160,9 @@ QString ScriptExtenderWrapper::loaderPath() const return basicWrapperFunctionImplementation(this, "loaderPath"); } -QStringList ScriptExtenderWrapper::saveGameAttachmentExtensions() const +QString ScriptExtenderWrapper::savegameExtension() const { - return basicWrapperFunctionImplementation(this, "saveGameAttachmentExtensions"); + return basicWrapperFunctionImplementation(this, "savegameExtension"); } bool ScriptExtenderWrapper::isInstalled() const @@ -327,7 +327,7 @@ void registerGameFeaturesPythonConverters() .def("PluginPath", bpy::pure_virtual(&ScriptExtender::PluginPath)) .def("loaderName", bpy::pure_virtual(&ScriptExtender::loaderName)) .def("loaderPath", bpy::pure_virtual(&ScriptExtender::loaderPath)) - .def("saveGameAttachmentExtensions", bpy::pure_virtual(&ScriptExtender::saveGameAttachmentExtensions)) + .def("savegameExtension", bpy::pure_virtual(&ScriptExtender::savegameExtension)) .def("isInstalled", bpy::pure_virtual(&ScriptExtender::isInstalled)) .def("getExtenderVersion", bpy::pure_virtual(&ScriptExtender::getExtenderVersion)) .def("getArch", bpy::pure_virtual(&ScriptExtender::getArch)) diff --git a/src/runner/gamefeatureswrappers.h b/src/runner/gamefeatureswrappers.h index 3b1e99b..7155d8c 100644 --- a/src/runner/gamefeatureswrappers.h +++ b/src/runner/gamefeatureswrappers.h @@ -125,7 +125,7 @@ public: virtual QString PluginPath() const override; virtual QString loaderName() const override; virtual QString loaderPath() const override; - virtual QStringList saveGameAttachmentExtensions() const override; + virtual QString savegameExtension() const override; virtual bool isInstalled() const override; virtual QString getExtenderVersion() const override; virtual WORD getArch() const override; From a335adf9288179ba714b18888c0260a15973d445 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mika=C3=ABl=20Capelle?= Date: Sat, 21 Nov 2020 13:45:30 +0100 Subject: [PATCH 09/11] Add argument name to IPluginRequirement::check(). --- src/runner/pythonrunner.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/runner/pythonrunner.cpp b/src/runner/pythonrunner.cpp index b5cf1d2..e31ae20 100644 --- a/src/runner/pythonrunner.cpp +++ b/src/runner/pythonrunner.cpp @@ -286,7 +286,7 @@ BOOST_PYTHON_MODULE(mobase) .def("longDescription", &IPluginRequirement::Problem::longDescription); iPluginRequirementClass - .def("check", bpy::pure_virtual(&IPluginRequirement::check)) + .def("check", bpy::pure_virtual(&IPluginRequirement::check), bpy::arg("organizer")) ; } From 832aced4d3c12c53921e6ee3830064350f3e7e17 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mika=C3=ABl=20Capelle?= Date: Sat, 21 Nov 2020 14:11:36 +0100 Subject: [PATCH 10/11] Fix default binding for IPlugin::master(). --- src/runner/pythonrunner.cpp | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/runner/pythonrunner.cpp b/src/runner/pythonrunner.cpp index e31ae20..5ea14dd 100644 --- a/src/runner/pythonrunner.cpp +++ b/src/runner/pythonrunner.cpp @@ -846,7 +846,7 @@ BOOST_PYTHON_MODULE(mobase) .def("init", bpy::pure_virtual(&MOBase::IPlugin::init), bpy::arg("organizer")) .def("name", bpy::pure_virtual(&MOBase::IPlugin::name)) .def("localizedName", &MOBase::IPlugin::localizedName, &IPluginWrapper::localizedName_Default) - .def("master", &MOBase::IPlugin::master, &IPluginToolWrapper::master_Default) + .def("master", &MOBase::IPlugin::master, &IPluginWrapper::master_Default) .def("author", bpy::pure_virtual(&MOBase::IPlugin::author)) .def("description", bpy::pure_virtual(&MOBase::IPlugin::description)) .def("version", bpy::pure_virtual(&MOBase::IPlugin::version)) @@ -856,7 +856,7 @@ BOOST_PYTHON_MODULE(mobase) bpy::class_, boost::noncopyable>("IPluginDiagnose") .def("localizedName", &MOBase::IPlugin::localizedName, &IPluginDiagnoseWrapper::localizedName_Default) - .def("master", &MOBase::IPlugin::master, &IPluginToolWrapper::master_Default) + .def("master", &MOBase::IPlugin::master, &IPluginDiagnoseWrapper::master_Default) .def("requirements", &MOBase::IPlugin::requirements, &IPluginDiagnoseWrapper::requirements_Default) .def("activeProblems", bpy::pure_virtual(&MOBase::IPluginDiagnose::activeProblems)) @@ -883,7 +883,7 @@ BOOST_PYTHON_MODULE(mobase) bpy::class_, boost::noncopyable>("IPluginFileMapper") .def("localizedName", &MOBase::IPlugin::localizedName, &IPluginFileMapperWrapper::localizedName_Default) - .def("master", &MOBase::IPlugin::master, &IPluginToolWrapper::master_Default) + .def("master", &MOBase::IPlugin::master, &IPluginFileMapperWrapper::master_Default) .def("requirements", &MOBase::IPlugin::requirements, &IPluginFileMapperWrapper::requirements_Default) .def("mappings", bpy::pure_virtual(&MOBase::IPluginFileMapper::mappings)) ; @@ -918,7 +918,7 @@ BOOST_PYTHON_MODULE(mobase) bpy::class_, boost::noncopyable>("IPluginGame") .def("localizedName", &MOBase::IPlugin::localizedName, &IPluginGameWrapper::localizedName_Default) - .def("master", &MOBase::IPlugin::master, &IPluginToolWrapper::master_Default) + .def("master", &MOBase::IPlugin::master, &IPluginGameWrapper::master_Default) .def("detectGame", bpy::pure_virtual(&MOBase::IPluginGame::detectGame)) .def("gameName", bpy::pure_virtual(&MOBase::IPluginGame::gameName)) @@ -1015,7 +1015,7 @@ BOOST_PYTHON_MODULE(mobase) .def("onInstallationStart", &IPluginInstaller::onInstallationStart, (bpy::arg("archive"), bpy::arg("reinstallation"), bpy::arg("current_mod"))) .def("onInstallationEnd", &IPluginInstaller::onInstallationEnd, (bpy::arg("result"), bpy::arg("new_mod"))) .def("localizedName", &MOBase::IPlugin::localizedName, &IPluginInstallerSimpleWrapper::localizedName_Default) - .def("master", &MOBase::IPlugin::master, &IPluginToolWrapper::master_Default) + .def("master", &MOBase::IPlugin::master, &IPluginInstallerSimpleWrapper::master_Default) .def("requirements", &MOBase::IPlugin::requirements, &IPluginInstallerSimpleWrapper::requirements_Default) // Note: Keeping the variant here even if we always return a tuple to be consistent with the wrapper and @@ -1033,7 +1033,7 @@ BOOST_PYTHON_MODULE(mobase) .def("onInstallationStart", &IPluginInstaller::onInstallationStart, (bpy::arg("archive"), bpy::arg("reinstallation"), bpy::arg("current_mod"))) .def("onInstallationEnd", &IPluginInstaller::onInstallationEnd, (bpy::arg("result"), bpy::arg("new_mod"))) .def("localizedName", &MOBase::IPlugin::localizedName, &IPluginInstallerCustomWrapper::localizedName_Default) - .def("master", &MOBase::IPlugin::master, &IPluginToolWrapper::master_Default) + .def("master", &MOBase::IPlugin::master, &IPluginInstallerCustomWrapper::master_Default) .def("requirements", &MOBase::IPlugin::requirements, &IPluginInstallerCustomWrapper::requirements_Default) // Needs to add both otherwize boost does not understand: @@ -1047,7 +1047,7 @@ BOOST_PYTHON_MODULE(mobase) bpy::class_, boost::noncopyable>("IPluginModPage") .def("localizedName", &MOBase::IPlugin::localizedName, &IPluginModPageWrapper::localizedName_Default) - .def("master", &MOBase::IPlugin::master, &IPluginToolWrapper::master_Default) + .def("master", &MOBase::IPlugin::master, &IPluginModPageWrapper::master_Default) .def("requirements", &MOBase::IPlugin::requirements, &IPluginModPageWrapper::requirements_Default) .def("displayName", bpy::pure_virtual(&IPluginModPage::displayName)) @@ -1061,7 +1061,7 @@ BOOST_PYTHON_MODULE(mobase) bpy::class_, boost::noncopyable>("IPluginPreview") .def("localizedName", &MOBase::IPlugin::localizedName, &IPluginPreviewWrapper::localizedName_Default) - .def("master", &MOBase::IPlugin::master, &IPluginToolWrapper::master_Default) + .def("master", &MOBase::IPlugin::master, &IPluginPreviewWrapper::master_Default) .def("requirements", &MOBase::IPlugin::requirements, &IPluginPreviewWrapper::requirements_Default) .def("supportedExtensions", bpy::pure_virtual(&IPluginPreview::supportedExtensions)) From efc7d76a0ac83bd634dfdfb33c20916ee88152a8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mika=C3=ABl=20Capelle?= Date: Wed, 25 Nov 2020 22:47:10 +0100 Subject: [PATCH 11/11] Revert translation file. --- src/pythonrunner_en.ts | 86 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 86 insertions(+) diff --git a/src/pythonrunner_en.ts b/src/pythonrunner_en.ts index 28e31af..babd5b5 100644 --- a/src/pythonrunner_en.ts +++ b/src/pythonrunner_en.ts @@ -1,6 +1,92 @@ + + ProxyPython + + + Python Initialization failed + + + + + On a previous start the Python Plugin failed to initialize. +Either the value in Settings->Plugins->ProxyPython->plugin_dir is set incorrectly or it is empty and auto-detection doesn't work for whatever reason. +Do you want to try initializing python again (at the risk of another crash)? +Suggestion: Select "no", and click the warning sign for further help. Afterwards you have to re-enable the python plugin. + + + + + Proxy Plugin to allow plugins written in python to be loaded + + + + + Python not installed or not found + + + + + Python version is incompatible + + + + + Invalid python path + + + + + Initializing Python failed + + + + + Python auto-detection failed + + + + + ModOrganizer path contains a semicolon + + + + + invalid problem key %1 + + + + + Some MO plugins require the python interpreter to be installed. These plugins will not even show up in settings-&gt;plugins.<br>If you want to use those plugins, please install the 32-bit version of Python 2.7.x from <a href="%1">%1</a>.<br>This is only required to use some extended functionality in MO, you do not need Python to play the game. + + + + + Your installed python version has a different version than 2.7. Some MO plugins may not work.<br>If you have multiple versions of python installed you may have to configure the path to 2.7 (32 bit) in the settings dialog.<br>This is only required to use some extended functionality in MO, you do not need Python to play the game. + + + + + Please set python_dir in Settings->Plugins->ProxyPython to the path of your python 2.7 (32 bit) installation. + + + + + The auto-detection of the python path failed. I don't know why this would happen but you can try to fix it by setting python_dir in Settings->Plugins->ProxyPython to the path of your python 2.7 (32 bit) installation. + + + + + Sorry, I don't know any details. Most likely your python installation is not supported. + + + + + The path to Mod Organizer (%1) contains a semicolon. <br>While this is legal on NTFS drives there is a lot of software that doesn't handle it correctly.<br>Unfortunately MO depends on libraries that seem to fall into that group.<br>As a result the python plugin can't be loaded.<br>The only solution I can offer is to remove the semicolon / move MO to a path without a semicolon. + + + QObject