diff --git a/examples/README.md b/examples/README.md index 546ba26aa..fd40cde3d 100644 --- a/examples/README.md +++ b/examples/README.md @@ -48,7 +48,7 @@ they interact and exchange data. | complex data types | [Rust](rust/complex_data_types) | Send zero-copy compatible versions of `Vec` and `String`. Introduces `PlacementDefault` trait for large data types to perform an in place initialization where otherwise a stack overflow would be encountered.| | discovery | [C](c/discovery) [C++](cxx/discovery) [Rust](rust/discovery) | List all available services in a system. | | docker | [all](rust/docker) | Communicate between different docker containers and the host. | -| domains | [Rust](rust/domains) | Establish separate domains that operate independently from one another. | +| domains | [C++](cxx/domains) [Rust](rust/domains) | Establish separate domains that operate independently from one another. | | event | [C](c/event) [C++](cxx/event) [Rust](rust/event) | Push notifications - send event signals to wakeup processes that are waiting for them.| | publish subscribe | [C](c/publish_subscribe) [C++](cxx/publish_subscribe) [Rust](rust/publish_subscribe) | Communication between multiple processes with a [publish subscribe messaging pattern](https://en.wikipedia.org/wiki/Publish–subscribe_pattern). | | publish subscribe dynamic data | [Rust](rust/publish_subscribe_dynamic_data) | Communication between multiple processes with a [publish subscribe messaging pattern](https://en.wikipedia.org/wiki/Publish–subscribe_pattern) and payload data that has a dynamic size. | diff --git a/examples/cxx/CMakeLists.txt b/examples/cxx/CMakeLists.txt index 2379706b1..80468fd26 100644 --- a/examples/cxx/CMakeLists.txt +++ b/examples/cxx/CMakeLists.txt @@ -18,6 +18,7 @@ set(CMAKE_CXX_STANDARD_REQUIRED ON) add_subdirectory(complex_data_types) add_subdirectory(discovery) +add_subdirectory(domains) add_subdirectory(event) add_subdirectory(publish_subscribe) add_subdirectory(publish_subscribe_dynamic_data) diff --git a/examples/cxx/domains/BUILD.bazel b/examples/cxx/domains/BUILD.bazel new file mode 100644 index 000000000..76dc7ff4d --- /dev/null +++ b/examples/cxx/domains/BUILD.bazel @@ -0,0 +1,48 @@ +# Copyright (c) 2024 Contributors to the Eclipse Foundation +# +# See the NOTICE file(s) distributed with this work for additional +# information regarding copyright ownership. +# +# This program and the accompanying materials are made available under the +# terms of the Apache Software License 2.0 which is available at +# https://www.apache.org/licenses/LICENSE-2.0, or the MIT license +# which is available at https://opensource.org/licenses/MIT. +# +# SPDX-License-Identifier: Apache-2.0 OR MIT + +load("@rules_cc//cc:defs.bzl", "cc_binary", "cc_library") + +cc_binary( + name = "example_cxx_domains_publisher", + srcs = [ + "src/publisher.cpp", + "src/transmission_data.hpp", + ], + deps = [ + "@iceoryx//:iceoryx_hoofs", + "//:iceoryx2-cxx", + ], +) + +cc_binary( + name = "example_cxx_domains_subscriber", + srcs = [ + "src/subscriber.cpp", + "src/transmission_data.hpp", + ], + deps = [ + "@iceoryx//:iceoryx_hoofs", + "//:iceoryx2-cxx", + ], +) + +cc_binary( + name = "example_cxx_domains_discovery", + srcs = [ + "src/discovery.cpp", + ], + deps = [ + "@iceoryx//:iceoryx_hoofs", + "//:iceoryx2-cxx", + ], +) diff --git a/examples/cxx/domains/CMakeLists.txt b/examples/cxx/domains/CMakeLists.txt new file mode 100644 index 000000000..2b599ca8c --- /dev/null +++ b/examples/cxx/domains/CMakeLists.txt @@ -0,0 +1,25 @@ +# Copyright (c) 2024 Contributors to the Eclipse Foundation +# +# See the NOTICE file(s) distributed with this work for additional +# information regarding copyright ownership. +# +# This program and the accompanying materials are made available under the +# terms of the Apache Software License 2.0 which is available at +# https://www.apache.org/licenses/LICENSE-2.0, or the MIT license +# which is available at https://opensource.org/licenses/MIT. +# +# SPDX-License-Identifier: Apache-2.0 OR MIT + +cmake_minimum_required(VERSION 3.22) +project(example_cxx_domains LANGUAGES CXX) + +find_package(iceoryx2-cxx 0.3.0 REQUIRED) + +add_executable(example_cxx_domains_publisher src/publisher.cpp) +target_link_libraries(example_cxx_domains_publisher iceoryx2-cxx::static-lib-cxx) + +add_executable(example_cxx_domains_subscriber src/subscriber.cpp) +target_link_libraries(example_cxx_domains_subscriber iceoryx2-cxx::static-lib-cxx) + +add_executable(example_cxx_domains_discovery src/discovery.cpp) +target_link_libraries(example_cxx_domains_discovery iceoryx2-cxx::static-lib-cxx) diff --git a/examples/cxx/domains/README.md b/examples/cxx/domains/README.md new file mode 100644 index 000000000..2a62e1e2b --- /dev/null +++ b/examples/cxx/domains/README.md @@ -0,0 +1,67 @@ +# Domains + +Please install all dependencies first, as described in the [C++ Examples Readme](../README.md). + +Let's assume you want to create multiple iceoryx2 groups of processes where the +processes inside a group can communicate and interact with each other. However, +the groups themselves should remain isolated, meaning a process from one group +cannot interact with a process from another group. + +In other words, we aim to create different iceoryx2 domains on a local machine +that are strictly separated. + +This strict separation can be achieved by using the iceoryx2 configuration. +Within the configuration, a wide range of parameters can be adjusted, such as +the directory used for files containing static service information (a detailed +description of the service) or static node information (a detailed description +of a node). Additionally, the prefix of all files, which is by default `iox2_`, +can be modified. + +In this example, we use the prefix to separate the iceoryx2 groups. For all +examples, the user can set the iceoryx2 domain using `-d $DOMAIN_NAME$`. The +domain name must be a valid file name. The example will only operate within +this domain and cannot interact with any services in other domains with +different names. + +The `domains_discovery` binary illustrates this by listing all services +available in a given domain. Similarly, the `domains_publisher` will send data +only to subscribers within the same domain. Subscribers in other domains will +not receive any data. + +## Implementation + +To achieve this, we create a copy of the global configuration, modify the +setting `config.global.prefix` using the user-provided CLI argument, and then +set up the example accordingly. + +## Running The Example + +You can experiment with this setup by creating multiple publishers and +subscribers with different service names using `-s $SERVICE_NAME`. Only +publisher-subscriber pairs within the same domain will be able to communicate, +and the discovery tool will only detect services from within the same domain. + +First you have to build the C++ examples: + +```sh +cmake -S . -B target/ffi/build -DBUILD_EXAMPLES=ON +cmake --build target/ffi/build +``` + +**Terminal 1:** Subscriber in domain "fuu" subscribing to service "bar" + +```sh +./target/ffi/build/examples/cxx/domains/example_cxx_domains_subscriber -d "fuu" -s "bar" +``` + +**Terminal 2** Publisher in domain "fuu" publishing on service "bar" + +```sh +./target/ffi/build/examples/cxx/domains/example_cxx_domains_publisher -d "fuu" -s "bar" +``` + +**Terminal 3** List all services of domain "fuu" + +```sh +./target/ffi/build/examples/cxx/domains/example_cxx_domains_discovery -d "fuu" +``` diff --git a/examples/cxx/domains/src/discovery.cpp b/examples/cxx/domains/src/discovery.cpp new file mode 100644 index 000000000..08c20541a --- /dev/null +++ b/examples/cxx/domains/src/discovery.cpp @@ -0,0 +1,47 @@ +// Copyright (c) 2024 Contributors to the Eclipse Foundation +// +// See the NOTICE file(s) distributed with this work for additional +// information regarding copyright ownership. +// +// This program and the accompanying materials are made available under the +// terms of the Apache Software License 2.0 which is available at +// https://www.apache.org/licenses/LICENSE-2.0, or the MIT license +// which is available at https://opensource.org/licenses/MIT. +// +// SPDX-License-Identifier: Apache-2.0 OR MIT + +#include "iox/cli_definition.hpp" +#include "iox2/callback_progression.hpp" +#include "iox2/config.hpp" +#include "iox2/service.hpp" +#include "iox2/service_type.hpp" + +#include + +// NOLINTBEGIN +struct Args { + IOX_CLI_DEFINITION(Args); + IOX_CLI_OPTIONAL( + iox::string<32>, domain, { "iox2_" }, 'd', "domain", "The name of the domain. Must be a valid file name."); + IOX_CLI_SWITCH(debug, 'e', "debug", "Enable full debug log output"); +}; +// NOLINTEND + +auto main(int argc, char** argv) -> int { + using namespace iox2; + auto args = Args::parse(argc, argv, "Discovery of the domain example."); + + // create a new config based on the global config + auto config = Config::global_config().to_owned(); + + // The domain name becomes the prefix for all resources. + // Therefore, different domain names never share the same resources. + config.global().set_prefix(iox::FileName::create(args.domain()).expect("valid domain name")); + + Service::list(config.view(), [](auto service) { + std::cout << service.static_details << std::endl; + return CallbackProgression::Continue; + }).expect("discover all available services"); + + return 0; +} diff --git a/examples/cxx/domains/src/publisher.cpp b/examples/cxx/domains/src/publisher.cpp new file mode 100644 index 000000000..b20a66b87 --- /dev/null +++ b/examples/cxx/domains/src/publisher.cpp @@ -0,0 +1,80 @@ +// Copyright (c) 2024 Contributors to the Eclipse Foundation +// +// See the NOTICE file(s) distributed with this work for additional +// information regarding copyright ownership. +// +// This program and the accompanying materials are made available under the +// terms of the Apache Software License 2.0 which is available at +// https://www.apache.org/licenses/LICENSE-2.0, or the MIT license +// which is available at https://opensource.org/licenses/MIT. +// +// SPDX-License-Identifier: Apache-2.0 OR MIT + +#include "iox/cli_definition.hpp" +#include "iox/duration.hpp" +#include "iox/string.hpp" +#include "iox2/config.hpp" +#include "iox2/node.hpp" +#include "iox2/sample_mut.hpp" +#include "iox2/service_name.hpp" +#include "iox2/service_type.hpp" +#include "transmission_data.hpp" + +#include +#include + +// NOLINTBEGIN +struct Args { + IOX_CLI_DEFINITION(Args); + IOX_CLI_OPTIONAL( + iox::string<32>, domain, { "iox2_" }, 'd', "domain", "The name of the domain. Must be a valid file name."); + IOX_CLI_OPTIONAL(iox::string<256>, service, { "my_funky_service" }, 's', "service", "The name of the service."); + IOX_CLI_SWITCH(debug, 'e', "debug", "Enable full debug log output"); +}; +// NOLINTEND + +constexpr iox::units::Duration CYCLE_TIME = iox::units::Duration::fromSeconds(1); + +auto main(int argc, char** argv) -> int { + using namespace iox2; + auto args = Args::parse(argc, argv, "Publisher of the domain example."); + + // create a new config based on the global config + auto config = Config::global_config().to_owned(); + + // The domain name becomes the prefix for all resources. + // Therefore, different domain names never share the same resources. + config.global().set_prefix(iox::FileName::create(args.domain()).expect("valid domain name")); + + auto node = NodeBuilder() + // use the custom config when creating the custom node + // every service constructed by the node will use this config + .config(config) + .create() + .expect("successful node creation"); + + auto service = node.service_builder(ServiceName::create(args.service().c_str()).expect("valid service name")) + .publish_subscribe() + .open_or_create() + .expect("successful service creation/opening"); + + auto publisher = service.publisher_builder().create().expect("successful publisher creation"); + + auto counter = 0; + while (node.wait(CYCLE_TIME) == NodeEvent::Tick) { + counter += 1; + + auto sample = publisher.loan_uninit().expect("acquire sample"); + + sample.write_payload(TransmissionData { counter, counter * 3, counter * 812.12 }); // NOLINT + + send_sample(std::move(sample)).expect("send successful"); + + std::cout << "[domain: \"" << args.domain() << "\", service: \"" << args.service() << "] Send sample " + << counter << "..." << std::endl; + } + + std::cout << "exit" << std::endl; + + return 0; +} diff --git a/examples/cxx/domains/src/subscriber.cpp b/examples/cxx/domains/src/subscriber.cpp new file mode 100644 index 000000000..2a3dc1f91 --- /dev/null +++ b/examples/cxx/domains/src/subscriber.cpp @@ -0,0 +1,72 @@ +// Copyright (c) 2024 Contributors to the Eclipse Foundation +// +// See the NOTICE file(s) distributed with this work for additional +// information regarding copyright ownership. +// +// This program and the accompanying materials are made available under the +// terms of the Apache Software License 2.0 which is available at +// https://www.apache.org/licenses/LICENSE-2.0, or the MIT license +// which is available at https://opensource.org/licenses/MIT. +// +// SPDX-License-Identifier: Apache-2.0 OR MIT + +#include + +#include "iox/cli_definition.hpp" +#include "iox/duration.hpp" +#include "iox2/node.hpp" +#include "iox2/service_name.hpp" +#include "iox2/service_type.hpp" +#include "transmission_data.hpp" + +// NOLINTBEGIN +struct Args { + IOX_CLI_DEFINITION(Args); + IOX_CLI_OPTIONAL( + iox::string<32>, domain, { "iox2_" }, 'd', "domain", "The name of the domain. Must be a valid file name."); + IOX_CLI_OPTIONAL(iox::string<256>, service, { "my_funky_service" }, 's', "service", "The name of the service."); + IOX_CLI_SWITCH(debug, 'e', "debug", "Enable full debug log output"); +}; +// NOLINTEND + +constexpr iox::units::Duration CYCLE_TIME = iox::units::Duration::fromSeconds(1); + +auto main(int argc, char** argv) -> int { + using namespace iox2; + auto args = Args::parse(argc, argv, "Subscriber of the domain example."); + + // create a new config based on the global config + auto config = Config::global_config().to_owned(); + + // The domain name becomes the prefix for all resources. + // Therefore, different domain names never share the same resources. + config.global().set_prefix(iox::FileName::create(args.domain()).expect("valid domain name")); + + auto node = NodeBuilder() + // use the custom config when creating the custom node + // every service constructed by the node will use this config + .config(config) + .create() + .expect("successful node creation"); + + auto service = node.service_builder(ServiceName::create(args.service().c_str()).expect("valid service name")) + .publish_subscribe() + .open_or_create() + .expect("successful service creation/opening"); + + auto subscriber = service.subscriber_builder().create().expect("successful subscriber creation"); + + std::cout << "subscribed to: [domain: \"" << args.domain() << "\", service: \"" << args.service() << "\"]" + << std::endl; + while (node.wait(CYCLE_TIME) == NodeEvent::Tick) { + auto sample = subscriber.receive().expect("receive succeeds"); + while (sample.has_value()) { + std::cout << "received: " << sample->payload() << std::endl; + sample = subscriber.receive().expect("receive succeeds"); + } + } + + std::cout << "exit" << std::endl; + + return 0; +} diff --git a/examples/cxx/domains/src/transmission_data.hpp b/examples/cxx/domains/src/transmission_data.hpp new file mode 100644 index 000000000..e5b83272d --- /dev/null +++ b/examples/cxx/domains/src/transmission_data.hpp @@ -0,0 +1,30 @@ +// Copyright (c) 2024 Contributors to the Eclipse Foundation +// +// See the NOTICE file(s) distributed with this work for additional +// information regarding copyright ownership. +// +// This program and the accompanying materials are made available under the +// terms of the Apache Software License 2.0 which is available at +// https://www.apache.org/licenses/LICENSE-2.0, or the MIT license +// which is available at https://opensource.org/licenses/MIT. +// +// SPDX-License-Identifier: Apache-2.0 OR MIT + +#ifndef IOX2_EXAMPLES_TRANSMISSION_DATA_HPP +#define IOX2_EXAMPLES_TRANSMISSION_DATA_HPP + +#include +#include + +struct TransmissionData { + std::int32_t x; + std::int32_t y; + double funky; +}; + +inline auto operator<<(std::ostream& stream, const TransmissionData& value) -> std::ostream& { + stream << "TransmissionData { x: " << value.x << ", y: " << value.y << ", funky: " << value.funky << " }"; + return stream; +} + +#endif diff --git a/examples/rust/domains/publisher.rs b/examples/rust/domains/publisher.rs index b843775f1..af1f3c808 100644 --- a/examples/rust/domains/publisher.rs +++ b/examples/rust/domains/publisher.rs @@ -79,9 +79,9 @@ fn main() -> Result<(), Box> { #[derive(Parser, Debug)] struct Args { /// The name of the domain. Must be a valid file name. - #[clap(short, long, default_value = "iox2")] + #[clap(short, long, default_value = "iox2_")] domain: String, - /// The of the service. + /// The name of the service. #[clap(short, long, default_value = "my_funky_service")] service: String, /// Enable full debug log output diff --git a/iceoryx2-ffi/cxx/include/iox2/config.hpp b/iceoryx2-ffi/cxx/include/iox2/config.hpp index 8e7729abb..f9ce9c47b 100644 --- a/iceoryx2-ffi/cxx/include/iox2/config.hpp +++ b/iceoryx2-ffi/cxx/include/iox2/config.hpp @@ -22,6 +22,7 @@ namespace iox2 { class Config; +class NodeBuilder; namespace config { class Global; @@ -286,9 +287,13 @@ class Config { /// Returns a [`ConfigView`] to the current global config. static auto global_config() -> ConfigView; + /// Returns the [`ConfigView`] to this [`Config`] + auto view() -> ConfigView; + private: friend class ConfigView; friend class config::Global; + friend class NodeBuilder; explicit Config(iox2_config_h handle); void drop(); diff --git a/iceoryx2-ffi/cxx/src/config.cpp b/iceoryx2-ffi/cxx/src/config.cpp index bc78ff953..ea982aa8b 100644 --- a/iceoryx2-ffi/cxx/src/config.cpp +++ b/iceoryx2-ffi/cxx/src/config.cpp @@ -104,6 +104,10 @@ auto Config::defaults() -> config::Defaults { auto Config::global_config() -> ConfigView { return ConfigView { iox2_config_global_config() }; } + +auto Config::view() -> ConfigView { + return ConfigView { iox2_cast_config_ptr(m_handle) }; +} ///////////////////////// // END: Config ///////////////////////// diff --git a/iceoryx2-ffi/cxx/src/node.cpp b/iceoryx2-ffi/cxx/src/node.cpp index 1d619eb68..30f181170 100644 --- a/iceoryx2-ffi/cxx/src/node.cpp +++ b/iceoryx2-ffi/cxx/src/node.cpp @@ -137,7 +137,8 @@ auto NodeBuilder::create() const&& -> iox::expected, NodeCreationFailure } if (m_config.has_value()) { - IOX_TODO(); + auto* config_handle_ref = iox2_cast_config_ref_h(m_config.value().m_handle); + iox2_node_builder_set_config(handle_ref, config_handle_ref); } iox2_node_h node_handle {}; diff --git a/iceoryx2-ffi/ffi/src/api/config.rs b/iceoryx2-ffi/ffi/src/api/config.rs index 522303661..4874689de 100644 --- a/iceoryx2-ffi/ffi/src/api/config.rs +++ b/iceoryx2-ffi/ffi/src/api/config.rs @@ -67,7 +67,7 @@ pub type iox2_config_ptr = *const Config; pub type iox2_config_mut_ptr = *mut Config; pub(super) struct ConfigOwner { - value: ManuallyDrop, + pub(crate) value: ManuallyDrop, } /// A storage object that has the size to store a config @@ -81,7 +81,7 @@ pub struct iox2_config_storage_t { #[repr(C)] #[iceoryx2_ffi(ConfigOwner)] pub struct iox2_config_t { - value: iox2_config_storage_t, + pub(crate) value: iox2_config_storage_t, deleter: fn(*mut iox2_config_t), } @@ -137,6 +137,26 @@ pub unsafe extern "C" fn iox2_cast_config_ref_h(handle: iox2_config_h) -> iox2_c (*handle.as_type()).as_ref_handle() as *mut _ as _ } +/// This function casts a [`iox2_config_h`] into a [`iox2_config_ptr`] +/// +/// # Arguments +/// +/// * `handle` obtained by [`iox2_config_from_file()`], [`iox2_config_default()`], +/// [`iox2_config_clone()`] or [`iox2_config_from_ptr()`] +/// +/// Returns a [`iox2_config_ptr`] +/// +/// # Safety +/// +/// * The `config_handle` must be a valid handle. +/// * The `config_handle` is still valid after the call to this function. +#[no_mangle] +pub unsafe extern "C" fn iox2_cast_config_ptr(config_handle: iox2_config_h) -> iox2_config_ptr { + debug_assert!(!config_handle.is_null()); + + &*(*config_handle.as_type()).value.as_ref().value +} + /// Returns a pointer to the global config #[no_mangle] pub extern "C" fn iox2_config_global_config() -> iox2_config_ptr { diff --git a/iceoryx2-ffi/ffi/src/api/node_builder.rs b/iceoryx2-ffi/ffi/src/api/node_builder.rs index 71e297a9b..e7a5634c2 100644 --- a/iceoryx2-ffi/ffi/src/api/node_builder.rs +++ b/iceoryx2-ffi/ffi/src/api/node_builder.rs @@ -16,6 +16,7 @@ use crate::api::{ iox2_node_h, iox2_node_name_ptr, iox2_node_t, iox2_service_type_e, HandleToType, IntoCInt, NodeUnion, IOX2_OK, }; +use crate::iox2_config_ref_h; use iceoryx2::node::NodeCreationFailure; use iceoryx2::prelude::*; @@ -169,14 +170,29 @@ pub unsafe extern "C" fn iox2_node_builder_set_name( IOX2_OK } +/// Sets the node config for the builder +/// +/// Returns IOX2_OK +/// +/// # Safety +/// +/// * `node_builder_handle` - Must be a valid [`iox2_node_builder_ref_h`] obtained by [`iox2_node_builder_new`] and casted by [`iox2_cast_node_builder_ref_h`]. +/// * `config_handle` - Must be a valid [`iox2_config_ref_h`] +/// #[no_mangle] -pub extern "C" fn iox2_node_builder_set_config( +pub unsafe extern "C" fn iox2_node_builder_set_config( node_builder_handle: iox2_node_builder_ref_h, -) -> c_int { + config_handle: iox2_config_ref_h, +) { debug_assert!(!node_builder_handle.is_null()); - todo!() // TODO: [#210] implement + debug_assert!(!config_handle.is_null()); + + let node_builder_struct = &mut *node_builder_handle.as_type(); + let config = &*config_handle.as_type(); - // IOX2_OK + let node_builder = node_builder_struct.take().unwrap(); + let node_builder = node_builder.config(&config.value.as_ref().value); + node_builder_struct.set(node_builder); } // intentionally not public API