diff --git a/Cargo.lock b/Cargo.lock deleted file mode 100644 index 40e4fa6..0000000 --- a/Cargo.lock +++ /dev/null @@ -1,348 +0,0 @@ -# This file is automatically @generated by Cargo. -# It is not intended for manual editing. -version = 4 - -[[package]] -name = "addr2line" -version = "0.24.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1" -dependencies = [ - "gimli", -] - -[[package]] -name = "adler2" -version = "2.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" - -[[package]] -name = "autocfg" -version = "1.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" - -[[package]] -name = "backtrace" -version = "0.3.75" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6806a6321ec58106fea15becdad98371e28d92ccbc7c8f1b3b6dd724fe8f1002" -dependencies = [ - "addr2line", - "cfg-if", - "libc", - "miniz_oxide", - "object", - "rustc-demangle", - "windows-targets", -] - -[[package]] -name = "cfg-if" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9555578bc9e57714c812a1f84e4fc5b4d21fcb063490c624de019f7464c91268" - -[[package]] -name = "com-api" -version = "0.1.0" - -[[package]] -name = "com-api-example" -version = "0.1.0" -dependencies = [ - "com-api", - "com-api-sample-gen", - "com-api-sample-runtime", - "tokio", -] - -[[package]] -name = "com-api-sample-gen" -version = "0.1.0" -dependencies = [ - "com-api", - "com-api-sample-runtime", -] - -[[package]] -name = "com-api-sample-runtime" -version = "0.1.0" -dependencies = [ - "com-api", - "futures", -] - -[[package]] -name = "futures" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" -dependencies = [ - "futures-channel", - "futures-core", - "futures-executor", - "futures-io", - "futures-sink", - "futures-task", - "futures-util", -] - -[[package]] -name = "futures-channel" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" -dependencies = [ - "futures-core", - "futures-sink", -] - -[[package]] -name = "futures-core" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" - -[[package]] -name = "futures-executor" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f" -dependencies = [ - "futures-core", - "futures-task", - "futures-util", -] - -[[package]] -name = "futures-io" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" - -[[package]] -name = "futures-macro" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "futures-sink" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" - -[[package]] -name = "futures-task" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" - -[[package]] -name = "futures-util" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" -dependencies = [ - "futures-channel", - "futures-core", - "futures-io", - "futures-macro", - "futures-sink", - "futures-task", - "memchr", - "pin-project-lite", - "pin-utils", - "slab", -] - -[[package]] -name = "gimli" -version = "0.31.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" - -[[package]] -name = "libc" -version = "0.2.172" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d750af042f7ef4f724306de029d18836c26c1765a54a6a3f094cbd23a7267ffa" - -[[package]] -name = "memchr" -version = "2.7.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" - -[[package]] -name = "miniz_oxide" -version = "0.8.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" -dependencies = [ - "adler2", -] - -[[package]] -name = "object" -version = "0.36.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87" -dependencies = [ - "memchr", -] - -[[package]] -name = "pin-project-lite" -version = "0.2.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" - -[[package]] -name = "pin-utils" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" - -[[package]] -name = "proc-macro2" -version = "1.0.95" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778" -dependencies = [ - "unicode-ident", -] - -[[package]] -name = "quote" -version = "1.0.40" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" -dependencies = [ - "proc-macro2", -] - -[[package]] -name = "rustc-demangle" -version = "0.1.25" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "989e6739f80c4ad5b13e0fd7fe89531180375b18520cc8c82080e4dc4035b84f" - -[[package]] -name = "slab" -version = "0.4.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" -dependencies = [ - "autocfg", -] - -[[package]] -name = "syn" -version = "2.0.101" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ce2b7fc941b3a24138a0a7cf8e858bfc6a992e7978a068a5c760deb0ed43caf" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", -] - -[[package]] -name = "tokio" -version = "1.43.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "492a604e2fd7f814268a378409e6c92b5525d747d10db9a229723f55a417958c" -dependencies = [ - "backtrace", - "pin-project-lite", - "tokio-macros", -] - -[[package]] -name = "tokio-macros" -version = "2.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "unicode-ident" -version = "1.0.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" - -[[package]] -name = "windows-targets" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" -dependencies = [ - "windows_aarch64_gnullvm", - "windows_aarch64_msvc", - "windows_i686_gnu", - "windows_i686_gnullvm", - "windows_i686_msvc", - "windows_x86_64_gnu", - "windows_x86_64_gnullvm", - "windows_x86_64_msvc", -] - -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" - -[[package]] -name = "windows_i686_gnu" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" - -[[package]] -name = "windows_i686_gnullvm" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" - -[[package]] -name = "windows_i686_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" - -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" diff --git a/Cargo.toml b/Cargo.toml deleted file mode 100644 index 47b0c6f..0000000 --- a/Cargo.toml +++ /dev/null @@ -1,3 +0,0 @@ -[workspace] -members = ["com-api", "com-api-example", "com-api-sample-gen", "com-api-sample-runtime"] -resolver = "3" diff --git a/README.md b/README.md index 9454f5b..6d3b57c 100644 --- a/README.md +++ b/README.md @@ -5,12 +5,17 @@ Incubation repository for interprocess communication framework Examples can be built from examples directory by passing desired IPC adapter as feature. -For mock build: +For build: ``` -inc_mw_com/com-api$ cargo run --example basic-consumer-producer --features "mock" +inc_mw_com/com-api$ cargo build --example basic-consumer-producer ``` -For LoLa build: +For test: ``` -inc_mw_com/com-api$ cargo run --example basic-consumer-producer --features "lola" +inc_mw_com/com-api$ cargo test --test basic-consumer-producer-test +``` + +For Build and Run: +``` +inc_mw_com/com-api$ cargo run --example basic-consumer-producer ``` diff --git a/com-api/Cargo.lock b/com-api/Cargo.lock index 960d88e..a744b11 100644 --- a/com-api/Cargo.lock +++ b/com-api/Cargo.lock @@ -63,25 +63,8 @@ dependencies = [ [[package]] name = "com-api-gen" version = "0.1.0" -dependencies = [ - "com-api-gen-lola", - "com-api-gen-mock", -] - -[[package]] -name = "com-api-gen-lola" -version = "0.1.0" dependencies = [ "com-api", - "com-api-runtime-lola", -] - -[[package]] -name = "com-api-gen-mock" -version = "0.1.0" -dependencies = [ - "com-api", - "com-api-runtime-mock", ] [[package]] diff --git a/com-api/Cargo.toml b/com-api/Cargo.toml index c463bc4..b6c9a3f 100644 --- a/com-api/Cargo.toml +++ b/com-api/Cargo.toml @@ -7,7 +7,7 @@ members = [ "com-api-runtime-lola", "examples", - "examples/com-api-gen" + "examples/com-api-gen", ] [workspace.package] @@ -16,9 +16,13 @@ edition = "2024" publish = ["common"] license = "Apache-2.0" +[workspace.lints.clippy] +std_instead_of_core = "warn" +alloc_instead_of_core = "warn" + [workspace.dependencies] com-api = { path = "com-api"} com-api-concept = { path = "com-api-concept"} com-api-runtime-mock = { path = "com-api-runtime-mock"} com-api-runtime-lola = { path = "com-api-runtime-lola"} -com-api-gen = { path = "examples/com-api-gen"} +com-api-gen = { path = "examples/com-api-gen"} \ No newline at end of file diff --git a/com-api/com-api-concept/Cargo.toml b/com-api/com-api-concept/Cargo.toml index 656c1b9..864ef31 100644 --- a/com-api/com-api-concept/Cargo.toml +++ b/com-api/com-api-concept/Cargo.toml @@ -4,3 +4,4 @@ version = "0.1.0" edition = "2024" publish = ["common"] license = "Apache-2.0" + diff --git a/com-api/com-api-concept/src/lib.rs b/com-api/com-api-concept/src/lib.rs index 2f39c8d..b24b3b6 100644 --- a/com-api/com-api-concept/src/lib.rs +++ b/com-api/com-api-concept/src/lib.rs @@ -12,7 +12,7 @@ //! This crate defines the concepts and traits of the COM API. It does not provide any concrete //! implementations. It is meant to be used as a common interface for different implementations //! of the COM API, e.g., for different IPC backends. -//! +//! //! # API Design principles //! //! - We stick to the builder pattern down to a single service (TODO: Should this be introduced to the C++ API?) @@ -44,9 +44,12 @@ //! - Structures //! - Tuples +use core::fmt::Debug; +use core::future::Future; +use core::ops::{Deref, DerefMut}; +pub mod reloc; +pub use reloc::Reloc; use std::collections::VecDeque; -use std::fmt::Debug; -use std::ops::{Deref, DerefMut}; use std::path::Path; #[derive(Debug)] @@ -58,7 +61,7 @@ pub enum Error { SubscribeFailed, } -pub type Result = std::result::Result; +pub type Result = core::result::Result; /// Generic trait for all "factory-like" types pub trait Builder { @@ -68,8 +71,27 @@ pub trait Builder { /// This represents the com implementation and acts as a root for all types and objects provided by /// the implementation. +// +// Associated types: +// * ProviderInfo - Information about a producer instance required to pass to different traits/types/methods +// * ConsumerInfo - Information about a consumer instance required to pass to different traits/types/methods pub trait Runtime { - type Sample<'a, T: Reloc + Send + std::fmt::Debug + 'a>: Sample; + type ServiceDiscovery: ServiceDiscovery; + type Subscriber: Subscriber; + type ProducerBuilder>: ProducerBuilder; + type Publisher: Publisher; + type ProviderInfo: Send + Clone; + type ConsumerInfo: Send + Clone; + + fn find_service( + &self, + instance_specifier: FindServiceSpecifier, + ) -> Self::ServiceDiscovery; + + fn producer_builder>( + &self, + instance_specifier: InstanceSpecifier, + ) -> Self::ProducerBuilder; } pub trait RuntimeBuilder: Builder @@ -79,8 +101,84 @@ where fn load_config(&mut self, config: &Path) -> &mut Self; } +/// Technology independent description of a service instance "location" +/// +/// The string shall describe where to find a certain instance of a service. Each level shall look +/// like this +/// /my/path/to/service_name +/// validation for service name- /my/path/to/service_name +/// allowed characters: a-z A-Z 0-9 and '/' +/// Must start with leading/trailing check +/// Not allowed consecutive '/' characters +/// '_' is allowed in names +#[derive(Clone, Debug)] pub struct InstanceSpecifier { - pub specifier: String, + specifier: String, +} + +impl InstanceSpecifier { + fn check_str(service_name: &str) -> bool { + // Must start with exactly one leading slash + if !service_name.starts_with('/') || service_name.starts_with("//") { + return false; + } + + // Remove the single leading slash + let service_name = service_name.strip_prefix('/').unwrap(); + + // Check each character + let is_legal_char = |c| { + (c >= '0' && c <= '9') || (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || c == '_' + }; + + //validation of each path segment + !service_name.is_empty() + && service_name.split('/').all(|parts| { + // No empty segments (reject trailing "/" and "//" in the middle) + !parts.is_empty() && parts.chars().all(|c| is_legal_char(c)) + }) + } + + /// Create a new instance specifier, using the string-like input as the path to the + /// instance. + /// + /// The returned instance specifier will only match if the instance exactly matches the given + /// string. + pub fn new(service_name: impl AsRef) -> Result { + let service_name = service_name.as_ref(); + if Self::check_str(service_name) { + Ok(Self { + specifier: service_name.to_string(), + }) + } else { + Err(Error::Fail) + } + } +} + +impl TryFrom<&str> for InstanceSpecifier { + type Error = Error; + fn try_from(s: &str) -> Result { + Self::new(s) + } +} + +impl AsRef for InstanceSpecifier { + fn as_ref(&self) -> &str { + &self.specifier + } +} +/// Specifies whether to find a specific service instance or any available instance +pub enum FindServiceSpecifier { + Specific(InstanceSpecifier), + Any, +} + +/// Convert an InstanceSpecifier into a FindServiceSpecifier +impl Into for InstanceSpecifier { + fn into(self) -> FindServiceSpecifier { + FindServiceSpecifier::Specific(self) + } } /// This trait shall ensure that we can safely use an instance of the implementing type across @@ -97,10 +195,6 @@ pub struct InstanceSpecifier { /// /// Since it is yet to be proven whether this trait can be implemented safely (assumption is: no) it /// is unsafe for now. The expectation is that very few users ever need to implement this manually. -pub unsafe trait Reloc {} - -unsafe impl Reloc for () {} -unsafe impl Reloc for u32 {} /// A `Sample` provides a reference to a memory buffer of an event with immutable value. /// @@ -109,19 +203,18 @@ unsafe impl Reloc for u32 {} /// /// The ordering of SamplePtrs is total over the reception order // TODO: C++ doesn't yet support this. Expose API to compare SamplePtr ages. -pub trait Sample: Deref + Send + PartialOrd + Ord +pub trait Sample: Deref + Send + PartialOrd + Ord + Debug where - T: Send + Reloc, + T: Send + Reloc + Debug, { } - /// A `SampleMut` provides a reference to a memory buffer of an event with mutable value. /// /// By implementing the `DerefMut` trait implementations of the trait support the `.` operator for dereferencing. /// The buffers with its data lives as long as there are references to it existing in the framework. -pub trait SampleMut: DerefMut +pub trait SampleMut: DerefMut + Debug where - T: Send + Reloc, + T: Send + Reloc + Debug, { /// The associated read-only sample type. type Sample: Sample; @@ -140,9 +233,9 @@ where /// /// TODO: Shall we also require DerefMut> from implementing types? How to deal /// TODO: with the ambiguous assume_init() then? -pub trait SampleMaybeUninit +pub trait SampleMaybeUninit: Debug + AsMut> where - T: Send + Reloc, + T: Send + Reloc + Debug, { /// Buffer type for mutable data after initialization type SampleMut: SampleMut; @@ -162,47 +255,88 @@ where fn write(self, value: T) -> Self::SampleMut; } -pub trait Interface {} +pub trait Interface { + const TYPE_ID: &'static str; // the id will be used at backend + type Consumer: Consumer; + type Producer: Producer; +} -pub trait OfferedProducer { +#[must_use = "if a service is offered it will be unoffered and dropped immediately, causing unexpected behavior in the system"] +pub trait OfferedProducer { type Interface: Interface; - type Producer: Producer; + type Producer: Producer; fn unoffer(self) -> Self::Producer; } -pub trait Producer { +pub trait Producer { type Interface: Interface; - type OfferedProducer: OfferedProducer; - + type OfferedProducer: OfferedProducer; + fn new(instance_info: R::ProviderInfo) -> Result + where + Self: Sized; fn offer(self) -> Result; } -pub trait Consumer {} +pub trait Publisher +where + T: Reloc + Send + Debug, +{ + type SampleMaybeUninit<'a>: SampleMaybeUninit + 'a + where + Self: 'a; + + fn allocate<'a>(&'a self) -> Result>; + + fn new(identifier: &str, instance_info: R::ProviderInfo) -> Result + where + Self: Sized; + + fn send(&self, value: T) -> Result<()> { + let sample = self.allocate()?; + let init_sample = sample.write(value); + init_sample.send() + } +} + +pub trait Consumer { + fn new(instance_info: R::ConsumerInfo) -> Result + where + Self: Sized; +} -pub trait ProducerBuilder>: +pub trait ProducerBuilder, R: Runtime + ?Sized>: Builder

