Skip to content

Commit 5fc628e

Browse files
committed
Add Arena for ergonomics
Makes track construction more ergonomic. Also added new functions for working with `u7` slices.
1 parent 39e6e91 commit 5fc628e

File tree

6 files changed

+241
-12
lines changed

6 files changed

+241
-12
lines changed

CHANGELOG.md

+3
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,9 @@
2222
- Added a `live` module that allows parsing raw MIDI event bytes.
2323
- Added a `stream` module to support raw MIDI stream decoding.
2424
- All types now implement `Debug`, and all data types implement `Hash`.
25+
- `Smf::new` no longer returns an error, and creates an empty `Smf` with no tracks. To create an
26+
`Smf` with prebuilt tracks use `Smf { header, tracks }` construction.
27+
- Added `Arena` to make track construction more ergonomic.
2528

2629
## 0.4
2730

src/arena.rs

+147
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
#![cfg(feature = "alloc")]
2+
3+
use crate::prelude::*;
4+
use core::cell::UnsafeCell;
5+
6+
/// Helps overcome limitations of the lifetime system when constructing MIDI events and files.
7+
///
8+
/// Because many events contain references to data that outlives them, it can be hard to build a
9+
/// MIDI file on-the-fly.
10+
///
11+
/// Consider the following code:
12+
///
13+
/// ```rust,compile_fail
14+
/// use midly::{TrackEvent, TrackEventKind, MetaMessage};
15+
///
16+
/// let mut track = Vec::new();
17+
/// for i in 0..64 {
18+
/// let marker_name = format!("Marker {}", i);
19+
/// let marker_ref = marker_name.as_bytes();
20+
/// track.push(TrackEvent {
21+
/// delta: 0.into(),
22+
/// kind: TrackEventKind::Meta(MetaMessage::Marker(marker_ref)),
23+
/// });
24+
/// }
25+
/// ```
26+
///
27+
/// Looks pretty good, but it fails to compile with
28+
/// `error[E0597]: "marker_name" does not live long enough`, with a rightful reason: `marker_name`
29+
/// is dropped before the next iteration of the loop.
30+
///
31+
/// Instead, use `Arena` like the following code:
32+
///
33+
/// ```rust
34+
/// use midly::{TrackEvent, TrackEventKind, MetaMessage};
35+
///
36+
/// let arena = midly::Arena::new();
37+
/// let mut track = Vec::new();
38+
/// for i in 0..64 {
39+
/// let marker_name = format!("Marker {}", i);
40+
/// let marker_ref = arena.add(marker_name.as_bytes());
41+
/// track.push(TrackEvent {
42+
/// delta: 0.into(),
43+
/// kind: TrackEventKind::Meta(MetaMessage::Marker(marker_ref)),
44+
/// });
45+
/// }
46+
/// ```
47+
#[derive(Default)]
48+
pub struct Arena {
49+
allocations: UnsafeCell<Vec<*mut [u8]>>,
50+
}
51+
impl Arena {
52+
/// Create a new empty arena.
53+
pub fn new() -> Arena {
54+
Self::default()
55+
}
56+
57+
/// Empty this arena, deallocating all added bytes.
58+
///
59+
/// This method is safe to call because it requires a mutable reference.
60+
pub fn clear(&mut self) {
61+
// SAFETY:
62+
// Accessing the `UnsafeCell` is safe because we have a mutable reference to it.
63+
// Since we have a mutable reference to `Arena`, there are no more references into
64+
// the boxed bytes. Therefore, it is safe to drop the boxed bytes themselves.
65+
unsafe {
66+
for bytes in (*self.allocations.get()).drain(..) {
67+
drop(Box::from_raw(bytes));
68+
}
69+
}
70+
}
71+
72+
/// Get the amount of allocations in the arena.
73+
pub fn len(&self) -> usize {
74+
// SAFETY:
75+
// Accessing `self.allocations` is safe as long as there are no concurrent reads or writes.
76+
// The _contents_ of `self.allocations` might have outstanding references, but reading the
77+
// length does not require derefencing the contents.
78+
unsafe { (*self.allocations.get()).len() }
79+
}
80+
81+
/// Add a set of bytes to the arena, returning a longer-lived mutable reference to a copy of
82+
/// these same bytes.
83+
pub fn add<'a, 'b>(&'a self, bytes: &'b [u8]) -> &'a mut [u8] {
84+
self.add_boxed(Box::from(bytes))
85+
}
86+
87+
/// Add a `Vec<u8>` to the arena, returning a long-lived mutable reference to its contents.
88+
///
89+
/// This method is very similar to `add`, but avoids an allocation and a copy.
90+
pub fn add_vec<'a>(&'a self, bytes: Vec<u8>) -> &'a mut [u8] {
91+
self.add_boxed(bytes.into_boxed_slice())
92+
}
93+
94+
/// Add a set of databytes to the arena, returning a longer-lived mutable reference to a copy
95+
/// of these same databytes.
96+
pub fn add_u7<'a, 'b>(&'a self, databytes: &'b [u7]) -> &'a mut [u7] {
97+
// SAFETY:
98+
// The returned `&mut [u8]` is transformed into a `&mut [u7]` without checking its
99+
// contents, which is safe because it was originally a `&[u7]`.
100+
unsafe { u7::slice_from_int_unchecked_mut(self.add(u7::slice_as_int(databytes))) }
101+
}
102+
103+
/// Add a `Vec<u7>` to the arena, returning a long-lived mutable reference to its contents.
104+
///
105+
/// This method is very similar to `add_u7`, but avoids an allocation and a copy.
106+
pub fn add_u7_vec<'a>(&'a self, databytes: Vec<u7>) -> &'a mut [u7] {
107+
// SAFETY:
108+
// Two unsafe actions are done:
109+
// First, a `Vec<u7>` is transmuted into a `Vec<u8>`. This is valid because `u7` has the
110+
// same representation as `u8` (guaranteed by `repr(transparent)`), and every `u7` bit
111+
// pattern is a valid `u8` bit pattern.
112+
// Second, the returned `&mut [u8]` is transformed into a `&mut [u7]` without checking its
113+
// contents, which is safe because it was originally a `Vec<u7>`.
114+
unsafe {
115+
u7::slice_from_int_unchecked_mut(
116+
self.add_boxed(mem::transmute::<Vec<u7>, Vec<u8>>(databytes).into_boxed_slice()),
117+
)
118+
}
119+
}
120+
121+
fn add_boxed<'a>(&'a self, boxed_bytes: Box<[u8]>) -> &'a mut [u8] {
122+
// SAFETY:
123+
// This block moves `boxed_bytes` into `self` and returns a mutable reference to its
124+
// contents.
125+
// Further pushes to `self.allocations` may move the _pointers_ to the boxes, but not the
126+
// box contents themselves, therefore it's safe to hand out mutable references and modify
127+
// `self.allocations` at the same time.
128+
unsafe {
129+
let pointer = Box::into_raw(boxed_bytes);
130+
(*self.allocations.get()).push(pointer);
131+
&mut *pointer
132+
}
133+
}
134+
}
135+
impl Drop for Arena {
136+
fn drop(&mut self) {
137+
self.clear();
138+
}
139+
}
140+
impl fmt::Debug for Arena {
141+
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
142+
//TODO: Once the `len()` method for raw pointers to slices is stabilized, add better
143+
//debug print, showing the size of each allocation.
144+
write!(f, "Arena({})", self.len())?;
145+
Ok(())
146+
}
147+
}

