Skip to content

Commit

Permalink
cxx-qt-gen: add cxx_qt::qobject to determine which struct is the QObj…
Browse files Browse the repository at this point in the history
…ect (#197)
  • Loading branch information
ahayzen-kdab authored Aug 10, 2022
1 parent 47c979b commit b62375a
Show file tree
Hide file tree
Showing 42 changed files with 155 additions and 102 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ mod my_object {
}
}

#[cxx_qt::qobject]
#[derive(Default)]
pub struct RustObj;

Expand Down
2 changes: 1 addition & 1 deletion book/src/SUMMARY.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ SPDX-License-Identifier: MIT OR Apache-2.0
- [QObject](./qobject/index.md)
- [Macro](./qobject/macro.md)
- [Data Struct](./qobject/data_struct.md)
- [RustObj Struct](./qobject/rustobj_struct.md)
- [QObject marked Struct](./qobject/qobject_struct.md)
- [Cpp Object](./qobject/cpp_object.md)
- [Signals enum](./qobject/signals_enum.md)
- [Handlers](./qobject/handlers.md)
Expand Down
2 changes: 1 addition & 1 deletion book/src/concepts/bridge.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,4 @@ We provide [Qt types](./types.md) to help pass common data types across the brid

When Rust items are exposed to C++ we automatically perform a conversion between Snake case and Camel case. So that items (such as properties and invokables) appear as Camel case to C++ but Snake case to Rust.

