Skip to content

Commit 41fb9d6

Browse files
committed
✨ Add boxed module
This is an abstraction over `heapless::pool::boxed` and `alloc::boxed`.
1 parent e8a8388 commit 41fb9d6

File tree

4 files changed

+218
-9
lines changed

4 files changed

+218
-9
lines changed

src/boxed.rs

Lines changed: 200 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,200 @@
1+
//! Abstraction over `heapless::pool::boxed` and `alloc::boxed`.
2+
//!
3+
//! The API is modeled after `heapless::pool::boxed` but simpler. This module is only available
4+
//! when either:
5+
//!
6+
//! - `alloc` feature is enabled, or
7+
//! - `heapless` and `portable-atomic` features are enabled.
8+
//!
9+
//! # Usage
10+
//!
11+
//! ```
12+
//! use mayheap::{box_pool, boxed::{BoxPool, Box}};
13+
//!
14+
//! // Create a pool for u32 type with a capacity of 10.
15+
//! box_pool!(MyBoxPool: u32, 2);
16+
//!
17+
//! // Allocate a new boxed value from the pool.
18+
//! let mut boxed = MyBoxPool.alloc(42).unwrap();
19+
//! assert_eq!(*boxed, 42);
20+
//!
21+
//! // Let's mutate the boxed value.
22+
//! *boxed = 100;
23+
//! assert_eq!(*boxed, 100);
24+
//!
25+
//! // Let's allocate more.
26+
//! let _boxed = MyBoxPool.alloc(43).unwrap();
27+
//!
28+
//! #[cfg(feature = "alloc")]
29+
//! {
30+
//! // This will work fine since capacity (which is 2 here) is irrelevant when using alloc.
31+
//! let boxed = MyBoxPool.alloc(44).unwrap();
32+
//! assert_eq!(*boxed, 44);
33+
//! }
34+
//! #[cfg(feature = "heapless")]
35+
//! {
36+
//! // This will not.
37+
//! let res = MyBoxPool.alloc(45);
38+
//! assert_eq!(res, Err(45));
39+
//! }
40+
//! ```
41+
42+
use core::ops::{Deref, DerefMut};
43+
44+
/// A singleton that manages pool::boxed::Box-es.
45+
///
46+
/// Don't implement this trait directly. Use [`box_pool`] to create an implementation.
47+
pub trait BoxPool {
48+
/// The data type managed by the memory pool.
49+
type Data;
50+
/// The implementation-specific type of the boxed value.
51+
type BoxedValue: DerefMut<Target = Self::Data>;
52+
53+
/// Allocates a new boxed value from the pool.
54+
fn alloc(&self, value: Self::Data) -> Result<Box<Self>, Self::Data>
55+
where
56+
Self: Sized;
57+
}
58+
59+
/// A boxed value managed by a [`BoxPool`].
60+
#[derive(Debug, PartialEq, Eq, Clone)]
61+
pub struct Box<P: BoxPool>(P::BoxedValue);
62+
63+
impl<P: BoxPool> Box<P> {
64+
/// Allocates a new boxed value from the pool.
65+
pub fn new(value: P::BoxedValue) -> Self {
66+
Self(value)
67+
}
68+
}
69+
70+
impl<P: BoxPool> Deref for Box<P> {
71+
type Target = P::Data;
72+
73+
fn deref(&self) -> &Self::Target {
74+
self.0.deref()
75+
}
76+
}
77+
78+
impl<P: BoxPool> DerefMut for Box<P> {
79+
fn deref_mut(&mut self) -> &mut Self::Target {
80+
self.0.deref_mut()
81+
}
82+
}
83+
84+
/// Creates a new BoxPool singleton with the given $name that manages the specified $data_type
85+
#[cfg(feature = "alloc")]
86+
#[macro_export]
87+
macro_rules! box_pool {
88+
($name:ident: $ty:ty, $capacity:expr) => {
89+
#[derive(Debug, Clone, PartialEq, Eq)]
90+
pub struct $name;
91+
92+
impl $crate::boxed::BoxPool for $name {
93+
type Data = $ty;
94+
type BoxedValue = $crate::reexports::alloc::boxed::Box<$ty>;
95+
96+
fn alloc(&self, value: Self::Data) -> Result<$crate::boxed::Box<Self>, Self::Data> {
97+
Ok($crate::boxed::Box::new(
98+
$crate::reexports::alloc::boxed::Box::new(value),
99+
))
100+
}
101+
}
102+
103+
$crate::reexports::paste::paste! {
104+
// Let's use the $capacity variable so callers don't get "unused const" warnings.
105+
#[allow(non_upper_case_globals, dead_code)]
106+
const [<__dummy__ $name>]: () = {
107+
let _ = $capacity;
108+
};
109+
}
110+
};
111+
}
112+
113+
/// Creates a new BoxPool singleton with the given $name that manages the specified $data_type
114+
#[cfg(not(feature = "alloc"))]
115+
#[macro_export]
116+
macro_rules! box_pool {
117+
($name:ident: $ty:ty, $capacity:expr) => {
118+
$crate::reexports::paste::paste! {
119+
heapless::box_pool!([<$name Pool>]: $ty);
120+
121+
#[derive(Debug, Clone, PartialEq, Eq)]
122+
pub struct $name;
123+
124+
impl $crate::boxed::BoxPool for $name {
125+
type Data = $ty;
126+
type BoxedValue = heapless::pool::boxed::Box<[<$name Pool>]>;
127+
128+
fn alloc(&self, value: Self::Data) -> Result<$crate::boxed::Box<Self>, $ty> {
129+
$name.init();
130+
131+
[<$name Pool>].alloc(value).map($crate::boxed::Box::new)
132+
}
133+
}
134+
135+
impl $name {
136+
fn init(&self) {
137+
use portable_atomic::{AtomicU8, Ordering};
138+
use heapless::pool::boxed::BoxBlock;
139+
140+
static STATE: AtomicU8 = AtomicU8::new(InitState::Uninitialized as u8);
141+
142+
match STATE
143+
.compare_exchange(
144+
InitState::Uninitialized as u8,
145+
InitState::Initializing as u8,
146+
Ordering::AcqRel,
147+
Ordering::Acquire,
148+
)
149+
.map(|state| state.into())
150+
.map_err(|state| state.into())
151+
{
152+
Ok(InitState::Uninitialized) => {
153+
// We won the race, initialize.
154+
let blocks: &'static mut [BoxBlock<$ty>] = {
155+
#[allow(clippy::declare_interior_mutable_const)]
156+
const BLOCK: BoxBlock<$ty> = BoxBlock::new();
157+
static mut BLOCKS: [BoxBlock<$ty>; $capacity] = [BLOCK; $capacity];
158+
unsafe { core::ptr::addr_of_mut!(BLOCKS).as_mut().unwrap() }
159+
};
160+
for block in blocks {
161+
[<$name Pool>].manage(block);
162+
}
163+
STATE.store(InitState::Initialized as u8, Ordering::Release);
164+
}
165+
Err(InitState::Initializing) => {
166+
// Someone else is initializing, wait.
167+
while STATE.load(Ordering::Acquire) == InitState::Initializing as u8 {
168+
core::hint::spin_loop();
169+
}
170+
}
171+
Err(InitState::Initialized) => {
172+
// Already initialized.
173+
}
174+
// All other states should never happen.
175+
_ => unreachable!(),
176+
}
177+
178+
#[repr(u8)]
179+
#[derive(PartialEq)]
180+
enum InitState {
181+
Uninitialized = 0,
182+
Initializing = 1,
183+
Initialized = 2,
184+
}
185+
186+
impl From<u8> for InitState {
187+
fn from(value: u8) -> Self {
188+
match value {
189+
0 => InitState::Uninitialized,
190+
1 => InitState::Initializing,
191+
2 => InitState::Initialized,
192+
_ => unreachable!(),
193+
}
194+
}
195+
}
196+
}
197+
}
198+
}
199+
};
200+
}

