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    /// The empty view: null data, zero length. What a hidden-pointer return
67    /// slot starts as, and what the server reads as "no text".
68    pub const EMPTY: Self = Self {
69        data: std::ptr::null(),
70        len: 0,
71    };
72
73    /// A view of `s` to hand the server for the length of one call.
74    ///
75    /// The view carries no lifetime, so it is the caller's to keep `s` alive
76    /// while the server reads it. Every interface that takes a `StringView`
77    /// copies what it keeps, so the duration of the call is enough.
78    #[must_use]
79    pub const fn of(s: &str) -> Self {
80        Self {
81            data: s.as_ptr(),
82            len: s.len(),
83        }
84    }
85
86    /// Creates a `StringView` from a static `&str`, which outlives any use.
87    #[must_use]
88    pub const fn from_static(s: &'static str) -> Self {
89        Self::of(s)
90    }
91
92    /// Copies the viewed bytes into a `String`.
93    ///
94    /// `None` when the view is empty, null, or not UTF-8 — the server's answer
95    /// for "no text" and a malformed one read the same to a caller.
96    ///
97    /// # Safety
98    /// A non-null `data` must point to `len` readable bytes.
99    #[must_use]
100    pub unsafe fn to_owned_string(self) -> Option<String> {
101        if self.data.is_null() || self.len == 0 {
102            return None;
103        }
104        let bytes = unsafe { std::slice::from_raw_parts(self.data, self.len) };
105        std::str::from_utf8(bytes).ok().map(String::from)
106    }
107
108    /// Converts to `&str`. Safe only if the pointer is valid and UTF-8.
109    ///
110    /// # Safety
111    /// The pointer must be valid and point to `len` bytes of valid UTF-8
112    /// for the lifetime `'a`.
113    #[must_use]
114    pub unsafe fn as_str<'a>(self) -> &'a str {
115        let slice = unsafe { std::slice::from_raw_parts(self.data, self.len) };
116        // Open Multiplayer guarantees UTF-8 strings on the SDK interfaces
117        unsafe { std::str::from_utf8_unchecked(slice) }
118    }
119
120    /// Converts to `&str` with explicit UTF-8 validation.
121    ///
122    /// Preferable to [`as_str`] when the string content is untrusted or in
123    /// contexts where defensive validation is needed.
124    ///
125    /// # Safety
126    /// The pointer must be valid and point to `len` bytes for the lifetime `'a`.
127    ///
128    /// # Errors
129    /// [`std::str::Utf8Error`] if the pointed-to bytes do not form valid UTF-8.
130    ///
131    /// [`as_str`]: Self::as_str
132    pub unsafe fn try_as_str<'a>(self) -> Result<&'a str, std::str::Utf8Error> {
133        let slice = unsafe { std::slice::from_raw_parts(self.data, self.len) };
134        std::str::from_utf8(slice)
135    }
136}
137
138/// RGBA color.
139///
140/// Equivalent to `Colour` in `types.hpp`.
141#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
142#[repr(C)]
143pub struct Colour {
144    pub r: u8,
145    pub g: u8,
146    pub b: u8,
147    pub a: u8,
148}
149
150impl Colour {
151    #[must_use]
152    pub const fn rgba(r: u8, g: u8, b: u8, a: u8) -> Self {
153        Self { r, g, b, a }
154    }
155
156    #[must_use]
157    pub const fn rgb(r: u8, g: u8, b: u8) -> Self {
158        Self { r, g, b, a: 0xFF }
159    }
160
161    #[must_use]
162    pub fn from_rgba_u32(v: u32) -> Self {
163        Self {
164            r: ((v & 0xFF00_0000) >> 24) as u8,
165            g: ((v & 0x00FF_0000) >> 16) as u8,
166            b: ((v & 0x0000_FF00) >> 8) as u8,
167            a: (v & 0x0000_00FF) as u8,
168        }
169    }
170
171    #[must_use]
172    pub fn to_rgba_u32(self) -> u32 {
173        (u32::from(self.r) << 24)
174            | (u32::from(self.g) << 16)
175            | (u32::from(self.b) << 8)
176            | u32::from(self.a)
177    }
178
179    pub const WHITE: Self = Self::rgba(0xFF, 0xFF, 0xFF, 0xFF);
180    pub const BLACK: Self = Self::rgba(0x00, 0x00, 0x00, 0xFF);
181    pub const NONE: Self = Self::rgba(0x00, 0x00, 0x00, 0x00);
182}
183
184/// 2D vector.
185#[derive(Debug, Clone, Copy, PartialEq, Default)]
186#[repr(C)]
187pub struct Vector2 {
188    pub x: f32,
189    pub y: f32,
190}
191
192/// 3D vector — used for world positions in SA-MP/Open Multiplayer.
193#[derive(Debug, Clone, Copy, PartialEq)]
194#[repr(C)]
195pub struct Vector3 {
196    pub x: f32,
197    pub y: f32,
198    pub z: f32,
199}
200
201impl Vector3 {
202    /// The origin — also what a position accessor answers when the entity is
203    /// not there to ask.
204    pub const ZERO: Self = Self {
205        x: 0.0,
206        y: 0.0,
207        z: 0.0,
208    };
209}
210
211/// 4D vector.
212#[derive(Debug, Clone, Copy, PartialEq)]
213#[repr(C)]
214pub struct Vector4 {
215    pub x: f32,
216    pub y: f32,
217    pub z: f32,
218    pub w: f32,
219}
220
221impl Vector4 {
222    /// All zero — what a getter answers when the entity is not there to ask.
223    pub const ZERO: Self = Self {
224        x: 0.0,
225        y: 0.0,
226        z: 0.0,
227        w: 0.0,
228    };
229}
230
231/// A rotation, as `GTAQuat` in `gtaquat.hpp` stores it: a `glm::quat`.
232///
233/// The SDK's build defines `GLM_FORCE_QUAT_DATA_WXYZ`, so `w` comes first —
234/// the server was built that way, and so is this.
235#[derive(Debug, Clone, Copy, PartialEq)]
236#[repr(C)]
237pub struct GTAQuat {
238    pub w: f32,
239    pub x: f32,
240    pub y: f32,
241    pub z: f32,
242}
243
244impl GTAQuat {
245    /// No rotation — also what `GTAQuat()` constructs.
246    pub const IDENTITY: Self = Self {
247        w: 1.0,
248        x: 0.0,
249        y: 0.0,
250        z: 0.0,
251    };
252}
253
254impl Default for GTAQuat {
255    fn default() -> Self {
256        Self::IDENTITY
257    }
258}
259
260/// `std::chrono` durations as the server's headers use them: `Milliseconds`,
261/// `Seconds`, `Minutes`, `Hours`.
262///
263/// Each is the duration's count and nothing else, laid out as the C++ class
264/// is. The count's width is the standard library's choice: 64 bits in both for
265/// milliseconds and seconds, while Microsoft's library counts minutes and hours
266/// in an `int` where libstdc++ keeps 64 bits — hence [`HoursRep`].
267macro_rules! durations {
268    ($($(#[$meta:meta])* $name:ident($rep:ty);)*) => {$(
269        $(#[$meta])*
270        #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
271        #[repr(C)]
272        pub struct $name(pub $rep);
273    )*};
274}
275
276/// The count type of [`Minutes`] and [`Hours`]: `int` under MSVC, 64 bits under
277/// libstdc++.
278#[cfg(target_env = "msvc")]
279pub type HoursRep = i32;
280/// The count type of [`Minutes`] and [`Hours`]: `int` under MSVC, 64 bits under
281/// libstdc++.
282#[cfg(not(target_env = "msvc"))]
283pub type HoursRep = i64;
284
285durations! {
286    /// `std::chrono::milliseconds`.
287    Milliseconds(i64);
288    /// `std::chrono::seconds`.
289    Seconds(i64);
290    /// `std::chrono::minutes`.
291    Minutes(HoursRep);
292    /// `std::chrono::hours`.
293    Hours(HoursRep);
294}
295
296durations! {
297    /// `std::chrono::microseconds`.
298    Microseconds(i64);
299    /// `TimePoint`, a `std::chrono::steady_clock::time_point`: nanoseconds
300    /// since an unspecified start, under both standard libraries.
301    TimePoint(i64);
302    /// `WorldTimePoint`, a `std::chrono::system_clock::time_point`. The unit is
303    /// the library's: nanoseconds under libstdc++, 100-nanosecond ticks under
304    /// Microsoft's — hence [`WORLD_TICKS_PER_SECOND`].
305    WorldTimePoint(i64);
306}
307
308/// Ticks of a [`WorldTimePoint`] per second, on this target.
309#[cfg(target_env = "msvc")]
310pub const WORLD_TICKS_PER_SECOND: i64 = 10_000_000;
311/// Ticks of a [`WorldTimePoint`] per second, on this target.
312#[cfg(not(target_env = "msvc"))]
313pub const WORLD_TICKS_PER_SECOND: i64 = 1_000_000_000;
314
315/// Component type.
316///
317/// Equivalent to `ComponentType` in `component.hpp`.
318#[derive(Debug, Clone, Copy, PartialEq, Eq)]
319#[repr(C)]
320pub enum ComponentType {
321    Other = 0,
322    Network = 1,
323    Pool = 2,
324}
325
326#[cfg(test)]
327mod tests {
328    use super::*;
329
330    // --- SemanticVersion ---
331
332    #[test]
333    fn semantic_version_new_fields() {
334        let v = SemanticVersion::new(1, 2, 3);
335        assert_eq!(v.major, 1);
336        assert_eq!(v.minor, 2);
337        assert_eq!(v.patch, 3);
338        assert_eq!(v.prerel, 0);
339    }
340
341    #[test]
342    fn semantic_version_with_prerel() {
343        let v = SemanticVersion::with_prerel(1, 0, 0, 5);
344        assert_eq!(v.prerel, 5);
345    }
346
347    #[test]
348    fn semantic_version_equality() {
349        assert_eq!(SemanticVersion::new(1, 2, 3), SemanticVersion::new(1, 2, 3));
350        assert_ne!(SemanticVersion::new(1, 2, 3), SemanticVersion::new(1, 2, 4));
351    }
352
353    #[test]
354    fn semantic_version_clone() {
355        let v = SemanticVersion::new(2, 0, 0);
356        assert_eq!(v, v);
357    }
358
359    // --- StringView ---
360
361    #[test]
362    fn stringview_from_static_len() {
363        let sv = StringView::from_static("hello");
364        assert_eq!(sv.len, 5);
365        assert!(!sv.data.is_null());
366    }
367
368    #[test]
369    fn stringview_from_static_empty() {
370        let sv = StringView::from_static("");
371        assert_eq!(sv.len, 0);
372    }
373
374    #[test]
375    fn stringview_as_str_roundtrip() {
376        let sv = StringView::from_static("rust-samp");
377        let s = unsafe { sv.as_str() };
378        assert_eq!(s, "rust-samp");
379    }
380
381    #[test]
382    fn stringview_try_as_str_valid_utf8() {
383        let sv = StringView::from_static("naïve");
384        let result = unsafe { sv.try_as_str() };
385        assert!(result.is_ok());
386        assert_eq!(result.unwrap(), "naïve");
387    }
388
389    #[test]
390    fn stringview_try_as_str_invalid_utf8_returns_err() {
391        let bad = [0xFF_u8, 0xFE];
392        let sv = StringView {
393            data: bad.as_ptr(),
394            len: bad.len(),
395        };
396        let result = unsafe { sv.try_as_str() };
397        assert!(result.is_err());
398    }
399
400    // --- Colour ---
401
402    #[test]
403    fn colour_rgba_fields() {
404        let c = Colour::rgba(1, 2, 3, 4);
405        assert_eq!((c.r, c.g, c.b, c.a), (1, 2, 3, 4));
406    }
407
408    #[test]
409    fn colour_rgb_has_full_alpha() {
410        let c = Colour::rgb(10, 20, 30);
411        assert_eq!(c.a, 0xFF);
412    }
413
414    #[test]
415    fn colour_from_to_rgba_u32_roundtrip() {
416        let original = 0xDEAD_BEEF_u32;
417        let c = Colour::from_rgba_u32(original);
418        assert_eq!(c.to_rgba_u32(), original);
419    }
420
421    #[test]
422    fn colour_white_constant() {
423        assert_eq!(Colour::WHITE, Colour::rgba(0xFF, 0xFF, 0xFF, 0xFF));
424    }
425
426    #[test]
427    fn colour_black_constant() {
428        assert_eq!(Colour::BLACK, Colour::rgba(0x00, 0x00, 0x00, 0xFF));
429    }
430
431    #[test]
432    fn colour_none_is_transparent() {
433        assert_eq!(Colour::NONE.a, 0x00);
434    }
435
436    // --- Vector2 / Vector3 / Vector4 ---
437
438    #[test]
439    fn vector2_fields() {
440        let v = Vector2 { x: 1.0, y: 2.0 };
441        assert_eq!((v.x, v.y), (1.0, 2.0));
442    }
443
444    #[test]
445    fn vector3_fields() {
446        let v = Vector3 {
447            x: 1.0,
448            y: 2.0,
449            z: 3.0,
450        };
451        assert_eq!(v.z.to_bits(), 3.0_f32.to_bits());
452    }
453
454    #[test]
455    fn vector4_fields() {
456        let v = Vector4 {
457            x: 1.0,
458            y: 2.0,
459            z: 3.0,
460            w: 4.0,
461        };
462        assert_eq!(v.w.to_bits(), 4.0_f32.to_bits());
463    }
464
465    #[test]
466    fn vectors_clone_and_eq() {
467        let v = Vector3 {
468            x: 1.0,
469            y: 2.0,
470            z: 3.0,
471        };
472        assert_eq!(v, v);
473    }
474
475    // --- ComponentType ---
476
477    #[test]
478    fn component_type_discriminants() {
479        assert_eq!(ComponentType::Other as i32, 0);
480        assert_eq!(ComponentType::Network as i32, 1);
481        assert_eq!(ComponentType::Pool as i32, 2);
482    }
483}