Skip to main content

kevy_resp/
argv.rs

1//! The parsed-command argument vector. Two-allocation layout so a SET command
2//! drops from 4 mallocs (Vec of Vec) to 2 (one buffer, one offset table).
3
4/// A parsed command's argument vector.
5///
6/// Stored in **two allocations** — all argument bytes concatenated in `buf`,
7/// with `ends[i]` the end offset of argument `i` — instead of the `N+1` a
8/// `Vec<Vec<u8>>` needs (one outer `Vec` plus one per argument). Parsing a SET
9/// drops from 4 allocations to 2. It is `Send` (two `Vec`s), so the
10/// thread-per-core runtime still forwards it across cores by value.
11///
12/// Index/`get`/`first`/`iter` return `&[u8]` argument slices. It compares equal
13/// to a `Vec<Vec<u8>>` of the same arguments, so call sites and tests read
14/// naturally.
15#[derive(Clone, Default, Debug, Eq)]
16pub struct Argv {
17    buf: Vec<u8>,
18    ends: Vec<u32>,
19}
20
21impl Argv {
22    /// An empty argv, pre-sizing for `argc` args totalling `bytes` bytes.
23    pub fn with_capacity(argc: usize, bytes: usize) -> Self {
24        Argv {
25            buf: Vec::with_capacity(bytes),
26            ends: Vec::with_capacity(argc),
27        }
28    }
29
30    /// Drop all args while keeping the buf + ends capacity. Used by the
31    /// reactor's per-command scratch `Argv`: `parse_command_into` clears
32    /// then refills, so the hot path's malloc rate drops to ~0.
33    #[inline]
34    pub fn clear(&mut self) {
35        self.buf.clear();
36        self.ends.clear();
37    }
38
39    /// Reserve room for `argc` args totalling `bytes` bytes on top of what is
40    /// already there (no shrink).
41    #[inline]
42    pub fn reserve_for(&mut self, argc: usize, bytes: usize) {
43        self.buf.reserve(bytes);
44        self.ends.reserve(argc);
45    }
46
47    /// Capacity of the concatenated-bytes buffer. Drives [`crate::ArgvPool`]'s
48    /// retention policy (don't keep a huge one-off buffer alive in the pool).
49    pub(crate) fn buf_capacity(&self) -> usize {
50        self.buf.capacity()
51    }
52
53    /// Append one argument.
54    pub fn push(&mut self, arg: &[u8]) {
55        self.buf.extend_from_slice(arg);
56        self.ends.push(self.buf.len() as u32);
57    }
58
59    /// Number of arguments.
60    pub fn len(&self) -> usize {
61        self.ends.len()
62    }
63
64    /// Whether there are no arguments.
65    pub fn is_empty(&self) -> bool {
66        self.ends.is_empty()
67    }
68
69    /// Argument `i` as a byte slice, or `None` if out of range.
70    pub fn get(&self, i: usize) -> Option<&[u8]> {
71        let end = *self.ends.get(i)? as usize;
72        let start = if i == 0 { 0 } else { self.ends[i - 1] as usize };
73        Some(&self.buf[start..end])
74    }
75
76    /// The first argument (the command name), or `None` if empty.
77    pub fn first(&self) -> Option<&[u8]> {
78        self.get(0)
79    }
80
81    /// Iterate the arguments as byte slices.
82    pub fn iter(&self) -> impl Iterator<Item = &[u8]> {
83        (0..self.len()).map(move |i| self.get(i).expect("in range"))
84    }
85}
86
87impl core::ops::Index<usize> for Argv {
88    type Output = [u8];
89    fn index(&self, i: usize) -> &[u8] {
90        self.get(i).expect("argv index out of bounds")
91    }
92}
93
94/// Compare to a `Vec<Vec<u8>>` of the same arguments (keeps call sites + tests
95/// that build the expected value as a vec-of-vecs readable).
96impl PartialEq<Vec<Vec<u8>>> for Argv {
97    fn eq(&self, other: &Vec<Vec<u8>>) -> bool {
98        self.len() == other.len() && self.iter().zip(other).all(|(a, b)| a == b.as_slice())
99    }
100}
101
102impl PartialEq for Argv {
103    fn eq(&self, other: &Argv) -> bool {
104        self.buf == other.buf && self.ends == other.ends
105    }
106}
107
108/// Build from a vec-of-vecs (test/embedding convenience; the wire path uses
109/// [`parse_command`](crate::parse_command), which builds an [`Argv`] directly
110/// without the intermediate allocations).
111impl From<Vec<Vec<u8>>> for Argv {
112    fn from(v: Vec<Vec<u8>>) -> Self {
113        let mut a = Argv::with_capacity(v.len(), v.iter().map(Vec::len).sum());
114        for arg in &v {
115            a.push(arg);
116        }
117        a
118    }
119}
120
121/// A parsed command: `argv`, where `argv[0]` is the command name.
122pub type Command = Argv;
123
124#[cfg(test)]
125mod tests {
126    use super::*;
127
128    #[test]
129    fn with_capacity_preallocates_both_buffers() {
130        let a: Argv = Argv::with_capacity(4, 32);
131        assert_eq!(a.len(), 0);
132        assert!(a.is_empty());
133        // Underlying Vecs reserve at least what we asked for (no shrink).
134        assert!(a.buf.capacity() >= 32);
135        assert!(a.ends.capacity() >= 4);
136    }
137
138    #[test]
139    fn clear_keeps_capacity_resets_len() {
140        let mut a = Argv::default();
141        a.push(b"foo");
142        a.push(b"barbaz");
143        assert_eq!(a.len(), 2);
144        let cap_buf = a.buf.capacity();
145        let cap_ends = a.ends.capacity();
146        a.clear();
147        assert_eq!(a.len(), 0);
148        assert!(a.is_empty());
149        // Capacities preserved (the reuse contract the hot reactor relies on).
150        assert_eq!(a.buf.capacity(), cap_buf);
151        assert_eq!(a.ends.capacity(), cap_ends);
152    }
153
154    #[test]
155    fn reserve_for_grows_to_at_least_requested() {
156        let mut a = Argv::with_capacity(1, 4);
157        a.reserve_for(8, 64);
158        assert!(a.buf.capacity() >= 64);
159        assert!(a.ends.capacity() >= 8);
160        // reserve_for never shrinks.
161        a.reserve_for(0, 0);
162        assert!(a.buf.capacity() >= 64);
163        assert!(a.ends.capacity() >= 8);
164    }
165
166    #[test]
167    fn push_get_iter_first_round_trip() {
168        let mut a = Argv::default();
169        a.push(b"SET");
170        a.push(b"key");
171        a.push(b"value");
172        assert_eq!(a.len(), 3);
173        assert_eq!(a.first(), Some(b"SET" as &[u8]));
174        assert_eq!(a.get(0), Some(b"SET" as &[u8]));
175        assert_eq!(a.get(1), Some(b"key" as &[u8]));
176        assert_eq!(a.get(2), Some(b"value" as &[u8]));
177        assert_eq!(a.get(3), None);
178        let collected: Vec<&[u8]> = a.iter().collect();
179        assert_eq!(collected, vec![b"SET" as &[u8], b"key", b"value"]);
180    }
181
182    #[test]
183    fn first_on_empty_returns_none() {
184        let a = Argv::default();
185        assert_eq!(a.first(), None);
186        assert_eq!(a.get(0), None);
187    }
188
189    #[test]
190    fn index_returns_correct_slice() {
191        let mut a = Argv::default();
192        a.push(b"hi");
193        a.push(b"there");
194        assert_eq!(&a[0], b"hi" as &[u8]);
195        assert_eq!(&a[1], b"there" as &[u8]);
196    }
197
198    #[test]
199    #[should_panic(expected = "argv index out of bounds")]
200    fn index_out_of_bounds_panics() {
201        let a = Argv::default();
202        let _ = &a[0];
203    }
204
205    #[test]
206    fn eq_against_vec_of_vec() {
207        let mut a = Argv::default();
208        a.push(b"PING");
209        a.push(b"hello");
210        assert_eq!(a, vec![b"PING".to_vec(), b"hello".to_vec()]);
211        assert_ne!(a, vec![b"PING".to_vec()]);
212        assert_ne!(a, vec![b"PING".to_vec(), b"world".to_vec()]);
213    }
214
215    #[test]
216    fn eq_argv_vs_argv() {
217        let mut a = Argv::default();
218        a.push(b"A");
219        a.push(b"B");
220        let mut b = Argv::default();
221        b.push(b"A");
222        b.push(b"B");
223        let mut c = Argv::default();
224        c.push(b"A");
225        c.push(b"C");
226        assert_eq!(a, b);
227        assert_ne!(a, c);
228    }
229
230    #[test]
231    fn from_vec_of_vec_preserves_args() {
232        let v = vec![b"GET".to_vec(), b"my-key".to_vec()];
233        let a: Argv = Argv::from(v.clone());
234        assert_eq!(a.len(), 2);
235        assert_eq!(a, v);
236        // From<Vec<Vec<u8>>> reserves exactly the right total size.
237        assert_eq!(a.buf.len(), 3 + 6);
238    }
239
240    #[test]
241    fn clone_makes_independent_argv() {
242        let mut a = Argv::default();
243        a.push(b"X");
244        let b = a.clone();
245        assert_eq!(a, b);
246        // mutating original doesn't affect clone.
247        a.push(b"Y");
248        assert_ne!(a, b);
249        assert_eq!(b.len(), 1);
250    }
251}