Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Public Result class encapsulating a result value and a status code #207

Merged
merged 19 commits into from
Mar 21, 2024
Merged
Show file tree
Hide file tree
Changes from 16 commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
8e54642
first changes for Result
chandryan Feb 27, 2024
81faad4
move Result class out of detail, replace tryInvokeGetStatus with impl…
chandryan Feb 28, 2024
bdab4b0
Merge branch 'master' of https://github.com/open62541pp/open62541pp i…
chandryan Feb 28, 2024
18f35f4
fix Result test cases, add bool op and documentation
chandryan Mar 5, 2024
d0f3ab6
Merge branch 'master' of https://github.com/open62541pp/open62541pp i…
chandryan Mar 5, 2024
07401d9
use Result in async services
chandryan Mar 9, 2024
de5ab33
refactor: Rewrite operator bool test for Result
chandryan Mar 9, 2024
12904a8
change future to return promise<Result<T>>
chandryan Mar 9, 2024
4394c6a
Merge branch 'master' of https://github.com/open62541pp/open62541pp i…
chandryan Mar 9, 2024
67405f6
add workarounds for MSVC
chandryan Mar 9, 2024
0570563
update comment in async.h
chandryan Mar 9, 2024
e3ca699
Merge branch 'master' of https://github.com/open62541pp/open62541pp i…
chandryan Mar 18, 2024
bb9f603
adapt async docs and clean up minor code locations
chandryan Mar 18, 2024
a5cc13e
add default constructor for Result<T>
chandryan Mar 19, 2024
31e06dd
fix issue of gcc<11 with nullopt init in optional
chandryan Mar 19, 2024
0e96145
add Result<void>::hasValue
chandryan Mar 19, 2024
971b197
work on review comments
chandryan Mar 20, 2024
a69fb50
make BadStatus independent, remove value & hasValue from Result<void>
chandryan Mar 20, 2024
eb245c2
add template specialization for BadResult instead of UA_StatusCode
chandryan Mar 21, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 14 additions & 19 deletions doc/async_model.md
lukasberbuer marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ Open62541++ adapts the well-proven [asynchronous model of (Boost) Asio](https://
Open62541++ accepts completion tokens as the final argument of asynchronous operations.

```cpp
// Function signature of the completion handler: void(opcua::StatusCode, opcua::Variant&)
// Function signature of the completion handler: void(opcua::Result<opcua::Variant>&)
template <typename CompletionToken = opcua::DefaultCompletionToken>
auto opcua::services::readValueAsync(
opcua::Client& client,
Expand All @@ -21,7 +21,7 @@ auto opcua::services::readValueAsync(
Following completion tokens can be used (and described in more detail below):

- A callback function (object) with the expected completion signature
- `opcua::useFuture` to return a future object `std::future<T>`
- `opcua::useFuture` to return a future object `std::future<opcua::Result<T>>`
- `opcua::useDeferred` to return a callable for deferred execution
- `opcua::useDetached` to detach the asynchronous operation
- Custom tokens by providing template specializations for `opcua::AsyncResult`
Expand All @@ -31,31 +31,29 @@ Following completion tokens can be used (and described in more detail below):
If the user passes a function object as the completion token, the asynchronous operation behaves as previously described: the operation begins, and when the operation completes the result is passed to the callback. The callback function must match the expected signature:

```cpp
void(opcua::StatusCode code) // for void result types
void(opcua::StatusCode code, T& result); // for non-void result types
void(opcua::StatusCode code, T result); // for non-void result types (trivially-copyable)
void(opcua::Result<T>); // for void and non-void, trivially-copyable value types
void(opcua::Result<T>&); // for non-void value types
```

```cpp
opcua::services::readValueAsync(
client,
id,
[](opcua::StatusCode code, opcua::Variant& result) {
[](opcua::Result<opcua::Variant>& result) {
// ...
}
);
```

The status code `opcua::StatusCode` needs to be checked to determine the validity of the result.
The callback is executed within the client's or server's event loop. Please make sure not to block the event loop. Waiting for asynchronous results within the callback will block the further execution of the event loop.

### Future completion token

The special token `opcua::useFuture` can be passed as completion token to return a future object `std::future<T>`.
The special token `opcua::useFuture` can be passed as completion token to return a future object `std::future<opcua::Result<T>>`.

```cpp
std::future<opcua::Variant> future = opcua::services::readValueAsync(client, id, opcua::useFuture);
auto result = future.get(); // throws an exception if an error occurred
std::future<opcua::Result<opcua::Variant>> future = opcua::services::readValueAsync(client, id, opcua::useFuture);
auto value = future.get().value(); // throws an exception if an error occurred
```

### Deferred completion token
Expand All @@ -66,7 +64,7 @@ The token `opcua::useDeferred` is used to indicate that an asynchronous operatio
auto func = opcua::services::readValueAsync(client, id, opcua::useDeferred);

// start operation with provided completion token
func([](auto code, auto&& result) {
func([](auto&& result) {
// ...
});
```
Expand All @@ -90,14 +88,14 @@ struct PrintResultToken {};

namespace opcua {

template <typename Result>
struct AsyncResult<PrintResultToken, Result> {
template <typename T>
struct AsyncResult<PrintResultToken, T> {
template <typename Initiation, typename CompletionHandler, typename... Args>
static void initiate(Initiation&& initiation, PrintResultToken, Args&&... args) {
std::invoke(
std::forward<Initiation>(initiation),
[](StatusCode code, auto& result) {
std::cout << "Async operation completed: code=" << code << ", result=" << result << std::endl;
[](opcua::Result<T>& result) {
std::cout << "Async operation completed: code=" << result.code() << ", value=" << result.value() << std::endl;
},
std::forward<Args>(args)...
);
Expand All @@ -107,12 +105,9 @@ struct AsyncResult<PrintResultToken, Result> {
}
```

The `Result` template parameter specifies the result type of the asynchronous operation.
If `Result` is `void`, the expected signature of the completion handler is `void(opcua::StatusCode)`, otherwise it is `void(opcua::StatusCode, Result&)`.

The trait's `opcua::AsyncResult::initiate` member function is called with three arguments:
1. A function object that launches the async operation (initiating function)
2. A concrete completion handler with the signature `void(opcua::StatusCode, Result)`
2. A concrete completion handler with the signature `void(opcua::Result<T>)` or `void(opcua::Result<T>&)`
3. Any additional arguments for the function object

Please have a look at implementations in @ref async.h for further details.
11 changes: 6 additions & 5 deletions examples/method/client_method_async.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,8 @@ int main() {
future.wait();

std::cout << "Future ready, get method output\n";
auto output = future.get();
std::cout << output.at(0).getScalar<opcua::String>() << std::endl;
auto result = future.get();
std::cout << result.value().at(0).getScalar<opcua::String>() << std::endl;
}

// Asynchronously call method (callback variant)
Expand All @@ -40,9 +40,10 @@ int main() {
objNode.getNodeId(),
greetMethodNode.getNodeId(),
{opcua::Variant::fromScalar("Callback World")},
[](opcua::StatusCode code, std::vector<opcua::Variant>& output) {
std::cout << "Callback with status code " << code << ", get method output\n";
std::cout << output.at(0).getScalar<opcua::String>() << std::endl;
[](const opcua::Result<std::vector<opcua::Variant>>& result) {
std::cout
<< "Callback with status code " << result.code() << ", get method output\n";
std::cout << result.value().at(0).getScalar<opcua::String>() << std::endl;
}
);
}
Expand Down
3 changes: 3 additions & 0 deletions include/open62541pp/ErrorHandling.h
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
#pragma once

#include <new>
#include <stdexcept>

#include "open62541pp/detail/open62541/common.h"
Expand Down Expand Up @@ -69,6 +70,8 @@ namespace detail {
}
} catch (const BadStatus& e) {
return e.code();
} catch (const std::bad_alloc& /*e*/) {
return UA_STATUSCODE_BADOUTOFMEMORY;
} catch (...) {
return UA_STATUSCODE_BADINTERNALERROR;
}
Expand Down
Loading
Loading