bp3d_util/
format.rs

1// Copyright (c) 2025, BlockProject 3D
2//
3// All rights reserved.
4//
5// Redistribution and use in source and binary forms, with or without modification,
6// are permitted provided that the following conditions are met:
7//
8//     * Redistributions of source code must retain the above copyright notice,
9//       this list of conditions and the following disclaimer.
10//     * Redistributions in binary form must reproduce the above copyright notice,
11//       this list of conditions and the following disclaimer in the documentation
12//       and/or other materials provided with the distribution.
13//     * Neither the name of BlockProject 3D nor the names of its contributors
14//       may be used to endorse or promote products derived from this software
15//       without specific prior written permission.
16//
17// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
18// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
19// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
20// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
21// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
22// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
23// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
24// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
25// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
26// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
27// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28
29//! Formatting utilities.
30
31use std::mem::MaybeUninit;
32
33/// Fixed length string buffer.
34#[derive(Clone, Debug)]
35pub struct FixedBufStr<const N: usize> {
36    len: usize,
37    buffer: [MaybeUninit<u8>; N],
38}
39
40impl<const N: usize> Default for FixedBufStr<N> {
41    fn default() -> Self {
42        Self::new()
43    }
44}
45
46// This function is full of unsafe because it ran slower than expected.
47// It appears that even a single subtraction has a HUGE impact on performance in Rust.
48// It also appears that having this as a function instead of being inlined multiplies by 2 running
49// time.
50// Unfortunately that thing is in a hot path within debug.tracing.
51#[inline(always)]
52fn utf8_max(buf: &[u8], max: usize) -> usize {
53    let buf_len = buf.len();
54    if buf_len <= max {
55        buf_len
56    } else if max == 0 {
57        0
58    } else if unsafe { buf.get_unchecked(max.unchecked_sub(1)) } & 0x80 == 0x00 {
59        max
60    } else {
61        let start = unsafe { max.unchecked_sub(1) };
62        let mut i = start;
63        unsafe {
64            while buf.get_unchecked(i) & 0xC0 == 0x80 {
65                i = i.unchecked_sub(1);
66            }
67            let n = start.unchecked_sub(i);
68            if (buf.get_unchecked(i) & 0xF0 == 0xF0 && n == 4)
69                || (buf.get_unchecked(i) & 0xE0 == 0xE0 && n == 3)
70                || (buf.get_unchecked(i) & 0xC0 == 0xC0 && n == 2)
71            {
72                max
73            } else {
74                i
75            }
76        }
77    }
78}
79
80impl<const N: usize> FixedBufStr<N> {
81    /// Creates a new fixed length string buffer.
82    pub fn new() -> FixedBufStr<N> {
83        FixedBufStr {
84            buffer: unsafe { MaybeUninit::uninit().assume_init() },
85            len: 0,
86        }
87    }
88
89    /// Extracts the string from this buffer.
90    //type inference works so why should the code look awfully more complex?
91    #[allow(clippy::missing_transmute_annotations)]
92    pub fn str(&self) -> &str {
93        unsafe { std::str::from_utf8_unchecked(std::mem::transmute(&self.buffer[..self.len as _])) }
94    }
95
96    /// Constructs this buffer from an existing string.
97    //type inference works so why should the code look awfully more complex?
98    #[allow(clippy::missing_transmute_annotations)]
99    //I believe this is a false-positive, FromStr returns a Result not Self.
100    #[allow(clippy::should_implement_trait)]
101    pub fn from_str(value: &str) -> Self {
102        let mut buffer = FixedBufStr::new();
103        let len = utf8_max(value.as_bytes(), N);
104        unsafe {
105            std::ptr::copy_nonoverlapping(
106                value.as_ptr(),
107                std::mem::transmute(buffer.buffer.as_mut_ptr()),
108                len,
109            );
110        }
111        buffer.len = len as _;
112        buffer
113    }
114
115    /// Appends a raw byte buffer at the end of this string buffer.
116    ///
117    /// Returns the number of bytes written.
118    ///
119    /// # Arguments
120    ///
121    /// * `buf`: the raw byte buffer to append.
122    ///
123    /// returns: usize
124    ///
125    /// # Safety
126    ///
127    /// * [FixedBufStr](FixedBufStr) contains only valid UTF-8 strings so buf must contain only valid UTF-8
128    ///   bytes.
129    /// * If buf contains invalid UTF-8 bytes, further operations on the log message buffer may
130    ///   result in UB.
131    //type inference works so why should the code look awfully more complex?
132    #[allow(clippy::missing_transmute_annotations)]
133    pub unsafe fn write(&mut self, buf: &[u8]) -> usize {
134        let len = utf8_max(buf, N - self.len);
135        unsafe {
136            std::ptr::copy_nonoverlapping(
137                buf.as_ptr(),
138                std::mem::transmute(self.buffer.as_mut_ptr().add(self.len)),
139                len,
140            );
141        }
142        self.len += len;
143        len
144    }
145}
146
147impl<const N: usize> std::fmt::Write for FixedBufStr<N> {
148    fn write_str(&mut self, value: &str) -> std::fmt::Result {
149        unsafe { self.write(value.as_bytes()) };
150        Ok(())
151    }
152}
153
154/// An io [Write](std::io::Write) to fmt [Write](std::fmt::Write).
155///
156/// This may look like a hack but is a requirement for pathological APIs such as presented by the
157/// time crate.
158pub struct IoToFmt<W: std::fmt::Write>(W);
159
160impl<W: std::fmt::Write> IoToFmt<W> {
161    /// Create a new [IoToFmt](IoToFmt) wrapper.
162    ///
163    /// # Arguments
164    ///
165    /// * `w`: target fmt [Write](std::fmt::Write) to write into.
166    ///
167    /// returns: IoToFmt<W>
168    pub fn new(w: W) -> Self {
169        Self(w)
170    }
171
172    /// Extracts the underlying [Write](std::fmt::Write).
173    pub fn into_inner(self) -> W {
174        self.0
175    }
176}
177
178impl<W: std::fmt::Write> std::io::Write for IoToFmt<W> {
179    fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
180        let str = std::str::from_utf8(buf)
181            .map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e))?;
182        self.0
183            .write_str(str)
184            .map(|_| str.len())
185            .map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e))
186    }
187
188    fn flush(&mut self) -> std::io::Result<()> {
189        Ok(())
190    }
191}
192
193#[cfg(test)]
194mod tests {
195    use crate::format::FixedBufStr;
196    use std::fmt::Write;
197
198    #[test]
199    fn basic() {
200        let mut msg: FixedBufStr<64> = FixedBufStr::new();
201        let _ = write!(msg, "this");
202        let _ = write!(msg, " is");
203        let _ = write!(msg, " a");
204        let _ = write!(msg, " test");
205        assert_eq!(msg.str(), "this is a test");
206    }
207
208    #[test]
209    fn truncate_ascii() {
210        let mut msg: FixedBufStr<4> = FixedBufStr::new();
211        let _ = write!(msg, "this");
212        let _ = write!(msg, " is");
213        let _ = write!(msg, " a");
214        let _ = write!(msg, " test");
215        assert_eq!(msg.str().len(), 4);
216        assert_eq!(msg.str(), "this");
217    }
218
219    #[test]
220    fn truncate_utf8_exact() {
221        let mut msg: FixedBufStr<3> = FixedBufStr::new();
222        let _ = write!(msg, "我");
223        assert_eq!(msg.str().len(), 3);
224        assert_eq!(msg.str(), "我");
225    }
226
227    #[test]
228    fn truncate_utf8_exact2() {
229        let mut msg: FixedBufStr<6> = FixedBufStr::new();
230        let _ = write!(msg, "我是");
231        assert_eq!(msg.str().len(), 6);
232        assert_eq!(msg.str(), "我是");
233    }
234
235    #[test]
236    fn truncate_utf8_exact3() {
237        let mut msg: FixedBufStr<6> = FixedBufStr::new();
238        let _ = write!(msg, "我abcd");
239        assert_eq!(msg.str().len(), 6);
240        assert_eq!(msg.str(), "我abc");
241    }
242
243    #[test]
244    fn truncate_utf8() {
245        let mut msg: FixedBufStr<4> = FixedBufStr::new();
246        let _ = write!(msg, "我是");
247        assert_eq!(msg.str().len(), 3);
248        assert_eq!(msg.str(), "我");
249    }
250}