Skip to main content

samp_sdk/omp/
types.rs

1//! Primitive types for the Open Multiplayer ABI: `UID`, `SemanticVersion`, `StringView`,
2//! `Colour`, `Vector{2,3,4}`, `ComponentType`.
3//!
4//! All use `#[repr(C)]` to guarantee binary layout identical to the C++ SDK's
5//! `types.hpp` header — do not reorder fields.
6
7/// 64-bit unique identifier of an Open Multiplayer component.
8///
9/// In the SDK, it is resolved in priority order by `samp-codegen`:
10///   1. `uid:` declared explicitly in `initialize_plugin!`
11///   2. `[package.metadata.samp] uid` in `Cargo.toml`
12///   3. FNV-1a 64-bit of `CARGO_PKG_NAME@CARGO_PKG_VERSION` (generated and
13///      written to `Cargo.toml` if none of the above options exist)
14pub type UID = u64;
15
16/// Semantic version major.minor.patch with pre-release support.
17///
18/// 6 bytes in memory (`#[repr(C)]` + `u16` alignment); returned by
19/// `componentVersion()` via hidden pointer on both ABIs.
20#[derive(Debug, Clone, Copy, PartialEq, Eq)]
21#[repr(C)]
22pub struct SemanticVersion {
23    pub major: u8,
24    pub minor: u8,
25    pub patch: u8,
26    pub prerel: u16,
27}
28
29impl SemanticVersion {
30    #[must_use]
31    pub const fn new(major: u8, minor: u8, patch: u8) -> Self {
32        Self {
33            major,
34            minor,
35            patch,
36            prerel: 0,
37        }
38    }
39
40    #[must_use]
41    pub const fn with_prerel(major: u8, minor: u8, patch: u8, prerel: u16) -> Self {
42        Self {
43            major,
44            minor,
45            patch,
46            prerel,
47        }
48    }
49}
50
51/// Non-owning string — `(pointer, length)` pair, no `\0` terminator.
52///
53/// Layout identical to `nonstd::string_view` in the C++ SDK. 8 bytes on x86 32-bit
54/// (ptr 4 + len 4). Returned by `componentName()` via hidden pointer.
55///
56/// `StringView` does not take ownership — the producer guarantees the pointer's
57/// validity for the duration of use.
58#[derive(Clone, Copy)]
59#[repr(C)]
60pub struct StringView {
61    pub data: *const u8,
62    pub len: usize,
63}
64
65impl StringView {
66    /// Creates a `StringView` from a static `&str`.
67    #[must_use]
68    pub fn from_static(s: &'static str) -> Self {
69        Self {
70            data: s.as_ptr(),
71            len: s.len(),
72        }
73    }
74
75    /// Converts to `&str`. Safe only if the pointer is valid and UTF-8.
76    ///
77    /// # Safety
78    /// The pointer must be valid and point to `len` bytes of valid UTF-8
79    /// for the lifetime `'a`.
80    #[must_use]
81    pub unsafe fn as_str<'a>(self) -> &'a str {
82        let slice = unsafe { std::slice::from_raw_parts(self.data, self.len) };
83        // Open Multiplayer guarantees UTF-8 strings on the SDK interfaces
84        unsafe { std::str::from_utf8_unchecked(slice) }
85    }
86
87    /// Converts to `&str` with explicit UTF-8 validation.
88    ///
89    /// Preferable to [`as_str`] when the string content is untrusted or in
90    /// contexts where defensive validation is needed.
91    ///
92    /// # Safety
93    /// The pointer must be valid and point to `len` bytes for the lifetime `'a`.
94    ///
95    /// # Errors
96    /// [`std::str::Utf8Error`] if the pointed-to bytes do not form valid UTF-8.
97    ///
98    /// [`as_str`]: Self::as_str
99    pub unsafe fn try_as_str<'a>(self) -> Result<&'a str, std::str::Utf8Error> {
100        let slice = unsafe { std::slice::from_raw_parts(self.data, self.len) };
101        std::str::from_utf8(slice)
102    }
103}
104
105/// RGBA color.
106///
107/// Equivalent to `Colour` in `types.hpp`.
108#[derive(Debug, Clone, Copy, PartialEq, Eq)]
109#[repr(C)]
110pub struct Colour {
111    pub r: u8,
112    pub g: u8,
113    pub b: u8,
114    pub a: u8,
115}
116
117impl Colour {
118    #[must_use]
119    pub const fn rgba(r: u8, g: u8, b: u8, a: u8) -> Self {
120        Self { r, g, b, a }
121    }
122
123    #[must_use]
124    pub const fn rgb(r: u8, g: u8, b: u8) -> Self {
125        Self { r, g, b, a: 0xFF }
126    }
127
128    #[must_use]
129    pub fn from_rgba_u32(v: u32) -> Self {
130        Self {
131            r: ((v & 0xFF00_0000) >> 24) as u8,
132            g: ((v & 0x00FF_0000) >> 16) as u8,
133            b: ((v & 0x0000_FF00) >> 8) as u8,
134            a: (v & 0x0000_00FF) as u8,
135        }
136    }
137
138    #[must_use]
139    pub fn to_rgba_u32(self) -> u32 {
140        (u32::from(self.r) << 24)
141            | (u32::from(self.g) << 16)
142            | (u32::from(self.b) << 8)
143            | u32::from(self.a)
144    }
145
146    pub const WHITE: Self = Self::rgba(0xFF, 0xFF, 0xFF, 0xFF);
147    pub const BLACK: Self = Self::rgba(0x00, 0x00, 0x00, 0xFF);
148    pub const NONE: Self = Self::rgba(0x00, 0x00, 0x00, 0x00);
149}
150
151/// 2D vector.
152#[derive(Debug, Clone, Copy, PartialEq)]
153#[repr(C)]
154pub struct Vector2 {
155    pub x: f32,
156    pub y: f32,
157}
158
159/// 3D vector — used for world positions in SA-MP/Open Multiplayer.
160#[derive(Debug, Clone, Copy, PartialEq)]
161#[repr(C)]
162pub struct Vector3 {
163    pub x: f32,
164    pub y: f32,
165    pub z: f32,
166}
167
168/// 4D vector.
169#[derive(Debug, Clone, Copy, PartialEq)]
170#[repr(C)]
171pub struct Vector4 {
172    pub x: f32,
173    pub y: f32,
174    pub z: f32,
175    pub w: f32,
176}
177
178/// Component type.
179///
180/// Equivalent to `ComponentType` in `component.hpp`.
181#[derive(Debug, Clone, Copy, PartialEq, Eq)]
182#[repr(C)]
183pub enum ComponentType {
184    Other = 0,
185    Network = 1,
186    Pool = 2,
187}
188
189#[cfg(test)]
190mod tests {
191    use super::*;
192
193    // --- SemanticVersion ---
194
195    #[test]
196    fn semantic_version_new_fields() {
197        let v = SemanticVersion::new(1, 2, 3);
198        assert_eq!(v.major, 1);
199        assert_eq!(v.minor, 2);
200        assert_eq!(v.patch, 3);
201        assert_eq!(v.prerel, 0);
202    }
203
204    #[test]
205    fn semantic_version_with_prerel() {
206        let v = SemanticVersion::with_prerel(1, 0, 0, 5);
207        assert_eq!(v.prerel, 5);
208    }
209
210    #[test]
211    fn semantic_version_equality() {
212        assert_eq!(SemanticVersion::new(1, 2, 3), SemanticVersion::new(1, 2, 3));
213        assert_ne!(SemanticVersion::new(1, 2, 3), SemanticVersion::new(1, 2, 4));
214    }
215
216    #[test]
217    fn semantic_version_clone() {
218        let v = SemanticVersion::new(2, 0, 0);
219        assert_eq!(v, v);
220    }
221
222    // --- StringView ---
223
224    #[test]
225    fn stringview_from_static_len() {
226        let sv = StringView::from_static("hello");
227        assert_eq!(sv.len, 5);
228        assert!(!sv.data.is_null());
229    }
230
231    #[test]
232    fn stringview_from_static_empty() {
233        let sv = StringView::from_static("");
234        assert_eq!(sv.len, 0);
235    }
236
237    #[test]
238    fn stringview_as_str_roundtrip() {
239        let sv = StringView::from_static("rust-samp");
240        let s = unsafe { sv.as_str() };
241        assert_eq!(s, "rust-samp");
242    }
243
244    #[test]
245    fn stringview_try_as_str_valid_utf8() {
246        let sv = StringView::from_static("naïve");
247        let result = unsafe { sv.try_as_str() };
248        assert!(result.is_ok());
249        assert_eq!(result.unwrap(), "naïve");
250    }
251
252    #[test]
253    fn stringview_try_as_str_invalid_utf8_returns_err() {
254        let bad = [0xFF_u8, 0xFE];
255        let sv = StringView {
256            data: bad.as_ptr(),
257            len: bad.len(),
258        };
259        let result = unsafe { sv.try_as_str() };
260        assert!(result.is_err());
261    }
262
263    // --- Colour ---
264
265    #[test]
266    fn colour_rgba_fields() {
267        let c = Colour::rgba(1, 2, 3, 4);
268        assert_eq!((c.r, c.g, c.b, c.a), (1, 2, 3, 4));
269    }
270
271    #[test]
272    fn colour_rgb_has_full_alpha() {
273        let c = Colour::rgb(10, 20, 30);
274        assert_eq!(c.a, 0xFF);
275    }
276
277    #[test]
278    fn colour_from_to_rgba_u32_roundtrip() {
279        let original = 0xDEAD_BEEF_u32;
280        let c = Colour::from_rgba_u32(original);
281        assert_eq!(c.to_rgba_u32(), original);
282    }
283
284    #[test]
285    fn colour_white_constant() {
286        assert_eq!(Colour::WHITE, Colour::rgba(0xFF, 0xFF, 0xFF, 0xFF));
287    }
288
289    #[test]
290    fn colour_black_constant() {
291        assert_eq!(Colour::BLACK, Colour::rgba(0x00, 0x00, 0x00, 0xFF));
292    }
293
294    #[test]
295    fn colour_none_is_transparent() {
296        assert_eq!(Colour::NONE.a, 0x00);
297    }
298
299    // --- Vector2 / Vector3 / Vector4 ---
300
301    #[test]
302    fn vector2_fields() {
303        let v = Vector2 { x: 1.0, y: 2.0 };
304        assert_eq!((v.x, v.y), (1.0, 2.0));
305    }
306
307    #[test]
308    fn vector3_fields() {
309        let v = Vector3 {
310            x: 1.0,
311            y: 2.0,
312            z: 3.0,
313        };
314        assert_eq!(v.z.to_bits(), 3.0_f32.to_bits());
315    }
316
317    #[test]
318    fn vector4_fields() {
319        let v = Vector4 {
320            x: 1.0,
321            y: 2.0,
322            z: 3.0,
323            w: 4.0,
324        };
325        assert_eq!(v.w.to_bits(), 4.0_f32.to_bits());
326    }
327
328    #[test]
329    fn vectors_clone_and_eq() {
330        let v = Vector3 {
331            x: 1.0,
332            y: 2.0,
333            z: 3.0,
334        };
335        assert_eq!(v, v);
336    }
337
338    // --- ComponentType ---
339
340    #[test]
341    fn component_type_discriminants() {
342        assert_eq!(ComponentType::Other as i32, 0);
343        assert_eq!(ComponentType::Network as i32, 1);
344        assert_eq!(ComponentType::Pool as i32, 2);
345    }
346}