Skip to content
This repository was archived by the owner on Mar 10, 2026. It is now read-only.
Open
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
6 changes: 4 additions & 2 deletions com-api-example/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@ fn main() {
#[cfg(test)]
mod test {
use com_api::{
Builder, ConsumerDescriptor, InstanceSpecifier, Producer, SampleContainer,
SampleMaybeUninit, SampleMut, ServiceDiscovery, Subscriber, Subscription,
Builder, ConsumerDescriptor, InstanceSpecifier, OfferedProducer, Producer, Publisher,
SampleContainer, SampleMaybeUninit, SampleMut, ServiceDiscovery, Subscriber, Subscription,
};
use com_api_sample_gen::{Tire, VehicleInterface};

Expand All @@ -34,6 +34,8 @@ mod test {
let uninit_sample = offered_producer.left_tire.allocate().unwrap();
let sample = uninit_sample.write(Tire {});
sample.send().unwrap();

offered_producer.unoffer();
}

#[test]
Expand Down
40 changes: 31 additions & 9 deletions com-api-sample-runtime/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,7 @@ where
T: Reloc,
{
data: T,
_lifetime: PhantomData<&'a T>,
lifetime: PhantomData<&'a T>,
}

impl<'a, T> com_api::SampleMut<T> for SampleMut<'a, T>
Expand Down Expand Up @@ -187,7 +187,7 @@ where
T: Reloc + Send,
{
data: MaybeUninit<T>,
_lifetime: PhantomData<&'a T>,
lifetime: PhantomData<&'a T>,
}

impl<'a, T> com_api::SampleMaybeUninit<T> for SampleMaybeUninit<'a, T>
Expand All @@ -199,25 +199,29 @@ where
fn write(self, val: T) -> SampleMut<'a, T> {
SampleMut {
data: val,
_lifetime: PhantomData,
lifetime: PhantomData,
}
}

fn as_mut_ptr(&mut self) -> *mut T {
self.data.as_mut_ptr()
}

unsafe fn assume_init(self) -> SampleMut<'a, T> {
SampleMut {
data: unsafe { self.data.assume_init() },
_lifetime: PhantomData,
lifetime: PhantomData,
}
}
}

pub struct SubscribableImpl<T> {
_data: PhantomData<T>,
data: PhantomData<T>,
}

impl<T> Default for SubscribableImpl<T> {
fn default() -> Self {
Self { _data: PhantomData }
Self { data: PhantomData }
}
}

Expand Down Expand Up @@ -305,11 +309,21 @@ where
pub fn new() -> Self {
Self { _data: PhantomData }
}
}

pub fn allocate<'a>(&'a self) -> com_api::Result<SampleMaybeUninit<'a, T>> {
impl<T> com_api::Publisher<T> for Publisher<T>
where
T: Reloc + Send,
{
type SampleMaybeUninit<'a>
= SampleMaybeUninit<'a, T>
where
Self: 'a;

fn allocate<'a>(&'a self) -> com_api::Result<Self::SampleMaybeUninit<'a>> {
Ok(SampleMaybeUninit {
data: MaybeUninit::uninit(),
_lifetime: PhantomData,
lifetime: PhantomData,
})
}
}
Expand Down Expand Up @@ -405,7 +419,7 @@ impl RuntimeBuilderImpl {

#[cfg(test)]
mod test {
use com_api::{SampleContainer, Subscription};
use com_api::{Publisher, SampleContainer, SampleMaybeUninit, SampleMut, Subscription};

#[test]
fn receive_stuff() {
Expand Down Expand Up @@ -446,4 +460,12 @@ mod test {
}
})
}

#[test]
fn send_stuff() {
let test_publisher = super::Publisher::<u32>::new();
let sample = test_publisher.allocate().expect("Couldn't allocate sample");
let sample = sample.write(42);
sample.send().expect("Send failed for sample");
}
}
36 changes: 28 additions & 8 deletions com-api/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -125,14 +125,12 @@ where
fn send(self) -> Result<()>;
}

