-
Notifications
You must be signed in to change notification settings - Fork 20
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
PacketBuffer rewrite #9
Conversation
src/lib.rs
Outdated
|
||
|
||
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))] | ||
type AlignmentMarker = [u8; 0]; // unaligned |
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.
What about other architectures than arm or x86 ? would it be possible to have the unaligned case for all the non arm-aarch64 architectures ?
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, that would be possible. I'm just not sure wheter "unaligned" is the right default, so I did it in such a way that it would error on other architectures. Is macOS/iOS even available for other architectures?
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 macOS/iOS even available for other architectures?
Good point. I guess that failing in case of unknown architecture is an acceptable solution here.
src/lib.rs
Outdated
// pointing to valid instances of MIDIPacketList. | ||
// This type must NOT implement `Copy`! | ||
inner: PacketListInner, | ||
_do_not_construct: AlignmentMarker |
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 _do_not_construct
a convention name for such cases ? If not, could you rename to something like _alignment_marker
?
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.
It's not a convention. I just did this to solve two problems at once, namely (1) to have the correct alignment and (2) to give a strong hint that any code that would try to construct an instance of this type would be wrong (but this is only relevant within the library, not for users of the library). I can rename if you prefer.
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.
Not feeling strong about this renaming, but given that it is not exposed to the public through the API, (1) is the main point to hint, so the proposed name looks more intuitive to me. You can focus on other comments first, and if having enough time do this ;-)
src/lib.rs
Outdated
#[repr(packed)] | ||
struct PacketListInner { | ||
num_packets: u32, | ||
data: [MIDIPacket; 0] |
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.
I am wondering if the differentiation between PacketList
and PacketListInner
is really necessary. Couldn't we just put the alignment marker after the data
field and rename PacketListInner
to PacketList
?
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, PacketListInner
is necessary, because of a lack of the #[repr(packed = "N")]
feature in Rust (see rust-lang/rust#33158). We want an alignment of 4 on ARM and 1 on other architectures, but #[repr(packed)]
always gives an alignment of 1. So I'm defining PacketListInner
with an alignment of 1, and using that within PacketList
together with AlignmentMarker
, so PacketList
will choose the alignment as max(alignof(PacketListInner), alignof(AlignmentMarker))
, which results in the correct alignment.
src/packets.rs
Outdated
// This type must NOT implement `Copy`! | ||
// On ARM, this must be 4-byte aligned. | ||
inner: PacketInner, | ||
_do_not_construct: AlignmentMarker |
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.
Same comments as with PacketList
/PacketListInner
Instead of storing a pointer, use references to PacketType directly, and disallow the creation of PacketTpye values.
Main improvement is that small packet lists are allocated directly on the stack. When adding packets to a list, it is now also possible for them to be merged with previous packets (same behaviour as MIDIPacketListAdd).
This is consistent with the naming in Rust's standard library.
f915b06
to
f72086a
Compare
The merge commit that you pushed introduced a failure. I force-pushed a rebased version with the fix (let's see if Travis agrees that it's correct 😉 ). And thanks for the review! |
|
||
/// Call this only with larger length values (won't make the buffer smaller) | ||
unsafe fn set_len(&mut self, new_length: usize) { | ||
if new_length < INLINE_PACKET_BUFFER_SIZE { return; } |
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.
If it is an External
case it could have a length shorter than INLINE_PACKET_BUFFER_SIZE
, right ?
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.
The way I've implemented it, the External
case will only ever be created with sizes larger than INLINE_PACKET_BUFFER_SIZE
(see the implementation of new
).
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.
I should probably document this somewhere ...
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.
I see.
src/packets.rs
Outdated
fn last_written_packet(&self) -> &Packet { | ||
// NOTE: This requires that there always is at least one packet in the buffer | ||
// (which is okay because we do not provide an empty constructor) | ||
unsafe { &*(self.storage.get_slice()[self.last_written_pkt_offset..].as_ptr() as *const Packet) } |
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.
Could you please avoid the one-liner here and use intermediary vars to make it more readable ?
src/packets.rs
Outdated
pkt.timeStamp = timestamp as MIDITimeStamp; | ||
pkt.length = data_len as UInt16; | ||
pkt.data[0..data_len].clone_from_slice(&data); | ||
pub fn push_packet(&mut self, time: MIDITimeStamp, data: &[u8]) -> &mut Self { |
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.
Does data
represent a Packet
or a list of MIDI bytes ? The name push_packet
is confusing. I'd rename it to push_data
to make this differentiation clear.
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.
Following the same logic I would also rename assign_packet
and extend_packet
for the PacketBufferStorage
.
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.
assign_packet
assigns data to a single MIDIPacket
, extend_packet
extends the data inside a single MIDIPacket
, so I think that these names are okay. You are right that push_packet
either creates a new packet or extends an existing one, so push_data
is probably better. I will rename it.
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.
I now remember why I didn't call it push_data
from the beginning: It is required that the data that is being pushed in a single call of this function actually is a valid packet and not arbitrary MIDI bytes. E.g. it is not allowed to push a NOTE ON followed by a SYSEX event in a single "packet". Also see the original documentation of MIDIPacketListAdd. There, a "packet" is called an "event", so we could also call this push_event
. I think I still prefer push_data
now, under the condition that the documentation makes it clear what is allowed as data passed to the function.
src/packets.rs
Outdated
previous_packet.data()[0] != 0xF0 && // previous packet not a sysex | ||
previous_packet.data()[0] & 0b10000000 != 0 && // but first byte is a status byte | ||
previous_data_len + data.len() < MAX_PACKET_DATA_LENGTH; // enough space left in the packet | ||
} |
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.
Could this block be extracted into a private function ?
let (can_merge, previous_data_len) = check_mergeable(...)
src/packets.rs
Outdated
fn get_next_offset(&self) -> usize { | ||
let length = self.last_written_packet().inner.length as usize; | ||
let next_unadjusted = self.last_written_pkt_offset + PACKET_HEADER_SIZE + length; | ||
if cfg!(any(target_arch = "arm", target_arch = "aarch64")) { |
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.
I've seen this check several times by now. If by any chance we need to cover a new architecture that requires u32 alignment, we will need to change several of those checks which is prone to error. Do you think it could be extracted into a macro and used all around ?
if align_u32!() { ... }
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.
I basically copied the logic from here. Trying to think about a way to extract this that works at the expression level (i.e. if cfg!(...) { ... }
) and also on the item definition level (i.e. #[cfg(...)]
), I can only come up with this:
#[cfg(any(target_arch = "arm", target_arch = "aarch64"))]
mod alignment {
type AlignmentMarker = [u32; 0];
const NEEDS_ALIGNMENT: bool = true;
}
#[cfg(not(any(target_arch = "arm", target_arch = "aarch64")))]
mod alignment {
type AlignmentMarker = [u8; 0];
const NEEDS_ALIGNMENT: bool = false;
}
... and then using if alignment::NEEDS_ALIGNMENT { ... }
Would that be okay?
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.
That's a very interesting approach. I like it.
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.
@Boddlnagg I really appreciate the effort you have put on this improvement. I finally had some time to review and learn from it. I added some more comments.
I really like you updated to coremidi-sys
2.0, I didn't realised of the new release. And also the approach of using an enum for the storage to cover inline and external cases.
Given that it touches some code that was contributed by @raphlinus previously, I would like to give him some time to review too (or decline if he is too busy ;-).
@chris-zen Thanks for the review! I will push a commit with the changes that you requested (is it okay if I just put them all in a single commit?). Actually, I am responsible for the 2.0 version of |
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.
I had a quick look, and everything looks good to me. I haven't had a chance to test it with my synthesizer though.
Glad to see interest in this type of thing picking up!
I pushed a commit addressing the review comments |
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.
Thank you very much ! Great work ;-)
Thanks! Do you plan to release a new version soon? Then I will be able to update midir :-) |
Done. Released 0.3.0 |
This adresses the points discussed in #5. Details:
&[u8]
to not require the caller to allocate aVec
PacketBuffer
type.new()
constructor has been removed, because having the buffer always contain at least one package allows some optimizations and I can't see reason for ever needing an emptyPacketBuffer
.new
now has the signature offrom_data
instead.with_data
topush_data
, because I think of it more as similar to theVec
API instead of a builder API.push_data
still returns&mut self
, so it can be chained. If you want me to undo these API changes, I can do that.PacketList::length()
tolen()
to make it consistent with Rust's standard library (all the methods to get the length of something are calledlen()
, see https://static.rust-lang.org/doc/master/std/index.html?search=length)push_data
. This matches the behavior ofMIDIPacketListAdd
, and there are unit tests to ensure this (by comparing the result of usingMIDIPacketListAdd
vs.PacketBuffer
).This PR also includes an upgrade to version 2.0 of coremidi-sys (the first two commits), getting rid of
coremidi_sys_ext
. If you want to have these as a seperate PR, I can do that, too.