Note that the Rust [`RustObj`](../qobject/rustobj_struct.md) of a constructed Qt object is owned by the C++ side of the bridge representing it. So when the C++ object is destroyed the Rust object will be destroyed. In the future there will be [handlers](../qobject/handlers.md) for executing Rust code from the (de)constructor of the C++ object [https://github.com/KDAB/cxx-qt/issues/13](https://github.com/KDAB/cxx-qt/issues/13).
Note that the Rust [`QObject marked struct`](../qobject/qobject_struct.md) of a constructed Qt object is owned by the C++ side of the bridge representing it. So when the C++ object is destroyed the Rust object will be destroyed. In the future there will be [handlers](../qobject/handlers.md) for executing Rust code from the (de)constructor of the C++ object [https://github.com/KDAB/cxx-qt/issues/13](https://github.com/KDAB/cxx-qt/issues/13).
6 changes: 1 addition & 5 deletions book/src/concepts/qt.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ SPDX-License-Identifier: MIT OR Apache-2.0

## Invokables

Invokables can be defined using the [RustObj Struct](../qobject/rustobj_struct.md), these will be exposed as methods on the C++ class with `Q_INVOKABLE` so that they are accessible for QML too.
Invokables can be defined using the [QObject Struct](../qobject/qobject_struct.md), these will be exposed as methods on the C++ class with `Q_INVOKABLE` so that they are accessible for QML too.

## Properties

Expand All @@ -18,7 +18,3 @@ Properties can be defined using the [Data struct](../qobject/data_struct.md), th
## Signals

Signals can be defined using the [Signals enum](../qobject/signals_enum.md), these will be exposed as `Q_SIGNALS` on the C++ class and therefore to QML as well.

## Change events

You can listen to property changes via the [handlers](../qobject/handlers.md) available in the RustObj Struct. These handlers are called from the Qt event loop thread to remain [thread safe](./threading.md).
2 changes: 1 addition & 1 deletion book/src/concepts/threading.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ We provide a solution to prevent entering deadlocks from signal connections, eg

To achieve safe multi-threading on the Rust side we use an `UpdateRequester`. Where the Rust thread is started (eg an invokable) the `UpdateRequester` should be cloned into the thread.

Then when the background thread needs to update a value in the Qt object it requests an update, this is posted into the same queue as above. Once the event loop occurs this calls `UpdateRequestHandler` in the [RustObj Handlers](../qobject/handlers.md) so that you can safely call setters or emit signals from the Qt thread and synchronise your state to the foreground.
Then when the background thread needs to update a value in the Qt object it requests an update, this is posted into the same queue as above. Once the event loop occurs this calls `UpdateRequestHandler` in the [Handlers](../qobject/handlers.md) so that you can safely call setters or emit signals from the Qt thread and synchronise your state to the foreground.

We recommend using a channel in the thread to send enums or values which are then processed in `UpdateRequestHandler`.

Expand Down
14 changes: 7 additions & 7 deletions book/src/getting-started/1-qobjects-in-rust.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,12 +40,12 @@ These CXX-Qt modules consist of multiple parts:
- Defines which Properties will be in the QObject subclass.
- Needs to implement the `Default` trait.
- This data will live as properties in the C++ subclass that is generated by CXX-Qt.
- A `RustObj` struct
- A struct marked with a `#[cxx_qt::qobject]` macro
- A normal Rust struct.
- One struct instance is created per class instance.
- Contains any Rust-only data.
- Needs to implement the `Default` trait.
- The `impl` of the `RustObj` struct (optional):
- The `impl` of the `#[cxx_qt::qobject]` marked struct (optional):
- Contains any Rust code.
- Functions marked with `#[invokable]` will be callable from QML and C++.
- The `Signal` enum
Expand All @@ -54,27 +54,27 @@ These CXX-Qt modules consist of multiple parts:

CXX-Qt will then expand this Rust module into two separate parts:
- A C++ subclass of QObject with the same name as the module
- The Rust struct `RustObj`
- The `#[cxx_qt::qobject]` marked Rust struct

<div style="background-color: white; padding: 1rem; text-align: center;">

![Overview of CXX-Qt module generation](../images/overview_abstract.svg)

</div>

CXX-Qt also generates the code needed for interaction of the C++ QObject subclass and the `RustObj` struct using the [CXX library](https://cxx.rs/).
CXX-Qt also generates the code needed for interaction of the C++ QObject subclass and the `#[cxx_qt::qobject]` marked struct using the [CXX library](https://cxx.rs/).
For more details, see the [Concepts: Bridge](../concepts/bridge.md) page.
Additionally, CXX-Qt wraps some Qt types for us, so they can be used easily by the Rust side.
See the [Concepts: Qt Types](../concepts/types.md) page for the list of available types.

The important take away here is the duality of any subclass generated by CXX-Qt.
These classes are made up of the actual QObject subclass instance that exists purely on the C++ side, as well as an instance of the `RustObj` struct.
These classes are made up of the actual QObject subclass instance that exists purely on the C++ side, as well as an instance of the `#[cxx_qt::qobject]` marked struct.
The lifetime and GUI data is therefore managed by the QObject instance on the C++ side.
Typically this will be instantiated by QML and the lifetime will be directly associated with the corresponding QML widget.
Any properties declared in the `Data` struct will be stored as a member of the C++ QObject.

However, the generated QObject subclass will defer to the `RustObj` struct for any behavior, which is then defined in Rust.
The `RustObj` struct can expose additional functionality with functions marked as `#[invokable]`, which will generate a function on the C++ side that will directly call the appropriate Rust method.
However, the generated QObject subclass will defer to the `#[cxx_qt::qobject]` marked struct for any behavior, which is then defined in Rust.
The `#[cxx_qt::qobject]` marked struct can expose additional functionality with functions marked as `#[invokable]`, which will generate a function on the C++ side that will directly call the appropriate Rust method.
These Rust methods can take a reference to the members of the C++ object via a wrapper called `CppObj`, so the Rust code can modify them.

Now that we have taken a look the theory of it all, lets jump in and write [our first CXX-Qt module](./2-our-first-cxx-qt-module.md).
Expand Down
10 changes: 5 additions & 5 deletions book/src/getting-started/2-our-first-cxx-qt-module.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,13 +57,13 @@ Now that we've defined the data that will live on the C++ side of things, let's
{{#include ../../../examples/qml_minimal/src/lib.rs:book_rustobj_struct}}
```
In our case, this is just an empty struct.
However, the `RustObj` could contain any data we want.
However, the `#[cxx_qt::qobject]` marked struct could contain any data we want.
It is not converted into a C++ class, so it isn't limited to the Qt-compatible types that the `Data` struct is.

An important point to note here is that the `RustObj`, like the `Data` struct must implement the `Default` trait.
Every instance of the `MyObject` class will automatically create a corresponding `RustObj` instance by using the `Default` trait.
An important point to note here is that the `#[cxx_qt::qobject]` marked struct, like the `Data` struct must implement the `Default` trait.
Every instance of the `MyObject` class will automatically create a corresponding `#[cxx_qt::qobject]` marked struct instance by using the `Default` trait.

Just because the `RustObj` struct doesn't contain any data, that still doesn't mean its not an important part of our `MyObject` class.
Just because the `#[cxx_qt::qobject]` marked struct struct doesn't contain any data, that still doesn't mean its not an important part of our `MyObject` class.
That is because it actually defines the behavior of our class through its `impl`:
```rust,ignore
{{#include ../../../examples/qml_minimal/src/lib.rs:book_rustobj_impl}}
Expand All @@ -80,7 +80,7 @@ In our case, we define two new functions:

Both functions are marked with the `#[invokable]` macro, which means the functions will be added to the C++ code of `MyObject` and will be callable from QML as well.

Apart from functions marked with the `#[invokable]` macro, the `RustObj` impl is just a normal Rust struct impl and can contain normal Rust functions, which the invokable functions can call as usual.
Apart from functions marked with the `#[invokable]` macro, the `#[cxx_qt::qobject]` marked struct impl is just a normal Rust struct impl and can contain normal Rust functions, which the invokable functions can call as usual.

And that's it. We've defined our first QObject subclass in Rust. That wasn't so hard, was it?

Expand Down
2 changes: 1 addition & 1 deletion book/src/qobject/cpp_object.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ If the [`Data` struct](./data_struct.md) has a field called `number: i32`, then

If there is a [`Signals` enum](./signals_enum.md) then you can call `emit_queued(&mut self, Signals)` or `unsafe emit_immediate(&mut self, Signals)` on the `CppObj` to emit a signal.

Note that `emit_immediate` is unsafe as it can cause deadlocks if the `Q_EMIT` is `Qt::DirectConnection` connected to a Rust invokable on the same QObject that has caused the `Q_EMIT`, as this would then try to lock the `RustObj` which is already locked.
Note that `emit_immediate` is unsafe as it can cause deadlocks if the `Q_EMIT` is `Qt::DirectConnection` connected to a Rust invokable on the same QObject that has caused the `Q_EMIT`, as this would then try to lock the internal Rust object which is already locked.

```rust,ignore,noplayground
{{#include ../../../examples/qml_features/src/signals.rs:book_rust_obj_impl}}
Expand Down
4 changes: 2 additions & 2 deletions book/src/qobject/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ A QObject is constructed with the following parts

* [A macro around the module](./macro.md)
* [A Data struct defining properties](./data_struct.md)
* [A RustObj defining invokables](./rustobj_struct.md)
* [A QObject marked struct defining invokables](./qobject_struct.md)
* [Cpp Object wrapper](./cpp_object.md)
* [A Signals enum for defining signals](./signals_enum.md)
* [Handlers on RustObj for processing events on the Qt thread](./handlers.md)
* [Handlers for processing events on the Qt thread](./handlers.md)
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,12 @@ SPDX-FileContributor: Andrew Hayzen <andrew.hayzen@kdab.com>
SPDX-License-Identifier: MIT OR Apache-2.0
-->

# RustObj Struct
# QObject Marked Struct

The RustObj struct allows you to define the following items
The `#[cxx_qt::qobject]` marked struct allows you to define the following items

* Invokable methods that are exposed to Qt
* Private methods and fields for RustObj to use (eg this is useful for storing the channels for [threading](../concepts/threading.md))
* Private methods and fields for struct to use (eg this is useful for storing the channels for [threading](../concepts/threading.md))
* Mutate C++ state with [`CppObj`](./cpp_object.md)
* Implement [handlers](./handlers.md) for property or update requests

Expand All @@ -20,7 +20,7 @@ The RustObj struct allows you to define the following items

## Invokables

A `impl cxx_qt::QObject<RustObj>` is used to define invokables, the `impl cxx_qt::QObject<RustObj>` defines that the methods are implemented onto the C++ QObject.
A `impl cxx_qt::QObject<T>` is used to define invokables, the `impl cxx_qt::QObject<T>` defines that the methods are implemented onto the C++ QObject `T`.
Therefore they have access to both C++ and Rust methods. Also CXX-Qt adds wrapper code around your invokables to automatically perform any conversion between the [C++ and Rust types](../concepts/types.md).

To mark a method as invokable simply add the `#[invokable]` attribute to the Rust method. This then causes `Q_INVOKABLE` to be set on the C++ definition of the method, allowing QML to call the invokable.
Expand All @@ -29,8 +29,8 @@ Note to access properties on the C++ object use [Cpp Object](./cpp_object.md).

## Private Methods and Fields

Unlike the [Data Struct](./data_struct.md) fields which are defined on the `RustObj` struct are not exposed as properties to Qt. These can be considered as "private to Rust" fields, and are useful for storing channels for threading or internal information for the QObject.
Unlike the [Data Struct](./data_struct.md) fields which are defined on the `#[cxx_qt::qobject]` marked struct are not exposed as properties to Qt. These can be considered as "private to Rust" fields, and are useful for storing channels for threading or internal information for the QObject.

Methods implemented using `impl RustObj` (and not `impl cxx_qt::QObject<RustObj>`) are just normal Rust member methods.
Methods implemented using `impl T` (and not `impl cxx_qt::QObject<T>`) are just normal Rust member methods.
Therefore they do not have access to any C++ or QObject functionality (e.g. emitting Signals, changing properties, etc.)
You will usually only need to use `impl RustObj` if you want to also use your RustObj struct as a normal Rust struct, that is not wrapped in a QObject.
You will usually only need to use `impl T` if you want to also use your struct as a normal Rust struct, that is not wrapped in a QObject.
2 changes: 1 addition & 1 deletion book/src/qobject/signals_enum.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ The `cxx_qt::signals(T)` attribute is used on an enum to define which signals sh

To emit a signal from Rust use the [`CppObj`](./cpp_object.md) and call either the `emit_queued(Signal)` or `unsafe emit_immediate(Signal)` method.

Note that `emit_immediate` is unsafe as it can cause deadlocks if the `Q_EMIT` is `Qt::DirectConnection` connected to a Rust invokable on the same QObject that has caused the `Q_EMIT`, as this would then try to lock the `RustObj` which is already locked.
Note that `emit_immediate` is unsafe as it can cause deadlocks if the `Q_EMIT` is `Qt::DirectConnection` connected to a Rust invokable on the same QObject that has caused the `Q_EMIT`, as this would then try to lock the internal Rust object which is already locked.

```rust,ignore,noplayground
{{#include ../../../examples/qml_features/src/signals.rs:book_rust_obj_impl}}
Expand Down
4 changes: 2 additions & 2 deletions cxx-qt-gen/src/extract.rs
Original file line number Diff line number Diff line change
Expand Up @@ -673,8 +673,8 @@ pub fn extract_qobject(module: &ItemMod) -> Result<QObject, TokenStream> {
//
// The original Data Item::Struct if one is found
let original_data_struct = qobject.data_struct;
// The original RustObj Item::Struct if one is found
let original_rust_struct = qobject.rust_struct;
// The original #[cxx_qt::qobject] marked struct Item::Struct if one is found
let original_rust_struct = qobject.qobject_struct;
// The name of the Qt object we are creating
let qt_ident = quote::format_ident!("{}", original_mod.ident.to_string().to_case(Case::Pascal));

Expand Down
6 changes: 3 additions & 3 deletions cxx-qt-gen/src/gen_rs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ pub fn generate_qobject_cxx(obj: &QObject) -> Result<TokenStream, TokenStream> {
let class_name = &obj.ident;
let rust_class_name_cpp = format_ident!("{}Qt", class_name);
let cxx_class_name_rust = format_ident!("{}Rust", class_name);
let rust_class_name = format_ident!("RustObj");
let rust_class_name = &obj.original_rust_struct.ident;

// Build a snake version of the class name, this is used for rust method names
//
Expand Down Expand Up @@ -731,7 +731,7 @@ pub fn generate_qobject_rs(obj: &QObject) -> Result<TokenStream, TokenStream> {
let class_name = &obj.ident;

// Cache the rust class name
let rust_class_name = format_ident!("RustObj");
let rust_class_name = &obj.original_rust_struct.ident;
let rust_wrapper_name = format_ident!("CppObj");

// Generate cxx block
Expand Down Expand Up @@ -960,7 +960,7 @@ pub fn generate_qobject_rs(obj: &QObject) -> Result<TokenStream, TokenStream> {

#(#original_passthrough_decls)*

pub fn create_rs() -> std::boxed::Box<RustObj> {
pub fn create_rs() -> std::boxed::Box<#rust_class_name> {
std::default::Default::default()
}

Expand Down
Loading

0 comments on commit b62375a

Please sign in to comment.