1pub type UID = u64;
15
16#[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#[derive(Clone, Copy)]
59#[repr(C)]
60pub struct StringView {
61 pub data: *const u8,
62 pub len: usize,
63}
64
65impl StringView {
66 pub const EMPTY: Self = Self {
69 data: std::ptr::null(),
70 len: 0,
71 };
72
73 #[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 #[must_use]
88 pub const fn from_static(s: &'static str) -> Self {
89 Self::of(s)
90 }
91
92 #[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 #[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 unsafe { std::str::from_utf8_unchecked(slice) }
118 }
119
120 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#[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#[derive(Debug, Clone, Copy, PartialEq, Default)]
186#[repr(C)]
187pub struct Vector2 {
188 pub x: f32,
189 pub y: f32,
190}
191
192#[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 pub const ZERO: Self = Self {
205 x: 0.0,
206 y: 0.0,
207 z: 0.0,
208 };
209}
210
211#[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 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#[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 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
260macro_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#[cfg(target_env = "msvc")]
279pub type HoursRep = i32;
280#[cfg(not(target_env = "msvc"))]
283pub type HoursRep = i64;
284
285durations! {
286 Milliseconds(i64);
288 Seconds(i64);
290 Minutes(HoursRep);
292 Hours(HoursRep);
294}
295
296durations! {
297 Microseconds(i64);
299 TimePoint(i64);
302 WorldTimePoint(i64);
306}
307
308#[cfg(target_env = "msvc")]
310pub const WORLD_TICKS_PER_SECOND: i64 = 10_000_000;
311#[cfg(not(target_env = "msvc"))]
313pub const WORLD_TICKS_PER_SECOND: i64 = 1_000_000_000;
314
315#[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 #[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 #[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 #[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 #[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 #[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}