src/lib.rs

+5-1
Original file line numberDiff line numberDiff line change
@@ -205,6 +205,7 @@ mod prelude {
205205
}
206206
}
207207

208+
mod arena;
208209
mod event;
209210
pub mod io;
210211
pub mod live;
@@ -216,7 +217,10 @@ pub mod stream;
216217
#[cfg(feature = "std")]
217218
pub use crate::smf::write_std;
218219
#[cfg(feature = "alloc")]
219-
pub use crate::smf::{BytemappedTrack, Smf, SmfBytemap};
220+
pub use crate::{
221+
arena::Arena,
222+
smf::{BytemappedTrack, Smf, SmfBytemap},
223+
};
220224
pub use crate::{
221225
error::{Error, ErrorKind, Result},
222226
event::{MetaMessage, MidiMessage, PitchBend, TrackEvent, TrackEventKind},

src/primitive.rs

+52-4
Original file line numberDiff line numberDiff line change
@@ -143,18 +143,66 @@ macro_rules! restricted_int {
143143
return None;
144144
}
145145
}
146-
unsafe { Some( &*( raw as *const [$inner] as *const [$name] ) ) }
146+
unsafe {
147+
Some(Self::slice_from_int_unchecked(raw))
148+
}
147149
}
148150

149151
/// Cast a slice of raw integers to a slice of restricted integers.
150-
/// The slice is truncated to the first out-of-range byte, if any exist.
152+
///
153+
/// The slice is truncated up to the first out-of-range integer, if there is any.
151154
pub fn slice_from_int(raw: &[$inner]) -> &[$name] {
152155
let first_oob = raw
153156
.iter()
154157
.position(|&b| b > Self::MASK)
155158
.unwrap_or(raw.len());
156-
let safe = &raw[..first_oob];
157-
unsafe { &*( safe as *const [$inner] as *const [$name] ) }
159+
unsafe {
160+
Self::slice_from_int_unchecked(&raw[..first_oob])
161+
}
162+
}
163+
164+
/// Cast a slice of raw integers to a slice of restricted integers.
165+
///
166+
/// # Safety
167+
///
168+
/// The input slice must not contain any out-of-range integers.
169+
pub unsafe fn slice_from_int_unchecked(raw: &[$inner]) -> &[$name] {
170+
&*( raw as *const [$inner] as *const [$name] )
171+
}
172+
173+
/// Cast a slice of mutable raw integers to a slice of mutable restricted integers, only
174+
/// if there are no out-of-range integers.
175+
pub fn slice_try_from_int_mut(raw: &mut [$inner]) -> Option<&mut [$name]> {
176+
for &int in raw.iter() {
177+
if int > Self::MASK {
178+
return None;
179+
}
180+
}
181+
unsafe {
182+
Some(Self::slice_from_int_unchecked_mut(raw))
183+
}
184+
}
185+
186+
/// Cast a slice of mutable raw integers to a slice of mutable restricted integers.
187+
///
188+
/// The slice is truncated up to the first out-of-range integer, if there is any.
189+
pub fn slice_from_int_mut(raw: &mut [$inner]) -> &mut [$name] {
190+
let first_oob = raw
191+
.iter()
192+
.position(|&b| b > Self::MASK)
193+
.unwrap_or(raw.len());
194+
unsafe {
195+
Self::slice_from_int_unchecked_mut(&mut raw[..first_oob])
196+
}
197+
}
198+
199+
/// Cast a slice of mutable raw integers to a slice of mutable restricted integers.
200+
///
201+
/// # Safety
202+
///
203+
/// The input slice must not contain any out-of-range integers.
204+
pub unsafe fn slice_from_int_unchecked_mut(raw: &mut [$inner]) -> &mut [$name] {
205+
&mut *( raw as *mut [$inner] as *mut [$name] )
158206
}
159207

