New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Iox 36 untyped api #58
Conversation
examples/publisher_untyped.rs
Outdated
let mut sample = publisher.loan(std::mem::size_of::<u32>(), std::mem::align_of::<u32>())?; | ||
sample.as_mut().put_u32(counter); | ||
publisher.publish(sample); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@neilisaac does this fit your use case?
I think I will introduce a loan_uninitialized
and let loan
zero the memory.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, this looks great!
For reference, what I had was:
pub struct PubChunkHandle<'a> {
data: *mut ffi::c_void,
publisher: &'a Publisher,
sent: bool, // used in Drop
size: usize,
}
impl PubChunkHandle<'_> {
/// Send the data via the associated Publisher
pub fn publish(mut self) {
unsafe { sys::iox_pub_publish_chunk(self.publisher.0, self.data) };
self.sent = true;
}
/// Safe interface to write into a shared memory chunk.
pub fn as_uninit_slice(&mut self) -> &mut UninitSlice {
unsafe { UninitSlice::from_raw_parts_mut(self.data.cast(), self.size) }
}
/// Expose the payload portion of the chunk as a mutable byte slice.
/// This can be written to conveniently using bytes::BufMut.
/// # Safety
/// Despite this function not being marked as unsafe, the data returned by as_slice is uninitialized.
pub fn as_slice(&mut self) -> &mut [u8] {
unsafe { std::slice::from_raw_parts_mut(self.data.cast(), self.size) }
}
/// Cast the chunk to a given type T.
/// This may be useful with a #[repr(C)] type for example.
/// # Safety
/// This function is marked as unsafe because it is the caller's responsibility
/// to ensure that T can safely be written into the chunk.
/// T must fit in the allocated chunk and must not reference data outside the chunk (ex. via pointers/references)
pub unsafe fn as_uninitialized<T>(&mut self) -> &mut std::mem::MaybeUninit<T> {
&mut *self.data.cast()
}
}
The main design difference there I think is the PubChunkHandle borrows the Publisher, but otherwise very similar.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice. Currently I plan to have this methods
pub fn loan_zeroed_slice(&self, len: usize) -> Result<SampleMut<[u8]>, IceoryxError> {
unimplemented!()
}
pub fn loan_aligned_zeroed_slice(&self, len: usize, alignment: usize) -> Result<SampleMut<[u8]>, IceoryxError> {
unimplemented!()
}
pub fn loan_uninit_slice(&self, len: usize) -> Result<SampleMut<[MaybeUninit<u8>]>, IceoryxError> {
unimplemented!()
}
pub fn loan_aligned_uninit_slice(&self, len: usize, alignment: usize) -> Result<SampleMut<[MaybeUninit<u8>]>, IceoryxError> {
unimplemented!()
}
I have to check the ergonomics with [MaybeUninit<u8>]
. Maybe this needs to be done in a different way.
It would also be possible to not restrict the API to [u8]
slices but to allow all types with T: ShmSend
. With this one can have dynamic data at runtime. At least the most simple one.
@elfenpiff what do you think about having a specialization Sample<cxx::span<T>>
for the typed C++ API?
fdd839e
to
3b8f145
Compare
@@ -10,15 +10,15 @@ use crate::IceoryxError; | |||
use std::marker::PhantomData; | |||
use std::mem::MaybeUninit; | |||
|
|||
pub struct PublisherBuilder<'a, T: ShmSend> { | |||
pub struct PublisherBuilder<'a, T: ShmSend + ?Sized> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
is ?Sized accepted in a trait declaration ex. pub unsafe trait ShmSend: ?Sized {}
, or is it special because it's an anti-constraint?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
traits are ?Sized
by default and one would have to set Sized
as trait bounds. But since I want to have [u8]
as type I cannot do it.
examples/publisher_untyped.rs
Outdated
let mut sample = publisher.loan(std::mem::size_of::<u32>(), std::mem::align_of::<u32>())?; | ||
sample.as_mut().put_u32(counter); | ||
publisher.publish(sample); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, this looks great!
For reference, what I had was:
pub struct PubChunkHandle<'a> {
data: *mut ffi::c_void,
publisher: &'a Publisher,
sent: bool, // used in Drop
size: usize,
}
impl PubChunkHandle<'_> {
/// Send the data via the associated Publisher
pub fn publish(mut self) {
unsafe { sys::iox_pub_publish_chunk(self.publisher.0, self.data) };
self.sent = true;
}
/// Safe interface to write into a shared memory chunk.
pub fn as_uninit_slice(&mut self) -> &mut UninitSlice {
unsafe { UninitSlice::from_raw_parts_mut(self.data.cast(), self.size) }
}
/// Expose the payload portion of the chunk as a mutable byte slice.
/// This can be written to conveniently using bytes::BufMut.
/// # Safety
/// Despite this function not being marked as unsafe, the data returned by as_slice is uninitialized.
pub fn as_slice(&mut self) -> &mut [u8] {
unsafe { std::slice::from_raw_parts_mut(self.data.cast(), self.size) }
}
/// Cast the chunk to a given type T.
/// This may be useful with a #[repr(C)] type for example.
/// # Safety
/// This function is marked as unsafe because it is the caller's responsibility
/// to ensure that T can safely be written into the chunk.
/// T must fit in the allocated chunk and must not reference data outside the chunk (ex. via pointers/references)
pub unsafe fn as_uninitialized<T>(&mut self) -> &mut std::mem::MaybeUninit<T> {
&mut *self.data.cast()
}
}
The main design difference there I think is the PubChunkHandle borrows the Publisher, but otherwise very similar.
213b49d
to
9050861
Compare
Codecov Report
@@ Coverage Diff @@
## master #58 +/- ##
==========================================
+ Coverage 51.29% 55.02% +3.73%
==========================================
Files 19 20 +1
Lines 774 945 +171
==========================================
+ Hits 397 520 +123
- Misses 377 425 +48
Continue to review full report at Codecov.
|
9050861
to
83386f4
Compare
examples/publisher_untyped.rs
Outdated
let sample = if counter % 2 == 1 { | ||
// with initialized slice | ||
let mut sample = publisher.loan_slice(std::mem::size_of::<u32>())?; | ||
sample.as_mut().put_u32(counter); | ||
sample | ||
} else { | ||
// with uninitialized slice | ||
let sample = publisher.loan_uninit_slice(std::mem::size_of::<u32>())?; | ||
unsafe { | ||
let mut sample = sample.assume_init(); | ||
sample.as_mut().put_u32(counter); | ||
sample | ||
} | ||
}; | ||
publisher.publish(sample); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@neilisaac what is your opinion? Maybe a bit awkward to call assume_init
before the buffer is written but not that bad.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
An alternative would be
let sample = publisher.loan_uninit_slice(std::mem::size_of::<u32>())?;
let sample = unsafe {
sample.slice_assume_init_mut().put_u32(counter);
sample.assume_init()
}
publisher.publish(sample);
83386f4
to
a0f12f4
Compare
examples/publisher_untyped.rs
Outdated
let sample = if counter % 2 == 1 { | ||
// with initialized slice | ||
let mut sample = publisher.loan_slice_with_alignment( | ||
std::mem::size_of::<u32>(), | ||
std::mem::align_of::<u32>(), | ||
)?; | ||
sample.as_mut().put_u32_le(counter); | ||
sample | ||
} else { | ||
// with uninitialized slice | ||
let mut sample = publisher.loan_uninit_slice_with_alignment( | ||
std::mem::size_of::<u32>(), | ||
std::mem::align_of::<u32>(), | ||
)?; | ||
unsafe { | ||
sample.slice_assume_init_mut().put_u32_le(counter); | ||
sample.assume_init() | ||
} | ||
}; | ||
publisher.publish(sample); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@neilisaac if there are no objections from you, I'll go with this API for the publisher
examples/subscriber_untyped.rs
Outdated
let (subscriber, sample_receive_token) = | ||
SubscriberBuilder::<[u8]>::new("Radar", "FrontLeft", "Counter") | ||
.queue_capacity(5) | ||
.create()?; | ||
|
||
let sample_receiver = subscriber.get_sample_receiver(sample_receive_token); | ||
|
||
loop { | ||
if sample_receiver.has_data() { | ||
while let Some(sample) = sample_receiver.take() { | ||
println!("Receiving: {}", sample.as_ref().get_u32_le()); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
and with this for the subscriber
examples/publisher_untyped.rs
Outdated
)?; | ||
unsafe { | ||
sample.slice_assume_init_mut().put_u32_le(counter); | ||
sample.assume_init() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
loan_slice_with_alignment seems sensible.
loan_uninit_slice_with_alignment doesn't seem to really provide a significant distinction here compared to loan_slice_with_alignment, since loan_slice_with_alignment returns an uninitialized slice without requiring unsafe handling.
The way I did it was also a little loose about initialization safety since it's always up to the user to populate the buffer with something meaningful before publishing regardless of how it's abstracted.
The publisher could loan a PubChunkHandle which owns the data and exposes different views for convenience (depending on how the user wants to write into the buffer). Regardless of which method is used, the PubChunkHandle owns the data and can publish it at will.
impl Publisher {
pub fn loan_chunk(&self, size: usize) -> Result<PubChunkHandle, AllocationError>
}
pub struct PubChunkHandle<'a> {
data: *mut ffi::c_void,
publisher: &'a Publisher,
sent: bool,
size: usize,
}
impl PubChunkHandle<'_> {
pub fn publish(mut self)
pub fn as_uninit_slice(&mut self) -> &mut UninitSlice
pub fn as_slice(&mut self) -> &mut [u8]
pub unsafe fn as_uninitialized<T>(&mut self) -> &mut std::mem::MaybeUninit<T>
}
The last one is probably a bit sketchy since the user data could probably overflow, and it's easy to accidentally use a type the contains pointers.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@neilisaac I think you overlooked the .fill_with(|| T::default());
in loan_slice_with_alignment
which initializes the slice. loan_uninit_slice_with_alignment
doesn't do this but is unsafe. I tried to match the proposed API for uninitialized constructors of Box, Rc, and Arc.
I like the idea to expose a different view and will add this for [u8]
unsafe fn try_as_uninit<T: ShmSend>(&mut self) -> Option<&mut MaybeUninit<T>>
This would return None
if the size and alignment do not match the requirements for T
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@neilisaac I added the convenience functions and if you do not have any concerns, I would merge the PR
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
API looks good to me!
@neilisaac I'm currently implementing the reactor pattern for the WaitSet/Listener. If you are interested, I can ping you once I have a draft PR. |
Pre-Review Checklist for the PR Author
rustfmt
iox-123-this-is-a-branch
)iox-#42 commit text
)task-list-completed
)Notes for Reviewer
Checklist for the PR Reviewer
Post-review Checklist for the PR Author
References