/// A `SampleMaybeUninit` provides a reference to a memory buffer of an event with a `MaybeUninit` value.
/// A `SampleMaybeUninit` provides a reference to a memory buffer of an event whose data hasn't been
/// initialized yet.
///
/// Utilizing `DerefMut` on the buffer reveals a reference to the internal `MaybeUninit<T>`.
/// The buffer can be assumed initialized with mutable access by calling `assume_init` which returns a `SampleMut`.
/// The buffer can be assumed initialized with mutable access by calling `assume_init` which returns
/// a `SampleMut`.
/// The buffers with its data lives as long as there are references to it existing in the framework.
///
/// TODO: Shall we also require DerefMut<Target=MaybeUninit<T>> from implementing types? How to deal
/// TODO: with the ambiguous assume_init() then?
pub trait SampleMaybeUninit<T>
where
T: Send + Reloc,
Expand All @@ -145,12 +143,16 @@ where
/// This corresponds to `MaybeUninit::write`.
fn write(self, value: T) -> Self::SampleMut;

/// Get a mutable pointer to the internal maybe uninitialized `T`.
///
/// The caller has to make sure to initialize the data in the buffer.
/// Reading from the received pointer before initialization is undefined behavior.
fn as_mut_ptr(&mut self) -> *mut T;

Choose a reason for hiding this comment

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

Which exact problem we are solving with this ? This is quite risky API since beside description, we hope user will know T may not be read really (and since as_mut_ptr is common in Rust codebases, it may be easy to miss-spot in review ).

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This enables initializing the sample pointer member-by-member. It becomes important in cases where you want your produce to put the result directly into the shared memory area without first having to initialize a local instance of the type T.

This becomes important when we're talking about really big data structures. In this case, you might either not have (or want to spend) the memory to build the type internally, and copying itself also may take a non-negligible amount of time. In order to safe this, one might want to create an uninitialized sample, use this method to obtain a pointer, and then initialize the type piece by piece by write-only accesses. Since we hand back a pointer, this method can even be safe since working with a pointer is unsafe anyway.

Choose a reason for hiding this comment

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

Yes this is placement new missing in API. So I would say we have two use case

  • default placement init for large types
  • non default placement init for large types (ie data comming from some other entity ie, hw or cpp code)

For the first I would try standarize approach ie:

pub trait PlacementDef {
    unsafe fn placement_def(ptr: *mut Self);
}

and then in SampleMutUninit

fn write_in_place<T: PlacementDef>(&mut self) {
    unsafe {
        T::placement_def(ptr);
    }
}

For the second, since it mimics MaybeUnint api with as_mut_ptr, it shall be fine. But maybe, the SampleMutUninit<T> shall really hold(mandate) MaybeUninit<T> instead and our api shall return ref to it ie, fn data(&mut self) -> &MaybeUninit<T>. From this point, user will get other abilities.


/// Render the buffer initialized for mutable access.
///
/// This corresponds to `MaybeUninit::assume_init`.
///
/// TODO: Collision with MaybeUninit::assume_init() needs to be resolved.
///
/// # Safety
///
/// The caller has to make sure to initialize the data in the buffer before calling this method.
Expand All @@ -159,6 +161,7 @@ where

pub trait Interface {}

#[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<Interface = Self::Interface>;
Expand All @@ -173,6 +176,23 @@ pub trait Producer {
fn offer(self) -> Result<Self::OfferedProducer>;
}

pub trait Publisher<T>
where
T: Reloc + Send,
{
type SampleMaybeUninit<'a>: SampleMaybeUninit<T> + 'a
where
Self: 'a;

fn allocate<'a>(&'a self) -> Result<Self::SampleMaybeUninit<'a>>;

fn send(&self, value: T) -> Result<()> {
let sample = self.allocate()?;
let init_sample = sample.write(value);
init_sample.send()
}
}

pub trait Consumer {}

pub trait ProducerBuilder<I: Interface, R: Runtime, P: Producer<Interface = I>>:
Expand Down