src/lib.rs

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,13 @@
1212
#[cfg(all(not(feature = "alloc"), not(feature = "heapless")))]
1313
compile_error!("Either the `alloc` or `heapless` feature must be enabled");
1414

15-
#[cfg(feature = "alloc")]
16-
extern crate alloc;
17-
18-
// Re-export `paste` for the macros.
15+
// Re-exports for the macros.
1916
#[doc(hidden)]
20-
pub use paste;
17+
pub mod reexports {
18+
#[cfg(feature = "alloc")]
19+
pub extern crate alloc;
20+
pub use paste;
21+
}
2122

2223
pub mod vec;
2324
pub use vec::Vec;
@@ -28,6 +29,12 @@ pub use string::String;
2829
mod error;
2930
pub use error::{Error, Result};
3031

32+
#[cfg(any(
33+
all(feature = "portable-atomic", feature = "heapless"),
34+
feature = "alloc"
35+
))]
36+
pub mod boxed;
37+
3138
#[cfg(test)]
3239
mod tests {
3340
#[cfg(feature = "serde")]

src/string.rs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ use core::{cmp::Ordering, fmt, hash, iter, ops, str};
55
use crate::Vec;
66

77
#[cfg(feature = "alloc")]
8-
type Inner<const N: usize> = alloc::string::String;
8+
type Inner<const N: usize> = crate::reexports::alloc::string::String;
99
#[cfg(not(feature = "alloc"))]
1010
type Inner<const N: usize> = heapless::String<N>;
1111

@@ -357,7 +357,9 @@ macro_rules! impl_try_from_num {
357357
fn try_from(s: $num) -> Result<Self, Self::Error> {
358358
#[cfg(feature = "alloc")]
359359
{
360-
Ok(Self(alloc::string::ToString::to_string(&s)))
360+
Ok(Self(crate::reexports::alloc::string::ToString::to_string(
361+
&s,
362+
)))
361363
}
362364
#[cfg(not(feature = "alloc"))]
363365
{

src/vec.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
use core::{cmp::Ordering, fmt, hash, iter::FromIterator, ops, slice};
66

77
#[cfg(feature = "alloc")]
8-
pub(crate) type Inner<T, const N: usize> = alloc::vec::Vec<T>;
8+
pub(crate) type Inner<T, const N: usize> = crate::reexports::alloc::vec::Vec<T>;
99
#[cfg(not(feature = "alloc"))]
1010
pub(crate) type Inner<T, const N: usize> = heapless::Vec<T, N>;
1111

@@ -437,7 +437,7 @@ impl<T, const N: usize> FromIterator<T> for Vec<T, N> {
437437
#[derive(Clone, Debug)]
438438
pub struct IntoIter<T, const N: usize> {
439439
#[cfg(feature = "alloc")]
440-
iter: alloc::vec::IntoIter<T>,
440+
iter: crate::reexports::alloc::vec::IntoIter<T>,
441441
// FIXME: Once the fix for https://github.com/rust-embedded/heapless/issues/530 is released. We
442442
// can turn this into a wrapper around `heapless::vec::IntoIter`.
443443
#[cfg(not(feature = "alloc"))]

0 commit comments

Comments
 (0)