aboutsummaryrefslogtreecommitdiff
path: root/libs/plugin_python/src/mobase/README.md
diff options
context:
space:
mode:
Diffstat (limited to 'libs/plugin_python/src/mobase/README.md')
-rw-r--r--libs/plugin_python/src/mobase/README.md110
1 files changed, 110 insertions, 0 deletions
diff --git a/libs/plugin_python/src/mobase/README.md b/libs/plugin_python/src/mobase/README.md
new file mode 100644
index 0000000..b4d184e
--- /dev/null
+++ b/libs/plugin_python/src/mobase/README.md
@@ -0,0 +1,110 @@
+# mobase
+
+`mobase` is a ModOrganizer2 Python API.
+It provides access to the part of
+[`uibase`](https://github.com/ModOrganizer2/modorganizer-uibase) C++ API from
+Python through [`pybind11`](https://github.com/pybind/pybind11).
+
+## Organization
+
+**Important:** All (most) files should include `pybind11_all.h` (either directly
+or through another header) to get proper `type_caster` available.
+
+- `mobase.cpp` contains the `PYBIND11_MODULE` definition of `mobase` but is otherwise
+ the entrypoint for other functions.
+- `wrappers.h` contains the declaration of most functions implemented under
+ `wrappers/`.
+- The other files under `wrappers/` contains bindings and trampoline classes (see
+ below) for `uibase` classes.
+ - `basic_classes.cpp` contains the bindings for most classes that cannot be extended
+ in Python (`IOrganizer`, `IModInterface`, etc.)
+ - `game_features.cpp` contains the bindings and trampoline classes for game features.
+ - `pyfiletree.h` and `pyfiletree.cpp` contains bindings for the `IFileTree`-related
+ classes.
+ - `pyplugins.h` contains the trampoline classes for the `IPluginXXX` classes and
+ `pyplugins.cpp` the bindings.
+ - `pyplugins.h` is required since the trampoline classes are tagged with `Q_OBJECT`,
+ and MOC does not work if the classes are declared in a C++ file.
+ - `pyplugins.cpp` also contains the `extract_plugins` function in `mobase.private`
+ that is used to extract plugins from Python object in the runner.
+ - `widgets.cpp` contains the bindings for the widget classes.
+ - `wrappers.cpp` contains the trampoline and bindings for non-plugin classes that can
+ be extended through Python.
+
+## Updating mobase
+
+### Classes that cannot 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.
+
+### Free functions
+
+Similar to classes that cannot be extended through Python, see above.
+
+### Classes that can be extended through Python: Plugins
+
+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.
+
+### Classes that can be extended through Python: Game Features
+
+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_bindings` in `game_features.cpp`.
+- For new feature, add the feature type to `GameFeaturesHelper::GameFeatures` in
+ `game_features.cpp`.
+
+### Classes that can be extended through Python: Others
+
+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 a `qobject_holder`.
+- Otherwise use a `std::shared_ptr<>` holder and add a `MO2_PYBIND11_SHARED_CPP_HOLDER`
+ declaration in `pybind11_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.
+
+## Things to remember
+
+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`, `QFileInfo` or `QDir` that represents a file or
+ a directory, wrapping the function with `wrap_for_filepath` or `wrap_for_directory` is
+ a good idea. This allows Python to call the function with `pathlib.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&` or `int&`).
+ The best way to expose such function is to bind a lambda that returns a variant from
+ Python, e.g.
+
+```cpp
+// 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);
+});
+```