{ } -pub trait ServiceDiscovery { +pub trait ServiceDiscovery { type ConsumerBuilder: ConsumerBuilder; type ServiceEnumerator: IntoIterator; fn get_available_instances(&self) -> Result; - // TODO: Provide an async stream for newly available services / ServiceDescriptors -} -pub trait ConsumerDescriptor { - fn get_instance_id(&self) -> usize; // TODO: Turn return type into separate type + #[allow(clippy::manual_async_fn)] + fn get_available_instances_async( + &self, + ) -> impl Future> + Send; } -pub trait ConsumerBuilder: ConsumerDescriptor {} +pub trait ConsumerDescriptor { + fn get_instance_identifier(&self) -> &InstanceSpecifier; +} -pub trait Subscriber { - type Subscription: Subscription; +pub trait ConsumerBuilder: + ConsumerDescriptor + Builder> +{ +} - fn subscribe(self, max_num_samples: usize) -> Result; +pub trait Subscriber { + type Subscription: Subscription; + fn new(identifier: &str, instance_info: R::ConsumerInfo) -> Result + where + Self: Sized; + fn subscribe(&self, max_num_samples: usize) -> Result; } pub struct SampleContainer { @@ -225,7 +359,7 @@ impl SampleContainer { pub fn iter<'a, T>(&'a self) -> impl Iterator where S: Sample, - T: Reloc + Send + 'a, + T: Reloc + Send + 'a + Debug, { self.inner.iter().map(::deref) } @@ -243,7 +377,7 @@ impl SampleContainer { self.inner.len() } - pub fn front(&self) -> Option<&T> + pub fn front(&self) -> Option<&T> where S: Sample, { @@ -251,8 +385,8 @@ impl SampleContainer { } } -pub trait Subscription { - type Subscriber: Subscriber; +pub trait Subscription { + type Subscriber: Subscriber; type Sample<'a>: Sample where Self: 'a; @@ -306,3 +440,48 @@ pub trait Subscription { max_samples: usize, ) -> impl Future> + Send; } + +mod tests { + #[test] + fn test_instance_specifier_validation() { + use super::InstanceSpecifier; + // Valid specifiers + let valid_specifiers = [ + "/my/service", + "/my/path/to/service_name", + "/Service_123/AnotherPart", + "/A", + "/A/abc_123/Xyz", + ]; + + for spec in &valid_specifiers { + assert!( + InstanceSpecifier::check_str(spec), + "Expected '{}' to be valid", + spec + ); + } + + // Invalid specifiers + let invalid_specifiers = [ + "my/service", // No leading slash + "/my//service", // Consecutive slashes + "/my/service/", // Trailing slash + "/my/ser!vice", // Illegal character '!' + "/my/ser vice", // Illegal character ' ' + "/", // Only root slash + "/my/path//to/service", // Consecutive slashes in the middle + "/my/path/to//", // Trailing consecutive slashes + "//my/service", // Leading consecutive slashes + "///my/service", // Leading consecutive slashes + ]; + + for spec in &invalid_specifiers { + assert!( + !InstanceSpecifier::check_str(spec), + "Expected '{}' to be invalid", + spec + ); + } + } +} diff --git a/com-api/com-api-concept/src/reloc.rs b/com-api/com-api-concept/src/reloc.rs new file mode 100644 index 0000000..033a5a0 --- /dev/null +++ b/com-api/com-api-concept/src/reloc.rs @@ -0,0 +1,45 @@ +// Copyright (c) 2025 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 License Version 2.0 which is available at +// +// +// SPDX-License-Identifier: Apache-2.0 + +//! Reloc trait and implementations + +pub unsafe trait Reloc {} + +unsafe impl Reloc for () {} +unsafe impl Reloc for u8 {} +unsafe impl Reloc for u16 {} +unsafe impl Reloc for u32 {} +unsafe impl Reloc for u64 {} +unsafe impl Reloc for i8 {} +unsafe impl Reloc for i16 {} +unsafe impl Reloc for i32 {} +unsafe impl Reloc for i64 {} +unsafe impl Reloc for f32 {} +unsafe impl Reloc for f64 {} +unsafe impl Reloc for bool {} +unsafe impl Reloc for char {} +unsafe impl Reloc for usize {} +unsafe impl Reloc for u128 {} +unsafe impl Reloc for i128 {} +unsafe impl Reloc for isize {} + +// Arrays +unsafe impl Reloc for [T; N] {} + +// MaybeUninit +unsafe impl Reloc for core::mem::MaybeUninit {} + +// Tuples (up to 5 elements) +unsafe impl Reloc for (T1,) {} +unsafe impl Reloc for (T1, T2) {} +unsafe impl Reloc for (T1, T2, T3) {} +unsafe impl Reloc for (T1, T2, T3, T4) {} +unsafe impl Reloc for (T1, T2, T3, T4, T5) {} diff --git a/com-api/com-api-runtime-lola/src/lib.rs b/com-api/com-api-runtime-lola/src/lib.rs index 4a21ba5..54dbc6e 100644 --- a/com-api/com-api-runtime-lola/src/lib.rs +++ b/com-api/com-api-runtime-lola/src/lib.rs @@ -14,49 +14,67 @@ #![allow(dead_code)] -use std::cmp::Ordering; +use core::cmp::Ordering; +use core::fmt::Debug; +use core::future::Future; +use core::marker::PhantomData; +use core::mem::MaybeUninit; +use core::ops::{Deref, DerefMut}; +use core::sync::atomic::AtomicUsize; use std::collections::VecDeque; -use std::marker::PhantomData; -use std::mem::MaybeUninit; -use std::ops::{Deref, DerefMut}; use std::path::Path; -use std::sync::atomic::AtomicUsize; use com_api_concept::{ - Builder, ConsumerBuilder, ConsumerDescriptor, InstanceSpecifier, Interface, Reloc, Runtime, + Builder, Consumer, ConsumerBuilder, ConsumerDescriptor, FindServiceSpecifier, + InstanceSpecifier, Interface, Producer, ProducerBuilder, Reloc, Result, Runtime, SampleContainer, ServiceDiscovery, Subscriber, Subscription, }; pub struct LolaRuntimeImpl {} -impl Runtime for LolaRuntimeImpl { - type Sample<'a, T: Reloc + Send + 'a + std::fmt::Debug> = Sample<'a, T>; +// Note: LolaProviderInfo is currently unused but will be utilized +// with the Producer::offer() method in future implementations. +#[derive(Clone)] +pub struct LolaProviderInfo { + instance_specifier: InstanceSpecifier, } -impl LolaRuntimeImpl { - // TODO: Any chance that these can be moved to a trait so that this becomes more testable? - // If yes, this trait is certainly located here since - pub fn find_service( +#[derive(Clone)] +pub struct LolaConsumerInfo { + instance_specifier: InstanceSpecifier, +} + +impl Runtime for LolaRuntimeImpl { + type ServiceDiscovery = SampleConsumerDiscovery; + type Subscriber = SubscribableImpl; + type ProducerBuilder> = SampleProducerBuilder; + type Publisher = Publisher; + type ProviderInfo = LolaProviderInfo; + type ConsumerInfo = LolaConsumerInfo; + + fn find_service( &self, - _instance_specifier: InstanceSpecifier, - ) -> SampleConsumerDiscovery { + _instance_specifier: FindServiceSpecifier, + ) -> Self::ServiceDiscovery { SampleConsumerDiscovery { _interface: PhantomData, } } - pub fn producer_builder( + fn producer_builder>( &self, instance_specifier: InstanceSpecifier, - ) -> SampleProducerBuilder { + ) -> Self::ProducerBuilder { SampleProducerBuilder::new(self, instance_specifier) } } +#[derive(Debug)] struct LolaEvent { event: PhantomData, } +#[derive(Debug)] struct LolaBinding<'a, T> where T: Send, @@ -67,6 +85,7 @@ where unsafe impl<'a, T> Send for LolaBinding<'a, T> where T: Send {} +#[derive(Debug)] enum SampleBinding<'a, T> where T: Send, @@ -75,6 +94,7 @@ where Test(Box), } +#[derive(Debug)] pub struct Sample<'a, T> where T: Reloc + Send, @@ -87,11 +107,11 @@ static ID_COUNTER: AtomicUsize = AtomicUsize::new(0); impl<'a, T> From for Sample<'a, T> where - T: Reloc + Send, + T: Reloc + Send + Debug, { fn from(value: T) -> Self { Self { - id: ID_COUNTER.fetch_add(1, std::sync::atomic::Ordering::Relaxed), + id: ID_COUNTER.fetch_add(1, core::sync::atomic::Ordering::Relaxed), inner: SampleBinding::Test(Box::new(value)), } } @@ -111,7 +131,7 @@ where } } -impl<'a, T> com_api_concept::Sample for Sample<'a, T> where T: Send + Reloc {} +impl<'a, T> com_api_concept::Sample for Sample<'a, T> where T: Send + Reloc + Debug {} impl<'a, T> PartialEq for Sample<'a, T> where @@ -142,17 +162,18 @@ where } } +#[derive(Debug)] pub struct SampleMut<'a, T> where T: Reloc, { data: T, - _lifetime: PhantomData<&'a T>, + lifetime: PhantomData<&'a T>, } impl<'a, T> com_api_concept::SampleMut for SampleMut<'a, T> where - T: Reloc + Send, + T: Reloc + Send + Debug, { type Sample = Sample<'a, T>; @@ -185,49 +206,71 @@ where } } +#[derive(Debug)] pub struct SampleMaybeUninit<'a, T> where T: Reloc + Send, { data: MaybeUninit, - _lifetime: PhantomData<&'a T>, + lifetime: PhantomData<&'a T>, } impl<'a, T> com_api_concept::SampleMaybeUninit for SampleMaybeUninit<'a, T> where - T: Reloc + Send, + T: Reloc + Send + Debug, { type SampleMut = SampleMut<'a, T>; fn write(self, val: T) -> SampleMut<'a, T> { SampleMut { data: val, - _lifetime: PhantomData, + lifetime: PhantomData, } } - unsafe fn assume_init(self) -> SampleMut<'a, T> { + unsafe fn assume_init(self) -> SampleMut<'a, T> { SampleMut { data: unsafe { self.data.assume_init() }, - _lifetime: PhantomData, + lifetime: PhantomData, } } } +impl<'a, T> AsMut> for SampleMaybeUninit<'a, T> +where + T: Reloc + Send + Debug, +{ + fn as_mut(&mut self) -> &mut core::mem::MaybeUninit { + &mut self.data + } +} + pub struct SubscribableImpl { - _data: PhantomData, + identifier: String, + instance_info: Option, + data: PhantomData, } impl Default for SubscribableImpl { fn default() -> Self { - Self { _data: PhantomData } + Self { + identifier: String::new(), + instance_info: None, + data: PhantomData, + } } } -impl Subscriber for SubscribableImpl { +impl Subscriber for SubscribableImpl { type Subscription = SubscriberImpl; - - fn subscribe(self, _max_num_samples: usize) -> com_api_concept::Result { + fn new(identifier: &str, instance_info: LolaConsumerInfo) -> com_api_concept::Result { + Ok(Self { + identifier: identifier.to_string(), + instance_info: Some(instance_info), + data: PhantomData, + }) + } + fn subscribe(&self, _max_num_samples: usize) -> com_api_concept::Result { Ok(SubscriberImpl::new()) } } @@ -255,9 +298,9 @@ where } } -impl Subscription for SubscriberImpl +impl Subscription for SubscriberImpl where - T: Reloc + Send, + T: Reloc + Send + Debug, { type Subscriber = SubscribableImpl; type Sample<'a> @@ -292,27 +335,23 @@ pub struct Publisher { _data: PhantomData, } -impl Default for Publisher +impl com_api_concept::Publisher for Publisher where - T: Reloc + Send, + T: Reloc + Send + Debug, { - fn default() -> Self { - Self::new() - } -} + type SampleMaybeUninit<'a> + = SampleMaybeUninit<'a, T> + where + Self: 'a; -impl Publisher -where - T: Reloc + Send, -{ - pub fn new() -> Self { - Self { _data: PhantomData } + fn new(_identifier: &str, _instance_info: LolaProviderInfo) -> com_api_concept::Result { + Ok(Self { _data: PhantomData }) } - pub fn allocate<'a>(&'a self) -> com_api_concept::Result> { + fn allocate<'a>(&'a self) -> com_api_concept::Result> { Ok(SampleMaybeUninit { data: MaybeUninit::uninit(), - _lifetime: PhantomData, + lifetime: PhantomData, }) } } @@ -339,6 +378,24 @@ where fn get_available_instances(&self) -> com_api_concept::Result { Ok(Vec::new()) } + + #[allow(clippy::manual_async_fn)] + fn get_available_instances_async( + &self, + ) -> impl Future> + Send { + async { Ok(Vec::new()) } + } +} + +impl ConsumerBuilder for SampleConsumerBuilder {} + +impl Builder> for SampleConsumerBuilder { + fn build(self) -> com_api_concept::Result> { + let instance_info = LolaConsumerInfo { + instance_specifier: self.instance_specifier.clone(), + }; + Ok(Consumer::new(instance_info)?) + } } pub struct SampleProducerBuilder { @@ -355,6 +412,19 @@ impl SampleProducerBuilder { } } +impl> + ProducerBuilder for SampleProducerBuilder +{ +} + +impl> Builder

+ for SampleProducerBuilder +{ + fn build(self) -> Result

{ + todo!() + } +} + pub struct SampleConsumerDescriptor { _interface: PhantomData, } @@ -373,7 +443,7 @@ pub struct SampleConsumerBuilder { } impl ConsumerDescriptor for SampleConsumerBuilder { - fn get_instance_id(&self) -> usize { + fn get_instance_identifier(&self) -> &InstanceSpecifier { todo!() } } @@ -408,7 +478,7 @@ impl RuntimeBuilderImpl { #[cfg(test)] mod test { - use com_api_concept::{SampleContainer, Subscription}; + use com_api_concept::{Publisher, SampleContainer, SampleMaybeUninit, SampleMut, Subscription}; #[test] fn receive_stuff() { @@ -449,4 +519,17 @@ mod test { } }) } + + #[test] + fn send_stuff() { + let provider_info = super::LolaProviderInfo { + instance_specifier: com_api_concept::InstanceSpecifier::new("/test/publisher") + .expect("Invalid instance specifier"), + }; + let test_publisher = super::Publisher::::new("test_publisher", provider_info) + .expect("Publisher creation failed"); + let sample = test_publisher.allocate().expect("Couldn't allocate sample"); + let sample = sample.write(42); + sample.send().expect("Send failed for sample"); + } } diff --git a/com-api/com-api-runtime-mock/src/lib.rs b/com-api/com-api-runtime-mock/src/lib.rs index ce2d49d..4287950 100644 --- a/com-api/com-api-runtime-mock/src/lib.rs +++ b/com-api/com-api-runtime-mock/src/lib.rs @@ -16,49 +16,67 @@ #![allow(dead_code)] -use std::cmp::Ordering; +use core::cmp::Ordering; +use core::fmt::Debug; +use core::future::Future; +use core::marker::PhantomData; +use core::mem::MaybeUninit; +use core::ops::{Deref, DerefMut}; +use core::sync::atomic::AtomicUsize; use std::collections::VecDeque; -use std::marker::PhantomData; -use std::mem::MaybeUninit; -use std::ops::{Deref, DerefMut}; use std::path::Path; -use std::sync::atomic::AtomicUsize; use com_api_concept::{ - Builder, ConsumerBuilder, ConsumerDescriptor, InstanceSpecifier, Interface, Reloc, Runtime, + Builder, Consumer, ConsumerBuilder, ConsumerDescriptor, FindServiceSpecifier, + InstanceSpecifier, Interface, Producer, ProducerBuilder, Reloc, Result, Runtime, SampleContainer, ServiceDiscovery, Subscriber, Subscription, }; pub struct MockRuntimeImpl {} -impl Runtime for MockRuntimeImpl { - type Sample<'a, T: Reloc + Send + 'a + std::fmt::Debug> = Sample<'a, T>; +// Note: ProviderInfo is currently unused but will be utilized +// with the Producer::offer() method in future implementations. +#[derive(Clone)] +pub struct MockProviderInfo { + instance_specifier: InstanceSpecifier, } -impl MockRuntimeImpl { - // TODO: Any chance that these can be moved to a trait so that this becomes more testable? - // If yes, this trait is certainly located here since - pub fn find_service( +#[derive(Clone)] +pub struct MockConsumerInfo { + instance_specifier: InstanceSpecifier, +} + +impl Runtime for MockRuntimeImpl { + type ServiceDiscovery = SampleConsumerDiscovery; + type Subscriber = SubscribableImpl; + type ProducerBuilder> = SampleProducerBuilder; + type Publisher = Publisher; + type ProviderInfo = MockProviderInfo; + type ConsumerInfo = MockConsumerInfo; + + fn find_service( &self, - _instance_specifier: InstanceSpecifier, - ) -> SampleConsumerDiscovery { + _instance_specifier: FindServiceSpecifier, + ) -> Self::ServiceDiscovery { SampleConsumerDiscovery { _interface: PhantomData, } } - pub fn producer_builder( + fn producer_builder>( &self, instance_specifier: InstanceSpecifier, - ) -> SampleProducerBuilder { + ) -> Self::ProducerBuilder { SampleProducerBuilder::new(self, instance_specifier) } } +#[derive(Debug)] struct MockEvent { event: PhantomData, } +#[derive(Debug)] struct MockBinding<'a, T> where T: Send, @@ -69,6 +87,7 @@ where unsafe impl<'a, T> Send for MockBinding<'a, T> where T: Send {} +#[derive(Debug)] enum SampleBinding<'a, T> where T: Send, @@ -77,6 +96,7 @@ where Test(Box), } +#[derive(Debug)] pub struct Sample<'a, T> where T: Reloc + Send, @@ -89,11 +109,11 @@ static ID_COUNTER: AtomicUsize = AtomicUsize::new(0); impl<'a, T> From for Sample<'a, T> where - T: Reloc + Send, + T: Reloc + Send + Debug, { fn from(value: T) -> Self { Self { - id: ID_COUNTER.fetch_add(1, std::sync::atomic::Ordering::Relaxed), + id: ID_COUNTER.fetch_add(1, core::sync::atomic::Ordering::Relaxed), inner: SampleBinding::Test(Box::new(value)), } } @@ -113,7 +133,7 @@ where } } -impl<'a, T> com_api_concept::Sample for Sample<'a, T> where T: Send + Reloc {} +impl<'a, T> com_api_concept::Sample for Sample<'a, T> where T: Send + Reloc + Debug {} impl<'a, T> PartialEq for Sample<'a, T> where @@ -144,17 +164,18 @@ where } } +#[derive(Debug)] pub struct SampleMut<'a, T> where T: Reloc, { data: T, - _lifetime: PhantomData<&'a T>, + lifetime: PhantomData<&'a T>, } impl<'a, T> com_api_concept::SampleMut for SampleMut<'a, T> where - T: Reloc + Send, + T: Reloc + Send + Debug, { type Sample = Sample<'a, T>; @@ -187,49 +208,71 @@ where } } +#[derive(Debug)] pub struct SampleMaybeUninit<'a, T> where T: Reloc + Send, { data: MaybeUninit, - _lifetime: PhantomData<&'a T>, + lifetime: PhantomData<&'a T>, } impl<'a, T> com_api_concept::SampleMaybeUninit for SampleMaybeUninit<'a, T> where - T: Reloc + Send, + T: Reloc + Send + Debug, { type SampleMut = SampleMut<'a, T>; fn write(self, val: T) -> SampleMut<'a, T> { SampleMut { data: val, - _lifetime: PhantomData, + lifetime: PhantomData, } } - unsafe fn assume_init(self) -> SampleMut<'a, T> { + unsafe fn assume_init(self) -> SampleMut<'a, T> { SampleMut { data: unsafe { self.data.assume_init() }, - _lifetime: PhantomData, + lifetime: PhantomData, } } } +impl<'a, T> AsMut> for SampleMaybeUninit<'a, T> +where + T: Reloc + Send + Debug, +{ + fn as_mut(&mut self) -> &mut core::mem::MaybeUninit { + &mut self.data + } +} + pub struct SubscribableImpl { - _data: PhantomData, + identifier: String, + instance_info: Option, + data: PhantomData, } impl Default for SubscribableImpl { fn default() -> Self { - Self { _data: PhantomData } + Self { + identifier: String::new(), + instance_info: None, + data: PhantomData, + } } } -impl Subscriber for SubscribableImpl { +impl Subscriber for SubscribableImpl { type Subscription = SubscriberImpl; - - fn subscribe(self, _max_num_samples: usize) -> com_api_concept::Result { + fn new(identifier: &str, instance_info: MockConsumerInfo) -> com_api_concept::Result { + Ok(Self { + identifier: identifier.to_string(), + instance_info: Some(instance_info), + data: PhantomData, + }) + } + fn subscribe(&self, _max_num_samples: usize) -> com_api_concept::Result { Ok(SubscriberImpl::new()) } } @@ -257,9 +300,9 @@ where } } -impl Subscription for SubscriberImpl +impl Subscription for SubscriberImpl where - T: Reloc + Send, + T: Reloc + Send + Debug, { type Subscriber = SubscribableImpl; type Sample<'a> @@ -294,27 +337,23 @@ pub struct Publisher { _data: PhantomData, } -impl Default for Publisher +impl com_api_concept::Publisher for Publisher where - T: Reloc + Send, + T: Reloc + Send + Debug, { - fn default() -> Self { - Self::new() - } -} + type SampleMaybeUninit<'a> + = SampleMaybeUninit<'a, T> + where + Self: 'a; -impl Publisher -where - T: Reloc + Send, -{ - pub fn new() -> Self { - Self { _data: PhantomData } + fn new(_identifier: &str, _instance_info: MockProviderInfo) -> com_api_concept::Result { + Ok(Self { _data: PhantomData }) } - pub fn allocate<'a>(&'a self) -> com_api_concept::Result> { + fn allocate<'a>(&'a self) -> com_api_concept::Result> { Ok(SampleMaybeUninit { data: MaybeUninit::uninit(), - _lifetime: PhantomData, + lifetime: PhantomData, }) } } @@ -341,6 +380,13 @@ where fn get_available_instances(&self) -> com_api_concept::Result { Ok(Vec::new()) } + + #[allow(clippy::manual_async_fn)] + fn get_available_instances_async( + &self, + ) -> impl Future> { + async { Ok(Vec::new()) } + } } pub struct SampleProducerBuilder { @@ -357,6 +403,19 @@ impl SampleProducerBuilder { } } +impl> + ProducerBuilder for SampleProducerBuilder +{ +} + +impl> Builder

+ for SampleProducerBuilder +{ + fn build(self) -> Result

{ + todo!() + } +} + pub struct SampleConsumerDescriptor { _interface: PhantomData, } @@ -375,11 +434,23 @@ pub struct SampleConsumerBuilder { } impl ConsumerDescriptor for SampleConsumerBuilder { - fn get_instance_id(&self) -> usize { + fn get_instance_identifier(&self) -> &InstanceSpecifier { todo!() } } +impl ConsumerBuilder for SampleConsumerBuilder {} + +impl Builder> for SampleConsumerBuilder { + fn build(self) -> com_api_concept::Result> { + let instance_info = MockConsumerInfo { + instance_specifier: self.instance_specifier.clone(), + }; + + Ok(Consumer::new(instance_info)?) + } +} + pub struct RuntimeBuilderImpl {} impl Builder for RuntimeBuilderImpl { @@ -402,7 +473,7 @@ impl Default for RuntimeBuilderImpl { } impl RuntimeBuilderImpl { - /// Creates a new instance of the default implementation of the com layer + /// Creates a new instance of the default implementation for the com module of s-core pub fn new() -> Self { Self {} } @@ -410,7 +481,7 @@ impl RuntimeBuilderImpl { #[cfg(test)] mod test { - use com_api_concept::{SampleContainer, Subscription}; + use com_api_concept::{Publisher, SampleContainer, SampleMaybeUninit, SampleMut, Subscription}; #[test] fn receive_stuff() { @@ -451,4 +522,17 @@ mod test { } }) } + + #[test] + fn send_stuff() { + let provider_info = super::MockProviderInfo { + instance_specifier: com_api_concept::InstanceSpecifier::new("/test/publisher") + .expect("Invalid instance specifier"), + }; + let test_publisher = super::Publisher::::new("test_publisher", provider_info) + .expect("Publisher creation failed"); + let sample = test_publisher.allocate().expect("Couldn't allocate sample"); + let sample = sample.write(42); + sample.send().expect("Send failed for sample"); + } } diff --git a/com-api/com-api/Cargo.toml b/com-api/com-api/Cargo.toml index 0664f1f..fdcd21e 100644 --- a/com-api/com-api/Cargo.toml +++ b/com-api/com-api/Cargo.toml @@ -5,11 +5,7 @@ edition = "2024" publish = ["common"] license = "Apache-2.0" -[features] -mock = ["com-api-runtime-mock"] -lola = ["com-api-runtime-lola"] - [dependencies] com-api-concept = { workspace = true } -com-api-runtime-mock = { workspace = true, optional = true } -com-api-runtime-lola = { workspace = true, optional = true } +com-api-runtime-mock = { workspace = true } +com-api-runtime-lola = { workspace = true } diff --git a/com-api/com-api/src/lib.rs b/com-api/com-api/src/lib.rs index 6924002..30bfa6f 100644 --- a/com-api/com-api/src/lib.rs +++ b/com-api/com-api/src/lib.rs @@ -12,23 +12,15 @@ //! This crate provides the COM API, which is a common interface for different implementations //! of the COM API, e.g., for different IPC backends. //! The actual implementations are provided by the `com-api-runtime-mock` and `com-api-runtime-lola` crates. -//! The user must enable one of these features to use the COM API. - -#[cfg(not(any(feature = "mock", feature = "lola")))] -compile_error!("You must enable at least one feature: `mock` or `lola`!"); - -#[cfg(feature = "mock")] -pub use com_api_runtime_mock::RuntimeBuilderImpl; -#[cfg(feature = "mock")] -pub use com_api_runtime_mock::MockRuntimeImpl; -#[cfg(feature = "lola")] -pub use com_api_runtime_lola::RuntimeBuilderImpl; -#[cfg(feature = "lola")] pub use com_api_runtime_lola::LolaRuntimeImpl; +pub use com_api_runtime_lola::RuntimeBuilderImpl as LolaRuntimeBuilderImpl; +pub use com_api_runtime_mock::MockRuntimeImpl; +pub use com_api_runtime_mock::RuntimeBuilderImpl as MockRuntimeBuilderImpl; pub use com_api_concept::{ - Builder, Consumer, ConsumerBuilder, ConsumerDescriptor, InstanceSpecifier, Interface, - OfferedProducer, Producer, ProducerBuilder, Reloc, Result, SampleContainer, SampleMaybeUninit, - SampleMut, ServiceDiscovery, Subscriber, Subscription, + Builder, Consumer, ConsumerBuilder, ConsumerDescriptor, Error, FindServiceSpecifier, + InstanceSpecifier, Interface, OfferedProducer, Producer, ProducerBuilder, Publisher, Reloc, + Result, Runtime, SampleContainer, SampleMaybeUninit, SampleMut, ServiceDiscovery, Subscriber, + Subscription, }; diff --git a/com-api/examples/Cargo.toml b/com-api/examples/Cargo.toml index 10575a9..0441e68 100644 --- a/com-api/examples/Cargo.toml +++ b/com-api/examples/Cargo.toml @@ -5,13 +5,9 @@ edition = "2024" publish = ["common"] license = "Apache-2.0" -[features] -mock = ["com-api/mock", "com-api-gen/mock"] -lola = ["com-api/lola", "com-api-gen/lola"] - [dependencies] com-api = { workspace = true } -com-api-gen = { workspace = true, optional = true } +com-api-gen = { workspace = true } tokio = { version = "~1.43", features = ["rt-multi-thread", "macros", "test-util"] } [[example]] diff --git a/com-api/examples/basic-consumer-producer.rs b/com-api/examples/basic-consumer-producer.rs index dd05b16..ec59464 100644 --- a/com-api/examples/basic-consumer-producer.rs +++ b/com-api/examples/basic-consumer-producer.rs @@ -12,51 +12,92 @@ use com_api::*; use com_api_gen::*; -fn main() { - let runtime_builder = RuntimeBuilderImpl::new(); - let runtime = Builder::::build(runtime_builder).unwrap(); - let producer_builder = runtime.producer_builder::(InstanceSpecifier { - specifier: "My/Funk/ServiceName".to_string(), - }); - let producer = producer_builder.build().unwrap(); - let offered_producer = producer.offer().unwrap(); +// Example struct demonstrating composition with VehicleConsumer +pub struct VehicleMonitor { + _consumer: VehicleConsumer, + producer: VehicleOfferedProducer, + tire_subscriber: <::Subscriber as Subscriber>::Subscription, +} - // Business logic - let uninit_sample = offered_producer.left_tire.allocate().unwrap(); - let sample = uninit_sample.write(Tire {}); - sample.send().unwrap(); +impl VehicleMonitor { + /// Create a new VehicleMonitor with a consumer + pub fn new(consumer: VehicleConsumer, producer: VehicleOfferedProducer) -> Self { + let tire_subscriber = consumer.left_tire.subscribe(3).unwrap(); + Self { + _consumer: consumer, + producer, + tire_subscriber, + } + } - // Create service discovery - let consumer_discovery = runtime.find_service::(InstanceSpecifier { - specifier: "My/Funk/ServiceName".to_string(), - }); + /// Monitor tire data from the consumer + pub fn read_tire_data(&self) -> Result { + let mut sample_buf = SampleContainer::new(); + + match self.tire_subscriber.try_receive(&mut sample_buf, 1) { + Ok(0) => Err(Error::Fail), + Ok(x) => { + let sample = sample_buf.pop_front().unwrap(); + Ok(format!("{} samples received: sample[0] = {:?}", x, *sample)) + } + Err(e) => Err(e), + } + } + + pub fn write_tire_data(&self, tire: Tire) -> Result<()> { + let uninit_sample = self.producer.left_tire.allocate()?; + let sample = uninit_sample.write(tire); + sample.send()?; + Ok(()) + } +} + +fn use_consumer(runtime: &R) -> VehicleConsumer { + // Find all the avaiable service instances using ANY specifier + let consumer_discovery = runtime.find_service::(FindServiceSpecifier::Any); let available_service_instances = consumer_discovery.get_available_instances().unwrap(); - // Create consumer from first discovered service let consumer_builder = available_service_instances .into_iter() - .find(|desc| desc.get_instance_id() == 42) + .find(|desc| desc.get_instance_identifier().as_ref() == "/My/Funk/ServiceName") .unwrap(); + let consumer = consumer_builder.build().unwrap(); + // + consumer +} - // Subscribe to one event - let subscribed = consumer.left_tire.subscribe(3).unwrap(); - - // Create sample buffer to be used during receive - let mut sample_buf = SampleContainer::new(); - for _ in 0..10 { - let uninit_sample = offered_producer.left_tire.allocate().unwrap(); - let sample = uninit_sample.write(Tire {}); - sample.send().unwrap(); - match subscribed.try_receive(&mut sample_buf, 1) { - Ok(0) => panic!("No sample received"), - Ok(x) => { - let sample = sample_buf.pop_front().unwrap(); - println!("{} sample received: sample[0] = {:?}", x, *sample) - } - Err(e) => panic!("{:?}", e), - } +fn use_producer(runtime: &R) -> VehicleOfferedProducer { + let producer_builder = runtime.producer_builder::>( + InstanceSpecifier::new("/My/Funk/ServiceName").unwrap(), + ); + let producer = producer_builder.build().unwrap(); + let offered_producer = producer.offer().unwrap(); + offered_producer +} + +fn run_with_runtime(name: &str, runtime: &R) { + println!("\n=== Running with {} runtime ===", name); + let producer = use_producer(runtime); + let consumer = use_consumer(runtime); + let monitor = VehicleMonitor::new(consumer, producer); + + for _ in 0..5 { + monitor.write_tire_data(Tire {}).unwrap(); + let tire_data = monitor.read_tire_data().unwrap(); + println!("{}", tire_data); } + println!("=== {} runtime completed ===\n", name); +} + +fn main() { + let mock_runtime_builder = MockRuntimeBuilderImpl::new(); + let mock_runtime = Builder::::build(mock_runtime_builder).unwrap(); + run_with_runtime("Mock", &mock_runtime); + + let lola_runtime_builder = LolaRuntimeBuilderImpl::new(); + let lola_runtime = Builder::::build(lola_runtime_builder).unwrap(); + run_with_runtime("Lola", &lola_runtime); } #[cfg(test)] @@ -66,57 +107,27 @@ mod test { #[test] fn create_producer() { // Factory - let runtime_builder = RuntimeBuilderImpl::new(); - let runtime = runtime_builder.build().unwrap(); - let producer_builder = runtime.producer_builder::(InstanceSpecifier { - specifier: "My/Funk/ServiceName".to_string(), - }); - let producer = producer_builder.build().unwrap(); - let offered_producer = producer.offer().unwrap(); - - // Business logic - let uninit_sample = offered_producer.left_tire.allocate().unwrap(); - let sample = uninit_sample.write(Tire {}); - sample.send().unwrap(); + let mock_runtime_builder = MockRuntimeBuilderImpl::new(); + let runtime = Builder::::build(mock_runtime_builder).unwrap(); + use_producer(&runtime); + + let lola_runtime_builder = LolaRuntimeBuilderImpl::new(); + let runtime = Builder::::build(lola_runtime_builder).unwrap(); + use_producer(&runtime); } #[test] fn create_consumer() { - // Create runtime - let runtime_builder = RuntimeBuilderImpl::new(); - let runtime = runtime_builder.build().unwrap(); - - // Create service discovery - let consumer_discovery = runtime.find_service::(InstanceSpecifier { - specifier: "My/Funk/ServiceName".to_string(), - }); - let available_service_instances = consumer_discovery.get_available_instances().unwrap(); + let mock_runtime_builder = MockRuntimeBuilderImpl::new(); + let runtime = Builder::::build(mock_runtime_builder).unwrap(); + use_consumer(&runtime); - // Create consumer from first discovered service - let consumer_builder = available_service_instances - .into_iter() - .find(|desc| desc.get_instance_id() == 42) - .unwrap(); - let consumer = consumer_builder.build().unwrap(); - - // Subscribe to one event - let subscribed = consumer.left_tire.subscribe(3).unwrap(); - - // Create sample buffer to be used during receive - let mut sample_buf = SampleContainer::new(); - for _ in 0..10 { - match subscribed.try_receive(&mut sample_buf, 1) { - Ok(0) => panic!("No sample received"), - Ok(x) => { - let sample = sample_buf.pop_front().unwrap(); - println!("{} samples received: sample[0] = {:?}", x, *sample) - } - Err(e) => panic!("{:?}", e), - } - } + let lola_runtime_builder = LolaRuntimeBuilderImpl::new(); + let runtime = Builder::::build(lola_runtime_builder).unwrap(); + use_consumer(&runtime); } - async fn async_data_processor_fn(subscribed: impl Subscription) { + async fn async_data_processor_fn(subscribed: impl Subscription) { let mut buffer = SampleContainer::new(); for _ in 0..10 { match subscribed.receive(&mut buffer, 1, 1).await { @@ -135,18 +146,17 @@ mod test { #[tokio::test(flavor = "multi_thread")] async fn schedule_subscription_on_mt_scheduler() { - let runtime_builder = RuntimeBuilderImpl::new(); - let runtime = runtime_builder.build().unwrap(); + let mock_runtime_builder = MockRuntimeBuilderImpl::new(); + let runtime = Builder::::build(mock_runtime_builder).unwrap(); - let consumer_discovery = runtime.find_service::(InstanceSpecifier { - specifier: "My/Funk/ServiceName".to_string(), - }); + let consumer_discovery = + runtime.find_service::(FindServiceSpecifier::Any); let available_service_instances = consumer_discovery.get_available_instances().unwrap(); // Create consumer from first discovered service let consumer_builder = available_service_instances .into_iter() - .find(|desc| desc.get_instance_id() == 42) + .find(|desc| desc.get_instance_identifier().as_ref() == "/My/Funk/ServiceName") .unwrap(); let consumer = consumer_builder.build().unwrap(); diff --git a/com-api/examples/com-api-gen/Cargo.toml b/com-api/examples/com-api-gen/Cargo.toml index b4aa992..e786d59 100644 --- a/com-api/examples/com-api-gen/Cargo.toml +++ b/com-api/examples/com-api-gen/Cargo.toml @@ -5,10 +5,5 @@ edition = "2024" publish = ["common"] license = "Apache-2.0" -[features] -mock = ["com-api-gen-mock"] -lola = ["com-api-gen-lola"] - [dependencies] -com-api-gen-mock = { path = "com-api-gen-mock", optional = true } -com-api-gen-lola = { path = "com-api-gen-lola", optional = true } +com-api = { workspace = true } diff --git a/com-api/examples/com-api-gen/com-api-gen-lola/Cargo.toml b/com-api/examples/com-api-gen/com-api-gen-lola/Cargo.toml deleted file mode 100644 index a1652e4..0000000 --- a/com-api/examples/com-api-gen/com-api-gen-lola/Cargo.toml +++ /dev/null @@ -1,13 +0,0 @@ -[package] -name = "com-api-gen-lola" -version = "0.1.0" -edition = "2024" -publish = ["common"] -license = "Apache-2.0" - -[features] -lola = ["com-api/lola"] - -[dependencies] -com-api-runtime-lola = { workspace = true } -com-api = { workspace = true } diff --git a/com-api/examples/com-api-gen/com-api-gen-lola/src/lib.rs b/com-api/examples/com-api-gen/com-api-gen-lola/src/lib.rs deleted file mode 100644 index 24b681e..0000000 --- a/com-api/examples/com-api-gen/com-api-gen-lola/src/lib.rs +++ /dev/null @@ -1,93 +0,0 @@ -// Copyright (c) 2025 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 License Version 2.0 which is available at -// -// -// SPDX-License-Identifier: Apache-2.0 - -//! This is the "generated" code for an interface that looks like this (pseudo-IDL): -//! -//! ```poor-persons-idl -//! interface Vehicle { -//! left_tire: Event, -//! exhaust: Event, -//! set_indicator_state: FnMut(indicator_status: IndicatorStatus) -> Result, -//! } -//! -//! interface Another {} -//! -//! ``` - -use com_api::*; -use com_api_runtime_lola::{LolaRuntimeImpl, SampleConsumerBuilder, SampleProducerBuilder}; - -#[derive(Debug)] -pub struct Tire {} -unsafe impl Reloc for Tire {} - -pub struct Exhaust {} -unsafe impl Reloc for Exhaust {} - -pub struct VehicleInterface {} - -/// Generic -impl Interface for VehicleInterface {} - -pub struct AnotherInterface {} - -impl Interface for AnotherInterface {} - -pub struct VehicleProducer {} - -impl Producer for VehicleProducer { - type Interface = VehicleInterface; - type OfferedProducer = VehicleOfferedProducer; - - fn offer(self) -> com_api::Result { - todo!() - } -} - -pub struct VehicleOfferedProducer { - pub left_tire: com_api_runtime_lola::Publisher, - pub exhaust: com_api_runtime_lola::Publisher, -} - -impl OfferedProducer for VehicleOfferedProducer { - type Interface = VehicleInterface; - type Producer = VehicleProducer; - - fn unoffer(self) -> Self::Producer { - VehicleProducer {} - } -} - -impl Builder for SampleProducerBuilder { - fn build(self) -> com_api::Result { - todo!() - } -} - -impl ProducerBuilder - for SampleProducerBuilder -{ -} - -pub struct VehicleConsumer { - pub left_tire: com_api_runtime_lola::SubscribableImpl, - pub exhaust: com_api_runtime_lola::SubscribableImpl, -} - -impl Consumer for VehicleConsumer {} - -impl ConsumerBuilder for SampleConsumerBuilder {} - -impl Builder for SampleConsumerBuilder { - fn build(self) -> com_api::Result { - todo!() - } -} diff --git a/com-api/examples/com-api-gen/com-api-gen-mock/Cargo.toml b/com-api/examples/com-api-gen/com-api-gen-mock/Cargo.toml deleted file mode 100644 index 9a12571..0000000 --- a/com-api/examples/com-api-gen/com-api-gen-mock/Cargo.toml +++ /dev/null @@ -1,13 +0,0 @@ -[package] -name = "com-api-gen-mock" -version = "0.1.0" -edition = "2024" -publish = ["common"] -license = "Apache-2.0" - -[features] -mock = ["com-api/mock"] - -[dependencies] -com-api-runtime-mock = { workspace = true } -com-api = { workspace = true } diff --git a/com-api/examples/com-api-gen/com-api-gen-mock/src/lib.rs b/com-api/examples/com-api-gen/com-api-gen-mock/src/lib.rs deleted file mode 100644 index e99f950..0000000 --- a/com-api/examples/com-api-gen/com-api-gen-mock/src/lib.rs +++ /dev/null @@ -1,93 +0,0 @@ -// Copyright (c) 2025 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 License Version 2.0 which is available at -// -// -// SPDX-License-Identifier: Apache-2.0 - -//! This is the "generated" code for an interface that looks like this (pseudo-IDL): -//! -//! ```poor-persons-idl -//! interface Vehicle { -//! left_tire: Event, -//! exhaust: Event, -//! set_indicator_state: FnMut(indicator_status: IndicatorStatus) -> Result, -//! } -//! -//! interface Another {} -//! -//! ``` - -use com_api::*; -use com_api_runtime_mock::{MockRuntimeImpl, SampleConsumerBuilder, SampleProducerBuilder}; - -#[derive(Debug)] -pub struct Tire {} -unsafe impl Reloc for Tire {} - -pub struct Exhaust {} -unsafe impl Reloc for Exhaust {} - -pub struct VehicleInterface {} - -/// Generic -impl Interface for VehicleInterface {} - -pub struct AnotherInterface {} - -impl Interface for AnotherInterface {} - -pub struct VehicleProducer {} - -impl Producer for VehicleProducer { - type Interface = VehicleInterface; - type OfferedProducer = VehicleOfferedProducer; - - fn offer(self) -> com_api::Result { - todo!() - } -} - -pub struct VehicleOfferedProducer { - pub left_tire: com_api_runtime_mock::Publisher, - pub exhaust: com_api_runtime_mock::Publisher, -} - -impl OfferedProducer for VehicleOfferedProducer { - type Interface = VehicleInterface; - type Producer = VehicleProducer; - - fn unoffer(self) -> Self::Producer { - VehicleProducer {} - } -} - -impl Builder for SampleProducerBuilder { - fn build(self) -> com_api::Result { - todo!() - } -} - -impl ProducerBuilder - for SampleProducerBuilder -{ -} - -pub struct VehicleConsumer { - pub left_tire: com_api_runtime_mock::SubscribableImpl, - pub exhaust: com_api_runtime_mock::SubscribableImpl, -} - -impl Consumer for VehicleConsumer {} - -impl ConsumerBuilder for SampleConsumerBuilder {} - -impl Builder for SampleConsumerBuilder { - fn build(self) -> com_api::Result { - todo!() - } -} diff --git a/com-api/examples/com-api-gen/src/lib.rs b/com-api/examples/com-api-gen/src/lib.rs index b08db41..bbd520c 100644 --- a/com-api/examples/com-api-gen/src/lib.rs +++ b/com-api/examples/com-api-gen/src/lib.rs @@ -9,7 +9,99 @@ // // SPDX-License-Identifier: Apache-2.0 -#[cfg(feature = "mock")] -pub use com_api_gen_mock::{Tire, VehicleInterface}; -#[cfg(feature = "lola")] -pub use com_api_gen_lola::{Tire, VehicleInterface}; +//! This is the "generated" code for an interface that looks like this (pseudo-IDL): +//! +//! ```poor-persons-idl +//! interface Vehicle { +//! left_tire: Event, +//! exhaust: Event, +//! set_indicator_state: FnMut(indicator_status: IndicatorStatus) -> Result, +//! } +//! +//! interface Another {} +//! +//! ``` + +use com_api::{ + Consumer, Interface, OfferedProducer, Producer, Publisher, Reloc, Runtime, Subscriber, +}; + +#[derive(Debug)] +pub struct Tire {} +unsafe impl Reloc for Tire {} + +#[derive(Debug)] +pub struct Exhaust {} +unsafe impl Reloc for Exhaust {} + +pub struct VehicleInterface {} + +/// Generic +impl Interface for VehicleInterface { + const TYPE_ID: &'static str = "VehicleInterface"; + type Consumer = VehicleConsumer; + type Producer = VehicleProducer; +} + +pub struct VehicleConsumer { + pub left_tire: R::Subscriber, + pub exhaust: R::Subscriber, +} + +impl Consumer for VehicleConsumer { + fn new(instance_info: R::ConsumerInfo) -> com_api::Result { + Ok(VehicleConsumer { + left_tire: R::Subscriber::new("left_tire", instance_info.clone()) + .expect("Failed to create subscriber for left_tire"), + exhaust: R::Subscriber::new("exhaust", instance_info.clone()) + .expect("Failed to create subscriber for exhaust"), + }) + } +} + +pub struct AnotherInterface {} + +pub struct VehicleProducer { + _runtime: core::marker::PhantomData, + instance_info: R::ProviderInfo, +} + +impl Producer for VehicleProducer { + type Interface = VehicleInterface; + type OfferedProducer = VehicleOfferedProducer; + + fn offer(self) -> com_api::Result { + Ok(VehicleOfferedProducer { + left_tire: R::Publisher::new("left_tire", self.instance_info.clone()) + .expect("Failed to create Publisher for left_tire"), + exhaust: R::Publisher::new("exhaust", self.instance_info.clone()) + .expect("Failed to create Publisher for exhaust"), + instance_info: self.instance_info, + }) + } + + fn new(instance_info: R::ProviderInfo) -> com_api::Result { + Ok(VehicleProducer { + _runtime: core::marker::PhantomData, + instance_info, + }) + } +} + +pub struct VehicleOfferedProducer { + pub left_tire: R::Publisher, + pub exhaust: R::Publisher, + instance_info: R::ProviderInfo, +} + +impl OfferedProducer for VehicleOfferedProducer { + type Interface = VehicleInterface; + type Producer = VehicleProducer; + + fn unoffer(self) -> Self::Producer { + VehicleProducer { + _runtime: std::marker::PhantomData, + instance_info: self.instance_info, + } + } +}