1use crate::error::{DataError, DataResult};
7use heapless::{String, Vec};
8
9pub mod sizes {
11 pub mod ultra {
13 pub type LabelString = heapless::String<8>;
15 pub type SmallVec<T> = heapless::Vec<T, 4>;
17 pub type DataVec<T> = heapless::Vec<T, 16>;
19 pub type SeriesVec<T> = heapless::Vec<T, 2>;
21 }
22
23 pub mod small {
25 pub type LabelString = heapless::String<16>;
27 pub type SmallVec<T> = heapless::Vec<T, 8>;
29 pub type DataVec<T> = heapless::Vec<T, 64>;
31 pub type SeriesVec<T> = heapless::Vec<T, 4>;
33 }
34
35 pub mod medium {
37 pub type LabelString = heapless::String<32>;
39 pub type SmallVec<T> = heapless::Vec<T, 16>;
41 pub type DataVec<T> = heapless::Vec<T, 256>;
43 pub type SeriesVec<T> = heapless::Vec<T, 8>;
45 }
46
47 pub mod large {
49 pub type LabelString = heapless::String<64>;
51 pub type SmallVec<T> = heapless::Vec<T, 32>;
53 pub type DataVec<T> = heapless::Vec<T, 512>;
55 pub type SeriesVec<T> = heapless::Vec<T, 16>;
57 }
58
59 #[cfg(feature = "minimal-memory")]
61 pub use ultra::*;
62
63 #[cfg(all(feature = "static-only", not(feature = "minimal-memory")))]
64 pub use small::*;
65
66 #[cfg(all(
67 not(feature = "static-only"),
68 not(feature = "minimal-memory"),
69 feature = "no_std"
70 ))]
71 pub use medium::*;
72
73 #[cfg(all(
74 feature = "std",
75 not(any(feature = "static-only", feature = "minimal-memory"))
76 ))]
77 pub use large::*;
78
79 #[cfg(not(any(
80 feature = "minimal-memory",
81 feature = "static-only",
82 feature = "no_std",
83 feature = "std"
84 )))]
85 pub use medium::*;
86}
87
88pub mod string {
90 use super::*;
91
92 pub fn try_from_str<const N: usize>(s: &str) -> DataResult<String<N>> {
94 String::try_from(s).map_err(|_| DataError::buffer_full("create heapless string", N))
95 }
96
97 pub fn from_str_truncate<const N: usize>(s: &str) -> String<N> {
99 let truncated = if s.len() > N { &s[..N] } else { s };
100 String::try_from(truncated).unwrap_or_else(|_| String::new())
101 }
102
103 pub fn push_str_safe<const N: usize>(string: &mut String<N>, s: &str) -> DataResult<()> {
105 string
106 .push_str(s)
107 .map_err(|_| DataError::buffer_full("push string", N))
108 }
109
110 pub fn push_char_safe<const N: usize>(string: &mut String<N>, c: char) -> DataResult<()> {
112 string
113 .push(c)
114 .map_err(|_| DataError::buffer_full("push character", N))
115 }
116
117 pub fn format_number<const N: usize>(value: f32, precision: usize) -> String<N> {
119 let mut result = String::new();
120
121 if value < 0.0 {
123 let _ = result.push('-');
124 }
125
126 let abs_value = if value < 0.0 { -value } else { value };
127
128 let integer_part = abs_value as u32;
130 let _ = format_integer(&mut result, integer_part);
131
132 if precision > 0 {
134 let _ = result.push('.');
135 let mut fractional = abs_value - integer_part as f32;
136
137 for _ in 0..precision {
138 fractional *= 10.0;
139 let digit = fractional as u32 % 10;
140 let _ = result.push((b'0' + digit as u8) as char);
141 }
142 }
143
144 result
145 }
146
147 fn format_integer<const N: usize>(string: &mut String<N>, mut value: u32) -> DataResult<()> {
149 if value == 0 {
150 return push_char_safe(string, '0');
151 }
152
153 let mut digits = Vec::<u8, 16>::new();
154 while value > 0 {
155 digits
156 .push((value % 10) as u8)
157 .map_err(|_| DataError::buffer_full("format integer", 16))?;
158 value /= 10;
159 }
160
161 for &digit in digits.iter().rev() {
163 push_char_safe(string, (b'0' + digit) as char)?;
164 }
165
166 Ok(())
167 }
168}
169
170pub mod vec {
172 use super::*;
173
174 pub fn push_safe<T, const N: usize>(vec: &mut Vec<T, N>, item: T) -> DataResult<()> {
176 vec.push(item)
177 .map_err(|_| DataError::buffer_full("push item", N))
178 }
179
180 pub fn extend_safe<T, I, const N: usize>(vec: &mut Vec<T, N>, iter: I) -> DataResult<()>
182 where
183 I: IntoIterator<Item = T>,
184 {
185 for item in iter {
186 push_safe(vec, item)?;
187 }
188 Ok(())
189 }
190
191 pub fn try_from_slice<T: Clone, const N: usize>(slice: &[T]) -> DataResult<Vec<T, N>> {
193 if slice.len() > N {
194 return Err(DataError::buffer_full("create from slice", N));
195 }
196 Vec::from_slice(slice).map_err(|_| DataError::buffer_full("create from slice", N))
197 }
198
199 pub fn insertion_sort<T: Ord + Clone, const N: usize>(vec: &mut Vec<T, N>) {
201 let len = vec.len();
202 for i in 1..len {
203 let key = vec[i].clone();
204 let mut j = i;
205
206 while j > 0 && vec[j - 1] > key {
207 vec[j] = vec[j - 1].clone();
208 j -= 1;
209 }
210 vec[j] = key;
211 }
212 }
213
214 pub fn find_index<T: PartialEq, const N: usize>(vec: &Vec<T, N>, item: &T) -> Option<usize> {
216 vec.iter().position(|x| x == item)
217 }
218
219 pub fn remove_item<T: PartialEq + Clone, const N: usize>(
221 vec: &mut Vec<T, N>,
222 item: &T,
223 ) -> bool {
224 if let Some(index) = find_index(vec, item) {
225 vec.remove(index);
226 true
227 } else {
228 false
229 }
230 }
231}
232
233pub struct HeaplessPool<T, const N: usize> {
235 pool: Vec<Option<T>, N>,
236 free_list: Vec<usize, N>,
237}
238
239impl<T, const N: usize> HeaplessPool<T, N> {
240 pub fn new() -> Self {
242 let mut pool = Vec::new();
243 let mut free_list = Vec::new();
244
245 for i in 0..N {
247 let _ = pool.push(None);
248 let _ = free_list.push(i);
249 }
250
251 Self { pool, free_list }
252 }
253
254 pub fn allocate(&mut self, item: T) -> Option<usize> {
256 if let Some(index) = self.free_list.pop() {
257 self.pool[index] = Some(item);
258 Some(index)
259 } else {
260 None
261 }
262 }
263
264 pub fn deallocate(&mut self, index: usize) -> Option<T> {
266 if index < N {
267 if let Some(item) = self.pool[index].take() {
268 let _ = self.free_list.push(index);
269 Some(item)
270 } else {
271 None
272 }
273 } else {
274 None
275 }
276 }
277
278 pub fn get(&self, index: usize) -> Option<&T> {
280 self.pool.get(index)?.as_ref()
281 }
282
283 pub fn get_mut(&mut self, index: usize) -> Option<&mut T> {
285 self.pool.get_mut(index)?.as_mut()
286 }
287
288 pub fn allocated_count(&self) -> usize {
290 N - self.free_list.len()
291 }
292
293 pub fn is_full(&self) -> bool {
295 self.free_list.is_empty()
296 }
297
298 pub fn is_empty(&self) -> bool {
300 self.free_list.len() == N
301 }
302}
303
304impl<T, const N: usize> Default for HeaplessPool<T, N> {
305 fn default() -> Self {
306 Self::new()
307 }
308}
309
310pub struct CircularBuffer<T: Copy, const N: usize> {
312 buffer: [Option<T>; N],
313 head: usize,
314 tail: usize,
315 full: bool,
316}
317
318impl<T: Copy, const N: usize> CircularBuffer<T, N> {
319 pub const fn new() -> Self {
321 Self {
322 buffer: [None; N],
323 head: 0,
324 tail: 0,
325 full: false,
326 }
327 }
328
329 pub fn push(&mut self, item: T) {
331 self.buffer[self.head] = Some(item);
332
333 if self.full {
334 self.tail = (self.tail + 1) % N;
335 }
336
337 self.head = (self.head + 1) % N;
338
339 if self.head == self.tail {
340 self.full = true;
341 }
342 }
343
344 pub fn pop(&mut self) -> Option<T> {
346 if self.is_empty() {
347 return None;
348 }
349
350 let item = self.buffer[self.tail].take();
351 self.tail = (self.tail + 1) % N;
352 self.full = false;
353 item
354 }
355
356 pub fn len(&self) -> usize {
358 if self.full {
359 N
360 } else if self.head >= self.tail {
361 self.head - self.tail
362 } else {
363 N - self.tail + self.head
364 }
365 }
366
367 pub fn is_empty(&self) -> bool {
369 !self.full && self.head == self.tail
370 }
371
372 pub fn is_full(&self) -> bool {
374 self.full
375 }
376
377 pub const fn capacity(&self) -> usize {
379 N
380 }
381
382 pub fn clear(&mut self) {
384 self.buffer = [None; N];
385 self.head = 0;
386 self.tail = 0;
387 self.full = false;
388 }
389
390 pub fn iter(&self) -> CircularBufferIter<T, N> {
392 CircularBufferIter {
393 buffer: &self.buffer,
394 current: self.tail,
395 remaining: self.len(),
396 }
397 }
398}
399
400impl<T: Copy, const N: usize> Default for CircularBuffer<T, N> {
401 fn default() -> Self {
402 Self::new()
403 }
404}
405
406pub struct CircularBufferIter<'a, T: Copy, const N: usize> {
408 buffer: &'a [Option<T>; N],
409 current: usize,
410 remaining: usize,
411}
412
413impl<'a, T: Copy, const N: usize> Iterator for CircularBufferIter<'a, T, N> {
414 type Item = T;
415
416 fn next(&mut self) -> Option<Self::Item> {
417 if self.remaining == 0 {
418 return None;
419 }
420
421 let item = self.buffer[self.current]?;
422 self.current = (self.current + 1) % N;
423 self.remaining -= 1;
424 Some(item)
425 }
426
427 fn size_hint(&self) -> (usize, Option<usize>) {
428 (self.remaining, Some(self.remaining))
429 }
430}
431
432impl<'a, T: Copy, const N: usize> ExactSizeIterator for CircularBufferIter<'a, T, N> {}
433
434#[macro_export]
446macro_rules! heapless_string {
447 ($s:expr) => {
448 $crate::heapless_utils::string::try_from_str($s)
449 };
450 ($s:expr, $n:expr) => {
451 heapless::String::<$n>::try_from($s)
452 };
453}
454
455#[macro_export]
467macro_rules! heapless_vec {
468 ($($item:expr),* $(,)?) => {{
469 let mut vec = heapless::Vec::new();
470 $(
471 let _ = vec.push($item);
472 )*
473 vec
474 }};
475 ($item:expr; $count:expr) => {{
476 let mut vec = heapless::Vec::new();
477 for _ in 0..$count {
478 let _ = vec.push($item);
479 }
480 vec
481 }};
482}
483
484pub struct HeaplessConfig {
486 pub max_string_length: usize,
488 pub max_small_vec_capacity: usize,
490 pub max_data_points: usize,
492 pub max_series_count: usize,
494}
495
496impl HeaplessConfig {
497 pub const ULTRA: Self = Self {
499 max_string_length: 8,
500 max_small_vec_capacity: 4,
501 max_data_points: 16,
502 max_series_count: 2,
503 };
504
505 pub const SMALL: Self = Self {
507 max_string_length: 16,
508 max_small_vec_capacity: 8,
509 max_data_points: 64,
510 max_series_count: 4,
511 };
512
513 pub const MEDIUM: Self = Self {
515 max_string_length: 32,
516 max_small_vec_capacity: 16,
517 max_data_points: 256,
518 max_series_count: 8,
519 };
520
521 pub const LARGE: Self = Self {
523 max_string_length: 64,
524 max_small_vec_capacity: 32,
525 max_data_points: 512,
526 max_series_count: 16,
527 };
528
529 pub const fn default() -> &'static Self {
531 #[cfg(feature = "minimal-memory")]
532 return &Self::ULTRA;
533
534 #[cfg(all(feature = "static-only", not(feature = "minimal-memory")))]
535 return &Self::SMALL;
536
537 #[cfg(all(
538 not(feature = "static-only"),
539 not(feature = "minimal-memory"),
540 feature = "no_std"
541 ))]
542 return &Self::MEDIUM;
543
544 #[cfg(all(
545 feature = "std",
546 not(any(feature = "static-only", feature = "minimal-memory"))
547 ))]
548 return &Self::LARGE;
549
550 #[cfg(not(any(
551 feature = "minimal-memory",
552 feature = "static-only",
553 feature = "no_std",
554 feature = "std"
555 )))]
556 return &Self::MEDIUM;
557 }
558}
559
560#[cfg(test)]
561mod tests {
562 use super::*;
563
564 #[test]
565 fn test_string_utilities() {
566 let result: DataResult<String<16>> = string::try_from_str("Hello");
568 assert!(result.is_ok());
569 assert_eq!(result.unwrap().as_str(), "Hello");
570
571 let result: DataResult<String<8>> = string::try_from_str("This is too long");
573 assert!(result.is_err());
574
575 let truncated = string::from_str_truncate::<8>("This is too long");
577 assert_eq!(truncated.as_str(), "This is ");
578
579 let number_str = string::format_number::<16>(123.45, 2);
581 assert!(number_str.as_str() == "123.45" || number_str.as_str() == "123.44");
583 }
584
585 #[test]
586 fn test_vec_utilities() {
587 let mut vec: Vec<i32, 8> = Vec::new();
588
589 assert!(vec::push_safe(&mut vec, 42).is_ok());
591 assert_eq!(vec.len(), 1);
592 assert_eq!(vec[0], 42);
593
594 assert!(vec::extend_safe(&mut vec, [1, 2, 3]).is_ok());
596 assert_eq!(vec.len(), 4);
597
598 vec::insertion_sort(&mut vec);
600 assert_eq!(vec.as_slice(), &[1, 2, 3, 42]);
601 }
602
603 #[test]
604 fn test_circular_buffer() {
605 let mut buffer: CircularBuffer<i32, 4> = CircularBuffer::new();
606
607 assert!(buffer.is_empty());
608 assert_eq!(buffer.len(), 0);
609
610 buffer.push(1);
612 buffer.push(2);
613 buffer.push(3);
614 assert_eq!(buffer.len(), 3);
615
616 buffer.push(4);
618 assert!(buffer.is_full());
619 assert_eq!(buffer.len(), 4);
620
621 buffer.push(5);
623 assert_eq!(buffer.len(), 4);
624
625 let items: Vec<i32, 4> = buffer.iter().collect();
627 assert_eq!(items.as_slice(), &[2, 3, 4, 5]);
628 }
629
630 #[test]
631 fn test_memory_pool() {
632 let mut pool: HeaplessPool<String<16>, 4> = HeaplessPool::new();
633
634 assert!(pool.is_empty());
635 assert!(!pool.is_full());
636
637 let idx1 = pool.allocate(String::try_from("Hello").unwrap()).unwrap();
639 let idx2 = pool.allocate(String::try_from("World").unwrap()).unwrap();
640
641 assert_eq!(pool.allocated_count(), 2);
642 assert_eq!(pool.get(idx1).unwrap().as_str(), "Hello");
643 assert_eq!(pool.get(idx2).unwrap().as_str(), "World");
644
645 let item = pool.deallocate(idx1).unwrap();
647 assert_eq!(item.as_str(), "Hello");
648 assert_eq!(pool.allocated_count(), 1);
649 }
650
651 #[test]
652 fn test_heapless_config() {
653 let config = HeaplessConfig::default();
654 assert!(config.max_string_length > 0);
655 assert!(config.max_data_points > 0);
656 }
657}