aboutsummaryrefslogtreecommitdiffstats
path: root/sources/pyside6/doc/developer
diff options
context:
space:
mode:
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.