diff --git a/src/mobase/mobase.cpp b/src/mobase/mobase.cpp index efe41ab..575e716 100644 --- a/src/mobase/mobase.cpp +++ b/src/mobase/mobase.cpp @@ -48,6 +48,7 @@ PYBIND11_MODULE(mobase, m) // game features must be added before plugins mo2::python::add_game_feature_bindings(m); + mo2::python::add_igamefeatures_classes(m); mo2::python::add_plugins_bindings(m); diff --git a/src/mobase/pybind11_all.h b/src/mobase/pybind11_all.h index 096c13a..019e98d 100644 --- a/src/mobase/pybind11_all.h +++ b/src/mobase/pybind11_all.h @@ -16,6 +16,7 @@ #include "pybind11_utils/shared_cpp_owner.h" #include "pybind11_utils/smart_variant_wrapper.h" +#include #include #include @@ -139,5 +140,6 @@ namespace mo2::python { MO2_PYBIND11_SHARED_CPP_HOLDER(MOBase::IPluginRequirement) MO2_PYBIND11_SHARED_CPP_HOLDER(MOBase::ISaveGame) +MO2_PYBIND11_SHARED_CPP_HOLDER(MOBase::GameFeature) #endif diff --git a/src/mobase/wrappers/basic_classes.cpp b/src/mobase/wrappers/basic_classes.cpp index 169c4f3..72f78ec 100644 --- a/src/mobase/wrappers/basic_classes.cpp +++ b/src/mobase/wrappers/basic_classes.cpp @@ -8,6 +8,7 @@ #include #include #include +#include #include #include #include @@ -514,6 +515,8 @@ namespace mo2::python { .def("pluginList", &IOrganizer::pluginList, py::return_value_policy::reference) .def("modList", &IOrganizer::modList, py::return_value_policy::reference) + .def("gameFeatures", &IOrganizer::gameFeatures, + py::return_value_policy::reference) .def("profile", &IOrganizer::profile, py::return_value_policy::reference) // custom implementation for startApplication and @@ -775,7 +778,7 @@ namespace mo2::python { [](const IProfile* p) { bool supported; bool active = p->invalidationActive(&supported); - return py::make_tuple(active, supported); + return std::make_tuple(active, supported); }) .def("absoluteIniFilePath", &IProfile::absoluteIniFilePath, "inifile"_a); diff --git a/src/mobase/wrappers/game_features.cpp b/src/mobase/wrappers/game_features.cpp index 257cdff..de5e13d 100644 --- a/src/mobase/wrappers/game_features.cpp +++ b/src/mobase/wrappers/game_features.cpp @@ -10,6 +10,7 @@ #include #include #include +#include #include #include #include @@ -20,6 +21,7 @@ #include "pyfiletree.h" namespace py = pybind11; + using namespace MOBase; using namespace pybind11::literals; @@ -198,7 +200,7 @@ namespace mo2::python { } }; - class PyPyUnmanagedMods : public UnmanagedMods { + class PyUnmanagedMods : public UnmanagedMods { public: QStringList mods(bool onlyOfficial) const override { @@ -223,9 +225,15 @@ namespace mo2::python { void add_game_feature_bindings(pybind11::module_ m) { + // this is just to allow accepting GameFeature in function, we do not expose + // anything from game feature to Python since typeInfo() is useless in Python + // + py::class_>(m, "GameFeature"); + // BSAInvalidation - py::class_(m, "BSAInvalidation") + py::class_>(m, "BSAInvalidation") .def(py::init<>()) .def("isInvalidationBSA", &BSAInvalidation::isInvalidationBSA, "name"_a) .def("deactivate", &BSAInvalidation::deactivate, "profile"_a) @@ -233,7 +241,8 @@ namespace mo2::python { // DataArchives - py::class_(m, "DataArchives") + py::class_>(m, "DataArchives") .def(py::init<>()) .def("vanillaArchives", &DataArchives::vanillaArchives) .def("archives", &DataArchives::archives, "profile"_a) @@ -243,7 +252,8 @@ namespace mo2::python { // GamePlugins - py::class_(m, "GamePlugins") + py::class_>(m, "GamePlugins") .def(py::init<>()) .def("writePluginLists", &GamePlugins::writePluginLists, "plugin_list"_a) .def("readPluginLists", &GamePlugins::readPluginLists, "plugin_list"_a) @@ -254,15 +264,17 @@ namespace mo2::python { // LocalSavegames - py::class_(m, "LocalSavegames") + py::class_>(m, "LocalSavegames") .def(py::init<>()) .def("mappings", &LocalSavegames::mappings, "profile_save_dir"_a) .def("prepareProfile", &LocalSavegames::prepareProfile, "profile"_a); // ModDataChecker - py::class_ pyModDataChecker(m, - "ModDataChecker"); + py::class_> + pyModDataChecker(m, "ModDataChecker"); py::enum_(pyModDataChecker, "CheckReturn") .value("INVALID", ModDataChecker::CheckReturn::INVALID) @@ -275,8 +287,9 @@ namespace mo2::python { .def("fix", &ModDataChecker::fix, "filetree"_a); // ModDataContent - py::class_ pyModDataContent(m, - "ModDataContent"); + py::class_> + pyModDataContent(m, "ModDataContent"); py::class_(pyModDataContent, "Content") .def(py::init(), "id"_a, "name"_a, "icon"_a, @@ -292,7 +305,8 @@ namespace mo2::python { // SaveGameInfo - py::class_(m, "SaveGameInfo") + py::class_>(m, "SaveGameInfo") .def(py::init<>()) .def("getMissingAssets", &SaveGameInfo::getMissingAssets, "save"_a) .def("getSaveGameWidget", &SaveGameInfo::getSaveGameWidget, @@ -300,7 +314,8 @@ namespace mo2::python { // ScriptExtender - py::class_(m, "ScriptExtender") + py::class_>(m, "ScriptExtender") .def(py::init<>()) .def("binaryName", &ScriptExtender::BinaryName) .def("pluginPath", wrap_return_for_directory(&ScriptExtender::PluginPath)) @@ -313,7 +328,8 @@ namespace mo2::python { // UnmanagedMods - py::class_(m, "UnmanagedMods") + py::class_>(m, "UnmanagedMods") .def(py::init<>()) .def("mods", &UnmanagedMods::mods, "official_only"_a) .def("displayName", &UnmanagedMods::displayName, "mod_name"_a) @@ -328,19 +344,39 @@ namespace mo2::python { "mod_name"_a); } + void add_igamefeatures_classes(py::module_ m) + { + py::class_(m, "IGameFeatures") + .def("registerFeature", + py::overload_cast, + int, bool>(&IGameFeatures::registerFeature), + "games"_a, "feature"_a, "priority"_a, "replace"_a = false) + .def("registerFeature", + py::overload_cast, + int, bool>(&IGameFeatures::registerFeature), + "game"_a, "feature"_a, "priority"_a, "replace"_a = false) + .def("registerFeature", + py::overload_cast, int, bool>( + &IGameFeatures::registerFeature), + "feature"_a, "priority"_a, "replace"_a = false) + .def("unregisterFeature", &IGameFeatures::unregisterFeature, "feature"_a) + .def("unregisterFeatures", &unregister_feature, "feature_type"_a) + .def("gameFeature", &extract_feature, "feature_type"_a, + py ::return_value_policy::reference); + } + } // namespace mo2::python namespace mo2::python { class GameFeaturesHelper { - using GameFeatures = std::tuple; - template static void helper(F&& f, std::index_sequence) { - (f(static_cast*>(nullptr)), ...); + (f(static_cast< + std::tuple_element_t>( + nullptr)), + ...); } public: @@ -349,45 +385,33 @@ namespace mo2::python { template static void apply(F&& f) { - helper(f, std::make_index_sequence>{}); + helper(f, std::make_index_sequence< + std::tuple_size_v>{}); } }; - pybind11::object extract_feature(IPluginGame const& game, pybind11::object type) + pybind11::object extract_feature(IGameFeatures const& gameFeatures, + pybind11::object type) { py::object py_feature = py::none(); GameFeaturesHelper::apply([&](Feature*) { if (py::type::of().is(type)) { - py_feature = py::cast(game.feature(), + py_feature = py::cast(gameFeatures.gameFeature(), py::return_value_policy::reference); } }); return py_feature; } - pybind11::dict extract_feature_list(IPluginGame const& game) + int unregister_feature(MOBase::IGameFeatures& gameFeatures, pybind11::object type) { - // constructing a dict from class name to actual object - py::dict dict; + int count = 0; GameFeaturesHelper::apply([&](Feature*) { - dict[py::type::of()] = - py::cast(game.feature(), py::return_value_policy::reference); - }); - return dict; - } - - std::map - convert_feature_list(py::dict const& py_features) - { - std::map features; - GameFeaturesHelper::apply([&](Feature*) { - const auto py_type = py::type::of(); - if (py_features.contains(py_type)) { - features[std::type_index(typeid(Feature))] = - py_features[py_type].cast(); + if (py::type::of().is(type)) { + count = gameFeatures.unregisterFeatures(); } }); - return features; + return count; } } // namespace mo2::python diff --git a/src/mobase/wrappers/pyplugins.cpp b/src/mobase/wrappers/pyplugins.cpp index 01465ea..21fd436 100644 --- a/src/mobase/wrappers/pyplugins.cpp +++ b/src/mobase/wrappers/pyplugins.cpp @@ -10,15 +10,6 @@ using namespace MOBase; namespace mo2::python { - std::map PyPluginGame::featureList() const - { - py::dict pyFeatures = [this]() { - PYBIND11_OVERRIDE_PURE(py::dict, IPluginGame, _featureList, ); - }(); - - return convert_feature_list(pyFeatures); - } - // this one is kind of big so it has its own function void add_iplugingame_bindings(pybind11::module_ m) { @@ -54,13 +45,9 @@ namespace mo2::python { std::unique_ptr>( m, "IPluginGame", py::multiple_inheritance()) .def(py::init<>()) - - .def("featureList", &extract_feature_list) - .def("feature", &extract_feature, "feature_type"_a, - py::return_value_policy::reference) - .def("detectGame", &IPluginGame::detectGame) .def("gameName", &IPluginGame::gameName) + .def("displayGameName", &IPluginGame::displayGameName) .def("initializeProfile", &IPluginGame::initializeProfile, "directory"_a, "settings"_a) .def("listSaves", &IPluginGame::listSaves, "folder"_a) @@ -81,6 +68,7 @@ namespace mo2::python { .def("setGameVariant", &IPluginGame::setGameVariant, "variant"_a) .def("binaryName", &IPluginGame::binaryName) .def("gameShortName", &IPluginGame::gameShortName) + .def("lootGameName", &IPluginGame::lootGameName) .def("primarySources", &IPluginGame::primarySources) .def("validShortNames", &IPluginGame::validShortNames) .def("gameNexusName", &IPluginGame::gameNexusName) diff --git a/src/mobase/wrappers/pyplugins.h b/src/mobase/wrappers/pyplugins.h index fb5af23..ff2b479 100644 --- a/src/mobase/wrappers/pyplugins.h +++ b/src/mobase/wrappers/pyplugins.h @@ -353,6 +353,10 @@ namespace mo2::python { { PYBIND11_OVERRIDE_PURE(QString, IPluginGame, gameName, ); } + QString displayGameName() const override + { + PYBIND11_OVERRIDE(QString, IPluginGame, displayGameName, ); + } void initializeProfile(const QDir& directory, ProfileSettings settings) const override { @@ -435,6 +439,10 @@ namespace mo2::python { { PYBIND11_OVERRIDE_PURE(QString, IPluginGame, gameShortName, ); } + QString lootGameName() const override + { + PYBIND11_OVERRIDE(QString, IPluginGame, lootGameName, ); + } QStringList primarySources() const override { PYBIND11_OVERRIDE(QStringList, IPluginGame, primarySources, ); @@ -491,9 +499,6 @@ namespace mo2::python { { PYBIND11_OVERRIDE(QString, IPluginGame, getSupportURL, ); } - - protected: - std::map featureList() const override; }; } // namespace mo2::python diff --git a/src/mobase/wrappers/wrappers.h b/src/mobase/wrappers/wrappers.h index 6847926..d4af629 100644 --- a/src/mobase/wrappers/wrappers.h +++ b/src/mobase/wrappers/wrappers.h @@ -77,36 +77,32 @@ namespace mo2::python { void add_game_feature_bindings(pybind11::module_ m); /** - * @brief Create the game feature corresponding to the given Python type from the - * given game. + * @brief Add bindings for IGameFeatures. * - * @param game Game plugin to extract the feature from. + * @param m Python module to add bindings to. + */ + void add_igamefeatures_classes(pybind11::module_ m); + + /** + * @brief Extract the game feature corresponding to the given Python type. + * + * @param gameFeatures Game features to extract the feature from. * @param type Type of the feature to extract. * * @return the feature from the game, or None is the game as no such feature. */ - pybind11::object extract_feature(MOBase::IPluginGame const& game, + pybind11::object extract_feature(MOBase::IGameFeatures const& gameFeatures, pybind11::object type); /** - * @brief Create Python dictionary mapping game feature classes to the game feature - * instances for the given game. + * @brief Unregister the game feature corresponding to the given Python type. * - * @param game Game plugin to extract features from. + * @param gameFeatures Game features to unregister the feature from. + * @param type Type of the feature to unregister. * - * @return a python dictionary mapping feature types (in Python) to feature objects. + * @return the feature from the game, or None is the game as no such feature. */ - pybind11::dict extract_feature_list(MOBase::IPluginGame const& game); - - /** - * @brief Convert the given python map of features to a C++ one. - * - * @param py_features Python features to convert (type to feature). - * - * @return the map of features. - */ - std::map - convert_feature_list(pybind11::dict const& py_features); + int unregister_feature(MOBase::IGameFeatures& gameFeatures, pybind11::object type); } // namespace mo2::python diff --git a/src/runner/pythonrunner.cpp b/src/runner/pythonrunner.cpp index 69d2b29..20e55d2 100644 --- a/src/runner/pythonrunner.cpp +++ b/src/runner/pythonrunner.cpp @@ -82,17 +82,6 @@ namespace mo2::python { try { static const char* argv0 = "ModOrganizer.exe"; - PyConfig config; - PyConfig_InitIsolatedConfig(&config); - - // from PyBind11 - config.parse_argv = 0; - config.install_signal_handlers = 0; - - // from MO2 - config.site_import = 1; - config.optimization_level = 2; - // set the module search paths // auto paths = pythonPaths; @@ -110,6 +99,18 @@ namespace mo2::python { } } + PyConfig config; + PyConfig_InitIsolatedConfig(&config); + + // from PyBind11 + config.parse_argv = 0; + config.install_signal_handlers = 0; + + // from MO2 + config.site_import = 1; + config.optimization_level = 2; + + // set paths to configuration if (!paths.empty()) { config.module_search_paths_set = 1; for (auto const& path : paths) { diff --git a/tests/mocks/MockOrganizer.h b/tests/mocks/MockOrganizer.h index 1e393ea..5ef98e8 100644 --- a/tests/mocks/MockOrganizer.h +++ b/tests/mocks/MockOrganizer.h @@ -36,6 +36,7 @@ public: MOCK_METHOD(MOBase::IPluginList*, pluginList, (), (const, override)); MOCK_METHOD(MOBase::IModList*, modList, (), (const, override)); MOCK_METHOD(MOBase::IProfile*, profile, (), (const, override)); + MOCK_METHOD(MOBase::IGameFeatures*, gameFeatures, (), (const, override)); MOCK_METHOD(HANDLE, startApplication, (const QString &executable, const QStringList &args, const QString &cwd, const QString &profile, const QString &forcedCustomOverwrite, bool ignoreCustomOverwrite), (override)); MOCK_METHOD(bool, waitForApplication, (HANDLE handle, bool refresh, LPDWORD exitCode), (const, override)); MOCK_METHOD(bool, onAboutToRun, (const std::function &func), (override)); diff --git a/tests/runner/CMakeLists.txt b/tests/runner/CMakeLists.txt index 71a6843..73e344c 100644 --- a/tests/runner/CMakeLists.txt +++ b/tests/runner/CMakeLists.txt @@ -39,7 +39,7 @@ string(APPEND extra_paths "\\;${PYTHON_ROOT}/PCbuild/amd64") string(APPEND extra_paths "\\;$") set_tests_properties(${runner-tests_gtests} PROPERTIES - WORKING_DIRECTORY "${MO2_INSTALL_PATH}/bin" ENVIRONMENT "PLUGIN_DIR=${CMAKE_CURRENT_SOURCE_DIR}/plugins" ENVIRONMENT_MODIFICATION - "PATH=path_list_prepend:${extra_paths};PYTHONPATH=set:${PYTHONPATH}") + "PATH=path_list_prepend:${extra_paths};PYTHONPATH=set:${PYTHONPATH}" +) diff --git a/tests/runner/test_game.cpp b/tests/runner/test_game.cpp index cba4ea3..7e96d42 100644 --- a/tests/runner/test_game.cpp +++ b/tests/runner/test_game.cpp @@ -7,10 +7,6 @@ #include "MockOrganizer.h" #include "iplugingame.h" -#include "localsavegames.h" -#include "moddatachecker.h" -#include "moddatacontent.h" -#include "savegameinfo.h" using namespace MOBase; @@ -28,10 +24,4 @@ TEST(IPluginGame, Simple) // load the IPlugin IPluginGame* plugin = qobject_cast(objects[0]); EXPECT_NE(plugin, nullptr); - - // check features - EXPECT_EQ(plugin->feature(), nullptr); - EXPECT_EQ(plugin->feature(), nullptr); - EXPECT_NE(plugin->feature(), nullptr); - EXPECT_NE(plugin->feature(), nullptr); }