Skip to content

Commit e8ceebb

Browse files
committed
docs: update documentation
Update the main tutorial with the new awaitable-based async method.
1 parent d444fde commit e8ceebb

1 file changed

Lines changed: 63 additions & 10 deletions

File tree

docs/using-sdbus-c++.md

Lines changed: 63 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -759,7 +759,7 @@ private:
759759
760760
Analogously to the adaptor classes described above, there is one proxy class generated for one interface in the XML IDL file. The class is de facto a proxy to the concrete single interface of a remote object. For each D-Bus signal there is a pure virtual member function whose body must be provided in a child class. For each method, there is a public function member that calls the method remotely.
761761
762-
Generated proxy classes are not copyable and not moveable by design. One can create them on the heap and manage them in e.g. a `std::unique_ptr` if move semantics is needed (for example, when they are stored in a container).
762+
Generated proxy classes are not copyable and not moveable by design. One can create them on the heap and manage them in e.g. a `std::unique_ptr` if move semantics is needed (for example, when they are stored in a container).
763763
764764
```cpp
765765
/*
@@ -1130,11 +1130,11 @@ For a real example of a server-side asynchronous D-Bus method, please look at sd
11301130
Asynchronous client-side methods
11311131
--------------------------------
11321132
1133-
sdbus-c++ also supports asynchronous approach at the client (the proxy) side. With this approach, we can issue a D-Bus method call without blocking current thread's execution while waiting for the reply. We go on doing other things, and when the reply comes, either a given callback handler will be invoked within the context of the event loop thread, or a future object returned by the async call will be set the returned value.6
1133+
sdbus-c++ also supports asynchronous approach at the client (the proxy) side. With this approach, we can issue a D-Bus method call without blocking current thread's execution while waiting for the reply. We go on doing other things, and when the reply comes, either a given callback handler will be invoked within the context of the event loop thread, a future object returned by the async call will be set the returned value, or (with C++20) an awaitable can be `co_await`ed in a coroutine.
11341134
11351135
### Lower-level API
11361136
1137-
Considering the Concatenator example based on lower-level API, if we wanted to call `concatenate` in an async way, we have two options: We either pass a callback to the proxy when issuing the call, and that callback gets invoked when the reply arrives:
1137+
Considering the Concatenator example based on lower-level API, if we wanted to call `concatenate` in an async way, we have several options. We can pass a callback to the proxy when issuing the call, and that callback gets invoked when the reply arrives:
11381138
11391139
```c++
11401140
int main(int argc, char *argv[])
@@ -1204,6 +1204,18 @@ Another option is to use `std::future`-based overload of the `IProxy::callMethod
12041204
}
12051205
```
12061206
1207+
A third option, available with C++20, is to use `sdbus::with_awaitable` to get an `Awaitable<MethodReply>` that can be `co_await`ed in a coroutine:
1208+
1209+
```c++
1210+
// In a coroutine context:
1211+
auto method = concatenatorProxy->createMethodCall(interfaceName, concatenate);
1212+
method << numbers << separator;
1213+
auto reply = co_await concatenatorProxy->callMethod(method, sdbus::with_awaitable);
1214+
std::string result;
1215+
reply >> result;
1216+
// If an error occurs, sdbus::Error is thrown when co_await completes
1217+
```
1218+
12071219
### Convenience API
12081220
12091221
On the convenience API level, the call statement starts with `callMethodAsync()`, and one option is to finish the statement with `uponReplyInvoke()` that takes a callback handler. The callback is a void-returning function that takes at least one argument: `std::optional<sdbus::Error>`. All subsequent arguments shall exactly reflect the D-Bus method output arguments. A concatenator example:
@@ -1264,6 +1276,18 @@ The future object will contain void for a void-returning D-Bus method, a single
12641276
...
12651277
```
12661278
1279+
A third option, available with C++20, is to finish the async call statement with `getResultAsAwaitable<ReturnTypes...>()`, which returns an `Awaitable<T>` that can be `co_await`ed in a coroutine. The template arguments are the D-Bus method return types (empty for void-returning methods). The awaitable returns `void`, a single value, or a `std::tuple` for multiple return values:
1280+
1281+
```c++
1282+
// In a coroutine context:
1283+
auto result = co_await concatenatorProxy->callMethodAsync("concatenate")
1284+
.onInterface(interfaceName)
1285+
.withArguments(numbers, separator)
1286+
.getResultAsAwaitable<std::string>();
1287+
std::cout << "Got concatenate result: " << result << std::endl;
1288+
// If an error occurs, sdbus::Error is thrown when co_await completes
1289+
```
1290+
12671291
### Marking client-side async methods in the IDL
12681292
12691293
sdbus-c++-xml2cpp can generate C++ code for client-side async methods. We just need to annotate the method with `org.freedesktop.DBus.Method.Async`. The annotation element value must be either `client` (async on the client-side only) or `client-server` (async method on both client- and server-side):
@@ -1286,19 +1310,39 @@ sdbus-c++-xml2cpp can generate C++ code for client-side async methods. We just n
12861310
</node>
12871311
```
12881312
1289-
An asynchronous method can be generated as a callback-based method or `std::future`-based method. This can optionally be customized through an additional `org.freedesktop.DBus.Method.Async.ClientImpl` annotation. Its supported values are `callback` and `std::future`. The default behavior is callback-based method.
1313+
An asynchronous method can be generated as a callback-based method, `std::future`-based method, or C++20 awaitable-based method. This can optionally be customized through an additional `org.freedesktop.DBus.Method.Async.ClientImpl` annotation. Its supported values are `callback`, `future` and `awaitable`. The default behavior is callback-based method.
12901314
12911315
#### Generating callback-based async methods
12921316
12931317
For each client-side async method, a corresponding `on<MethodName>Reply` pure virtual function, where `<MethodName>` is the capitalized D-Bus method name, is generated in the generated proxy class. This function is the callback invoked when the D-Bus method reply arrives, and must be provided a body by overriding it in the implementation class.
12941318
12951319
So in the specific example above, the tool will generate a `Concatenator_proxy` class similar to one shown in a [dedicated section above](#concatenator-client-glueh), with the difference that it will also generate an additional `virtual void onConcatenateReply(std::optional<sdbus::Error> error, const std::string& concatenatedString);` method, which we shall override in the derived `ConcatenatorProxy`.
12961320
1297-
#### Generating std:future-based async methods
1321+
#### Generating std::future-based async methods
12981322
12991323
In this case, a `std::future` is returned by the method, which will later, when the reply arrives, get set to contain the return value. Or if the call returns an error, `sdbus::Error` will be thrown by `std::future::get()`.
13001324
1301-
For a real example of a client-side asynchronous D-Bus methods, please look at sdbus-c++ [stress tests](/tests/stresstests).
1325+
#### Generating awaitable-based async methods
1326+
1327+
> **_Note_:** This requires C++20 support. The generated code uses `sdbus::Awaitable<T>` which requires compiling with C++20 or newer.
1328+
1329+
When using `awaitable` as the `ClientImpl` annotation value, the generated method returns an `sdbus::Awaitable<T>` that can be used with C++20 coroutines. The return type `T` is `void` for void-returning D-Bus methods, a single type for single-value methods, or `std::tuple<Types...>` for multi-value methods.
1330+
1331+
Example annotation:
1332+
1333+
```xml
1334+
<method name="concatenate">
1335+
<annotation name="org.freedesktop.DBus.Method.Async" value="client" />
1336+
<annotation name="org.freedesktop.DBus.Method.Async.ClientImpl" value="awaitable" />
1337+
<arg type="ai" name="numbers" direction="in" />
1338+
<arg type="s" name="separator" direction="in" />
1339+
<arg type="s" name="concatenatedString" direction="out" />
1340+
</method>
1341+
```
1342+
1343+
This generates a method that can be `co_await`ed: `std::string result = co_await proxy.concatenate({1, 2, 3}, ":");`
1344+
1345+
For a real example of a client-side asynchronous D-Bus methods, please look at sdbus-c++ [stress tests](/tests/stresstests) and [integration tests](/tests/integrationtests).
13021346
13031347
## Method call timeout
13041348
@@ -1338,7 +1382,7 @@ We read property value easily through `IProxy::getProperty()` method:
13381382
uint32_t status = proxy->getProperty("status").onInterface("org.sdbuscpp.Concatenator");
13391383
```
13401384
1341-
Getting a property in asynchronous manner is also possible, in both callback-based and future-based way, by calling `IProxy::getPropertyAsync()` method:
1385+
Getting a property in asynchronous manner is also possible, in callback-based, future-based, or (with C++20) awaitable way, by calling `IProxy::getPropertyAsync()` method:
13421386
13431387
```c++
13441388
// Callback-based method:
@@ -1347,10 +1391,15 @@ auto callback = [](std::optional<sdbus::Error> /*error*/, sdbus::Variant value)
13471391
std::cout << "Got property value: " << value.get<uint32_t>() << std::endl;
13481392
};
13491393
uint32_t status = proxy->getPropertyAsync("status").onInterface("org.sdbuscpp.Concatenator").uponReplyInvoke(std::move(callback));
1394+
13501395
// Future-based method:
13511396
std::future<sdbus::Variant> statusFuture = object.getPropertyAsync("status").onInterface("org.sdbuscpp.Concatenator").getResultAsFuture();
13521397
...
13531398
std::cout << "Got property value: " << statusFuture.get().get<uint32_t>() << std::endl;
1399+
1400+
// Awaitable method (C++20):
1401+
auto value = co_await proxy->getPropertyAsync("status").onInterface("org.sdbuscpp.Concatenator").getResultAsAwaitable();
1402+
std::cout << "Got property value: " << value.get<uint32_t>() << std::endl;
13541403
```
13551404
13561405
More information on an `error` callback handler parameter, on behavior of `future` in erroneous situations, can be found in section [Asynchronous client-side methods](#asynchronous-client-side-methods).
@@ -1364,14 +1413,18 @@ uint32_t status = ...;
13641413
proxy->setProperty("status").onInterface("org.sdbuscpp.Concatenator").toValue(status);
13651414
```
13661415
1367-
Setting a property in asynchronous manner is also possible, in both callback-based and future-based way, by calling `IProxy::setPropertyAsync()` method:
1416+
Setting a property in asynchronous manner is also possible, in callback-based, future-based, or awaitable way, by calling `IProxy::setPropertyAsync()` method:
13681417
13691418
```c++
13701419
// Callback-based method:
13711420
auto callback = [](std::optional<sdbus::Error> error { /*... Error handling in case error contains a value...*/ };
13721421
uint32_t status = proxy->setPropertyAsync("status").onInterface("org.sdbuscpp.Concatenator").toValue(status).uponReplyInvoke(std::move(callback));
1422+
13731423
// Future-based method:
13741424
std::future<void> statusFuture = object.setPropertyAsync("status").onInterface("org.sdbuscpp.Concatenator").getResultAsFuture();
1425+
1426+
// Awaitable method (C++20):
1427+
co_await proxy->setPropertyAsync("status").onInterface("org.sdbuscpp.Concatenator").toValue(status).getResultAsAwaitable();
13751428
```
13761429
13771430
More information on `error` callback handler parameter, on behavior of `future` in erroneous situations, can be found in section [Asynchronous client-side methods](#asynchronous-client-side-methods).
@@ -1468,7 +1521,7 @@ When implementing the adaptor, we simply need to provide the body for the `statu
14681521
14691522
We can mark the property so that the generator generates either asynchronous variant of getter method, or asynchronous variant of setter method, or both. Annotations names are `org.freedesktop.DBus.Property.Get.Async`, or `org.freedesktop.DBus.Property.Set.Async`, respectively. Their values must be set to `client`.
14701523
1471-
In addition, we can choose through annotations `org.freedesktop.DBus.Property.Get.Async.ClientImpl`, or `org.freedesktop.DBus.Property.Set.Async.ClientImpl`, respectively, whether a callback-based or future-based variant will be generated. The concept is analogous to the one for asynchronous D-Bus methods described above in this document.
1524+
In addition, we can choose through annotations `org.freedesktop.DBus.Property.Get.Async.ClientImpl`, or `org.freedesktop.DBus.Property.Set.Async.ClientImpl`, respectively, whether a callback-based, future-based, or awaitable variant will be generated. Supported values are `callback`, `future`, and `awaitable`. The concept is analogous to the one for asynchronous D-Bus methods described above in this document.
14721525
14731526
The callback-based method will generate a pure virtual function `On<PropertyName>Property[Get|Set]Reply()`, which must be overridden by the derived class.
14741527
@@ -1679,7 +1732,7 @@ The macro must be placed in the global namespace. The first argument is the stru
16791732
16801733
This is described in detail in the following sections.
16811734
1682-
> **_Note_:** The macro supports **max 16 struct members**. If you need more, feel free to open an issue, or implement the teaching code yourself :o)
1735+
> **_Note_:** The macro supports **max 16 struct members**. If you need more, feel free to open an issue, or implement the teaching code yourself :o)
16831736
16841737
> **_Another note_:** You may have noticed one of `my::Struct` members is `std::list`. Thanks to the custom support for `std::list` implemented higher above, it's now automatically accepted by sdbus-c++ as a D-Bus array representation.
16851738

0 commit comments

Comments
 (0)