Skip to content

Commit 6c6f2c3

Browse files
Merge pull request #903 from ekxide/iox2-817-blackboard-examples
[#817] blackboard examples + e2e tests
2 parents 4fcb536 + 2d40c41 commit 6c6f2c3

File tree

11 files changed

+322
-57
lines changed

11 files changed

+322
-57
lines changed

examples/README.md

Lines changed: 22 additions & 17 deletions
Large diffs are not rendered by default.

examples/rust/blackboard/BUILD.bazel

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
# Copyright (c) 2025 Contributors to the Eclipse Foundation
2+
#
3+
# See the NOTICE file(s) distributed with this work for additional
4+
# information regarding copyright ownership.
5+
#
6+
# This program and the accompanying materials are made available under the
7+
# terms of the Apache Software License 2.0 which is available at
8+
# https://www.apache.org/licenses/LICENSE-2.0, or the MIT license
9+
# which is available at https://opensource.org/licenses/MIT.
10+
#
11+
# SPDX-License-Identifier: Apache-2.0 OR MIT
12+
13+
load("@rules_rust//rust:defs.bzl", "rust_binary")
14+
15+
rust_binary(
16+
name = "creator",
17+
srcs = [
18+
"creator.rs",
19+
],
20+
deps = [
21+
"//iceoryx2:iceoryx2",
22+
"//iceoryx2-bb/container:iceoryx2-bb-container",
23+
],
24+
)
25+
26+
rust_binary(
27+
name = "opener",
28+
srcs = [
29+
"opener.rs",
30+
],
31+
deps = [
32+
"//iceoryx2:iceoryx2",
33+
"//iceoryx2-bb/container:iceoryx2-bb-container",
34+
],
35+
)

examples/rust/blackboard/README.md

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
# Blackboard
2+
3+
> [!CAUTION]
4+
> Every payload you transmit with iceoryx2 must implement [`ZeroCopySend`] to
5+
> be compatible with shared memory.
6+
> Usually, you can use the derive-macro `#[derive(ZeroCopySend)]` for most
7+
> types. If you implement it manually you must ensure that the payload type:
8+
>
9+
> * is self contained, no heap, no pointers to external sources
10+
> * has a uniform memory representation -> `#[repr(C)]`
11+
> * does not use pointers to manage their internal structure
12+
> * and its members don't implement `Drop` explicitly
13+
> * has a `'static` lifetime
14+
>
15+
> Data types like `String` or `Vec` will cause undefined behavior and may
16+
> result in segmentation faults. We provide alternative data types that are
17+
> compatible with shared memory. See the
18+
> [complex data type example](../complex_data_types) for guidance on how to
19+
> use them.
20+
21+
This example illustrates the blackboard messaging pattern. A writer updates the
22+
values in the blackboard every second and a reader reads and prints them to the
23+
console. The key-value pairs must be defined via the the service builder:
24+
25+
```rust
26+
node.service_builder(&service_name)
27+
.blackboard_creator::<u32>()
28+
.add_with_default::<u64>(0)
29+
.add::<FixedSizeByteString<30>>(5, "Groovy".try_into()?)
30+
.add_with_default::<f32>(9)
31+
.create()?;
32+
```
33+
34+
## How to Run
35+
36+
To observe the blackboard messaging pattern in action, open two separate
37+
terminals and execute the following commands:
38+
39+
### Terminal 1
40+
41+
```sh
42+
cargo run --example blackboard_creator
43+
```
44+
45+
### Terminal 2
46+
47+
```sh
48+
cargo run --example blackboard_opener
49+
```
50+
51+
Feel free to run multiple instances of reader processes simultaneously but note
52+
that the `blackboard_creator` must run first to create the blackboard service
53+
with the key-value pairs and that there can be only one writer.

examples/rust/blackboard/creator.rs

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -24,15 +24,17 @@ fn main() -> Result<(), Box<dyn core::error::Error>> {
2424
let service = node
2525
.service_builder(&"My/Funk/ServiceName".try_into()?)
2626
.blackboard_creator::<KeyType>()
27-
.add::<u64>(0, 0)
28-
.add::<FixedSizeByteString<100>>(5, "Groovy".try_into()?)
29-
.add::<f32>(9, 0.0)
27+
.add_with_default::<u64>(0)
28+
.add::<FixedSizeByteString<30>>(5, "Groovy".try_into()?)
29+
.add_with_default::<f32>(9)
3030
.create()?;
3131

32+
println!("Blackboard created.\n");
33+
3234
let writer = service.writer_builder().create()?;
3335

3436
let writer_handle_0 = writer.entry::<u64>(&0)?;
35-
let mut writer_handle_5 = writer.entry::<FixedSizeByteString<100>>(&5)?;
37+
let mut writer_handle_5 = writer.entry::<FixedSizeByteString<30>>(&5)?;
3638
let writer_handle_9 = writer.entry::<f32>(&9)?;
3739

3840
let mut counter = 0;
@@ -46,13 +48,13 @@ fn main() -> Result<(), Box<dyn core::error::Error>> {
4648
let entry_value_uninit = writer_handle_5.loan_uninit();
4749
let value = format!("Funky {}", counter);
4850
let entry_value =
49-
entry_value_uninit.write(FixedSizeByteString::<100>::from_bytes(value.as_bytes())?);
51+
entry_value_uninit.write(FixedSizeByteString::<30>::from_bytes(value.as_bytes())?);
5052
writer_handle_5 = entry_value.update();
5153
println!("Write new value for key 5: {}", value);
5254

5355
let value = counter as f32 * 7.7;
5456
writer_handle_9.update_with_copy(value);
55-
println!("Write new value for key 9: {value}");
57+
println!("Write new value for key 9: {value}\n");
5658
}
5759

5860
println!("exit");

examples/rust/blackboard/opener.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -29,15 +29,15 @@ fn main() -> Result<(), Box<dyn core::error::Error>> {
2929
let reader = service.reader_builder().create()?;
3030

3131
let reader_handle_0 = reader.entry::<u64>(&0)?;
32-
let reader_handle_5 = reader.entry::<FixedSizeByteString<100>>(&5)?;
32+
let reader_handle_5 = reader.entry::<FixedSizeByteString<30>>(&5)?;
3333
let reader_handle_9 = reader.entry::<f32>(&9)?;
3434

3535
while node.wait(CYCLE_TIME).is_ok() {
3636
println!("read values:");
3737

38-
println!("key: 0, {}", reader_handle_0.get());
39-
println!("key: 5, {}", reader_handle_5.get());
40-
println!("key: 9, {}\n", reader_handle_9.get());
38+
println!("key: 0, value: {}", reader_handle_0.get());
39+
println!("key: 5, value: {}", reader_handle_5.get());
40+
println!("key: 9, value: {}\n", reader_handle_9.get());
4141
}
4242

4343
println!("exit");
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
#!/usr/bin/expect
2+
# Copyright (c) 2025 Contributors to the Eclipse Foundation
3+
#
4+
# See the NOTICE file(s) distributed with this work for additional
5+
# information regarding copyright ownership.
6+
#
7+
# This program and the accompanying materials are made available under the
8+
# terms of the Apache Software License 2.0 which is available at
9+
# https://www.apache.org/licenses/LICENSE-2.0, or the MIT license
10+
# which is available at https://opensource.org/licenses/MIT.
11+
#
12+
# SPDX-License-Identifier: Apache-2.0 OR MIT
13+
14+
#### Common Setup
15+
16+
set REPO_ROOT [exec git rev-parse --show-toplevel]
17+
cd ${REPO_ROOT}
18+
19+
source examples/cross-language-end-to-end-tests/common.exp
20+
21+
#### Test Setup
22+
23+
set timeout 10
24+
25+
spawn cargo run --example blackboard_creator
26+
set id_creator $spawn_id
27+
# wait until ready
28+
expect_output "Blackboard created."
29+
30+
spawn cargo run --example blackboard_opener
31+
32+
#### Test Assertion
33+
34+
expect_output_from $id_creator "Write new value for key 0: 4"
35+
expect_output "key: 0, value: 4"
36+
37+
show_test_passed
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
# Copyright (c) 2025 Contributors to the Eclipse Foundation
2+
#
3+
# See the NOTICE file(s) distributed with this work for additional
4+
# information regarding copyright ownership.
5+
#
6+
# This program and the accompanying materials are made available under the
7+
# terms of the Apache Software License 2.0 which is available at
8+
# https://www.apache.org/licenses/LICENSE-2.0, or the MIT license
9+
# which is available at https://opensource.org/licenses/MIT.
10+
#
11+
# SPDX-License-Identifier: Apache-2.0 OR MIT
12+
13+
load("@rules_rust//rust:defs.bzl", "rust_binary")
14+
15+
rust_binary(
16+
name = "creator",
17+
srcs = [
18+
"creator.rs",
19+
],
20+
deps = [
21+
"//iceoryx2:iceoryx2",
22+
"//iceoryx2-bb/container:iceoryx2-bb-container",
23+
],
24+
)
25+
26+
rust_binary(
27+
name = "opener",
28+
srcs = [
29+
"opener.rs",
30+
],
31+
deps = [
32+
"//iceoryx2:iceoryx2",
33+
"//iceoryx2-bb/container:iceoryx2-bb-container",
34+
],
35+
)
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
# Blackboard with Notification on Value Update
2+
3+
> [!CAUTION]
4+
> Every payload you transmit with iceoryx2 must implement [`ZeroCopySend`] to
5+
> be compatible with shared memory.
6+
> Usually, you can use the derive-macro `#[derive(ZeroCopySend)]` for most
7+
> types. If you implement it manually you must ensure that the payload type:
8+
>
9+
> * is self contained, no heap, no pointers to external sources
10+
> * has a uniform memory representation -> `#[repr(C)]`
11+
> * does not use pointers to manage their internal structure
12+
> * and its members don't implement `Drop` explicitly
13+
> * has a `'static` lifetime
14+
>
15+
> Data types like `String` or `Vec` will cause undefined behavior and may
16+
> result in segmentation faults. We provide alternative data types that are
17+
> compatible with shared memory. See the
18+
> [complex data type example](../complex_data_types) for guidance on how to
19+
> use them.
20+
21+
This example demonstrates how to combine the blackboard with the event messaging
22+
pattern so that values can be read when updated instead of using a polling loop.
23+
Besides the [blackboard](../blackboard) service, an additional [event](../event)
24+
service is created. This is used to create a notifier that sends a notification
25+
whenever a value is updated, using the entry id, and a listener that waits for
26+
notifications with a certain entry id and reads the updated value.
27+
28+
## How to Run
29+
30+
To observe the blackboard messaging pattern with notifications on value update
31+
in action, open two separate terminals and execute the following commands:
32+
33+
### Terminal 1
34+
35+
```sh
36+
cargo run --example blackboard_event_based_creator
37+
```
38+
39+
### Terminal 2
40+
41+
```sh
42+
cargo run --example blackboard_event_based_opener
43+
```

examples/rust/blackboard_event_based_communication/creator.rs

Lines changed: 30 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -16,33 +16,48 @@ use iceoryx2::prelude::*;
1616
const CYCLE_TIME: Duration = Duration::from_secs(1);
1717

1818
fn main() -> Result<(), Box<dyn core::error::Error>> {
19-
set_log_level_from_env_or(LogLevel::Trace);
19+
set_log_level_from_env_or(LogLevel::Info);
2020
let node = NodeBuilder::new().create::<ipc::Service>()?;
2121
type KeyType = u32;
22-
let key = 99;
22+
const INTERESTING_KEY: u32 = 1;
2323

2424
let service = node
2525
.service_builder(&"My/Funk/ServiceName".try_into()?)
2626
.blackboard_creator::<KeyType>()
27-
.add::<u64>(key, 2023)
27+
.add_with_default::<u64>(0)
28+
.add_with_default::<u64>(INTERESTING_KEY)
2829
.create()?;
2930

31+
println!("Blackboard created.\n");
32+
3033
let event_service = node
3134
.service_builder(&"My/Funk/ServiceName".try_into()?)
3235
.event()
3336
.open_or_create()?;
34-
let listener = event_service.listener_builder().create()?;
35-
36-
let reader = service.reader_builder().create()?;
37-
let reader_handle = reader.entry::<u64>(&key)?;
38-
39-
// wait for entry id
40-
while node.wait(Duration::ZERO).is_ok() {
41-
if let Ok(Some(id)) = listener.timed_wait_one(CYCLE_TIME) {
42-
if id == reader_handle.entry_id() {
43-
println!("read u64: {}", reader_handle.get());
44-
}
45-
}
37+
let notifier = event_service.notifier_builder().create()?;
38+
39+
let writer = service.writer_builder().create()?;
40+
41+
let writer_handle = writer.entry::<u64>(&0)?;
42+
let entry_id = writer_handle.entry_id();
43+
44+
let interesting_writer_handle = writer.entry::<u64>(&INTERESTING_KEY)?;
45+
let interesting_entry_id = interesting_writer_handle.entry_id();
46+
47+
// notify with entry id
48+
let mut counter: u64 = 0;
49+
while node.wait(CYCLE_TIME).is_ok() {
50+
counter += 1;
51+
interesting_writer_handle.update_with_copy(counter);
52+
notifier.notify_with_custom_event_id(interesting_entry_id)?;
53+
println!(
54+
"Trigger event with entry id {}",
55+
interesting_entry_id.as_value()
56+
);
57+
58+
writer_handle.update_with_copy(2 * counter);
59+
notifier.notify_with_custom_event_id(entry_id)?;
60+
println!("Trigger event with entry id {}", entry_id.as_value());
4661
}
4762

4863
println!("exit");

examples/rust/blackboard_event_based_communication/opener.rs

Lines changed: 18 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,10 @@ use iceoryx2::prelude::*;
1616
const CYCLE_TIME: Duration = Duration::from_secs(1);
1717

1818
fn main() -> Result<(), Box<dyn core::error::Error>> {
19-
set_log_level_from_env_or(LogLevel::Trace);
19+
set_log_level_from_env_or(LogLevel::Info);
2020
let node = NodeBuilder::new().create::<ipc::Service>()?;
2121
type KeyType = u32;
22-
let key = 99;
22+
const INTERESTING_KEY: u32 = 1;
2323

2424
let service = node
2525
.service_builder(&"My/Funk/ServiceName".try_into()?)
@@ -30,19 +30,22 @@ fn main() -> Result<(), Box<dyn core::error::Error>> {
3030
.service_builder(&"My/Funk/ServiceName".try_into()?)
3131
.event()
3232
.open_or_create()?;
33-
let notifier = event_service.notifier_builder().create()?;
34-
35-
let writer = service.writer_builder().create()?;
36-
let writer_handle = writer.entry::<u64>(&key)?;
37-
38-
// notify with entry id
39-
let mut counter: u64 = 0;
40-
while node.wait(CYCLE_TIME).is_ok() {
41-
counter += 1;
42-
writer_handle.update_with_copy(counter);
43-
notifier.notify_with_custom_event_id(writer_handle.entry_id())?;
44-
45-
println!("Trigger event with entry id...");
33+
let listener = event_service.listener_builder().create()?;
34+
35+
let reader = service.reader_builder().create()?;
36+
let reader_handle = reader.entry::<u64>(&INTERESTING_KEY)?;
37+
38+
// wait for entry id
39+
while node.wait(Duration::ZERO).is_ok() {
40+
if let Ok(Some(id)) = listener.timed_wait_one(CYCLE_TIME) {
41+
if id == reader_handle.entry_id() {
42+
println!(
43+
"read: {} for entry id {}",
44+
reader_handle.get(),
45+
id.as_value()
46+
);
47+
}
48+
}
4649
}
4750

4851
println!("exit");

0 commit comments

Comments
 (0)