Skip to main content

qubit_io/stream/
buffered_binary_writer.rs

1/*******************************************************************************
2 *
3 *    Copyright (c) 2026 Haixing Hu.
4 *
5 *    SPDX-License-Identifier: Apache-2.0
6 *
7 *    Licensed under the Apache License, Version 2.0.
8 *
9 ******************************************************************************/
10
11use core::marker::PhantomData;
12use std::io::{
13    Result,
14    Seek,
15    SeekFrom,
16    Write,
17};
18
19use crate::codec::{
20    BigEndian,
21    BinaryCodec,
22    ByteOrder,
23    ByteOrderSpec,
24    LittleEndian,
25};
26use crate::stream::BufferedOutput;
27
28/// Buffered writer for fixed-width binary values.
29///
30/// Scalar writes encode directly into the internal output buffer and flush that
31/// buffer to the wrapped writer only when it becomes full or when explicitly
32/// flushed.
33///
34/// # Flush contract
35///
36/// Pending buffered bytes are not flushed from [`Drop`]. Call [`Write::flush`]
37/// or [`Self::into_inner`] to guarantee that all bytes reach the wrapped
38/// writer. [`Self::inner`] and [`Self::inner_mut`] can observe the wrapped
39/// writer before pending bytes have been flushed.
40pub struct BufferedBinaryWriter<W, O = BigEndian> {
41    output: BufferedOutput<W>,
42    marker: PhantomData<fn() -> O>,
43}
44
45impl<W, O> BufferedBinaryWriter<W, O>
46where
47    O: ByteOrderSpec,
48{
49    /// Creates a buffered binary writer with the default buffer capacity.
50    #[must_use]
51    #[inline]
52    pub fn new(inner: W) -> Self {
53        Self {
54            output: BufferedOutput::new(inner),
55            marker: PhantomData,
56        }
57    }
58
59    /// Creates a buffered binary writer with at least `capacity` bytes.
60    #[must_use]
61    #[inline]
62    pub fn with_capacity(inner: W, capacity: usize) -> Self {
63        Self {
64            output: BufferedOutput::with_capacity(inner, capacity),
65            marker: PhantomData,
66        }
67    }
68
69    /// Returns the byte order selected by this writer.
70    #[must_use]
71    #[inline]
72    pub const fn byte_order(&self) -> ByteOrder {
73        O::ORDER
74    }
75
76    /// Returns a shared reference to the underlying writer.
77    ///
78    /// Pending bytes may still be held in this wrapper's internal buffer.
79    #[must_use]
80    #[inline]
81    pub const fn inner(&self) -> &W {
82        self.output.inner()
83    }
84
85    /// Returns an exclusive reference to the underlying writer.
86    ///
87    /// Pending bytes may still be held in this wrapper's internal buffer.
88    /// Flush first if the underlying writer must observe all previous writes.
89    #[must_use]
90    #[inline]
91    pub fn inner_mut(&mut self) -> &mut W {
92        self.output.inner_mut()
93    }
94}
95
96impl<W, O> BufferedBinaryWriter<W, O>
97where
98    W: Write,
99    O: ByteOrderSpec,
100{
101    /// Flushes pending bytes and returns the underlying writer.
102    #[inline]
103    pub fn into_inner(self) -> Result<W> {
104        self.output.into_inner()
105    }
106}
107
108macro_rules! impl_value_write {
109    ($order:ty, $method:ident, $ty:ty, $doc:literal) => {
110        #[doc = $doc]
111        #[inline]
112        pub fn $method(&mut self, value: $ty) -> Result<()> {
113            type Codec = BinaryCodec<$ty, $order>;
114
115            const LEN: usize = Codec::REQUIRED_MIN_BUFFER_LEN;
116            self.output
117                .write_fixed::<LEN, _, _>(value, |bytes, index, value| {
118                    // SAFETY: `write_fixed` guarantees that `LEN` writable bytes
119                    // starting at `index` are available in the internal buffer.
120                    unsafe {
121                        Codec::write_unchecked(bytes, index, value);
122                    }
123                })
124        }
125    };
126}
127
128macro_rules! impl_for_order {
129    ($order:ty) => {
130        impl<W> BufferedBinaryWriter<W, $order>
131        where
132            W: Write,
133        {
134            impl_value_write!($order, write_u8, u8, "Writes an unsigned 8-bit integer.");
135            impl_value_write!($order, write_i8, i8, "Writes a signed 8-bit integer.");
136            impl_value_write!($order, write_u16, u16, "Writes an unsigned 16-bit integer.");
137            impl_value_write!($order, write_u32, u32, "Writes an unsigned 32-bit integer.");
138            impl_value_write!($order, write_u64, u64, "Writes an unsigned 64-bit integer.");
139            impl_value_write!($order, write_u128, u128, "Writes an unsigned 128-bit integer.");
140            impl_value_write!($order, write_i16, i16, "Writes a signed 16-bit integer.");
141            impl_value_write!($order, write_i32, i32, "Writes a signed 32-bit integer.");
142            impl_value_write!($order, write_i64, i64, "Writes a signed 64-bit integer.");
143            impl_value_write!($order, write_i128, i128, "Writes a signed 128-bit integer.");
144            impl_value_write!($order, write_f32, f32, "Writes a 32-bit float.");
145            impl_value_write!($order, write_f64, f64, "Writes a 64-bit float.");
146        }
147    };
148}
149
150impl_for_order!(BigEndian);
151impl_for_order!(LittleEndian);
152
153impl<W, O> Write for BufferedBinaryWriter<W, O>
154where
155    W: Write,
156{
157    /// Writes bytes through the internal buffer.
158    #[inline]
159    fn write(&mut self, buffer: &[u8]) -> Result<usize> {
160        self.output.write_raw(buffer)
161    }
162
163    /// Writes all bytes through the internal buffer.
164    #[inline]
165    fn write_all(&mut self, buffer: &[u8]) -> Result<()> {
166        self.output.write_all_buffered(buffer)
167    }
168
169    /// Flushes the internal buffer and then the wrapped writer.
170    #[inline]
171    fn flush(&mut self) -> Result<()> {
172        self.output.flush_all()
173    }
174}
175
176impl<W, O> Seek for BufferedBinaryWriter<W, O>
177where
178    W: Write + Seek,
179{
180    /// Flushes pending bytes before seeking the wrapped writer.
181    #[inline]
182    fn seek(&mut self, position: SeekFrom) -> Result<u64> {
183        self.output.seek_raw(position)
184    }
185}