mobase is a ModOrganizer2 Python API.
It provides access to the part of
uibase C++ API from
Python through pybind11.
Important: All (most) files should include pybind11_all.h (either directly
or through another header) to get proper type_caster available.
mobase.cppcontains thePYBIND11_MODULEdefinition ofmobasebut is otherwise the entrypoint for other functions.wrappers.hcontains the declaration of most functions implemented underwrappers/.- The other files under
wrappers/contains bindings and trampoline classes (see below) foruibaseclasses.basic_classes.cppcontains the bindings for most classes that cannot be extended in Python (IOrganizer,IModInterface, etc.)game_features.cppcontains the bindings and trampoline classes for game features.pyfiletree.handpyfiletree.cppcontains bindings for theIFileTree-related classes.pyplugins.hcontains the trampoline classes for theIPluginXXXclasses andpyplugins.cppthe bindings.pyplugins.his required since the trampoline classes are tagged withQ_OBJECT, and MOC does not work if the classes are declared in a C++ file.pyplugins.cppalso contains theextract_pluginsfunction inmobase.privatethat is used to extract plugins from Python object in the runner.
widgets.cppcontains the bindings for the widget classes.wrappers.cppcontains the trampoline and bindings for non-plugin classes that can be extended through Python.
Updating or adding classes that cannot be extended through Python is quite easily.
One simply needs to declare the appropriate py::class_ or add new .def().
See below for things to remember when creating pybind11 bindings.
Similar to classes that cannot be extended through Python, see above.
To extend plugins, simply update the trampoline classes in pyplugins.h and the
bindings in pyplugins.cpp.
Note: For new plugins, simply look at the existing one.
To extend or expose game features:
- Create (if there is not already one) a trampoline class for the feature in
game_features.cpp.- Add implementation of missing functions if required.
- Add the bindings in
add_game_feature_bindingsingame_features.cpp. - For new feature, add the feature type to
GameFeaturesHelper::GameFeaturesingame_features.cpp.
Non-plugin classes should be added to the wrappers.cpp file and should be exposed
with std::shared_ptr<> or qobject_holder<> holders.
- If the classes extends
QObject, use aqobject_holder. - Otherwise use a
std::shared_ptr<>holder and add aMO2_PYBIND11_SHARED_CPP_HOLDERdeclaration inpybind11_all.h.
Trampoline can be defined directly in wrappers.cpp, and bindings in the appropriate
function.
See the existing classes for example.
Important:
You need to make sure that uibase manipulates such classes through
std::shared_ptr<> (unless those inherit QObject).
Using std::unique_ptr<> is not possible since std::unique_ptr<> cannot have custom
runtime-specified deleters.
Here are a few things to remember when creating bindings:
- If a function has multiple overloads that can conflict in Python, the more complex one must be defined first as pybind11 will try calling them in order.
- If a C++ function expect a
QString,QFileInfoorQDirthat represents a file or a directory, wrapping the function withwrap_for_filepathorwrap_for_directoryis a good idea. This allows Python to call the function withpathlib.Path. - Most of the C++ function taking a reference to modify in C++ cannot be directly
exposed in Python since Python cannot modify reference to simple type (e.g.
QString&orint&). The best way to expose such function is to bind a lambda that returns a variant from Python, e.g.
// assume the C++ function is QString fn(QString& foo, QString const& bar, int& maz);
m.def("function", [](QString& foo, QString const& bar, int& maz) {
// call the function
auto ret = function(foo, bar, maz);
// make a tuple containing the return value (if there is one), and the modified
// values passed by reference
return std::make_tuple(ret, foo, maz);
});