Skip to main content

samp_sdk/cell/
buffer.rs

1//! AMX cell vectors (Pawn arrays) — `Buffer` (sized) and
2//! `UnsizedBuffer` (unsized, received as a native argument).
3
4use std::ops::{Deref, DerefMut};
5
6use super::{AmxCell, Ref};
7use crate::amx::Amx;
8use crate::cell::repr::CellConvert;
9use crate::cell::string;
10use crate::error::AmxResult;
11
12/// AMX cell array with a known size.
13///
14/// Implements [`Deref<Target = [i32]>`], so the full `&[i32]` API is
15/// available (`iter`, `len`, indexing, etc). For non-`i32` types (`f32`,
16/// `bool`, etc), use [`iter_as`], [`get_as`], [`set_as`].
17///
18/// # Example
19/// ```
20/// use samp_sdk::cell::{UnsizedBuffer, Buffer};
21/// # use samp_sdk::amx::Amx;
22/// fn double_all(amx: &Amx, buffer: UnsizedBuffer, size: usize) {
23///     let mut buffer: Buffer = buffer.into_sized_buffer(size);
24///     buffer.iter_mut().for_each(|cell| *cell *= 2);
25/// }
26/// ```
27///
28/// [`iter_as`]: Buffer::iter_as
29/// [`get_as`]: Buffer::get_as
30/// [`set_as`]: Buffer::set_as
31pub struct Buffer<'amx> {
32    inner: Ref<'amx, i32>,
33    len: usize,
34}
35
36impl<'amx> Buffer<'amx> {
37    /// Builds a `Buffer` from the `Ref` to the first cell and its size.
38    #[must_use]
39    pub fn new(reference: Ref<'amx, i32>, len: usize) -> Buffer<'amx> {
40        Buffer {
41            inner: reference,
42            len,
43        }
44    }
45
46    /// Number of cells in the buffer.
47    #[must_use]
48    pub fn len(&self) -> usize {
49        self.len
50    }
51
52    /// `true` if the buffer has no cells.
53    #[must_use]
54    pub fn is_empty(&self) -> bool {
55        self.len == 0
56    }
57
58    /// Read-only slice covering every cell.
59    #[inline]
60    #[must_use]
61    pub fn as_slice(&self) -> &[i32] {
62        unsafe { std::slice::from_raw_parts(self.inner.as_ptr(), self.len) }
63    }
64
65    /// Mutable slice covering every cell.
66    #[inline]
67    pub fn as_mut_slice(&mut self) -> &mut [i32] {
68        unsafe { std::slice::from_raw_parts_mut(self.inner.as_mut_ptr(), self.len) }
69    }
70
71    /// Iterator that converts each cell to `T` via [`CellConvert`].
72    ///
73    /// Idiomatic ergonomics for arrays of `f32`, `bool` etc. — combine with
74    /// iterator adapters (`sum`, `filter_map`, ...).
75    ///
76    /// ```rust,no_run
77    /// # use samp_sdk::cell::Buffer;
78    /// fn sum_floats(buf: &Buffer) -> f32 { buf.iter_as::<f32>().sum() }
79    /// ```
80    pub fn iter_as<T: CellConvert>(&self) -> impl Iterator<Item = T> + '_ {
81        self.as_slice().iter().map(|&raw| T::from_cell(raw))
82    }
83
84    /// Reads the cell at `index`, converting to `T`. `None` if out of bounds.
85    #[must_use]
86    pub fn get_as<T: CellConvert>(&self, index: usize) -> Option<T> {
87        self.as_slice().get(index).map(|&raw| T::from_cell(raw))
88    }
89
90    /// Converts `value` to a raw cell and writes it at `index`.
91    ///
92    /// Returns `true` if the write happened, `false` if `index` was out of bounds.
93    pub fn set_as<T: CellConvert>(&mut self, index: usize, value: T) -> bool {
94        if let Some(cell) = self.as_mut_slice().get_mut(index) {
95            *cell = value.into_cell();
96            true
97        } else {
98            false
99        }
100    }
101
102    /// Writes a Rust string into the buffer (unpacked format, `0` terminator).
103    ///
104    /// Requires `s.len() + 1` cells of space.
105    ///
106    /// # Errors
107    /// `AmxError::General` if the encoded string is >= the buffer size.
108    pub fn write_str(&mut self, s: &str) -> AmxResult<()> {
109        string::put_in_buffer(self, s)
110    }
111}
112
113// `Buffer` cannot be parsed directly from a cell — use `UnsizedBuffer`
114// as the native argument and then `.into_sized_buffer(len)`.
115impl<'amx> AmxCell<'amx> for Buffer<'amx> {
116    #[inline]
117    fn as_cell(&self) -> i32 {
118        self.inner.as_cell()
119    }
120}
121
122impl Deref for Buffer<'_> {
123    type Target = [i32];
124
125    fn deref(&self) -> &[i32] {
126        self.as_slice()
127    }
128}
129
130impl DerefMut for Buffer<'_> {
131    fn deref_mut(&mut self) -> &mut [i32] {
132        self.as_mut_slice()
133    }
134}
135
136impl std::fmt::Debug for Buffer<'_> {
137    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
138        write!(f, "{:?}", self.as_slice())
139    }
140}
141
142/// Array with unknown size — received as a native argument when the
143/// Pawn signature is `array[]` without a fixed dimension.
144///
145/// The actual size usually comes as another parameter (`sizeof(array)`). Use
146/// [`into_sized_buffer`] to convert into [`Buffer`] before iterating.
147///
148/// [`into_sized_buffer`]: UnsizedBuffer::into_sized_buffer
149pub struct UnsizedBuffer<'amx> {
150    inner: Ref<'amx, i32>,
151}
152
153impl<'amx> UnsizedBuffer<'amx> {
154    /// Converts into `Buffer` by declaring the size.
155    ///
156    /// `len` must be <= the actual number of allocated cells — larger values
157    /// cause UB when accessing cells outside the Pawn array. The SDK caps it
158    /// at 1 MiB as a defense against a corrupted `len` from the script.
159    #[must_use]
160    pub fn into_sized_buffer(self, len: usize) -> Buffer<'amx> {
161        const MAX_BUFFER_CELLS: usize = 1024 * 1024;
162        debug_assert!(
163            len <= MAX_BUFFER_CELLS,
164            "into_sized_buffer() received len={len} above the {MAX_BUFFER_CELLS} limit"
165        );
166        let len = len.min(MAX_BUFFER_CELLS);
167        Buffer::new(self.inner, len)
168    }
169
170    /// Pointer to the first cell.
171    #[inline]
172    #[must_use]
173    pub fn as_ptr(&self) -> *const i32 {
174        self.inner.as_ptr()
175    }
176
177    /// Mutable pointer to the first cell.
178    #[inline]
179    pub fn as_mut_ptr(&mut self) -> *mut i32 {
180        self.inner.as_mut_ptr()
181    }
182
183    /// Constructor for tests/benchmarks — not part of the stable API.
184    #[doc(hidden)]
185    #[must_use]
186    pub fn from_raw_parts(inner: Ref<'amx, i32>) -> Self {
187        UnsizedBuffer { inner }
188    }
189
190    /// Sizes the buffer to `max_len` and writes `s` in one call.
191    ///
192    /// Equivalent to `into_sized_buffer(max_len).write_str(s)`. This is the
193    /// recommended way to fill an output string in natives.
194    ///
195    /// # Errors
196    /// `AmxError::General` if the encoded `s` is >= `max_len` (no room for
197    /// the `0` terminator).
198    pub fn write_str(self, max_len: usize, s: &str) -> AmxResult<()> {
199        let mut buf = self.into_sized_buffer(max_len);
200        string::put_in_buffer(&mut buf, s)
201    }
202}
203
204impl<'amx> AmxCell<'amx> for UnsizedBuffer<'amx> {
205    fn from_raw(amx: &'amx Amx, cell: i32) -> AmxResult<UnsizedBuffer<'amx>> {
206        Ok(UnsizedBuffer {
207            inner: amx.get_ref(cell)?,
208        })
209    }
210
211    #[inline]
212    fn as_cell(&self) -> i32 {
213        self.inner.as_cell()
214    }
215}
216
217#[cfg(test)]
218mod tests {
219    use super::*;
220    use crate::cell::Ref;
221    use crate::cell::repr::CellConvert;
222
223    fn make_ref(data: &mut Vec<i32>) -> Ref<'_, i32> {
224        unsafe { Ref::new(0, data.as_mut_ptr()) }
225    }
226
227    fn make_buffer(data: &mut Vec<i32>) -> Buffer<'_> {
228        let len = data.len();
229        let r = make_ref(data);
230        Buffer::new(r, len)
231    }
232
233    fn make_unsized(data: &mut Vec<i32>) -> UnsizedBuffer<'_> {
234        UnsizedBuffer {
235            inner: make_ref(data),
236        }
237    }
238
239    // --- Buffer ---
240
241    #[test]
242    fn buffer_len_and_is_empty() {
243        let mut data = vec![0i32; 4];
244        let buf = make_buffer(&mut data);
245        assert_eq!(buf.len(), 4);
246        assert!(!buf.is_empty());
247
248        let mut empty = vec![];
249        let empty_buf = make_buffer(&mut empty);
250        assert_eq!(empty_buf.len(), 0);
251        assert!(empty_buf.is_empty());
252    }
253
254    #[test]
255    fn buffer_deref_reads_values() {
256        let mut data = vec![10i32, 20, 30];
257        let buf = make_buffer(&mut data);
258        assert_eq!(&buf[..], &[10, 20, 30]);
259        assert_eq!(buf[0], 10);
260        assert_eq!(buf[2], 30);
261    }
262
263    #[test]
264    fn buffer_deref_mut_writes_values() {
265        let mut data = vec![0i32; 3];
266        let mut buf = make_buffer(&mut data);
267        buf[0] = 100;
268        buf[1] = 200;
269        buf[2] = 300;
270        assert_eq!(&data, &[100, 200, 300]);
271    }
272
273    #[test]
274    fn buffer_iter_works() {
275        let mut data = vec![1i32, 2, 3, 4];
276        let buf = make_buffer(&mut data);
277        let sum: i32 = buf.iter().sum();
278        assert_eq!(sum, 10);
279    }
280
281    #[test]
282    fn buffer_iter_mut_modifies_in_place() {
283        let mut data = vec![1i32, 2, 3];
284        let mut buf = make_buffer(&mut data);
285        buf.iter_mut().for_each(|x| *x *= 2);
286        assert_eq!(&data, &[2, 4, 6]);
287    }
288
289    #[test]
290    fn buffer_debug_format() {
291        let mut data = vec![1i32, 2, 3];
292        let buf = make_buffer(&mut data);
293        assert_eq!(format!("{buf:?}"), "[1, 2, 3]");
294    }
295
296    #[test]
297    fn buffer_as_cell_returns_amx_addr() {
298        let mut data = vec![0i32; 4];
299        let buf = make_buffer(&mut data);
300        // as_cell() returns the AMX address of the inner Ref (0 in our helper)
301        assert_eq!(buf.as_cell(), 0);
302    }
303
304    // --- UnsizedBuffer ---
305
306    #[test]
307    fn unsized_into_sized_normal_len() {
308        let mut data = vec![1i32, 2, 3, 4, 5];
309        let ub = make_unsized(&mut data);
310        let buf = ub.into_sized_buffer(3);
311        assert_eq!(buf.len(), 3);
312        assert_eq!(buf[0], 1);
313        assert_eq!(buf[2], 3);
314    }
315
316    /// In debug, `debug_assert!` fires for values above the limit.
317    /// In release, the value is silently clamped.
318    #[test]
319    #[cfg_attr(
320        debug_assertions,
321        should_panic(expected = "into_sized_buffer() received len=")
322    )]
323    fn unsized_into_sized_clamps_to_max_in_release() {
324        let mut data = vec![0i32; 8];
325        let ub = make_unsized(&mut data);
326        let buf = ub.into_sized_buffer(1024 * 1024 + 1);
327        // Only reaches here in release — verifies the clamp
328        assert_eq!(buf.len(), 1024 * 1024);
329    }
330
331    #[test]
332    fn unsized_into_sized_at_exact_max() {
333        let mut data = vec![0i32; 8];
334        let ub = make_unsized(&mut data);
335        let buf = ub.into_sized_buffer(1024 * 1024);
336        assert_eq!(buf.len(), 1024 * 1024);
337    }
338
339    #[test]
340    fn unsized_as_ptr_not_null() {
341        let mut data = vec![42i32];
342        let ub = make_unsized(&mut data);
343        assert!(!ub.as_ptr().is_null());
344    }
345
346    #[test]
347    fn unsized_as_cell_returns_amx_addr() {
348        let mut data = vec![0i32];
349        let ub = make_unsized(&mut data);
350        assert_eq!(ub.as_cell(), 0);
351    }
352
353    // --- Buffer::get_as / set_as ---
354
355    #[test]
356    fn get_as_i32_reads_value() {
357        let mut data = vec![10i32, 20, 30];
358        let buf = make_buffer(&mut data);
359        assert_eq!(buf.get_as::<i32>(0), Some(10));
360        assert_eq!(buf.get_as::<i32>(2), Some(30));
361    }
362
363    #[test]
364    fn get_as_out_of_bounds_returns_none() {
365        let mut data = vec![1i32, 2];
366        let buf = make_buffer(&mut data);
367        assert_eq!(buf.get_as::<i32>(2), None);
368        assert_eq!(buf.get_as::<i32>(99), None);
369    }
370
371    #[test]
372    fn set_as_i32_writes_value() {
373        let mut data = vec![0i32; 3];
374        let mut buf = make_buffer(&mut data);
375        assert!(buf.set_as(1, 42i32));
376        assert_eq!(data[1], 42);
377    }
378
379    #[test]
380    fn set_as_out_of_bounds_returns_false() {
381        let mut data = vec![0i32; 2];
382        let mut buf = make_buffer(&mut data);
383        assert!(!buf.set_as(5, 99i32));
384    }
385
386    #[test]
387    fn get_as_f32_roundtrip() {
388        let value = 1.5f32; // exact IEEE-754 value, no approx_constant risk
389        let mut data = vec![value.into_cell()];
390        let buf = make_buffer(&mut data);
391        let recovered: f32 = buf.get_as::<f32>(0).unwrap();
392        assert!(
393            (recovered - value).abs() < f32::EPSILON,
394            "f32 roundtrip failed: {recovered} != {value}"
395        );
396    }
397
398    #[test]
399    fn set_as_f32_stores_bits_correctly() {
400        let mut data = vec![0i32];
401        let mut buf = make_buffer(&mut data);
402        buf.set_as(0, 1.5f32);
403        assert_eq!(data[0].cast_unsigned(), 1.5f32.to_bits());
404    }
405
406    #[test]
407    fn get_as_bool_true_and_false() {
408        let mut data = vec![1i32, 0, 42];
409        let buf = make_buffer(&mut data);
410        assert_eq!(buf.get_as::<bool>(0), Some(true));
411        assert_eq!(buf.get_as::<bool>(1), Some(false));
412        // any non-zero value is true
413        assert_eq!(buf.get_as::<bool>(2), Some(true));
414    }
415
416    #[test]
417    fn set_as_bool_writes_zero_and_one() {
418        let mut data = vec![0i32; 2];
419        let mut buf = make_buffer(&mut data);
420        buf.set_as(0, true);
421        buf.set_as(1, false);
422        assert_eq!(data[0], 1);
423        assert_eq!(data[1], 0);
424    }
425
426    #[test]
427    fn get_as_u8_reads_byte() {
428        let mut data = vec![255i32];
429        let buf = make_buffer(&mut data);
430        assert_eq!(buf.get_as::<u8>(0), Some(255u8));
431    }
432
433    // --- Buffer::iter_as ---
434
435    #[test]
436    fn iter_as_i32_collects_all() {
437        let mut data = vec![1i32, 2, 3, 4];
438        let buf = make_buffer(&mut data);
439        let vals: Vec<i32> = buf.iter_as::<i32>().collect();
440        assert_eq!(vals, vec![1, 2, 3, 4]);
441    }
442
443    #[test]
444    fn iter_as_i32_sum() {
445        let mut data = vec![10i32, 20, 30];
446        let buf = make_buffer(&mut data);
447        let sum: i32 = buf.iter_as::<i32>().sum();
448        assert_eq!(sum, 60);
449    }
450
451    #[test]
452    fn iter_as_f32_roundtrip() {
453        let values = [1.0f32, 2.5, 1.25];
454        let mut data: Vec<i32> = values.iter().map(|&v| v.into_cell()).collect();
455        let buf = make_buffer(&mut data);
456        let recovered: Vec<f32> = buf.iter_as::<f32>().collect();
457        for (orig, got) in values.iter().zip(recovered.iter()) {
458            assert!((orig - got).abs() < f32::EPSILON, "{orig} != {got}");
459        }
460    }
461
462    #[test]
463    fn iter_as_bool_any_and_all() {
464        let mut data = vec![1i32, 0, 1, 1];
465        let buf = make_buffer(&mut data);
466        assert!(buf.iter_as::<bool>().any(|v| !v));
467        assert!(!buf.iter_as::<bool>().all(|v| v));
468    }
469
470    #[test]
471    fn iter_as_empty_buffer() {
472        let mut data: Vec<i32> = vec![];
473        let buf = make_buffer(&mut data);
474        assert_eq!(buf.iter_as::<i32>().count(), 0);
475    }
476
477    #[test]
478    fn iter_as_matches_get_as_loop() {
479        let mut data = vec![10i32, 20, 30, 40];
480        let buf = make_buffer(&mut data);
481        let via_iter: Vec<i32> = buf.iter_as::<i32>().collect();
482        let via_loop: Vec<i32> = (0..buf.len())
483            .filter_map(|i| buf.get_as::<i32>(i))
484            .collect();
485        assert_eq!(via_iter, via_loop);
486    }
487
488    // --- Buffer::write_str ---
489
490    #[test]
491    fn write_str_encodes_string_into_cells() {
492        // "hi" -> cells [104, 105, 0] (h=104, i=105, nul=0)
493        let mut data = vec![0i32; 3];
494        let mut buf = make_buffer(&mut data);
495        assert!(buf.write_str("hi").is_ok());
496        assert_eq!(data[0], i32::from(b'h'));
497        assert_eq!(data[1], i32::from(b'i'));
498        assert_eq!(data[2], 0); // null terminator
499    }
500
501    #[test]
502    fn write_str_empty_string_writes_null_terminator() {
503        let mut data = vec![99i32; 2];
504        let mut buf = make_buffer(&mut data);
505        assert!(buf.write_str("").is_ok());
506        assert_eq!(data[0], 0);
507    }
508
509    #[test]
510    fn write_str_exact_fit_fails() {
511        // A 3-cell buffer cannot hold "abc" (it would need 4: a, b, c, nul)
512        let mut data = vec![0i32; 3];
513        let mut buf = make_buffer(&mut data);
514        assert!(buf.write_str("abc").is_err());
515    }
516
517    // --- UnsizedBuffer::write_str ---
518
519    #[test]
520    fn unsized_write_str_sizes_and_writes() {
521        let mut data = vec![0i32; 5];
522        let ub = make_unsized(&mut data);
523        assert!(ub.write_str(5, "hi").is_ok());
524        assert_eq!(data[0], i32::from(b'h'));
525        assert_eq!(data[1], i32::from(b'i'));
526        assert_eq!(data[2], 0);
527    }
528
529    #[test]
530    fn unsized_write_str_too_long_returns_err() {
531        let mut data = vec![0i32; 3];
532        let ub = make_unsized(&mut data);
533        assert!(ub.write_str(3, "abc").is_err());
534    }
535}