From 957b786d8c4df5e48e5eb6db18360ef532e85369 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mika=C3=ABl=20Capelle?= Date: Sat, 9 May 2020 18:19:31 +0200 Subject: [PATCH 01/35] Start cleaning interfaces. --- src/runner/gamefeatureswrappers.cpp | 2 +- src/runner/pythonrunner.cpp | 106 ++++++++++++++++++---------- 2 files changed, 70 insertions(+), 38 deletions(-) diff --git a/src/runner/gamefeatureswrappers.cpp b/src/runner/gamefeatureswrappers.cpp index 5642e56..2d24352 100644 --- a/src/runner/gamefeatureswrappers.cpp +++ b/src/runner/gamefeatureswrappers.cpp @@ -277,7 +277,7 @@ void registerGameFeaturesPythonConverters() bpy::class_("SaveGameInfo") .def("getSaveGameInfo", bpy::pure_virtual(&SaveGameInfo::getSaveGameInfo), bpy::return_value_policy()) .def("getMissingAssets", bpy::pure_virtual(&SaveGameInfo::getMissingAssets)) - .def("getSaveGameWidget", bpy::pure_virtual(&SaveGameInfo::getSaveGameWidget), bpy::return_value_policy()) + .def("getSaveGameWidget", bpy::pure_virtual(&SaveGameInfo::getSaveGameWidget), bpy::return_value_policy(), "[optional]") .def("hasScriptExtenderSave", bpy::pure_virtual(&SaveGameInfo::hasScriptExtenderSave)) ; diff --git a/src/runner/pythonrunner.cpp b/src/runner/pythonrunner.cpp index 0d9b6cc..60297b4 100644 --- a/src/runner/pythonrunner.cpp +++ b/src/runner/pythonrunner.cpp @@ -725,6 +725,7 @@ BOOST_PYTHON_MODULE(mobase) .def("displayString", &VersionInfo::displayString) .def("isValid", &VersionInfo::isValid) .def("scheme", &VersionInfo::scheme) + .def("__str__", &VersionInfo::canonicalString) .def(bpy::self < bpy::self) .def(bpy::self > bpy::self) .def(bpy::self <= bpy::self) @@ -830,7 +831,7 @@ BOOST_PYTHON_MODULE(mobase) .def("name", &FileTreeEntry::name) .def("suffix", &FileTreeEntry::suffix) .def("time", &FileTreeEntry::time) - .def("parent", static_cast(FileTreeEntry::*)()>(&FileTreeEntry::parent)) + .def("parent", static_cast(FileTreeEntry::*)()>(&FileTreeEntry::parent), "[optional]") .def("path", &FileTreeEntry::path, bpy::arg("sep") = "\\") .def("pathFrom", &FileTreeEntry::pathFrom, bpy::arg("sep") = "\\") @@ -853,7 +854,7 @@ BOOST_PYTHON_MODULE(mobase) } // IFileTree scope: - auto iFileTreeClass = bpy::class_, boost::noncopyable>("IFileTree", bpy::no_init);; + auto iFileTreeClass = bpy::class_, boost::noncopyable>("IFileTree", bpy::no_init); { bpy::scope scope = iFileTreeClass; @@ -871,15 +872,15 @@ BOOST_PYTHON_MODULE(mobase) // special python methods): .def("exists", static_cast(&IFileTree::exists), (bpy::arg("type") = IFileTree::FILE_OR_DIRECTORY)) .def("find", static_cast(IFileTree::*)(QString, IFileTree::FileTypes)>(&IFileTree::find), - bpy::arg("type") = IFileTree::FILE_OR_DIRECTORY, bpy::return_value_policy>()) + bpy::arg("type") = IFileTree::FILE_OR_DIRECTORY, bpy::return_value_policy>(), "[optional]") .def("pathTo", &IFileTree::pathTo, bpy::arg("sep") = "\\") // Kind-of-static operations: .def("createOrphanTree", &IFileTree::createOrphanTree, bpy::arg("name") = "") // Mutable operations: - .def("addFile", &IFileTree::addFile, bpy::arg("time") = QDateTime()) - .def("addDirectory", &IFileTree::addDirectory) + .def("addFile", &IFileTree::addFile, bpy::arg("time") = QDateTime(), "[optional]") + .def("addDirectory", &IFileTree::addDirectory, "[optional]") .def("insert", +[]( IFileTree* p, std::shared_ptr entry, IFileTree::InsertPolicy insertPolicy) { return p->insert(entry, insertPolicy) != p->end(); }, bpy::arg("policy") = IFileTree::InsertPolicy::FAIL_IF_EXISTS) @@ -950,7 +951,7 @@ BOOST_PYTHON_MODULE(mobase) bpy::class_("ModRepositoryFileInfo") .def(bpy::init()) .def(bpy::init>()) - .def("toString", &ModRepositoryFileInfo::toString) + .def("__str__", &ModRepositoryFileInfo::toString) .def("createFromJson", &ModRepositoryFileInfo::createFromJson).staticmethod("createFromJson") .def_readwrite("name", &ModRepositoryFileInfo::name) .def_readwrite("uri", &ModRepositoryFileInfo::uri) @@ -1092,9 +1093,17 @@ BOOST_PYTHON_MODULE(mobase) .def("onModMoved", bpy::pure_virtual(&MOBase::IModList::onModMoved)) ; - bpy::class_("IPlugin"); + bpy::class_("IPlugin") + .def("init", bpy::pure_virtual(&MOBase::IPlugin::init)) + .def("name", bpy::pure_virtual(&MOBase::IPlugin::name)) + .def("author", bpy::pure_virtual(&MOBase::IPlugin::author)) + .def("description", bpy::pure_virtual(&MOBase::IPlugin::description)) + .def("version", bpy::pure_virtual(&MOBase::IPlugin::version)) + .def("isActive", bpy::pure_virtual(&MOBase::IPlugin::isActive)) + .def("settings", bpy::pure_virtual(&MOBase::IPlugin::settings)) + ; - bpy::class_("IPluginDiagnose") + bpy::class_, boost::noncopyable>("IPluginDiagnose") .def("activeProblems", bpy::pure_virtual(&MOBase::IPluginDiagnose::activeProblems)) .def("shortDescription", bpy::pure_virtual(&MOBase::IPluginDiagnose::shortDescription)) .def("fullDescription", bpy::pure_virtual(&MOBase::IPluginDiagnose::fullDescription)) @@ -1110,7 +1119,7 @@ BOOST_PYTHON_MODULE(mobase) .def_readwrite("createTarget", &Mapping::createTarget) ; - bpy::class_("IPluginFileMapper") + bpy::class_, boost::noncopyable>("IPluginFileMapper") .def("mappings", bpy::pure_virtual(&MOBase::IPluginFileMapper::mappings)) ; @@ -1137,7 +1146,7 @@ BOOST_PYTHON_MODULE(mobase) bpy::to_python_converter>(); QFlags_from_python_obj(); - bpy::class_("IPluginGame") + bpy::class_, boost::noncopyable>("IPluginGame") .def("gameName", bpy::pure_virtual(&MOBase::IPluginGame::gameName)) .def("initializeProfile", bpy::pure_virtual(&MOBase::IPluginGame::initializeProfile)) .def("savegameExtension", bpy::pure_virtual(&MOBase::IPluginGame::savegameExtension)) @@ -1170,23 +1179,36 @@ BOOST_PYTHON_MODULE(mobase) .def("gameVersion", bpy::pure_virtual(&MOBase::IPluginGame::gameVersion)) .def("getLauncherName", bpy::pure_virtual(&MOBase::IPluginGame::getLauncherName)) - //Plugin interface. - .def("init", bpy::pure_virtual(&MOBase::IPluginGame::init)) - .def("name", bpy::pure_virtual(&MOBase::IPluginGame::name)) - .def("author", bpy::pure_virtual(&MOBase::IPluginGame::author)) - .def("description", bpy::pure_virtual(&MOBase::IPluginGame::description)) - .def("version", bpy::pure_virtual(&MOBase::IPluginGame::version)) - .def("isActive", bpy::pure_virtual(&MOBase::IPluginGame::isActive)) - .def("settings", bpy::pure_virtual(&MOBase::IPluginGame::settings)) + .def("featureList", +[](MOBase::IPluginGame* p) { + // Constructing a dict from class name to actual object: + bpy::dict dict; + mp11::mp_for_each< + mp11::mp_transform< + // Must user pointers because mp_for_each construct object: + std::add_pointer_t, + mp11::mp_list< + BSAInvalidation, + DataArchives, + GamePlugins, + LocalSavegames, + SaveGameInfo, + ScriptExtender, + UnmanagedMods + > + > + >([&](auto* pt) { + using T = std::remove_pointer_t; + typename bpy::reference_existing_object::apply::type converter; - // The syntax has to differ slightly from C++ because these are templated - .def("featureBSAInvalidation", &MOBase::IPluginGame::feature, bpy::return_value_policy()) - .def("featureDataArchives", &MOBase::IPluginGame::feature, bpy::return_value_policy()) - .def("featureGamePlugins", &MOBase::IPluginGame::feature, bpy::return_value_policy()) - .def("featureLocalSavegames", &MOBase::IPluginGame::feature, bpy::return_value_policy()) - .def("featureSaveGameInfo", &MOBase::IPluginGame::feature, bpy::return_value_policy()) - .def("featureScriptExtender", &MOBase::IPluginGame::feature, bpy::return_value_policy()) - .def("featureUnmanagedMods", &MOBase::IPluginGame::feature, bpy::return_value_policy()) + // Retrieve the python class object: + const bpy::converter::registration* registration = bpy::converter::registry::query(bpy::type_id()); + bpy::object key = bpy::object(bpy::handle<>(bpy::borrowed(registration->get_class_object()))); + + // Set the object: + dict[key] = bpy::handle<>(converter(p->feature())); + }); + return dict; + }) ; bpy::enum_("InstallResult") @@ -1197,8 +1219,16 @@ BOOST_PYTHON_MODULE(mobase) .value("NOT_ATTEMPTED", MOBase::IPluginInstaller::RESULT_NOTATTEMPTED) ; - bpy::class_("IPluginInstallerSimple") - // Note: Keeping the variant here if we always return a tuple to be consistent with the wrapper and + bpy::class_, boost::noncopyable>("IPluginInstaller", bpy::no_init) + .def("isArchiveSupported", &IPluginInstaller::isArchiveSupported) + .def("priority", &IPluginInstaller::priority) + .def("isManualInstaller", &IPluginInstaller::isManualInstaller) + .def("setParentWidget", &IPluginInstaller::setParentWidget) + .def("setInstallationManager", &IPluginInstaller::setInstallationManager) + ; + + bpy::class_, boost::noncopyable>("IPluginInstallerSimple") + // Note: Keeping the variant here even if we always return a tuple to be consistent with the wrapper and // have proper stubs generation. .def("install", +[](IPluginInstallerSimple* p, GuessedValue& modName, std::shared_ptr& tree, QString& version, int& nexusID) -> std::variant, std::tuple, QString, int>> { @@ -1209,19 +1239,21 @@ BOOST_PYTHON_MODULE(mobase) .def("manager", &IPluginInstallerSimpleWrapper::manager, bpy::return_value_policy()) ; - bpy::class_("IPluginInstallerCustom") - .def("isArchiveSupported", &IPluginInstallerCustom::isArchiveSupported) - .def("supportedExtensions", &IPluginInstallerCustom::supportedExtensions) - .def("install", &IPluginInstallerCustom::install) - .def("parentWidget", &IPluginInstallerSimpleWrapper::parentWidget, bpy::return_value_policy()) - .def("manager", &IPluginInstallerCustomWrapper::manager, bpy::return_value_policy()) - ; + bpy::class_, boost::noncopyable>("IPluginInstallerCustom") + // Needs to add both otherwize boost does not understanda: + .def("isArchiveSupported", &IPluginInstaller::isArchiveSupported) + .def("isArchiveSupported", &IPluginInstallerCustom::isArchiveSupported) + .def("supportedExtensions", &IPluginInstallerCustom::supportedExtensions) + .def("install", &IPluginInstallerCustom::install) + .def("parentWidget", &IPluginInstallerSimpleWrapper::parentWidget, bpy::return_value_policy()) + .def("manager", &IPluginInstallerCustomWrapper::manager, bpy::return_value_policy()) + ; - bpy::class_("IPluginModPage") + bpy::class_, boost::noncopyable>("IPluginModPage") .def("setParentWidget", bpy::pure_virtual(&MOBase::IPluginModPage::setParentWidget)) ; - bpy::class_("IPluginPreview") + bpy::class_, boost::noncopyable>("IPluginPreview") ; bpy::class_, boost::noncopyable>("IPluginTool") From 62f10c03ff867e9c7fe645b4adcdba00c3624e1e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mika=C3=ABl=20Capelle?= Date: Sat, 9 May 2020 21:13:45 +0200 Subject: [PATCH 02/35] Temporary commit. --- src/runner/error.cpp | 17 --- src/runner/error.h | 110 +++++++++++++++- src/runner/proxypluginwrappers.cpp | 17 ++- src/runner/proxypluginwrappers.h | 14 ++ src/runner/pythonrunner.cpp | 142 +++++++++++---------- src/runner/pythonutils.h | 7 +- src/runner/pythonwrapperutilities.h | 69 +++++++--- src/runner/uibasewrappers.h | 191 +--------------------------- 8 files changed, 271 insertions(+), 296 deletions(-) diff --git a/src/runner/error.cpp b/src/runner/error.cpp index e17cd14..7ef6700 100644 --- a/src/runner/error.cpp +++ b/src/runner/error.cpp @@ -8,23 +8,6 @@ using namespace MOBase; namespace bpy = boost::python; -void reportPythonError() -{ - if (PyErr_Occurred()) { - ErrWrapper &errWrapper = ErrWrapper::instance(); - - errWrapper.startRecordingExceptionMessage(); - PyErr_Print(); - errWrapper.stopRecordingExceptionMessage(); - - QString errMsg = errWrapper.getLastExceptionMessage(); - - throw MyException(errMsg); - } else { - throw MyException("An unexpected C++ exception was thrown in python code"); - } -} - ErrWrapper & ErrWrapper::instance() { static ErrWrapper err; diff --git a/src/runner/error.h b/src/runner/error.h index 2cadb57..d4f8b10 100644 --- a/src/runner/error.h +++ b/src/runner/error.h @@ -1,16 +1,17 @@ #ifndef ERROR_H #define ERROR_H -#include -#include -// turn an error from the python interpreter into an exception -void reportPythonError(); +#include + +#include + +#include struct ErrWrapper { - static ErrWrapper & instance(); - - void write(const char * message); + static ErrWrapper& instance(); + + void write(const char* message); void startRecordingExceptionMessage(); @@ -23,4 +24,99 @@ struct ErrWrapper std::stringstream lastException; }; +namespace pyexcept { + + /** + * @brief Exception to throw when a python implementation does not implement + * a pure virtual function. + */ + class MissingImplementation : public MOBase::MyException { + public: + MissingImplementation(std::string const& className, std::string const& methodName) : + MyException(QString::fromStdString( + fmt::format("Python class implementing \"{}\" has no implementation of method \"{}\".", + className, methodName))) { } + + }; + + /** + * @brief Exception to throw when a python error occurs. + */ + class PythonError : public MOBase::MyException { + public: + + /** + * @brief Create a new PythonError, fetching the error message from python. If the message + * cannot be retrieved, `defaultErrorMessage()` is used instead. + */ + PythonError() : MyException(getPythonErrorMessage()) { } + + /** + * @brief Create a new PythonError with the given message. + * + * @param message Message for the exception. + */ + PythonError(QString message) : MyException(message) { } + + protected: + + /** + * + */ + static QString defaultErrorMessage() { + return QObject::tr("An unexpected C++ exception was thrown in python code."); + } + + /** + * + */ + static QString getPythonErrorMessage() { + if (PyErr_Occurred()) { + ErrWrapper& errWrapper = ErrWrapper::instance(); + + errWrapper.startRecordingExceptionMessage(); + PyErr_Print(); + errWrapper.stopRecordingExceptionMessage(); + + return errWrapper.getLastExceptionMessage(); + } + else { + return defaultErrorMessage(); + } + } + }; + + /** + * @brief Exception to throw when an unknown error occured. This is typically thrown + * from a catch(...) block. + */ + class UnknownException : public MOBase::MyException { + public: + + /** + * @brief Create a new UnknownException with the default message. + * + * @see defaultErrorMessage + */ + UnknownException() : MyException(defaultErrorMessage()) { } + + /** + * @brief Create a new UnknownException with the given message. + * + * @param message Message for the exception. + */ + UnknownException(QString message) : MyException(message) { } + + protected: + + /** + * + */ + static QString defaultErrorMessage() { + return QObject::tr("An unknown exception was thrown in python code."); + } + }; + +} + #endif // ERROR_H diff --git a/src/runner/proxypluginwrappers.cpp b/src/runner/proxypluginwrappers.cpp index bbcee99..5f7b18d 100644 --- a/src/runner/proxypluginwrappers.cpp +++ b/src/runner/proxypluginwrappers.cpp @@ -393,7 +393,7 @@ bool IPluginModPageWrapper::handlesDownload(const QUrl & pageURL, const QUrl & d void IPluginModPageWrapper::setParentWidget(QWidget * widget) { - basicWrapperFunctionImplementation(this, "setParentWidget", widget); + basicWrapperFunctionImplementationWithDefault(this, &IPluginModPageWrapper::setParentWidget_Default, "setParentWidget", widget); } /// end IPluginModPage Wrapper ///////////////////////////// @@ -414,12 +414,21 @@ QWidget *IPluginPreviewWrapper::genFilePreview(const QString &fileName, const QS GILock lock; boost::python::override implementation = this->get_override("genFilePreview"); if (!implementation) - throw MissingImplementation(this->className, "genFilePreview"); + throw pyexcept::MissingImplementation(this->className, "genFilePreview"); boost::python::object pyVersion = implementation(fileName, maxSize); // We need responsibility for deleting the QWidget to be transferred to C++ sipAPIAccess::sipAPI()->api_transfer_to(pyVersion.ptr(), Py_None); return boost::python::extract(pyVersion)(); - } PYCATCH; + } + catch (const boost::python::error_already_set&) { + throw pyexcept::PythonError(); + } + catch (pyexcept::MissingImplementation const& missingImplementation) { + throw missingImplementation; + } + catch (...) { + throw pyexcept::UnknownException(); + } } /// end IPluginPreview Wrapper ///////////////////////////// @@ -445,7 +454,7 @@ QIcon IPluginToolWrapper::icon() const void IPluginToolWrapper::setParentWidget(QWidget *parent) { - basicWrapperFunctionImplementation(this, "setParentWidget", parent); + basicWrapperFunctionImplementationWithDefault(this, &IPluginToolWrapper::setParentWidget_Default, "setParentWidget", parent); } void IPluginToolWrapper::display() const diff --git a/src/runner/proxypluginwrappers.h b/src/runner/proxypluginwrappers.h index c24d4ad..f2ca83f 100644 --- a/src/runner/proxypluginwrappers.h +++ b/src/runner/proxypluginwrappers.h @@ -186,12 +186,19 @@ public: static constexpr const char* className = "IPluginModPageWrapper"; using boost::python::wrapper::get_override; + // Bring in public scope: + using IPluginModPage::parentWidget; + virtual QString displayName() const override; virtual QIcon icon() const override; virtual QUrl pageURL() const override; virtual bool useIntegratedBrowser() const override; virtual bool handlesDownload(const QUrl &pageURL, const QUrl &downloadURL, MOBase::ModRepositoryFileInfo &fileInfo) const override; virtual void setParentWidget(QWidget *widget) override; + + void setParentWidget_Default(QWidget* parent) { + IPluginModPage::setParentWidget(parent); + } }; @@ -220,11 +227,18 @@ public: static constexpr const char* className = "IPluginToolWrapper"; using boost::python::wrapper::get_override; + // Bring in public scope: + using IPluginTool::parentWidget; + virtual QString displayName() const; virtual QString tooltip() const; virtual QIcon icon() const; virtual void setParentWidget(QWidget *parent); + void setParentWidget_Default(QWidget* parent) { + IPluginTool::setParentWidget(parent); + } + public Q_SLOTS: virtual void display() const; }; diff --git a/src/runner/pythonrunner.cpp b/src/runner/pythonrunner.cpp index 0d9b6cc..938186f 100644 --- a/src/runner/pythonrunner.cpp +++ b/src/runner/pythonrunner.cpp @@ -569,16 +569,6 @@ struct Functor_converter }; -// We must wrap IOrganizer::waitForApplication to convert the out parameter to a return value and also because bpy doesn't like coverting to void* (HANDLE) even if a converter exists. -static PyObject *waitForApplication(const bpy::object &self, size_t handle) -{ - IOrganizer& organizer = bpy::extract(self)(); - DWORD returnCode; - bool result = organizer.waitForApplication((HANDLE)handle, &returnCode); - return bpy::incref(bpy::make_tuple(result, returnCode).ptr()); -} - - /** * @brief Call policy that automatically downcast shared pointer of type FromType * to shared pointer of type ToType. @@ -671,6 +661,7 @@ BOOST_PYTHON_MODULE(mobase) utils::register_associative_container(); // Tuple: + bpy::register_tuple>(); // IOrganizer::waitForApplication bpy::register_tuple, QString, int>>(); // Variants: @@ -683,13 +674,13 @@ BOOST_PYTHON_MODULE(mobase) // Functions: Functor_converter(); // converter for the onRefreshed-callback + Functor_converter(); + Functor_converter(); Functor_converter(); // converter for the onModStateChanged-callback Functor_converter(); - Functor_converter(); Functor_converter(); - Functor_converter(); - Functor_converter(QString const&)>(); Functor_converter const&)>(); + Functor_converter(QString const&)>(); bpy::def("toPyQt", &toPyQt); @@ -765,45 +756,55 @@ BOOST_PYTHON_MODULE(mobase) .def_readwrite("origins", &IOrganizer::FileInfo::origins) ; - bpy::class_("IOrganizer") - .def("createNexusBridge", bpy::pure_virtual(&IOrganizer::createNexusBridge), bpy::return_value_policy()) - .def("profileName", bpy::pure_virtual(&IOrganizer::profileName)) - .def("profilePath", bpy::pure_virtual(&IOrganizer::profilePath)) - .def("downloadsPath", bpy::pure_virtual(&IOrganizer::downloadsPath)) - .def("overwritePath", bpy::pure_virtual(&IOrganizer::overwritePath)) - .def("basePath", bpy::pure_virtual(&IOrganizer::basePath)) - .def("modsPath", bpy::pure_virtual(&IOrganizer::modsPath)) - .def("appVersion", bpy::pure_virtual(&IOrganizer::appVersion)) - .def("getMod", bpy::pure_virtual(&IOrganizer::getMod), bpy::return_value_policy()) - .def("createMod", bpy::pure_virtual(&IOrganizer::createMod), bpy::return_value_policy()) - .def("getGame", bpy::pure_virtual(&IOrganizer::getGame), bpy::return_value_policy()) - .def("removeMod", bpy::pure_virtual(&IOrganizer::removeMod)) - .def("modDataChanged", bpy::pure_virtual(&IOrganizer::modDataChanged)) - .def("pluginSetting", bpy::pure_virtual(&IOrganizer::pluginSetting)) - .def("setPluginSetting", bpy::pure_virtual(&IOrganizer::setPluginSetting)) - .def("persistent", bpy::pure_virtual(&IOrganizer::persistent)) - .def("setPersistent", bpy::pure_virtual(&IOrganizer::setPersistent)) - .def("pluginDataPath", bpy::pure_virtual(&IOrganizer::pluginDataPath)) - .def("installMod", bpy::pure_virtual(&IOrganizer::installMod),(bpy::arg("nameSuggestion")=""), bpy::return_value_policy()) - .def("resolvePath", bpy::pure_virtual(&IOrganizer::resolvePath)) - .def("listDirectories", bpy::pure_virtual(&IOrganizer::listDirectories)) - .def("findFiles", bpy::pure_virtual(&IOrganizer::findFiles)) - .def("getFileOrigins", bpy::pure_virtual(&IOrganizer::getFileOrigins)) - .def("findFileInfos", bpy::pure_virtual(&IOrganizer::findFileInfos)) - .def("downloadManager", bpy::pure_virtual(&IOrganizer::downloadManager), bpy::return_value_policy()) - .def("pluginList", bpy::pure_virtual(&IOrganizer::pluginList), bpy::return_value_policy()) - .def("modList", bpy::pure_virtual(&IOrganizer::modList), bpy::return_value_policy()) - .def("profile", bpy::pure_virtual(&IOrganizer::profile), bpy::return_value_policy()) - .def("startApplication", bpy::pure_virtual(&IOrganizer::startApplication), ((bpy::arg("args")=QStringList()), (bpy::arg("cwd")=""), (bpy::arg("profile")=""), (bpy::arg("forcedCustomOverwrite")=""), (bpy::arg("ignoreCustomOverwrite")=false)), bpy::return_value_policy()) + bpy::class_("IOrganizer", bpy::no_init) + .def("createNexusBridge", &IOrganizer::createNexusBridge, bpy::return_value_policy()) + .def("profileName", &IOrganizer::profileName) + .def("profilePath", &IOrganizer::profilePath) + .def("downloadsPath", &IOrganizer::downloadsPath) + .def("overwritePath", &IOrganizer::overwritePath) + .def("basePath", &IOrganizer::basePath) + .def("modsPath", &IOrganizer::modsPath) + .def("appVersion", &IOrganizer::appVersion) + .def("getMod", &IOrganizer::getMod, bpy::return_value_policy()) + .def("createMod", &IOrganizer::createMod, bpy::return_value_policy()) + .def("getGame", &IOrganizer::getGame, bpy::return_value_policy()) + .def("removeMod", &IOrganizer::removeMod) + .def("modDataChanged", &IOrganizer::modDataChanged) + .def("pluginSetting", &IOrganizer::pluginSetting) + .def("setPluginSetting", &IOrganizer::setPluginSetting) + .def("persistent", &IOrganizer::persistent, bpy::arg("persistent") = QVariant()) + .def("setPersistent", &IOrganizer::setPersistent, bpy::arg("sync") = true) + .def("pluginDataPath", &IOrganizer::pluginDataPath) + .def("installMod", &IOrganizer::installMod, (bpy::arg("name_suggestion") = ""), bpy::return_value_policy()) + .def("resolvePath", &IOrganizer::resolvePath) + .def("listDirectories", &IOrganizer::listDirectories) + .def("findFiles", &IOrganizer::findFiles) + .def("getFileOrigins", &IOrganizer::getFileOrigins) + .def("findFileInfos", &IOrganizer::findFileInfos) + .def("downloadManager", &IOrganizer::downloadManager, bpy::return_value_policy()) + .def("pluginList", &IOrganizer::pluginList, bpy::return_value_policy()) + .def("modList", &IOrganizer::modList, bpy::return_value_policy()) + .def("profile", &IOrganizer::profile, bpy::return_value_policy()) + .def("startApplication", + +[](IOrganizer* o, const QString& executable, const QStringList& args, const QString& cwd, const QString& profile, + const QString& forcedCustomOverwrite, bool ignoreCustomOverwrite) { + return (std::uintptr_t) o->startApplication(executable, args, cwd, profile, forcedCustomOverwrite, ignoreCustomOverwrite); + }, + ((bpy::arg("args") = QStringList()), (bpy::arg("cwd") = ""), (bpy::arg("profile") = ""), (bpy::arg("forcedCustomOverwrite") = ""), (bpy::arg("ignoreCustomOverwrite") = false)), bpy::return_value_policy()) //.def("waitForApplication", bpy::pure_virtual(&IOrganizer::waitForApplication), (bpy::arg("exitCode")=nullptr), bpy::return_value_policy()) // Use wrapped version - .def("waitForApplication", waitForApplication) - .def("onModInstalled", bpy::pure_virtual(&IOrganizer::onModInstalled)) - .def("onAboutToRun", bpy::pure_virtual(&IOrganizer::onAboutToRun)) - .def("onFinishedRun", bpy::pure_virtual(&IOrganizer::onFinishedRun)) - .def("refreshModList", bpy::pure_virtual(&IOrganizer::refreshModList), (bpy::arg("saveChanges")=true)) - .def("managedGame", bpy::pure_virtual(&IOrganizer::managedGame), bpy::return_value_policy()) - .def("modsSortedByProfilePriority", bpy::pure_virtual(&IOrganizer::modsSortedByProfilePriority)) + .def("waitForApplication", +[](IOrganizer *o, std::uintptr_t handle) { + DWORD returnCode; + bool result = o->waitForApplication((HANDLE)handle, &returnCode); + return std::make_tuple(result, returnCode); + } + ) + .def("onModInstalled", &IOrganizer::onModInstalled) + .def("onAboutToRun", &IOrganizer::onAboutToRun) + .def("onFinishedRun", &IOrganizer::onFinishedRun) + .def("refreshModList", &IOrganizer::refreshModList, (bpy::arg("save_changes")=true)) + .def("managedGame", &IOrganizer::managedGame, bpy::return_value_policy()) + .def("modsSortedByProfilePriority", &IOrganizer::modsSortedByProfilePriority) ; // FileTreeEntry Scope: @@ -977,11 +978,11 @@ BOOST_PYTHON_MODULE(mobase) ; utils::register_sequence_container>>(); - bpy::class_("IInstallationManager") - .def("extractFile", bpy::pure_virtual(&IInstallationManager::extractFile)) - .def("extractFiles", bpy::pure_virtual(&IInstallationManager::extractFiles)) - .def("installArchive", bpy::pure_virtual(&IInstallationManager::installArchive)) - .def("setURL", bpy::pure_virtual(&IInstallationManager::setURL)) + bpy::class_("IInstallationManager", bpy::no_init) + .def("extractFile", &IInstallationManager::extractFile) + .def("extractFiles", &IInstallationManager::extractFiles) + .def("installArchive", &IInstallationManager::installArchive) + .def("setURL", &IInstallationManager::setURL) ; bpy::class_("IModInterface") @@ -1205,27 +1206,39 @@ BOOST_PYTHON_MODULE(mobase) auto result = p->install(modName, tree, version, nexusID); return std::make_tuple(result, tree, version, nexusID); }) - .def("parentWidget", &IPluginInstallerSimpleWrapper::parentWidget, bpy::return_value_policy()) - .def("manager", &IPluginInstallerSimpleWrapper::manager, bpy::return_value_policy()) + .def("_parentWidget", &IPluginInstallerSimpleWrapper::parentWidget, bpy::return_value_policy()) + .def("_manager", &IPluginInstallerSimpleWrapper::manager, bpy::return_value_policy()) ; bpy::class_("IPluginInstallerCustom") .def("isArchiveSupported", &IPluginInstallerCustom::isArchiveSupported) .def("supportedExtensions", &IPluginInstallerCustom::supportedExtensions) .def("install", &IPluginInstallerCustom::install) - .def("parentWidget", &IPluginInstallerSimpleWrapper::parentWidget, bpy::return_value_policy()) - .def("manager", &IPluginInstallerCustomWrapper::manager, bpy::return_value_policy()) + .def("_parentWidget", &IPluginInstallerSimpleWrapper::parentWidget, bpy::return_value_policy()) + .def("_manager", &IPluginInstallerCustomWrapper::manager, bpy::return_value_policy()) ; bpy::class_("IPluginModPage") - .def("setParentWidget", bpy::pure_virtual(&MOBase::IPluginModPage::setParentWidget)) + .def("displayName", bpy::pure_virtual(&IPluginModPage::displayName)) + .def("icon", bpy::pure_virtual(&IPluginModPage::icon)) + .def("pageURL", bpy::pure_virtual(&IPluginModPage::pageURL)) + .def("useIntegratedBrowser", bpy::pure_virtual(&IPluginModPage::useIntegratedBrowser)) + .def("handlesDownload", bpy::pure_virtual(&IPluginModPage::handlesDownload)) + .def("setParentWidget", &IPluginModPage::setParentWidget, &IPluginModPageWrapper::setParentWidget_Default) + .def("_parentWidget", &IPluginModPageWrapper::parentWidget, bpy::return_value_policy()) ; - bpy::class_("IPluginPreview") + bpy::class_, boost::noncopyable>("IPluginPreview") + .def("supportedExtensions", bpy::pure_virtual(&IPluginPreview::supportedExtensions)) + .def("genFilePreview", bpy::pure_virtual(&IPluginPreview::genFilePreview), bpy::return_value_policy()) ; bpy::class_, boost::noncopyable>("IPluginTool") - .def("setParentWidget", bpy::pure_virtual(&MOBase::IPluginTool::setParentWidget)) + .def("displayName", bpy::pure_virtual(&IPluginTool::displayName)) + .def("tooltip", bpy::pure_virtual(&IPluginTool::tooltip)) + .def("icon", bpy::pure_virtual(&IPluginTool::icon)) + .def("setParentWidget", &IPluginTool::setParentWidget, &IPluginToolWrapper::setParentWidget_Default) + .def("_parentWidget", &IPluginToolWrapper::parentWidget, bpy::return_value_policy()) ; HANDLE_converters(); @@ -1374,8 +1387,7 @@ QList PythonRunner::instantiate(const QString &pluginName) std::string temp = ToString(pluginName); if (handled_exec_file(temp.c_str(), moduleNamespace)) { - reportPythonError(); - return QList(); + throw pyexcept::PythonError(); } m_PythonObjects[pluginName] = moduleNamespace["createPlugin"](); @@ -1399,7 +1411,7 @@ QList PythonRunner::instantiate(const QString &pluginName) return interfaceList; } catch (const bpy::error_already_set&) { qWarning("failed to run python script \"%s\"", qUtf8Printable(pluginName)); - reportPythonError(); + throw pyexcept::PythonError(); } return QList(); } diff --git a/src/runner/pythonutils.h b/src/runner/pythonutils.h index 4aeb550..b424d15 100644 --- a/src/runner/pythonutils.h +++ b/src/runner/pythonutils.h @@ -70,11 +70,12 @@ namespace utils { bpy::list pyList; try { - for (auto& item : container) + for (auto& item : container) { pyList.append(item); + } } catch (const bpy::error_already_set&) { - reportPythonError(); + throw pyexcept::PythonError(); } return bpy::incref(pyList.ptr()); @@ -115,7 +116,7 @@ namespace utils { pyList.append(item); } catch (const bpy::error_already_set&) { - reportPythonError(); + throw pyexcept::PythonError(); } return bpy::incref(pyList.ptr()); diff --git a/src/runner/pythonwrapperutilities.h b/src/runner/pythonwrapperutilities.h index 86317e0..d11acc2 100644 --- a/src/runner/pythonwrapperutilities.h +++ b/src/runner/pythonwrapperutilities.h @@ -1,32 +1,71 @@ #ifndef PYTHONWRAPPERUTILITIES_H #define PYTHONWRAPPERUTILITIES_H +#include + #include #include "error.h" -class MissingImplementation : public MOBase::MyException { -public: - MissingImplementation(QString className, QString methodName) : MyException("Python class implementing \"" + - className + - "\" has no implementation of method \"" + - methodName + "\"") {} -}; - -#define PYCATCH catch (const boost::python::error_already_set &) { reportPythonError(); throw MOBase::MyException("unhandled exception"); }\ - catch (const MissingImplementation &missingImplementationException) { throw missingImplementationException; }\ - catch (...) { throw MOBase::MyException("An unknown exception was thrown in python code"); } - -template +template ReturnType basicWrapperFunctionImplementation(const WrapperType *wrapper, const char *methodName, Args... args) { try { GILock lock; boost::python::override implementation = wrapper->get_override(methodName); if (!implementation) - throw MissingImplementation(wrapper->className, methodName); + throw pyexcept::MissingImplementation(wrapper->className, methodName); return implementation(args...).as(); - } PYCATCH; + } + catch (const boost::python::error_already_set&) { + throw pyexcept::PythonError(); + } + catch (pyexcept::MissingImplementation const& missingImplementation) { + throw missingImplementation; + } + catch (...) { + throw pyexcept::UnknownException(); + } +} + +template +ReturnType basicWrapperFunctionImplementationWithDefault(WrapperType* wrapper, Fn fn, const char* methodName, Args... args) +{ + try { + GILock lock; + boost::python::override implementation = wrapper->get_override(methodName); + if (implementation) { + return implementation(args...).as(); + } + } + catch (const boost::python::error_already_set&) { + throw pyexcept::PythonError(); + } + catch (...) { + throw pyexcept::UnknownException(); + } + + return std::invoke(fn, wrapper, args...); +} + +template +ReturnType basicWrapperFunctionImplementationWithDefault(const WrapperType* wrapper, Fn fn, const char* methodName, Args... args) +{ + try { + GILock lock; + boost::python::override implementation = wrapper->get_override(methodName); + if (implementation) { + return implementation(args...).as(); + } + } + catch (const boost::python::error_already_set&) { + throw pyexcept::PythonError(); + } + catch (...) { + throw pyexcept::UnknownException(); + } + + return std::invoke(fn, wrapper, args...); } #endif // PYTHONWRAPPERUTILITIES_H diff --git a/src/runner/uibasewrappers.h b/src/runner/uibasewrappers.h index bab65bb..e1e14ce 100644 --- a/src/runner/uibasewrappers.h +++ b/src/runner/uibasewrappers.h @@ -126,7 +126,7 @@ private Q_SLOTS: GILock lock; m_FilesAvailableHandler(modID, userData, resultData); } catch (const boost::python::error_already_set&) { - reportPythonError(); + throw pyexcept::PythonError(); } } @@ -141,7 +141,7 @@ private Q_SLOTS: GILock lock; m_DescriptionAvailableHandler(modID, userData, resultData); } catch (const boost::python::error_already_set&) { - reportPythonError(); + throw pyexcept::PythonError(); } } catch (const std::exception &e) { qCritical("failed to report event: %s", e.what()); @@ -160,7 +160,7 @@ private Q_SLOTS: GILock lock; m_FileInfoHandler(modID, fileID, userData, resultData); } catch (const boost::python::error_already_set&) { - reportPythonError(); + throw pyexcept::PythonError(); } } catch (const std::exception &e) { qCritical("failed to report event: %s", e.what()); @@ -180,7 +180,7 @@ private Q_SLOTS: GILock lock; m_EndorsementToggledHandler(modID, userData, resultData); } catch (const boost::python::error_already_set&) { - reportPythonError(); + throw pyexcept::PythonError(); } } catch (const std::exception &e) { qCritical("failed to report event: %s", e.what()); @@ -200,7 +200,7 @@ private Q_SLOTS: GILock lock; m_TrackingToggledHandler(modID, userData, tracked); } catch (const boost::python::error_already_set&) { - reportPythonError(); + throw pyexcept::PythonError(); } } catch (const std::exception &e) { qCritical("failed to report event: %s", e.what()); @@ -215,7 +215,7 @@ private Q_SLOTS: GILock lock; m_FailedHandler(modID, fileID, userData, errorMessage); } catch (const boost::python::error_already_set&) { - reportPythonError(); + throw pyexcept::PythonError(); } } @@ -231,176 +231,6 @@ private: }; -// NOTE: Completely unnecessary - we're never going to override IOrganizer from within Python -struct IOrganizerWrapper : MOBase::IOrganizer, - boost::python::wrapper { - virtual MOBase::IModRepositoryBridge *createNexusBridge() const override - { - return this->get_override("createNexusBridge")(); - } - virtual QString profileName() const override - { - return this->get_override("profileName")(); - } - virtual QString profilePath() const override - { - return this->get_override("profilePath")(); - } - virtual QString downloadsPath() const override - { - return this->get_override("downloadsPath")(); - } - virtual QString overwritePath() const override - { - return this->get_override("overwritePath")(); - } - virtual QString basePath() const // override - { - return this->get_override("basePath")(); - } - virtual QString modsPath() const override - { - return this->get_override("modsPath")(); - } - virtual MOBase::VersionInfo appVersion() const override - { - return this->get_override("appVersion")(); - } - virtual MOBase::IModInterface *getMod(const QString &name) const override - { - return this->get_override("getMod")(name); - } - virtual MOBase::IModInterface * - createMod(MOBase::GuessedValue &name) override - { - return this->get_override("createMod")(name); - } - virtual MOBase::IPluginGame *getGame(const QString &gameName) const override - { - return this->get_override("getGame")(gameName); - } - virtual bool removeMod(MOBase::IModInterface *mod) override - { - return this->get_override("removeMod")(mod); - } - virtual void modDataChanged(MOBase::IModInterface *mod) override - { - this->get_override("modDataChanged")(mod); - } - virtual QVariant pluginSetting(const QString &pluginName, - const QString &key) const override - { - return this->get_override("pluginSetting")(pluginName, key).as(); - } - virtual void setPluginSetting(const QString &pluginName, const QString &key, - const QVariant &value) override - { - this->get_override("setPluginSetting")(pluginName, key, value); - } - virtual QVariant persistent(const QString &pluginName, const QString &key, - const QVariant &def = QVariant()) const override - { - return this->get_override("persistent")(pluginName, key, def).as(); - } - virtual void setPersistent(const QString &pluginName, const QString &key, - const QVariant &value, bool sync = true) override - { - this->get_override("setPersistent")(pluginName, key, value, sync); - } - virtual QString pluginDataPath() const override - { - return this->get_override("pluginDataPath")(); - } - virtual MOBase::IModInterface *installMod(const QString &fileName, - const QString &nameSuggestion - = QString()) override - { - return this->get_override("installMod")(fileName, nameSuggestion); - } - virtual MOBase::IDownloadManager *downloadManager() const override - { - return this->get_override("downloadManager")(); - } - virtual MOBase::IPluginList *pluginList() const override - { - return this->get_override("pluginList")(); - } - virtual MOBase::IModList *modList() const override - { - return this->get_override("modList")(); - } - virtual QString resolvePath(const QString &fileName) const override - { - return this->get_override("resolvePath")(fileName); - } - virtual QStringList - listDirectories(const QString &directoryName) const override - { - return this->get_override("listDirectories")(directoryName); - } - virtual QStringList - findFiles(const QString &path, - const std::function &filter) const override - { - return this->get_override("findFiles")(path, filter); - } - virtual QStringList getFileOrigins(const QString &fileName) const override - { - return this->get_override("getFileOrigins")(fileName); - } - virtual QList findFileInfos( - const QString &path, - const std::function &filter) const override - { - return this->get_override("findFileInfos")(path, filter); - } - virtual HANDLE startApplication(const QString &executable, - const QStringList &args = QStringList(), - const QString &cwd = "", - const QString &profile = "", - const QString &forcedCustomOverwrite = "", - bool ignoreCustomOverwrite = false) override - { - return reinterpret_cast(this->get_override("startApplication")(executable, args, cwd, profile, forcedCustomOverwrite, ignoreCustomOverwrite).as()); - } - virtual bool waitForApplication(HANDLE handle, - LPDWORD exitCode = nullptr) const override - { - return this->get_override("waitForApplication")(reinterpret_cast(handle), exitCode); - } - virtual void refreshModList(bool saveChanges = true) override - { - this->get_override("refreshModList")(saveChanges); - } - virtual bool - onAboutToRun(const std::function &func) override - { - return this->get_override("onAboutToRun")(func); - } - virtual bool onFinishedRun( - const std::function &func) override - { - return this->get_override("onFinishedRun")(func); - } - virtual bool - onModInstalled(const std::function &func) override - { - return this->get_override("onModInstalled")(func); - } - virtual MOBase::IProfile *profile() const override - { - return this->get_override("profile")(); - } - virtual MOBase::IPluginGame const *managedGame() const override - { - return this->get_override("managedGame")(); - } - virtual QStringList modsSortedByProfilePriority() const override - { - return this->get_override("modsSortedByProfilePriority")(); - } -}; - struct IProfileWrapper: MOBase::IProfile, boost::python::wrapper { virtual QString name() const override { return this->get_override("name")(); } @@ -426,15 +256,6 @@ struct IModRepositoryBridgeWrapper: MOBase::IModRepositoryBridge, boost::python: virtual void requestToggleEndorsement(QString gameName, int modID, QString modVersion, bool endorse, QVariant userData) { this->get_override("requestToggleEndorsement")(gameName, modID, endorse, userData); } }; -struct IInstallationManagerWrapper: MOBase::IInstallationManager, boost::python::wrapper -{ - virtual QString extractFile(std::shared_ptr entry) override { return this->get_override("extractFile")(entry); } - virtual QStringList extractFiles(std::vector> const& entries) override { return this->get_override("extractFiles")(entries); } - virtual MOBase::IPluginInstaller::EInstallResult installArchive(MOBase::GuessedValue &modName, const QString &archiveFile, int modId = 0) override { - return this->get_override("installArchive")(modName, archiveFile, modId); } - virtual void setURL(QString const &url) override { this->get_override("setURL")(url); } -}; - struct IModInterfaceWrapper: MOBase::IModInterface, boost::python::wrapper { virtual QString name() const override { return this->get_override("name")(); } From 2f1646a2e6102d4cc66d623d61f7966149ef11a3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mika=C3=ABl=20Capelle?= Date: Sat, 9 May 2020 21:27:00 +0200 Subject: [PATCH 03/35] Remove HANDLE_converters since it is not used anymore and did not really work. --- src/runner/pythonrunner.cpp | 40 ------------------------------------- 1 file changed, 40 deletions(-) diff --git a/src/runner/pythonrunner.cpp b/src/runner/pythonrunner.cpp index 70d46cc..13266be 100644 --- a/src/runner/pythonrunner.cpp +++ b/src/runner/pythonrunner.cpp @@ -142,44 +142,6 @@ struct QString_from_python_str }; -struct HANDLE_converters -{ - struct HANDLE_to_python - { - static PyObject *convert(HANDLE handle) { - size_t size_t_version = (size_t)handle; - return bpy::incref(bpy::object(size_t_version).ptr()); - } - }; - - // bpy isn't keen on actually using this. - // maybe it's detecting that the function receives a pointer, and assumes that it needs to convert to the pointer's target. - // the issue can be worked around by wrapping the function to take a size_t and converting it there - struct HANDLE_from_python - { - HANDLE_from_python() { - bpy::converter::registry::push_back(&convertible, &construct, bpy::type_id()); - } - - static void *convertible(PyObject *objPtr) { - return PyLong_Check(objPtr) ? objPtr : nullptr; - } - - static void construct(PyObject *objPtr, bpy::converter::rvalue_from_python_stage1_data *data) { - void *storage = ((bpy::converter::rvalue_from_python_storage*)data)->storage.bytes; - HANDLE *result = new (storage) HANDLE; - *result = (HANDLE)bpy::extract(objPtr)(); - } - }; - - HANDLE_converters() - { - HANDLE_from_python(); - bpy::to_python_converter(); - } -}; - - struct QVariant_to_python_obj { static PyObject *convert(const QVariant &var) { @@ -1273,8 +1235,6 @@ BOOST_PYTHON_MODULE(mobase) .def("_parentWidget", &IPluginToolWrapper::parentWidget, bpy::return_value_policy()) ; - HANDLE_converters(); - registerGameFeaturesPythonConverters(); } From 622d4246a7b491c66f35c8fd20715684dbb0ff0d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mika=C3=ABl=20Capelle?= Date: Sat, 9 May 2020 21:32:23 +0200 Subject: [PATCH 04/35] Clean some comments. --- src/runner/pythonrunner.cpp | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/runner/pythonrunner.cpp b/src/runner/pythonrunner.cpp index 13266be..7ae75f5 100644 --- a/src/runner/pythonrunner.cpp +++ b/src/runner/pythonrunner.cpp @@ -748,20 +748,21 @@ BOOST_PYTHON_MODULE(mobase) .def("pluginList", &IOrganizer::pluginList, bpy::return_value_policy()) .def("modList", &IOrganizer::modList, bpy::return_value_policy()) .def("profile", &IOrganizer::profile, bpy::return_value_policy()) + + // Custom implementation for startApplication and waitForApplication because 1) HANDLE (= void*) is not properly + // converted from/to python, and 2) we need to convert the by-ptr argument to a return-tuple for waitForApplication: .def("startApplication", +[](IOrganizer* o, const QString& executable, const QStringList& args, const QString& cwd, const QString& profile, const QString& forcedCustomOverwrite, bool ignoreCustomOverwrite) { return (std::uintptr_t) o->startApplication(executable, args, cwd, profile, forcedCustomOverwrite, ignoreCustomOverwrite); - }, - ((bpy::arg("args") = QStringList()), (bpy::arg("cwd") = ""), (bpy::arg("profile") = ""), (bpy::arg("forcedCustomOverwrite") = ""), (bpy::arg("ignoreCustomOverwrite") = false)), bpy::return_value_policy()) - //.def("waitForApplication", bpy::pure_virtual(&IOrganizer::waitForApplication), (bpy::arg("exitCode")=nullptr), bpy::return_value_policy()) - // Use wrapped version + }, ((bpy::arg("args") = QStringList()), (bpy::arg("cwd") = ""), (bpy::arg("profile") = ""), + (bpy::arg("forcedCustomOverwrite") = ""), (bpy::arg("ignoreCustomOverwrite") = false)), bpy::return_value_policy()) .def("waitForApplication", +[](IOrganizer *o, std::uintptr_t handle) { DWORD returnCode; bool result = o->waitForApplication((HANDLE)handle, &returnCode); return std::make_tuple(result, returnCode); - } - ) + }) + .def("onModInstalled", &IOrganizer::onModInstalled) .def("onAboutToRun", &IOrganizer::onAboutToRun) .def("onFinishedRun", &IOrganizer::onFinishedRun) From 039e33f5faf30a71e8b0d00bbbfe6a118a48ab9e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mika=C3=ABl=20Capelle?= Date: Sat, 9 May 2020 23:30:10 +0200 Subject: [PATCH 05/35] Minor clean and first try for ISaveGameInfoWidget. --- src/runner/gamefeatureswrappers.cpp | 7 +++++-- src/runner/pythonrunner.cpp | 8 ++++---- src/runner/uibasewrappers.h | 15 +++++++++++++-- 3 files changed, 22 insertions(+), 8 deletions(-) diff --git a/src/runner/gamefeatureswrappers.cpp b/src/runner/gamefeatureswrappers.cpp index 2d24352..91a5e8f 100644 --- a/src/runner/gamefeatureswrappers.cpp +++ b/src/runner/gamefeatureswrappers.cpp @@ -114,10 +114,13 @@ SaveGameInfoWrapper::MissingAssets SaveGameInfoWrapper::getMissingAssets(QString return basicWrapperFunctionImplementation(this, "getMissingAssets", file); } -MOBase::ISaveGameInfoWidget * SaveGameInfoWrapper::getSaveGameWidget(QWidget * parent) const +MOBase::ISaveGameInfoWidget* SaveGameInfoWrapper::getSaveGameWidget(QWidget* parent) const { + // This will require a lot of works as ISaveGameInfoWidget inherits QWidget and I currently found no + // way of exposing a class that inherits QWidget without having to expose manually the whole QWidget, + // and even with this, I am not sure how this would fit with PyQt/sip. qCritical("Calling method with unimplemented from_python converter."); - return basicWrapperFunctionImplementation(this, "getSaveGameWidget", boost::python::ptr(parent)); + return nullptr; } bool SaveGameInfoWrapper::hasScriptExtenderSave(QString const & file) const diff --git a/src/runner/pythonrunner.cpp b/src/runner/pythonrunner.cpp index 7ae75f5..7ee3524 100644 --- a/src/runner/pythonrunner.cpp +++ b/src/runner/pythonrunner.cpp @@ -711,12 +711,12 @@ BOOST_PYTHON_MODULE(mobase) .def("hasScriptExtenderFile", bpy::pure_virtual(&ISaveGame::hasScriptExtenderFile)) ; - // TODO: ISaveGameInfoWidget bindings + // TODO: ISaveGameInfoWidget. bpy::class_("FileInfo", bpy::init<>()) - .def_readwrite("filePath", &IOrganizer::FileInfo::filePath) - .def_readwrite("archive", &IOrganizer::FileInfo::archive) - .def_readwrite("origins", &IOrganizer::FileInfo::origins) + .def_readwrite("filePath", &IOrganizer::FileInfo::filePath) + .def_readwrite("archive", &IOrganizer::FileInfo::archive) + .def_readwrite("origins", &IOrganizer::FileInfo::origins) ; bpy::class_("IOrganizer", bpy::no_init) diff --git a/src/runner/uibasewrappers.h b/src/runner/uibasewrappers.h index e1e14ce..3af6b4c 100644 --- a/src/runner/uibasewrappers.h +++ b/src/runner/uibasewrappers.h @@ -303,8 +303,7 @@ struct IModListWrapper: MOBase::IModList, boost::python::wrapper { public: @@ -318,5 +317,17 @@ public: virtual bool hasScriptExtenderFile() const override { return basicWrapperFunctionImplementation(this, "hasScriptExtenderFile"); }; }; +// This needs a wrapper but currently I have no idea how to expose this properly to python: +class ISaveGameInfoWidgetWrapper : public MOBase::ISaveGameInfoWidget, public boost::python::wrapper +{ +public: + static constexpr const char* className = "ISaveGameInfoWidgetWrapper"; + using boost::python::wrapper::get_override; + + // Bring the constructor: + using ISaveGameInfoWidget::ISaveGameInfoWidget; + + virtual void setSave(QString const& save) override { basicWrapperFunctionImplementation(this, "setSave", save); }; +}; #endif // UIBASEWRAPPERS_H From b360cae555a65a143df00c57757e888ad7c32a01 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mika=C3=ABl=20Capelle?= Date: Sun, 10 May 2020 15:57:46 +0200 Subject: [PATCH 06/35] Add missing include in error.h. --- src/runner/error.h | 1 + 1 file changed, 1 insertion(+) diff --git a/src/runner/error.h b/src/runner/error.h index d4f8b10..01100a6 100644 --- a/src/runner/error.h +++ b/src/runner/error.h @@ -6,6 +6,7 @@ #include #include +#include struct ErrWrapper { From d404a37feda44a1a45764870f81ef3fad5ecc4bd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mika=C3=ABl=20Capelle?= Date: Sun, 10 May 2020 16:01:14 +0200 Subject: [PATCH 07/35] Add/fix bindings for ISaveGame and ISaveGameInfoWidget. --- src/runner/gamefeatureswrappers.cpp | 9 ++------ src/runner/gamefeatureswrappers.h | 7 ++++++ src/runner/pythonrunner.cpp | 17 ++++++++++++-- src/runner/pythonwrapperutilities.h | 36 ++++++++++++++++++++++++++++- 4 files changed, 59 insertions(+), 10 deletions(-) diff --git a/src/runner/gamefeatureswrappers.cpp b/src/runner/gamefeatureswrappers.cpp index 91a5e8f..a00b8b1 100644 --- a/src/runner/gamefeatureswrappers.cpp +++ b/src/runner/gamefeatureswrappers.cpp @@ -9,7 +9,6 @@ #include #include -#include "gilock.h" #include "pythonwrapperutilities.h" ///////////////////////////// @@ -106,7 +105,7 @@ bool LocalSavegamesWrapper::prepareProfile(MOBase::IProfile * profile) MOBase::ISaveGame const * SaveGameInfoWrapper::getSaveGameInfo(QString const & file) const { - return basicWrapperFunctionImplementation(this, "getSaveGameInfo", file); + return basicWrapperFunctionImplementation(this, m_SaveGames[file], "getSaveGameInfo", file); } SaveGameInfoWrapper::MissingAssets SaveGameInfoWrapper::getMissingAssets(QString const & file) const @@ -116,11 +115,7 @@ SaveGameInfoWrapper::MissingAssets SaveGameInfoWrapper::getMissingAssets(QString MOBase::ISaveGameInfoWidget* SaveGameInfoWrapper::getSaveGameWidget(QWidget* parent) const { - // This will require a lot of works as ISaveGameInfoWidget inherits QWidget and I currently found no - // way of exposing a class that inherits QWidget without having to expose manually the whole QWidget, - // and even with this, I am not sure how this would fit with PyQt/sip. - qCritical("Calling method with unimplemented from_python converter."); - return nullptr; + return basicWrapperFunctionImplementation(this, m_SaveGameWidget, "getSaveGameWidget", parent); } bool SaveGameInfoWrapper::hasScriptExtenderSave(QString const & file) const diff --git a/src/runner/gamefeatureswrappers.h b/src/runner/gamefeatureswrappers.h index 05191ef..9eee37c 100644 --- a/src/runner/gamefeatureswrappers.h +++ b/src/runner/gamefeatureswrappers.h @@ -1,6 +1,8 @@ #ifndef GAMEFEATURESWRAPPERS_H #define GAMEFEATURESWRAPPERS_H +#include + #include #include #include @@ -71,6 +73,11 @@ public: virtual MissingAssets getMissingAssets(QString const &file) 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: + mutable std::map m_SaveGames; + mutable boost::python::object m_SaveGameWidget; }; class ScriptExtenderWrapper : public ScriptExtender, public boost::python::wrapper diff --git a/src/runner/pythonrunner.cpp b/src/runner/pythonrunner.cpp index 7ee3524..97e8f8c 100644 --- a/src/runner/pythonrunner.cpp +++ b/src/runner/pythonrunner.cpp @@ -703,7 +703,7 @@ BOOST_PYTHON_MODULE(mobase) .def("isCustom", &ExecutableInfo::isCustom) ; - bpy::class_("ISaveGame") + bpy::class_, ISaveGameWrapper*, boost::noncopyable>("ISaveGame") .def("getFilename", bpy::pure_virtual(&ISaveGame::getFilename)) .def("getCreationTime", bpy::pure_virtual(&ISaveGame::getCreationTime)) .def("getSaveGroupIdentifier", bpy::pure_virtual(&ISaveGame::getSaveGroupIdentifier)) @@ -711,7 +711,20 @@ BOOST_PYTHON_MODULE(mobase) .def("hasScriptExtenderFile", bpy::pure_virtual(&ISaveGame::hasScriptExtenderFile)) ; - // TODO: ISaveGameInfoWidget. + // This is tricky because there is no way to tell boost::python that ISaveGameInfoWidget inherits + // QWidget (bpy::bases crashes when loading the module... ). Without it, the python class + // does not expose the QWidget methods, which makes it useless. + // There is two way to do this: 1) expose the widget via a `_widget()` method that basically returns + // the object, but as a QWidget, or 2) override __getattr__ to forward everything to the QWidget (note + // that __getattr__ is only called if the attribute is not found in the class by standard mean). + bpy::class_, ISaveGameInfoWidgetWrapper*, boost::noncopyable>("ISaveGameInfoWidget", bpy::init>()) + .def("setSave", bpy::pure_virtual(&ISaveGameInfoWidget::setSave)) + .def("__getattr__", +[](ISaveGameInfoWidget *w, bpy::str str) -> bpy::object { + // Create an object corresponding to the widget: + bpy::object obj{ (QWidget*)w }; + return obj.attr(str); + }) + ; bpy::class_("FileInfo", bpy::init<>()) .def_readwrite("filePath", &IOrganizer::FileInfo::filePath) diff --git a/src/runner/pythonwrapperutilities.h b/src/runner/pythonwrapperutilities.h index d11acc2..6b25478 100644 --- a/src/runner/pythonwrapperutilities.h +++ b/src/runner/pythonwrapperutilities.h @@ -3,18 +3,26 @@ #include +#include + #include #include "error.h" +#include "gilock.h" +/** + * @brief Call the given method on the wrapper with the given arguments, with proper + * exception handling. + */ template ReturnType basicWrapperFunctionImplementation(const WrapperType *wrapper, const char *methodName, Args... args) { try { GILock lock; boost::python::override implementation = wrapper->get_override(methodName); - if (!implementation) + if (!implementation) { throw pyexcept::MissingImplementation(wrapper->className, methodName); + } return implementation(args...).as(); } catch (const boost::python::error_already_set&) { @@ -28,6 +36,32 @@ ReturnType basicWrapperFunctionImplementation(const WrapperType *wrapper, const } } +/** + * @brief Similar to the first-overload but also stores the python object in the given reference. + */ +template +ReturnType basicWrapperFunctionImplementation(const WrapperType* wrapper, boost::python::object &ref, const char* methodName, Args... args) +{ + try { + GILock lock; + boost::python::override implementation = wrapper->get_override(methodName); + if (!implementation) { + throw pyexcept::MissingImplementation(wrapper->className, methodName); + } + ref = implementation(args...); + return boost::python::extract(ref)(); + } + catch (const boost::python::error_already_set&) { + throw pyexcept::PythonError(); + } + catch (pyexcept::MissingImplementation const& missingImplementation) { + throw missingImplementation; + } + catch (...) { + throw pyexcept::UnknownException(); + } +} + template ReturnType basicWrapperFunctionImplementationWithDefault(WrapperType* wrapper, Fn fn, const char* methodName, Args... args) { From fcf1aa8b42a41866f29a46d4da6c08fed139ef37 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mika=C3=ABl=20Capelle?= Date: Mon, 11 May 2020 23:00:29 +0200 Subject: [PATCH 08/35] Move converter to a dedicate file. Add Q_DELEGATE for QObject-derived wrappers. --- src/runner/converters.h | 467 +++++++++++++++++++++++++ src/runner/pythonrunner.cpp | 664 +++++------------------------------- 2 files changed, 555 insertions(+), 576 deletions(-) create mode 100644 src/runner/converters.h diff --git a/src/runner/converters.h b/src/runner/converters.h new file mode 100644 index 0000000..6fa50e4 --- /dev/null +++ b/src/runner/converters.h @@ -0,0 +1,467 @@ +#ifndef PYTHON_CONVERTERS_HPP +#define PYTHON_CONVERTERS_HPP + +#include +#include +#include +#include +#include +#include + +// sip and qt slots seems to conflict +#include + +#include "idownloadmanager.h" +#include "imodrepositorybridge.h" + +// Include the container converters from utils: +#include "pythonutils.h" + +namespace utils { + + namespace bpy = boost::python; + + namespace QString_converter { + + /** + * We need this since sip does not expose QString but uses standard python str. + */ + struct QString_to_python_str + { + static PyObject* convert(const QString& str) { + // It's safer to explicitly convert to unicode as if we don't, this can return + // either str or unicode without it being easy to know which to expect + bpy::object pyStr = bpy::object(qUtf8Printable(str)); + if (SIPBytes_Check(pyStr.ptr())) + pyStr = pyStr.attr("decode")("utf-8"); + return bpy::incref(pyStr.ptr()); + } + }; + + struct QString_from_python_str + { + + static void* convertible(PyObject* objPtr) { + return SIPBytes_Check(objPtr) || PyUnicode_Check(objPtr) ? objPtr : nullptr; + } + + static void construct(PyObject* objPtr, bpy::converter::rvalue_from_python_stage1_data* data) { + // Ensure the string uses 8-bit characters + PyObject* strPtr = PyUnicode_Check(objPtr) ? PyUnicode_AsUTF8String(objPtr) : objPtr; + + // Extract the character data from the python string + const char* value = SIPBytes_AsString(strPtr); + assert(value != nullptr); + + // allocate storage + void* storage = ((bpy::converter::rvalue_from_python_storage*)data)->storage.bytes; + + // construct QString in the allocated memory + new (storage) QString(value); + + data->convertible = storage; + + // Deallocate local copy if one was made + if (strPtr != objPtr) + Py_DecRef(strPtr); + } + }; + + } + + namespace QFlags_converter { + + /** + * + */ + template + struct QFlags_to_int + { + static PyObject* convert(const QFlags& flags) { + return bpy::incref(bpy::object(static_cast(flags)).ptr()); + } + }; + + template + struct QFlags_from_python_obj + { + + static void* convertible(PyObject* objPtr) { + return SIPLong_Check(objPtr) ? objPtr : nullptr; + } + + static void construct(PyObject* objPtr, bpy::converter::rvalue_from_python_stage1_data* data) { + int intVersion = (int)SIPLong_AsLong(objPtr); + T tVersion = (T)intVersion; + void* storage = ((bpy::converter::rvalue_from_python_storage>*)data)->storage.bytes; + new (storage) QFlags(tVersion); + + data->convertible = storage; + } + }; + + } + + namespace QVariant_converter { + + struct QVariant_to_python_obj + { + static PyObject* convert(const QVariant& var) { + switch (var.type()) { + case QVariant::Invalid: return bpy::incref(Py_None); + case QVariant::Int: return SIPLong_FromLong(var.toInt()); + case QVariant::UInt: return PyLong_FromUnsignedLong(var.toUInt()); + case QVariant::Bool: return PyBool_FromLong(var.toBool()); + case QVariant::String: return bpy::incref(bpy::object(var.toString()).ptr()); + case QVariant::List: { + return bpy::incref(bpy::object(var.toList()).ptr()); + } break; + case QVariant::Map: { + return bpy::incref(bpy::object(var.toMap()).ptr()); + } break; + default: { + PyErr_Format(PyExc_TypeError, "type unsupported: %d", var.type()); + throw bpy::error_already_set(); + } break; + } + } + }; + + struct QVariant_from_python_obj + { + + static void* convertible(PyObject* objPtr) { + if (!SIPBytes_Check(objPtr) && !PyUnicode_Check(objPtr) && !PyLong_Check(objPtr) && + !PyBool_Check(objPtr) && !PyList_Check(objPtr) && !PyDict_Check(objPtr) && + objPtr != Py_None) { + return nullptr; + } + return objPtr; + } + + template + static void constructVariant(const T& value, bpy::converter::rvalue_from_python_stage1_data* data) { + void* storage = ((bpy::converter::rvalue_from_python_storage*)data)->storage.bytes; + + new (storage) QVariant(value); + + data->convertible = storage; + } + + static void constructVariant(bpy::converter::rvalue_from_python_stage1_data* data) { + void* storage = ((bpy::converter::rvalue_from_python_storage*)data)->storage.bytes; + + new (storage) QVariant(); + + data->convertible = storage; + } + + static void construct(PyObject* objPtr, bpy::converter::rvalue_from_python_stage1_data* data) { + // PyBools will also return true for SIPLong_Check but not the other way around, so the order + // here is relevant + if (PyList_Check(objPtr)) { + constructVariant(bpy::extract(objPtr)(), data); + } + else if (objPtr == Py_None) { + constructVariant(data); + } + else if (PyDict_Check(objPtr)) { + constructVariant(bpy::extract(objPtr)(), data); + } + else if (SIPBytes_Check(objPtr) || PyUnicode_Check(objPtr)) { + constructVariant(bpy::extract(objPtr)(), data); + } + else if (PyBool_Check(objPtr)) { + constructVariant(bpy::extract(objPtr)(), data); + } + else if (SIPLong_Check(objPtr)) { + //QVariant doesn't have long. It has int or long long. Given that on m/s, + //long is 32 bits for 32- and 64- bit code... + constructVariant(bpy::extract(objPtr)(), data); + } + else { + PyErr_SetString(PyExc_TypeError, "type unsupported"); + throw bpy::error_already_set(); + } + } + }; + + } + + namespace QClass_converter { + + template struct MetaData; + + template <> struct MetaData { static const char* className() { return "QObject"; } }; + template <> struct MetaData { static const char* className() { return "QWidget"; } }; + template <> struct MetaData { static const char* className() { return "QDateTime"; } }; + template <> struct MetaData { static const char* className() { return "QDir"; } }; + template <> struct MetaData { static const char* className() { return "QFileInfo"; } }; + template <> struct MetaData { static const char* className() { return "QIcon"; } }; + template <> struct MetaData { static const char* className() { return "QSize"; } }; + template <> struct MetaData { static const char* className() { return "QStringList"; } }; + template <> struct MetaData { static const char* className() { return "QUrl"; } }; + template <> struct MetaData { static const char* className() { return "QVariant"; } }; + + template + struct QClass_converters + { + struct QClass_to_PyQt + { + template + static typename std::enable_if_t, T*> getSafeCopy(T* qClass) + { + return new T(*qClass); + } + + template + static typename std::enable_if_t, T*> getSafeCopy(T* qClass) + { + return qClass; + } + + static PyObject* convert(const T& object) { + const sipTypeDef* type = sipAPIAccess::sipAPI()->api_find_type(MetaData::className()); + if (type == nullptr) { + return bpy::incref(Py_None); + } + + PyObject* sipObj = sipAPIAccess::sipAPI()->api_convert_from_type((void*)getSafeCopy((T*)&object), type, 0); + if (sipObj == nullptr) { + return bpy::incref(Py_None); + } + + if (std::is_copy_constructible_v) + // Ensure Python deletes the C++ component + sipAPIAccess::sipAPI()->api_transfer_back(sipObj); + + return bpy::incref(sipObj); + } + + static PyObject* convert(T* object) { + if (object == nullptr) { + return bpy::incref(Py_None); + } + + const sipTypeDef* type = sipAPIAccess::sipAPI()->api_find_type(MetaData::className()); + if (type == nullptr) { + return bpy::incref(Py_None); + } + + PyObject* sipObj = sipAPIAccess::sipAPI()->api_convert_from_type(getSafeCopy(object), type, 0); + if (sipObj == nullptr) { + return bpy::incref(Py_None); + } + + if (std::is_copy_constructible_v) + // Ensure Python deletes the C++ component + sipAPIAccess::sipAPI()->api_transfer_back(sipObj); + + return bpy::incref(sipObj); + } + + static PyObject* convert(const T* object) { + return convert((T*)object); + } + + static PyTypeObject const* get_pytype() { + const sipTypeDef* type = sipAPIAccess::sipAPI()->api_find_type(MetaData::className()); + if (type == nullptr) { + return bpy::incref(Py_None); + } + return bpy::incref(type->td_py_type); + } + }; + + static void* QClass_from_PyQt(PyObject* objPtr) + { + // This would transfer responsibility for deconstructing the object to C++, but Boost assumes l-value converters (such as this) don't do that + // Instead, this should be called within the wrappers for functions which return deletable pointers. + //sipAPI()->api_transfer_to(objPtr, Py_None); + if (PyObject_TypeCheck(objPtr, sipAPIAccess::sipAPI()->api_simplewrapper_type)) { + sipSimpleWrapper* wrapper; + wrapper = reinterpret_cast(objPtr); + return wrapper->data; + } + else if (PyObject_TypeCheck(objPtr, sipAPIAccess::sipAPI()->api_wrapper_type)) { + sipWrapper* wrapper; + wrapper = reinterpret_cast(objPtr); + return wrapper->super.data; + } + else { + if constexpr (std::is_same_v) + { + // QStringLists aren't wrapped by PyQt - regular Python string/unicode lists are used instead + bpy::extract> extractor(objPtr); + if (extractor.check()) + return new QStringList(extractor()); + } + PyErr_SetString(PyExc_TypeError, "type not wrapped"); + bpy::throw_error_already_set(); + } + return new void*; + } + }; + + } + + namespace { + int getArgCount(PyObject* object) { + int result = 0; + PyObject* funcCode = PyObject_GetAttrString(object, "__code__"); + if (funcCode) { + PyObject* argCount = PyObject_GetAttrString(funcCode, "co_argcount"); + if (argCount) { + result = SIPLong_AsLong(argCount); + Py_DECREF(argCount); + } + Py_DECREF(funcCode); + } + return result; + } + } + + template + struct Functor_converter; + + template + struct Functor_converter + { + + struct FunctorWrapper + { + FunctorWrapper(boost::python::object callable) : m_Callable(callable) { + } + + ~FunctorWrapper() { + GILock lock; + m_Callable = bpy::object(); + } + + RET operator()(const PARAMS&...params) { + GILock lock; + if constexpr (std::is_same_v) { + m_Callable(params...); + } + else { + return bpy::extract(m_Callable(params...)); + } + } + + boost::python::object m_Callable; + }; + + static void* convertible(PyObject* object) + { + if (!PyCallable_Check(object) + || (getArgCount(object) != sizeof...(PARAMS))) { + return nullptr; + } + return object; + } + + static void construct(PyObject* object, bpy::converter::rvalue_from_python_stage1_data* data) + { + bpy::object callable(bpy::handle<>(bpy::borrowed(object))); + void* storage = ((bpy::converter::rvalue_from_python_storage>*)data)->storage.bytes; + new (storage) std::function(FunctorWrapper(callable)); + data->convertible = storage; + } + }; + + /** + * @brief Call policy that automatically downcast shared pointer of type FromType + * to shared pointer of type ToType. + */ + template + struct DowncastConverter { + + bool convertible() const { return true; } + + inline PyObject* operator()(std::shared_ptr p) const { + if (p == nullptr) { + return bpy::detail::none(); + } + else { + auto downcast_p = std::dynamic_pointer_cast(p); + bpy::object p_value = downcast_p == nullptr ? bpy::object{ p } : bpy::object{ downcast_p }; + return bpy::incref(p_value.ptr()); + } + } + + inline PyTypeObject const* get_pytype() const { + return bpy::converter::registered_pytype::get_pytype(); + } + + }; + + template + struct downcast_return { + + template + struct apply_; + + template + struct apply_> { + static_assert(std::is_convertible_v, std::shared_ptr>); + using type = DowncastConverter; + }; + + template + using apply = apply_>; + + }; + + // Functions: + inline void register_qstring_converter() { + using namespace QString_converter; + bpy::to_python_converter(); + bpy::converter::registry::push_back( + &QString_from_python_str::convertible, + &QString_from_python_str::construct, + bpy::type_id()); + } + + inline void register_qvariant_converter() { + using namespace QVariant_converter; + bpy::to_python_converter(); + bpy::converter::registry::push_back( + &QVariant_from_python_obj::convertible, + &QVariant_from_python_obj::construct, + bpy::type_id()); + } + + template + inline void register_qflags_converter() { + using T = typename Flags::enum_type; + using namespace QFlags_converter; + bpy::to_python_converter>(); + bpy::converter::registry::push_back( + &QFlags_from_python_obj::convertible, + &QFlags_from_python_obj::construct, + bpy::type_id()); + } + + template + inline void register_qclass_converter() { + using Converter = QClass_converter::QClass_converters; + bpy::converter::registry::insert(&Converter::QClass_from_PyQt, bpy::type_id()); + bpy::to_python_converter(); + bpy::to_python_converter(); + bpy::to_python_converter(); + } + + template + inline void register_functor_converter() { + using Converter = Functor_converter; + bpy::converter::registry::push_back( + &Converter::convertible, + &Converter::construct, + bpy::type_id>()); + } + + + +} + +#endif \ No newline at end of file diff --git a/src/runner/pythonrunner.cpp b/src/runner/pythonrunner.cpp index 97e8f8c..c9bc844 100644 --- a/src/runner/pythonrunner.cpp +++ b/src/runner/pythonrunner.cpp @@ -20,20 +20,17 @@ #include #include -// sip and qt slots seems to conflict -#include - #include #include #ifndef Q_MOC_RUN #include #include +#endif #include "tuple_helper.h" #include "variant_helper.h" -#include "pythonutils.h" -#endif +#include "converters.h" MOBase::IOrganizer *s_Organizer = nullptr; @@ -44,535 +41,11 @@ using namespace MOBase; namespace bpy = boost::python; namespace mp11 = boost::mp11; - -class PythonRunner : public IPythonRunner -{ - -public: - PythonRunner(const MOBase::IOrganizer *moInfo); - bool initPython(const QString &pythonDir); - QList instantiate(const QString &pluginName); - bool isPythonInstalled() const; - bool isPythonVersionSupported() const; - -private: - - void initPath(); - - /** - * @brief Append the underlying object of the given python object to the - * interface list if it is an instance (pointer) of the given type. - * - * @param obj The object to check. - * @param interfaces The list to append the object to. - * - */ - template - void appendIfInstance(bpy::object const& obj, QList &interfaces); - -private: - std::map m_PythonObjects; - const MOBase::IOrganizer *m_MOInfo; - wchar_t *m_PythonHome; -}; - - -IPythonRunner *CreatePythonRunner(MOBase::IOrganizer *moInfo, const QString &pythonDir) -{ - s_Organizer = moInfo; - PythonRunner *result = new PythonRunner(moInfo); - if (result->initPython(pythonDir)) { - return result; - } else { - delete result; - return nullptr; - } -} - -struct QString_to_python_str -{ - static PyObject *convert(const QString &str) { - // It's safer to explicitly convert to unicode as if we don't, this can return either str or unicode without it being easy to know which to expect - bpy::object pyStr = bpy::object(qUtf8Printable(str)); - if (SIPBytes_Check(pyStr.ptr())) - pyStr = pyStr.attr("decode")("utf-8"); - return bpy::incref(pyStr.ptr()); - } -}; - -template -struct QFlags_to_int -{ - static PyObject *convert(const QFlags &flags) { - return bpy::incref(bpy::object(static_cast(flags)).ptr()); - } -}; - - -struct QString_from_python_str -{ - QString_from_python_str() { - bpy::converter::registry::push_back(&convertible, &construct, bpy::type_id()); - } - - static void *convertible(PyObject *objPtr) { - return SIPBytes_Check(objPtr) || PyUnicode_Check(objPtr) ? objPtr : nullptr; - } - - static void construct(PyObject *objPtr, bpy::converter::rvalue_from_python_stage1_data *data) { - // Ensure the string uses 8-bit characters - PyObject *strPtr = PyUnicode_Check(objPtr) ? PyUnicode_AsUTF8String(objPtr) : objPtr; - - // Extract the character data from the python string - const char* value = SIPBytes_AsString(strPtr); - assert(value != nullptr); - - // allocate storage - void *storage = ((bpy::converter::rvalue_from_python_storage*)data)->storage.bytes; - - // construct QString in the allocated memory - new (storage) QString(value); - - data->convertible = storage; - - // Deallocate local copy if one was made - if (strPtr != objPtr) - Py_DecRef(strPtr); - } -}; - - -struct QVariant_to_python_obj -{ - static PyObject *convert(const QVariant &var) { - switch (var.type()) { - case QVariant::Invalid: return bpy::incref(Py_None); - case QVariant::Int: return SIPLong_FromLong(var.toInt()); - case QVariant::UInt: return PyLong_FromUnsignedLong(var.toUInt()); - case QVariant::Bool: return PyBool_FromLong(var.toBool()); - case QVariant::String: return bpy::incref(bpy::object(var.toString()).ptr()); - case QVariant::List: { - return bpy::incref(bpy::object(var.toList()).ptr()); - } break; - case QVariant::Map: { - return bpy::incref(bpy::object(var.toMap()).ptr()); - } break; - default: { - PyErr_Format(PyExc_TypeError, "type unsupported: %d", var.type()); - throw bpy::error_already_set(); - } break; - } - } -}; - - -struct QVariant_from_python_obj -{ - QVariant_from_python_obj() { - bpy::converter::registry::push_back(&convertible, &construct, bpy::type_id()); - } - - static void *convertible(PyObject *objPtr) { - if (!SIPBytes_Check(objPtr) && !PyUnicode_Check(objPtr) && !PyLong_Check(objPtr) && - !PyBool_Check(objPtr) && !PyList_Check(objPtr) && !PyDict_Check(objPtr) && - objPtr != Py_None) { - return nullptr; - } - return objPtr; - } - - template - static void constructVariant(const T &value, bpy::converter::rvalue_from_python_stage1_data *data) { - void* storage = ((bpy::converter::rvalue_from_python_storage*)data)->storage.bytes; - - new (storage) QVariant(value); - - data->convertible = storage; - } - - static void constructVariant(bpy::converter::rvalue_from_python_stage1_data *data) { - void* storage = ((bpy::converter::rvalue_from_python_storage*)data)->storage.bytes; - - new (storage) QVariant(); - - data->convertible = storage; - } - - static void construct(PyObject *objPtr, bpy::converter::rvalue_from_python_stage1_data *data) { - // PyBools will also return true for SIPLong_Check but not the other way around, so the order - // here is relevant - if (PyList_Check(objPtr)) { - constructVariant(bpy::extract(objPtr)(), data); - } else if (objPtr == Py_None) { - constructVariant(data); - } else if (PyDict_Check(objPtr)) { - constructVariant(bpy::extract(objPtr)(), data); - } else if (SIPBytes_Check(objPtr) || PyUnicode_Check(objPtr)) { - constructVariant(bpy::extract(objPtr)(), data); - } else if (PyBool_Check(objPtr)) { - constructVariant(bpy::extract(objPtr)(), data); - } else if (SIPLong_Check(objPtr)) { - //QVariant doesn't have long. It has int or long long. Given that on m/s, - //long is 32 bits for 32- and 64- bit code... - constructVariant(bpy::extract(objPtr)(), data); - } else { - PyErr_SetString(PyExc_TypeError, "type unsupported"); - throw bpy::error_already_set(); - } - } -}; - -template -struct QFlags_from_python_obj -{ - QFlags_from_python_obj() { - bpy::converter::registry::push_back( - &convertible, - &construct, - bpy::type_id>()); - } - - static void* convertible(PyObject *objPtr) { - return SIPLong_Check(objPtr) ? objPtr : nullptr; - } - - static void construct(PyObject *objPtr, bpy::converter::rvalue_from_python_stage1_data *data) { - int intVersion = (int)SIPLong_AsLong(objPtr); - T tVersion = (T)intVersion; - void *storage = ((bpy::converter::rvalue_from_python_storage> *)data)->storage.bytes; - new (storage) QFlags(tVersion); - - data->convertible = storage; - } -}; - - -struct IModRepositoryBridge_to_python -{ - static PyObject *convert(IModRepositoryBridge *bridge) - { - ModRepositoryBridgeWrapper wrapper(bridge); - - return bpy::incref(bpy::object(wrapper).ptr()); - } -}; - - - -template struct MetaData; - -template <> struct MetaData { static const char *className() { return "QObject"; } }; -template <> struct MetaData { static const char *className() { return "QObject"; } }; -template <> struct MetaData { static const char *className() { return "QObject"; } }; -template <> struct MetaData { static const char *className() { return "QWidget"; } }; -template <> struct MetaData { static const char *className() { return "QDateTime"; } }; -template <> struct MetaData { static const char *className() { return "QDir"; } }; -template <> struct MetaData { static const char *className() { return "QFileInfo"; } }; -template <> struct MetaData { static const char *className() { return "QIcon"; } }; -template <> struct MetaData { static const char *className() { return "QSize"; } }; -template <> struct MetaData { static const char *className() { return "QStringList"; } }; -template <> struct MetaData { static const char *className() { return "QUrl"; } }; -template <> struct MetaData { static const char *className() { return "QVariant"; } }; - - -template -PyObject *toPyQt(T *objPtr) -{ - if (objPtr == nullptr) { - qDebug("no input object"); - return bpy::incref(Py_None); - } - const sipTypeDef *type = sipAPIAccess::sipAPI()->api_find_type(MetaData::className()); - - if (type == nullptr) { - qDebug("failed to determine type: %s", MetaData::className()); - return bpy::incref(Py_None); - } - - PyObject *sipObj = sipAPIAccess::sipAPI()->api_convert_from_type(objPtr, type, 0); - if (sipObj == nullptr) { - qDebug("failed to convert"); - return bpy::incref(Py_None); - } - return bpy::incref(sipObj); -} - - -template -struct QClass_converters -{ - struct QClass_to_PyQt - { - template - static typename std::enable_if_t, T*> getSafeCopy(T *qClass) - { - return new T(*qClass); - } - - template - static typename std::enable_if_t, T*> getSafeCopy(T *qClass) - { - return qClass; - } - - static PyObject *convert(const T &object) { - const sipTypeDef *type = sipAPIAccess::sipAPI()->api_find_type(MetaData::className()); - if (type == nullptr) { - return bpy::incref(Py_None); - } - - PyObject *sipObj = sipAPIAccess::sipAPI()->api_convert_from_type((void*)getSafeCopy((T*)&object), type, 0); - if (sipObj == nullptr) { - return bpy::incref(Py_None); - } - - if (std::is_copy_constructible_v) - // Ensure Python deletes the C++ component - sipAPIAccess::sipAPI()->api_transfer_back(sipObj); - - return bpy::incref(sipObj); - } - - static PyObject *convert(T *object) { - if (object == nullptr) { - return bpy::incref(Py_None); - } - - const sipTypeDef *type = sipAPIAccess::sipAPI()->api_find_type(MetaData::className()); - if (type == nullptr) { - return bpy::incref(Py_None); - } - - PyObject *sipObj = sipAPIAccess::sipAPI()->api_convert_from_type(getSafeCopy(object), type, 0); - if (sipObj == nullptr) { - return bpy::incref(Py_None); - } - - if (std::is_copy_constructible_v) - // Ensure Python deletes the C++ component - sipAPIAccess::sipAPI()->api_transfer_back(sipObj); - - return bpy::incref(sipObj); - } - - static PyObject *convert(const T *object) { - return convert((T*)object); - } - }; - - static void *QClass_from_PyQt(PyObject *objPtr) - { - // This would transfer responsibility for deconstructing the object to C++, but Boost assumes l-value converters (such as this) don't do that - // Instead, this should be called within the wrappers for functions which return deletable pointers. - //sipAPI()->api_transfer_to(objPtr, Py_None); - if (PyObject_TypeCheck(objPtr, sipAPIAccess::sipAPI()->api_simplewrapper_type)) { - sipSimpleWrapper *wrapper; - wrapper = reinterpret_cast(objPtr); - return wrapper->data; - } else if (PyObject_TypeCheck(objPtr, sipAPIAccess::sipAPI()->api_wrapper_type)) { - sipWrapper *wrapper; - wrapper = reinterpret_cast(objPtr); - return wrapper->super.data; - } else { - if constexpr (std::is_same_v) - { - // QStringLists aren't wrapped by PyQt - regular Python string/unicode lists are used instead - bpy::extract> extractor(objPtr); - if (extractor.check()) - return new QStringList(extractor()); - } - PyErr_SetString(PyExc_TypeError, "type not wrapped"); - bpy::throw_error_already_set(); - } - return new void*; - } - - QClass_converters() - { - bpy::converter::registry::insert(&QClass_from_PyQt, bpy::type_id()); - bpy::to_python_converter(); - bpy::to_python_converter(); - bpy::to_python_converter(); - } -}; - - -template -struct QInterface_converters -{ - struct QInterface_to_PyQt - { - static PyObject *convert(const T &object) { - const sipTypeDef *type = sipAPIAccess::sipAPI()->api_find_type(MetaData::className()); - if (type == nullptr) { - return bpy::incref(Py_None); - } - - PyObject *sipObj = sipAPIAccess::sipAPI()->api_convert_from_type((void*)(&object), type, 0); - if (sipObj == nullptr) { - return bpy::incref(Py_None); - } - - return bpy::incref(sipObj); - } - - static PyObject *convert(T *object) { - if (object == nullptr) { - return bpy::incref(Py_None); - } - - const sipTypeDef *type = sipAPIAccess::sipAPI()->api_find_type(MetaData::className()); - if (type == nullptr) { - return bpy::incref(Py_None); - } - - PyObject *sipObj = sipAPIAccess::sipAPI()->api_convert_from_type(object, type, 0); - if (sipObj == nullptr) { - return bpy::incref(Py_None); - } - - return bpy::incref(sipObj); - } - - static PyObject *convert(const T *object) { - return convert((T*)object); - } - }; - - static void *QInterface_from_PyQt(PyObject *objPtr) - { - if (!PyObject_TypeCheck(objPtr, sipAPIAccess::sipAPI()->api_wrapper_type)) { - bpy::throw_error_already_set(); - } - - // This would transfer responsibility for deconstructing the object to C++, but Boost assumes l-value converters (such as this) don't do that - // Instead, this should be called within the wrappers for functions which return deletable pointers. - //sipAPI()->api_transfer_to(objPtr, Py_None); - - sipSimpleWrapper *wrapper = reinterpret_cast(objPtr); - return wrapper->data; - } - - QInterface_converters() - { - bpy::converter::registry::insert(&QInterface_from_PyQt, bpy::type_id()); - bpy::to_python_converter(); - bpy::to_python_converter(); - } -}; - - -int getArgCount(PyObject *object) { - int result = 0; - PyObject *funcCode = PyObject_GetAttrString(object, "__code__"); - if (funcCode) { - PyObject *argCount = PyObject_GetAttrString(funcCode, "co_argcount"); - if(argCount) { - result = SIPLong_AsLong(argCount); - Py_DECREF(argCount); - } - Py_DECREF(funcCode); - } - return result; -} - -template -struct Functor_converter; - - -template -struct Functor_converter -{ - - struct FunctorWrapper - { - FunctorWrapper(boost::python::object callable) : m_Callable(callable) { - } - - ~FunctorWrapper() { - GILock lock; - m_Callable = bpy::object(); - } - - RET operator()(const PARAMS &...params) { - GILock lock; - if constexpr (std::is_same_v) { - m_Callable(params...); - } - else { - return bpy::extract(m_Callable(params...)); - } - } - - boost::python::object m_Callable; - }; - - Functor_converter() - { - bpy::converter::registry::push_back(&convertible, &construct, bpy::type_id>()); - } - - static void *convertible(PyObject *object) - { - if (!PyCallable_Check(object) - || (getArgCount(object) != sizeof...(PARAMS))) { - return nullptr; - } - return object; - } - - static void construct(PyObject *object, bpy::converter::rvalue_from_python_stage1_data *data) - { - bpy::object callable(bpy::handle<>(bpy::borrowed(object))); - void *storage = ((bpy::converter::rvalue_from_python_storage>*)data)->storage.bytes; - new (storage) std::function(FunctorWrapper(callable)); - data->convertible = storage; - } -}; - - -/** - * @brief Call policy that automatically downcast shared pointer of type FromType - * to shared pointer of type ToType. - */ -template -struct DowncastConverter { - - bool convertible() const { return true; } - - inline PyObject* operator()(std::shared_ptr p) const { - if (p == nullptr) { - return bpy::detail::none(); - } - else { - auto downcast_p = std::dynamic_pointer_cast(p); - bpy::object p_value = downcast_p == nullptr ? bpy::object{ p } : bpy::object{ downcast_p }; - return bpy::incref(p_value.ptr()); - } - } - - inline PyTypeObject const* get_pytype() const { - return bpy::converter::registered_pytype::get_pytype(); - } - -}; - -template -struct DowncastReturn { - - template - struct apply_; - - template - struct apply_> { - static_assert(std::is_convertible_v, std::shared_ptr>); - using type = DowncastConverter; - }; - - template - using apply = apply_>; - -}; +#define Q_DELEGATE(Class, QClass, Name) \ + .def(Name, +[](Class* w) -> QClass* { return w; }, bpy::return_value_policy()) \ + .def("__getattr__", +[](Class* w, bpy::str str) -> bpy::object { \ + return bpy::object{ (QClass*)w }.attr(str); \ + }) BOOST_PYTHON_MODULE(mobase) @@ -580,23 +53,24 @@ BOOST_PYTHON_MODULE(mobase) PyEval_InitThreads(); bpy::import("PyQt5.QtCore"); + bpy::import("PyQt5.QtWidgets"); - bpy::to_python_converter(); - QVariant_from_python_obj(); + utils::register_qstring_converter(); + utils::register_qvariant_converter(); - bpy::to_python_converter(); - QString_from_python_str(); + utils::register_qclass_converter(); + utils::register_qclass_converter(); + utils::register_qclass_converter(); + utils::register_qclass_converter(); + utils::register_qclass_converter(); + utils::register_qclass_converter(); + utils::register_qclass_converter(); + utils::register_qclass_converter(); + utils::register_qclass_converter(); - //QClass_converters(); - QClass_converters(); - QClass_converters(); - QClass_converters(); - QClass_converters(); - QClass_converters(); - QClass_converters(); - QClass_converters(); - QClass_converters(); - QInterface_converters(); + // QFlags: + utils::register_qflags_converter(); + utils::register_qflags_converter(); // Pointers: bpy::register_ptr_to_python>(); @@ -635,18 +109,18 @@ BOOST_PYTHON_MODULE(mobase) bpy::register_variant>(); // Functions: - Functor_converter(); // converter for the onRefreshed-callback - Functor_converter(); - Functor_converter(); - Functor_converter(); // converter for the onModStateChanged-callback - Functor_converter(); - Functor_converter(); - Functor_converter const&)>(); - Functor_converter(QString const&)>(); + utils::register_functor_converter(); // converter for the onRefreshed-callback + utils::register_functor_converter(); + utils::register_functor_converter(); + utils::register_functor_converter(); // converter for the onModStateChanged-callback + utils::register_functor_converter(); + utils::register_functor_converter(); + utils::register_functor_converter const&)>(); + utils::register_functor_converter(QString const&)>(); - - bpy::def("toPyQt", &toPyQt); - bpy::def("toPyQt", &toPyQt); + // + // Class declarations: + // bpy::enum_("ReleaseType") .value("final", MOBase::VersionInfo::RELEASE_FINAL) @@ -719,11 +193,8 @@ BOOST_PYTHON_MODULE(mobase) // that __getattr__ is only called if the attribute is not found in the class by standard mean). bpy::class_, ISaveGameInfoWidgetWrapper*, boost::noncopyable>("ISaveGameInfoWidget", bpy::init>()) .def("setSave", bpy::pure_virtual(&ISaveGameInfoWidget::setSave)) - .def("__getattr__", +[](ISaveGameInfoWidget *w, bpy::str str) -> bpy::object { - // Create an object corresponding to the widget: - bpy::object obj{ (QWidget*)w }; - return obj.attr(str); - }) + + Q_DELEGATE(ISaveGameInfoWidget, QWidget, "_widget") ; bpy::class_("FileInfo", bpy::init<>()) @@ -849,7 +320,7 @@ BOOST_PYTHON_MODULE(mobase) // special python methods): .def("exists", static_cast(&IFileTree::exists), (bpy::arg("type") = IFileTree::FILE_OR_DIRECTORY)) .def("find", static_cast(IFileTree::*)(QString, IFileTree::FileTypes)>(&IFileTree::find), - bpy::arg("type") = IFileTree::FILE_OR_DIRECTORY, bpy::return_value_policy>(), "[optional]") + bpy::arg("type") = IFileTree::FILE_OR_DIRECTORY, bpy::return_value_policy>(), "[optional]") .def("pathTo", &IFileTree::pathTo, bpy::arg("sep") = "\\") // Kind-of-static operations: @@ -885,8 +356,8 @@ BOOST_PYTHON_MODULE(mobase) // Special methods: .def("__getitem__", static_cast(IFileTree::*)(std::size_t)>(&IFileTree::at), - bpy::return_value_policy>()) - .def("__iter__", bpy::range>>( + bpy::return_value_policy>()) + .def("__iter__", bpy::range>>( static_cast(&IFileTree::begin), static_cast(&IFileTree::end))) .def("__len__", &IFileTree::size) @@ -915,6 +386,8 @@ BOOST_PYTHON_MODULE(mobase) .def("onDescriptionAvailable", &ModRepositoryBridgeWrapper::onDescriptionAvailable) .def("onEndorsementToggled", &ModRepositoryBridgeWrapper::onEndorsementToggled) .def("onRequestFailed", &ModRepositoryBridgeWrapper::onRequestFailed) + + Q_DELEGATE(IModRepositoryBridge, QObject, "_object") ; bpy::class_("IModRepositoryBridge") @@ -952,6 +425,8 @@ BOOST_PYTHON_MODULE(mobase) .def("startDownloadURLs", bpy::pure_virtual(&IDownloadManager::startDownloadURLs)) .def("startDownloadNexusFile", bpy::pure_virtual(&IDownloadManager::startDownloadNexusFile)) .def("downloadPath", bpy::pure_virtual(&IDownloadManager::downloadPath)) + + Q_DELEGATE(IDownloadManager, QObject, "_object") ; utils::register_sequence_container>>(); @@ -1023,9 +498,6 @@ BOOST_PYTHON_MODULE(mobase) .def("__str__", &MOBase::GuessedValue::operator const QString&, bpy::return_value_policy()) ; - bpy::to_python_converter>(); - QFlags_from_python_obj(); - bpy::enum_("PluginState") .value("missing", IPluginList::STATE_MISSING) .value("inactive", IPluginList::STATE_INACTIVE) @@ -1046,9 +518,6 @@ BOOST_PYTHON_MODULE(mobase) .def("setLoadOrder", bpy::pure_virtual(&MOBase::IPluginList::setLoadOrder)) ; - bpy::to_python_converter>(); - QFlags_from_python_obj(); - bpy::enum_("ModState") .value("exists", IModList::STATE_EXISTS) .value("active", IModList::STATE_ACTIVE) @@ -1120,9 +589,6 @@ BOOST_PYTHON_MODULE(mobase) .value("preferDefaults", MOBase::IPluginGame::PREFER_DEFAULTS) ; - bpy::to_python_converter>(); - QFlags_from_python_obj(); - bpy::class_, boost::noncopyable>("IPluginGame") .def("gameName", bpy::pure_virtual(&MOBase::IPluginGame::gameName)) .def("initializeProfile", bpy::pure_virtual(&MOBase::IPluginGame::initializeProfile)) @@ -1252,6 +718,52 @@ BOOST_PYTHON_MODULE(mobase) registerGameFeaturesPythonConverters(); } +/** + * + */ +class PythonRunner : public IPythonRunner +{ + +public: + PythonRunner(const MOBase::IOrganizer* moInfo); + bool initPython(const QString& pythonDir); + QList instantiate(const QString& pluginName); + bool isPythonInstalled() const; + bool isPythonVersionSupported() const; + +private: + + void initPath(); + + /** + * @brief Append the underlying object of the given python object to the + * interface list if it is an instance (pointer) of the given type. + * + * @param obj The object to check. + * @param interfaces The list to append the object to. + * + */ + template + void appendIfInstance(bpy::object const& obj, QList& interfaces); + +private: + std::map m_PythonObjects; + const MOBase::IOrganizer* m_MOInfo; + wchar_t* m_PythonHome; +}; + +IPythonRunner* CreatePythonRunner(MOBase::IOrganizer* moInfo, const QString& pythonDir) +{ + s_Organizer = moInfo; + PythonRunner* result = new PythonRunner(moInfo); + if (result->initPython(pythonDir)) { + return result; + } + else { + delete result; + return nullptr; + } +} PythonRunner::PythonRunner(const MOBase::IOrganizer *moInfo) : m_MOInfo(moInfo) From e9fdfd40aa9a7032c75382dbcd0fa688762d3085 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mika=C3=ABl=20Capelle?= Date: Mon, 11 May 2020 23:02:08 +0200 Subject: [PATCH 09/35] Add comment for Q_DELEGATE. --- src/runner/pythonrunner.cpp | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/runner/pythonrunner.cpp b/src/runner/pythonrunner.cpp index c9bc844..7d4aa6b 100644 --- a/src/runner/pythonrunner.cpp +++ b/src/runner/pythonrunner.cpp @@ -41,6 +41,14 @@ using namespace MOBase; namespace bpy = boost::python; namespace mp11 = boost::mp11; +/** + * This macro should be used within a bpy::class_ declaration and will define two + * methods: __getattr__ and Name, where Name will simply return the object as a QClass* + * object, while __getattr__ will delegate to the underlying QClass object when required. + * + * This allow access to Qt interface for object exposed using boost::python (e.g., signals, + * methods from QObject or QWidget, etc.). + */ #define Q_DELEGATE(Class, QClass, Name) \ .def(Name, +[](Class* w) -> QClass* { return w; }, bpy::return_value_policy()) \ .def("__getattr__", +[](Class* w, bpy::str str) -> bpy::object { \ From 83a89ca5951557eb7f47b5a79b7ef7c21dc81eec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mika=C3=ABl=20Capelle?= Date: Mon, 11 May 2020 23:16:51 +0200 Subject: [PATCH 10/35] Move utils::register_ at the top of the module definition. --- 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 7d4aa6b..c71def2 100644 --- a/src/runner/pythonrunner.cpp +++ b/src/runner/pythonrunner.cpp @@ -93,6 +93,7 @@ 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>(); @@ -437,7 +438,6 @@ BOOST_PYTHON_MODULE(mobase) Q_DELEGATE(IDownloadManager, QObject, "_object") ; - utils::register_sequence_container>>(); bpy::class_("IInstallationManager", bpy::no_init) .def("extractFile", &IInstallationManager::extractFile) .def("extractFiles", &IInstallationManager::extractFiles) From 1825c586669cf759f50219aa83b6a2e7c24c1078 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mika=C3=ABl=20Capelle?= Date: Mon, 11 May 2020 23:18:44 +0200 Subject: [PATCH 11/35] Remove comment. --- src/runner/pythonrunner.cpp | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/src/runner/pythonrunner.cpp b/src/runner/pythonrunner.cpp index c71def2..3aaf328 100644 --- a/src/runner/pythonrunner.cpp +++ b/src/runner/pythonrunner.cpp @@ -194,12 +194,7 @@ BOOST_PYTHON_MODULE(mobase) .def("hasScriptExtenderFile", bpy::pure_virtual(&ISaveGame::hasScriptExtenderFile)) ; - // This is tricky because there is no way to tell boost::python that ISaveGameInfoWidget inherits - // QWidget (bpy::bases crashes when loading the module... ). Without it, the python class - // does not expose the QWidget methods, which makes it useless. - // There is two way to do this: 1) expose the widget via a `_widget()` method that basically returns - // the object, but as a QWidget, or 2) override __getattr__ to forward everything to the QWidget (note - // that __getattr__ is only called if the attribute is not found in the class by standard mean). + // See Q_DELEGATE for more details. bpy::class_, ISaveGameInfoWidgetWrapper*, boost::noncopyable>("ISaveGameInfoWidget", bpy::init>()) .def("setSave", bpy::pure_virtual(&ISaveGameInfoWidget::setSave)) From 6b88cee9d893946acad0fa4ff4795712839b426e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mika=C3=ABl=20Capelle?= Date: Mon, 11 May 2020 23:26:06 +0200 Subject: [PATCH 12/35] Remove useless includes. --- src/runner/converters.h | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/runner/converters.h b/src/runner/converters.h index 6fa50e4..988f839 100644 --- a/src/runner/converters.h +++ b/src/runner/converters.h @@ -11,9 +11,6 @@ // sip and qt slots seems to conflict #include -#include "idownloadmanager.h" -#include "imodrepositorybridge.h" - // Include the container converters from utils: #include "pythonutils.h" From f0f949c4ef94fa67c0b76777c2e8034bfc0c4e37 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mika=C3=ABl=20Capelle?= Date: Mon, 11 May 2020 23:26:29 +0200 Subject: [PATCH 13/35] Remove ModRepositoryBridgeWrapper since it is not needed anymore. --- src/runner/pythonrunner.cpp | 18 ++-- src/runner/uibasewrappers.h | 201 +----------------------------------- 2 files changed, 8 insertions(+), 211 deletions(-) diff --git a/src/runner/pythonrunner.cpp b/src/runner/pythonrunner.cpp index 3aaf328..c503221 100644 --- a/src/runner/pythonrunner.cpp +++ b/src/runner/pythonrunner.cpp @@ -379,17 +379,13 @@ BOOST_PYTHON_MODULE(mobase) .def("invalidationActive", bpy::pure_virtual(&IProfile::invalidationActive)) ; - bpy::class_("ModRepositoryBridge") - .def(bpy::init()) - .def("requestDescription", &ModRepositoryBridgeWrapper::requestDescription) - .def("requestFiles", &ModRepositoryBridgeWrapper::requestFiles) - .def("requestFileInfo", &ModRepositoryBridgeWrapper::requestFileInfo) - .def("requestToggleEndorsement", &ModRepositoryBridgeWrapper::requestToggleEndorsement) - .def("onFilesAvailable", &ModRepositoryBridgeWrapper::onFilesAvailable) - .def("onFileInfoAvailable", &ModRepositoryBridgeWrapper::onFileInfoAvailable) - .def("onDescriptionAvailable", &ModRepositoryBridgeWrapper::onDescriptionAvailable) - .def("onEndorsementToggled", &ModRepositoryBridgeWrapper::onEndorsementToggled) - .def("onRequestFailed", &ModRepositoryBridgeWrapper::onRequestFailed) + bpy::class_("ModRepositoryBridge") + .def(bpy::init>()) + .def("requestDescription", &IModRepositoryBridge::requestDescription) + .def("requestFiles", &IModRepositoryBridge::requestFiles) + .def("requestFileInfo", &IModRepositoryBridge::requestFileInfo) + .def("requestDownloadURL", &IModRepositoryBridge::requestDownloadURL) + .def("requestToggleEndorsement", &IModRepositoryBridge::requestToggleEndorsement) Q_DELEGATE(IModRepositoryBridge, QObject, "_object") ; diff --git a/src/runner/uibasewrappers.h b/src/runner/uibasewrappers.h index 3af6b4c..6a1419a 100644 --- a/src/runner/uibasewrappers.h +++ b/src/runner/uibasewrappers.h @@ -31,206 +31,6 @@ extern MOBase::IOrganizer *s_Organizer; -using MOBase::ModRepositoryFileInfo; - -/** - * @brief Wrapper class for the bridge to a mod repository. Awkward: This may be - * unnecessary but I didn't manage to figure out how to correctly connect python - * code to C++ signals - */ -class ModRepositoryBridgeWrapper : public QObject -{ - Q_OBJECT -public: - - ModRepositoryBridgeWrapper() - : m_Wrapped(s_Organizer->createNexusBridge()) - { - } - - ModRepositoryBridgeWrapper(MOBase::IModRepositoryBridge *wrapped) - : m_Wrapped(wrapped) - { - } - - ~ModRepositoryBridgeWrapper() - { - delete m_Wrapped; - } - - void requestDescription(QString gameName, int modID, QVariant userData) - { m_Wrapped->requestDescription(gameName, modID, userData); } - void requestFiles(QString gameName, int modID, QVariant userData) - { m_Wrapped->requestFiles(gameName, modID, userData); } - void requestFileInfo(QString gameName, int modID, int fileID, QVariant userData) - { m_Wrapped->requestFileInfo(gameName, modID, fileID, userData); } - void requestToggleEndorsement(QString gameName, int modID, QString modVersion, bool endorse, QVariant userData) - { m_Wrapped->requestToggleEndorsement(gameName, modID, modVersion, endorse, userData); } - - void onFilesAvailable(boost::python::object callback) { - m_FilesAvailableHandler = callback; - connect(m_Wrapped, SIGNAL(filesAvailable(int,QVariant,const QList&)), - this, SLOT(filesAvailable(int,QVariant,const QList&)), - Qt::UniqueConnection); - } - - void onDescriptionAvailable(boost::python::object callback) { - m_DescriptionAvailableHandler = callback; - connect(m_Wrapped, SIGNAL(descriptionAvailable(int,QVariant,QVariant)), - this, SLOT(descriptionAvailable(int,QVariant,QVariant)), - Qt::UniqueConnection); - } - - void onFileInfoAvailable(boost::python::object callback) { - m_FileInfoHandler = callback; - connect(m_Wrapped, SIGNAL(fileInfoAvailable(int,int,QVariant,QVariant)), - this, SLOT(fileInfoAvailable(int,int,QVariant,QVariant)), - Qt::UniqueConnection); - } - - void onEndorsementToggled(boost::python::object callback) { - m_EndorsementToggledHandler = callback; - connect(m_Wrapped, SIGNAL(endorsementToggled(int,QVariant,QVariant)), - this, SLOT(endorsementToggled(int,QVariant,QVariant)), - Qt::UniqueConnection); - } - - void onTrackingToggled(boost::python::object callback) { - m_TrackingToggledHandler = callback; - connect(m_Wrapped, SIGNAL(trackingToggled(int,QVariant,bool)), - this, SLOT(trackingToggled(int,QVariant,bool)), - Qt::UniqueConnection); - - } - - void onRequestFailed(boost::python::object callback) { - m_FailedHandler = callback; - connect(m_Wrapped, SIGNAL(requestFailed(int,int,QVariant,QString)), - this, SLOT(requestFailed(int,int,QVariant,QString)), - Qt::UniqueConnection); - } - -private: - - Q_DISABLE_COPY(ModRepositoryBridgeWrapper) - -private Q_SLOTS: - - void filesAvailable(int modID, QVariant userData, const QList &resultData) - { - if (m_FilesAvailableHandler.is_none()) { - qCritical("no handler connected"); - return; - } - try { - GILock lock; - m_FilesAvailableHandler(modID, userData, resultData); - } catch (const boost::python::error_already_set&) { - throw pyexcept::PythonError(); - } - } - - void descriptionAvailable(int modID, QVariant userData, const QVariant resultData) - { - try { - if (m_DescriptionAvailableHandler.is_none()) { - qCritical("no handler connected"); - return; - } - try { - GILock lock; - m_DescriptionAvailableHandler(modID, userData, resultData); - } catch (const boost::python::error_already_set&) { - throw pyexcept::PythonError(); - } - } catch (const std::exception &e) { - qCritical("failed to report event: %s", e.what()); - } catch (...) { - qCritical("failed to report event"); - } - } - - void fileInfoAvailable(int modID, int fileID, QVariant userData, const QVariant resultData) { - try { - if (m_FileInfoHandler.is_none()) { - qCritical("no handler connected"); - return; - } - try { - GILock lock; - m_FileInfoHandler(modID, fileID, userData, resultData); - } catch (const boost::python::error_already_set&) { - throw pyexcept::PythonError(); - } - } catch (const std::exception &e) { - qCritical("failed to report event: %s", e.what()); - } catch (...) { - qCritical("failed to report event"); - } - } - - void endorsementToggled(int modID, QVariant userData, const QVariant resultData) - { - try { - if (m_EndorsementToggledHandler.is_none()) { - qCritical("no handler connected"); - return; - } - try { - GILock lock; - m_EndorsementToggledHandler(modID, userData, resultData); - } catch (const boost::python::error_already_set&) { - throw pyexcept::PythonError(); - } - } catch (const std::exception &e) { - qCritical("failed to report event: %s", e.what()); - } catch (...) { - qCritical("failed to report event"); - } - } - - void trackingToggled(int modID, QVariant userData, bool tracked) - { - try { - if (m_TrackingToggledHandler.is_none()) { - qCritical("no handler connected"); - return; - } - try { - GILock lock; - m_TrackingToggledHandler(modID, userData, tracked); - } catch (const boost::python::error_already_set&) { - throw pyexcept::PythonError(); - } - } catch (const std::exception &e) { - qCritical("failed to report event: %s", e.what()); - } catch (...) { - qCritical("failed to report event"); - } - } - - void requestFailed(int modID, int fileID, QVariant userData, const QString &errorMessage) - { - try { - GILock lock; - m_FailedHandler(modID, fileID, userData, errorMessage); - } catch (const boost::python::error_already_set&) { - throw pyexcept::PythonError(); - } - } - -private: - - MOBase::IModRepositoryBridge *m_Wrapped; - boost::python::object m_FilesAvailableHandler; - boost::python::object m_DescriptionAvailableHandler; - boost::python::object m_FileInfoHandler; - boost::python::object m_EndorsementToggledHandler; - boost::python::object m_TrackingToggledHandler; - boost::python::object m_FailedHandler; - -}; - struct IProfileWrapper: MOBase::IProfile, boost::python::wrapper { virtual QString name() const override { return this->get_override("name")(); } @@ -249,6 +49,7 @@ struct IDownloadManagerWrapper: MOBase::IDownloadManager, boost::python::wrapper struct IModRepositoryBridgeWrapper: MOBase::IModRepositoryBridge, boost::python::wrapper { + using IModRepositoryBridge::IModRepositoryBridge; virtual void requestDescription(QString gameName, int modID, QVariant userData) { this->get_override("requestDescription")(gameName, modID, userData); } virtual void requestFiles(QString gameName, int modID, QVariant userData) { this->get_override("requestFiles")(gameName, modID, userData); } virtual void requestFileInfo(QString gameName, int modID, int fileID, QVariant userData) { this->get_override("requestFileInfo")(gameName, modID, fileID, userData); } From 24205bba665c945dac50b1d3aeab77072d7e52ef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mika=C3=ABl=20Capelle?= Date: Mon, 11 May 2020 23:36:35 +0200 Subject: [PATCH 14/35] Remove useless wrappers. --- src/runner/pythonrunner.cpp | 103 ++++++++++++++++-------------------- src/runner/uibasewrappers.h | 73 ------------------------- 2 files changed, 47 insertions(+), 129 deletions(-) diff --git a/src/runner/pythonrunner.cpp b/src/runner/pythonrunner.cpp index c503221..404f299 100644 --- a/src/runner/pythonrunner.cpp +++ b/src/runner/pythonrunner.cpp @@ -371,16 +371,15 @@ BOOST_PYTHON_MODULE(mobase) } - bpy::class_("IProfile") - .def("name", bpy::pure_virtual(&IProfile::name)) - .def("absolutePath", bpy::pure_virtual(&IProfile::absolutePath)) - .def("localSavesEnabled", bpy::pure_virtual(&IProfile::localSavesEnabled)) - .def("localSettingsEnabled", bpy::pure_virtual(&IProfile::localSettingsEnabled)) - .def("invalidationActive", bpy::pure_virtual(&IProfile::invalidationActive)) + bpy::class_("IProfile", bpy::no_init) + .def("name", &IProfile::name) + .def("absolutePath", &IProfile::absolutePath) + .def("localSavesEnabled", &IProfile::localSavesEnabled) + .def("localSettingsEnabled", &IProfile::localSettingsEnabled) + .def("invalidationActive", &IProfile::invalidationActive) ; - bpy::class_("ModRepositoryBridge") - .def(bpy::init>()) + bpy::class_("ModRepositoryBridge", bpy::no_init) .def("requestDescription", &IModRepositoryBridge::requestDescription) .def("requestFiles", &IModRepositoryBridge::requestFiles) .def("requestFileInfo", &IModRepositoryBridge::requestFileInfo) @@ -390,15 +389,7 @@ BOOST_PYTHON_MODULE(mobase) Q_DELEGATE(IModRepositoryBridge, QObject, "_object") ; - bpy::class_("IModRepositoryBridge") - .def("requestDescription", bpy::pure_virtual(&IModRepositoryBridge::requestDescription)) - .def("requestFiles", bpy::pure_virtual(&IModRepositoryBridge::requestFiles)) - .def("requestFileInfo", bpy::pure_virtual(&IModRepositoryBridge::requestFileInfo)) - .def("requestDownloadURL", bpy::pure_virtual(&IModRepositoryBridge::requestDownloadURL)) - .def("requestToggleEndorsement", bpy::pure_virtual(&IModRepositoryBridge::requestToggleEndorsement)) - ; - - bpy::class_("ModRepositoryFileInfo") + bpy::class_("ModRepositoryFileInfo", bpy::no_init) .def(bpy::init()) .def(bpy::init>()) .def("__str__", &ModRepositoryFileInfo::toString) @@ -421,10 +412,10 @@ BOOST_PYTHON_MODULE(mobase) .def_readwrite("userData", &ModRepositoryFileInfo::userData) ; - bpy::class_("IDownloadManager") - .def("startDownloadURLs", bpy::pure_virtual(&IDownloadManager::startDownloadURLs)) - .def("startDownloadNexusFile", bpy::pure_virtual(&IDownloadManager::startDownloadNexusFile)) - .def("downloadPath", bpy::pure_virtual(&IDownloadManager::downloadPath)) + bpy::class_("IDownloadManager", bpy::no_init) + .def("startDownloadURLs", &IDownloadManager::startDownloadURLs) + .def("startDownloadNexusFile", &IDownloadManager::startDownloadNexusFile) + .def("downloadPath", &IDownloadManager::downloadPath) Q_DELEGATE(IDownloadManager, QObject, "_object") ; @@ -436,20 +427,20 @@ BOOST_PYTHON_MODULE(mobase) .def("setURL", &IInstallationManager::setURL) ; - bpy::class_("IModInterface") - .def("name", bpy::pure_virtual(&IModInterface::name)) - .def("absolutePath", bpy::pure_virtual(&IModInterface::absolutePath)) - .def("setVersion", bpy::pure_virtual(&IModInterface::setVersion)) - .def("setNewestVersion", bpy::pure_virtual(&IModInterface::setNewestVersion)) - .def("setIsEndorsed", bpy::pure_virtual(&IModInterface::setIsEndorsed)) - .def("setNexusID", bpy::pure_virtual(&IModInterface::setNexusID)) - .def("addNexusCategory", bpy::pure_virtual(&IModInterface::addNexusCategory)) - .def("addCategory", bpy::pure_virtual(&IModInterface::addCategory)) - .def("removeCategory", bpy::pure_virtual(&IModInterface::removeCategory)) - .def("categories", bpy::pure_virtual(&IModInterface::categories)) - .def("setGamePlugin", bpy::pure_virtual(&IModInterface::setGamePlugin)) - .def("setName", bpy::pure_virtual(&IModInterface::setName)) - .def("remove", bpy::pure_virtual(&IModInterface::remove)) + bpy::class_("IModInterface", bpy::no_init) + .def("name", &IModInterface::name) + .def("absolutePath", &IModInterface::absolutePath) + .def("setVersion", &IModInterface::setVersion) + .def("setNewestVersion", &IModInterface::setNewestVersion) + .def("setIsEndorsed", &IModInterface::setIsEndorsed) + .def("setNexusID", &IModInterface::setNexusID) + .def("addNexusCategory", &IModInterface::addNexusCategory) + .def("addCategory", &IModInterface::addCategory) + .def("removeCategory", &IModInterface::removeCategory) + .def("categories", &IModInterface::categories) + .def("setGamePlugin", &IModInterface::setGamePlugin) + .def("setName", &IModInterface::setName) + .def("remove", &IModInterface::remove) ; bpy::enum_("GuessQuality") @@ -503,18 +494,18 @@ BOOST_PYTHON_MODULE(mobase) .value("active", IPluginList::STATE_ACTIVE) ; - bpy::class_("IPluginList") - .def("state", bpy::pure_virtual(&MOBase::IPluginList::state)) - .def("priority", bpy::pure_virtual(&MOBase::IPluginList::priority)) - .def("loadOrder", bpy::pure_virtual(&MOBase::IPluginList::loadOrder)) - .def("isMaster", bpy::pure_virtual(&MOBase::IPluginList::isMaster)) - .def("masters", bpy::pure_virtual(&MOBase::IPluginList::masters)) - .def("origin", bpy::pure_virtual(&MOBase::IPluginList::origin)) - .def("onRefreshed", bpy::pure_virtual(&MOBase::IPluginList::onRefreshed)) - .def("onPluginMoved", bpy::pure_virtual(&MOBase::IPluginList::onPluginMoved)) - .def("pluginNames", bpy::pure_virtual(&MOBase::IPluginList::pluginNames)) - .def("setState", bpy::pure_virtual(&MOBase::IPluginList::setState)) - .def("setLoadOrder", bpy::pure_virtual(&MOBase::IPluginList::setLoadOrder)) + bpy::class_("IPluginList", bpy::no_init) + .def("state", &MOBase::IPluginList::state) + .def("priority", &MOBase::IPluginList::priority) + .def("loadOrder", &MOBase::IPluginList::loadOrder) + .def("isMaster", &MOBase::IPluginList::isMaster) + .def("masters", &MOBase::IPluginList::masters) + .def("origin", &MOBase::IPluginList::origin) + .def("onRefreshed", &MOBase::IPluginList::onRefreshed) + .def("onPluginMoved", &MOBase::IPluginList::onPluginMoved) + .def("pluginNames", &MOBase::IPluginList::pluginNames) + .def("setState", &MOBase::IPluginList::setState) + .def("setLoadOrder", &MOBase::IPluginList::setLoadOrder) ; bpy::enum_("ModState") @@ -527,15 +518,15 @@ BOOST_PYTHON_MODULE(mobase) .value("alternate", IModList::STATE_ALTERNATE) ; - bpy::class_("IModList") - .def("displayName", bpy::pure_virtual(&MOBase::IModList::displayName)) - .def("allMods", bpy::pure_virtual(&MOBase::IModList::allMods)) - .def("state", bpy::pure_virtual(&MOBase::IModList::state)) - .def("setActive", bpy::pure_virtual(&MOBase::IModList::setActive)) - .def("priority", bpy::pure_virtual(&MOBase::IModList::priority)) - .def("setPriority", bpy::pure_virtual(&MOBase::IModList::setPriority)) - .def("onModStateChanged", bpy::pure_virtual(&MOBase::IModList::onModStateChanged)) - .def("onModMoved", bpy::pure_virtual(&MOBase::IModList::onModMoved)) + bpy::class_("IModList", bpy::no_init) + .def("displayName", &MOBase::IModList::displayName) + .def("allMods", &MOBase::IModList::allMods) + .def("state", &MOBase::IModList::state) + .def("setActive", &MOBase::IModList::setActive) + .def("priority", &MOBase::IModList::priority) + .def("setPriority", &MOBase::IModList::setPriority) + .def("onModStateChanged", &MOBase::IModList::onModStateChanged) + .def("onModMoved", &MOBase::IModList::onModMoved) ; bpy::class_("IPlugin") diff --git a/src/runner/uibasewrappers.h b/src/runner/uibasewrappers.h index 6a1419a..d7e4397 100644 --- a/src/runner/uibasewrappers.h +++ b/src/runner/uibasewrappers.h @@ -31,79 +31,6 @@ extern MOBase::IOrganizer *s_Organizer; -struct IProfileWrapper: MOBase::IProfile, boost::python::wrapper -{ - virtual QString name() const override { return this->get_override("name")(); } - virtual QString absolutePath() const override { return this->get_override("absolutePath")(); } - virtual bool localSavesEnabled() const override { return this->get_override("localSavesEnabled")(); } - virtual bool localSettingsEnabled() const override { return this->get_override("localSettingsEnabled")(); } - virtual bool invalidationActive(bool *supported) const override { return this->get_override("invalidationActive")(supported); } -}; - -struct IDownloadManagerWrapper: MOBase::IDownloadManager, boost::python::wrapper -{ - virtual int startDownloadURLs(const QStringList &urls) { return this->get_override("startDownloadURLs")(urls); } - virtual int startDownloadNexusFile(int modID, int fileID) { return this->get_override("startDownloadNexusFile")(modID, fileID); } - virtual QString downloadPath(int id) { return this->get_override("downloadPath")(id); } -}; - -struct IModRepositoryBridgeWrapper: MOBase::IModRepositoryBridge, boost::python::wrapper -{ - using IModRepositoryBridge::IModRepositoryBridge; - virtual void requestDescription(QString gameName, int modID, QVariant userData) { this->get_override("requestDescription")(gameName, modID, userData); } - virtual void requestFiles(QString gameName, int modID, QVariant userData) { this->get_override("requestFiles")(gameName, modID, userData); } - virtual void requestFileInfo(QString gameName, int modID, int fileID, QVariant userData) { this->get_override("requestFileInfo")(gameName, modID, fileID, userData); } - virtual void requestDownloadURL(QString gameName, int modID, int fileID, QVariant userData) { this->get_override("requestDownloadURL")(gameName, modID, fileID, userData); } - virtual void requestToggleEndorsement(QString gameName, int modID, QString modVersion, bool endorse, QVariant userData) { this->get_override("requestToggleEndorsement")(gameName, modID, endorse, userData); } -}; - -struct IModInterfaceWrapper: MOBase::IModInterface, boost::python::wrapper -{ - virtual QString name() const override { return this->get_override("name")(); } - virtual QString absolutePath() const override { return this->get_override("absolutePath")(); } - virtual void setVersion(const MOBase::VersionInfo &version) override { this->get_override("setVersion")(version); } - virtual void setNewestVersion(const MOBase::VersionInfo &version) override { this->get_override("setNewestVersion")(version); } - virtual void setIsEndorsed(bool endorsed) override { this->get_override("setIsEndorsed")(endorsed); } - virtual void setNexusID(int nexusID) override { this->get_override("setNexusID")(nexusID); } - virtual void setInstallationFile(const QString &fileName) override { this->get_override("setInstallationFile")(fileName); } - virtual void addNexusCategory(int categoryID) override { this->get_override("addNexusCategory")(categoryID); } - virtual void setGamePlugin(const MOBase::IPluginGame *gamePlugin) override { this->get_override("setGamePlugin")(gamePlugin); } - virtual bool setName(const QString &name) override { return this->get_override("setName")(name); } - virtual bool remove() override { return this->get_override("remove")(); } - virtual void addCategory(const QString &categoryName) override { this->get_override("addCategory")(categoryName); } - virtual bool removeCategory(const QString &categoryName) override { return this->get_override("removeCategory")(categoryName); } - virtual QStringList categories() const override { return this->get_override("categories")(); } -}; - - -struct IPluginListWrapper: MOBase::IPluginList, boost::python::wrapper { - virtual PluginStates state(const QString &name) const { return this->get_override("state")(name); } - virtual int priority(const QString &name) const { return this->get_override("priority")(name); } - virtual int loadOrder(const QString &name) const { return this->get_override("loadOrder")(name); } - virtual bool isMaster(const QString &name) const { return this->get_override("isMaster")(name); } - virtual QStringList masters(const QString &name) const { return this->get_override("masters")(name); } - virtual QString origin(const QString &name) const { return this->get_override("origin")(name); } - virtual bool onRefreshed(const std::function &callback) { return this->get_override("onRefreshed")(callback); } - virtual bool onPluginMoved(const std::function &callback) { return this->get_override("onPluginMoved")(callback); } - virtual bool onPluginStateChanged(const std::function &callback) override { return this->get_override("onPluginStateChanged")(callback); } - virtual QStringList pluginNames() const override { return this->get_override("pluginNames")(); } - virtual void setState(const QString &name, PluginStates state) override { this->get_override("setState")(name, state); } - virtual void setLoadOrder(const QStringList &pluginList) override { this->get_override("setLoadOrder")(pluginList); } -}; - - -struct IModListWrapper: MOBase::IModList, boost::python::wrapper { - virtual QString displayName(const QString &internalName) const override { return this->get_override("displayName")(internalName); } - virtual QStringList allMods() const override { return this->get_override("allMods")(); } - virtual ModStates state(const QString &name) const override { return this->get_override("state")(name); } - virtual int priority(const QString &name) const override { return this->get_override("priority")(name); } - virtual bool setActive(const QString &name, bool active) override { return this->get_override("setActive")(name, active); } - virtual bool setPriority(const QString &name, int newPriority) override { return this->get_override("setPriority")(name, newPriority); } - virtual bool onModStateChanged(const std::function &func) override { return this->get_override("onModStateChanged")(func); } - virtual bool onModMoved(const std::function &func) override { return this->get_override("onModMoved")(func); } -}; - - // This needs to be extendable in Python, so actually needs a wrapper (everything else probably doesn't): class ISaveGameWrapper : public MOBase::ISaveGame, public boost::python::wrapper { From 0c52c2417cdb4bf5ef12c4fb0d6addcb52c238d3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mika=C3=ABl=20Capelle?= Date: Mon, 11 May 2020 23:39:55 +0200 Subject: [PATCH 15/35] Remove unused global variable. --- src/runner/pythonrunner.cpp | 5 ----- src/runner/uibasewrappers.h | 2 -- 2 files changed, 7 deletions(-) diff --git a/src/runner/pythonrunner.cpp b/src/runner/pythonrunner.cpp index 404f299..8014846 100644 --- a/src/runner/pythonrunner.cpp +++ b/src/runner/pythonrunner.cpp @@ -32,10 +32,6 @@ #include "variant_helper.h" #include "converters.h" -MOBase::IOrganizer *s_Organizer = nullptr; - - - using namespace MOBase; namespace bpy = boost::python; @@ -744,7 +740,6 @@ private: IPythonRunner* CreatePythonRunner(MOBase::IOrganizer* moInfo, const QString& pythonDir) { - s_Organizer = moInfo; PythonRunner* result = new PythonRunner(moInfo); if (result->initPython(pythonDir)) { return result; diff --git a/src/runner/uibasewrappers.h b/src/runner/uibasewrappers.h index d7e4397..a2904f9 100644 --- a/src/runner/uibasewrappers.h +++ b/src/runner/uibasewrappers.h @@ -29,8 +29,6 @@ #include "gilock.h" #include "pythonwrapperutilities.h" -extern MOBase::IOrganizer *s_Organizer; - // This needs to be extendable in Python, so actually needs a wrapper (everything else probably doesn't): class ISaveGameWrapper : public MOBase::ISaveGame, public boost::python::wrapper { From d6b8c669117d2dac6b29036a385cebaa72d93a11 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mika=C3=ABl=20Capelle?= Date: Mon, 11 May 2020 23:40:06 +0200 Subject: [PATCH 16/35] Move include directive where they should be. --- src/runner/pythonrunner.cpp | 7 +++++++ src/runner/uibasewrappers.h | 8 -------- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/src/runner/pythonrunner.cpp b/src/runner/pythonrunner.cpp index 8014846..c93c333 100644 --- a/src/runner/pythonrunner.cpp +++ b/src/runner/pythonrunner.cpp @@ -3,11 +3,18 @@ #pragma warning( disable : 4100 ) #pragma warning( disable : 4996 ) +#include #include +#include +#include +#include +#include #include #include #include #include +#include + #include "uibasewrappers.h" #include "proxypluginwrappers.h" #include "gamefeatureswrappers.h" diff --git a/src/runner/uibasewrappers.h b/src/runner/uibasewrappers.h index a2904f9..c06c1e5 100644 --- a/src/runner/uibasewrappers.h +++ b/src/runner/uibasewrappers.h @@ -13,17 +13,9 @@ #include #include -#include "iplugingame.h" -#include -#include -#include -#include -#include #include -#include #include #include -#include "ifiletree.h" #include "error.h" #include "gilock.h" From 288f9f9a956068982f345c6add6344bcd58976e9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mika=C3=ABl=20Capelle?= Date: Tue, 12 May 2020 19:25:57 +0200 Subject: [PATCH 17/35] Rename IModRepositoryBridge to be consistent with other interfaces. --- 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 c93c333..16a6912 100644 --- a/src/runner/pythonrunner.cpp +++ b/src/runner/pythonrunner.cpp @@ -382,7 +382,7 @@ BOOST_PYTHON_MODULE(mobase) .def("invalidationActive", &IProfile::invalidationActive) ; - bpy::class_("ModRepositoryBridge", bpy::no_init) + bpy::class_("IModRepositoryBridge", bpy::no_init) .def("requestDescription", &IModRepositoryBridge::requestDescription) .def("requestFiles", &IModRepositoryBridge::requestFiles) .def("requestFileInfo", &IModRepositoryBridge::requestFileInfo) From 7c474c58caecf09e10800a65835334677c7f41d8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mika=C3=ABl=20Capelle?= Date: Wed, 13 May 2020 14:33:15 +0200 Subject: [PATCH 18/35] Add small comments. --- src/runner/pythonrunner.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/runner/pythonrunner.cpp b/src/runner/pythonrunner.cpp index 16a6912..794e677 100644 --- a/src/runner/pythonrunner.cpp +++ b/src/runner/pythonrunner.cpp @@ -95,7 +95,7 @@ BOOST_PYTHON_MODULE(mobase) 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>(); @@ -103,7 +103,7 @@ BOOST_PYTHON_MODULE(mobase) utils::register_set_container>(); - utils::register_associative_container>(); + utils::register_associative_container>(); // Required for QVariant since this is QVariantMap. utils::register_associative_container>(); utils::register_associative_container(); From 90a99b37c985d56f00c4b26decf39604a48703c9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mika=C3=ABl=20Capelle?= Date: Wed, 13 May 2020 14:34:17 +0200 Subject: [PATCH 19/35] Add missing override annotation and use using to bring invalidate in public scope. --- src/runner/proxypluginwrappers.cpp | 4 ---- src/runner/proxypluginwrappers.h | 16 ++++++++-------- 2 files changed, 8 insertions(+), 12 deletions(-) diff --git a/src/runner/proxypluginwrappers.cpp b/src/runner/proxypluginwrappers.cpp index 5f7b18d..6c2b849 100644 --- a/src/runner/proxypluginwrappers.cpp +++ b/src/runner/proxypluginwrappers.cpp @@ -102,10 +102,6 @@ void IPluginDiagnoseWrapper::startGuidedFix(unsigned int key) const basicWrapperFunctionImplementation(this, "startGuidedFix", key); } -void IPluginDiagnoseWrapper::invalidate() -{ - IPluginDiagnose::invalidate(); -} /// end IPluginDiagnose Wrapper ///////////////////////////////////// /// IPluginFileMapper Wrapper diff --git a/src/runner/proxypluginwrappers.h b/src/runner/proxypluginwrappers.h index f2ca83f..0c77ab4 100644 --- a/src/runner/proxypluginwrappers.h +++ b/src/runner/proxypluginwrappers.h @@ -51,14 +51,14 @@ public: static constexpr const char* className = "IPluginDiagnoseWrapper"; using boost::python::wrapper::get_override; + // Bring in public scope: + using IPluginDiagnose::invalidate; + virtual std::vector activeProblems() const override; virtual QString shortDescription(unsigned int key) const override; virtual QString fullDescription(unsigned int key) const override; virtual bool hasGuidedFix(unsigned int key) const override; virtual void startGuidedFix(unsigned int key) const override; - // Other functions exist, but shouldn't need wrapping as a default implementation exists - // This was protected, but Python doesn't have that, so it needs making public - virtual void invalidate(); COMMON_I_PLUGIN_WRAPPER_DECLARATIONS }; @@ -230,17 +230,17 @@ public: // Bring in public scope: using IPluginTool::parentWidget; - virtual QString displayName() const; - virtual QString tooltip() const; - virtual QIcon icon() const; - virtual void setParentWidget(QWidget *parent); + virtual QString displayName() const override; + virtual QString tooltip() const override; + virtual QIcon icon() const override; + virtual void setParentWidget(QWidget *parent) override; void setParentWidget_Default(QWidget* parent) { IPluginTool::setParentWidget(parent); } public Q_SLOTS: - virtual void display() const; + virtual void display() const override; }; From fe0a75b819a0303dfeca73542219479c16104bac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mika=C3=ABl=20Capelle?= Date: Wed, 13 May 2020 15:35:50 +0200 Subject: [PATCH 20/35] Add to-python converter for QVariant holding QStringList. --- src/runner/converters.h | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/src/runner/converters.h b/src/runner/converters.h index 988f839..a96d627 100644 --- a/src/runner/converters.h +++ b/src/runner/converters.h @@ -110,6 +110,9 @@ namespace utils { case QVariant::UInt: return PyLong_FromUnsignedLong(var.toUInt()); case QVariant::Bool: return PyBool_FromLong(var.toBool()); case QVariant::String: return bpy::incref(bpy::object(var.toString()).ptr()); + // We need to check for StringList here because these are not considered List + // since List is QList will StringList is QList: + case QVariant::StringList: return bpy::incref(bpy::object(var.toStringList()).ptr()); case QVariant::List: { return bpy::incref(bpy::object(var.toList()).ptr()); } break; @@ -154,9 +157,9 @@ namespace utils { } static void construct(PyObject* objPtr, bpy::converter::rvalue_from_python_stage1_data* data) { - // PyBools will also return true for SIPLong_Check but not the other way around, so the order - // here is relevant if (PyList_Check(objPtr)) { + // We could check if all the elements can be converted to QString and store a QStringList + // in the QVariant but I am not sure that is really useful. constructVariant(bpy::extract(objPtr)(), data); } else if (objPtr == Py_None) { @@ -168,12 +171,14 @@ namespace utils { else if (SIPBytes_Check(objPtr) || PyUnicode_Check(objPtr)) { constructVariant(bpy::extract(objPtr)(), data); } + // PyBools will also return true for SIPLong_Check but not the other way around, so the order + // here is relevant. else if (PyBool_Check(objPtr)) { constructVariant(bpy::extract(objPtr)(), data); } else if (SIPLong_Check(objPtr)) { - //QVariant doesn't have long. It has int or long long. Given that on m/s, - //long is 32 bits for 32- and 64- bit code... + // QVariant doesn't have long. It has int or long long. Given that on m/s, + // long is 32 bits for 32- and 64- bit code... constructVariant(bpy::extract(objPtr)(), data); } else { From eb29202b2f3c77ff69c26c7bb43710aa68a06d91 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mika=C3=ABl=20Capelle?= Date: Wed, 13 May 2020 15:55:05 +0200 Subject: [PATCH 21/35] Use UPPER_CASE for enumeration values (kept non-uppercase for backward compatibility). --- src/runner/pythonrunner.cpp | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/src/runner/pythonrunner.cpp b/src/runner/pythonrunner.cpp index 794e677..aab292f 100644 --- a/src/runner/pythonrunner.cpp +++ b/src/runner/pythonrunner.cpp @@ -140,6 +140,12 @@ BOOST_PYTHON_MODULE(mobase) .value("beta", MOBase::VersionInfo::RELEASE_BETA) .value("alpha", MOBase::VersionInfo::RELEASE_ALPHA) .value("prealpha", MOBase::VersionInfo::RELEASE_PREALPHA) + + .value("FINAL", MOBase::VersionInfo::RELEASE_FINAL) + .value("CANDIDATE", MOBase::VersionInfo::RELEASE_CANDIDATE) + .value("BETA", MOBase::VersionInfo::RELEASE_BETA) + .value("ALPHA", MOBase::VersionInfo::RELEASE_ALPHA) + .value("PRE_ALPHA", MOBase::VersionInfo::RELEASE_PREALPHA) ; bpy::enum_("VersionScheme") @@ -149,6 +155,13 @@ BOOST_PYTHON_MODULE(mobase) .value("numbersandletters", MOBase::VersionInfo::SCHEME_NUMBERSANDLETTERS) .value("date", MOBase::VersionInfo::SCHEME_DATE) .value("literal", MOBase::VersionInfo::SCHEME_LITERAL) + + .value("DISCOVER", MOBase::VersionInfo::SCHEME_DISCOVER) + .value("REGULAR", MOBase::VersionInfo::SCHEME_REGULAR) + .value("DECIMAL_MARK", MOBase::VersionInfo::SCHEME_DECIMALMARK) + .value("NUMBERS_AND_LETTERS", MOBase::VersionInfo::SCHEME_NUMBERSANDLETTERS) + .value("DATE", MOBase::VersionInfo::SCHEME_DATE) + .value("LITERAL", MOBase::VersionInfo::SCHEME_LITERAL) ; bpy::class_("VersionInfo") @@ -495,6 +508,10 @@ BOOST_PYTHON_MODULE(mobase) .value("missing", IPluginList::STATE_MISSING) .value("inactive", IPluginList::STATE_INACTIVE) .value("active", IPluginList::STATE_ACTIVE) + + .value("MISSING", IPluginList::STATE_MISSING) + .value("INACTIVE", IPluginList::STATE_INACTIVE) + .value("ACTIVE", IPluginList::STATE_ACTIVE) ; bpy::class_("IPluginList", bpy::no_init) @@ -519,6 +536,14 @@ BOOST_PYTHON_MODULE(mobase) .value("endorsed", IModList::STATE_ENDORSED) .value("valid", IModList::STATE_VALID) .value("alternate", IModList::STATE_ALTERNATE) + + .value("EXISTS", IModList::STATE_EXISTS) + .value("ACTIVE", IModList::STATE_ACTIVE) + .value("ESSENTIAL", IModList::STATE_ESSENTIAL) + .value("EMPTY", IModList::STATE_EMPTY) + .value("ENDORSED", IModList::STATE_ENDORSED) + .value("VALID", IModList::STATE_VALID) + .value("ALTERNATE", IModList::STATE_ALTERNATE) ; bpy::class_("IModList", bpy::no_init) @@ -565,6 +590,9 @@ BOOST_PYTHON_MODULE(mobase) bpy::enum_("LoadOrderMechanism") .value("FileTime", MOBase::IPluginGame::LoadOrderMechanism::FileTime) .value("PluginsTxt", MOBase::IPluginGame::LoadOrderMechanism::PluginsTxt) + + .value("FILE_TIME", MOBase::IPluginGame::LoadOrderMechanism::FileTime) + .value("PLUGINS_TXT", MOBase::IPluginGame::LoadOrderMechanism::PluginsTxt) ; bpy::enum_("SortMechanism") @@ -580,6 +608,11 @@ BOOST_PYTHON_MODULE(mobase) .value("configuration", MOBase::IPluginGame::CONFIGURATION) .value("savegames", MOBase::IPluginGame::SAVEGAMES) .value("preferDefaults", MOBase::IPluginGame::PREFER_DEFAULTS) + + .value("MODS", MOBase::IPluginGame::MODS) + .value("CONFIGURATION", MOBase::IPluginGame::CONFIGURATION) + .value("SAVEGAMES", MOBase::IPluginGame::SAVEGAMES) + .value("PREFER_DEFAULTS", MOBase::IPluginGame::PREFER_DEFAULTS) ; bpy::class_, boost::noncopyable>("IPluginGame") From aa75c3b18e04a70bafd1e45eaf735a79aaea7db7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mika=C3=ABl=20Capelle?= Date: Wed, 13 May 2020 17:35:33 +0200 Subject: [PATCH 22/35] Add .feature() method to the Python interface of IPluginGame. --- src/runner/pythonrunner.cpp | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/src/runner/pythonrunner.cpp b/src/runner/pythonrunner.cpp index aab292f..f3c2e72 100644 --- a/src/runner/pythonrunner.cpp +++ b/src/runner/pythonrunner.cpp @@ -678,6 +678,36 @@ BOOST_PYTHON_MODULE(mobase) }); return dict; }) + + .def("feature", +[](MOBase::IPluginGame* p, bpy::object clsObj) { + bpy::object feature; + mp11::mp_for_each< + mp11::mp_transform< + // Must user pointers because mp_for_each construct object: + std::add_pointer_t, + mp11::mp_list< + BSAInvalidation, + DataArchives, + GamePlugins, + LocalSavegames, + SaveGameInfo, + ScriptExtender, + UnmanagedMods + > + > + >([&](auto* pt) { + using T = std::remove_pointer_t; + typename bpy::reference_existing_object::apply::type converter; + + // Retrieve the python class object: + const bpy::converter::registration* registration = bpy::converter::registry::query(bpy::type_id()); + + if (clsObj.ptr() == (PyObject*) registration->get_class_object()) { + feature = bpy::object(bpy::handle<>(converter(p->feature()))); + } + }); + return feature; + }) ; bpy::enum_("InstallResult") From d931d71a430c19d1c4bf08f5416336fed631f7ac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mika=C3=ABl=20Capelle?= Date: Thu, 14 May 2020 23:18:23 +0200 Subject: [PATCH 23/35] Translate failure in exceptions for some IFileTree methods. Add IFileTree::walk. --- src/runner/pythonrunner.cpp | 46 +++++++++++++++++++++++++++---------- 1 file changed, 34 insertions(+), 12 deletions(-) diff --git a/src/runner/pythonrunner.cpp b/src/runner/pythonrunner.cpp index f3c2e72..cafc72f 100644 --- a/src/runner/pythonrunner.cpp +++ b/src/runner/pythonrunner.cpp @@ -117,7 +117,7 @@ BOOST_PYTHON_MODULE(mobase) IPluginInstaller::EInstallResult, std::shared_ptr, std::tuple, QString, int>>>(); - bpy::register_variant>(); + bpy::register_variant>(); bpy::register_variant>(); // Functions: @@ -128,6 +128,7 @@ BOOST_PYTHON_MODULE(mobase) utils::register_functor_converter(); utils::register_functor_converter(); utils::register_functor_converter const&)>(); + utils::register_functor_converter)>(); utils::register_functor_converter(QString const&)>(); // @@ -336,28 +337,43 @@ BOOST_PYTHON_MODULE(mobase) iFileTreeClass - // Non-mutable operations (note: iterator and some methods are at the end with - // special python methods): + // Non-mutable operations: .def("exists", static_cast(&IFileTree::exists), (bpy::arg("type") = IFileTree::FILE_OR_DIRECTORY)) .def("find", static_cast(IFileTree::*)(QString, IFileTree::FileTypes)>(&IFileTree::find), bpy::arg("type") = IFileTree::FILE_OR_DIRECTORY, bpy::return_value_policy>(), "[optional]") .def("pathTo", &IFileTree::pathTo, bpy::arg("sep") = "\\") + // Note: walk() would probably be better as a generator in python, but it is likely impossible to construct + // from the C++ walk() method. + .def("walk", &IFileTree::walk, bpy::arg("sep") = "\\") + // Kind-of-static operations: .def("createOrphanTree", &IFileTree::createOrphanTree, bpy::arg("name") = "") - // Mutable operations: - .def("addFile", &IFileTree::addFile, bpy::arg("time") = QDateTime(), "[optional]") - .def("addDirectory", &IFileTree::addDirectory, "[optional]") - .def("insert", +[]( - IFileTree* p, std::shared_ptr entry, IFileTree::InsertPolicy insertPolicy) { - return p->insert(entry, insertPolicy) != p->end(); }, bpy::arg("policy") = IFileTree::InsertPolicy::FAIL_IF_EXISTS) + // addFile() and addDirectory throws exception instead of returning null pointer in order + // to have better traces. + .def("addFile", +[](IFileTree* w, QString name, QDateTime time) { + auto result = w->addFile(name, time); + if (result == nullptr) { + throw std::logic_error("addFile failed"); + } + return result; + }, bpy::arg("time") = QDateTime()) + .def("addDirectory", +[](IFileTree* w, QString name) { + auto result = w->addDirectory(name); + if (result == nullptr) { + throw std::logic_error("addDirectory failed"); + } + return result; + }) - .def("merge", +[](IFileTree* p, std::shared_ptr other, bool returnOverwrites) -> std::variant { + // Merge needs custom return types depending if the user wants overrides or not. A failure is translated + // into an exception for easier tracing and handling. + .def("merge", +[](IFileTree* p, std::shared_ptr other, bool returnOverwrites) -> std::variant { IFileTree::OverwritesType overwrites; auto result = p->merge(other, returnOverwrites ? &overwrites : nullptr); if (result == IFileTree::MERGE_FAILED) { - return { false }; + throw std::logic_error("merge failed"); } if (returnOverwrites) { return { overwrites }; @@ -365,11 +381,17 @@ BOOST_PYTHON_MODULE(mobase) return { result }; }, bpy::arg("overwrites") = false) - .def("move", &IFileTree::move, bpy::arg("policy") = IFileTree::InsertPolicy::FAIL_IF_EXISTS) + // Insert and erase returns an iterator, which makes no sense in python, so we convert it to bool. Erase is also + // renamed "remove" since "erase" is very C++. + .def("insert", +[](IFileTree* p, std::shared_ptr entry, IFileTree::InsertPolicy insertPolicy) { + return p->insert(entry, insertPolicy) == p->end(); + }, bpy::arg("policy") = IFileTree::InsertPolicy::FAIL_IF_EXISTS) .def("remove", +[](IFileTree* p, QString name) { return p->erase(name).first != p->end(); }) .def("remove", +[](IFileTree* p, std::shared_ptr entry) { return p->erase(entry) != p->end(); }) + .def("move", &IFileTree::move, bpy::arg("policy") = IFileTree::InsertPolicy::FAIL_IF_EXISTS) + .def("clear", &IFileTree::clear) .def("removeAll", &IFileTree::removeAll) .def("removeIf", &IFileTree::removeIf) From f12bb05d618478672ed170fc5d0035aa39340031 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mika=C3=ABl=20Capelle?= Date: Thu, 14 May 2020 23:24:38 +0200 Subject: [PATCH 24/35] Update python version to 3.8. --- 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 cafc72f..007f21e 100644 --- a/src/runner/pythonrunner.cpp +++ b/src/runner/pythonrunner.cpp @@ -1020,6 +1020,6 @@ bool PythonRunner::isPythonInstalled() const bool PythonRunner::isPythonVersionSupported() const { const char *version = Py_GetVersion(); - return strstr(version, "3.7") == version; + return strstr(version, "3.8") == version; } From 23c6f3fad9c7f002a3c042eb7e477e49f6848b8e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mika=C3=ABl=20Capelle?= Date: Sat, 16 May 2020 17:37:22 +0200 Subject: [PATCH 25/35] Better arity check for functor converter. --- src/runner/converters.h | 38 +++++++++++++++++++++++++------------- 1 file changed, 25 insertions(+), 13 deletions(-) diff --git a/src/runner/converters.h b/src/runner/converters.h index a96d627..b5fe206 100644 --- a/src/runner/converters.h +++ b/src/runner/converters.h @@ -308,21 +308,34 @@ namespace utils { } namespace { - int getArgCount(PyObject* object) { - int result = 0; - PyObject* funcCode = PyObject_GetAttrString(object, "__code__"); - if (funcCode) { - PyObject* argCount = PyObject_GetAttrString(funcCode, "co_argcount"); - if (argCount) { - result = SIPLong_AsLong(argCount); - Py_DECREF(argCount); - } - Py_DECREF(funcCode); + bool has_arity(PyObject* object, std::size_t arity) { + // Mostly from https://stackoverflow.com/a/36143796/2666289 + bpy::object fn(bpy::handle<>(bpy::borrowed(object))); + + auto inspect = bpy::import("inspect"); + auto arg_spec = inspect.attr("getfullargspec")(fn); + bpy::object args = arg_spec.attr("args"), + varargs = arg_spec.attr("varargs"), + defaults = arg_spec.attr("defaults"); + + auto args_count = args ? bpy::len(args) : 0; + auto defaults_count = defaults ? bpy::len(defaults) : 0; + + if (static_cast(inspect.attr("ismethod")(fn)) && fn.attr("__self__")) { + --args_count; } - return result; + + auto required_count = args_count - defaults_count; + + return required_count <= arity // Cannot require more parameters than given, + && (args_count >= arity || varargs); // Must accept enough parameters. } } + /** + * @brief Convert a python callable to a valid C++ Callable object. Also works + * for None. + */ template struct Functor_converter; @@ -355,8 +368,7 @@ namespace utils { static void* convertible(PyObject* object) { - if (!PyCallable_Check(object) - || (getArgCount(object) != sizeof...(PARAMS))) { + if (!PyCallable_Check(object) || !has_arity(object, sizeof...(PARAMS))) { return nullptr; } return object; From fae9f1b52f7dcbba3ee751cb4add4560620bebd1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mika=C3=ABl=20Capelle?= Date: Sat, 16 May 2020 17:37:41 +0200 Subject: [PATCH 26/35] Allow None value from python in functor converter. --- src/runner/converters.h | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/src/runner/converters.h b/src/runner/converters.h index b5fe206..9eba894 100644 --- a/src/runner/converters.h +++ b/src/runner/converters.h @@ -368,6 +368,12 @@ namespace utils { static void* convertible(PyObject* object) { + // We allow None here, we will just default-construct a std::function: + if (object == Py_None) { + return object; + } + + // Otherwize we check that we have a callable object: if (!PyCallable_Check(object) || !has_arity(object, sizeof...(PARAMS))) { return nullptr; } @@ -377,12 +383,19 @@ namespace utils { static void construct(PyObject* object, bpy::converter::rvalue_from_python_stage1_data* data) { bpy::object callable(bpy::handle<>(bpy::borrowed(object))); - void* storage = ((bpy::converter::rvalue_from_python_storage>*)data)->storage.bytes; - new (storage) std::function(FunctorWrapper(callable)); + void* storage =((bpy::converter::rvalue_from_python_storage>*)data)->storage.bytes; + if (callable.is_none()) { + new (storage) std::function{}; + } + else { + new (storage) std::function(FunctorWrapper(callable)); + } data->convertible = storage; } }; + + /** * @brief Call policy that automatically downcast shared pointer of type FromType * to shared pointer of type ToType. From 7333bde006b6adcbc31f242b1fe7b5d325a53710 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mika=C3=ABl=20Capelle?= Date: Sat, 16 May 2020 17:37:57 +0200 Subject: [PATCH 27/35] Add makeTree in moprivate. --- src/runner/pythonrunner.cpp | 40 +++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/src/runner/pythonrunner.cpp b/src/runner/pythonrunner.cpp index 007f21e..1cd31ca 100644 --- a/src/runner/pythonrunner.cpp +++ b/src/runner/pythonrunner.cpp @@ -879,6 +879,46 @@ BOOST_PYTHON_MODULE(moprivate) .def("startRecordingExceptionMessage", &ErrWrapper::startRecordingExceptionMessage) .def("stopRecordingExceptionMessage", &ErrWrapper::stopRecordingExceptionMessage) .def("getLastExceptionMessage", &ErrWrapper::getLastExceptionMessage); + + utils::register_functor_converter(); + + // Expose a function to create a particular tree, only for debugging purpose, not in mobase. + bpy::def("makeTree", +[](std::function callback) -> std::shared_ptr { + struct FileTree : IFileTree { + + using callback_t = std::function; + + FileTree(std::shared_ptr parent, QString name, callback_t callback) : + FileTreeEntry(parent, name), IFileTree(), m_Callback(callback){ } + + std::shared_ptr addFile(QString name, QDateTime time) override { + if (m_Callback && !m_Callback(name, false)) { + throw UnsupportedOperationException("File rejected by callback."); + } + return IFileTree::addFile(name, time); + } + + std::shared_ptr addDirectory(QString name) override { + if (m_Callback && !m_Callback(name, true)) { + throw UnsupportedOperationException("Directory rejected by callback."); + } + return IFileTree::addDirectory(name); + } + + protected: + + std::shared_ptr makeDirectory(std::shared_ptr parent, QString name) const override { + return std::make_shared(parent, name, m_Callback); + } + + void doPopulate(std::shared_ptr parent, std::vector>& entries) const override { } + std::shared_ptr doClone() const override { return std::make_shared(nullptr, name(), m_Callback); } + + private: + callback_t m_Callback; + }; + return std::make_shared(nullptr, "", callback); + }, bpy::arg("callback") = bpy::object{}); } bool PythonRunner::initPython(const QString &pythonPath) From 7c1389d479367937d768ddd1576727ca1a9f303b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mika=C3=ABl=20Capelle?= Date: Sat, 16 May 2020 20:47:41 +0200 Subject: [PATCH 28/35] Add ModDataChecker game feature and clean some game-feature related code. --- src/runner/gamefeatureswrappers.cpp | 56 ++++++++++++++++++----------- src/runner/gamefeatureswrappers.h | 24 +++++++++++++ src/runner/pythonrunner.cpp | 28 ++------------- 3 files changed, 62 insertions(+), 46 deletions(-) diff --git a/src/runner/gamefeatureswrappers.cpp b/src/runner/gamefeatureswrappers.cpp index a00b8b1..3efb7f4 100644 --- a/src/runner/gamefeatureswrappers.cpp +++ b/src/runner/gamefeatureswrappers.cpp @@ -100,6 +100,18 @@ bool LocalSavegamesWrapper::prepareProfile(MOBase::IProfile * profile) /// end LocalSavegames Wrapper ///////////////////////////// +/// ModDataChecker Wrapper + +QString ModDataCheckerWrapper::getDataFolderName() const { + return basicWrapperFunctionImplementation(this, "getDataFolderName"); +} + +bool ModDataCheckerWrapper::dataLooksValid(std::shared_ptr fileTree) const { + return basicWrapperFunctionImplementation(this, "dataLooksValid", fileTree); +} + +/// end ModDataChecker Wrapper +///////////////////////////// /// SaveGameInfo Wrapper @@ -193,11 +205,6 @@ QStringList UnmanagedModsWrapper::secondaryFiles(const QString & modName) const /// end UnmanagedMods Wrapper ///////////////////////////// -template -void insertGameFeature(std::map &map, const boost::python::object &pyObject) -{ - map[std::type_index(typeid(T))] = boost::python::extract(pyObject)(); -} game_features_map_from_python::game_features_map_from_python() { @@ -209,6 +216,12 @@ void * game_features_map_from_python::convertible(PyObject * objPtr) return PyDict_Check(objPtr) ? objPtr : nullptr; } +template +void insertGameFeature(std::map& map, const boost::python::object& pyObject) +{ + map[std::type_index(typeid(T))] = boost::python::extract(pyObject)(); +} + void game_features_map_from_python::construct(PyObject * objPtr, boost::python::converter::rvalue_from_python_stage1_data * data) { void *storage = ((boost::python::converter::rvalue_from_python_storage>*)data)->storage.bytes; @@ -219,22 +232,18 @@ void game_features_map_from_python::construct(PyObject * objPtr, boost::python:: for (int i = 0; i < len; ++i) { boost::python::object pyKey = keys[i]; - // pyKey should be a Boost.Python.class corresponding to a game feature. - std::string className = boost::python::extract(pyKey.attr("__name__"))(); - if (className == "BSAInvalidation") - insertGameFeature(*result, source[pyKey]); - else if (className == "DataArchives") - insertGameFeature(*result, source[pyKey]); - else if (className == "GamePlugins") - insertGameFeature(*result, source[pyKey]); - else if (className == "LocalSavegames") - insertGameFeature(*result, source[pyKey]); - else if (className == "SaveGameInfo") - insertGameFeature(*result, source[pyKey]); - else if (className == "ScriptExtender") - insertGameFeature(*result, source[pyKey]); - else if (className == "UnmanagedMods") - insertGameFeature(*result, source[pyKey]); + boost::python::object pyValue = source[pyKey]; + + boost::mp11::mp_for_each< + // Must user pointers because mp_for_each construct object: + boost::mp11::mp_transform + >([&](auto* pt) { + using T = std::remove_pointer_t; + boost::python::extract extract(pyValue); + if (extract.check()) { + (*result)[std::type_index(typeid(T))] = extract(); + } + }); } data->convertible = storage; @@ -272,6 +281,11 @@ void registerGameFeaturesPythonConverters() .def("prepareProfile", bpy::pure_virtual(&LocalSavegames::prepareProfile)) ; + bpy::class_("ModDataChecker") + .def("getDataFolderName", bpy::pure_virtual(&ModDataChecker::getDataFolderName)) + .def("dataLooksValid", bpy::pure_virtual(&ModDataChecker::dataLooksValid)) + ; + bpy::class_("SaveGameInfo") .def("getSaveGameInfo", bpy::pure_virtual(&SaveGameInfo::getSaveGameInfo), bpy::return_value_policy()) .def("getMissingAssets", bpy::pure_virtual(&SaveGameInfo::getMissingAssets)) diff --git a/src/runner/gamefeatureswrappers.h b/src/runner/gamefeatureswrappers.h index 9eee37c..2cedf61 100644 --- a/src/runner/gamefeatureswrappers.h +++ b/src/runner/gamefeatureswrappers.h @@ -7,12 +7,26 @@ #include #include #include +#include #include #include #include // this might need turning off if Q_MOC_RUN is defined #include +#include + +// This is a simple MPL list that contains all the game features in one place: +using MpGameFeaturesList = boost::mp11::mp_list< + BSAInvalidation, + DataArchives, + GamePlugins, + LocalSavegames, + ModDataChecker, + SaveGameInfo, + ScriptExtender, + UnmanagedMods +>; ///////////////////////////// /// Wrapper declarations @@ -63,6 +77,16 @@ public: virtual bool prepareProfile(MOBase::IProfile *profile) override; }; +class ModDataCheckerWrapper : public ModDataChecker, public boost::python::wrapper +{ +public: + static constexpr const char* className = "ModDataCheckerWrapper"; + using boost::python::wrapper::get_override; + + virtual QString getDataFolderName() const override; + virtual bool dataLooksValid(std::shared_ptr fileTree) const; +}; + class SaveGameInfoWrapper : public SaveGameInfo, public boost::python::wrapper { public: diff --git a/src/runner/pythonrunner.cpp b/src/runner/pythonrunner.cpp index 1cd31ca..dee49ea 100644 --- a/src/runner/pythonrunner.cpp +++ b/src/runner/pythonrunner.cpp @@ -674,19 +674,8 @@ BOOST_PYTHON_MODULE(mobase) // Constructing a dict from class name to actual object: bpy::dict dict; mp11::mp_for_each< - mp11::mp_transform< - // Must user pointers because mp_for_each construct object: - std::add_pointer_t, - mp11::mp_list< - BSAInvalidation, - DataArchives, - GamePlugins, - LocalSavegames, - SaveGameInfo, - ScriptExtender, - UnmanagedMods - > - > + // Must user pointers because mp_for_each construct object: + mp11::mp_transform >([&](auto* pt) { using T = std::remove_pointer_t; typename bpy::reference_existing_object::apply::type converter; @@ -704,19 +693,8 @@ BOOST_PYTHON_MODULE(mobase) .def("feature", +[](MOBase::IPluginGame* p, bpy::object clsObj) { bpy::object feature; mp11::mp_for_each< - mp11::mp_transform< // Must user pointers because mp_for_each construct object: - std::add_pointer_t, - mp11::mp_list< - BSAInvalidation, - DataArchives, - GamePlugins, - LocalSavegames, - SaveGameInfo, - ScriptExtender, - UnmanagedMods - > - > + mp11::mp_transform >([&](auto* pt) { using T = std::remove_pointer_t; typename bpy::reference_existing_object::apply::type converter; From d9393a2d2be43991630d9ebf2ca2fce1156470e9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mika=C3=ABl=20Capelle?= Date: Mon, 18 May 2020 16:16:26 +0200 Subject: [PATCH 29/35] Add WalkReturn enumeration. --- src/runner/pythonrunner.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/runner/pythonrunner.cpp b/src/runner/pythonrunner.cpp index dee49ea..89bf571 100644 --- a/src/runner/pythonrunner.cpp +++ b/src/runner/pythonrunner.cpp @@ -335,6 +335,13 @@ BOOST_PYTHON_MODULE(mobase) .export_values() ; + bpy::enum_("WalkReturn") + .value("CONTINUE", IFileTree::WalkReturn::CONTINUE) + .value("STOP", IFileTree::WalkReturn::STOP) + .value("SKIP", IFileTree::WalkReturn::SKIP) + .export_values() + ; + iFileTreeClass // Non-mutable operations: From 5f9d2b296d4d4e648d0cf0bc8b22789137760a04 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mika=C3=ABl=20Capelle?= Date: Wed, 20 May 2020 19:53:49 +0200 Subject: [PATCH 30/35] Remove usage of getDataFolderName() for ModDataChecker. --- src/runner/gamefeatureswrappers.cpp | 5 ----- src/runner/gamefeatureswrappers.h | 1 - 2 files changed, 6 deletions(-) diff --git a/src/runner/gamefeatureswrappers.cpp b/src/runner/gamefeatureswrappers.cpp index 3efb7f4..462ce91 100644 --- a/src/runner/gamefeatureswrappers.cpp +++ b/src/runner/gamefeatureswrappers.cpp @@ -102,10 +102,6 @@ bool LocalSavegamesWrapper::prepareProfile(MOBase::IProfile * profile) ///////////////////////////// /// ModDataChecker Wrapper -QString ModDataCheckerWrapper::getDataFolderName() const { - return basicWrapperFunctionImplementation(this, "getDataFolderName"); -} - bool ModDataCheckerWrapper::dataLooksValid(std::shared_ptr fileTree) const { return basicWrapperFunctionImplementation(this, "dataLooksValid", fileTree); } @@ -282,7 +278,6 @@ void registerGameFeaturesPythonConverters() ; bpy::class_("ModDataChecker") - .def("getDataFolderName", bpy::pure_virtual(&ModDataChecker::getDataFolderName)) .def("dataLooksValid", bpy::pure_virtual(&ModDataChecker::dataLooksValid)) ; diff --git a/src/runner/gamefeatureswrappers.h b/src/runner/gamefeatureswrappers.h index 2cedf61..23c8ae7 100644 --- a/src/runner/gamefeatureswrappers.h +++ b/src/runner/gamefeatureswrappers.h @@ -83,7 +83,6 @@ public: static constexpr const char* className = "ModDataCheckerWrapper"; using boost::python::wrapper::get_override; - virtual QString getDataFolderName() const override; virtual bool dataLooksValid(std::shared_ptr fileTree) const; }; From 4b4c36df7ac1bb88c29b930eb1d8d5f3fbba35d5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mika=C3=ABl=20Capelle?= Date: Wed, 20 May 2020 20:52:31 +0200 Subject: [PATCH 31/35] Add possibility to create multiple plugins object from a single .py file or python module. --- src/runner/pythonrunner.cpp | 124 +++++++++++++++++++++++++++--------- 1 file changed, 95 insertions(+), 29 deletions(-) diff --git a/src/runner/pythonrunner.cpp b/src/runner/pythonrunner.cpp index 0717cd7..3e4d8b9 100644 --- a/src/runner/pythonrunner.cpp +++ b/src/runner/pythonrunner.cpp @@ -14,6 +14,7 @@ #include #include #include +#include #include "uibasewrappers.h" #include "proxypluginwrappers.h" @@ -815,7 +816,10 @@ private: void appendIfInstance(bpy::object const& obj, QList& interfaces); private: - std::map m_PythonObjects; + + // List of python objects representing plugins to keep all the bpy::object "alive" + // during the execution. + std::vector m_PythonObjects; const MOBase::IOrganizer* m_MOInfo; wchar_t* m_PythonHome; }; @@ -948,6 +952,7 @@ bool PythonRunner::initPython(const QString &pythonPath) bpy::object mainNamespace = mainModule.attr("__dict__"); mainNamespace["sys"] = bpy::import("sys"); mainNamespace["moprivate"] = bpy::import("moprivate"); + mainNamespace["mobase"] = bpy::import("mobase"); bpy::import("site"); bpy::exec("sys.stdout = moprivate.PrintWrapper()\n" "sys.stderr = moprivate.ErrWrapper.instance()\n" @@ -1012,54 +1017,115 @@ void PythonRunner::appendIfInstance(bpy::object const& obj, QList &int QList PythonRunner::instantiate(const QString &pluginName) { + // `pluginName` can either be a python file (single-file plugin or a folder (whole module). + // + // For whole module, we simply add the parent folder to path, then we load the module with a simple + // bpy::import, and we retrieve the associated __dict__ from which we extract either createPlugin or + // createPlugins. + // + // For single file, we need to use bpy::exec_file, and we will use the context (global variables) + // from __main__ (already contains mobase, and other required module). Since the context is shared + // between called of `instantiate`, we need to make sure to remove createPlugin(s) from previous call. try { GILock lock; - bpy::object mainModule = bpy::import("__main__"); - bpy::object moduleNamespace = mainModule.attr("__dict__"); - bpy::object sys = bpy::import("sys"); - moduleNamespace["sys"] = sys; - moduleNamespace["mobase"] = bpy::import("mobase"); + // Dictionary that will contain createPlugin() or createPlugins(). + bpy::dict moduleDict; if (pluginName.endsWith(".py")) { + bpy::object mainModule = bpy::import("__main__"); + bpy::dict moduleNamespace = bpy::extract(mainModule.attr("__dict__"))(); + std::string temp = ToString(pluginName); - if (handled_exec_file(temp.c_str(), moduleNamespace)) { - throw pyexcept::PythonError(); + if (!handled_exec_file(temp.c_str(), moduleNamespace)) { + moduleDict = moduleNamespace; } - m_PythonObjects[pluginName] = moduleNamespace["createPlugin"](); } else { // Retrieve the module name: QStringList parts = pluginName.split("/"); std::string moduleName = ToString(parts.takeLast()); ensureFolderInPath(parts.join("/")); - bpy::object createPlugin = bpy::import(moduleName.c_str()).attr("createPlugin"); - m_PythonObjects[pluginName] = createPlugin(); + moduleDict = bpy::dict(bpy::import(moduleName.c_str()).attr("__dict__")); } - bpy::object pluginObj = m_PythonObjects[pluginName]; - QList interfaceList; + if (bpy::len(moduleDict) == 0) { + MOBase::log::error("Failed to import plugin from {}.", pluginName); + throw pyexcept::PythonError(); + } - appendIfInstance(pluginObj, interfaceList); - // Must try the wrapper because it's only a plugin extension interface in C++, so doesn't extend QObject - appendIfInstance(pluginObj, interfaceList); - // Must try the wrapper because it's only a plugin extension interface in C++, so doesn't extend QObject - appendIfInstance(pluginObj, interfaceList); - appendIfInstance(pluginObj, interfaceList); - appendIfInstance(pluginObj, interfaceList); - appendIfInstance(pluginObj, interfaceList); - appendIfInstance(pluginObj, interfaceList); - appendIfInstance(pluginObj, interfaceList); + // Create the plugins: + std::vector plugins; - if (interfaceList.isEmpty()) - appendIfInstance(pluginObj, interfaceList); + if (moduleDict.has_key("createPlugin")) { + plugins.push_back(moduleDict["createPlugin"]()); - return interfaceList; - } catch (const bpy::error_already_set&) { - qWarning("failed to run python script \"%s\"", qUtf8Printable(pluginName)); + // Clear for future call + bpy::delitem(moduleDict, bpy::str("createPlugin")); + } + else if (moduleDict.has_key("createPlugins")) { + bpy::object pyPlugins = moduleDict["createPlugins"](); + if (!PySequence_Check(pyPlugins.ptr())) { + MOBase::log::error("Plugin {}: createPlugins must return a list.", pluginName); + } + else { + bpy::list pyList(pyPlugins); + int nPlugins = bpy::len(pyList); + for (int i = 0; i < nPlugins; ++i) { + plugins.push_back(pyList[i]); + } + } + + // Clear for future call + bpy::delitem(moduleDict, bpy::str("createPlugins")); + } + else { + MOBase::log::error("Plugin {}: missing a createPlugin(s) function.", pluginName); + } + + // If we have no plugins, there was an issue, and we already logged the problem: + if (plugins.empty()) { + return QList(); + } + + QList allInterfaceList; + + for (bpy::object pluginObj : plugins) { + + // Add the plugin to keep it alive: + m_PythonObjects.push_back(pluginObj); + + QList interfaceList; + + appendIfInstance(pluginObj, interfaceList); + // Must try the wrapper because it's only a plugin extension interface in C++, so doesn't extend QObject + appendIfInstance(pluginObj, interfaceList); + // Must try the wrapper because it's only a plugin extension interface in C++, so doesn't extend QObject + appendIfInstance(pluginObj, interfaceList); + appendIfInstance(pluginObj, interfaceList); + appendIfInstance(pluginObj, interfaceList); + appendIfInstance(pluginObj, interfaceList); + appendIfInstance(pluginObj, interfaceList); + appendIfInstance(pluginObj, interfaceList); + + if (interfaceList.isEmpty()) { + appendIfInstance(pluginObj, interfaceList); + } + + if (interfaceList.isEmpty()) { + MOBase::log::error("Plugin {}: no plugin interface implemented.", pluginName); + } + + // Append the plugins to the main list: + allInterfaceList.append(interfaceList); + } + + return allInterfaceList; + } + catch (const bpy::error_already_set&) { + MOBase::log::error("Failed to import plugin from {}.", pluginName); throw pyexcept::PythonError(); } - return QList(); } bool PythonRunner::isPythonInstalled() const From c96fe2d21a3564134f79a34ca326a28b4857851c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mika=C3=ABl=20Capelle?= Date: Thu, 21 May 2020 17:47:08 +0200 Subject: [PATCH 32/35] Add missing qflags converter for ModState. --- src/runner/pythonrunner.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/runner/pythonrunner.cpp b/src/runner/pythonrunner.cpp index 3e4d8b9..87d3656 100644 --- a/src/runner/pythonrunner.cpp +++ b/src/runner/pythonrunner.cpp @@ -83,6 +83,7 @@ BOOST_PYTHON_MODULE(mobase) // QFlags: utils::register_qflags_converter(); utils::register_qflags_converter(); + utils::register_qflags_converter(); // Pointers: bpy::register_ptr_to_python>(); From 751e67e86c6a1bc0e77dbd4f10df6fc2b6eb134b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mika=C3=ABl=20Capelle?= Date: Thu, 21 May 2020 22:08:20 +0200 Subject: [PATCH 33/35] Fix issue with Python errors not being retrieved correctly in C++. --- src/runner/pythonwrapperutilities.h | 54 ++++++++++++++--------------- 1 file changed, 27 insertions(+), 27 deletions(-) diff --git a/src/runner/pythonwrapperutilities.h b/src/runner/pythonwrapperutilities.h index 6b25478..ab44ddc 100644 --- a/src/runner/pythonwrapperutilities.h +++ b/src/runner/pythonwrapperutilities.h @@ -5,6 +5,7 @@ #include +#include #include #include "error.h" @@ -17,20 +18,17 @@ template ReturnType basicWrapperFunctionImplementation(const WrapperType *wrapper, const char *methodName, Args... args) { + GILock lock; + boost::python::override implementation = wrapper->get_override(methodName); + if (!implementation) { + throw pyexcept::MissingImplementation(wrapper->className, methodName); + } try { - GILock lock; - boost::python::override implementation = wrapper->get_override(methodName); - if (!implementation) { - throw pyexcept::MissingImplementation(wrapper->className, methodName); - } return implementation(args...).as(); } catch (const boost::python::error_already_set&) { throw pyexcept::PythonError(); } - catch (pyexcept::MissingImplementation const& missingImplementation) { - throw missingImplementation; - } catch (...) { throw pyexcept::UnknownException(); } @@ -42,12 +40,12 @@ ReturnType basicWrapperFunctionImplementation(const WrapperType *wrapper, const template ReturnType basicWrapperFunctionImplementation(const WrapperType* wrapper, boost::python::object &ref, const char* methodName, Args... args) { + GILock lock; + boost::python::override implementation = wrapper->get_override(methodName); + if (!implementation) { + throw pyexcept::MissingImplementation(wrapper->className, methodName); + } try { - GILock lock; - boost::python::override implementation = wrapper->get_override(methodName); - if (!implementation) { - throw pyexcept::MissingImplementation(wrapper->className, methodName); - } ref = implementation(args...); return boost::python::extract(ref)(); } @@ -65,12 +63,15 @@ ReturnType basicWrapperFunctionImplementation(const WrapperType* wrapper, boost: template ReturnType basicWrapperFunctionImplementationWithDefault(WrapperType* wrapper, Fn fn, const char* methodName, Args... args) { + GILock lock; + boost::python::override implementation = wrapper->get_override(methodName); + + if (!implementation) { + return std::invoke(fn, wrapper, args...); + } + try { - GILock lock; - boost::python::override implementation = wrapper->get_override(methodName); - if (implementation) { - return implementation(args...).as(); - } + return implementation(args...).as(); } catch (const boost::python::error_already_set&) { throw pyexcept::PythonError(); @@ -78,19 +79,20 @@ ReturnType basicWrapperFunctionImplementationWithDefault(WrapperType* wrapper, F catch (...) { throw pyexcept::UnknownException(); } - - return std::invoke(fn, wrapper, args...); } template ReturnType basicWrapperFunctionImplementationWithDefault(const WrapperType* wrapper, Fn fn, const char* methodName, Args... args) { + GILock lock; + boost::python::override implementation = wrapper->get_override(methodName); + + if (!implementation) { + return std::invoke(fn, wrapper, args...); + } + try { - GILock lock; - boost::python::override implementation = wrapper->get_override(methodName); - if (implementation) { - return implementation(args...).as(); - } + return implementation(args...).as(); } catch (const boost::python::error_already_set&) { throw pyexcept::PythonError(); @@ -98,8 +100,6 @@ ReturnType basicWrapperFunctionImplementationWithDefault(const WrapperType* wrap catch (...) { throw pyexcept::UnknownException(); } - - return std::invoke(fn, wrapper, args...); } #endif // PYTHONWRAPPERUTILITIES_H From da679b1d6da889a5a7ba1b0860c5b9631fdbda0e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mika=C3=ABl=20Capelle?= Date: Thu, 21 May 2020 23:11:56 +0200 Subject: [PATCH 34/35] Common code for basicWrapperFunction functions. --- src/runner/gamefeatureswrappers.cpp | 62 ++++++------- src/runner/proxypluginwrappers.cpp | 130 ++++++++++++++-------------- src/runner/pythonrunner.cpp | 1 + src/runner/pythonwrapperutilities.h | 116 +++++++++---------------- src/runner/uibasewrappers.h | 12 +-- 5 files changed, 146 insertions(+), 175 deletions(-) diff --git a/src/runner/gamefeatureswrappers.cpp b/src/runner/gamefeatureswrappers.cpp index 462ce91..dd70821 100644 --- a/src/runner/gamefeatureswrappers.cpp +++ b/src/runner/gamefeatureswrappers.cpp @@ -17,22 +17,22 @@ bool BSAInvalidationWrapper::isInvalidationBSA(const QString &bsaName) { - return basicWrapperFunctionImplementation(this, "isInvalidationBSA", bsaName); + return basicWrapperFunctionImplementation(this, "isInvalidationBSA", bsaName); } void BSAInvalidationWrapper::deactivate(MOBase::IProfile *profile) { - return basicWrapperFunctionImplementation(this, "deactivate", boost::python::ptr(profile)); + return basicWrapperFunctionImplementation(this, "deactivate", boost::python::ptr(profile)); } void BSAInvalidationWrapper::activate(MOBase::IProfile *profile) { - return basicWrapperFunctionImplementation(this, "activate", boost::python::ptr(profile)); + return basicWrapperFunctionImplementation(this, "activate", boost::python::ptr(profile)); } bool BSAInvalidationWrapper::prepareProfile(MOBase::IProfile *profile) { - return basicWrapperFunctionImplementation(this, "prepareProfile", boost::python::ptr(profile)); + return basicWrapperFunctionImplementation(this, "prepareProfile", boost::python::ptr(profile)); } /// end BSAInvalidation Wrapper ///////////////////////////// @@ -41,22 +41,22 @@ bool BSAInvalidationWrapper::prepareProfile(MOBase::IProfile *profile) QStringList DataArchivesWrapper::vanillaArchives() const { - return basicWrapperFunctionImplementation(this, "vanillaArchives"); + return basicWrapperFunctionImplementation(this, "vanillaArchives"); } QStringList DataArchivesWrapper::archives(const MOBase::IProfile *profile) const { - return basicWrapperFunctionImplementation(this, "archives", boost::python::ptr(profile)); + return basicWrapperFunctionImplementation(this, "archives", boost::python::ptr(profile)); } void DataArchivesWrapper::addArchive(MOBase::IProfile *profile, int index, const QString &archiveName) { - return basicWrapperFunctionImplementation(this, "addArchive", boost::python::ptr(profile), index, archiveName); + return basicWrapperFunctionImplementation(this, "addArchive", boost::python::ptr(profile), index, archiveName); } void DataArchivesWrapper::removeArchive(MOBase::IProfile *profile, const QString &archiveName) { - return basicWrapperFunctionImplementation(this, "removeArchive", boost::python::ptr(profile), archiveName); + return basicWrapperFunctionImplementation(this, "removeArchive", boost::python::ptr(profile), archiveName); } /// end DataArchives Wrapper ///////////////////////////// @@ -65,22 +65,22 @@ void DataArchivesWrapper::removeArchive(MOBase::IProfile *profile, const QString void GamePluginsWrapper::writePluginLists(const MOBase::IPluginList * pluginList) { - return basicWrapperFunctionImplementation(this, "writePluginLists", boost::python::ptr(pluginList)); + return basicWrapperFunctionImplementation(this, "writePluginLists", boost::python::ptr(pluginList)); } void GamePluginsWrapper::readPluginLists(MOBase::IPluginList * pluginList) { - return basicWrapperFunctionImplementation(this, "readPluginLists", boost::python::ptr(pluginList)); + return basicWrapperFunctionImplementation(this, "readPluginLists", boost::python::ptr(pluginList)); } void GamePluginsWrapper::getLoadOrder(QStringList &loadOrder) { - return basicWrapperFunctionImplementation(this, "getLoadOrder", loadOrder); + return basicWrapperFunctionImplementation(this, "getLoadOrder", loadOrder); } bool GamePluginsWrapper::lightPluginsAreSupported() { - return basicWrapperFunctionImplementation(this, "lightPluginsAreSupported"); + return basicWrapperFunctionImplementation(this, "lightPluginsAreSupported"); } /// end GamePlugins Wrapper @@ -90,12 +90,12 @@ bool GamePluginsWrapper::lightPluginsAreSupported() MappingType LocalSavegamesWrapper::mappings(const QDir & profileSaveDir) const { - return basicWrapperFunctionImplementation(this, "mappings", profileSaveDir); + return basicWrapperFunctionImplementation(this, "mappings", profileSaveDir); } bool LocalSavegamesWrapper::prepareProfile(MOBase::IProfile * profile) { - return basicWrapperFunctionImplementation(this, "prepareProfile", boost::python::ptr(profile)); + return basicWrapperFunctionImplementation(this, "prepareProfile", boost::python::ptr(profile)); } /// end LocalSavegames Wrapper @@ -103,7 +103,7 @@ bool LocalSavegamesWrapper::prepareProfile(MOBase::IProfile * profile) /// ModDataChecker Wrapper bool ModDataCheckerWrapper::dataLooksValid(std::shared_ptr fileTree) const { - return basicWrapperFunctionImplementation(this, "dataLooksValid", fileTree); + return basicWrapperFunctionImplementation(this, "dataLooksValid", fileTree); } /// end ModDataChecker Wrapper @@ -113,22 +113,22 @@ bool ModDataCheckerWrapper::dataLooksValid(std::shared_ptr(this, m_SaveGames[file], "getSaveGameInfo", file); + return basicWrapperFunctionImplementation(this, m_SaveGames[file], "getSaveGameInfo", file); } SaveGameInfoWrapper::MissingAssets SaveGameInfoWrapper::getMissingAssets(QString const & file) const { - return basicWrapperFunctionImplementation(this, "getMissingAssets", file); + return basicWrapperFunctionImplementation(this, "getMissingAssets", file); } MOBase::ISaveGameInfoWidget* SaveGameInfoWrapper::getSaveGameWidget(QWidget* parent) const { - return basicWrapperFunctionImplementation(this, m_SaveGameWidget, "getSaveGameWidget", parent); + return basicWrapperFunctionImplementation(this, m_SaveGameWidget, "getSaveGameWidget", parent); } bool SaveGameInfoWrapper::hasScriptExtenderSave(QString const & file) const { - return basicWrapperFunctionImplementation(this, "hasScriptExtenderSave", file); + return basicWrapperFunctionImplementation(this, "hasScriptExtenderSave", file); } /// end SaveGameInfo Wrapper ///////////////////////////// @@ -136,42 +136,42 @@ bool SaveGameInfoWrapper::hasScriptExtenderSave(QString const & file) const QString ScriptExtenderWrapper::BinaryName() const { - return basicWrapperFunctionImplementation(this, "BinaryName"); + return basicWrapperFunctionImplementation(this, "BinaryName"); } QString ScriptExtenderWrapper::PluginPath() const { - return basicWrapperFunctionImplementation(this, "PluginPath"); + return basicWrapperFunctionImplementation(this, "PluginPath"); } QString ScriptExtenderWrapper::loaderName() const { - return basicWrapperFunctionImplementation(this, "loaderName"); + return basicWrapperFunctionImplementation(this, "loaderName"); } QString ScriptExtenderWrapper::loaderPath() const { - return basicWrapperFunctionImplementation(this, "loaderPath"); + return basicWrapperFunctionImplementation(this, "loaderPath"); } QStringList ScriptExtenderWrapper::saveGameAttachmentExtensions() const { - return basicWrapperFunctionImplementation(this, "saveGameAttachmentExtensions"); + return basicWrapperFunctionImplementation(this, "saveGameAttachmentExtensions"); } bool ScriptExtenderWrapper::isInstalled() const { - return basicWrapperFunctionImplementation(this, "isInstalled"); + return basicWrapperFunctionImplementation(this, "isInstalled"); } QString ScriptExtenderWrapper::getExtenderVersion() const { - return basicWrapperFunctionImplementation(this, "getExtenderVersion"); + return basicWrapperFunctionImplementation(this, "getExtenderVersion"); } WORD ScriptExtenderWrapper::getArch() const { - return basicWrapperFunctionImplementation(this, "getArch"); + return basicWrapperFunctionImplementation(this, "getArch"); } /// end ScriptExtender Wrapper @@ -181,22 +181,22 @@ WORD ScriptExtenderWrapper::getArch() const QStringList UnmanagedModsWrapper::mods(bool onlyOfficial) const { - return basicWrapperFunctionImplementation(this, "mods", onlyOfficial); + return basicWrapperFunctionImplementation(this, "mods", onlyOfficial); } QString UnmanagedModsWrapper::displayName(const QString & modName) const { - return basicWrapperFunctionImplementation(this, "displayName", modName); + return basicWrapperFunctionImplementation(this, "displayName", modName); } QFileInfo UnmanagedModsWrapper::referenceFile(const QString & modName) const { - return basicWrapperFunctionImplementation(this, "referenceFile", modName); + return basicWrapperFunctionImplementation(this, "referenceFile", modName); } QStringList UnmanagedModsWrapper::secondaryFiles(const QString & modName) const { - return basicWrapperFunctionImplementation(this, "secondaryFiles", modName); + return basicWrapperFunctionImplementation(this, "secondaryFiles", modName); } /// end UnmanagedMods Wrapper ///////////////////////////// diff --git a/src/runner/proxypluginwrappers.cpp b/src/runner/proxypluginwrappers.cpp index 6c2b849..32695eb 100644 --- a/src/runner/proxypluginwrappers.cpp +++ b/src/runner/proxypluginwrappers.cpp @@ -31,37 +31,37 @@ using namespace MOBase; #define COMMON_I_PLUGIN_WRAPPER_DEFINITIONS(class_name) \ bool class_name::init(MOBase::IOrganizer *moInfo) \ { \ - return basicWrapperFunctionImplementation(this, "init", boost::python::ptr(moInfo)); \ + return basicWrapperFunctionImplementation(this, "init", boost::python::ptr(moInfo)); \ } \ \ QString class_name::name() const \ { \ - return basicWrapperFunctionImplementation(this, "name"); \ + return basicWrapperFunctionImplementation(this, "name"); \ } \ \ QString class_name::author() const \ { \ - return basicWrapperFunctionImplementation(this, "author"); \ + return basicWrapperFunctionImplementation(this, "author"); \ } \ \ QString class_name::description() const \ { \ - return basicWrapperFunctionImplementation(this, "description"); \ + return basicWrapperFunctionImplementation(this, "description"); \ } \ \ MOBase::VersionInfo class_name::version() const \ { \ - return basicWrapperFunctionImplementation(this, "version"); \ + return basicWrapperFunctionImplementation(this, "version"); \ } \ \ bool class_name::isActive() const \ { \ - return basicWrapperFunctionImplementation(this, "isActive"); \ + return basicWrapperFunctionImplementation(this, "isActive"); \ } \ \ QList class_name::settings() const \ { \ - return basicWrapperFunctionImplementation>(this, "settings"); \ + return basicWrapperFunctionImplementation>(this, "settings"); \ } /// end COMMON_I_PLUGIN_WRAPPER_DEFINITIONS @@ -79,27 +79,27 @@ COMMON_I_PLUGIN_WRAPPER_DEFINITIONS(IPluginDiagnoseWrapper) std::vector IPluginDiagnoseWrapper::activeProblems() const { - return basicWrapperFunctionImplementation>(this, "activeProblems"); + return basicWrapperFunctionImplementation>(this, "activeProblems"); } QString IPluginDiagnoseWrapper::shortDescription(unsigned int key) const { - return basicWrapperFunctionImplementation(this, "shortDescription", key); + return basicWrapperFunctionImplementation(this, "shortDescription", key); } QString IPluginDiagnoseWrapper::fullDescription(unsigned int key) const { - return basicWrapperFunctionImplementation(this, "fullDescription", key); + return basicWrapperFunctionImplementation(this, "fullDescription", key); } bool IPluginDiagnoseWrapper::hasGuidedFix(unsigned int key) const { - return basicWrapperFunctionImplementation(this, "hasGuidedFix", key); + return basicWrapperFunctionImplementation(this, "hasGuidedFix", key); } void IPluginDiagnoseWrapper::startGuidedFix(unsigned int key) const { - basicWrapperFunctionImplementation(this, "startGuidedFix", key); + basicWrapperFunctionImplementation(this, "startGuidedFix", key); } /// end IPluginDiagnose Wrapper @@ -111,7 +111,7 @@ COMMON_I_PLUGIN_WRAPPER_DEFINITIONS(IPluginFileMapperWrapper) MappingType IPluginFileMapperWrapper::mappings() const { - return basicWrapperFunctionImplementation(this, "mappings"); + return basicWrapperFunctionImplementation(this, "mappings"); } /// end IPluginFileMapper Wrapper ///////////////////////////////////// @@ -120,178 +120,178 @@ MappingType IPluginFileMapperWrapper::mappings() const QString IPluginGameWrapper::gameName() const { - return basicWrapperFunctionImplementation(this, "gameName"); + return basicWrapperFunctionImplementation(this, "gameName"); } void IPluginGameWrapper::initializeProfile(const QDir & directory, ProfileSettings settings) const { - basicWrapperFunctionImplementation(this, "initializeProfile", directory, settings); + basicWrapperFunctionImplementation(this, "initializeProfile", directory, settings); } QString IPluginGameWrapper::savegameExtension() const { - return basicWrapperFunctionImplementation(this, "savegameExtension"); + return basicWrapperFunctionImplementation(this, "savegameExtension"); } QString IPluginGameWrapper::savegameSEExtension() const { - return basicWrapperFunctionImplementation(this, "savegameSEExtension"); + return basicWrapperFunctionImplementation(this, "savegameSEExtension"); } bool IPluginGameWrapper::isInstalled() const { - return basicWrapperFunctionImplementation(this, "isInstalled"); + return basicWrapperFunctionImplementation(this, "isInstalled"); } QIcon IPluginGameWrapper::gameIcon() const { - return basicWrapperFunctionImplementation(this, "gameIcon"); + return basicWrapperFunctionImplementation(this, "gameIcon"); } QDir IPluginGameWrapper::gameDirectory() const { - return basicWrapperFunctionImplementation(this, "gameDirectory"); + return basicWrapperFunctionImplementation(this, "gameDirectory"); } QDir IPluginGameWrapper::dataDirectory() const { - return basicWrapperFunctionImplementation(this, "dataDirectory"); + return basicWrapperFunctionImplementation(this, "dataDirectory"); } void IPluginGameWrapper::setGamePath(const QString & path) { - basicWrapperFunctionImplementation(this, "setGamePath", path); + basicWrapperFunctionImplementation(this, "setGamePath", path); } QDir IPluginGameWrapper::documentsDirectory() const { - return basicWrapperFunctionImplementation(this, "documentsDirectory"); + return basicWrapperFunctionImplementation(this, "documentsDirectory"); } QDir IPluginGameWrapper::savesDirectory() const { - return basicWrapperFunctionImplementation(this, "savesDirectory"); + return basicWrapperFunctionImplementation(this, "savesDirectory"); } QList IPluginGameWrapper::executables() const { - return basicWrapperFunctionImplementation>(this, "executables"); + return basicWrapperFunctionImplementation>(this, "executables"); } QList IPluginGameWrapper::executableForcedLoads() const { - return basicWrapperFunctionImplementation>(this, "executableForcedLoads"); + return basicWrapperFunctionImplementation>(this, "executableForcedLoads"); } QString IPluginGameWrapper::steamAPPId() const { - return basicWrapperFunctionImplementation(this, "steamAPPId"); + return basicWrapperFunctionImplementation(this, "steamAPPId"); } QStringList IPluginGameWrapper::primaryPlugins() const { - return basicWrapperFunctionImplementation(this, "primaryPlugins"); + return basicWrapperFunctionImplementation(this, "primaryPlugins"); } QStringList IPluginGameWrapper::gameVariants() const { - return basicWrapperFunctionImplementation(this, "gameVariants"); + return basicWrapperFunctionImplementation(this, "gameVariants"); } void IPluginGameWrapper::setGameVariant(const QString & variant) { - basicWrapperFunctionImplementation(this, "setGameVariant", variant); + basicWrapperFunctionImplementation(this, "setGameVariant", variant); } QString IPluginGameWrapper::binaryName() const { - return basicWrapperFunctionImplementation(this, "binaryName"); + return basicWrapperFunctionImplementation(this, "binaryName"); } QString IPluginGameWrapper::gameShortName() const { - return basicWrapperFunctionImplementation(this, "gameShortName"); + return basicWrapperFunctionImplementation(this, "gameShortName"); } QStringList IPluginGameWrapper::primarySources() const { - return basicWrapperFunctionImplementation(this, "primarySources"); + return basicWrapperFunctionImplementation(this, "primarySources"); } QStringList IPluginGameWrapper::validShortNames() const { - return basicWrapperFunctionImplementation(this, "validShortNames"); + return basicWrapperFunctionImplementation(this, "validShortNames"); } QString IPluginGameWrapper::gameNexusName() const { - return basicWrapperFunctionImplementation(this, "gameNexusName"); + return basicWrapperFunctionImplementation(this, "gameNexusName"); } QStringList IPluginGameWrapper::iniFiles() const { - return basicWrapperFunctionImplementation(this, "iniFiles"); + return basicWrapperFunctionImplementation(this, "iniFiles"); } QStringList IPluginGameWrapper::DLCPlugins() const { - return basicWrapperFunctionImplementation(this, "DLCPlugins"); + return basicWrapperFunctionImplementation(this, "DLCPlugins"); } QStringList IPluginGameWrapper::CCPlugins() const { - return basicWrapperFunctionImplementation(this, "CCPlugins"); + return basicWrapperFunctionImplementation(this, "CCPlugins"); } IPluginGame::LoadOrderMechanism IPluginGameWrapper::loadOrderMechanism() const { - return basicWrapperFunctionImplementation(this, "loadOrderMechanism"); + return basicWrapperFunctionImplementation(this, "loadOrderMechanism"); } IPluginGame::SortMechanism IPluginGameWrapper::sortMechanism() const { - return basicWrapperFunctionImplementation(this, "sortMechanism"); + return basicWrapperFunctionImplementation(this, "sortMechanism"); } int IPluginGameWrapper::nexusModOrganizerID() const { - return basicWrapperFunctionImplementation(this, "nexusModOrganizerID"); + return basicWrapperFunctionImplementation(this, "nexusModOrganizerID"); } int IPluginGameWrapper::nexusGameID() const { - return basicWrapperFunctionImplementation(this, "nexusGameID"); + return basicWrapperFunctionImplementation(this, "nexusGameID"); } bool IPluginGameWrapper::looksValid(QDir const & dir) const { - return basicWrapperFunctionImplementation(this, "looksValid", dir); + return basicWrapperFunctionImplementation(this, "looksValid", dir); } QString IPluginGameWrapper::gameVersion() const { - return basicWrapperFunctionImplementation(this, "gameVersion"); + return basicWrapperFunctionImplementation(this, "gameVersion"); } QString IPluginGameWrapper::getLauncherName() const { - return basicWrapperFunctionImplementation(this, "getLauncherName"); + return basicWrapperFunctionImplementation(this, "getLauncherName"); } COMMON_I_PLUGIN_WRAPPER_DEFINITIONS(IPluginGameWrapper) std::map IPluginGameWrapper::featureList() const { - return basicWrapperFunctionImplementation>(this, "_featureList"); + return basicWrapperFunctionImplementation>(this, "_featureList"); } /// end IPluginGame Wrapper ///////////////////////////////////// /// IPluginInstaller macro #define COMMON_I_PLUGIN_INSTALLER_WRAPPER_DEFINITIONS(class_name) \ -unsigned int class_name::priority() const { return basicWrapperFunctionImplementation(this, "priority"); } \ -bool class_name::isManualInstaller() const { return basicWrapperFunctionImplementation(this, "isManualInstaller"); } \ -bool class_name::isArchiveSupported(std::shared_ptr tree) const { return basicWrapperFunctionImplementation(this, "isArchiveSupported", tree); } +unsigned int class_name::priority() const { return basicWrapperFunctionImplementation(this, "priority"); } \ +bool class_name::isManualInstaller() const { return basicWrapperFunctionImplementation(this, "isManualInstaller"); } \ +bool class_name::isArchiveSupported(std::shared_ptr tree) const { return basicWrapperFunctionImplementation(this, "isArchiveSupported", tree); } /// end IPluginInstaller macro ///////////////////////////////////// @@ -310,7 +310,7 @@ IPluginInstaller::EInstallResult IPluginInstallerSimpleWrapper::install( IPluginInstaller::EInstallResult, std::shared_ptr, std::tuple, QString, int>> ; - auto ret = basicWrapperFunctionImplementation(this, "install", boost::ref(modName), tree, version, nexusID); + auto ret = basicWrapperFunctionImplementation(this, "install", boost::ref(modName), tree, version, nexusID); return std::visit([&](auto const& t) { using type = std::decay_t; @@ -338,12 +338,12 @@ COMMON_I_PLUGIN_INSTALLER_WRAPPER_DEFINITIONS(IPluginInstallerCustomWrapper) bool IPluginInstallerCustomWrapper::isArchiveSupported(const QString &archiveName) const { - return basicWrapperFunctionImplementation(this, "isArchiveSupported", archiveName); + return basicWrapperFunctionImplementation(this, "isArchiveSupported", archiveName); } std::set IPluginInstallerCustomWrapper::supportedExtensions() const { - return basicWrapperFunctionImplementation>(this, "supportedExtensions"); + return basicWrapperFunctionImplementation>(this, "supportedExtensions"); } IPluginInstaller::EInstallResult IPluginInstallerCustomWrapper::install( @@ -351,7 +351,7 @@ IPluginInstaller::EInstallResult IPluginInstallerCustomWrapper::install( { // Note: This requires far more less trouble than the "Simple" installer version since 1) there is no tree // and 2) there version and modId cannot be modified: - return basicWrapperFunctionImplementation( + return basicWrapperFunctionImplementation( this, "install", boost::ref(modName), gameName, archiveName, version, modID); } @@ -364,32 +364,32 @@ COMMON_I_PLUGIN_WRAPPER_DEFINITIONS(IPluginModPageWrapper) QString IPluginModPageWrapper::displayName() const { - return basicWrapperFunctionImplementation(this, "displayName"); + return basicWrapperFunctionImplementation(this, "displayName"); } QIcon IPluginModPageWrapper::icon() const { - return basicWrapperFunctionImplementation(this, "icon"); + return basicWrapperFunctionImplementation(this, "icon"); } QUrl IPluginModPageWrapper::pageURL() const { - return basicWrapperFunctionImplementation(this, "pageURL"); + return basicWrapperFunctionImplementation(this, "pageURL"); } bool IPluginModPageWrapper::useIntegratedBrowser() const { - return basicWrapperFunctionImplementation(this, "useIntegratedBrowser"); + return basicWrapperFunctionImplementation(this, "useIntegratedBrowser"); } bool IPluginModPageWrapper::handlesDownload(const QUrl & pageURL, const QUrl & downloadURL, MOBase::ModRepositoryFileInfo & fileInfo) const { - return basicWrapperFunctionImplementation(this, "handlesDownload", pageURL, downloadURL, fileInfo); + return basicWrapperFunctionImplementation(this, "handlesDownload", pageURL, downloadURL, fileInfo); } void IPluginModPageWrapper::setParentWidget(QWidget * widget) { - basicWrapperFunctionImplementationWithDefault(this, &IPluginModPageWrapper::setParentWidget_Default, "setParentWidget", widget); + basicWrapperFunctionImplementationWithDefault(this, &IPluginModPageWrapper::setParentWidget_Default, "setParentWidget", widget); } /// end IPluginModPage Wrapper ///////////////////////////// @@ -400,7 +400,7 @@ COMMON_I_PLUGIN_WRAPPER_DEFINITIONS(IPluginPreviewWrapper) std::set IPluginPreviewWrapper::supportedExtensions() const { - return basicWrapperFunctionImplementation>(this, "supportedExtensions"); + return basicWrapperFunctionImplementation>(this, "supportedExtensions"); } QWidget *IPluginPreviewWrapper::genFilePreview(const QString &fileName, const QSize &maxSize) const @@ -435,27 +435,27 @@ COMMON_I_PLUGIN_WRAPPER_DEFINITIONS(IPluginToolWrapper) QString IPluginToolWrapper::displayName() const { - return basicWrapperFunctionImplementation(this, "displayName"); + return basicWrapperFunctionImplementation(this, "displayName"); } QString IPluginToolWrapper::tooltip() const { - return basicWrapperFunctionImplementation(this, "tooltip"); + return basicWrapperFunctionImplementation(this, "tooltip"); } QIcon IPluginToolWrapper::icon() const { - return basicWrapperFunctionImplementation(this, "icon"); + return basicWrapperFunctionImplementation(this, "icon"); } void IPluginToolWrapper::setParentWidget(QWidget *parent) { - basicWrapperFunctionImplementationWithDefault(this, &IPluginToolWrapper::setParentWidget_Default, "setParentWidget", parent); + basicWrapperFunctionImplementationWithDefault(this, &IPluginToolWrapper::setParentWidget_Default, "setParentWidget", parent); } void IPluginToolWrapper::display() const { - basicWrapperFunctionImplementation(this, "display"); + basicWrapperFunctionImplementation(this, "display"); } /// end IPluginTool Wrapper diff --git a/src/runner/pythonrunner.cpp b/src/runner/pythonrunner.cpp index 87d3656..b562f06 100644 --- a/src/runner/pythonrunner.cpp +++ b/src/runner/pythonrunner.cpp @@ -776,6 +776,7 @@ BOOST_PYTHON_MODULE(mobase) .def("displayName", bpy::pure_virtual(&IPluginTool::displayName)) .def("tooltip", bpy::pure_virtual(&IPluginTool::tooltip)) .def("icon", bpy::pure_virtual(&IPluginTool::icon)) + .def("display", bpy::pure_virtual(&IPluginTool::display)) .def("setParentWidget", &IPluginTool::setParentWidget, &IPluginToolWrapper::setParentWidget_Default) .def("_parentWidget", &IPluginToolWrapper::parentWidget, bpy::return_value_policy()) ; diff --git a/src/runner/pythonwrapperutilities.h b/src/runner/pythonwrapperutilities.h index ab44ddc..6e80dbf 100644 --- a/src/runner/pythonwrapperutilities.h +++ b/src/runner/pythonwrapperutilities.h @@ -11,95 +11,65 @@ #include "error.h" #include "gilock.h" +namespace details { + + /** + * @brief Common stuffs for all basicWrapperFunction methods. + */ + template + ReturnType wrapperFunctionImplementation(WrapperTypePtr wrapper, Fn fn, boost::python::object* objPtr, const char *methodName, Args... args) { + GILock lock; + boost::python::override implementation = wrapper->get_override(methodName); + if (!implementation) { + if constexpr (std::is_same_v) { + throw pyexcept::MissingImplementation(wrapper->className, methodName); + } + else { + return std::invoke(fn, wrapper, args...); + } + } + try { + boost::python::object result = implementation(args...); + if (objPtr) { + *objPtr = result; + } + if constexpr (!std::is_same_v) { + return boost::python::extract(result)(); + } + } + catch (const boost::python::error_already_set&) { + throw pyexcept::PythonError(); + } + catch (...) { + throw pyexcept::UnknownException(); + } + } + +} + /** * @brief Call the given method on the wrapper with the given arguments, with proper * exception handling. */ -template +template ReturnType basicWrapperFunctionImplementation(const WrapperType *wrapper, const char *methodName, Args... args) { - GILock lock; - boost::python::override implementation = wrapper->get_override(methodName); - if (!implementation) { - throw pyexcept::MissingImplementation(wrapper->className, methodName); - } - try { - return implementation(args...).as(); - } - catch (const boost::python::error_already_set&) { - throw pyexcept::PythonError(); - } - catch (...) { - throw pyexcept::UnknownException(); - } + return details::wrapperFunctionImplementation(wrapper, nullptr, nullptr, methodName, args...); } /** * @brief Similar to the first-overload but also stores the python object in the given reference. */ -template +template ReturnType basicWrapperFunctionImplementation(const WrapperType* wrapper, boost::python::object &ref, const char* methodName, Args... args) { - GILock lock; - boost::python::override implementation = wrapper->get_override(methodName); - if (!implementation) { - throw pyexcept::MissingImplementation(wrapper->className, methodName); - } - try { - ref = implementation(args...); - return boost::python::extract(ref)(); - } - catch (const boost::python::error_already_set&) { - throw pyexcept::PythonError(); - } - catch (pyexcept::MissingImplementation const& missingImplementation) { - throw missingImplementation; - } - catch (...) { - throw pyexcept::UnknownException(); - } + return details::wrapperFunctionImplementation(wrapper, nullptr, &ref, methodName, args...); } -template -ReturnType basicWrapperFunctionImplementationWithDefault(WrapperType* wrapper, Fn fn, const char* methodName, Args... args) +template +ReturnType basicWrapperFunctionImplementationWithDefault(WrapperTypePtr wrapper, Fn fn, const char* methodName, Args... args) { - GILock lock; - boost::python::override implementation = wrapper->get_override(methodName); - - if (!implementation) { - return std::invoke(fn, wrapper, args...); - } - - try { - return implementation(args...).as(); - } - catch (const boost::python::error_already_set&) { - throw pyexcept::PythonError(); - } - catch (...) { - throw pyexcept::UnknownException(); - } -} - -template -ReturnType basicWrapperFunctionImplementationWithDefault(const WrapperType* wrapper, Fn fn, const char* methodName, Args... args) -{ - GILock lock; - boost::python::override implementation = wrapper->get_override(methodName); - - if (!implementation) { - return std::invoke(fn, wrapper, args...); - } - - try { - return implementation(args...).as(); - } - catch (const boost::python::error_already_set&) { - throw pyexcept::PythonError(); - } - catch (...) { - throw pyexcept::UnknownException(); - } + return details::wrapperFunctionImplementation(wrapper, fn, nullptr, methodName, args...); } #endif // PYTHONWRAPPERUTILITIES_H diff --git a/src/runner/uibasewrappers.h b/src/runner/uibasewrappers.h index c06c1e5..a032853 100644 --- a/src/runner/uibasewrappers.h +++ b/src/runner/uibasewrappers.h @@ -28,11 +28,11 @@ public: static constexpr const char* className = "ISaveGameWrapper"; using boost::python::wrapper::get_override; - virtual QString getFilename() const override { return basicWrapperFunctionImplementation(this, "getFilename"); }; - virtual QDateTime getCreationTime() const override { return basicWrapperFunctionImplementation(this, "getCreationTime"); }; - 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"); }; + virtual QString getFilename() const override { return basicWrapperFunctionImplementation(this, "getFilename"); }; + virtual QDateTime getCreationTime() const override { return basicWrapperFunctionImplementation(this, "getCreationTime"); }; + 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"); }; }; // This needs a wrapper but currently I have no idea how to expose this properly to python: @@ -45,7 +45,7 @@ public: // Bring the constructor: using ISaveGameInfoWidget::ISaveGameInfoWidget; - virtual void setSave(QString const& save) override { basicWrapperFunctionImplementation(this, "setSave", save); }; + virtual void setSave(QString const& save) override { basicWrapperFunctionImplementation(this, "setSave", save); }; }; #endif // UIBASEWRAPPERS_H From d5f44f424d5f63afa0693fb009e474ac0e412f49 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mika=C3=ABl=20Capelle?= Date: Thu, 21 May 2020 23:15:41 +0200 Subject: [PATCH 35/35] Add comments for basicWrapperFunction functions. --- src/runner/pythonwrapperutilities.h | 43 ++++++++++++++++++++++++++++- 1 file changed, 42 insertions(+), 1 deletion(-) diff --git a/src/runner/pythonwrapperutilities.h b/src/runner/pythonwrapperutilities.h index 6e80dbf..55d6af1 100644 --- a/src/runner/pythonwrapperutilities.h +++ b/src/runner/pythonwrapperutilities.h @@ -50,6 +50,17 @@ namespace details { /** * @brief Call the given method on the wrapper with the given arguments, with proper * exception handling. + * + * @param wrapper The wrapper object to use to retrieve the python method. Must have a publicly + * available `className` attribute. + * @param methodName The name of the method. + * @param args... Arguments for the method. + * + * @return the result of calling the given Python method on the wrapper. + * + * @throw pyexcept::MissingImplementation if the method does not exist. + * @throw pyexcept::PythonError if an error occurs while executing the python method. + * @throw pyexecpt::UnknownException if an unknown error occurs. */ template ReturnType basicWrapperFunctionImplementation(const WrapperType *wrapper, const char *methodName, Args... args) @@ -58,7 +69,20 @@ ReturnType basicWrapperFunctionImplementation(const WrapperType *wrapper, const } /** - * @brief Similar to the first-overload but also stores the python object in the given reference. + * @brief Call the given method on the wrapper with the given arguments, with proper + * exception handling, and store the intermediate result in the given python object. + * + * @param wrapper The wrapper object to use to retrieve the python method. Must have a publicly + * available `className` attribute. + * @param ref Python object to which the result of `get_override()` should be stored. + * @param methodName The name of the method. + * @param args... Arguments for the method. + * + * @return the result of calling the given Python method on the wrapper. + * + * @throw pyexcept::MissingImplementation if the method does not exist. + * @throw pyexcept::PythonError if an error occurs while executing the python method. + * @throw pyexecpt::UnknownException if an unknown error occurs. */ template ReturnType basicWrapperFunctionImplementation(const WrapperType* wrapper, boost::python::object &ref, const char* methodName, Args... args) @@ -66,6 +90,23 @@ ReturnType basicWrapperFunctionImplementation(const WrapperType* wrapper, boost: return details::wrapperFunctionImplementation(wrapper, nullptr, &ref, methodName, args...); } +/** + * @brief Call the given method on the wrapper with the given arguments, with proper + * exception handling, falling back to the given function if the method does not exist. + * + * @param wrapper The wrapper object to use to retrieve the python method. Must have a publicly + * available `className` attribute. + * @param fn The function to call if the method does not exists. + * @param methodName The name of the method. + * @param args... Arguments for the method. + * + * Note: `fn` does not have to be a member-function of `wrapper` but `std::invoke(fn, wrapper, args...)` must be valid. + * + * @return the result of calling the given Python method on the wrapper. + * + * @throw pyexcept::PythonError if an error occurs while executing the python method. + * @throw pyexecpt::UnknownException if an unknown error occurs. + */ template ReturnType basicWrapperFunctionImplementationWithDefault(WrapperTypePtr wrapper, Fn fn, const char* methodName, Args... args) {