gcode/buffers.rs
1//! Buffer Management.
2//!
3//! This module is mainly intended for use cases when the amount of space that
4//! can be consumed by buffers needs to be defined at compile time. For most
5//! users, the [`DefaultBuffers`] alias should be suitable.
6//!
7//! For most end users it is probably simpler to determine a "good enough"
8//! buffer size and create type aliases of [`GCode`] and [`Line`] for that size.
9
10use crate::{Comment, GCode, Word};
11use arrayvec::{Array, ArrayVec};
12use core::{
13 fmt::{self, Debug, Display, Formatter},
14 marker::PhantomData,
15};
16
17#[allow(unused_imports)] // for rustdoc links
18use crate::Line;
19
20cfg_if::cfg_if! {
21 if #[cfg(feature = "std")] {
22 /// The default buffer type for this platform.
23 ///
24 /// This is a type alias for [`VecBuffers`] because the crate is compiled
25 /// with the *"std"* feature.
26 pub type DefaultBuffers = VecBuffers;
27
28 /// The default [`Buffer`] to use for a [`GCode`]'s arguments.
29 ///
30 /// This is a type alias for [`Vec<Word>`] because the crate is compiled
31 /// with the *"std"* feature.
32 pub type DefaultArguments = Vec<Word>;
33 } else {
34 /// The default buffer type for this platform.
35 ///
36 /// This is a type alias for [`SmallFixedBuffers`] because the crate is compiled
37 /// without the *"std"* feature.
38 pub type DefaultBuffers = SmallFixedBuffers;
39
40 /// The default [`Buffer`] to use for a [`GCode`]'s arguments.
41 ///
42 /// This is a type alias for [`ArrayVec`] because the crate is compiled
43 /// without the *"std"* feature.
44 pub type DefaultArguments = ArrayVec<[Word; 5]>;
45 }
46}
47
48/// A set of type aliases defining the types to use when storing data.
49pub trait Buffers<'input> {
50 /// The [`Buffer`] used to store [`GCode`] arguments.
51 type Arguments: Buffer<Word> + Default;
52 /// The [`Buffer`] used to store [`GCode`]s.
53 type Commands: Buffer<GCode<Self::Arguments>> + Default;
54 /// The [`Buffer`] used to store [`Comment`]s.
55 type Comments: Buffer<Comment<'input>> + Default;
56}
57
58/// Something which can store items sequentially in memory. This doesn't
59/// necessarily require dynamic memory allocation.
60pub trait Buffer<T> {
61 /// Try to add another item to this [`Buffer`], returning the item if there
62 /// is no more room.
63 fn try_push(&mut self, item: T) -> Result<(), CapacityError<T>>;
64
65 /// The items currently stored in the [`Buffer`].
66 fn as_slice(&self) -> &[T];
67}
68
69impl<T, A: Array<Item = T>> Buffer<T> for ArrayVec<A> {
70 fn try_push(&mut self, item: T) -> Result<(), CapacityError<T>> {
71 ArrayVec::try_push(self, item).map_err(|e| CapacityError(e.element()))
72 }
73
74 fn as_slice(&self) -> &[T] { &self }
75}
76
77/// The smallest usable set of [`Buffers`].
78///
79/// ```rust
80/// # use gcode::{Line, GCode, buffers::{Buffers, SmallFixedBuffers}};
81/// let line_size = std::mem::size_of::<Line<'_, SmallFixedBuffers>>();
82/// assert!(line_size <= 350, "Got {}", line_size);
83///
84/// // the explicit type for a `GCode` backed by `SmallFixedBuffers`
85/// type SmallBufferGCode<'a> = GCode<<SmallFixedBuffers as Buffers<'a>>::Arguments>;
86///
87/// let gcode_size = std::mem::size_of::<SmallBufferGCode<'_>>();
88/// assert!(gcode_size <= 250, "Got {}", gcode_size);
89/// ```
90#[derive(Debug, Copy, Clone, PartialEq)]
91pub enum SmallFixedBuffers {}
92
93impl<'input> Buffers<'input> for SmallFixedBuffers {
94 type Arguments = DefaultArguments;
95 type Commands = ArrayVec<[GCode<Self::Arguments>; 1]>;
96 type Comments = ArrayVec<[Comment<'input>; 1]>;
97}
98
99with_std! {
100 /// A [`Buffers`] implementation which uses [`std::vec::Vec`] for storing items.
101 ///
102 /// In terms of memory usage, this has the potential to use a lot less overall
103 /// than something like [`SmallFixedBuffers`] because we've traded deterministic
104 /// memory usage for only allocating memory when it is required.
105 #[derive(Debug, Copy, Clone, PartialEq)]
106 pub enum VecBuffers {}
107
108 impl<'input> Buffers<'input> for VecBuffers {
109 type Arguments = DefaultArguments;
110 type Commands = Vec<GCode<Self::Arguments>>;
111 type Comments = Vec<Comment<'input>>;
112 }
113
114 impl<T> Buffer<T> for Vec<T> {
115 fn try_push(&mut self, item: T) -> Result<(), CapacityError<T>> {
116 self.push(item);
117 Ok(())
118 }
119
120 fn as_slice(&self) -> &[T] { &self }
121 }
122}
123
124/// An error returned when [`Buffer::try_push()`] fails.
125///
126/// When a [`Buffer`] can't add an item, it will use [`CapacityError`] to pass
127/// the original item back to the caller.
128#[derive(Debug, Copy, Clone, PartialEq)]
129pub struct CapacityError<T>(pub T);
130
131impl<T: Debug> Display for CapacityError<T> {
132 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
133 write!(f, "insufficient capacity")
134 }
135}
136
137with_std! {
138 impl<T: Debug> std::error::Error for CapacityError<T> {}
139}
140
141/// Debug *any* [`Buffer`] when the item is [`Debug`].
142pub(crate) fn debug<'a, T, B>(buffer: &'a B) -> impl Debug + 'a
143where
144 B: Buffer<T> + 'a,
145 T: Debug + 'a,
146{
147 DebugBuffer::new(buffer)
148}
149
150struct DebugBuffer<'a, B, T> {
151 buffer: &'a B,
152 _item: PhantomData<&'a T>,
153}
154
155impl<'a, T, B: Buffer<T>> DebugBuffer<'a, B, T> {
156 fn new(buffer: &'a B) -> Self {
157 DebugBuffer {
158 buffer,
159 _item: PhantomData,
160 }
161 }
162}
163
164impl<'a, B, T> Debug for DebugBuffer<'a, B, T>
165where
166 B: Buffer<T>,
167 T: Debug,
168{
169 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
170 let entries =
171 self.buffer.as_slice().iter().map(|item| item as &dyn Debug);
172
173 f.debug_list().entries(entries).finish()
174 }
175}