diff --git a/score/mw/com/example/com-api-example/BUILD b/score/mw/com/example/com-api-example/BUILD index 91bfb1091..7a3e5da5b 100644 --- a/score/mw/com/example/com-api-example/BUILD +++ b/score/mw/com/example/com-api-example/BUILD @@ -23,6 +23,8 @@ rust_binary( "//score/mw/com/example/com-api-example/com-api-gen", "//score/mw/com/impl/rust/com-api/com-api", "@score_communication_crate_index//:clap", + "@score_communication_crate_index//:futures", + "@score_communication_crate_index//:tokio", ], ) diff --git a/score/mw/com/example/com-api-example/basic-consumer-producer.rs b/score/mw/com/example/com-api-example/basic-consumer-producer.rs index 90079e61f..fad5b980a 100644 --- a/score/mw/com/example/com-api-example/basic-consumer-producer.rs +++ b/score/mw/com/example/com-api-example/basic-consumer-producer.rs @@ -11,23 +11,25 @@ * SPDX-License-Identifier: Apache-2.0 ********************************************************************************/ +#![allow(unused)] + // This demo app writing and reading tire pressure data using producer and consumer respectively. // It is demonstrating the composition of consumer and producer in one struct, // but they can be used separately as well. // The example is using Lola runtime, but it can be used with any runtime by changing the runtime initialization part. // Note: The example is using unwrap and panic in some places for simplicity, // but it is recommended to handle errors properly in production code. - use clap::Parser; use std::path::PathBuf; use com_api::{ - Builder, FindServiceSpecifier, InstanceSpecifier, Interface, LolaRuntimeBuilderImpl, - OfferedProducer, Producer, Publisher, Result, Runtime, RuntimeBuilder, SampleContainer, - SampleMaybeUninit, SampleMut, ServiceDiscovery, Subscriber, Subscription, + Builder, FieldPublisher, FieldSubscriber, FieldSubscription, FindServiceSpecifier, + InstanceSpecifier, Interface, LolaRuntimeBuilderImpl, OfferedProducer, Producer, Publisher, + Result, Runtime, RuntimeBuilder, SampleContainer, SampleMaybeUninit, SampleMut, + ServiceDiscovery, Subscriber, Subscription, }; -use com_api_gen::{Exhaust, Tire, VehicleInterface}; +use com_api_gen::{Exhaust, Tire, VehicleFieldInterface, VehicleInterface}; #[derive(Parser)] struct Arguments { @@ -45,6 +47,13 @@ type VehicleConsumer = ::Consumer; // VehicleOfferedProducer is the offered producer type generated for the Vehicle interface, parameterized by the runtime R type VehicleOfferedProducer = <::Producer as Producer>::OfferedProducer; +// VehicleFieldProducer is the producer type for the VehicleField interface (before offering) +type VehicleFieldProducer = ::Producer; +// VehicleFieldOfferedProducer is the offered producer type for the VehicleField interface (fields support update/set-handler) +type VehicleFieldOfferedProducer = + <::Producer as Producer>::OfferedProducer; + +type VehicleFieldConsumer = ::Consumer; // Example struct demonstrating composition with VehicleConsumer pub struct VehicleMonitor { @@ -179,6 +188,155 @@ fn create_producer( producer.offer().expect("Failed to offer producer instance") } +fn process_received(val: &Tire) { + //Update some internal value and process it + //maybe need to apply synchronation if we are using thread pool + //just for demonstration, updating same value with incremented pressure. +} + +fn create_producer_field( + runtime: &R, + service_id: InstanceSpecifier, + initial_tire_value: Tire, + initial_exhaust_value: Exhaust, +) -> VehicleFieldOfferedProducer +where + ::FieldPublisher: Send + Sync, + ::FieldPublisher: Send, +{ + let producer_builder = runtime.producer_builder::(service_id); + let producer = producer_builder + .build() + .expect("Failed to build producer instance"); + + // Use validator pattern with compile-time type-state validation + // Must register handlers AND initialize all fields before offer() is available + let offered = producer + .validator() + .register_set_handler_left_tire(move |val: &Tire| { + println!("Received tire pressure update: {:?}", val); + process_received(val); + }) + .register_set_handler_exhaust(|_val: &Exhaust| { + println!("Received exhaust update"); + }) + .update_left_tire(&initial_tire_value) + .expect("Failed to update left_tire field") + .update_exhaust(&initial_exhaust_value) + .expect("Failed to update exhaust field") + .offer() + .expect("Failed to offer producer instance"); + + offered +} + +fn offered_producer_process(offered_producer: VehicleFieldOfferedProducer) { + // Use the offered producer to update fields + let new_tire_value = Tire { pressure: 32.0 }; + let new_exhaust_value = Exhaust {}; + offered_producer + .left_tire + .update(&new_tire_value) + .expect("Failed to update left_tire field"); + offered_producer + .exhaust + .update(&new_exhaust_value) + .expect("Failed to update exhaust field"); +} + +fn create_consumer_field( + runtime: &R, + service_id: InstanceSpecifier, +) -> VehicleFieldConsumer { + let consumer_discovery = + runtime.find_service::(FindServiceSpecifier::Specific(service_id)); + let available_service_instances = consumer_discovery + .get_available_instances() + .expect("Failed to get available service instances"); + + // Select service instance at specific handle_index + let handle_index = 0; // or any index you need from vector of instances + let consumer_builder = available_service_instances + .into_iter() + .nth(handle_index) + .expect("Failed to get consumer builder at specified handle index"); + + consumer_builder + .build() + .expect("Failed to build consumer instance") +} + +async fn process_subscription_async(subscription: S) +where + S: FieldSubscription, + R: Runtime, +{ + // Get field value asynchronously + match subscription.get().await { + Ok(_method_return) => { + println!("Current tire pressure from spawned task"); + } + Err(e) => eprintln!("Failed to get tire pressure: {:?}", e), + } + + println!("Async subscription processing in spawned task completed"); +} + +fn consumer_processing_field(consumer: VehicleFieldConsumer) +where + <::FieldSubscriber as Subscriber>::Subscription: Send + 'static, +{ + // Field consumer API methods + // But they demonstrate the correct API usage pattern + // TODO: Currently we are not offering the get method async in FieldSubscriber + // because async call will may run in different thread and that will cause the issue in subscription. + let _ = consumer + .left_tire + .get() + .map(|result| println!("Got field value via consumer: {:?}", result)); + + let _ = consumer + .left_tire + .set(Tire { pressure: 30.0 }) + .map(|result| println!("Set field value via consumer: {:?}", result)); + + // Subscribe to the field to receive updates + let subscription = consumer + .left_tire + .subscribe(3) + .expect("Failed to subscribe to field"); + + // Create scope for sample_buf to ensure it's dropped before tokio::spawn + { + let mut sample_buf = SampleContainer::new(3); + + // Poll for updates (non-blocking) + match subscription.try_receive(&mut sample_buf, 1) { + Ok(n) if n > 0 => { + while let Some(sample) = sample_buf.pop_front() { + println!("Updated tire pressure: {:?}", *sample); + } + } + _ => { + println!("No new tire pressure updates available"); + } + } + // sample_buf is dropped here at end of scope + } + + // Set via subscription + let _ = subscription + .set(Tire { pressure: 35.0 }) + .map(|result| println!("Set field value via subscription: {:?}", result)); + + // Spawn async task with subscription + // The subscription is moved into the task + tokio::spawn(async move { + process_subscription_async(subscription).await; + // subscription is automatically unsubscribed when dropped at end of task + }); +} + // Run the example with the specified runtime fn run_with_runtime(name: &str, runtime: &R) { println!("\n=== Running with {name} runtime ==="); diff --git a/score/mw/com/example/com-api-example/com-api-gen/com_api_gen.rs b/score/mw/com/example/com-api-example/com-api-gen/com_api_gen.rs index 506df07aa..0e2b5c761 100644 --- a/score/mw/com/example/com-api-example/com-api-gen/com_api_gen.rs +++ b/score/mw/com/example/com-api-example/com-api-gen/com_api_gen.rs @@ -11,16 +11,16 @@ * SPDX-License-Identifier: Apache-2.0 ********************************************************************************/ -use com_api::{interface, CommData, ProviderInfo, Publisher, Reloc, Subscriber}; +use com_api::{interface, CommData, FieldPublisher, ProviderInfo, Publisher, Reloc, Subscriber}; -#[derive(Debug, Reloc, CommData)] +#[derive(Debug, Clone, Default, Reloc, CommData)] #[repr(C)] #[comm_data(id = "Tire")] pub struct Tire { pub pressure: f32, } -#[derive(Debug, Reloc, CommData)] +#[derive(Debug, Clone, Default, Reloc, CommData)] #[repr(C)] // No explicit ID provided, so it will be auto-generated as "com_api_gen::Exhaust" pub struct Exhaust {} @@ -39,6 +39,7 @@ pub struct Exhaust {} // "exhaust" events. // - VehicleOfferedProducer struct that implements OfferedProducer trait for offering // "left_tire" and "exhaust" events. +//Let's remove interface!( interface Vehicle, { Id = "VehicleInterface", @@ -46,3 +47,14 @@ interface!( exhaust: Event, } ); + +// Field-based interface with compile-time initialization safety. +// All fields must be explicitly initialized via the validator pattern before offering. +// The validator pattern ensures that you cannot call offer() until all fields have been updated. +interface!( + interface VehicleField, { + Id = "VehicleFieldInterface", + left_tire: Field, + exhaust: Field, + } +); diff --git a/score/mw/com/impl/rust/com-api/com-api-concept-macros/BUILD b/score/mw/com/impl/rust/com-api/com-api-concept-macros/BUILD index 7b0903ba6..f2839e148 100644 --- a/score/mw/com/impl/rust/com-api/com-api-concept-macros/BUILD +++ b/score/mw/com/impl/rust/com-api/com-api-concept-macros/BUILD @@ -15,7 +15,10 @@ load("@rules_rust//rust:defs.bzl", "rust_doc_test", "rust_proc_macro") rust_proc_macro( name = "com-api-concept-macros", - srcs = ["lib.rs"], + srcs = [ + "lib.rs", + "type_state_validator.rs", + ], crate_name = "com_api_concept_macros", visibility = [ "//visibility:public", diff --git a/score/mw/com/impl/rust/com-api/com-api-concept-macros/lib.rs b/score/mw/com/impl/rust/com-api/com-api-concept-macros/lib.rs index f19b5c94c..0721add2e 100644 --- a/score/mw/com/impl/rust/com-api/com-api-concept-macros/lib.rs +++ b/score/mw/com/impl/rust/com-api/com-api-concept-macros/lib.rs @@ -13,8 +13,11 @@ use proc_macro::TokenStream; use quote::quote; +use syn::spanned::Spanned; use syn::{parse_macro_input, parse_quote, Data, DeriveInput, Fields, Generics, Meta, Type}; +mod type_state_validator; + /// Derive macro for the `CommData` trait. /// /// Implements `CommData` for a struct or C-like enum, providing a stable string identity @@ -335,6 +338,37 @@ fn collect_field_types(data: &Data) -> Result, ()> { Ok(out) } +/// Procedural macro to generate compile-time type-state validator for Field-based producers. +/// +/// This macro generates a validator struct with phantom type parameters that track +/// the initialization state of each field at compile time. The `offer()` method is only +/// available when all fields have been initialized, preventing runtime errors. +/// +/// # Usage +/// +/// Apply this macro alongside the `interface!` macro for Field-based interfaces: +/// +/// ```ignore +/// #[derive(TypeStateFieldValidator)] +/// struct VehicleFieldProducerInternal { +/// #[field_name = "left_tire"] +/// left_tire_field: R::FieldPublisher, +/// #[field_name = "exhaust"] +/// exhaust_field: R::FieldPublisher, +/// } +/// ``` +/// +/// The macro generates: +/// - `Uninit` and `Init` marker types +/// - `ValidatorN<'a, R, Field1State, Field2State, ...>` struct +/// - `update_field_name()` methods that transition states +/// - `update_field_name()` methods for each field +/// - `offer()` method that validates all fields initialized before proceeding +#[proc_macro_derive(TypeStateFieldValidator, attributes(field_name))] +pub fn derive_typestate_field_validator(input: TokenStream) -> TokenStream { + type_state_validator::derive_typestate_field_validator_impl(input) +} + // Use doctest to test failed compilations and successful ones /// ``` diff --git a/score/mw/com/impl/rust/com-api/com-api-concept-macros/type_state_validator.rs b/score/mw/com/impl/rust/com-api/com-api-concept-macros/type_state_validator.rs new file mode 100644 index 000000000..a43d47cf1 --- /dev/null +++ b/score/mw/com/impl/rust/com-api/com-api-concept-macros/type_state_validator.rs @@ -0,0 +1,293 @@ +/******************************************************************************** + * Copyright (c) 2026 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 + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ + +use proc_macro::TokenStream; +use quote::quote; +use syn::spanned::Spanned; +use syn::{parse_macro_input, Data, DeriveInput, Fields, Type}; + +/// The macro generates a validator struct with phantom type parameters that track +/// both the initial value update AND handler registration of each field at compile time. +/// The `offer()` method is only available when all fields have been initialized AND +/// all handlers have been registered, preventing runtime errors. +/// +/// # Usage +/// +/// Apply this macro alongside the `interface!` macro for Field-based interfaces: +/// +/// ```ignore +/// #[derive(TypeStateFieldValidator)] +/// struct VehicleFieldProducerInternal { +/// left_tire: R::FieldPublisher, +/// exhaust: R::FieldPublisher, +/// } +/// ``` +/// +/// The macro generates: +/// - `Uninit`/`Init` marker types for update state +/// - `HandlerNotSet`/`HandlerSet` marker types for handler registration state +/// - `ValidatorN<'a, R, S0, S1, ..., H0, H1, ...>` struct with dual type-state tracking +/// - `update_field_name()` methods that transition update state from Uninit -> Init +/// - `register_set_handler_field_name()` methods that transition handler state from HandlerNotSet -> HandlerSet +/// - `offer()` method only available when all fields are Init AND all handlers are HandlerSet +pub fn derive_typestate_field_validator_impl(input: TokenStream) -> TokenStream { + let input = parse_macro_input!(input as DeriveInput); + let name = &input.ident; + + // Extract runtime generic parameter + let (runtime_param_name, runtime_param_with_bounds) = + if let Some(param) = input.generics.params.first() { + match param { + syn::GenericParam::Type(type_param) => { + let name = &type_param.ident; + (quote! { #name }, quote! { #param }) + } + _ => (quote! { R }, quote! { R: com_api::Runtime + ?Sized }), + } + } else { + (quote! { R }, quote! { R: com_api::Runtime + ?Sized }) + }; + + let fields = match &input.data { + Data::Struct(data) => match &data.fields { + Fields::Named(fields) => &fields.named, + _ => { + return syn::Error::new_spanned( + name, + "TypeStateFieldValidator only supports structs with named fields", + ) + .to_compile_error() + .into(); + } + }, + _ => { + return syn::Error::new_spanned(name, "TypeStateFieldValidator only supports structs") + .to_compile_error() + .into(); + } + }; + + // Extract field information - use all fields except instance_info + let field_info: Vec<_> = fields + .iter() + .filter_map(|f| { + let ident = f.ident.as_ref()?; + + // Skip instance_info field + // Note: type name is using here as we have same name in interface_macros + // If that change then this also need to be updated. + if ident == "instance_info" { + return None; + } + + Some(( + ident, // struct field name + ident, // public field name (same as struct field) + &f.ty, // field type + )) + }) + .collect(); + + if field_info.is_empty() { + return syn::Error::new_spanned( + name, + "No fields found for validation (excluding instance_info)", + ) + .to_compile_error() + .into(); + } + + let struct_field_names: Vec<_> = field_info.iter().map(|(sf, _, _)| sf).collect(); + let public_field_names: Vec<_> = field_info.iter().map(|(_, pf, _)| pf).collect(); + let field_types: Vec<_> = field_info.iter().map(|(_, _, ty)| ty).collect(); + + // Extract inner types from R::FieldPublisher -> T + let inner_types: Vec<_> = field_types + .iter() + .map(|ty| { + // Try to extract T from R::FieldPublisher + if let Type::Path(type_path) = ty { + // Look for the last segment which should be FieldPublisher + if let Some(segment) = type_path.path.segments.last() { + //Note: Same here we are using trait name directly + // But if that change then this also need to be updated. + if segment.ident == "FieldPublisher" { + // Extract the type argument + if let syn::PathArguments::AngleBracketed(args) = &segment.arguments { + if let Some(syn::GenericArgument::Type(inner_ty)) = args.args.first() { + return inner_ty; + } + } + } + } + } + // Fallback: use the full type + *ty + }) + .collect(); + + let validator_name = syn::Ident::new(&format!("{}Validator", name), name.span()); + + // Generate type parameters for each field's UPDATE state (S0, S1, S2, ...) + let field_update_state_params: Vec<_> = public_field_names + .iter() + .enumerate() + .map(|(i, _)| syn::Ident::new(&format!("S{}", i), proc_macro::Span::call_site().into())) + .collect(); + + // Generate type parameters for each field's HANDLER state (H0, H1, H2, ...) + let field_handler_state_params: Vec<_> = public_field_names + .iter() + .enumerate() + .map(|(i, _)| syn::Ident::new(&format!("H{}", i), proc_macro::Span::call_site().into())) + .collect(); + + // Generate update methods - each one changes its field's UPDATE state from current to Init + // while preserving HANDLER state + let update_methods = public_field_names + .iter() + .zip(struct_field_names.iter()) + .zip(inner_types.iter()) + .enumerate() + .map(|(i, ((pub_name, struct_name), inner_ty))| { + let update_fn = syn::Ident::new(&format!("update_{}", pub_name), pub_name.span()); + + // Build the "after" UPDATE state parameter list where this field is Init + let after_update_states: Vec<_> = field_update_state_params + .iter() + .enumerate() + .map(|(j, param)| { + if i == j { + quote! { ::com_api::Init } + } else { + quote! { #param } + } + }) + .collect(); + + quote! { + impl<#runtime_param_with_bounds, #(#field_update_state_params),*, #(#field_handler_state_params),*> + #validator_name<#runtime_param_name, #(#field_update_state_params),*, #(#field_handler_state_params),*> + { + pub fn #update_fn( + mut self, + value: &#inner_ty + ) -> com_api::Result<#validator_name<#runtime_param_name, #(#after_update_states),*, #(#field_handler_state_params),*>> + { + self.producer.#struct_name.update(value)?; + Ok(#validator_name { + producer: self.producer, + _phantom: core::marker::PhantomData, + }) + } + } + } + }); + + // Generate register_set_handler methods - each one changes its field's HANDLER state + // from HandlerNotSet to HandlerSet while preserving UPDATE state + let register_handler_methods = public_field_names + .iter() + .zip(struct_field_names.iter()) + .zip(inner_types.iter()) + .enumerate() + .map(|(i, ((pub_name, struct_name), inner_ty))| { + let register_fn = syn::Ident::new( + &format!("register_set_handler_{}", pub_name), + pub_name.span(), + ); + + // Build the "after" HANDLER state parameter list where this field is HandlerSet + let after_handler_states: Vec<_> = field_handler_state_params + .iter() + .enumerate() + .map(|(j, param)| { + if i == j { + quote! { ::com_api::HandlerSet } + } else { + quote! { #param } + } + }) + .collect(); + + quote! { + impl<#runtime_param_with_bounds, #(#field_update_state_params),*, #(#field_handler_state_params),*> + #validator_name<#runtime_param_name, #(#field_update_state_params),*, #(#field_handler_state_params),*> + where + <#runtime_param_name as com_api::Runtime>::FieldPublisher<#inner_ty>: Send, + { + pub fn #register_fn(mut self, handler: F) -> #validator_name<#runtime_param_name, #(#field_update_state_params),*, #(#after_handler_states),*> + where + F: Fn(&#inner_ty) + Send + 'static, + { + self.producer.#struct_name.register_set_handler(handler); + #validator_name { + producer: self.producer, + _phantom: core::marker::PhantomData, + } + } + } + } + }); + + // Generate list of all Init states for the offer() impl + let all_init_states = vec![quote! { ::com_api::Init }; field_update_state_params.len()]; + + // Generate list of all HandlerSet states for the offer() impl + let all_handler_set_states = + vec![quote! { ::com_api::HandlerSet }; field_handler_state_params.len()]; + + // Generate list of all Uninit states for the validator() method + let all_uninit_states = vec![quote! { ::com_api::Uninit }; field_update_state_params.len()]; + + // Generate list of all HandlerNotSet states for the validator() method + let all_handler_not_set_states = + vec![quote! { ::com_api::HandlerNotSet }; field_handler_state_params.len()]; + + let expanded = quote! { + // Validator struct with dual type-state tracking: + // - First set of params (S0, S1, ...) track field UPDATE state (Uninit/Init) + // - Second set of params (H0, H1, ...) track HANDLER registration state (HandlerNotSet/HandlerSet) + pub struct #validator_name<#runtime_param_with_bounds, #(#field_update_state_params),*, #(#field_handler_state_params),*> { + producer: #name<#runtime_param_name>, + _phantom: core::marker::PhantomData<(#(#field_update_state_params,)* #(#field_handler_state_params,)*)>, + } + + // Update methods that change UPDATE state types (Uninit -> Init) + #(#update_methods)* + + // Register set handler methods that change HANDLER state types (HandlerNotSet -> HandlerSet) + #(#register_handler_methods)* + + // offer() is only available when ALL fields are Init AND all handlers are HandlerSet + impl<#runtime_param_with_bounds> #validator_name<#runtime_param_name, #(#all_init_states),*, #(#all_handler_set_states),*> { + pub fn offer(self) -> com_api::Result<<#name<#runtime_param_name> as com_api::Producer<#runtime_param_name>>::OfferedProducer> { + // Extract producer and call its offer() method + use com_api::Producer; + self.producer.offer() + } + } + + // validator() method consumes producer and returns validator with all fields Uninit and all handlers HandlerNotSet + impl<#runtime_param_with_bounds> #name<#runtime_param_name> { + pub fn validator(self) -> #validator_name<#runtime_param_name, #(#all_uninit_states),*, #(#all_handler_not_set_states),*> { + #validator_name { + producer: self, + _phantom: core::marker::PhantomData, + } + } + } + }; + + TokenStream::from(expanded) +} diff --git a/score/mw/com/impl/rust/com-api/com-api-concept/BUILD b/score/mw/com/impl/rust/com-api/com-api-concept/BUILD index aefc913db..87faddebf 100644 --- a/score/mw/com/impl/rust/com-api/com-api-concept/BUILD +++ b/score/mw/com/impl/rust/com-api/com-api-concept/BUILD @@ -16,13 +16,7 @@ load("//quality/unit_testing:unit_testing.bzl", "rust_unit_test") rust_library( name = "com-api-concept", - srcs = [ - "com_api_concept.rs", - "error.rs", - "interface_macros.rs", - "lib.rs", - "reloc.rs", - ], + srcs = glob(["**/*.rs"]), edition = "2024", proc_macro_deps = [ "//score/mw/com/impl/rust/com-api/com-api-concept-macros", diff --git a/score/mw/com/impl/rust/com-api/com-api-concept/com_api_concept.rs b/score/mw/com/impl/rust/com-api/com-api-concept/com_api_concept.rs index 79697a168..318a6a8e2 100644 --- a/score/mw/com/impl/rust/com-api/com-api-concept/com_api_concept.rs +++ b/score/mw/com/impl/rust/com-api/com-api-concept/com_api_concept.rs @@ -48,10 +48,12 @@ //! - Structures //! - Tuples +use crate::com_api_field::*; use crate::error::*; use crate::Reloc; pub use com_api_concept_macros::CommData; use containers::fixed_capacity::FixedCapacityQueue; +use core::default::Default; use core::fmt::Debug; use core::future::Future; use core::ops::{Deref, DerefMut}; @@ -61,6 +63,12 @@ use std::path::Path; /// Result type alias with `std::result::Result` using `com_api::Error` as error type pub type Result = core::result::Result; +pub enum TagValue { + Field, + Method, + Event, +} + /// A factory-like trait for constructing complex objects through a builder pattern. /// /// This trait enables type-safe construction of objects by moving self during the build phase, @@ -106,6 +114,9 @@ pub trait Runtime { /// `ConsumerInfo` types for Configuration data for service consumers instances type ConsumerInfo: Send + Clone; + type FieldSubscriber: FieldSubscriber; + type FieldPublisher: FieldPublisher; + /// Find a service instance for the given interface and instance specifier. /// Locate available instances of a service interface. /// @@ -206,7 +217,13 @@ where /// avoid conflicts in the system. /// If no custom ID is provided, the type name will be used as the default ID with module path as /// prefix to ensure uniqueness. -pub trait CommData: Reloc { +/// +/// # Future Breaking Change +/// **Note**: `Default` will become a required supertrait in a future version to support +/// in-place initialization patterns. Existing `CommData` types should implement `Default` +/// to prepare for this change and avoid future breakage. +// TODO: Remove Default trait in final PR, with above warning +pub trait CommData: Reloc + Default { const ID: &'static str; } @@ -321,6 +338,12 @@ where { } +pub trait SampleMut: DerefMut + Debug +where + T: CommData + 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 @@ -329,18 +352,10 @@ where /// /// # Type Parameters /// * `T` - The relocatable event data type -pub trait SampleMut: DerefMut + Debug +pub trait EventSampleMut: SampleMut where T: CommData + Debug, { - /// Send the sample and consume it. - /// - /// # Returns - /// - /// A `Result` indicating success or failure of the send operation. - /// - /// # Errors - /// Returns 'Error' if the send operation fails. fn send(self) -> Result<()>; } @@ -477,8 +492,17 @@ pub trait Publisher where T: CommData + Debug, { - /// Associated sample type for uninitialized event data - type SampleMaybeUninit<'a>: SampleMaybeUninit + 'a + /// The fully-initialized sample type produced by `SampleMaybeUninit::write` / `assume_init`. + /// Must implement `EventSampleMut` so the default `send()` impl can call `.send()` on it. + type CommittedSample<'a>: EventSampleMut + where + Self: 'a; + + /// Associated sample type for uninitialized event data. + /// Its `SampleMut` is pinned to `Self::CommittedSample` via associated-type equality, + /// which lets the compiler verify the `EventSampleMut` bound without a self-referential + /// where clause on the GAT itself. + type SampleMaybeUninit<'a>: SampleMaybeUninit> + 'a where Self: 'a; /// Allocate a buffer slot for the event publication. @@ -947,7 +971,7 @@ pub trait Subscription { /// If the stream encounters an error, it will yield `Err(Error)` for that sample, but will continue to yield subsequent samples as they become available. /// The stream only terminates when the subscription is unsubscribed or dropped or if the stream is explicitly dropped by the user. /// With this design, users can handle errors on it side and take appropriate actions. - fn to_stream<'a>(&'a mut self) -> impl Stream>> + Unpin + 'a; + fn to_stream<'a>(&'a mut self) -> impl Stream>> + Unpin + 'a; } /// A trait for types that can be default-constructed in place, skipping intermediate moves. diff --git a/score/mw/com/impl/rust/com-api/com-api-concept/com_api_field.rs b/score/mw/com/impl/rust/com-api/com-api-concept/com_api_field.rs new file mode 100644 index 000000000..59650524a --- /dev/null +++ b/score/mw/com/impl/rust/com-api/com-api-concept/com_api_field.rs @@ -0,0 +1,134 @@ +/******************************************************************************** + * Copyright (c) 2026 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 + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ + +// TODO: Need to discuss: +// 1.Get and Set methods for field it is enabled based on tag, do we want to keep same kind of mechanism +// or by default we will enable for user, +// -> My suggestion is we can keep it default enable as of now, +// and later we can add tag based mechanism if required because Interface side we need to check how we can do this +// 2. We are offering get method after subscrption async but before subscription it is sync, +// It is because subscribe API take the consumer instance by value and if we offer async get method then it will create issue with subscription. + +// Note: We are using the event related trait as a base trait for whereever we have same common +// APIs or functionality, as of now there are derived from concept crate but +// we will create a module which will have common trait for event and field which will be used by both event and field as a super trait and +// for this we need to create marker trait for event. + +use crate::*; +use std::fmt::Debug; +use std::future::Future; + +#[allow(dead_code)] +// Temp for build test +// We will remove this once memory layout of same created in rust side like SamplePtr. +#[repr(C)] +#[derive(Debug)] +pub struct MethodReturnTypePtr { + pub value: T, + pub status: Result<()>, +} + +/// FieldSubscriber trait is used to subscribe to a field and get the value of the field. +/// It provides the `get` and `set` methods to get and set the value of the field. +/// It derived from `com_api_concept::Subscriber` trait which provides the `subscribe` method to create a field subscription. +/// The `get` and `set` methods for the field instance can be used before subscription. +/// Event related APIs follow the same restriction for concurrent access. +pub trait FieldSubscriber: + com_api_concept::Subscriber> +{ + ///Get the current value of the field. + /// + /// #returns + /// Return the result of `MethodReturnTypePtr` which contains the current value of the field. + fn get(&self) -> Result>; + + ///Set the value of the field. + /// + /// # Parameters + /// * `value` - The value to set for the field. + /// + /// # Returns + /// Return the result of `MethodReturnTypePtr` which contains the status of the set operation. + /// with the current value of the field. + // TODO: should we pass value by reference as underlying API is taking value as reference. + fn set(&self, value: T) -> Result>; +} + +/// FieldSubscriber trait is provides the receiving APIs for the field subscription and +/// it is derived from `com_api_concept::Subscription` trait which provides the receiving APIs for the field subscription. +/// Additional methods which the field subscription provides are added in this trait. +pub trait FieldSubscription: + com_api_concept::Subscription +{ + /// Returns the number of new samples a call to try_receive (given parameter max_num_samples + /// doesn't restrict it) would currently provide. + /// How many new sample available for the user of this field subscription to receive. + fn get_num_new_samples_available(&self) -> Result; + + /// Get the number of samples that can still be received by the user of this field. + /// This is for checking the capacity of the field subscription and to avoid overflow of the field subscription limit. + fn get_free_sample_count(&self) -> Result; + + ///Get the current value of the field. + /// + /// #returns + /// Return the result of `MethodReturnTypePtr` which contains the current value of the field. /// The returned future is `Send` to allow usage with `tokio::spawn`. + fn get(&self) -> impl Future>> + Send; + + ///Set the value of the field. + /// + /// # Parameters + /// * `value` - The value to set for the field. + /// + /// # Returns + /// Return the result of `MethodReturnTypePtr` which contains the status of the set operation. + /// with the current value of the field. + // TODO: should we pass value by reference as underlying API is taking value as reference. + fn set(&self, value: T) -> Result>; +} + +/// FieldPublisher trait is used to publish a field and update the value of the field. +// Note: We can not use publisher trait from event because that contains the Send Method which is not correct semantic for field. +pub trait FieldPublisher: Clone { + type FieldSample<'a>: FieldSampleMut + where + Self: 'a; + + type SampleMaybeUninit<'a>: SampleMaybeUninit> + 'a + where + Self: 'a; + + /// Create a new publisher for the specified event source. + fn new(identifier: &str, instance_info: R::ProviderInfo) -> Result + where + Self: Sized; + + /// Get the allocate sample ptr for the field publisher. + fn allocate(&self) -> Result>; + + /// Update the value of the field with the provided value. + /// This is not zero-copy API. + fn update(&self, value: &T) -> Result<()>; + + /// This API will return a future whenever the field value is set by using `set' method of the field subscriber. + /// Future will be resolved with value. + fn register_set_handler<'a>(&self, callback: impl Fn(&T) + Send + 'a) -> Result<()>; +} + +/// FieldSampleMut trait is used to update the value of the field sample for zero-copy API. +pub trait FieldSampleMut: com_api_concept::SampleMut +where + T: CommData + Debug, +{ + fn update(self) -> Result<()>; +} diff --git a/score/mw/com/impl/rust/com-api/com-api-concept/interface_macros.rs b/score/mw/com/impl/rust/com-api/com-api-concept/interface_macros.rs index 868859576..278bb06c7 100644 --- a/score/mw/com/impl/rust/com-api/com-api-concept/interface_macros.rs +++ b/score/mw/com/impl/rust/com-api/com-api-concept/interface_macros.rs @@ -11,6 +11,22 @@ * SPDX-License-Identifier: Apache-2.0 ********************************************************************************/ +/// Type-state marker for uninitialized field state (compile-time tracking). +#[allow(dead_code)] +pub struct Uninit; + +/// Type-state marker for initialized field state (compile-time tracking). +#[allow(dead_code)] +pub struct Init; + +/// Type-state marker for handler not registered (compile-time tracking). +#[allow(dead_code)] +pub struct HandlerNotSet; + +/// Type-state marker for handler registered (compile-time tracking). +#[allow(dead_code)] +pub struct HandlerSet; + /// Main interface macro that generates Consumer, Producer, and OfferedProducer types /// along with all necessary trait implementations. /// @@ -102,11 +118,18 @@ macro_rules! interface { ); }; - (interface $id:ident { $($event_name:ident : Field<$event_type:ty>),+$(,)? }) => { - compile_error!( - "Field definitions are not supported in this macro version. \ - Please use Event syntax for defining events." - ); + (interface $id:ident { $($field_name:ident : Field<$field_type:ty>),+$(,)? }) => { + $crate::interface_common!($id); + $crate::interface_consumer!($id, $($field_name, Field<$field_type>),+); + $crate::interface_producer!($id, $($field_name, Field<$field_type>),+); + }; + (interface $id:ident, { + Id = $uid:expr, + $($field_name:ident : Field<$field_type:ty>),+ $(,)? + }) => { + $crate::interface_common!($id, $uid); + $crate::interface_consumer!($id, $($field_name, Field<$field_type>),+); + $crate::interface_producer!($id, $($field_name, Field<$field_type>),+); }; } @@ -172,6 +195,32 @@ macro_rules! interface_consumer { } } }; + + ($id:ident, $($field_name:ident, Field<$field_type:ty>),+$(,)?) => { + com_api::paste::paste! { + pub struct [<$id Consumer>] { + $( + pub $field_name: R::FieldSubscriber<$field_type>, + )+ + } + + impl com_api::Consumer for [<$id Consumer>] { + fn new(instance_info: R::ConsumerInfo) -> Self { + [<$id Consumer>] { + $( + $field_name: R::FieldSubscriber::new( + stringify!($field_name), + instance_info.clone() + ).expect(&format!( + "Failed to create subscriber for {}", + stringify!($field_name) + )), + )+ + } + } + } + } + }; } /// Macro to implement the Producer and OfferedProducer traits for @@ -226,6 +275,7 @@ macro_rules! interface_producer { for [<$id OfferedProducer>] { type Interface = [<$id Interface>]; type Producer = [<$id Producer>]; + fn unoffer(self) -> com_api::Result { let producer = [<$id Producer>] { _runtime: core::marker::PhantomData, @@ -238,6 +288,76 @@ macro_rules! interface_producer { } } }; + + ($id:ident, $($field_name:ident, Field<$field_type:ty>),+$(,)?) => { + com_api::paste::paste! { + // Producer struct with proc macro validation + #[derive($crate::com_api_concept_macros::TypeStateFieldValidator)] + pub struct [<$id Producer>] { + $( + pub $field_name: R::FieldPublisher<$field_type>, + )+ + pub instance_info: R::ProviderInfo, + } + + pub struct [<$id OfferedProducer>] { + $( + pub $field_name: R::FieldPublisher<$field_type>, + )+ + instance_info: R::ProviderInfo, + } + + impl com_api::Producer for [<$id Producer>] { + type Interface = [<$id Interface>]; + type OfferedProducer = [<$id OfferedProducer>]; + + fn offer(self) -> com_api::Result { + // Create OfferedProducer from consumed producer + let offered = [<$id OfferedProducer>] { + $( + $field_name: self.$field_name.clone(), + )+ + instance_info: self.instance_info.clone(), + }; + // Offer the service instance to make it discoverable + self.instance_info.offer_service()?; + Ok(offered) + } + + fn new(instance_info: R::ProviderInfo) -> com_api::Result { + Ok(Self { + $( + $field_name: R::FieldPublisher::new( + stringify!($field_name), + instance_info.clone() + ).expect(&format!( + "Failed to create field publisher for {}", + stringify!($field_name) + )), + )+ + instance_info, + }) + } + } + + impl com_api::OfferedProducer + for [<$id OfferedProducer>] { + type Interface = [<$id Interface>]; + type Producer = [<$id Producer>]; + + fn unoffer(self) -> com_api::Result { + let producer = [<$id Producer>] { + $( + $field_name: self.$field_name.clone(), + )+ + instance_info: self.instance_info.clone(), + }; + self.instance_info.stop_offer_service()?; + Ok(producer) + } + } + } + }; } mod tests { diff --git a/score/mw/com/impl/rust/com-api/com-api-concept/lib.rs b/score/mw/com/impl/rust/com-api/com-api-concept/lib.rs index a944aa17f..df25158c4 100644 --- a/score/mw/com/impl/rust/com-api/com-api-concept/lib.rs +++ b/score/mw/com/impl/rust/com-api/com-api-concept/lib.rs @@ -25,6 +25,12 @@ mod interface_macros; mod reloc; pub use com_api_concept::*; pub use error::*; +// Export type-state markers for compile-time field validation +#[doc(hidden)] +pub use com_api_concept_macros; +pub use interface_macros::{HandlerNotSet, HandlerSet, Init, Uninit}; #[doc(hidden)] pub use paste; pub use reloc::Reloc; +mod com_api_field; +pub use com_api_field::*; diff --git a/score/mw/com/impl/rust/com-api/com-api-runtime-lola/BUILD b/score/mw/com/impl/rust/com-api/com-api-runtime-lola/BUILD index 1fbf183a8..c1b290169 100644 --- a/score/mw/com/impl/rust/com-api/com-api-runtime-lola/BUILD +++ b/score/mw/com/impl/rust/com-api/com-api-runtime-lola/BUILD @@ -15,12 +15,7 @@ load("@rules_rust//rust:defs.bzl", "rust_doc_test", "rust_library", "rust_test") rust_library( name = "com-api-runtime-lola", - srcs = [ - "consumer.rs", - "lib.rs", - "producer.rs", - "runtime.rs", - ], + srcs = glob(["**/*.rs"]), edition = "2024", visibility = [ "//visibility:public", # platform_only diff --git a/score/mw/com/impl/rust/com-api/com-api-runtime-lola/field_consumer.rs b/score/mw/com/impl/rust/com-api/com-api-runtime-lola/field_consumer.rs new file mode 100644 index 000000000..024b3eb27 --- /dev/null +++ b/score/mw/com/impl/rust/com-api/com-api-runtime-lola/field_consumer.rs @@ -0,0 +1,130 @@ +/******************************************************************************** + * Copyright (c) 2026 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 + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ + +//! Field consumer implementation for Lola runtime. +//! It implements the field consumer related traits. +//! We are using the event related trait as a base trait for whereever we have same common +//! APIs or functionality, as of now there are derived from concept crate but +//! we will create a module which will have common trait for event and field which will be used by both event and field consumer/publisher. + +use core::fmt::Debug; +use core::marker::PhantomData; +use core::todo; + +use bridge_ffi_rs::FFIBridge; +use com_api_concept::{ + CommData, FieldSubscriber, FieldSubscription, MethodReturnTypePtr, Result, Runtime, + SampleContainer, Subscriber, Subscription, +}; + +use crate::consumer::Sample; +use crate::{LolaConsumerInfo, LolaRuntimeImpl}; + +/// Field subscriber type which implements the FieldSubscriber trait for Lola runtime. +/// It will implement `subscribe` method to create a field subscription and `get` and `set` methods to get and set the value of the field. +pub struct LolaFieldSubscriber { + _data: PhantomData, + _bridge: PhantomData, +} + +/// Marker implementation of FieldSubscriber trait. +impl FieldSubscriber> + for LolaFieldSubscriber +{ + fn get(&self) -> Result> { + todo!() + } + fn set(&self, _value: T) -> Result> { + todo!() + } +} + +/// Implementation of Subscriber trait which provides `new` and `subscribe` methods for LolaFieldSubscriber. +impl Subscriber> + for LolaFieldSubscriber +{ + type Subscription = LolaFieldSubscription; + + fn new(_identifier: &'static str, _instance_info: LolaConsumerInfo) -> Result { + todo!() + } + + fn subscribe(self, _max_num_samples: usize) -> Result { + todo!() + } +} + +/// FieldSubscription type which provides data receiving APIs and unsubscribe method. +pub struct LolaFieldSubscription { + _data: PhantomData, + _bridge: PhantomData, +} + +impl FieldSubscription> + for LolaFieldSubscription +{ + fn get_free_sample_count(&self) -> Result { + todo!() + } + + fn get_num_new_samples_available(&self) -> Result { + todo!() + } + + fn get(&self) -> impl Future>> + Send { + async { todo!() } + } + fn set(&self, _value: T) -> Result> { + todo!() + } +} + +/// Implementation of Subscription trait which provides receiving APIs. +impl Subscription> + for LolaFieldSubscription +{ + type Subscriber = LolaFieldSubscriber; + type Sample<'a> + = Sample + where + Self: 'a; + + fn unsubscribe(self) -> Self::Subscriber { + todo!() + } + + fn try_receive<'a>( + &'a self, + _scratch: &'_ mut SampleContainer>, + _max_samples: usize, + ) -> Result { + todo!() + } + + fn cancellable_receive<'a>( + &'a self, + _scratch: SampleContainer>, + _new_samples: usize, + _max_samples: usize, + _cancellation: impl core::future::Future + Send + 'static, + ) -> impl core::future::Future>, Result)> + 'a + { + async { todo!() } + } + + fn to_stream<'a>( + &'a mut self, + ) -> impl futures::stream::Stream>> + Unpin + 'a { + futures::stream::empty() + } +} diff --git a/score/mw/com/impl/rust/com-api/com-api-runtime-lola/field_producer.rs b/score/mw/com/impl/rust/com-api/com-api-runtime-lola/field_producer.rs new file mode 100644 index 000000000..72f17dabc --- /dev/null +++ b/score/mw/com/impl/rust/com-api/com-api-runtime-lola/field_producer.rs @@ -0,0 +1,126 @@ +/******************************************************************************** + * Copyright (c) 2026 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 + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ + +//! Field producer implementation for Lola runtime. + +use core::fmt::Debug; +use core::marker::PhantomData; +use core::todo; + +use bridge_ffi_rs::FFIBridge; +use com_api_concept::{ + CommData, FieldPublisher, FieldSampleMut, Result, SampleMaybeUninit as SampleMaybeUninitTrait, +}; + +use crate::LolaProviderInfo; +use crate::LolaRuntimeImpl; + +pub struct LolaFieldPublisher { + _data: PhantomData, + _bridge: PhantomData, +} + +//TODO: Need to think about this clone, we added as Producer and OfferProducer both +//Type is using same field publisher to call the Update and set handler. +//As Set handler is async call so we need to keep producer active for that. +impl Clone for LolaFieldPublisher { + fn clone(&self) -> Self { + LolaFieldPublisher { + _data: PhantomData, + _bridge: PhantomData, + } + } +} + +impl Copy for LolaFieldPublisher {} + +#[derive(Debug)] +pub struct LolaFieldSampleMut { + _data: PhantomData, +} + +impl core::ops::Deref for LolaFieldSampleMut { + type Target = T; + fn deref(&self) -> &T { + todo!() + } +} +impl core::ops::DerefMut for LolaFieldSampleMut { + fn deref_mut(&mut self) -> &mut T { + todo!() + } +} + +impl com_api_concept::SampleMut for LolaFieldSampleMut {} + +impl FieldSampleMut for LolaFieldSampleMut { + fn update(self) -> Result<()> { + todo!() + } +} + +#[derive(Debug)] +pub struct LolaFieldSampleMaybeUninit<'a, T> { + _data: core::mem::MaybeUninit, + _lt: PhantomData<&'a T>, +} + +impl<'a, T: CommData + Debug> AsMut> + for LolaFieldSampleMaybeUninit<'a, T> +{ + fn as_mut(&mut self) -> &mut core::mem::MaybeUninit { + &mut self._data + } +} +impl<'a, T: CommData + Debug> SampleMaybeUninitTrait for LolaFieldSampleMaybeUninit<'a, T> { + type SampleMut = LolaFieldSampleMut; + unsafe fn assume_init(self) -> LolaFieldSampleMut { + todo!() + } + fn write(self, _value: T) -> LolaFieldSampleMut { + todo!() + } +} + +impl FieldPublisher> + for LolaFieldPublisher +{ + type FieldSample<'a> + = LolaFieldSampleMut + where + Self: 'a; + type SampleMaybeUninit<'a> + = LolaFieldSampleMaybeUninit<'a, T> + where + Self: 'a; + + fn new(_identifier: &str, _instance_info: LolaProviderInfo) -> Result { + todo!() + } + fn allocate(&self) -> Result> { + todo!() + } + fn update(&self, _value: &T) -> Result<()> { + todo!() + } + fn register_set_handler<'a>(&self, callback: impl Fn(&T) + Send + 'a) -> Result<()> { + + //If waker get the notification form FFI call then + //Create a task to call the callback with value. + //Thread pool is a option here to run the callback in a separate thread. + //But i feel we still need to think about exection order of that callback, + //Because separate thread can raise concurrency issue / race condition. + + todo!() + } +} diff --git a/score/mw/com/impl/rust/com-api/com-api-runtime-lola/lib.rs b/score/mw/com/impl/rust/com-api/com-api-runtime-lola/lib.rs index a1c958ff7..63eb4d38e 100644 --- a/score/mw/com/impl/rust/com-api/com-api-runtime-lola/lib.rs +++ b/score/mw/com/impl/rust/com-api/com-api-runtime-lola/lib.rs @@ -26,12 +26,19 @@ //! that utilize the COM API abstractions. mod consumer; +mod field_consumer; +mod field_producer; mod producer; mod runtime; -pub use consumer::{LolaConsumerInfo, Sample, LolaConsumerDiscovery, SubscribableImpl}; +pub use consumer::{ + LolaConsumerDiscovery, LolaConsumerInfo, Sample, SubscribableImpl, +}; +pub use field_consumer::LolaFieldSubscriber; +pub use field_producer::LolaFieldPublisher; pub use producer::{ - LolaProviderInfo, Publisher, SampleMaybeUninit, SampleMut, LolaProducerBuilder, + LolaProducerBuilder, LolaProviderInfo, Publisher, SampleMaybeUninit, + SampleMut, }; pub use runtime::{LolaRuntimeImpl, RuntimeBuilderImpl}; diff --git a/score/mw/com/impl/rust/com-api/com-api-runtime-lola/producer.rs b/score/mw/com/impl/rust/com-api/com-api-runtime-lola/producer.rs index f8e11756e..906076432 100644 --- a/score/mw/com/impl/rust/com-api/com-api-runtime-lola/producer.rs +++ b/score/mw/com/impl/rust/com-api/com-api-runtime-lola/producer.rs @@ -187,7 +187,12 @@ where } } -impl<'a, T, B: FFIBridge> com_api_concept::SampleMut for SampleMut<'a, T, B> +impl<'a, T, B: FFIBridge> com_api_concept::SampleMut for SampleMut<'a, T, B> where + T: CommData + Debug +{ +} + +impl<'a, T, B: FFIBridge> com_api_concept::EventSampleMut for SampleMut<'a, T, B> where T: CommData + Debug, { @@ -405,6 +410,10 @@ impl com_api_concept::Publisher> for Publ where T: CommData + Debug, { + type CommittedSample<'a> + = SampleMut<'a, T, B> + where + Self: 'a; type SampleMaybeUninit<'a> = SampleMaybeUninit<'a, T, B> where diff --git a/score/mw/com/impl/rust/com-api/com-api-runtime-lola/runtime.rs b/score/mw/com/impl/rust/com-api/com-api-runtime-lola/runtime.rs index f079c8646..2eabf8c69 100644 --- a/score/mw/com/impl/rust/com-api/com-api-runtime-lola/runtime.rs +++ b/score/mw/com/impl/rust/com-api/com-api-runtime-lola/runtime.rs @@ -16,8 +16,8 @@ use core::marker::PhantomData; use std::path::{Path, PathBuf}; use crate::{ - LolaConsumerDiscovery, LolaConsumerInfo, LolaProducerBuilder, LolaProviderInfo, Publisher, - SubscribableImpl, + LolaConsumerDiscovery, LolaConsumerInfo, LolaFieldPublisher, LolaFieldSubscriber, + LolaProducerBuilder, LolaProviderInfo, Publisher, SubscribableImpl, }; use com_api_concept::{ Builder, CommData, FindServiceSpecifier, InstanceSpecifier, Interface, Result, Runtime, @@ -37,6 +37,8 @@ impl Runtime for LolaRuntimeImpl { type Publisher = Publisher; type ProviderInfo = LolaProviderInfo; type ConsumerInfo = LolaConsumerInfo; + type FieldPublisher = LolaFieldPublisher; + type FieldSubscriber = LolaFieldSubscriber; fn find_service( &self, diff --git a/score/mw/com/impl/rust/com-api/com-api-runtime-mock/runtime.rs b/score/mw/com/impl/rust/com-api/com-api-runtime-mock/runtime.rs index d026b68a0..0e6cb0f8c 100644 --- a/score/mw/com/impl/rust/com-api/com-api-runtime-mock/runtime.rs +++ b/score/mw/com/impl/rust/com-api/com-api-runtime-mock/runtime.rs @@ -36,9 +36,11 @@ use std::collections::VecDeque; use std::path::Path; use com_api_concept::{ - Builder, CommData, Consumer, ConsumerBuilder, ConsumerDescriptor, FindServiceSpecifier, - InstanceSpecifier, Interface, Producer, ProducerBuilder, ProviderInfo, Result, Runtime, - SampleContainer, ServiceDiscovery, Subscriber, Subscription, + Builder, CommData, Consumer, ConsumerBuilder, ConsumerDescriptor, FieldPublisher, + FieldSampleMut, FieldSubscriber, FieldSubscription, FindServiceSpecifier, InstanceSpecifier, + Interface, MethodReturnTypePtr, Producer, ProducerBuilder, ProviderInfo, Result, Runtime, + SampleContainer, SampleMaybeUninit as SampleMaybeUninitTrait, ServiceDiscovery, Subscriber, + Subscription, }; pub struct MockRuntimeImpl {} @@ -70,6 +72,8 @@ impl Runtime for MockRuntimeImpl { type Publisher = Publisher; type ProviderInfo = MockProviderInfo; type ConsumerInfo = MockConsumerInfo; + type FieldSubscriber = MockFieldSubscriber; + type FieldPublisher = MockFieldPublisher; fn find_service( &self, @@ -190,7 +194,11 @@ where lifetime: PhantomData<&'a T>, } -impl<'a, T> com_api_concept::SampleMut for SampleMut<'a, T> +/// Base trait — no commit operation, just DerefMut access. +impl<'a, T> com_api_concept::SampleMut for SampleMut<'a, T> where T: CommData + Debug {} + +/// Event-specific extension: `send()` consumes and publishes the sample. +impl<'a, T> com_api_concept::EventSampleMut for SampleMut<'a, T> where T: CommData + Debug, { @@ -373,6 +381,12 @@ impl com_api_concept::Publisher for Publisher where T: CommData + Debug, { + // SampleMut<'a, T> implements EventSampleMut, satisfying the CommittedSample bound. + type CommittedSample<'a> + = SampleMut<'a, T> + where + Self: 'a; + type SampleMaybeUninit<'a> = SampleMaybeUninit<'a, T> where @@ -509,6 +523,195 @@ impl RuntimeBuilderImpl { } } +/// Field subscriber type which implements the FieldSubscriber trait for Mock runtime. +pub struct MockFieldSubscriber { + identifier: &'static str, + instance_info: MockConsumerInfo, + _data: PhantomData, +} + +/// Marker implementation of FieldSubscriber trait. +impl FieldSubscriber for MockFieldSubscriber {} + +/// Implementation of Subscriber trait for MockFieldSubscriber. +impl Subscriber for MockFieldSubscriber { + type Subscription = MockFieldSubscription; + + fn new(identifier: &'static str, instance_info: MockConsumerInfo) -> Result { + Ok(Self { + identifier, + instance_info, + _data: PhantomData, + }) + } + + fn subscribe(self, _max_num_samples: usize) -> Result { + Ok(MockFieldSubscription { + identifier: self.identifier, + instance_info: self.instance_info, + _data: PhantomData, + }) + } +} + +/// FieldSubscription type which provides data receiving APIs and unsubscribe method. +pub struct MockFieldSubscription { + identifier: &'static str, + instance_info: MockConsumerInfo, + _data: PhantomData, +} + +impl FieldSubscription for MockFieldSubscription { + fn get_free_sample_count(&self) -> Result { + todo!() + } + + fn get_num_new_samples_available(&self) -> Result { + todo!() + } +} + +/// Implementation of Subscription trait which provides receiving APIs. +impl Subscription for MockFieldSubscription { + type Subscriber = MockFieldSubscriber; + type Sample<'a> + = Sample<'a, T> + where + Self: 'a; + + fn unsubscribe(self) -> Self::Subscriber { + MockFieldSubscriber { + identifier: self.identifier, + instance_info: self.instance_info, + _data: PhantomData, + } + } + + fn try_receive<'a>( + &'a self, + _scratch: &'_ mut SampleContainer>, + _max_samples: usize, + ) -> Result { + todo!() + } + + fn cancellable_receive<'a>( + &'a self, + _scratch: SampleContainer>, + _new_samples: usize, + _max_samples: usize, + _cancellation: impl Future + Send + 'static, + ) -> impl Future>, Result)> + 'a { + async { todo!() } + } + + fn to_stream<'a>(&'a mut self) -> impl Stream>> + Unpin + 'a { + stream::empty() + } +} + +/// Field publisher type for Mock runtime. +pub struct MockFieldPublisher { + _data: PhantomData, +} + +impl Clone for MockFieldPublisher { + fn clone(&self) -> Self { + MockFieldPublisher { _data: PhantomData } + } +} + +impl Copy for MockFieldPublisher {} + +/// Field sample mutable type. +#[derive(Debug)] +pub struct MockFieldSampleMut<'a, T: CommData + Debug> { + data: T, + _lifetime: PhantomData<&'a T>, +} + +impl<'a, T: CommData + Debug> Deref for MockFieldSampleMut<'a, T> { + type Target = T; + fn deref(&self) -> &T { + &self.data + } +} + +impl<'a, T: CommData + Debug> DerefMut for MockFieldSampleMut<'a, T> { + fn deref_mut(&mut self) -> &mut T { + &mut self.data + } +} + +impl<'a, T: CommData + Debug> com_api_concept::SampleMut for MockFieldSampleMut<'a, T> {} + +impl<'a, T: CommData + Debug> FieldSampleMut for MockFieldSampleMut<'a, T> { + fn update(self) -> Result<()> { + todo!() + } +} + +/// Field sample maybe uninit type. +#[derive(Debug)] +pub struct MockFieldSampleMaybeUninit<'a, T: CommData + Debug> { + data: MaybeUninit, + _lifetime: PhantomData<&'a T>, +} + +impl<'a, T: CommData + Debug> AsMut> for MockFieldSampleMaybeUninit<'a, T> { + fn as_mut(&mut self) -> &mut MaybeUninit { + &mut self.data + } +} + +impl<'a, T: CommData + Debug> SampleMaybeUninitTrait for MockFieldSampleMaybeUninit<'a, T> { + type SampleMut = MockFieldSampleMut<'a, T>; + + unsafe fn assume_init(self) -> MockFieldSampleMut<'a, T> { + MockFieldSampleMut { + data: unsafe { self.data.assume_init() }, + _lifetime: PhantomData, + } + } + + fn write(self, value: T) -> MockFieldSampleMut<'a, T> { + MockFieldSampleMut { + data: value, + _lifetime: PhantomData, + } + } +} + +impl FieldPublisher for MockFieldPublisher { + type FieldSample<'a> + = MockFieldSampleMut<'a, T> + where + Self: 'a; + type SampleMaybeUninit<'a> + = MockFieldSampleMaybeUninit<'a, T> + where + Self: 'a; + + fn new(_identifier: &str, _instance_info: MockProviderInfo) -> Result { + Ok(Self { _data: PhantomData }) + } + + fn allocate(&self) -> Result> { + Ok(MockFieldSampleMaybeUninit { + data: MaybeUninit::uninit(), + _lifetime: PhantomData, + }) + } + + fn update(&self, _value: &T) -> Result<()> { + todo!() + } + + fn register_set_handler<'a>(&self) -> impl Future> + 'a { + async { todo!() } + } +} + #[cfg(test)] mod test { use com_api_concept::{Publisher, SampleContainer, SampleMaybeUninit, SampleMut, Subscription}; diff --git a/score/mw/com/impl/rust/com-api/com-api/BUILD b/score/mw/com/impl/rust/com-api/com-api/BUILD index e7c57f55f..4587c7688 100644 --- a/score/mw/com/impl/rust/com-api/com-api/BUILD +++ b/score/mw/com/impl/rust/com-api/com-api/BUILD @@ -25,6 +25,6 @@ rust_library( deps = [ "//score/mw/com/impl/rust/com-api/com-api-concept", "//score/mw/com/impl/rust/com-api/com-api-runtime-lola", - "//score/mw/com/impl/rust/com-api/com-api-runtime-mock", + #"//score/mw/com/impl/rust/com-api/com-api-runtime-mock", ], ) diff --git a/score/mw/com/impl/rust/com-api/com-api/com_api.rs b/score/mw/com/impl/rust/com-api/com-api/com_api.rs index 66ec17bed..c88a392e8 100644 --- a/score/mw/com/impl/rust/com-api/com-api/com_api.rs +++ b/score/mw/com/impl/rust/com-api/com-api/com_api.rs @@ -115,17 +115,17 @@ //! //! // Or wait asynchronously for events //! let (container, result) = subscription.receive(container, 1, 3).await; -//! +//! //! // Or use the cancelable future to wait for events with timeout //! let timeout_future = tokio::time::sleep(std::time::Duration::from_secs(1)); //! let (container, result) = subscription.cancellable_receive(container, 1, 3, timeout_future).await; -//! +//! //! // Or use the stream interface to process events as they arrive //! let mut stream = subscription.to_stream(); //! while let Some(sample) = stream.next().await { //! println!("Tire pressure: {}", sample.pressure); //! } -//! +//! //! ``` //! # Further reading //! - `com_api_concept` crate — trait definitions and full API documentation @@ -133,15 +133,18 @@ 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; +//TODO: This is just commented for now, we will enable it before PR open for review. We need to implement the MockRuntimeImpl and MockRuntimeBuilderImpl for the mock runtime. +// pub use com_api_runtime_mock::MockRuntimeImpl; +// pub use com_api_runtime_mock::RuntimeBuilderImpl as MockRuntimeBuilderImpl; pub use com_api_concept::{ interface, interface_common, interface_consumer, interface_producer, Builder, CommData, - Consumer, ConsumerBuilder, ConsumerDescriptor, Error, FindServiceSpecifier, InstanceSpecifier, - Interface, OfferedProducer, PlacementDefault, Producer, ProducerBuilder, ProviderInfo, - Publisher, Reloc, Result, Runtime, RuntimeBuilder, SampleContainer, SampleMaybeUninit, - SampleMut, ServiceDiscovery, Subscriber, Subscription, + Consumer, ConsumerBuilder, ConsumerDescriptor, Error, EventSampleMut as SampleMut, + FieldPublisher, FieldSampleMut, FieldSubscriber, FieldSubscription, FindServiceSpecifier, + HandlerNotSet, HandlerSet, Init, InstanceSpecifier, Interface, MethodReturnTypePtr, + OfferedProducer, PlacementDefault, Producer, ProducerBuilder, ProviderInfo, Publisher, Reloc, + Result, Runtime, RuntimeBuilder, SampleContainer, SampleMaybeUninit, ServiceDiscovery, + Subscriber, Subscription, Uninit, }; #[doc(hidden)]