samp_sdk/args.rs
1//! Parsing of arguments from a Pawn native call.
2//!
3//! The AMX delivers arguments in a `*mut i32` pointer where:
4//! - `args[0]` = number of bytes used by the arguments (not the count)
5//! - `args[1..]` = the cells with each argument, in signature order
6//!
7//! This module wraps that indirection and converts each cell to the correct
8//! Rust type via [`AmxCell`].
9
10use crate::amx::Amx;
11use crate::cell::AmxCell;
12
13/// Typed list of arguments for a native function.
14///
15/// Generally the `#[native]` proc macro builds and consumes an `Args` automatically
16/// — call manually only in `raw` natives that receive `(amx, args)` directly.
17pub struct Args<'a> {
18 amx: &'a Amx,
19 params: *const i32,
20 offset: usize,
21}
22
23impl<'a> Args<'a> {
24 /// Builds from the `Amx` and the `args` pointer received by the native.
25 ///
26 /// # Example
27 /// ```
28 /// use samp_sdk::args::Args;
29 /// use samp_sdk::amx::Amx;
30 /// use samp_sdk::cell::AmxString;
31 /// # use samp_sdk::raw::types::AMX;
32 ///
33 /// // native RawNative(const say_that[]);
34 /// extern "C" fn raw_native(amx: *mut AMX, args: *mut i32) -> i32 {
35 /// # let amx_exports = 0;
36 /// let amx = Amx::new(amx, amx_exports);
37 /// let mut args = Args::new(&amx, args);
38 /// let Some(text) = args.next_arg::<AmxString>() else { return 0 };
39 /// println!("RawNative: {}", &*text);
40 /// 1
41 /// }
42 /// ```
43 #[must_use]
44 pub fn new(amx: &'a Amx, params: *const i32) -> Args<'a> {
45 Args {
46 amx,
47 params,
48 offset: 0,
49 }
50 }
51
52 /// Next argument in signature order. `None` at the end of the list.
53 pub fn next_arg<T: AmxCell<'a> + 'a>(&mut self) -> Option<T> {
54 let result = self.get(self.offset);
55 self.offset += 1;
56
57 result
58 }
59
60 /// Argument at position `offset` (zero-indexed). `None` if out of bounds.
61 #[must_use]
62 pub fn get<T: AmxCell<'a> + 'a>(&self, offset: usize) -> Option<T> {
63 if offset >= self.count() {
64 return None;
65 }
66
67 unsafe { T::from_raw(self.amx, self.params.add(offset + 1).read()).ok() }
68 }
69
70 /// Resets the [`next_arg`] cursor back to the start of the list.
71 ///
72 /// [`next_arg`]: Args::next_arg
73 pub fn reset(&mut self) {
74 self.offset = 0;
75 }
76
77 /// How many arguments were received.
78 ///
79 /// Reads from `args[0]` (total bytes) and divides by 4 (cell size).
80 /// Negative or zero values return `0` — defense against dirty pointers.
81 #[must_use]
82 pub fn count(&self) -> usize {
83 let raw = unsafe { self.params.read() };
84 if raw <= 0 {
85 return 0;
86 }
87 // `raw > 0` was validated above; `/ 4` keeps it positive.
88 #[allow(clippy::cast_sign_loss)]
89 let count = (raw / 4) as usize;
90 count
91 }
92}
93
94#[cfg(test)]
95mod tests {
96 use super::*;
97
98 #[test]
99 fn count_with_zero_returns_zero() {
100 let data: [i32; 1] = [0];
101 let amx = Amx::new(std::ptr::null_mut(), 0);
102 let args = Args::new(&amx, data.as_ptr());
103 assert_eq!(args.count(), 0);
104 }
105
106 #[test]
107 fn count_with_negative_returns_zero() {
108 let data: [i32; 1] = [-8];
109 let amx = Amx::new(std::ptr::null_mut(), 0);
110 let args = Args::new(&amx, data.as_ptr());
111 assert_eq!(args.count(), 0);
112 }
113
114 #[test]
115 fn count_with_valid_args() {
116 // 3 arguments = 12 bytes (3 * 4)
117 let data: [i32; 4] = [12, 100, 200, 300];
118 let amx = Amx::new(std::ptr::null_mut(), 0);
119 let args = Args::new(&amx, data.as_ptr());
120 assert_eq!(args.count(), 3);
121 }
122
123 #[test]
124 fn get_out_of_bounds_returns_none() {
125 let data: [i32; 2] = [4, 42]; // 1 argument
126 let amx = Amx::new(std::ptr::null_mut(), 0);
127 let args = Args::new(&amx, data.as_ptr());
128 // offset == count should return None
129 assert!(args.get::<crate::cell::Ref<i32>>(1).is_none());
130 }
131
132 #[test]
133 fn reset_resets_offset() {
134 let data: [i32; 1] = [0];
135 let amx = Amx::new(std::ptr::null_mut(), 0);
136 let mut args = Args::new(&amx, data.as_ptr());
137 args.offset = 5;
138 args.reset();
139 assert_eq!(args.offset, 0);
140 }
141}