Skip to content

Commit 790d0a1

Browse files
authored
Refactoring of game features for better management. (#126)
1 parent a966db7 commit 790d0a1

File tree

11 files changed

+109
-98
lines changed

11 files changed

+109
-98
lines changed

src/mobase/mobase.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ PYBIND11_MODULE(mobase, m)
4848

4949
// game features must be added before plugins
5050
mo2::python::add_game_feature_bindings(m);
51+
mo2::python::add_igamefeatures_classes(m);
5152

5253
mo2::python::add_plugins_bindings(m);
5354

src/mobase/pybind11_all.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
#include "pybind11_utils/shared_cpp_owner.h"
1717
#include "pybind11_utils/smart_variant_wrapper.h"
1818

19+
#include <game_feature.h>
1920
#include <isavegame.h>
2021
#include <pluginrequirements.h>
2122

@@ -139,5 +140,6 @@ namespace mo2::python {
139140

140141
MO2_PYBIND11_SHARED_CPP_HOLDER(MOBase::IPluginRequirement)
141142
MO2_PYBIND11_SHARED_CPP_HOLDER(MOBase::ISaveGame)
143+
MO2_PYBIND11_SHARED_CPP_HOLDER(MOBase::GameFeature)
142144

143145
#endif

src/mobase/wrappers/basic_classes.cpp

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
#include <filemapping.h>
99
#include <guessedvalue.h>
1010
#include <idownloadmanager.h>
11+
#include <igamefeatures.h>
1112
#include <iinstallationmanager.h>
1213
#include <imodinterface.h>
1314
#include <imodrepositorybridge.h>
@@ -514,6 +515,8 @@ namespace mo2::python {
514515
.def("pluginList", &IOrganizer::pluginList,
515516
py::return_value_policy::reference)
516517
.def("modList", &IOrganizer::modList, py::return_value_policy::reference)
518+
.def("gameFeatures", &IOrganizer::gameFeatures,
519+
py::return_value_policy::reference)
517520
.def("profile", &IOrganizer::profile, py::return_value_policy::reference)
518521

519522
// custom implementation for startApplication and
@@ -775,7 +778,7 @@ namespace mo2::python {
775778
[](const IProfile* p) {
776779
bool supported;
777780
bool active = p->invalidationActive(&supported);
778-
return py::make_tuple(active, supported);
781+
return std::make_tuple(active, supported);
779782
})
780783
.def("absoluteIniFilePath", &IProfile::absoluteIniFilePath, "inifile"_a);
781784

src/mobase/wrappers/game_features.cpp

Lines changed: 63 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
#include <bsainvalidation.h>
1111
#include <dataarchives.h>
1212
#include <gameplugins.h>
13+
#include <igamefeatures.h>
1314
#include <localsavegames.h>
1415
#include <moddatachecker.h>
1516
#include <moddatacontent.h>
@@ -20,6 +21,7 @@
2021
#include "pyfiletree.h"
2122

2223
namespace py = pybind11;
24+
2325
using namespace MOBase;
2426
using namespace pybind11::literals;
2527

@@ -198,7 +200,7 @@ namespace mo2::python {
198200
}
199201
};
200202

201-
class PyPyUnmanagedMods : public UnmanagedMods {
203+
class PyUnmanagedMods : public UnmanagedMods {
202204
public:
203205
QStringList mods(bool onlyOfficial) const override
204206
{
@@ -223,17 +225,24 @@ namespace mo2::python {
223225

224226
void add_game_feature_bindings(pybind11::module_ m)
225227
{
228+
// this is just to allow accepting GameFeature in function, we do not expose
229+
// anything from game feature to Python since typeInfo() is useless in Python
230+
//
231+
py::class_<GameFeature, std::shared_ptr<GameFeature>>(m, "GameFeature");
232+
226233
// BSAInvalidation
227234

228-
py::class_<BSAInvalidation, PyBSAInvalidation>(m, "BSAInvalidation")
235+
py::class_<BSAInvalidation, GameFeature, PyBSAInvalidation,
236+
std::shared_ptr<BSAInvalidation>>(m, "BSAInvalidation")
229237
.def(py::init<>())
230238
.def("isInvalidationBSA", &BSAInvalidation::isInvalidationBSA, "name"_a)
231239
.def("deactivate", &BSAInvalidation::deactivate, "profile"_a)
232240
.def("activate", &BSAInvalidation::activate, "profile"_a);
233241

234242
// DataArchives
235243

236-
py::class_<DataArchives, PyDataArchives>(m, "DataArchives")
244+
py::class_<DataArchives, GameFeature, PyDataArchives,
245+
std::shared_ptr<DataArchives>>(m, "DataArchives")
237246
.def(py::init<>())
238247
.def("vanillaArchives", &DataArchives::vanillaArchives)
239248
.def("archives", &DataArchives::archives, "profile"_a)
@@ -243,7 +252,8 @@ namespace mo2::python {
243252

244253
// GamePlugins
245254

246-
py::class_<GamePlugins, PyGamePlugins>(m, "GamePlugins")
255+
py::class_<GamePlugins, GameFeature, PyGamePlugins,
256+
std::shared_ptr<GamePlugins>>(m, "GamePlugins")
247257
.def(py::init<>())
248258
.def("writePluginLists", &GamePlugins::writePluginLists, "plugin_list"_a)
249259
.def("readPluginLists", &GamePlugins::readPluginLists, "plugin_list"_a)
@@ -254,15 +264,17 @@ namespace mo2::python {
254264

255265
// LocalSavegames
256266

257-
py::class_<LocalSavegames, PyLocalSavegames>(m, "LocalSavegames")
267+
py::class_<LocalSavegames, GameFeature, PyLocalSavegames,
268+
std::shared_ptr<LocalSavegames>>(m, "LocalSavegames")
258269
.def(py::init<>())
259270
.def("mappings", &LocalSavegames::mappings, "profile_save_dir"_a)
260271
.def("prepareProfile", &LocalSavegames::prepareProfile, "profile"_a);
261272

262273
// ModDataChecker
263274

264-
py::class_<ModDataChecker, PyModDataChecker> pyModDataChecker(m,
265-
"ModDataChecker");
275+
py::class_<ModDataChecker, GameFeature, PyModDataChecker,
276+
std::shared_ptr<ModDataChecker>>
277+
pyModDataChecker(m, "ModDataChecker");
266278

267279
py::enum_<ModDataChecker::CheckReturn>(pyModDataChecker, "CheckReturn")
268280
.value("INVALID", ModDataChecker::CheckReturn::INVALID)
@@ -275,8 +287,9 @@ namespace mo2::python {
275287
.def("fix", &ModDataChecker::fix, "filetree"_a);
276288

277289
// ModDataContent
278-
py::class_<ModDataContent, PyModDataContent> pyModDataContent(m,
279-
"ModDataContent");
290+
py::class_<ModDataContent, GameFeature, PyModDataContent,
291+
std::shared_ptr<ModDataContent>>
292+
pyModDataContent(m, "ModDataContent");
280293

281294
py::class_<ModDataContent::Content>(pyModDataContent, "Content")
282295
.def(py::init<int, QString, QString, bool>(), "id"_a, "name"_a, "icon"_a,
@@ -292,15 +305,17 @@ namespace mo2::python {
292305

293306
// SaveGameInfo
294307

295-
py::class_<SaveGameInfo, PySaveGameInfo>(m, "SaveGameInfo")
308+
py::class_<SaveGameInfo, GameFeature, PySaveGameInfo,
309+
std::shared_ptr<SaveGameInfo>>(m, "SaveGameInfo")
296310
.def(py::init<>())
297311
.def("getMissingAssets", &SaveGameInfo::getMissingAssets, "save"_a)
298312
.def("getSaveGameWidget", &SaveGameInfo::getSaveGameWidget,
299313
py::return_value_policy::reference, "parent"_a);
300314

301315
// ScriptExtender
302316

303-
py::class_<ScriptExtender, PyScriptExtender>(m, "ScriptExtender")
317+
py::class_<ScriptExtender, GameFeature, PyScriptExtender,
318+
std::shared_ptr<ScriptExtender>>(m, "ScriptExtender")
304319
.def(py::init<>())
305320
.def("binaryName", &ScriptExtender::BinaryName)
306321
.def("pluginPath", wrap_return_for_directory(&ScriptExtender::PluginPath))
@@ -313,7 +328,8 @@ namespace mo2::python {
313328

314329
// UnmanagedMods
315330

316-
py::class_<UnmanagedMods, PyPyUnmanagedMods>(m, "UnmanagedMods")
331+
py::class_<UnmanagedMods, GameFeature, PyUnmanagedMods,
332+
std::shared_ptr<UnmanagedMods>>(m, "UnmanagedMods")
317333
.def(py::init<>())
318334
.def("mods", &UnmanagedMods::mods, "official_only"_a)
319335
.def("displayName", &UnmanagedMods::displayName, "mod_name"_a)
@@ -328,19 +344,39 @@ namespace mo2::python {
328344
"mod_name"_a);
329345
}
330346

347+
void add_igamefeatures_classes(py::module_ m)
348+
{
349+
py::class_<IGameFeatures>(m, "IGameFeatures")
350+
.def("registerFeature",
351+
py::overload_cast<QStringList const&, std::shared_ptr<GameFeature>,
352+
int, bool>(&IGameFeatures::registerFeature),
353+
"games"_a, "feature"_a, "priority"_a, "replace"_a = false)
354+
.def("registerFeature",
355+
py::overload_cast<MOBase::IPluginGame*, std::shared_ptr<GameFeature>,
356+
int, bool>(&IGameFeatures::registerFeature),
357+
"game"_a, "feature"_a, "priority"_a, "replace"_a = false)
358+
.def("registerFeature",
359+
py::overload_cast<std::shared_ptr<GameFeature>, int, bool>(
360+
&IGameFeatures::registerFeature),
361+
"feature"_a, "priority"_a, "replace"_a = false)
362+
.def("unregisterFeature", &IGameFeatures::unregisterFeature, "feature"_a)
363+
.def("unregisterFeatures", &unregister_feature, "feature_type"_a)
364+
.def("gameFeature", &extract_feature, "feature_type"_a,
365+
py ::return_value_policy::reference);
366+
}
367+
331368
} // namespace mo2::python
332369

333370
namespace mo2::python {
334371

335372
class GameFeaturesHelper {
336-
using GameFeatures = std::tuple<BSAInvalidation, DataArchives, GamePlugins,
337-
LocalSavegames, ModDataChecker, ModDataContent,
338-
SaveGameInfo, ScriptExtender, UnmanagedMods>;
339-
340373
template <class F, std::size_t... Is>
341374
static void helper(F&& f, std::index_sequence<Is...>)
342375
{
343-
(f(static_cast<std::tuple_element_t<Is, GameFeatures>*>(nullptr)), ...);
376+
(f(static_cast<
377+
std::tuple_element_t<Is, MOBase::details::BaseGameFeaturesP>>(
378+
nullptr)),
379+
...);
344380
}
345381

346382
public:
@@ -349,45 +385,33 @@ namespace mo2::python {
349385
template <class F>
350386
static void apply(F&& f)
351387
{
352-
helper(f, std::make_index_sequence<std::tuple_size_v<GameFeatures>>{});
388+
helper(f, std::make_index_sequence<
389+
std::tuple_size_v<MOBase::details::BaseGameFeaturesP>>{});
353390
}
354391
};
355392

356-
pybind11::object extract_feature(IPluginGame const& game, pybind11::object type)
393+
pybind11::object extract_feature(IGameFeatures const& gameFeatures,
394+
pybind11::object type)
357395
{
358396
py::object py_feature = py::none();
359397
GameFeaturesHelper::apply([&]<class Feature>(Feature*) {
360398
if (py::type::of<Feature>().is(type)) {
361-
py_feature = py::cast(game.feature<Feature>(),
399+
py_feature = py::cast(gameFeatures.gameFeature<Feature>(),
362400
py::return_value_policy::reference);
363401
}
364402
});
365403
return py_feature;
366404
}
367405

368-
pybind11::dict extract_feature_list(IPluginGame const& game)
406+
int unregister_feature(MOBase::IGameFeatures& gameFeatures, pybind11::object type)
369407
{
370-
// constructing a dict from class name to actual object
371-
py::dict dict;
408+
int count = 0;
372409
GameFeaturesHelper::apply([&]<class Feature>(Feature*) {
373-
dict[py::type::of<Feature>()] =
374-
py::cast(game.feature<Feature>(), py::return_value_policy::reference);
375-
});
376-
return dict;
377-
}
378-
379-
std::map<std::type_index, std::any>
380-
convert_feature_list(py::dict const& py_features)
381-
{
382-
std::map<std::type_index, std::any> features;
383-
GameFeaturesHelper::apply([&]<class Feature>(Feature*) {
384-
const auto py_type = py::type::of<Feature>();
385-
if (py_features.contains(py_type)) {
386-
features[std::type_index(typeid(Feature))] =
387-
py_features[py_type].cast<Feature*>();
410+
if (py::type::of<Feature>().is(type)) {
411+
count = gameFeatures.unregisterFeatures<Feature>();
388412
}
389413
});
390-
return features;
414+
return count;
391415
}
392416

393417
} // namespace mo2::python

src/mobase/wrappers/pyplugins.cpp

Lines changed: 2 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -10,15 +10,6 @@ using namespace MOBase;
1010

1111
namespace mo2::python {
1212

13-
std::map<std::type_index, std::any> PyPluginGame::featureList() const
14-
{
15-
py::dict pyFeatures = [this]() {
16-
PYBIND11_OVERRIDE_PURE(py::dict, IPluginGame, _featureList, );
17-
}();
18-
19-
return convert_feature_list(pyFeatures);
20-
}
21-
2213
// this one is kind of big so it has its own function
2314
void add_iplugingame_bindings(pybind11::module_ m)
2415
{
@@ -54,13 +45,9 @@ namespace mo2::python {
5445
std::unique_ptr<IPluginGame, py::nodelete>>(
5546
m, "IPluginGame", py::multiple_inheritance())
5647
.def(py::init<>())
57-
58-
.def("featureList", &extract_feature_list)
59-
.def("feature", &extract_feature, "feature_type"_a,
60-
py::return_value_policy::reference)
61-
6248
.def("detectGame", &IPluginGame::detectGame)
6349
.def("gameName", &IPluginGame::gameName)
50+
.def("displayGameName", &IPluginGame::displayGameName)
6451
.def("initializeProfile", &IPluginGame::initializeProfile, "directory"_a,
6552
"settings"_a)
6653
.def("listSaves", &IPluginGame::listSaves, "folder"_a)
@@ -81,6 +68,7 @@ namespace mo2::python {
8168
.def("setGameVariant", &IPluginGame::setGameVariant, "variant"_a)
8269
.def("binaryName", &IPluginGame::binaryName)
8370
.def("gameShortName", &IPluginGame::gameShortName)
71+
.def("lootGameName", &IPluginGame::lootGameName)
8472
.def("primarySources", &IPluginGame::primarySources)
8573
.def("validShortNames", &IPluginGame::validShortNames)
8674
.def("gameNexusName", &IPluginGame::gameNexusName)

src/mobase/wrappers/pyplugins.h

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -353,6 +353,10 @@ namespace mo2::python {
353353
{
354354
PYBIND11_OVERRIDE_PURE(QString, IPluginGame, gameName, );
355355
}
356+
QString displayGameName() const override
357+
{
358+
PYBIND11_OVERRIDE(QString, IPluginGame, displayGameName, );
359+
}
356360
void initializeProfile(const QDir& directory,
357361
ProfileSettings settings) const override
358362
{
@@ -435,6 +439,10 @@ namespace mo2::python {
435439
{
436440
PYBIND11_OVERRIDE_PURE(QString, IPluginGame, gameShortName, );
437441
}
442+
QString lootGameName() const override
443+
{
444+
PYBIND11_OVERRIDE(QString, IPluginGame, lootGameName, );
445+
}
438446
QStringList primarySources() const override
439447
{
440448
PYBIND11_OVERRIDE(QStringList, IPluginGame, primarySources, );
@@ -491,9 +499,6 @@ namespace mo2::python {
491499
{
492500
PYBIND11_OVERRIDE(QString, IPluginGame, getSupportURL, );
493501
}
494-
495-
protected:
496-
std::map<std::type_index, std::any> featureList() const override;
497502
};
498503

499504
} // namespace mo2::python

src/mobase/wrappers/wrappers.h

Lines changed: 14 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -77,36 +77,32 @@ namespace mo2::python {
7777
void add_game_feature_bindings(pybind11::module_ m);
7878

7979
/**
80-
* @brief Create the game feature corresponding to the given Python type from the
81-
* given game.
80+
* @brief Add bindings for IGameFeatures.
8281
*
83-
* @param game Game plugin to extract the feature from.
84-
* @param type Type of the feature to extract.
85-
*
86-
* @return the feature from the game, or None is the game as no such feature.
82+
* @param m Python module to add bindings to.
8783
*/
88-
pybind11::object extract_feature(MOBase::IPluginGame const& game,
89-
pybind11::object type);
84+
void add_igamefeatures_classes(pybind11::module_ m);
9085

9186
/**
92-
* @brief Create Python dictionary mapping game feature classes to the game feature
93-
* instances for the given game.
87+
* @brief Extract the game feature corresponding to the given Python type.
9488
*
95-
* @param game Game plugin to extract features from.
89+
* @param gameFeatures Game features to extract the feature from.
90+
* @param type Type of the feature to extract.
9691
*
97-
* @return a python dictionary mapping feature types (in Python) to feature objects.
92+
* @return the feature from the game, or None is the game as no such feature.
9893
*/
99-
pybind11::dict extract_feature_list(MOBase::IPluginGame const& game);
94+
pybind11::object extract_feature(MOBase::IGameFeatures const& gameFeatures,
95+
pybind11::object type);
10096

10197
/**
102-
* @brief Convert the given python map of features to a C++ one.
98+
* @brief Unregister the game feature corresponding to the given Python type.
10399
*
104-
* @param py_features Python features to convert (type to feature).
100+
* @param gameFeatures Game features to unregister the feature from.
101+
* @param type Type of the feature to unregister.
105102
*
106-
* @return the map of features.
103+
* @return the feature from the game, or None is the game as no such feature.
107104
*/
108-
std::map<std::type_index, std::any>
109-
convert_feature_list(pybind11::dict const& py_features);
105+
int unregister_feature(MOBase::IGameFeatures& gameFeatures, pybind11::object type);
110106

111107
} // namespace mo2::python
112108

0 commit comments

Comments
 (0)