Skip to main content

warcraft3_stats_observer/
string_utils.rs

1use std::borrow::Cow;
2use std::fmt::{Debug, Display};
3
4/// Fixed-size, optionally NULL-terminated UTF-8 string read from the observer
5/// memory map.
6///
7/// The backing buffer is always `SIZE` bytes wide. Logical string content runs
8/// from byte 0 up to (but excluding) the first `0x00` byte, or to the end of
9/// the buffer if no NUL byte is present.
10///
11/// `PaddedString` has alignment 1, so it can safely be borrowed by reference
12/// even when nested inside a `#[repr(C, packed)]` struct.
13#[repr(C)]
14#[derive(Clone, Copy, PartialEq, Eq, Hash)]
15pub struct PaddedString<const SIZE: usize> {
16    array: [u8; SIZE],
17}
18
19impl<const SIZE: usize> PaddedString<SIZE> {
20    /// Returns the bytes up to (but excluding) the first NUL byte.
21    ///
22    /// If the buffer contains no NUL byte, all `SIZE` bytes are returned.
23    pub fn as_bytes(&self) -> &[u8] {
24        &self.array[..self.len()]
25    }
26
27    /// Lossy UTF-8 view of [`Self::as_bytes`].
28    ///
29    /// Invalid UTF-8 sequences are replaced with `U+FFFD`.
30    pub fn to_string_lossy(&self) -> Cow<'_, str> {
31        String::from_utf8_lossy(self.as_bytes())
32    }
33
34    /// Number of logical bytes in the string — the position of the first NULL
35    /// byte, or `SIZE` if the buffer is not NULL-terminated.
36    pub fn len(&self) -> usize {
37        self.array.iter().position(|&b| b == 0).unwrap_or(SIZE)
38    }
39
40    /// Returns `true` if the first byte is NUL (i.e. the logical length is 0).
41    pub fn is_empty(&self) -> bool {
42        self.array.first() == Some(&0)
43    }
44}
45
46impl<const SIZE: usize> Display for PaddedString<SIZE> {
47    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
48        write!(f, "{}", self.to_string_lossy())
49    }
50}
51
52impl<const SIZE: usize> Debug for PaddedString<SIZE> {
53    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
54        Debug::fmt(&self.to_string_lossy(), f)
55    }
56}
57
58impl<const SIZE: usize> AsRef<[u8]> for PaddedString<SIZE> {
59    fn as_ref(&self) -> &[u8] {
60        self.as_bytes()
61    }
62}