diff --git a/src/proxy/proxypython.cpp b/src/proxy/proxypython.cpp index 6d75ee7..73dec01 100644 --- a/src/proxy/proxypython.cpp +++ b/src/proxy/proxypython.cpp @@ -216,7 +216,7 @@ QString ProxyPython::description() const VersionInfo ProxyPython::version() const { - return VersionInfo(2, 1, 0, VersionInfo::RELEASE_FINAL); + return VersionInfo(2, 2, 0, VersionInfo::RELEASE_FINAL); } QList ProxyPython::settings() const diff --git a/src/runner/gamefeatureswrappers.cpp b/src/runner/gamefeatureswrappers.cpp index f83dd2a..936bf7f 100644 --- a/src/runner/gamefeatureswrappers.cpp +++ b/src/runner/gamefeatureswrappers.cpp @@ -9,6 +9,7 @@ #include #include +#include "shared_ptr_converter.h" #include "ifiletree.h" #include "pythonwrapperutilities.h" @@ -108,7 +109,9 @@ ModDataChecker::CheckReturn ModDataCheckerWrapper::dataLooksValid(std::shared_pt } std::shared_ptr ModDataCheckerWrapper::fix(std::shared_ptr fileTree) const { - return basicWrapperFunctionImplementationWithDefault>(this, [](auto&&... args) { return nullptr; }, "fix", fileTree); + return utils::clean_shared_ptr( + basicWrapperFunctionImplementationWithDefault>( + this, [](auto&&... args) { return nullptr; }, "fix", fileTree)); } /// end ModDataChecker Wrapper diff --git a/src/runner/proxypluginwrappers.cpp b/src/runner/proxypluginwrappers.cpp index 3d6e99e..e89a7cc 100644 --- a/src/runner/proxypluginwrappers.cpp +++ b/src/runner/proxypluginwrappers.cpp @@ -4,6 +4,7 @@ #include #include +#include "shared_ptr_converter.h" #include "pythonwrapperutilities.h" #include "uibasewrappers.h" @@ -76,7 +77,12 @@ BOOST_PP_EXPR_IF(include_requirements, \ return basicWrapperFunctionImplementationWithDefault>>( \ this, &class_name::requirements_Default, "requirements"); \ } \ - std::vector> class_name::requirements_Default() const { return IPlugin::requirements(); }) + std::vector> class_name::requirements_Default() const { return IPlugin::requirements(); } \ + bool class_name::enabledByDefault() const \ + { \ + return basicWrapperFunctionImplementationWithDefault(this, &class_name::enabledByDefault_Default, "enabledByDefault"); \ + } \ + bool class_name::enabledByDefault_Default() const { return IPlugin::enabledByDefault(); }) #define COMMON_I_PLUGIN_WRAPPER_DEFINITIONS(class_name) COMMON_I_PLUGIN_WRAPPER_DEFINITIONS_IMPL(class_name, 1) @@ -330,10 +336,10 @@ IPluginInstaller::EInstallResult IPluginInstallerSimpleWrapper::install( using return_type = std::variant< IPluginInstaller::EInstallResult, std::shared_ptr, - std::tuple, QString, int>> ; + std::tuple, QString, int>>; auto ret = basicWrapperFunctionImplementation(this, "install", boost::ref(modName), tree, version, nexusID); - return std::visit([&](auto const& t) { + auto result = std::visit([&](auto const& t) { using type = std::decay_t; if constexpr (std::is_same_v) { return t; @@ -349,6 +355,9 @@ IPluginInstaller::EInstallResult IPluginInstallerSimpleWrapper::install( return std::get<0>(t); } }, ret); + + tree = utils::clean_shared_ptr(tree); + return result; } /// end IPluginInstallerSimple Wrapper diff --git a/src/runner/proxypluginwrappers.h b/src/runner/proxypluginwrappers.h index 1d71f2e..ccc20b0 100644 --- a/src/runner/proxypluginwrappers.h +++ b/src/runner/proxypluginwrappers.h @@ -16,7 +16,7 @@ #include #endif -// The wrapper for IPluginGame cannot override requirements() since it's final, +// The wrapper for IPluginGame cannot override requirements or enabledByDefault since they're final, // so we need to be able to exclude the declarations. #define COMMON_I_PLUGIN_WRAPPER_DECLARATIONS_IMPL(include_requirements) \ public: \ @@ -32,7 +32,9 @@ QString localizedName_Default() const; \ QString master_Default() const; \ BOOST_PP_EXPR_IF(include_requirements, \ virtual std::vector> requirements() const override; \ - std::vector> requirements_Default() const;) + std::vector> requirements_Default() const; \ + virtual bool enabledByDefault() const override; \ + bool enabledByDefault_Default() const;) #define COMMON_I_PLUGIN_WRAPPER_DECLARATIONS COMMON_I_PLUGIN_WRAPPER_DECLARATIONS_IMPL(1) diff --git a/src/runner/pythonrunner.cpp b/src/runner/pythonrunner.cpp index 0cce0d1..6578059 100644 --- a/src/runner/pythonrunner.cpp +++ b/src/runner/pythonrunner.cpp @@ -867,7 +867,7 @@ BOOST_PYTHON_MODULE(mobase) .def("onModMoved", &MOBase::IModList::onModMoved, bpy::arg("callback")) ; - // Note: localizedName(), master() and requirements have to go in all the plugin wrappers declaration, + // Note: localizedName, master, requirements and enabledByDefault have to go in all the plugin wrappers declaration, // since the default functions are specific to each wrapper, otherwise in turns into an // infinite recursion mess. bpy::class_("IPlugin") @@ -880,12 +880,14 @@ BOOST_PYTHON_MODULE(mobase) .def("version", bpy::pure_virtual(&MOBase::IPlugin::version)) .def("requirements", &MOBase::IPlugin::requirements, &IPluginWrapper::requirements_Default) .def("settings", bpy::pure_virtual(&MOBase::IPlugin::settings)) + .def("enabledByDefault", &MOBase::IPlugin::enabledByDefault, &IPluginWrapper::enabledByDefault_Default) ; bpy::class_, boost::noncopyable>("IPluginDiagnose") .def("localizedName", &MOBase::IPlugin::localizedName, &IPluginDiagnoseWrapper::localizedName_Default) .def("master", &MOBase::IPlugin::master, &IPluginDiagnoseWrapper::master_Default) .def("requirements", &MOBase::IPlugin::requirements, &IPluginDiagnoseWrapper::requirements_Default) + .def("enabledByDefault", &MOBase::IPlugin::enabledByDefault, &IPluginDiagnoseWrapper::enabledByDefault_Default) .def("activeProblems", bpy::pure_virtual(&MOBase::IPluginDiagnose::activeProblems)) .def("shortDescription", bpy::pure_virtual(&MOBase::IPluginDiagnose::shortDescription), bpy::arg("key")) @@ -913,6 +915,8 @@ BOOST_PYTHON_MODULE(mobase) .def("localizedName", &MOBase::IPlugin::localizedName, &IPluginFileMapperWrapper::localizedName_Default) .def("master", &MOBase::IPlugin::master, &IPluginFileMapperWrapper::master_Default) .def("requirements", &MOBase::IPlugin::requirements, &IPluginFileMapperWrapper::requirements_Default) + .def("enabledByDefault", &MOBase::IPlugin::enabledByDefault, &IPluginFileMapperWrapper::enabledByDefault_Default) + .def("mappings", bpy::pure_virtual(&MOBase::IPluginFileMapper::mappings)) ; @@ -1045,6 +1049,7 @@ BOOST_PYTHON_MODULE(mobase) .def("localizedName", &MOBase::IPlugin::localizedName, &IPluginInstallerSimpleWrapper::localizedName_Default) .def("master", &MOBase::IPlugin::master, &IPluginInstallerSimpleWrapper::master_Default) .def("requirements", &MOBase::IPlugin::requirements, &IPluginInstallerSimpleWrapper::requirements_Default) + .def("enabledByDefault", &MOBase::IPlugin::enabledByDefault, &IPluginInstallerSimpleWrapper::enabledByDefault_Default) // Note: Keeping the variant here even if we always return a tuple to be consistent with the wrapper and // have proper stubs generation. @@ -1063,6 +1068,7 @@ BOOST_PYTHON_MODULE(mobase) .def("localizedName", &MOBase::IPlugin::localizedName, &IPluginInstallerCustomWrapper::localizedName_Default) .def("master", &MOBase::IPlugin::master, &IPluginInstallerCustomWrapper::master_Default) .def("requirements", &MOBase::IPlugin::requirements, &IPluginInstallerCustomWrapper::requirements_Default) + .def("enabledByDefault", &MOBase::IPlugin::enabledByDefault, &IPluginInstallerCustomWrapper::enabledByDefault_Default) // Needs to add both otherwize boost does not understand: .def("isArchiveSupported", &IPluginInstaller::isArchiveSupported, bpy::arg("tree")) @@ -1077,6 +1083,7 @@ BOOST_PYTHON_MODULE(mobase) .def("localizedName", &MOBase::IPlugin::localizedName, &IPluginModPageWrapper::localizedName_Default) .def("master", &MOBase::IPlugin::master, &IPluginModPageWrapper::master_Default) .def("requirements", &MOBase::IPlugin::requirements, &IPluginModPageWrapper::requirements_Default) + .def("enabledByDefault", &MOBase::IPlugin::enabledByDefault, &IPluginModPageWrapper::enabledByDefault_Default) .def("displayName", bpy::pure_virtual(&IPluginModPage::displayName)) .def("icon", bpy::pure_virtual(&IPluginModPage::icon)) @@ -1091,6 +1098,7 @@ BOOST_PYTHON_MODULE(mobase) .def("localizedName", &MOBase::IPlugin::localizedName, &IPluginPreviewWrapper::localizedName_Default) .def("master", &MOBase::IPlugin::master, &IPluginPreviewWrapper::master_Default) .def("requirements", &MOBase::IPlugin::requirements, &IPluginPreviewWrapper::requirements_Default) + .def("enabledByDefault", &MOBase::IPlugin::enabledByDefault, &IPluginPreviewWrapper::enabledByDefault_Default) .def("supportedExtensions", bpy::pure_virtual(&IPluginPreview::supportedExtensions)) .def("genFilePreview", bpy::pure_virtual(&IPluginPreview::genFilePreview), bpy::return_value_policy(), @@ -1101,6 +1109,7 @@ BOOST_PYTHON_MODULE(mobase) .def("localizedName", &MOBase::IPlugin::localizedName, &IPluginToolWrapper::localizedName_Default) .def("master", &MOBase::IPlugin::master, &IPluginToolWrapper::master_Default) .def("requirements", &MOBase::IPlugin::requirements, &IPluginToolWrapper::requirements_Default) + .def("enabledByDefault", &MOBase::IPlugin::enabledByDefault, &IPluginToolWrapper::enabledByDefault_Default) .def("displayName", bpy::pure_virtual(&IPluginTool::displayName)) .def("tooltip", bpy::pure_virtual(&IPluginTool::tooltip)) @@ -1319,13 +1328,14 @@ 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" "sys.excepthook = lambda x, y, z: sys.__excepthook__(x, y, z)\n", mainNamespace); + + mainNamespace["mobase"] = bpy::import("mobase"); configure_python_logging(mainNamespace["mobase"]); PyEval_SaveThread(); diff --git a/src/runner/shared_ptr_converter.h b/src/runner/shared_ptr_converter.h index 8c3cf83..ab80ac3 100644 --- a/src/runner/shared_ptr_converter.h +++ b/src/runner/shared_ptr_converter.h @@ -96,6 +96,30 @@ namespace utils { } }; + // release the bpy::object associated with the deleter of the given shared_ptr, + // if the given shared_ptr has a Boost.Python deleter + // + // this should only be used when returning from Python objects that have been created + // on the C++ side, e.g. if IFileTree.createOrphanTree() from Python and then return + // the tree + // + // for reason yet to be known, Boost.Python had a custom deleter in this case that tries + // to delete the bpy::object and fails, so we have to release the object manually + // + template + SharedPtr clean_shared_ptr(SharedPtr&& ptr) { + if (auto* d = get_deleter(ptr); d != nullptr) { + // we cannot do a proper reset() here, even with the GIL lock, for unknown reason, + // so we only release + // + // this might create lost references to Python object but this should not happen + // too often so hopefully it's not a big issue + // + d->owner.release(); + } + return ptr; + } + } #endif \ No newline at end of file