use crate::error::{DataError, DataResult};
use heapless::{String, Vec};
pub mod sizes {
pub mod ultra {
pub type LabelString = heapless::String<8>;
pub type SmallVec<T> = heapless::Vec<T, 4>;
pub type DataVec<T> = heapless::Vec<T, 16>;
pub type SeriesVec<T> = heapless::Vec<T, 2>;
}
pub mod small {
pub type LabelString = heapless::String<16>;
pub type SmallVec<T> = heapless::Vec<T, 8>;
pub type DataVec<T> = heapless::Vec<T, 64>;
pub type SeriesVec<T> = heapless::Vec<T, 4>;
}
pub mod medium {
pub type LabelString = heapless::String<32>;
pub type SmallVec<T> = heapless::Vec<T, 16>;
pub type DataVec<T> = heapless::Vec<T, 256>;
pub type SeriesVec<T> = heapless::Vec<T, 8>;
}
pub mod large {
pub type LabelString = heapless::String<64>;
pub type SmallVec<T> = heapless::Vec<T, 32>;
pub type DataVec<T> = heapless::Vec<T, 512>;
pub type SeriesVec<T> = heapless::Vec<T, 16>;
}
#[cfg(feature = "minimal-memory")]
pub use ultra::*;
#[cfg(all(feature = "static-only", not(feature = "minimal-memory")))]
pub use small::*;
#[cfg(all(
not(feature = "static-only"),
not(feature = "minimal-memory"),
feature = "no_std"
))]
pub use medium::*;
#[cfg(all(
feature = "std",
not(any(feature = "static-only", feature = "minimal-memory"))
))]
pub use large::*;
#[cfg(not(any(
feature = "minimal-memory",
feature = "static-only",
feature = "no_std",
feature = "std"
)))]
pub use medium::*;
}
pub mod string {
use super::*;
pub fn try_from_str<const N: usize>(s: &str) -> DataResult<String<N>> {
String::try_from(s).map_err(|_| DataError::buffer_full("create heapless string", N))
}
pub fn from_str_truncate<const N: usize>(s: &str) -> String<N> {
let truncated = if s.len() > N { &s[..N] } else { s };
String::try_from(truncated).unwrap_or_else(|_| String::new())
}
pub fn push_str_safe<const N: usize>(string: &mut String<N>, s: &str) -> DataResult<()> {
string
.push_str(s)
.map_err(|_| DataError::buffer_full("push string", N))
}
pub fn push_char_safe<const N: usize>(string: &mut String<N>, c: char) -> DataResult<()> {
string
.push(c)
.map_err(|_| DataError::buffer_full("push character", N))
}
pub fn format_number<const N: usize>(value: f32, precision: usize) -> String<N> {
let mut result = String::new();
if value < 0.0 {
let _ = result.push('-');
}
let abs_value = if value < 0.0 { -value } else { value };
let integer_part = abs_value as u32;
let _ = format_integer(&mut result, integer_part);
if precision > 0 {
let _ = result.push('.');
let mut fractional = abs_value - integer_part as f32;
for _ in 0..precision {
fractional *= 10.0;
let digit = fractional as u32 % 10;
let _ = result.push((b'0' + digit as u8) as char);
}
}
result
}
fn format_integer<const N: usize>(string: &mut String<N>, mut value: u32) -> DataResult<()> {
if value == 0 {
return push_char_safe(string, '0');
}
let mut digits = Vec::<u8, 16>::new();
while value > 0 {
digits
.push((value % 10) as u8)
.map_err(|_| DataError::buffer_full("format integer", 16))?;
value /= 10;
}
for &digit in digits.iter().rev() {
push_char_safe(string, (b'0' + digit) as char)?;
}
Ok(())
}
}
pub mod vec {
use super::*;
pub fn push_safe<T, const N: usize>(vec: &mut Vec<T, N>, item: T) -> DataResult<()> {
vec.push(item)
.map_err(|_| DataError::buffer_full("push item", N))
}
pub fn extend_safe<T, I, const N: usize>(vec: &mut Vec<T, N>, iter: I) -> DataResult<()>
where
I: IntoIterator<Item = T>,
{
for item in iter {
push_safe(vec, item)?;
}
Ok(())
}
pub fn try_from_slice<T: Clone, const N: usize>(slice: &[T]) -> DataResult<Vec<T, N>> {
if slice.len() > N {
return Err(DataError::buffer_full("create from slice", N));
}
Vec::from_slice(slice).map_err(|_| DataError::buffer_full("create from slice", N))
}
pub fn insertion_sort<T: Ord + Clone, const N: usize>(vec: &mut Vec<T, N>) {
let len = vec.len();
for i in 1..len {
let key = vec[i].clone();
let mut j = i;
while j > 0 && vec[j - 1] > key {
vec[j] = vec[j - 1].clone();
j -= 1;
}
vec[j] = key;
}
}
pub fn find_index<T: PartialEq, const N: usize>(vec: &Vec<T, N>, item: &T) -> Option<usize> {
vec.iter().position(|x| x == item)
}
pub fn remove_item<T: PartialEq + Clone, const N: usize>(
vec: &mut Vec<T, N>,
item: &T,
) -> bool {
if let Some(index) = find_index(vec, item) {
vec.remove(index);
true
} else {
false
}
}
}
pub struct HeaplessPool<T, const N: usize> {
pool: Vec<Option<T>, N>,
free_list: Vec<usize, N>,
}
impl<T, const N: usize> HeaplessPool<T, N> {
pub fn new() -> Self {
let mut pool = Vec::new();
let mut free_list = Vec::new();
for i in 0..N {
let _ = pool.push(None);
let _ = free_list.push(i);
}
Self { pool, free_list }
}
pub fn allocate(&mut self, item: T) -> Option<usize> {
if let Some(index) = self.free_list.pop() {
self.pool[index] = Some(item);
Some(index)
} else {
None
}
}
pub fn deallocate(&mut self, index: usize) -> Option<T> {
if index < N {
if let Some(item) = self.pool[index].take() {
let _ = self.free_list.push(index);
Some(item)
} else {
None
}
} else {
None
}
}
pub fn get(&self, index: usize) -> Option<&T> {
self.pool.get(index)?.as_ref()
}
pub fn get_mut(&mut self, index: usize) -> Option<&mut T> {
self.pool.get_mut(index)?.as_mut()
}
pub fn allocated_count(&self) -> usize {
N - self.free_list.len()
}
pub fn is_full(&self) -> bool {
self.free_list.is_empty()
}
pub fn is_empty(&self) -> bool {
self.free_list.len() == N
}
}
impl<T, const N: usize> Default for HeaplessPool<T, N> {
fn default() -> Self {
Self::new()
}
}
pub struct CircularBuffer<T: Copy, const N: usize> {
buffer: [Option<T>; N],
head: usize,
tail: usize,
full: bool,
}
impl<T: Copy, const N: usize> CircularBuffer<T, N> {
pub const fn new() -> Self {
Self {
buffer: [None; N],
head: 0,
tail: 0,
full: false,
}
}
pub fn push(&mut self, item: T) {
self.buffer[self.head] = Some(item);
if self.full {
self.tail = (self.tail + 1) % N;
}
self.head = (self.head + 1) % N;
if self.head == self.tail {
self.full = true;
}
}
pub fn pop(&mut self) -> Option<T> {
if self.is_empty() {
return None;
}
let item = self.buffer[self.tail].take();
self.tail = (self.tail + 1) % N;
self.full = false;
item
}
pub fn len(&self) -> usize {
if self.full {
N
} else if self.head >= self.tail {
self.head - self.tail
} else {
N - self.tail + self.head
}
}
pub fn is_empty(&self) -> bool {
!self.full && self.head == self.tail
}
pub fn is_full(&self) -> bool {
self.full
}
pub const fn capacity(&self) -> usize {
N
}
pub fn clear(&mut self) {
self.buffer = [None; N];
self.head = 0;
self.tail = 0;
self.full = false;
}
pub fn iter(&self) -> CircularBufferIter<T, N> {
CircularBufferIter {
buffer: &self.buffer,
current: self.tail,
remaining: self.len(),
}
}
}
impl<T: Copy, const N: usize> Default for CircularBuffer<T, N> {
fn default() -> Self {
Self::new()
}
}
pub struct CircularBufferIter<'a, T: Copy, const N: usize> {
buffer: &'a [Option<T>; N],
current: usize,
remaining: usize,
}
impl<'a, T: Copy, const N: usize> Iterator for CircularBufferIter<'a, T, N> {
type Item = T;
fn next(&mut self) -> Option<Self::Item> {
if self.remaining == 0 {
return None;
}
let item = self.buffer[self.current]?;
self.current = (self.current + 1) % N;
self.remaining -= 1;
Some(item)
}
fn size_hint(&self) -> (usize, Option<usize>) {
(self.remaining, Some(self.remaining))
}
}
impl<'a, T: Copy, const N: usize> ExactSizeIterator for CircularBufferIter<'a, T, N> {}
#[macro_export]
macro_rules! heapless_string {
($s:expr) => {
$crate::heapless_utils::string::try_from_str($s)
};
($s:expr, $n:expr) => {
heapless::String::<$n>::try_from($s)
};
}
#[macro_export]
macro_rules! heapless_vec {
($($item:expr),* $(,)?) => {{
let mut vec = heapless::Vec::new();
$(
let _ = vec.push($item);
)*
vec
}};
($item:expr; $count:expr) => {{
let mut vec = heapless::Vec::new();
for _ in 0..$count {
let _ = vec.push($item);
}
vec
}};
}
pub struct HeaplessConfig {
pub max_string_length: usize,
pub max_small_vec_capacity: usize,
pub max_data_points: usize,
pub max_series_count: usize,
}
impl HeaplessConfig {
pub const ULTRA: Self = Self {
max_string_length: 8,
max_small_vec_capacity: 4,
max_data_points: 16,
max_series_count: 2,
};
pub const SMALL: Self = Self {
max_string_length: 16,
max_small_vec_capacity: 8,
max_data_points: 64,
max_series_count: 4,
};
pub const MEDIUM: Self = Self {
max_string_length: 32,
max_small_vec_capacity: 16,
max_data_points: 256,
max_series_count: 8,
};
pub const LARGE: Self = Self {
max_string_length: 64,
max_small_vec_capacity: 32,
max_data_points: 512,
max_series_count: 16,
};
pub const fn default() -> &'static Self {
#[cfg(feature = "minimal-memory")]
return &Self::ULTRA;
#[cfg(all(feature = "static-only", not(feature = "minimal-memory")))]
return &Self::SMALL;
#[cfg(all(
not(feature = "static-only"),
not(feature = "minimal-memory"),
feature = "no_std"
))]
return &Self::MEDIUM;
#[cfg(all(
feature = "std",
not(any(feature = "static-only", feature = "minimal-memory"))
))]
return &Self::LARGE;
#[cfg(not(any(
feature = "minimal-memory",
feature = "static-only",
feature = "no_std",
feature = "std"
)))]
return &Self::MEDIUM;
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_string_utilities() {
let result: DataResult<String<16>> = string::try_from_str("Hello");
assert!(result.is_ok());
assert_eq!(result.unwrap().as_str(), "Hello");
let result: DataResult<String<8>> = string::try_from_str("This is too long");
assert!(result.is_err());
let truncated = string::from_str_truncate::<8>("This is too long");
assert_eq!(truncated.as_str(), "This is ");
let number_str = string::format_number::<16>(123.45, 2);
assert!(number_str.as_str() == "123.45" || number_str.as_str() == "123.44");
}
#[test]
fn test_vec_utilities() {
let mut vec: Vec<i32, 8> = Vec::new();
assert!(vec::push_safe(&mut vec, 42).is_ok());
assert_eq!(vec.len(), 1);
assert_eq!(vec[0], 42);
assert!(vec::extend_safe(&mut vec, [1, 2, 3]).is_ok());
assert_eq!(vec.len(), 4);
vec::insertion_sort(&mut vec);
assert_eq!(vec.as_slice(), &[1, 2, 3, 42]);
}
#[test]
fn test_circular_buffer() {
let mut buffer: CircularBuffer<i32, 4> = CircularBuffer::new();
assert!(buffer.is_empty());
assert_eq!(buffer.len(), 0);
buffer.push(1);
buffer.push(2);
buffer.push(3);
assert_eq!(buffer.len(), 3);
buffer.push(4);
assert!(buffer.is_full());
assert_eq!(buffer.len(), 4);
buffer.push(5);
assert_eq!(buffer.len(), 4);
let items: Vec<i32, 4> = buffer.iter().collect();
assert_eq!(items.as_slice(), &[2, 3, 4, 5]);
}
#[test]
fn test_memory_pool() {
let mut pool: HeaplessPool<String<16>, 4> = HeaplessPool::new();
assert!(pool.is_empty());
assert!(!pool.is_full());
let idx1 = pool.allocate(String::try_from("Hello").unwrap()).unwrap();
let idx2 = pool.allocate(String::try_from("World").unwrap()).unwrap();
assert_eq!(pool.allocated_count(), 2);
assert_eq!(pool.get(idx1).unwrap().as_str(), "Hello");
assert_eq!(pool.get(idx2).unwrap().as_str(), "World");
let item = pool.deallocate(idx1).unwrap();
assert_eq!(item.as_str(), "Hello");
assert_eq!(pool.allocated_count(), 1);
}
#[test]
fn test_heapless_config() {
let config = HeaplessConfig::default();
assert!(config.max_string_length > 0);
assert!(config.max_data_points > 0);
}
}