aboutsummaryrefslogtreecommitdiffstats
path: root/sources/pyside6/doc/developer
diff options
context:
space:
mode:
authorBrett Stottlemyer <bstottle@ford.com>2024-12-18 10:33:56 -0500
committerFriedemann Kleint <Friedemann.Kleint@qt.io>2025-03-13 16:28:42 +0100
commit19abd816e73bebdd489408d0a3b7676822bff39c (patch)
tree8459ae9401f5e190995b3e24b6ae6968cf457baf /sources/pyside6/doc/developer
parent3c66c456aeab597b7cb046f81c7f015433bb57a4 (diff)
Make Remote Objects usable beyond Models
While present, the Qt Remote Objects bindings to Python have not been very useful. The only usable components were those based on QAbstractItemModel, due to the lack of a way to interpret .rep files from Python. This addresses that limitation. Fixes: PYSIDE-862 Change-Id: Ice57c0c64f11c3c7e74d50ce3c48617bd9b422a3 Reviewed-by: Friedemann Kleint <Friedemann.Kleint@qt.io> Reviewed-by: Brett Stottlemyer <brett.stottlemyer@gmail.com>
Diffstat (limited to 'sources/pyside6/doc/developer')
-rw-r--r--sources/pyside6/doc/developer/index.rst1
-rw-r--r--sources/pyside6/doc/developer/remoteobjects.md162
2 files changed, 163 insertions, 0 deletions
diff --git a/sources/pyside6/doc/developer/index.rst b/sources/pyside6/doc/developer/index.rst
index bddd39d91..0260ad272 100644
--- a/sources/pyside6/doc/developer/index.rst
+++ b/sources/pyside6/doc/developer/index.rst
@@ -36,3 +36,4 @@ many features and implementation details that the project has:
signature_doc.rst
mypy-correctness.rst
feature-motivation.rst
+ remoteobjects.md
diff --git a/sources/pyside6/doc/developer/remoteobjects.md b/sources/pyside6/doc/developer/remoteobjects.md
new file mode 100644
index 000000000..7d4c29aa0
--- /dev/null
+++ b/sources/pyside6/doc/developer/remoteobjects.md
@@ -0,0 +1,162 @@
+# Qt Remote Objects Overview
+
+[Qt Remote Objects](https://doc.qt.io/qt-6/qtremoteobjects-index.html) (or QtRO)
+is described as an IPC module. That puts the focus on the internal details.
+It should be looked at more as a Connected Framework.
+
+QtRO lets you easily take an existing Qt application and interact with it from
+other devices. QtRO allows you to create a
+[_Replica_](https://doc.qt.io/qt-6/qtremoteobjects-replica.html) QObject, making
+the Replica a surrogate for the real QOject in your program (called the
+[_Source_](https://doc.qt.io/qt-6/qtremoteobjects-source.html)). You interact with
+the Replica the same way you would the Source (with one important difference) and QtRO
+ensures those interactions are forwarded to the source for handling. Changes to the
+Source are cascaded to any Replicas.
+
+The mechanism Qt Remote Objects provides for enabling these objects to connect to each
+other are a network of
+[_Nodes_](https://doc.qt.io/qt-6/qtremoteobjects-node.html). Nodes handle the details of
+connecting processes or devices. A Replica is created by calling
+[acquire()](https://doc.qt.io/qt-6/qremoteobjectnode.html#acquire) on a Node, and Sources
+are shared on the network using
+[enableRemoting()](https://doc.qt.io/qt-6/qremoteobjecthostbase.html#enableRemoting).
+
+## Replicas are _latent copies_
+
+Qt Remote Object interactions are inherently asynchronous. This _can_ lead to
+confusing results initially
+
+```python
+# Assume a replica initially has an int property `i` with a value of 2
+print(f"Value of i on replica = {replica.i}") # prints 2
+replica.iChanged.connect(lambda i: print(f"Value of i on replica changed to {i}"))
+replica.i = 3
+print(f"Value of i on replica = {replica.i}") # prints 2, not 3
+
+# When the eventloop runs, the change will be forwarded to the source instance,
+# the change will be made, and the new i value will be sent back to the replica.
+# The iChanged signal will be fired
+# after some delay.
+```
+
+Note: To avoid this confusion, Qt Remote Objects can change setters to "push"
+slots on the Replica class, making the asynchronous nature of the behavior
+clear.
+
+```python
+replica.pushI(3) # Request a change to `i` on the source object.
+```
+
+## How does this affect PySide?
+
+PySide wraps the Qt C++ classes used by QtRO, so much of the needed
+functionality for QtRO is available in PySide. However, the interaction between
+a Source and Replica are in effect a contract that is defined on a _per object_
+basis. I.e., different objects have different APIs, and every participant must
+know about the contracts for the objects they intend to use.
+
+In C++, Qt Remote Objects leverages the
+[Replica Compiler (repc)](https://doc.qt.io/qt-6/qtremoteobjects-repc.html) to
+generate QObject header and C++ code that enforce the contracts for each type.
+REPC uses a simplified text syntax to describe the desired API in .rep files.
+REPC is integrated with qmake and cmake, simplifying the process of leveraging
+QtRO in a C++ project. The challenges in PySide are
+1) To parse the .rep file to extract the desired syntax
+2) Allow generation of types that expose the desired API and match the needed
+ contract
+3) Provide appropriate errors and handling in cases that can't be dynamically
+ handled in Python.
+For example, C++ can register templated types such as a QMap<double, MyType>
+and serialize such types once registered. While Python can create a similar
+type, there isn't a path to dynamically serialize such a type so C++ could
+interpret it correctly on the other side of a QtRO network.
+
+Under the covers, QtRO leverages Qt's QVariant infrastructure heavily. For
+instance, a Replica internally holds a QVariantList where each element
+represents one of the exposed QProperty values. The property's QVariant is
+typed appropriately for the property, allows an autogenerated getter to (for
+instance with a float property) return `return variant.value<float >();`. This
+works well with PySide converters.
+
+## RepFile PySide type
+
+The first challenge is handled by adding a Python type RepFile can takes a .rep
+file and parses it into an Abstract Syntax Tree (AST) describing the type.
+
+A simple .rep might look like:
+```cpp
+class Thermistat
+{
+ PROP(int temp)
+}
+```
+
+The desired interface would be
+```python
+from pathlib import Path
+from PySide6.QtRemoteObjects import RepFile
+
+input_file = Path(__file__).parent / "thermistat.rep"
+rep_file = RepFile(input_file)
+```
+
+The RepFile holds dictionaries `source`, `replica` and `pod`. These use the
+names of the types as the key, and the value is the PyTypeObject* of the
+generated type meeting the desired contract:
+
+```python
+Source = rep_file.source["Thermistat"] # A Type object for Source implementation of the type
+Replica = rep_file.replica["Thermistat"] # A Type object for Replica implementation of the type
+```
+
+## Replica type
+
+A Replica for a given interface will be a distinct type. It should be usable
+directly from Python once instantiated and initialized.
+
+```python
+Replica = rep_file.replica["Thermistat"] # A Type object matching the Replica contract
+replica = node.acquire(Replica) # We need to tell the node what type to instantiate
+# These two lines can be combined
+replica_instance = node.acquire(rep_file.replica["Thermistat"])
+
+# If there is a Thermistat source on the network, our replica will get connected to it.
+if replica.isInitialized():
+ print(f"The current tempeerature is {replica.temp}")
+else:
+ replica.initialized.connect(lambda: print(f"replica is now initialized. Temp = {replica.temp}"))
+```
+
+## Source type
+
+Unlike a Replica, whose interface is a passthrough of another object, the
+Source needs to actually define the desired behavior. In C++, QtRO supports two
+modes for Source objects. A MyTypeSource C++ class is autogenerated that
+defines pure virtual getters and setters. This enables full customization of
+the implementation. A MyTypeSimpleSource C++ class is also autogenerated that
+creates basic data members for properties and getters/setters that work on
+those data members.
+
+The intent is to follow the SimpleSource pattern in Python if possible.
+
+```python
+ Thermistat = rep_file.source["Thermistat"]
+ class MyThermistat(Thermistat):
+ def __init__(self, parent = None):
+ super().__init__(parent)
+ # Get the current temp from the system
+ self.temp = get_temp_from_system()
+```
+
+## Realizing Source/Replica types in python
+
+Assume there is a RepFile for thermistat.rep that defines a Thermistat class
+interface.
+
+`ThermistatReplica = repFile.replica["Thermistat"]` should be a Shiboken.ObjectType
+type, with a base of QRemoteObjectReplica's shiboken type.
+
+`ThermistatSource = repFile.source["Thermistat"]` should be a abstract class of
+Shiboken.ObjectType type, with a base of QObject's shiboken type.
+
+Both should support new classes based on their type to customize behavior.