From 0e88c9784d3d30459145f3dc5c26087dc8a8c00f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dietrich=20Kr=C3=B6nke?= Date: Thu, 8 Apr 2021 21:30:39 +0200 Subject: [PATCH] iox-#482 spell checking and markdown linting in example docs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Dietrich Krönke --- iceoryx_examples/callbacks/README.md | 88 +++++--- iceoryx_examples/callbacks_in_c/README.md | 73 +++--- iceoryx_examples/icecrystal/Readme.md | 5 +- iceoryx_examples/icedelivery/README.md | 16 +- iceoryx_examples/icedelivery_in_c/README.md | 84 ++++--- iceoryx_examples/iceensemble/README.md | 2 +- iceoryx_examples/icehello/README.md | 2 +- iceoryx_examples/iceoptions/README.md | 2 +- iceoryx_examples/iceperf/README.md | 13 +- iceoryx_examples/singleprocess/README.md | 45 ++-- iceoryx_examples/waitset/README.md | 238 ++++++++++++-------- iceoryx_examples/waitset_in_c/README.md | 71 ++++-- 12 files changed, 385 insertions(+), 254 deletions(-) diff --git a/iceoryx_examples/callbacks/README.md b/iceoryx_examples/callbacks/README.md index 4ad4e599303..9b10b4752cd 100644 --- a/iceoryx_examples/callbacks/README.md +++ b/iceoryx_examples/callbacks/README.md @@ -1,8 +1,9 @@ # Listener (or how to use callbacks with iceoryx) ## Thread Safety + The Listener is thread-safe and can be used without restrictions. -But be aware that all provided callbacks are executed concurrently +But be aware that all provided callbacks are executed concurrently in the background thread of the Listener. If you access structures inside this callback you have to either ensure that you are the only one accessing it or that it is accessed with a guard like a `std::mutex`. @@ -12,14 +13,14 @@ one accessing it or that it is accessed with a guard like a `std::mutex`. For an introduction into the terminology please read the Glossary in the [WaitSet C++ example](https://github.com/eclipse-iceoryx/iceoryx/tree/master/iceoryx_examples/waitset). -The Listener is a completely thread-safe construct which reacts to events by -executing registered callbacks in a background thread. Events can be emitted by -_EventOrigins_ like a subscriber or a user trigger. Some of the _EventOrigins_ +The Listener is a completely thread-safe construct that reacts to events by +executing registered callbacks in a background thread. Events can be emitted by +_EventOrigins_ like a subscriber or a user trigger. Some of the _EventOrigins_ like the subscriber can hereby emit more than one event type. -The interface of a listener consists of two methods: `attachEvent` to attach a -new event with a callback and `detachEvent`. These two methods can be called -concurrently, even from inside a callback which was triggered by an event! +The interface of a listener consists of two methods: `attachEvent` to attach a +new event with a callback and `detachEvent`. These two methods can be called +concurrently, even from inside a callback that was triggered by an event! ## Expected Output @@ -27,42 +28,47 @@ concurrently, even from inside a callback which was triggered by an event! ## Code Walkthrough -!!! attention - Please be aware about the thread-safety restrictions of the _Listener_ and +!!! attention + Please be aware of the thread-safety restrictions of the _Listener_ and read the [Thread Safety](#thread-safety) chapter carefully. -Let's say we have an application which offers us two distinct services: -`Radar.FrontLeft.Counter` and `Rader.FrontRight.Counter`. Every time we have -received a sample from left and right we would like to calculate the sum with -the newest values and print it out. If we have received only one of the samples +Let's say we have an application that offers us two distinct services: +`Radar.FrontLeft.Counter` and `Rader.FrontRight.Counter`. Every time we have +received a sample from left and right we would like to calculate the sum with +the newest values and print it out. If we have received only one of the samples, we store it until we received the other side. ### ice_callbacks_publisher.cpp -The publisher of this example does not contain any new features but if you have -some questions take a look at the +The publisher of this example does not contain any new features but if you have +some questions take a look at the [icedelivery example](https://github.com/eclipse-iceoryx/iceoryx/tree/master/iceoryx_examples/icedelivery). ### ice_callbacks_subscriber.cpp + #### int main() -The subscriber main function starts as usual and after registering the runtime -we create the listener which starts a background thread. + +The subscriber main function starts as usual and after registering the runtime +we create the listener that starts a background thread. + ```cpp iox::popo::Listener listener; ``` -Because it is fun we also create a heartbeat trigger which will be triggered +Because it is fun, we also create a heartbeat trigger that will be triggered every 4 seconds so that `heartbeat received` can be printed to the console. -Furthermore, we have to create two subscriber to receive samples for the two +Furthermore, we have to create two subscribers to receive samples for the two services. + ```cpp iox::popo::UserTrigger heartbeat; iox::popo::Subscriber subscriberLeft({"Radar", "FrontLeft", "Counter"}); iox::popo::Subscriber subscriberRight({"Radar", "FrontRight", "Counter"}); ``` -Next thing is a `heartbeatThread` which will trigger our heartbeat trigger every +Next thing is a `heartbeatThread` which will trigger our heartbeat trigger every 4 seconds. + ```cpp std::thread heartbeatThread([&] { while (keepRunning) @@ -74,10 +80,11 @@ std::thread heartbeatThread([&] { ``` Now that everything is setup we can attach the subscribers to the listener so that -everytime a new sample (`iox::popo::SubscriberEvent::DATA_RECEIVED`) is received our callback -(`onSampleReceivedCallback`) will be called. We also attach -our `heartbeat` user trigger to print the hearbeat message to the console via another +every time a new sample (`iox::popo::SubscriberEvent::DATA_RECEIVED`) is received our callback +(`onSampleReceivedCallback`) will be called. We also attach +our `heartbeat` user trigger to print the heartbeat message to the console via another callback (`heartbeatCallback`). + ```cpp listener.attachEvent(heartbeat, heartbeatCallback).or_else([](auto) { std::cerr << "unable to attach heartbeat event" << std::endl; @@ -96,24 +103,27 @@ listener.attachEvent(subscriberRight, iox::popo::SubscriberEvent::DATA_RECEIVED, std::terminate(); }); ``` -Since a user trigger has only one event we do not have to specify an event when we attach + +Since a user trigger has only one event, we do not have to specify an event when we attach it to the listener. `attachEvent` returns a `cxx::expected` to inform us if the attachment -succeeded. When this is not the case the error handling is performed in the `.or_else([](auto){` part +succeeded. When this is not the case the error handling is performed in the `.or_else([](auto){` part after each `attachEvent` call. -In this example we choose to attach the same callback twice to make things easier +In this example, we choose to attach the same callback twice to make things easier but you are free to attach any callback with the signature `void(iox::popo::Subscriber *)`. -The setup is complete but it would terminate right away since we have no blocker which -waits until SIGINT or SIGTERM was send. In the other examples we hadn't have that problem -since we pulled all the events in a while true loop but working only with callbacks -requires something like our `shutdownSemaphore`, a semaphore on which we wait until +The setup is complete, but it would terminate right away since we have no blocker which +waits until SIGINT or SIGTERM was send. In the other examples, we had not that problem +since we pulled all the events in a while true loop but working only with callbacks +requires something like our `shutdownSemaphore`, a semaphore on which we wait until the signal callback increments it. + ```cpp shutdownSemaphore.wait(); ``` -When the `shutdownSemaphore` unblocks we clean up all resources and terminate the process +When the `shutdownSemaphore` unblocks we clean up all resources and terminate the process gracefully. + ```cpp listener.detachEvent(heartbeat); listener.detachEvent(subscriberLeft, iox::popo::SubscriberEvent::DATA_RECEIVED); @@ -122,13 +132,15 @@ listener.detachEvent(subscriberRight, iox::popo::SubscriberEvent::DATA_RECEIVED) heartbeatThread.join(); ``` -Hint: You do not have to detach an _EventOrigin_ like a subscriber or user trigger +Hint: You do not have to detach an _EventOrigin_ like a subscriber or user trigger before it goes out of scope. This also goes for the _Listener_, the implemented -RAII based design takes care of the resource cleanup. +RAII-based design takes care of the resource cleanup. #### The Callbacks + The callbacks must have a signature like `void(PointerToEventOrigin*)`. -Our `heartbeatCallback` for instance just prints the message `heartbeat received`. +Our `heartbeatCallback` for instance, just prints the message `heartbeat received`. + ```cpp void heartbeatCallback(iox::popo::UserTrigger*) { @@ -136,10 +148,11 @@ void heartbeatCallback(iox::popo::UserTrigger*) } ``` -The `onSampleReceivedCallback` is more complex. We first acquire the received +The `onSampleReceivedCallback` is more complex. We first acquire the received sample and check which subscriber signaled the event by acquiring the subscriber's service description. If the instance is equal to `FrontLeft` we store the sample in the `leftCache` otherwise in the `rightCache`. + ```cpp void onSampleReceivedCallback(iox::popo::Subscriber* subscriber) { @@ -157,9 +170,10 @@ void onSampleReceivedCallback(iox::popo::Subscriber* subscriber) } ``` -In a next step we check if both caches are filled. If this is the case we print +In the next step, we check if both caches are filled. If this is the case, we print an extra message which states the result of the sum of both received values. -Afterwards we reset both caches to start fresh again. +Afterward, we reset both caches to start fresh again. + ```cpp if (leftCache && rightCache) { diff --git a/iceoryx_examples/callbacks_in_c/README.md b/iceoryx_examples/callbacks_in_c/README.md index ccc71c6fb56..230d7c5f68c 100644 --- a/iceoryx_examples/callbacks_in_c/README.md +++ b/iceoryx_examples/callbacks_in_c/README.md @@ -1,6 +1,7 @@ # Listener in C (or how to use callbacks with iceoryx) ## Thread Safety + The Listener is thread-safe and can be used without restrictions. But be aware that all provided callbacks are executed concurrently in the background thread of the Listener. If you access structures @@ -10,9 +11,9 @@ one accessing it or that it is accessed with a guard like a `mutex`. ## Introduction For a general introduction into the Listener concept please take a look at -the first part of the +the first part of the [Listener C++ example](https://github.com/eclipse-iceoryx/iceoryx/tree/master/iceoryx_examples/callbacks) -and at the Glossary of the +and at the Glossary of the [WaitSet C++ example](https://github.com/eclipse-iceoryx/iceoryx/tree/master/iceoryx_examples/waitset). ## Expected Output @@ -21,7 +22,7 @@ and at the Glossary of the ## Code Walkthrough -!!! attention +!!! attention Please be aware about the thread-safety restrictions of the _Listener_ and read the [Thread Safety](#thread-safety) chapter carefully. @@ -30,48 +31,55 @@ C++ version. We have again an application which offers two services called `Radar.FrontLeft.Counter` and `Radar.FrontRight.Counter`. Every time we have received a sample from each service we calculate the sum of it. -### ice_c_callbacks_publisher.c +### ice_c_callbacks_publisher.c The publisher contains only already known iceoryx features. If some of them are not known to you please take a look at the [icedelivery in c example](https://github.com/eclipse-iceoryx/iceoryx/tree/master/iceoryx_examples/icedelivery_in_c). ### ice_c_callbacks_subscriber.c + #### int main() + The subscriber starts as usual by registering the process at the runtime. -In the next step we setup some `listenerStorage` on the stack where the listener -will be constructed in and initialize the listener which will start a background -thread for the upcoming event triggered callbacks. +In the next step, we setup some `listenerStorage` on the stack where the listener +will be constructed and initialize the listener that will start a background +thread for the upcoming event-triggered callbacks. + ```c iox_listener_storage_t listenerStorage; iox_listener_t listener = iox_listener_init(&listenerStorage); ``` -Besides the subscribers we also would like to have an event which will be triggered -by ourselfs - the `heartbeat`. +Besides the subscribers we also would like to have an event that will be triggered +by our self - the `heartbeat`. + ```c iox_user_trigger_storage_t heartbeatStorage; heartbeat = iox_user_trigger_init(&heartbeatStorage); ``` Both subscribers are having the same options which we setup with: + ```c iox_sub_options_t options; iox_sub_options_init(&options); options.historyRequest = 10U; options.queueCapacity = 5U; options.nodeName = "iox-c-callback-subscriber-node"; -``` +``` and then we can construct the two subscribers `subscriberLeft` and `subscriberRight`. -```c + +```c iox_sub_t subscriberLeft = iox_sub_init(&subscriberLeftStorage, "Radar", "FrontLeft", "Counter", &options); iox_sub_t subscriberRight = iox_sub_init(&subscriberRightStorage, "Radar", "FrontRight", "Counter", &options); ``` -Now that everything is initialized we start our `heartbeatTriggerThread` which +Now that everything is initialized, we start our `heartbeatTriggerThread` which triggers our `heartbeat` every 4 seconds. -```c + +```c pthread_t heartbeatTriggerThread; if (pthread_create(&heartbeatTriggerThread, NULL, cyclicHeartbeatTrigger, NULL)) { @@ -82,38 +90,43 @@ if (pthread_create(&heartbeatTriggerThread, NULL, cyclicHeartbeatTrigger, NULL)) Attaching the subscribers and the heartbeat allows the Listener to call the callbacks whenever the event is signaled by the _EventOrigin_. -```c + +```c iox_listener_attach_user_trigger_event(listener, heartbeat, &heartbeatCallback); iox_listener_attach_subscriber_event(listener, subscriberLeft, SubscriberEvent_DATA_RECEIVED, &onSampleReceivedCallback); iox_listener_attach_subscriber_event( listener, subscriberRight, SubscriberEvent_DATA_RECEIVED, &onSampleReceivedCallback); ``` + A user trigger can emit only one event therefore we do not provide the event type as an argument in the user trigger attach call. -Since we are following a push based approach - without an event loop which is pulling -the events and processing them, we require a blocker which waits until a signal +Since we are following a push-based approach - without an event loop that is pulling +the events and processing them, we require a blocker that waits until a signal signals the process to terminate. -```c + +```c while (keepRunning) { sleep_for(100); } ``` -When `keepRunning` is set to false we cleanup all the resources. First we detach +When `keepRunning` is set to false, we clean up all the resources. First, we detach the events from the Listener. This is an optional step since the Listener detaches -all events by itself when it is deinitialized. This goes also for all the _EventOrigins_, +all events by itself when it is deinitialized. This applies also for all the _EventOrigins_, if you for instance deinitialize an attached subscriber it will automatically detach itself from the Listener. -```c + +```c iox_listener_detach_user_trigger_event(listener, heartbeat); iox_listener_detach_subscriber_event(listener, subscriberLeft, SubscriberEvent_DATA_RECEIVED); iox_listener_detach_subscriber_event(listener, subscriberRight, SubscriberEvent_DATA_RECEIVED); ``` -In a last step we have to release all acquired resources -```c +In a last step we have to release all acquired resources + +```c pthread_join(heartbeatTriggerThread, NULL); iox_user_trigger_deinit(heartbeat); @@ -123,9 +136,11 @@ iox_listener_deinit(listener); ``` #### The callbacks + Every callback must have a signature like `void (iox_event_origin_t)`. Our `heartbeatCallback` just prints the message `heartbeat received` onto the console. -```c + +```c void heartbeatCallback(iox_user_trigger_t userTrigger) { (void)userTrigger; @@ -137,7 +152,8 @@ The `onSampleReceivedCallback` is a little bit more complex. First we acquire the chunk and then we have to find out which subscriber received the chunk. For that we acquire the service description of the subscriber and if its instance equals `FrontLeft` we store the chunk value in the `leftCache` otherwise in the `rightCache`. -```c + +```c void onSampleReceivedCallback(iox_sub_t subscriber) { const struct CounterTopic* userPayload; @@ -156,10 +172,11 @@ void onSampleReceivedCallback(iox_sub_t subscriber) } ``` -If both caches are set we can calculate the sum of both chunks and print them to -the console. To start fresh in the next cycle we reset the `leftCache` and -the `rightCache` afterwards. -```c +If both caches are set, we can calculate the sum of both chunks and print them to +the console. To start fresh in the next cycle, we reset the `leftCache` and +the `rightCache` afterward. + +```c if (leftCache.isSet && rightCache.isSet) { printf("Received samples from FrontLeft and FrontRight. Sum of %d + %d = %d\n", diff --git a/iceoryx_examples/icecrystal/Readme.md b/iceoryx_examples/icecrystal/Readme.md index 124f53d9b18..b77fac58c32 100644 --- a/iceoryx_examples/icecrystal/Readme.md +++ b/iceoryx_examples/icecrystal/Readme.md @@ -5,15 +5,16 @@ > This Readme.md is slightly outdated and not all functionality of the introspection is available with v1.0 This example teaches you how to make use of the introspection for debugging purposes. With the introspection you can -look into the machine room of RouDi. The introspection shows live information about the memory usage and all +look into the machine room of RouDi. It shows live information about the memory usage and all registered processes. Additionally, it shows the publisher and subscriber ports that are created inside the shared memory. ## Run icecrystal We reuse the binaries from -[icedelivery](https://github.com/eclipse-iceoryx/iceoryx/tree/master/iceoryx_examples/icedelivery). +[icedelivery](https://github.com/eclipse-iceoryx/iceoryx/tree/master/iceoryx_examples/icedelivery). Create four terminals and run one command in each of them. + ```sh # If installed and available in PATH environment variable iox-roudi diff --git a/iceoryx_examples/icedelivery/README.md b/iceoryx_examples/icedelivery/README.md index 8b5dbb8670c..0317bf7d273 100644 --- a/iceoryx_examples/icedelivery/README.md +++ b/iceoryx_examples/icedelivery/README.md @@ -13,8 +13,8 @@ Create three terminals and start RouDi, a publisher and a subscriber. You can al ## Code walkthrough -This example makes use of two kind of API flavours. With the untyped API you have the most flexibility. It enables you -to put higher level APIs with different look and feel on top of iceoryx. E.g. the ara::com API of AUTOSAR Adaptive or +This example makes use of two kinds of API flavors. With the untyped API, you have the most flexibility. It enables you +to put APIs on a higher level with a different look and feel on top of iceoryx. E.g. the ara::com API of AUTOSAR Adaptive or the ROS2 API. It is not meant to be used by developers in daily life, the assumption is that there will always be a higher abstraction. A simple example how such an abstraction could look like is given in the second step with the typed example. The typed API provides type safety combined with [RAII](https://en.cppreference.com/w/cpp/language/raii). @@ -106,7 +106,7 @@ data->y = 2.0; data->z = 3.0; ``` -And finanlly, in both ways, the value is made available to other subscribers with +And finally, in both ways, the value is made available to other subscribers with ```cpp publisher.publish(userPayload); @@ -119,7 +119,7 @@ captured with the signal handler and stops the loop. How can the subscriber application receive the data the publisher application just transmitted? -Similar to the publisher we include the topic data, the subscriber, the runtime as well as the signal handler header: +Similar to the publisher, we include the topic data, the subscriber, the runtime as well as the signal handler header: ```cpp #include "topic_data.hpp" @@ -165,11 +165,11 @@ while (!killswitch) }); ``` -The `killswitch` will be used to stop the programm execution. +The `killswitch` will be used to stop the program execution. Let's translate it into a story again: "Take the data and then if this succeeds, work with the sample, or else if an -error other than `NO_CHUNK_AVAILABLE` occured, print the string 'Error receiving chunk.'" Of course you don't need to -take care about all cases, but it is advised to do so. +error other than `NO_CHUNK_AVAILABLE` occurred, print the string 'Error receiving chunk.'" Of course, you don't need to +take care of all cases, but we advise doing so. In the `and_then` case the content of the sample is printed to the command line: @@ -178,7 +178,7 @@ auto object = static_cast(userPayload); std::cout << APP_NAME << " got value: " << object->x << std::endl; ``` -Please note the `static_cast` before reading out the data. It is necessary, because the untyped subscriber is unaware +Please note the `static_cast` before reading out the data. It is necessary because the untyped subscriber is unaware of the type of the transmitted data. After accessing the value, the chunk of memory needs to be explicitly released by calling: diff --git a/iceoryx_examples/icedelivery_in_c/README.md b/iceoryx_examples/icedelivery_in_c/README.md index c5092ed57b7..e2ffb63b3f4 100644 --- a/iceoryx_examples/icedelivery_in_c/README.md +++ b/iceoryx_examples/icedelivery_in_c/README.md @@ -1,11 +1,11 @@ # icedelivery in C -You can find a more detailed description of the C API in the +You can find a more detailed description of the C API in the [iceoryx_binding_c README.md](https://github.com/eclipse-iceoryx/iceoryx/blob/master/iceoryx_binding_c/README.md). ## Introduction -The behavior and structure is identical to the +The behavior and structure is identical to the [icedelivery C++ example](https://github.com/eclipse-iceoryx/iceoryx/tree/master/iceoryx_examples/icedelivery) so that we explain here only the C API differences and not the underlying mechanisms. @@ -27,10 +27,11 @@ we again follow the steps like: 4. **C API: Additionally, we have to remove the previously allocated Subscriber port!** -Let's take a look at the `receiving` function which comes with the +Let's take a look at the `receiving` function that comes with the `ice_c_subscriber.c` example. 1. We register our process at roudi with the name `iox-c-subscriber` + ```c const char APP_NAME[] = "iox-c-subscriber"; iox_runtime_init("APP_NAME"); @@ -42,9 +43,10 @@ Let's take a look at the `receiving` function which comes with the right after the connection is established and the `queueCapacity` how many samples the subscriber can hold. These are samples which the publisher has sent before the subscriber was connected. The `nodeName` is the name of the - node the subscriber belongs to. + node, where the subscriber belongs. The `subscriberStorage` is the place where the subscriber is stored in memory and `subscriber` is actually a pointer to that location. + ```c iox_sub_options_t options; iox_sub_options_init(&options); @@ -56,39 +58,42 @@ Let's take a look at the `receiving` function which comes with the iox_sub_t subscriber = iox_sub_init(&subscriberStorage, "Radar", "FrontLeft", "Object", &options); ``` - 3. In this loop we receive samples as long the `killswitch` is not - set to `true` by an external signal and then print the counter - value to the console. - ```c - while (!killswitch) - { - if (SubscribeState_SUBSCRIBED == iox_sub_get_subscription_state(subscriber)) - { - const void* userPayload = NULL; - while (ChunkReceiveResult_SUCCESS == iox_sub_take_chunk(subscriber, &userPayload)) - { - const struct RadarObject* sample = (const struct RadarObject*)(userPayload); - printf("%s got value: %.0f\n", APP_NAME, sample->x); - iox_sub_release_chunk(subscriber, userPayload); - } - } - else - { - printf("Not subscribed!\n"); - } - - sleep_for(1000); - } - ``` - - 4. When using the C API we have to cleanup the subscriber after - its usage. - ```c - iox_sub_deinit(subscriber); - ``` + 3. In this loop we receive samples as long the `killswitch` is not + set to `true` by an external signal and then print the counter + value to the console. + + ```c + while (!killswitch) + { + if (SubscribeState_SUBSCRIBED == iox_sub_get_subscription_state(subscriber)) + { + const void* userPayload = NULL; + while (ChunkReceiveResult_SUCCESS == iox_sub_take_chunk(subscriber, &userPayload)) + { + const struct RadarObject* sample = (const struct RadarObject*)(userPayload); + printf("%s got value: %.0f\n", APP_NAME, sample->x); + iox_sub_release_chunk(subscriber, userPayload); + } + } + else + { + printf("Not subscribed!\n"); + } + + sleep_for(1000); + } + ``` + + 4. When using the C API we have to clean up the subscriber after + its usage. + + ```c + iox_sub_deinit(subscriber); + ``` ### Publisher -The publisher is implemented in a way like in the + +The publisher is implemented in a similar way like in the [icedelivery C++ example](https://github.com/eclipse-iceoryx/iceoryx/tree/master/iceoryx_examples/icedelivery): 1. Create runtime instance. @@ -97,16 +102,19 @@ The publisher is implemented in a way like in the 4. **C API: Additionally, we have to remove the previously allocated Publisher port!** -Let's take a look at the `sending` function which comes with the +Let's take a look at the `sending` function that comes with the `ice_c_publisher.c` example. 1. We register our process at roudi with the name `iox-c-subscriber` + ```c const char APP_NAME[] = "iox-c-publisher"; iox_runtime_init("APP_NAME"); ``` + 2. We create a publisher with the service {"Radar", "FrontLeft", "Counter"} + ```c iox_pub_options_t options; iox_pub_options_init(&options); @@ -119,6 +127,7 @@ Let's take a look at the `sending` function which comes with the 3. Till an external signal sets `killswitch` to `true` we will send an incrementing number to all subscribers in every iteration and print the value of this number to the console. + ```c double ct = 0.0; @@ -148,7 +157,8 @@ Let's take a look at the `sending` function which comes with the } ``` - 5. And we cleanup our publisher port. + 4. And we cleanup our publisher port. + ```c iox_pub_destroy(publisher); ``` diff --git a/iceoryx_examples/iceensemble/README.md b/iceoryx_examples/iceensemble/README.md index cafe39f555d..26171fb0c88 100644 --- a/iceoryx_examples/iceensemble/README.md +++ b/iceoryx_examples/iceensemble/README.md @@ -29,7 +29,7 @@ The easiest way is to build all examples via `./tools/iceoryx_build_test.sh`. Th ./build/iceoryx_examples/iceoptions/iox-subscriber-with-options ``` -Alternatively you can use the provided [tmux](https://en.wikipedia.org/wiki/Tmux) script. +Alternatively, you can use the provided [tmux](https://en.wikipedia.org/wiki/Tmux) script. ```sh ./iceoryx_examples/iceensemble/run_iceensemble.sh diff --git a/iceoryx_examples/icehello/README.md b/iceoryx_examples/icehello/README.md index dc45bdf5eb4..dd1f6740b86 100644 --- a/iceoryx_examples/icehello/README.md +++ b/iceoryx_examples/icehello/README.md @@ -75,7 +75,7 @@ auto signalIntGuard = iox::posix::registerSignalHandler(iox::posix::Signal::INT, auto signalTermGuard = iox::posix::registerSignalHandler(iox::posix::Signal::TERM, sigHandler); ``` -In order to send our sample we loan some shared memory, inside the `while` loop: +In order to send our sample, we loan some shared memory inside the `while` loop: ```cpp auto loanResult = publisher.loan(); diff --git a/iceoryx_examples/iceoptions/README.md b/iceoryx_examples/iceoptions/README.md index 545fbdecc3f..6164a968093 100644 --- a/iceoryx_examples/iceoptions/README.md +++ b/iceoryx_examples/iceoptions/README.md @@ -3,7 +3,7 @@ ## Introduction This example demonstrates what kind of quality of service options can be configured on the publisher and subscriber -side. The options can be used for the typed and untyped C++ API flavours as well as the C API. +side. The options can be used for the typed and untyped C++ API flavors as well as the C API. ## Expected Output diff --git a/iceoryx_examples/iceperf/README.md b/iceoryx_examples/iceperf/README.md index 1fb67c586d9..908155d7001 100644 --- a/iceoryx_examples/iceperf/README.md +++ b/iceoryx_examples/iceperf/README.md @@ -16,10 +16,11 @@ At the end of the benchmark, the average latency for each payload size is printe ## Run iceperf Create three terminals and run one command in each of them. -The order is first the RouDi daemon, then iceperf-laurel which is the leader in this setup -and then iceperf-hardy for doing the ping pong measurements with iceperf-laurel. -You can set the number of measurement iterations (number of roundtrips) with a command line paramter +The order is first the RouDi daemon, then iceperf-laurel that is the leader in this setup +and then iceperf-hardy for doing the ping pong measurements with iceperf-laurel. +You can set the number of measurement iterations (number of roundtrips) with a command-line parameter of iceperf-laurel (e.g. `./iceperf-laurel 100000`) + ```sh # If installed and available in PATH environment variable iox-roudi @@ -31,8 +32,9 @@ of iceperf-laurel (e.g. `./iceperf-laurel 100000`) build/iceoryx_examples/iceperf/iceperf-hardy ``` -If you would like to test only the C++ API or the C API you can start iceperf-laurel and +If you would like to test only the C++ API or the C API you can start iceperf-laurel and iceperf-hardy with the parameter `cpp-api` or `c-api`. + ```sh build/iceoryx_examples/iceperf/iceperf-laurel 100000 cpp-api @@ -180,7 +182,7 @@ unix domain socket). Our focus here is on the abstraction layer on top which all ### iceperf-laurel application -Besides includes for the different IPC technologies, the topic_data.hpp file is included which contains the PerTopic struct that is used to transfer some information between the applications. Independent of the real payload size, this struct is used as some kind of header in each transferred sample. +Besides includes for the different IPC technologies, the topic_data.hpp file is included that contains the PerTopic struct that is used to transfer some information between the applications. Independent of the real payload size, this struct is used as some kind of header in each transferred sample. ```cpp struct PerfTopic @@ -194,6 +196,7 @@ Besides includes for the different IPC technologies, the topic_data.hpp file is With `payloadSize` as the payload size used for the current measurement. In case it is not possible to transfer the `payloadSize` with a single data transfer (e.g. OS limit for the payload of a single socket send), the payload is divided into several sub-packets. This is indicated with `subPackets`. The `run` flag is used to shutdown iceperf-hardy at the end of the benchmark. Let's set some constants to prevent magic values. The default number of round trips is set and names for the communication resources that are used. + ```cpp constexpr int64_t NUMBER_OF_ROUNDTRIPS{10000}; constexpr char APP_NAME[] = "laurel"; diff --git a/iceoryx_examples/singleprocess/README.md b/iceoryx_examples/singleprocess/README.md index 239d52db3fe..d152f5892ba 100644 --- a/iceoryx_examples/singleprocess/README.md +++ b/iceoryx_examples/singleprocess/README.md @@ -3,13 +3,14 @@ ## Introduction This example demonstrates how iceoryx can be used in a single process for -inter thread communication. This is for instance helpful if you would like +inter-thread communication. This is for instance helpful if you would like to establish a simple communication API in a 3D engine with a multitude of -threads which are interacting without starting separately RouDi everytime. +threads that are interacting without starting RouDi every time separately. ## Run singleprocess The example can be started with + ```sh build/iceoryx_examples/singleprocess/single_process ``` @@ -17,7 +18,8 @@ build/iceoryx_examples/singleprocess/single_process After you have started the example you should see an output like -``` + +```bash Log level set to: [ Error ] Reserving 71546016 bytes in the shared memory [/iceoryx_mgmt] [ Reserving shared memory successful ] @@ -39,7 +41,7 @@ The first lines until `RouDi is ready for clients` are coming from the RouDi startup in which the shared memory management segment and user data segment are created. -Afterwards the publisher and subscriber thread are started and are beginning to +Afterward, the publisher and subscriber thread are started and are beginning to transmit and receive data. ## Code Walkthrough @@ -60,6 +62,7 @@ transmit and receive data. default config. Additionally, RouDi needs some other components like a memory management unit which handles how the memory is created in which the transmission data is stored. The `IceOryxRouDiComponents` class is handling them for us + ```cpp iox::RouDiConfig_t defaultRouDiConfig = iox::RouDiConfig_t().setDefaults(); iox::roudi::IceOryxRouDiComponents roudiComponents(defaultRouDiConfig); @@ -69,21 +72,24 @@ transmit and receive data. disable monitoring. The last bool parameter `false` states that RouDi does not terminate all registered processes when he goes out of scope. If we would set it to `true`, our application would self terminate when the destructor is called. + ```cpp iox::roudi::RouDi roudi(roudiComponents.m_rouDiMemoryManager, roudiComponents.m_portManager, iox::roudi::RouDi::RoudiStartupParameters{iox::roudi::MonitoringMode::OFF, false}); ``` - 4. Here comes a key difference to an inter process application. If you would like - to communicate within one process you have to use `PoshRuntimeSingleProcess`. - You can create only one at a time! + 4. Here comes a key difference to an inter-process application. If you would like + to communicate within one process, you have to use `PoshRuntimeSingleProcess`. + You can create only one runtime at a time! + ```cpp iox::runtime::PoshRuntimeSingleProcess runtime("singleProcessDemo"); ``` - 5. Now that everything is up and running we can start the publisher and subscriber - thread, wait two seconds and then stop the example. + 5. Now that everything is up and running, we can start the publisher and subscriber + thread, wait two seconds and stop the example. + ```cpp std::thread publisherThread(publisher), subscriberThread(subscriber); @@ -98,20 +104,25 @@ transmit and receive data. ``` ### Implementation of Publisher and Subscriber -Since there are no differences to inter process ports you can take a look at the -[icedelivery example](https://github.com/eclipse-iceoryx/iceoryx/tree/master/iceoryx_examples/icedelivery) -for a detailed documentation. We only provide you here with a short overview. + +Since there are no differences to inter-process ports you can take a look at the +[icedelivery example](https://github.com/eclipse-iceoryx/iceoryx/tree/master/iceoryx_examples/icedelivery) +for detailed documentation. We only provide you here with a short overview. #### Publisher + We create a typed publisher with the following service description -(Service = `Single`, Instance = `Process`, Event = `Demo`) +(Service = `Single`, Instance = `Process`, Event = `Demo`) + ```cpp iox::popo::PublisherOptions publisherOptions; publisherOptions.historyCapacity = 10U; iox::popo::Publisher publisher({"Single", "Process", "Demo"}, publisherOptions); ``` -After that we are sending numbers in ascending order with an 100ms interval in a `while` loop till the + +After that, we are sending numbers in ascending order with a 100ms interval in a `while` loop till the variable `keepRunning` is false. + ```cpp uint64_t counter{0}; std::string greenRightArrow("\033[32m->\033[m "); @@ -128,16 +139,20 @@ while (keepRunning.load()) ``` #### Subscriber -Like with the publisher we are creating a corresponding subscriber port with the + +Like with the publisher, we are creating a corresponding subscriber port with the same service description. + ```cpp iox::popo::SubscriberOptions options; options.queueCapacity = 10U; options.historyRequest = 5U; iox::popo::Subscriber subscriber({"Single", "Process", "Demo"}, options); ``` + Now we can receive the data in a while loop till `keepRunning` is false. But we only try to acquire data if our `SubscribeState` is `SUBSCRIBED`. + ```cpp std::string orangeLeftArrow("\033[33m<-\033[m "); while (keepRunning.load()) diff --git a/iceoryx_examples/waitset/README.md b/iceoryx_examples/waitset/README.md index 81dd96d7d38..bb5cdde39c9 100644 --- a/iceoryx_examples/waitset/README.md +++ b/iceoryx_examples/waitset/README.md @@ -1,46 +1,48 @@ # WaitSet ## Thread Safety + The WaitSet is **not** thread-safe! + - It is **not** allowed to attach or detach _Triggerable_ classes with `attachEvent` or `detachEvent` when another thread is currently waiting for events with `wait` or `timedWait`. - Do **not** call any of the WaitSet methods concurrently. -The _TriggerHandle_ on the other hand is thread-safe! Therefore you are allowed to +The _TriggerHandle_ on the other hand, is thread-safe! Therefore you are allowed to attach/detach a _TriggerHandle_ to a _Triggerable_ while another thread may trigger the _TriggerHandle_. ## Introduction The WaitSet is a set where you can attach objects so that they can signal a wide variety -of events to one single notifyable. The typical approach is that one creates a +of events to one single notifiable. The typical approach is that one creates a WaitSet attaches multiple subscribers, user trigger or other _Triggerables_ to it and then wait till one or many of the attached entities signal an event. If that happens one receives a list of _EventInfos_ which is corresponding to all occurred events. ## Events and States -In this context we define the state of an object as a specified set of values -to which the members of that object are set. An event on the other hand -is defined as a state change. Usually an event changes the state of the corresponding +In this context, we define the state of an object as a specified set of values +to which the members of that object are set. An event on the other hand +is defined as a state change. Usually, an event changes the state of the corresponding object but this is not mandatory. -States and events can be attached to a WaitSet. The user will be informed only once -by the WaitSet for every event which occurred. If the event occurred multiple times -before the user has requested an event update from the WaitSet the user will still -be informed only once. State changes are induced by events +States and events can be attached to a WaitSet. The user will be informed only once +by the WaitSet for every event which occurred. If the event occurred multiple times +before the user has requested an event update from the WaitSet the user will still +be informed only once. State changes are induced by events and the user will be informed about a specific state as long as the state persists. -The subscriber for instance has the state `SubscriberState::HAS_DATA` and the event -`SubscriberEvent::DATA_RECEIVED`. If you attach the subscriber event -`SubscriberEvent::DATA_RECEIVED` to a WaitSet you will be notified about every new +The subscriber for instance has the state `SubscriberState::HAS_DATA` and the event +`SubscriberEvent::DATA_RECEIVED`. If you attach the subscriber event +`SubscriberEvent::DATA_RECEIVED` to a WaitSet you will be notified about every new incoming sample whenever you call `WaitSet::wait` or `WaitSet::timedWait`. If multiple -samples were sent before you called those methods you will still receive only one +samples were sent before you called those methods you will still receive only one notification. -If you attach on the other hand the state `SubscriberState::HAS_DATA` you will -be notified by `WaitSet::wait` or `WaitSet::timedWait` as long as there are received +If you attach the state `SubscriberState::HAS_DATA` you will +be notified by `WaitSet::wait` or `WaitSet::timedWait` as long as there are received samples present in the subscriber. ## Expected Output @@ -50,48 +52,49 @@ samples present in the subscriber. ## Glossary - - **EventCallback** a callback attached to an _EventInfo_. It must have the - following signature `void ( EventOrigin )`. Any free function, static - class method and non capturing lambda is allowed. You have to ensure the lifetime of that callback. - This can become important when you would like to use lambdas. - - **EventId** an id which is tagged to an event. It does not need to be unique +- **EventCallback** a callback attached to an _EventInfo_. It must have the + following signature `void ( EventOrigin )`. Any free function, static + class method and non capturing lambda is allowed. You have to ensure the lifetime of that callback. + This can become important when you would like to use lambdas. +- **EventId** an id which is tagged to an event. It does not need to be unique or follow any restrictions. The user can choose any arbitrary `uint64_t`. Assigning the same _EventId_ to multiple _Events_ can be useful when you would like to group _Events_. - - **EventInfo** a class which corresponds with _Triggers_ and is used to inform +- **EventInfo** a class which corresponds with _Triggers_ and is used to inform the user which _Event_ occurred. You can use the _EventInfo_ to acquire the _EventId_, call the _EventCallback_ or acquire the _EventOrigin_. - - **EventOrigin** the pointer to the class where the _Event_ originated from, short +- **EventOrigin** the pointer to the class where the _Event_ originated from, short pointer to the _Triggerable_. - - **Event** a state change of an object. - - **Events** a _Triggerable_ will signal an event via a _TriggerHandle_ to a _Notifyable_. +- **Event** a state change of an object. +- **Events** a _Triggerable_ will signal an event via a _TriggerHandle_ to a _Notifyable_. For instance one can attach the subscriber event `DATA_RECEIVED` to _WaitSet_. This will cause the subscriber to notify the WaitSet via the _TriggerHandle_ everytime when a sample was received. - - **Notifyable** is a class which listens to events. A _TriggerHandle_ which corresponds to a _Trigger_ +- **Notifyable** is a class which listens to events. A _TriggerHandle_ which corresponds to a _Trigger_ is used to notify the _Notifyable_ that an event occurred. The WaitSet is a _Notifyable_. - - **State** a specified set of values to which the members of an object are set. - - **Trigger** a class which is used by the _Notifyable_ to acquire the information which events were +- **State** a specified set of values to which the members of an object are set. +- **Trigger** a class which is used by the _Notifyable_ to acquire the information which events were signalled. It corresponds to a _TriggerHandle_. If the _Notifyable_ goes out of scope the corresponding _TriggerHandle_ will be invalidated and if the _Triggerable_ goes out of scope the corresponding _Trigger_ will be invalidated. - - **Triggerable** a class which has attached a _TriggerHandle_ to itself to signal +- **Triggerable** a class which has attached a _TriggerHandle_ to itself to signal certain _Events_ to a _Notifyable_. - - **TriggerHandle** a thread-safe class which can be used to trigger a _Notifyable_. +- **TriggerHandle** a thread-safe class which can be used to trigger a _Notifyable_. If a _TriggerHandle_ goes out of scope it will detach itself from the _Notifyable_. A _TriggerHandle_ is logical equal to another _Trigger_ if they: - - are attached to the same _Notifyable_ (or in other words they are using the + - are attached to the same _Notifyable_ (or in other words they are using the same `ConditionVariable`) - - they have the same _EventOrigin_ - - they have the same callback to verify that they were triggered + - they have the same _EventOrigin_ + - they have the same callback to verify that they were triggered (`hasEventCallback`) - - they have the same _EventId_ - - **WaitSet** a _Notifyable_ which manages a set of _Triggers_ which are corresponding to _Events_. - A user may attach or detach events. The _Waitset_ listens + - they have the same _EventId_ +- **WaitSet** a _Notifyable_ which manages a set of _Triggers_ which are corresponding to _Events_. + A user may attach or detach events. The _Waitset_ is listening to the whole set of _Triggers_ and if one or more _Triggers_ are triggered by an event it will notify the user. If a _WaitSet_ goes out of scope all attached _Triggers_ will be invalidated. ## Quick Overview + **Events** or **States** can be attached to a **Notifyable** like the **WaitSet**. The **WaitSet** will listen on **Triggers** for a signal that an **Event** has occurred and it hands out **TriggerHandles** to **Triggerable** objects. The **TriggerHandle** is used to inform the **WaitSet** @@ -99,8 +102,8 @@ about the occurrence of an **Event**. When returning from `WaitSet::wait()` the associated with **Events** which had occurred and **States** which persists. The **EventOrigin**, **EventId** and **EventCallback** are stored inside of the **EventInfo** and can be acquired by the user. -!!! attention - Please be aware about the thread-safety restrictions of the _WaitSet_ and +!!! attention + Please be aware of the thread-safety restrictions of the _WaitSet_ and read the [Thread Safety](#thread-safety) chapter carefully. ## Reference @@ -118,8 +121,9 @@ are stored inside of the **EventInfo** and can be acquired by the user. |acquire _EventOrigin_|`event->getOrigin();`| ## Use Cases + This example consists of 5 use cases. - + 1. `ice_waitset_gateway.cpp`: We build a gateway to forward data to another network. A list of subscriber events are handled in an uniform way by defining a callback which is executed for every subscriber who @@ -157,10 +161,11 @@ only once. Let's start by implementing our callback which prints the subscriber pointer, the payload size and the payload pointer to the console. We have to process all samples -as long as there are samples in the subscriber since we attached an event which notifies -us only once. But it is impossible to miss samples since the notification is reset -right after `wait` or `timedWait` is returned - this means if a sample arrives after +as long as there are samples in the subscriber since we attached an event that notifies +us only once. But it is impossible to miss samples since the notification is reset +right after `wait` or `timedWait` is returned - this means if a sample arrives after those calls we will be notified again. + ```cpp void subscriberCallback(iox::popo::UntypedSubscriber* const subscriber) { @@ -174,6 +179,7 @@ void subscriberCallback(iox::popo::UntypedSubscriber* const subscriber) } } ``` + An _Event_ always requires a callback which has the following signature `void (EventOrigin)`. In our example the _EventOrigin_ is a `iox::popo::UntypedSubscriber` pointer which we use to acquire the latest sample by calling @@ -183,6 +189,7 @@ the console inside of the `and_then` lambda. In our `main` function we create a _WaitSet_ which has storage capacity for 5 events, 4 subscribers and one shutdown trigger, after we registered us at our central broker RouDi. Then we attach our `shutdownTrigger` to handle `CTRL+c` events. + ```cpp iox::popo::WaitSet waitset; @@ -194,8 +201,8 @@ waitset.attachEvent(shutdownTrigger).or_else([](auto) { After that we create a vector to hold our subscribers, we create and then attach them to our _WaitSet_ with the `SubscriberEvent::DATA_RECEIVED` event and the `subscriberCallback`. -Everytime one -of the subscribers is receiving a new sample it will trigger the _WaitSet_. +Everytime one of the subscribers is receiving a new sample it will trigger the _WaitSet_. + ```cpp iox::cxx::vector subscriberVector; for (auto i = 0; i < NUMBER_OF_SUBSCRIBERS; ++i) @@ -210,6 +217,7 @@ for (auto i = 0; i < NUMBER_OF_SUBSCRIBERS; ++i) }); } ``` + `attachEvent` is returning a `cxx::expected` which informs us if attaching the event succeeded. In the `.or_else([&](auto){/*...*/})` part we perform the error handling whenever `attachEvent` failed. @@ -224,6 +232,7 @@ We iterate through this vector, if an _Event_ originated from the `shutdownTrigg we exit the program otherwise we just call the assigned callback by calling the trigger. This will then call `subscriberCallback` with the _EventOrigin_ (the pointer to the untyped subscriber) as parameter. + ```cpp while (true) { @@ -243,6 +252,7 @@ while (true) ``` ### Grouping + In our next use case we would like to divide the subscribers into two groups and we do not want to attach a callback to them. Instead we perform the calls on the subscribers directly. Additionally, we would like to be notified as long as there @@ -250,6 +260,7 @@ are samples in the subscriber queue therefore we have to attach the `SubscriberS We again start by creating a _WaitSet_ with a capacity of 5 (4 subscribers and 1 shutdownTrigger), and attach the `shutdownTrigger` to handle `CTRL+c`. + ```cpp iox::popo::WaitSet waitset; @@ -260,6 +271,7 @@ waitset.attachEvent(shutdownTrigger).or_else([](auto) { ``` Now we create a vector of 4 subscribers. + ```cpp iox::cxx::vector subscriberVector; for (auto i = 0; i < NUMBER_OF_SUBSCRIBERS; ++i) @@ -269,9 +281,9 @@ for (auto i = 0; i < NUMBER_OF_SUBSCRIBERS; ++i) } ``` -After that we define our two groups with the ids `FIRST_GROUP_ID` and `SECOND_GROUP_ID` -and attach the first two subscribers with the state `SubscriberState::HAS_DATA` to the first group and the remaining subscribers -to the second group. +After that, we define our two groups with the ids `FIRST_GROUP_ID` and `SECOND_GROUP_ID` +and attach the first two subscribers with the state `SubscriberState::HAS_DATA` to the first group and the remaining subscribers to the second group. + ```cpp for (auto i = 0; i < NUMBER_OF_SUBSCRIBERS / 2; ++i) { @@ -295,6 +307,7 @@ for (auto i = NUMBER_OF_SUBSCRIBERS / 2; i < NUMBER_OF_SUBSCRIBERS; ++i) The event loop calls `auto eventVector = waitset.wait()` in a blocking call to receive a vector of all the _EventInfos_ which are corresponding to the occurred events. If the _Event_ originated from the `shutdownTrigger` we terminate the program. + ```cpp while (true) { @@ -310,7 +323,8 @@ while (true) The remaining part of the loop is handling the subscribers. In the first group we would like to print the received data to the console and in the second group -we just dismiss the received data. +we just dismiss the received data. + ```cpp else if (event->getEventId() == FIRST_GROUP_ID) { @@ -327,11 +341,13 @@ we just dismiss the received data. subscriber->releaseQueuedData(); } ``` -!!! attention + +!!! attention In the second group we would not dismiss the data because we would be notified by the WaitSet immediately again since the subscriber has still the state `HAS_DATA`. ### Individual + When every _Triggerable_ requires a different reaction we need to know the origin of an _Event_. We can call `event.doesOriginateFrom(EventOrigin)` which will return true if the event originated from _EventOrigin_ and @@ -339,6 +355,7 @@ otherwise false. We start this example by creating a _WaitSet_ with the default capacity and attaching the `shutdownTrigger` to handle `CTRL-c`. + ```cpp iox::popo::WaitSet waitset<>; @@ -350,6 +367,7 @@ waitset.attachEvent(shutdownTrigger).or_else([](auto) { Additionally, we create two subscribers and attach them with the state `SubscriberState::HAS_DATA` to the waitset to let them inform us whenever they have samples in their queue. + ```cpp iox::popo::Subscriber subscriber1({"Radar", "FrontLeft", "Counter"}); iox::popo::Subscriber subscriber2({"Radar", "FrontLeft", "Counter"}); @@ -364,8 +382,8 @@ waitset.attachEvent(subscriber2, iox::popo::SubscriberState::HAS_DATA).or_else([ }); ``` -With that set up we enter the event loop and handle the program termination -first. +With that set up we enter the event loop and handle the program termination first. + ```cpp while (true) { @@ -382,7 +400,8 @@ while (true) When the origin is `subscriber1` we would like to state that subscriber 1 has received the following number X. But for `subscriber2` we just dismiss the received samples. We accomplish this by asking the `event` if it originated from the -corresponding subscriber. If so we act. +corresponding subscriber. If so, we act. + ```cpp else if (event->doesOriginateFrom(&subscriber1)) { @@ -398,9 +417,11 @@ corresponding subscriber. If so we act. ``` ### Sync + Let's say we have `SomeClass` and would like to execute a cyclic static method `cyclicRun` every second. We could execute any arbitrary algorithm in there but for now we just print `activation callback`. The class could look like + ```cpp class SomeClass { @@ -411,7 +432,8 @@ class SomeClass } }; ``` -!!! attention + +!!! attention The user trigger is event based and always reset after the WaitSet has acquired all triggered objects. @@ -419,6 +441,7 @@ We begin as always, by creating a _WaitSet_ with the default capacity and by attaching the `shutdownTrigger` to it. In this case we do not set an event id when calling `attachEvent` which means the default event id `EventInfo::INVALID_ID` is set. + ```cpp iox::popo::WaitSet<> waitset; @@ -432,6 +455,7 @@ waitset.attachEvent(shutdownTrigger).or_else([](auto) { After that we require a `cyclicTrigger` to trigger our `cyclicRun` every second. Therefore, we attach it to the `waitset` with eventId `0` and the callback `SomeClass::cyclicRun` + ```cpp iox::popo::UserTrigger cyclicTrigger; waitset.attachEvent(cyclicTrigger, 0U, &SomeClass::cyclicRun).or_else([](auto) { @@ -442,6 +466,7 @@ waitset.attachEvent(cyclicTrigger, 0U, &SomeClass::cyclicRun).or_else([](auto) { The next thing we need is something which will trigger our `cyclicTrigger` every second. We use a infinite loop packed inside of a thread. + ```cpp std::thread cyclicTriggerThread([&] { while (keepRunning.load()) @@ -454,6 +479,7 @@ std::thread cyclicTriggerThread([&] { Everything is set up and we can implement the event loop. As usual we handle `CTRL-c` which is indicated by the `shutdownTrigger`. + ```cpp while (keepRunning.load()) { @@ -468,6 +494,7 @@ while (keepRunning.load()) ``` The `cyclicTrigger` callback is called in the else part. + ```cpp else { @@ -476,28 +503,29 @@ The `cyclicTrigger` callback is called in the else part. ``` ### Trigger + In this example we describe how you would implement a _Triggerable_ class which -can be attached to a _WaitSet_ or a -[Listener](https://github.com/eclipse-iceoryx/iceoryx/tree/master/iceoryx_examples/callbacks). -Our class in this example will be called `MyTriggerClass` and it can signal the _WaitSet_ -the two states `HAS_PERFORMED_ACTION` and `IS_ACTIVATED`. Furthermore, we can also attach the -two corresponding events `PERFORM_ACTION_CALLED` and `ACTIVATE_CALLED`. The -`PERFORMED_ACTION_CALLED` event is triggered whenever the method `performAction` is called and -the state `HAS_PERFORMED_ACTION` persists until someone resets the state with the method -`reset()`. The same goes for the event `ACTIVATE_CALLED` which is triggered by an `activate()` -call and the corresponding state `IS_ACTIVATED` which stays until someone resets it with +can be attached to a _WaitSet_ or a +[Listener](https://github.com/eclipse-iceoryx/iceoryx/tree/master/iceoryx_examples/callbacks). +Our class in this example will be called `MyTriggerClass` and it can signal the _WaitSet_ +the two states `HAS_PERFORMED_ACTION` and `IS_ACTIVATED`. Furthermore, we can also attach the +two corresponding events `PERFORM_ACTION_CALLED` and `ACTIVATE_CALLED`. The +`PERFORMED_ACTION_CALLED` event is triggered whenever the method `performAction` is called and +the state `HAS_PERFORMED_ACTION` persists until someone resets the state with the method +`reset()`. The same goes for the event `ACTIVATE_CALLED` which is triggered by an `activate()` +call and the corresponding state `IS_ACTIVATED` which stays until someone resets it with `reset()`. #### MyTriggerClass ##### Attaching States -A class which would like to attach states to a _WaitSet_ has to implement the +A class that would like to attach states to a _WaitSet_ has to implement the following methods. - + 1. `void enableState(iox::popo::TriggerHandle&&, const UserDefinedStateEnum )` - Used by the _WaitSet_ to attach a trigger handle to the object so that the + Used by the _WaitSet_ to attach a trigger handle to the object so that the object can notify the _WaitSet_ that it entered a certain state. 2. `void disableState(const UserDefinedStateEnum)` @@ -506,18 +534,19 @@ following methods. 3. `void invalidateTrigger(const uint64_t uniqueTriggerId)` - If the _WaitSet_ goes out of scope it calls this method to invalidate the loan + If the _WaitSet_ goes out of scope it calls this method to invalidate the loan trigger. 4. `iox::popo::WaitSetIsConditionSatisfiedCallback getCallbackForIsStateConditionSatisfied(const UserDefinedStateEnum)` - With every iteration the _WaitSet_ has to ask the object if the attached state - still persists. This is done with the `isStateConditionSatisfied` callback which + With every iteration the _WaitSet_ has to ask the object if the attached state + still persists. This is done with the `isStateConditionSatisfied` callback which will be returned here. -The `UserDefinedStateEnum` can be some arbitrary enum class which requires -`iox::popo::StateEnumIdentifier` as underlying type so that it can be identified as +The `UserDefinedStateEnum` can be some arbitrary enum class which requires +`iox::popo::StateEnumIdentifier` as underlying type so that it can be identified as an enum which describes certain states. In our example it is called `MyTriggerClassStates`. + ```cpp enum class MyTriggerClassStates : iox::popo::StateEnumIdentifier { @@ -528,13 +557,13 @@ enum class MyTriggerClassStates : iox::popo::StateEnumIdentifier ##### Attaching Events -Events can be attached to _WaitSets_ and -[Listeners](https://github.com/eclipse-iceoryx/iceoryx/tree/master/iceoryx_examples/callbacks). +Events can be attached to _WaitSets_ and +[Listeners](https://github.com/eclipse-iceoryx/iceoryx/tree/master/iceoryx_examples/callbacks). For this to work the class has to implement the following methods. 1. `void enableEvent(iox::popo::TriggerHandle&&, const UserDefinedEvenEnum)` - Used by the _WaitSet_ or the _Listener_ to attach a trigger handle which signals + Used by the _WaitSet_ or the _Listener_ to attach a trigger handle which signals certain events to them. 2. `void disableEvent(const UserDefinedStateEnum)` @@ -543,12 +572,13 @@ For this to work the class has to implement the following methods. 3. `void invalidateTrigger(const uint64_t uniqueTriggerId)` - Used to cleanup all loan trigger handles when the _WaitSet_ or _Listener_ goes + Used to cleanup all loan trigger handles when the _WaitSet_ or _Listener_ goes out of scope. -Like with the state enum the event enum can be also any arbitrary enum class which -has `iox::popo::EventEnumIdentifier` as an underlying type. In our example it is called -`MyTriggerClassEvents` +Like with the state enum the event enum can be also any arbitrary enum class which +has `iox::popo::EventEnumIdentifier` as an underlying type. In our example it is called +`MyTriggerClassEvents`. + ```cpp enum class MyTriggerClassEvents : iox::popo::EventEnumIdentifier { @@ -561,10 +591,10 @@ enum class MyTriggerClassEvents : iox::popo::EventEnumIdentifier 1. `friend iox::popo::EventAttorney` - Methods like `enableEvent`, `disableEvent` etc. should never be accessible - via the public API and should be therefore private. To avoid that every class - has to befriend the _WaitSet_, _Listener_ and other internal structures we - implemented the client attorney pattern and the class has only to befriend + Methods like `enableEvent`, `disableEvent` etc. should never be accessible + via the public API and should be therefore private. To avoid that every class + has to befriend the _WaitSet_, _Listener_ and other internal structures we + implemented the client attorney pattern and the class has only to befriend the `iox::popo::EventAttorney`. 2. Deleted move and copy operations @@ -575,6 +605,7 @@ which are pointing to the _Triggerable_. After a move the callbacks inside of th would point to the wrong memory location and a copy could lead to an unattached object if there is no more space left in the _WaitSet_. Therefore we have to delete the move and copy operations for now. + ```cpp MyTriggerClass(const MyTriggerClass&) = delete; MyTriggerClass(MyTriggerClass&&) = delete; @@ -584,8 +615,9 @@ and copy operations for now. ##### Implementation -The method implementation of the two actions `activate` and `performAction` which trigger an +The method implementation of the two actions `activate` and `performAction` which trigger an event and causing a state change look like the following. + ```cpp class MyTriggerClass { @@ -611,6 +643,7 @@ just set a boolean to signal that the method was called. Every state based _Trigger_ requires a corresponding class method which returns a boolean stating if the state which led to the trigger still persists. In our case these are the two const methods `hasPerformedAction` and `isActivated`. + ```cpp bool hasPerformedAction() const noexcept { @@ -623,11 +656,11 @@ the two const methods `hasPerformedAction` and `isActivated`. } ``` -Since the following methods should not be accessible by the public but must be -accessible by any _Notifyable_ like the _WaitSet_ and to avoid that +Since the following methods should not be accessible by the public but must be +accessible by any _Notifyable_ like the _WaitSet_ and to avoid that we have to befriend every possible _Notifyable_ we created the `EventAttorney`. -Every _Triggerable_ has to befriend the `EventAttorney` which provides access -to the private methods `enableEvent`/`enableState`, `disableEvent`/`disableState`, `invalidateTrigger` and +Every _Triggerable_ has to befriend the `EventAttorney` which provides access +to the private methods `enableEvent`/`enableState`, `disableEvent`/`disableState`, `invalidateTrigger` and `getCallbackForIsStateConditionSatisfied` to all _Notifyables_. ```cpp @@ -636,7 +669,7 @@ to the private methods `enableEvent`/`enableState`, `disableEvent`/`disableState The method `enableEvent` is called by the _WaitSet_ when a `MyTriggerClass` event is being attached to it. During that process the _WaitSet_ creates a `triggerHandle` -and forwards the `event` to which this handle belongs. +and forwards the `event` to which this handle belongs. In the switch case statement we assign the `triggerHandle` to the corresponding internal trigger handle. @@ -658,6 +691,7 @@ internal trigger handle. ``` Attaching a state works in a similar fashion. + ```cpp void enableState(iox::popo::TriggerHandle&& triggerHandle, const MyTriggerClassStates state) noexcept { @@ -673,17 +707,18 @@ Attaching a state works in a similar fashion. } ``` -It is possible to use the same trigger for either a state or an event attachment -but then we loose the ability to attach the state and the corresponding event -at the same time to a _WaitSet_. In most cases it is not a problem and when you -attach an event when the corresponding state is already attached you will get -a warning message on the terminal and the already attached event is detached so that +It is possible to use the same trigger for either a state or an event attachment +but then we loose the ability to attach the state and the corresponding event +at the same time to a _WaitSet_. In most cases it is not a problem and when you +attach an event when the corresponding state is already attached you will get +a warning message on the terminal and the already attached event is detached so that the state can be attached. This is realized via the RAII idiom. The next thing on our checklist is the `invalidateTrigger` method used by the WaitSet to reset the _Trigger_ when it goes out of scope. Therefore we look up the -correct unique trigger id first and then `invalidate` it to make them unusable +correct unique trigger id first and then `invalidate` it to make them unusable in the future. + ```cpp void invalidateTrigger(const uint64_t uniqueTriggerId) { @@ -698,10 +733,11 @@ in the future. } ``` -Detaching an event in the _WaitSet_ will lead to a call to `disableEvent` in -our class. In this case we have to `reset` the corresponding trigger to invalidate -and release it from the _WaitSet_. Like before we use a switch case statement to +Detaching an event in the _WaitSet_ will lead to a call to `disableEvent` in +our class. In this case we have to `reset` the corresponding trigger to invalidate +and release it from the _WaitSet_. Like before we use a switch case statement to find the to the event corresponding trigger. + ```cpp void disableEvent(const MyTriggerClassEvents event) noexcept { @@ -718,6 +754,7 @@ find the to the event corresponding trigger. ``` The same idea is used when detaching a state. + ```cpp void disableState(const MyTriggerClassStates state) noexcept { @@ -735,10 +772,11 @@ The same idea is used when detaching a state. The last method we have to implement is `getCallbackForIsStateConditionSatisfied`. The _WaitSet_ can handle state based attachments and therefore it requires, beside the condition variable -which only states that something has happened, a callback to find the object -where it happened. This is the `isStateConditionSatisfied` callback. In our case we either return -the method pointer to `hasPerformedAction` or `isActivated` depending on which +which only states that something has happened, a callback to find the object +where it happened. This is the `isStateConditionSatisfied` callback. In our case we either return +the method pointer to `hasPerformedAction` or `isActivated` depending on which state was requested. + ```cpp iox::popo::WaitSetIsConditionSatisfiedCallback getCallbackForIsStateConditionSatisfied(const MyTriggerClassStates event) const noexcept @@ -788,6 +826,7 @@ void eventLoop() We start like in every other example by creating the `waitset` first. In this case the `waitset` and the `triggerClass` are stored inside of two global `optional`'s and have to be created with an `emplace` call. + ```cpp waitset.emplace(); triggerClass.emplace(); @@ -795,6 +834,7 @@ triggerClass.emplace(); After that we can attach the `IS_ACTIVATED` state and `PERFORM_ACTION_CALLED` event to the waitset and provide a callback for them. + ```cpp waitset->attachState(*triggerClass, MyTriggerClassStates::IS_ACTIVATED, ACTIVATE_ID, &callOnActivate) .or_else([](auto) { @@ -811,12 +851,14 @@ to the waitset and provide a callback for them. ``` Now that everything is set up we can start our `eventLoop` in a new thread. + ```cpp std::thread eventLoopThread(eventLoop); ``` A thread which will trigger an event every second is started with the following lines. + ```cpp std::thread triggerThread([&] { uint64_t activationCode = 1U; diff --git a/iceoryx_examples/waitset_in_c/README.md b/iceoryx_examples/waitset_in_c/README.md index abefaca3464..70cfaeb64fe 100644 --- a/iceoryx_examples/waitset_in_c/README.md +++ b/iceoryx_examples/waitset_in_c/README.md @@ -1,24 +1,26 @@ # WaitSet in C ## Thread Safety + The WaitSet is **not** thread-safe! + - It is **not** allowed to attach or detach _Triggerable_ classes with `iox_ws_attach_**` or `iox_ws_detach_**` when another thread is currently waiting for events with `iox_ws_wait` or `iox_ws_timed_wait`. - Do **not** call any of the `iox_ws_` functions concurrently. -The _TriggerHandle_ on the other hand is thread-safe! Therefore you are allowed to +The _TriggerHandle_ on the other hand, is thread-safe! Therefore you are allowed to attach/detach a _TriggerHandle_ to a _Triggerable_ while another thread may trigger the _TriggerHandle_. ## Introduction -A detailed introduction into the WaitSet nomenclature and topic can be found in the -[waitset C++ example](https://github.com/eclipse-iceoryx/iceoryx/tree/master/iceoryx_examples/waitset). -Here we will only introduce the C API and not the WaitSet in general. For that we will -take a look at the same use case as the -[waitset C++ example](https://github.com/eclipse-iceoryx/iceoryx/tree/master/iceoryx_examples/waitset). -The examples are also structured in the same way as the C++ ones. +A detailed introduction into the WaitSet nomenclature and topic can be found in the +[waitset C++ example](https://github.com/eclipse-iceoryx/iceoryx/tree/master/iceoryx_examples/waitset). +Here we will only introduce the C API and not the WaitSet in general. For this, we will +take a look at the same use case as the +[waitset C++ example](https://github.com/eclipse-iceoryx/iceoryx/tree/master/iceoryx_examples/waitset). +The examples are structured in the same way as the C++ ones. ## Expected Output @@ -26,8 +28,8 @@ The examples are also structured in the same way as the C++ ones. ## Code Walkthrough -!!! attention - Please be aware about the thread-safety restrictions of the _WaitSet_ and +!!! attention + Please be aware of the thread-safety restrictions of the _WaitSet_ and read the [Thread Safety](#thread-safety) chapter carefully. To run an example you need a running `iox-roudi` and the waitset publisher @@ -39,9 +41,10 @@ Let's say we would like to write a gateway and would like to forward every incoming message from a subscriber with the same callback. For instance we could perform a memcopy of the received data into a specific struct. -This could be performed by a function which we attach to an event as a -callback. In our case we have the function `subscriberCallback` which +This could be performed by a function that we attach to an event as a +callback. In our case, we have the function `subscriberCallback` that prints out the subscriber pointer and the content of the received sample. + ```c void subscriberCallback(iox_sub_t const subscriber) { @@ -54,13 +57,15 @@ void subscriberCallback(iox_sub_t const subscriber) } } ``` -Since we attach the `SubscriberEvent_DATA_RECEIVED` event to the _WaitSet_ which + +Since we attach the `SubscriberEvent_DATA_RECEIVED` event to the _WaitSet_ that notifies us just once when data was received we have to gather and process all chunks. One will never miss chunks since the event notification is reset after a call to `iox_ws_wait` or `iox_ws_timed_wait` which we introduce below. After we registered our runtime we create some stack storage for our WaitSet, initialize it and attach a `shutdownTrigger` to handle `CTRL-c`. + ```c iox_runtime_init("iox-c-waitset-gateway"); @@ -71,10 +76,11 @@ shutdownTrigger = iox_user_trigger_init(&shutdownTriggerStorage); iox_ws_attach_user_trigger_event(waitSet, shutdownTrigger, 0U, NULL); ``` -In the next steps we create 4 subscribers with `iox_sub_init`, +In the next steps, we create four subscribers with `iox_sub_init`, subscribe them to our topic and attach the event `SubscriberEvent_DATA_RECEIVED` to the WaitSet with the `subscriberCallback` and an event id `1U`. + ```c iox_sub_storage_t subscriberStorage[NUMBER_OF_SUBSCRIBERS]; @@ -94,6 +100,7 @@ for (uint64_t i = 0U; i < NUMBER_OF_SUBSCRIBERS; ++i) Now that everything is set up we enter the event loop. It always starts with a call to `iox_ws_wait`, a blocking call which returns us the number of occurred events. + ```c uint64_t missedElements = 0U; uint64_t numberOfEvents = 0U; @@ -112,6 +119,7 @@ while (keepRunning) The events which have occurred are stored in the `eventArray`. We iterate through it, if the `shutdownTrigger` was triggered we terminate the program otherwise we call the callback with `iox_event_info_call(event)`. + ```c for (uint64_t i = 0U; i < numberOfEvents; ++i) { @@ -128,7 +136,8 @@ for (uint64_t i = 0U; i < numberOfEvents; ++i) } ``` -Before we can close the program we cleanup all resources. +Before we can close the program, we cleanup all resources. + ```c for (uint64_t i = 0U; i < NUMBER_OF_SUBSCRIBERS; ++i) { @@ -141,12 +150,14 @@ iox_user_trigger_deinit(shutdownTrigger); ``` ### Grouping -In this scenario we have two groups of subscribers. We are interested in the + +In this scenario, we have two groups of subscribers. We are interested in the data of the first group and would like to print them onto the console and the data of the second group should be discarded. We start like in every example with creating the WaitSet and attaching the `shutdownTrigger`. + ```c iox_runtime_init("iox-c-waitset-grouping"); @@ -158,6 +169,7 @@ iox_ws_attach_user_trigger_event(waitSet, shutdownTrigger, 0U, NULL); ``` After that we can create a list of subscribers and subscribe them to our topic. + ```c iox_sub_storage_t subscriberStorage[NUMBER_OF_SUBSCRIBERS]; iox_sub_t subscriber[NUMBER_OF_SUBSCRIBERS]; @@ -179,6 +191,7 @@ To distinct our two groups we set the eventId of the first group to the `SubscriberState_HAS_DATA` state and the event id of the first group to our waitset. The third and forth subscriber are attached to the same waitset under the second group id. + ```c const uint64_t FIRST_GROUP_ID = 123; const uint64_t SECOND_GROUP_ID = 456; @@ -196,6 +209,7 @@ for (uint64_t i = 2U; i < 4U; ++i) We are again ready for our event loop. We start as usual by setting the array of events by calling `iox_ws_wait`. + ```c bool keepRunning = true; while (keepRunning) @@ -212,6 +226,7 @@ If that is the case we acquire the subscriber handle with sample and to print the result to the console. The second group is handled in the same way. But we do not print the new samples to screen, we just discard them. + ```c for (uint64_t i = 0U; i < numberOfEvents; ++i) { @@ -240,11 +255,13 @@ for (uint64_t i = 0U; i < numberOfEvents; ++i) } } ``` -In the case of the `SECOND_GROUP_ID` we have to release all queued chunks otherwise + +In the case of the `SECOND_GROUP_ID` we have to release all queued chunks otherwise the _WaitSet_ would notify us right away since the `SubscriberState_HAS_DATA` still persists. The last thing we have to do is to cleanup all the acquired resources. + ```c for (uint64_t i = 0U; i < NUMBER_OF_SUBSCRIBERS; ++i) { @@ -256,13 +273,15 @@ iox_user_trigger_deinit(shutdownTrigger); ``` ### Individual + We also can handle every event individually, for instance when you would like to have a different reaction for every subscriber which has received a sample. One way would be to assign every subscriber a different callback, here we look -at a different approach. We check if the event originated from a specific +at a different approach. We check if the event originated from a specific subscriber and then perform the calls on that subscriber directly. -We start as usual, by creating a WaitSet and attach the `shutdownTrigger` to it. +We start as usual by creating a WaitSet and attach the `shutdownTrigger` to it. + ```c iox_runtime_init("iox-c-waitset-individual"); @@ -275,6 +294,7 @@ iox_ws_attach_user_trigger_event(waitSet, shutdownTrigger, 0U, NULL); Now we create two subscribers, subscribe them to our topic and attach them to the waitset without a callback and with the same trigger id. + ```c iox_sub_options_t options; iox_sub_options_init(&options); @@ -292,6 +312,7 @@ iox_ws_attach_subscriber_state(waitSet, subscriber[1U], SubscriberState_HAS_DATA We are ready to start the event loop. We begin by acquiring the array of all the triggered triggers. + ```c bool keepRunning = true; while (keepRunning) @@ -301,10 +322,11 @@ while (keepRunning) ``` The `shutdownTrigger` is handled as usual and -we use `iox_event_info_does_originate_from_subscriber` +we use `iox_event_info_does_originate_from_subscriber` to identify the event that originated from a specific subscriber. If it originated -from the first subscriber we print the received data to the console, if it +from the first subscriber we print the received data to the console, if it originated from the second subscriber we discard the data. + ```c for (uint64_t i = 0U; i < numberOfEvents; ++i) { @@ -331,6 +353,7 @@ originated from the second subscriber we discard the data. } } ``` + We conclude the example as always, by cleaning up the resources. ```c @@ -344,13 +367,14 @@ iox_user_trigger_deinit(shutdownTrigger); ``` ### Sync -In this example we demonstrate how you can use the WaitSet to trigger a cyclic +In this example, we demonstrate how you can use the WaitSet to trigger a cyclic call every second. We use a user trigger which will be triggered in a separate thread every second to signal the WaitSet that it's time for the next run. Additionally, we attach a callback (`cyclicRun`) to this user trigger so that the event can directly call the cyclic call. We begin by creating the waitset and attach the `shutdownTrigger`. + ```c iox_runtime_init("iox-c-waitset-sync"); @@ -363,6 +387,7 @@ iox_ws_attach_user_trigger_event(waitSet, shutdownTrigger, 0, NULL); Now we create our cyclic trigger and attach it to our waitset with an eventId of `0` and the callback `cyclicRun`. + ```c cyclicTrigger = iox_user_trigger_init(&cyclicTriggerStorage); iox_ws_attach_user_trigger_event(waitSet, cyclicTrigger, 0, cyclicRun); @@ -370,6 +395,7 @@ iox_ws_attach_user_trigger_event(waitSet, cyclicTrigger, 0, cyclicRun); The thread which will trigger the `cyclicTrigger` every second is started in the next lines. + ```c pthread_t cyclicTriggerThread; if (pthread_create(&cyclicTriggerThread, NULL, cyclicTriggerCallback, NULL)) @@ -381,6 +407,7 @@ if (pthread_create(&cyclicTriggerThread, NULL, cyclicTriggerCallback, NULL)) Everything is prepared and we enter the event loop. We start by gathering all events in an array. + ```c while (keepRunning) { @@ -390,6 +417,7 @@ while (keepRunning) The `shutdownTrigger` is handled as usual and the `cyclicTrigger` is handled by just calling the attached callback with `iox_event_info_call(event)`. + ```c for (uint64_t i = 0U; i < numberOfEvents; ++i) { @@ -409,6 +437,7 @@ just calling the attached callback with `iox_event_info_call(event)`. ``` The last thing we have to do is to cleanup all the used resources. + ```c pthread_join(cyclicTriggerThread, NULL); iox_ws_deinit(waitSet);