mirror of
https://github.com/ModOrganizer2/modorganizer-plugin_python.git
synced 2026-07-27 14:03:33 -07:00
Refactoring of game features for better management. (#126)
This commit is contained in:
@@ -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);
|
||||
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
#include "pybind11_utils/shared_cpp_owner.h"
|
||||
#include "pybind11_utils/smart_variant_wrapper.h"
|
||||
|
||||
#include <game_feature.h>
|
||||
#include <isavegame.h>
|
||||
#include <pluginrequirements.h>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
#include <filemapping.h>
|
||||
#include <guessedvalue.h>
|
||||
#include <idownloadmanager.h>
|
||||
#include <igamefeatures.h>
|
||||
#include <iinstallationmanager.h>
|
||||
#include <imodinterface.h>
|
||||
#include <imodrepositorybridge.h>
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
#include <bsainvalidation.h>
|
||||
#include <dataarchives.h>
|
||||
#include <gameplugins.h>
|
||||
#include <igamefeatures.h>
|
||||
#include <localsavegames.h>
|
||||
#include <moddatachecker.h>
|
||||
#include <moddatacontent.h>
|
||||
@@ -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_<GameFeature, std::shared_ptr<GameFeature>>(m, "GameFeature");
|
||||
|
||||
// BSAInvalidation
|
||||
|
||||
py::class_<BSAInvalidation, PyBSAInvalidation>(m, "BSAInvalidation")
|
||||
py::class_<BSAInvalidation, GameFeature, PyBSAInvalidation,
|
||||
std::shared_ptr<BSAInvalidation>>(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_<DataArchives, PyDataArchives>(m, "DataArchives")
|
||||
py::class_<DataArchives, GameFeature, PyDataArchives,
|
||||
std::shared_ptr<DataArchives>>(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_<GamePlugins, PyGamePlugins>(m, "GamePlugins")
|
||||
py::class_<GamePlugins, GameFeature, PyGamePlugins,
|
||||
std::shared_ptr<GamePlugins>>(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_<LocalSavegames, PyLocalSavegames>(m, "LocalSavegames")
|
||||
py::class_<LocalSavegames, GameFeature, PyLocalSavegames,
|
||||
std::shared_ptr<LocalSavegames>>(m, "LocalSavegames")
|
||||
.def(py::init<>())
|
||||
.def("mappings", &LocalSavegames::mappings, "profile_save_dir"_a)
|
||||
.def("prepareProfile", &LocalSavegames::prepareProfile, "profile"_a);
|
||||
|
||||
// ModDataChecker
|
||||
|
||||
py::class_<ModDataChecker, PyModDataChecker> pyModDataChecker(m,
|
||||
"ModDataChecker");
|
||||
py::class_<ModDataChecker, GameFeature, PyModDataChecker,
|
||||
std::shared_ptr<ModDataChecker>>
|
||||
pyModDataChecker(m, "ModDataChecker");
|
||||
|
||||
py::enum_<ModDataChecker::CheckReturn>(pyModDataChecker, "CheckReturn")
|
||||
.value("INVALID", ModDataChecker::CheckReturn::INVALID)
|
||||
@@ -275,8 +287,9 @@ namespace mo2::python {
|
||||
.def("fix", &ModDataChecker::fix, "filetree"_a);
|
||||
|
||||
// ModDataContent
|
||||
py::class_<ModDataContent, PyModDataContent> pyModDataContent(m,
|
||||
"ModDataContent");
|
||||
py::class_<ModDataContent, GameFeature, PyModDataContent,
|
||||
std::shared_ptr<ModDataContent>>
|
||||
pyModDataContent(m, "ModDataContent");
|
||||
|
||||
py::class_<ModDataContent::Content>(pyModDataContent, "Content")
|
||||
.def(py::init<int, QString, QString, bool>(), "id"_a, "name"_a, "icon"_a,
|
||||
@@ -292,7 +305,8 @@ namespace mo2::python {
|
||||
|
||||
// SaveGameInfo
|
||||
|
||||
py::class_<SaveGameInfo, PySaveGameInfo>(m, "SaveGameInfo")
|
||||
py::class_<SaveGameInfo, GameFeature, PySaveGameInfo,
|
||||
std::shared_ptr<SaveGameInfo>>(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_<ScriptExtender, PyScriptExtender>(m, "ScriptExtender")
|
||||
py::class_<ScriptExtender, GameFeature, PyScriptExtender,
|
||||
std::shared_ptr<ScriptExtender>>(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_<UnmanagedMods, PyPyUnmanagedMods>(m, "UnmanagedMods")
|
||||
py::class_<UnmanagedMods, GameFeature, PyUnmanagedMods,
|
||||
std::shared_ptr<UnmanagedMods>>(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_<IGameFeatures>(m, "IGameFeatures")
|
||||
.def("registerFeature",
|
||||
py::overload_cast<QStringList const&, std::shared_ptr<GameFeature>,
|
||||
int, bool>(&IGameFeatures::registerFeature),
|
||||
"games"_a, "feature"_a, "priority"_a, "replace"_a = false)
|
||||
.def("registerFeature",
|
||||
py::overload_cast<MOBase::IPluginGame*, std::shared_ptr<GameFeature>,
|
||||
int, bool>(&IGameFeatures::registerFeature),
|
||||
"game"_a, "feature"_a, "priority"_a, "replace"_a = false)
|
||||
.def("registerFeature",
|
||||
py::overload_cast<std::shared_ptr<GameFeature>, 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<BSAInvalidation, DataArchives, GamePlugins,
|
||||
LocalSavegames, ModDataChecker, ModDataContent,
|
||||
SaveGameInfo, ScriptExtender, UnmanagedMods>;
|
||||
|
||||
template <class F, std::size_t... Is>
|
||||
static void helper(F&& f, std::index_sequence<Is...>)
|
||||
{
|
||||
(f(static_cast<std::tuple_element_t<Is, GameFeatures>*>(nullptr)), ...);
|
||||
(f(static_cast<
|
||||
std::tuple_element_t<Is, MOBase::details::BaseGameFeaturesP>>(
|
||||
nullptr)),
|
||||
...);
|
||||
}
|
||||
|
||||
public:
|
||||
@@ -349,45 +385,33 @@ namespace mo2::python {
|
||||
template <class F>
|
||||
static void apply(F&& f)
|
||||
{
|
||||
helper(f, std::make_index_sequence<std::tuple_size_v<GameFeatures>>{});
|
||||
helper(f, std::make_index_sequence<
|
||||
std::tuple_size_v<MOBase::details::BaseGameFeaturesP>>{});
|
||||
}
|
||||
};
|
||||
|
||||
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([&]<class Feature>(Feature*) {
|
||||
if (py::type::of<Feature>().is(type)) {
|
||||
py_feature = py::cast(game.feature<Feature>(),
|
||||
py_feature = py::cast(gameFeatures.gameFeature<Feature>(),
|
||||
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([&]<class Feature>(Feature*) {
|
||||
dict[py::type::of<Feature>()] =
|
||||
py::cast(game.feature<Feature>(), py::return_value_policy::reference);
|
||||
});
|
||||
return dict;
|
||||
}
|
||||
|
||||
std::map<std::type_index, std::any>
|
||||
convert_feature_list(py::dict const& py_features)
|
||||
{
|
||||
std::map<std::type_index, std::any> features;
|
||||
GameFeaturesHelper::apply([&]<class Feature>(Feature*) {
|
||||
const auto py_type = py::type::of<Feature>();
|
||||
if (py_features.contains(py_type)) {
|
||||
features[std::type_index(typeid(Feature))] =
|
||||
py_features[py_type].cast<Feature*>();
|
||||
if (py::type::of<Feature>().is(type)) {
|
||||
count = gameFeatures.unregisterFeatures<Feature>();
|
||||
}
|
||||
});
|
||||
return features;
|
||||
return count;
|
||||
}
|
||||
|
||||
} // namespace mo2::python
|
||||
|
||||
@@ -10,15 +10,6 @@ using namespace MOBase;
|
||||
|
||||
namespace mo2::python {
|
||||
|
||||
std::map<std::type_index, std::any> 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<IPluginGame, py::nodelete>>(
|
||||
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)
|
||||
|
||||
@@ -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<std::type_index, std::any> featureList() const override;
|
||||
};
|
||||
|
||||
} // namespace mo2::python
|
||||
|
||||
@@ -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<std::type_index, std::any>
|
||||
convert_feature_list(pybind11::dict const& py_features);
|
||||
int unregister_feature(MOBase::IGameFeatures& gameFeatures, pybind11::object type);
|
||||
|
||||
} // namespace mo2::python
|
||||
|
||||
|
||||
+12
-11
@@ -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) {
|
||||
|
||||
@@ -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<bool(const QString&)> &func), (override));
|
||||
|
||||
@@ -39,7 +39,7 @@ string(APPEND extra_paths "\\;${PYTHON_ROOT}/PCbuild/amd64")
|
||||
string(APPEND extra_paths "\\;$<TARGET_FILE_DIR:runner>")
|
||||
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}"
|
||||
)
|
||||
|
||||
@@ -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<IPluginGame*>(objects[0]);
|
||||
EXPECT_NE(plugin, nullptr);
|
||||
|
||||
// check features
|
||||
EXPECT_EQ(plugin->feature<LocalSavegames>(), nullptr);
|
||||
EXPECT_EQ(plugin->feature<ModDataContent>(), nullptr);
|
||||
EXPECT_NE(plugin->feature<ModDataChecker>(), nullptr);
|
||||
EXPECT_NE(plugin->feature<SaveGameInfo>(), nullptr);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user