Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions score/mw/com/example/com-api-example/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -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",
],
)

Expand Down
168 changes: 163 additions & 5 deletions score/mw/com/example/com-api-example/basic-consumer-producer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -45,6 +47,13 @@ type VehicleConsumer<R> = <VehicleInterface as Interface>::Consumer<R>;
// VehicleOfferedProducer is the offered producer type generated for the Vehicle interface, parameterized by the runtime R
type VehicleOfferedProducer<R> =
<<VehicleInterface as Interface>::Producer<R> as Producer<R>>::OfferedProducer;
// VehicleFieldProducer is the producer type for the VehicleField interface (before offering)
type VehicleFieldProducer<R> = <VehicleFieldInterface as Interface>::Producer<R>;
// VehicleFieldOfferedProducer is the offered producer type for the VehicleField interface (fields support update/set-handler)
type VehicleFieldOfferedProducer<R> =
<<VehicleFieldInterface as Interface>::Producer<R> as Producer<R>>::OfferedProducer;

type VehicleFieldConsumer<R> = <VehicleFieldInterface as Interface>::Consumer<R>;

// Example struct demonstrating composition with VehicleConsumer
pub struct VehicleMonitor<R: Runtime> {
Expand Down Expand Up @@ -179,6 +188,155 @@ fn create_producer<R: Runtime>(
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<R: Runtime + 'static>(
runtime: &R,
service_id: InstanceSpecifier,
initial_tire_value: Tire,
initial_exhaust_value: Exhaust,
) -> VehicleFieldOfferedProducer<R>
where
<R as Runtime>::FieldPublisher<Tire>: Send + Sync,
<R as Runtime>::FieldPublisher<Exhaust>: Send,
{
let producer_builder = runtime.producer_builder::<VehicleFieldInterface>(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<R: Runtime>(offered_producer: VehicleFieldOfferedProducer<R>) {
// 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<R: Runtime>(
runtime: &R,
service_id: InstanceSpecifier,
) -> VehicleFieldConsumer<R> {
let consumer_discovery =
runtime.find_service::<VehicleFieldInterface>(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<S, R>(subscription: S)
where
S: FieldSubscription<Tire, R>,
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<R: Runtime + 'static>(consumer: VehicleFieldConsumer<R>)
where
<<R as Runtime>::FieldSubscriber<Tire> as Subscriber<Tire, R>>::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<R: Runtime>(name: &str, runtime: &R) {
println!("\n=== Running with {name} runtime ===");
Expand Down
18 changes: 15 additions & 3 deletions score/mw/com/example/com-api-example/com-api-gen/com_api_gen.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {}
Expand All @@ -39,10 +39,22 @@ pub struct Exhaust {}
// "exhaust" events.
// - VehicleOfferedProducer<R> struct that implements OfferedProducer trait for offering
// "left_tire" and "exhaust" events.
//Let's remove
interface!(
interface Vehicle, {
Id = "VehicleInterface",
left_tire: Event<Tire>,
exhaust: Event<Exhaust>,
}
);

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

macro need to be optimize.

// 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!(

@bharatGoswami8 bharatGoswami8 Jul 9, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We need thread pool count for each field which can be part of macro.

left_tire : Field<Tire, Count>

interface VehicleField, {
Id = "VehicleFieldInterface",
left_tire: Field<Tire>,

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

left_tire : Field<Tire, Initial_value, NotifyOnSet>

exhaust: Field<Exhaust>,
}
);
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
34 changes: 34 additions & 0 deletions score/mw/com/impl/rust/com-api/com-api-concept-macros/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -335,6 +338,37 @@ fn collect_field_types(data: &Data) -> Result<Vec<&Type>, ()> {
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<R: Runtime> {
/// #[field_name = "left_tire"]
/// left_tire_field: R::FieldPublisher<Tire>,
/// #[field_name = "exhaust"]
/// exhaust_field: R::FieldPublisher<Exhaust>,
/// }
/// ```
///
/// 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

/// ```
Expand Down
Loading
Loading