160208
/// Cast a slice of restricted integers to the corresponding raw integers.

src/smf.rs

+13-7
Original file line numberDiff line numberDiff line change
@@ -63,10 +63,13 @@ pub struct Smf<'a> {
6363
pub tracks: Vec<Track<'a>>,
6464
}
6565
#[cfg(feature = "alloc")]
66-
impl Smf<'_> {
67-
/// Create a new `Smf` from its raw parts.
68-
pub fn new(header: Header, tracks: Vec<Track>) -> Smf {
69-
Smf { header, tracks }
66+
impl<'a> Smf<'a> {
67+
/// Create a new empty `Smf` with zero tracks, using the given header.
68+
pub fn new(header: Header) -> Smf<'a> {
69+
Smf {
70+
header,
71+
tracks: vec![],
72+
}
7073
}
7174

7275
/// Parse a `.mid` Standard Midi File from its raw bytes.
@@ -134,9 +137,12 @@ pub struct SmfBytemap<'a> {
134137
}
135138
#[cfg(feature = "alloc")]
136139
impl<'a> SmfBytemap<'a> {
137-
/// Create an `SmfBytemap` from its raw parts.
138-
pub fn new(header: Header, tracks: Vec<Vec<(&'a [u8], TrackEvent<'a>)>>) -> SmfBytemap<'a> {
139-
SmfBytemap { header, tracks }
140+
/// Create a new empty `SmfBytemap` with zero tracks, using the given header.
141+
pub fn new(header: Header) -> SmfBytemap<'a> {
142+
SmfBytemap {
143+
header,
144+
tracks: vec![],
145+
}
140146
}
141147

142148
/// Parse a Standard Midi File from its raw bytes, keeping a map to the original bytes that

src/test.rs

+21
Original file line numberDiff line numberDiff line change
@@ -479,4 +479,25 @@ mod parse {
479479
assert_eq!(buf.as_slice(), buf_copy.as_slice());
480480
assert_eq!(format!("{:?}", buf), format!("{:?}", buf_copy));
481481
}
482+
483+
#[test]
484+
fn stable_arena() {
485+
let arena = crate::Arena::new();
486+
let mut string = String::new();
487+
string.push_str("hello");
488+
let hello = arena.add(string.as_bytes());
489+
string.push_str(" world");
490+
let helloworld = arena.add(string.as_bytes());
491+
string.truncate(4);
492+
string.push_str("fire");
493+
let hellfire = arena.add(string.as_bytes());
494+
let hellsung = arena.add(string.as_bytes());
495+
hellsung.copy_from_slice(b"hellsung");
496+
drop(string);
497+
println!("hello: \"{}\"", String::from_utf8_lossy(hello));
498+
println!("helloworld: \"{}\"", String::from_utf8_lossy(helloworld));
499+
println!("hellfire: \"{}\"", String::from_utf8_lossy(hellfire));
500+
println!("hellsung: \"{}\"", String::from_utf8_lossy(hellsung));
501+
drop(arena);
502+
}
482503
}

0 commit comments

Comments
 (0)