Skip to main content

ferroui_base/rendering/composition/transport/
batch_stream.rs

1//! The serialized form of composition changes.
2//!
3//! As upstream, a batch carries two independent streams: plain values
4//! ("structs") and object references. Values are encoded explicitly
5//! (little-endian) through [`BatchValue`] instead of being blitted, so no
6//! `unsafe` is involved; object slots are a closed enum, [`BatchObject`],
7//! which is where the thread-crossing contract of the transport is visible.
8
9use crate::rendering::composition::server::{ServerCompositor, ServerObjectId};
10use crate::rendering::composition::server::IServerObject;
11use crate::media::imaging::{BitmapBlendingMode, BitmapInterpolationMode};
12use crate::media::{
13    AlignmentX, AlignmentY, BaselinePixelAlignment, BoxShadow, Color, EdgeMode, GradientSpreadMethod, PenLineCap,
14    PenLineJoin, RenderOptions, Stretch, TextHintingMode, TextOptions, TextRenderingMode, TileMode,
15};
16use crate::numerics::{Matrix3x2, Matrix4x4, Quaternion, Vector2, Vector3, Vector4};
17use crate::rendering::composition::{
18    CompositionBlendMode, CompositionGradientExtendMode, CompositionStretch, CompositionTileMode,
19    CompositionTransparencyLevel,
20};
21use crate::rendering::{LayoutPassTiming, RendererDebugOverlays};
22use crate::{
23    CornerRadius, Matrix, Point, Rect, RelativePoint, RelativeRect, RelativeScalar, RelativeUnit, RoundedRect, Size,
24    Vector, Vector3D,
25};
26use std::any::Any;
27use std::collections::VecDeque;
28use std::rc::Rc;
29use std::time::Duration;
30
31/// A job executed by the server compositor on the render thread.
32pub type ServerJob = Box<dyn FnOnce(&ServerCompositor) + Send>;
33
34/// A job executed by the server compositor on the render thread with a
35/// server object, which is resolved when the batch is read: upstream the
36/// job holds the server object itself, so it reaches an object the same
37/// batch disposes (the dispose list of a batch precedes its jobs).
38pub type ServerObjectJob = Box<dyn FnOnce(&ServerCompositor, Option<Rc<dyn IServerObject>>) + Send>;
39
40/// Creates the server-side counterpart of a composition object on the
41/// render thread.
42pub type ServerObjectFactory = Box<dyn FnOnce(&Rc<ServerCompositor>, ServerObjectId) -> Rc<dyn IServerObject> + Send>;
43
44/// Markers that structure the object stream of a batch.
45#[derive(Clone, Copy, Debug, PartialEq, Eq)]
46pub enum BatchMarker {
47    /// Followed by a count (value stream) and that many `ServerObject`,
48    /// `Create` pairs.
49    CreateStart,
50    /// Followed by a count (value stream) and that many `ServerObject`s to
51    /// dispose, each with two flags (value stream): whether to dispose the
52    /// object, and whether to release it (drop it from the object table).
53    RenderThreadDisposeStart,
54    RenderThreadJobsStart,
55    RenderThreadJobsEnd,
56    RenderThreadPostTargetJobsStart,
57    RenderThreadPostTargetJobsEnd,
58    /// Debug aid: written after each object's changes when serialization
59    /// checks are enabled.
60    ObjectEnd,
61}
62
63/// One slot of the object stream.
64///
65/// Everything a UI-thread object hands to the server goes through one of
66/// these variants. A `Value` payload is `Send`: render resources are held
67/// in `Arc`, and what belongs to the UI thread crosses in a form of its own
68/// (the shared form of a brush, the factory of an animation instance). The
69/// `Job` and `Create` payloads are closures that are not `Send` yet; see
70/// `docs/porting/render-thread.md`, stage R2.
71pub enum BatchObject {
72    Null,
73    /// A reference to a server-side object.
74    ServerObject(ServerObjectId),
75    Marker(BatchMarker),
76    Job(ServerJob),
77    /// Preceded by the `ServerObject` the job works with.
78    ObjectJob(ServerObjectJob),
79    Create(ServerObjectFactory),
80    /// Any other reference-typed payload (render data resources, platform
81    /// handles, lists).
82    Value(Box<dyn Any + Send>),
83}
84
85impl BatchObject {
86    /// Wraps a payload value.
87    pub fn value<T: Any + Send>(value: T) -> BatchObject {
88        BatchObject::Value(Box::new(value))
89    }
90}
91
92/// A reference-typed property value that is either sent as it is (an
93/// immutable brush, an immutable transform) or names a server object (the
94/// server-side counterpart of a mutable brush): what `GetServer` yields for
95/// a compositor.
96pub enum BatchResource<T: ?Sized> {
97    Value(Rc<T>),
98    Server(ServerObjectId),
99}
100
101impl<T: ?Sized> Clone for BatchResource<T> {
102    fn clone(&self) -> Self {
103        match self {
104            BatchResource::Value(value) => BatchResource::Value(value.clone()),
105            BatchResource::Server(id) => BatchResource::Server(*id),
106        }
107    }
108}
109
110/// A resource type whose values are sent to the render thread.
111///
112/// A value of the UI thread is an `Rc` handle to an object whose own
113/// handles are `Rc` too, so what crosses is its wire form: the values,
114/// `Send`. The server makes a handle of its own from them.
115pub trait BatchResourceValue: 'static {
116    /// What is sent.
117    type Wire: Any + Send;
118
119    /// The wire form of a value.
120    fn to_wire(value: &Rc<Self>) -> Self::Wire;
121
122    /// A handle to the received value, made on the receiving thread.
123    fn from_wire(wire: Self::Wire) -> Rc<Self>;
124}
125
126impl BatchResourceValue for dyn crate::media::IBrush {
127    type Wire = crate::media::SharedBrush;
128
129    fn to_wire(value: &Rc<Self>) -> Self::Wire {
130        crate::media::shared_brush_of(&**value)
131            .unwrap_or_else(|| panic!("The brush is not compatible with composition"))
132    }
133
134    fn from_wire(wire: Self::Wire) -> Rc<Self> {
135        Rc::new(wire)
136    }
137}
138
139impl BatchResourceValue for dyn crate::media::ITransform {
140    type Wire = crate::Matrix;
141
142    fn to_wire(value: &Rc<Self>) -> Self::Wire {
143        value.value()
144    }
145
146    fn from_wire(wire: Self::Wire) -> Rc<Self> {
147        Rc::new(crate::media::immutable::ImmutableTransform::new(wire))
148    }
149}
150
151impl BatchResourceValue for dyn crate::media::IGradientStop {
152    type Wire = (f64, crate::media::Color);
153
154    fn to_wire(value: &Rc<Self>) -> Self::Wire {
155        (value.offset(), value.color())
156    }
157
158    fn from_wire((offset, color): Self::Wire) -> Rc<Self> {
159        Rc::new(crate::media::immutable::ImmutableGradientStop::new(offset, color))
160    }
161}
162
163/// The two streams of a batch.
164#[derive(Default)]
165pub struct BatchStreamData {
166    pub(crate) objects: VecDeque<BatchObject>,
167    pub(crate) structs: Vec<u8>,
168    pub(crate) struct_read_offset: usize,
169}
170
171impl BatchStreamData {
172    pub fn new() -> Self {
173        Self::default()
174    }
175
176    /// Empties both streams, keeping their capacity for reuse.
177    pub(crate) fn reset(&mut self) {
178        self.objects.clear();
179        self.structs.clear();
180        self.struct_read_offset = 0;
181    }
182}
183
184/// A plain value that can be written to the value stream of a batch.
185pub trait BatchValue: Sized {
186    fn write_to(&self, out: &mut Vec<u8>);
187    fn read_from(input: &mut BatchValueReader<'_>) -> Self;
188}
189
190/// A cursor over the value stream.
191pub struct BatchValueReader<'a> {
192    data: &'a [u8],
193    offset: &'a mut usize,
194}
195
196impl<'a> BatchValueReader<'a> {
197    /// A cursor over `data` that reads from, and advances, `offset`.
198    pub fn new(data: &'a [u8], offset: &'a mut usize) -> Self {
199        Self { data, offset }
200    }
201
202    /// Takes the next `N` bytes. Panics at the end of the stream.
203    pub fn take<const N: usize>(&mut self) -> [u8; N] {
204        let end = *self.offset + N;
205        if end > self.data.len() {
206            panic!("attempted to read past the end of the batch value stream");
207        }
208        let mut bytes = [0u8; N];
209        bytes.copy_from_slice(&self.data[*self.offset..end]);
210        *self.offset = end;
211        bytes
212    }
213}
214
215macro_rules! impl_batch_value_for_number {
216    ($($ty:ty),*) => {$(
217        impl BatchValue for $ty {
218            #[inline]
219            fn write_to(&self, out: &mut Vec<u8>) {
220                out.extend_from_slice(&self.to_le_bytes());
221            }
222            #[inline]
223            fn read_from(input: &mut BatchValueReader<'_>) -> Self {
224                <$ty>::from_le_bytes(input.take())
225            }
226        }
227    )*};
228}
229
230impl_batch_value_for_number!(u8, i8, u16, i16, u32, i32, u64, i64, u128, f32, f64);
231
232impl BatchValue for bool {
233    fn write_to(&self, out: &mut Vec<u8>) {
234        out.push(u8::from(*self));
235    }
236    fn read_from(input: &mut BatchValueReader<'_>) -> Self {
237        input.take::<1>()[0] != 0
238    }
239}
240
241impl BatchValue for Duration {
242    fn write_to(&self, out: &mut Vec<u8>) {
243        self.as_nanos().write_to(out);
244    }
245    fn read_from(input: &mut BatchValueReader<'_>) -> Self {
246        let nanos = u128::read_from(input);
247        Duration::new((nanos / 1_000_000_000) as u64, (nanos % 1_000_000_000) as u32)
248    }
249}
250
251impl<T: BatchValue> BatchValue for Option<T> {
252    fn write_to(&self, out: &mut Vec<u8>) {
253        match self {
254            Some(value) => {
255                true.write_to(out);
256                value.write_to(out);
257            }
258            None => false.write_to(out),
259        }
260    }
261    fn read_from(input: &mut BatchValueReader<'_>) -> Self {
262        if bool::read_from(input) {
263            Some(T::read_from(input))
264        } else {
265            None
266        }
267    }
268}
269
270macro_rules! impl_batch_value_for_fields {
271    ($ty:ty { $($field:ident : $fty:ty),* } => $ctor:expr) => {
272        impl BatchValue for $ty {
273            fn write_to(&self, out: &mut Vec<u8>) {
274                $(self.$field.write_to(out);)*
275            }
276            fn read_from(input: &mut BatchValueReader<'_>) -> Self {
277                $(let $field = <$fty>::read_from(input);)*
278                $ctor
279            }
280        }
281    };
282}
283
284impl_batch_value_for_fields!(Point { x: f64, y: f64 } => Point::new(x, y));
285impl_batch_value_for_fields!(Vector { x: f64, y: f64 } => Vector::new(x, y));
286impl_batch_value_for_fields!(Size { width: f64, height: f64 } => Size::new(width, height));
287impl_batch_value_for_fields!(Rect { x: f64, y: f64, width: f64, height: f64 } => Rect::new(x, y, width, height));
288impl_batch_value_for_fields!(Vector3D { x: f64, y: f64, z: f64 } => Vector3D::new(x, y, z));
289impl_batch_value_for_fields!(RoundedRect {
290    rect: Rect, radii_top_left: Vector, radii_top_right: Vector, radii_bottom_left: Vector, radii_bottom_right: Vector
291} => RoundedRect { rect, radii_top_left, radii_top_right, radii_bottom_left, radii_bottom_right });
292
293impl_batch_value_for_fields!(BoxShadow {
294    offset_x: f64, offset_y: f64, blur: f64, spread: f64, color: Color, is_inset: bool
295} => BoxShadow { offset_x, offset_y, blur, spread, color, is_inset });
296
297impl_batch_value_for_fields!(Vector2 { x: f32, y: f32 } => Vector2::new(x, y));
298impl_batch_value_for_fields!(Vector3 { x: f32, y: f32, z: f32 } => Vector3::new(x, y, z));
299impl_batch_value_for_fields!(Vector4 { x: f32, y: f32, z: f32, w: f32 } => Vector4::new(x, y, z, w));
300impl_batch_value_for_fields!(Quaternion { x: f32, y: f32, z: f32, w: f32 } => Quaternion::new(x, y, z, w));
301impl_batch_value_for_fields!(Matrix3x2 { m11: f32, m12: f32, m21: f32, m22: f32, m31: f32, m32: f32 }
302    => Matrix3x2::new(m11, m12, m21, m22, m31, m32));
303impl_batch_value_for_fields!(Matrix4x4 {
304    m11: f32, m12: f32, m13: f32, m14: f32, m21: f32, m22: f32, m23: f32, m24: f32,
305    m31: f32, m32: f32, m33: f32, m34: f32, m41: f32, m42: f32, m43: f32, m44: f32
306} => Matrix4x4 { m11, m12, m13, m14, m21, m22, m23, m24, m31, m32, m33, m34, m41, m42, m43, m44 });
307impl_batch_value_for_fields!(CornerRadius { top_left: f64, top_right: f64, bottom_right: f64, bottom_left: f64 }
308    => CornerRadius::new(top_left, top_right, bottom_right, bottom_left));
309impl_batch_value_for_fields!(RelativePoint { point: Point, unit: RelativeUnit }
310    => RelativePoint::from_point(point, unit));
311impl_batch_value_for_fields!(RelativeScalar { scalar: f64, unit: RelativeUnit } => RelativeScalar::new(scalar, unit));
312impl_batch_value_for_fields!(RelativeRect { rect: Rect, unit: RelativeUnit } => RelativeRect::from_rect(rect, unit));
313impl_batch_value_for_fields!(LayoutPassTiming { pass_counter: i32, elapsed: Duration }
314    => LayoutPassTiming::new(pass_counter, elapsed));
315impl_batch_value_for_fields!(RenderOptions {
316    text_rendering_mode: TextRenderingMode,
317    bitmap_interpolation_mode: BitmapInterpolationMode,
318    edge_mode: EdgeMode,
319    bitmap_blending_mode: BitmapBlendingMode,
320    requires_full_opacity_handling: Option<bool>
321} => RenderOptions {
322    text_rendering_mode,
323    bitmap_interpolation_mode,
324    edge_mode,
325    bitmap_blending_mode,
326    requires_full_opacity_handling,
327});
328impl_batch_value_for_fields!(TextOptions {
329    text_rendering_mode: TextRenderingMode,
330    text_hinting_mode: TextHintingMode,
331    baseline_pixel_alignment: BaselinePixelAlignment
332} => TextOptions { text_rendering_mode, text_hinting_mode, baseline_pixel_alignment });
333
334/// Enumerations cross as their numeric value. A value that names no member
335/// cannot have been written by the other side: reading one panics.
336macro_rules! impl_batch_value_for_enum {
337    ($ty:ty as $repr:ty { $($variant:ident),* $(,)? }) => {
338        impl BatchValue for $ty {
339            #[inline]
340            fn write_to(&self, out: &mut Vec<u8>) {
341                (*self as $repr).write_to(out);
342            }
343            fn read_from(input: &mut BatchValueReader<'_>) -> Self {
344                let value = <$repr>::read_from(input);
345                $(if value == <$ty>::$variant as $repr {
346                    return <$ty>::$variant;
347                })*
348                panic!("the batch value stream holds {value}, which is not a {}", stringify!($ty));
349            }
350        }
351    };
352}
353
354impl_batch_value_for_enum!(RelativeUnit as u8 { Relative, Absolute });
355impl_batch_value_for_enum!(PenLineCap as i32 { Flat, Round, Square });
356impl_batch_value_for_enum!(PenLineJoin as i32 { Bevel, Miter, Round });
357impl_batch_value_for_enum!(AlignmentX as i32 { Left, Center, Right });
358impl_batch_value_for_enum!(AlignmentY as i32 { Top, Center, Bottom });
359impl_batch_value_for_enum!(Stretch as i32 { None, Fill, Uniform, UniformToFill });
360impl_batch_value_for_enum!(TileMode as i32 { None, FlipX, FlipY, FlipXY, Tile });
361impl_batch_value_for_enum!(GradientSpreadMethod as i32 { Pad, Reflect, Repeat });
362impl_batch_value_for_enum!(TextRenderingMode as u8 { Unspecified, SubpixelAntialias, Antialias, Alias });
363impl_batch_value_for_enum!(TextHintingMode as u8 { Unspecified, None, Light, Strong });
364impl_batch_value_for_enum!(BaselinePixelAlignment as u8 { Unspecified, Aligned, Unaligned });
365impl_batch_value_for_enum!(EdgeMode as u8 { Unspecified, Antialias, Aliased });
366impl_batch_value_for_enum!(BitmapInterpolationMode as u8 {
367    Unspecified, None, LowQuality, MediumQuality, HighQuality
368});
369impl_batch_value_for_enum!(BitmapBlendingMode as u8 {
370    Unspecified, SourceOver, Source, Destination, DestinationOver, SourceIn, DestinationIn, SourceOut,
371    DestinationOut, SourceAtop, DestinationAtop, Xor, Plus, Screen, Overlay, Darken, Lighten, ColorDodge,
372    ColorBurn, HardLight, SoftLight, Difference, Exclusion, Multiply, Hue, Saturation, Color, Luminosity
373});
374impl_batch_value_for_enum!(CompositionTransparencyLevel as u8 { None, Transparent, Blur, AcrylicBlur, Mica });
375impl_batch_value_for_enum!(CompositionBlendMode as i32 {
376    Clear, Src, Dst, SrcOver, DstOver, SrcIn, DstIn, SrcOut, DstOut, SrcATop, DstATop, Xor, Plus, Modulate,
377    Screen, Overlay, Darken, Lighten, ColorDodge, ColorBurn, HardLight, SoftLight, Difference, Exclusion,
378    Multiply, Hue, Saturation, Color, Luminosity
379});
380impl_batch_value_for_enum!(CompositionGradientExtendMode as i32 { Clamp, Wrap, Mirror });
381impl_batch_value_for_enum!(CompositionStretch as i32 { None, Fill });
382
383impl BatchValue for RendererDebugOverlays {
384    fn write_to(&self, out: &mut Vec<u8>) {
385        self.bits().write_to(out);
386    }
387    fn read_from(input: &mut BatchValueReader<'_>) -> Self {
388        RendererDebugOverlays::from_bits_retain(i32::read_from(input))
389    }
390}
391
392impl BatchValue for CompositionTileMode {
393    fn write_to(&self, out: &mut Vec<u8>) {
394        self.bits().write_to(out);
395    }
396    fn read_from(input: &mut BatchValueReader<'_>) -> Self {
397        CompositionTileMode::from_bits_retain(i32::read_from(input))
398    }
399}
400
401impl BatchValue for Color {
402    fn write_to(&self, out: &mut Vec<u8>) {
403        self.to_uint32().write_to(out);
404    }
405    fn read_from(input: &mut BatchValueReader<'_>) -> Self {
406        Color::from_uint32(u32::read_from(input))
407    }
408}
409
410impl BatchValue for Matrix {
411    fn write_to(&self, out: &mut Vec<u8>) {
412        for value in [self.m11, self.m12, self.m13, self.m21, self.m22, self.m23, self.m31, self.m32, self.m33] {
413            value.write_to(out);
414        }
415    }
416    fn read_from(input: &mut BatchValueReader<'_>) -> Self {
417        let mut v = [0.0f64; 9];
418        for value in &mut v {
419            *value = f64::read_from(input);
420        }
421        Matrix::new_3x3(v[0], v[1], v[2], v[3], v[4], v[5], v[6], v[7], v[8])
422    }
423}
424
425/// Writes changes into the streams of a batch.
426pub struct BatchStreamWriter<'a> {
427    output: &'a mut BatchStreamData,
428}
429
430impl<'a> BatchStreamWriter<'a> {
431    pub fn new(output: &'a mut BatchStreamData) -> Self {
432        Self { output }
433    }
434
435    /// Writes a plain value to the value stream.
436    #[inline]
437    pub fn write<T: BatchValue>(&mut self, item: T) {
438        item.write_to(&mut self.output.structs);
439    }
440
441    /// Writes raw bytes to the value stream (C# `Write(ReadOnlySpan<byte>)`).
442    #[inline]
443    pub fn write_bytes(&mut self, data: &[u8]) {
444        self.output.structs.extend_from_slice(data);
445    }
446
447    /// Writes an object slot to the object stream.
448    #[inline]
449    pub fn write_object(&mut self, item: BatchObject) {
450        self.output.objects.push_back(item);
451    }
452
453    /// Writes a reference to a server object (or null).
454    #[inline]
455    pub fn write_server_object(&mut self, id: Option<ServerObjectId>) {
456        self.write_object(match id {
457            Some(id) => BatchObject::ServerObject(id),
458            None => BatchObject::Null,
459        });
460    }
461
462    /// Writes a payload value (or null) to the object stream.
463    #[inline]
464    pub fn write_value<T: Any + Send>(&mut self, item: Option<T>) {
465        self.write_object(match item {
466            Some(item) => BatchObject::value(item),
467            None => BatchObject::Null,
468        });
469    }
470
471    /// Writes a resource reference (or null) to the object stream.
472    #[inline]
473    pub fn write_resource<T: ?Sized + BatchResourceValue>(&mut self, item: Option<BatchResource<T>>) {
474        self.write_object(match item {
475            Some(BatchResource::Value(value)) => BatchObject::value(T::to_wire(&value)),
476            Some(BatchResource::Server(id)) => BatchObject::ServerObject(id),
477            None => BatchObject::Null,
478        });
479    }
480
481    /// Writes a marker to the object stream.
482    #[inline]
483    pub fn write_marker(&mut self, marker: BatchMarker) {
484        self.write_object(BatchObject::Marker(marker));
485    }
486}
487
488/// Reads changes from the streams of a batch.
489pub struct BatchStreamReader<'a> {
490    input: &'a mut BatchStreamData,
491}
492
493impl<'a> BatchStreamReader<'a> {
494    pub fn new(input: &'a mut BatchStreamData) -> Self {
495        Self { input }
496    }
497
498    /// Reads a plain value from the value stream. Panics at the end of the
499    /// stream.
500    #[inline]
501    pub fn read<T: BatchValue>(&mut self) -> T {
502        let mut reader =
503            BatchValueReader { data: &self.input.structs, offset: &mut self.input.struct_read_offset };
504        T::read_from(&mut reader)
505    }
506
507    /// Fills `destination` with the next bytes of the value stream (C#
508    /// `Read(Span<byte>)`). Panics at the end of the stream.
509    pub fn read_bytes(&mut self, destination: &mut [u8]) {
510        let start = self.input.struct_read_offset;
511        let end = start + destination.len();
512        if end > self.input.structs.len() {
513            panic!("attempted to read past the end of the batch value stream");
514        }
515        destination.copy_from_slice(&self.input.structs[start..end]);
516        self.input.struct_read_offset = end;
517    }
518
519    /// Reads the next object slot. Panics at the end of the stream.
520    #[inline]
521    pub fn read_object(&mut self) -> BatchObject {
522        match self.input.objects.pop_front() {
523            Some(object) => object,
524            None => panic!("attempted to read past the end of the batch object stream"),
525        }
526    }
527
528    /// Reads a reference to a server object (or null).
529    pub fn read_server_object(&mut self) -> Option<ServerObjectId> {
530        match self.read_object() {
531            BatchObject::Null => None,
532            BatchObject::ServerObject(id) => Some(id),
533            _ => panic!("the batch object stream does not hold a server object reference here"),
534        }
535    }
536
537    /// Reads a payload value of type `T` (or null).
538    pub fn read_value<T: Any>(&mut self) -> Option<T> {
539        match self.read_object() {
540            BatchObject::Null => None,
541            BatchObject::Value(value) => match value.downcast::<T>() {
542                Ok(value) => Some(*value),
543                Err(_) => panic!("the batch object stream holds a value of another type here"),
544            },
545            _ => panic!("the batch object stream does not hold a value here"),
546        }
547    }
548
549    /// Reads a resource reference (or null): a payload value in its wire
550    /// form or the id of a server object.
551    pub fn read_resource<T: ?Sized + BatchResourceValue>(&mut self) -> Option<BatchResource<T>> {
552        match self.read_object() {
553            BatchObject::Null => None,
554            BatchObject::ServerObject(id) => Some(BatchResource::Server(id)),
555            BatchObject::Value(value) => match value.downcast::<T::Wire>() {
556                Ok(value) => Some(BatchResource::Value(T::from_wire(*value))),
557                Err(_) => panic!("the batch object stream holds a value of another type here"),
558            },
559            _ => panic!("the batch object stream does not hold a resource reference here"),
560        }
561    }
562
563    /// Whether the object stream is exhausted.
564    pub fn is_object_eof(&self) -> bool {
565        self.input.objects.is_empty()
566    }
567
568    /// Whether the value stream is exhausted.
569    pub fn is_struct_eof(&self) -> bool {
570        self.input.struct_read_offset >= self.input.structs.len()
571    }
572}
573
574#[cfg(test)]
575mod tests {
576    use super::*;
577
578    #[test]
579    fn batch_stream_correctly_writes_and_reads_data() {
580        let mut data = BatchStreamData::new();
581        let values: Vec<u128> = (0..453u128).map(|c| c.wrapping_mul(0x9E37_79B9_7F4A_7C15_F39C_C060_5CED_C835)).collect();
582        let objects: Vec<std::sync::Arc<()>> = (0..453).map(|_| std::sync::Arc::new(())).collect();
583
584        {
585            let mut writer = BatchStreamWriter::new(&mut data);
586            for value in &values {
587                writer.write(*value);
588            }
589            for object in &objects {
590                writer.write_object(BatchObject::value(object.clone()));
591            }
592        }
593
594        let mut reader = BatchStreamReader::new(&mut data);
595        for value in &values {
596            assert_eq!(*value, reader.read::<u128>());
597        }
598        for object in &objects {
599            assert!(std::sync::Arc::ptr_eq(object, &reader.read_value::<std::sync::Arc<()>>().unwrap()));
600        }
601        assert!(reader.is_object_eof());
602        assert!(reader.is_struct_eof());
603    }
604
605    #[test]
606    fn values_round_trip() {
607        let mut data = BatchStreamData::new();
608        {
609            let mut writer = BatchStreamWriter::new(&mut data);
610            writer.write(true);
611            writer.write(-7i32);
612            writer.write(1.5f32);
613            writer.write(Some(Rect::new(1.0, 2.0, 3.0, 4.0)));
614            writer.write(None::<f64>);
615            writer.write(Matrix::create_translation(3.0, 4.0));
616            writer.write(Duration::from_millis(1234));
617            writer.write(Color::from_uint32(0x80112233));
618            writer.write_server_object(None);
619            writer.write_marker(BatchMarker::RenderThreadJobsEnd);
620        }
621        let mut reader = BatchStreamReader::new(&mut data);
622        assert!(reader.read::<bool>());
623        assert_eq!(reader.read::<i32>(), -7);
624        assert_eq!(reader.read::<f32>(), 1.5);
625        assert_eq!(reader.read::<Option<Rect>>(), Some(Rect::new(1.0, 2.0, 3.0, 4.0)));
626        assert_eq!(reader.read::<Option<f64>>(), None);
627        assert_eq!(reader.read::<Matrix>(), Matrix::create_translation(3.0, 4.0));
628        assert_eq!(reader.read::<Duration>(), Duration::from_millis(1234));
629        assert_eq!(reader.read::<Color>(), Color::from_uint32(0x80112233));
630        assert_eq!(reader.read_server_object(), None);
631        assert!(matches!(reader.read_object(), BatchObject::Marker(BatchMarker::RenderThreadJobsEnd)));
632    }
633
634    #[test]
635    #[should_panic(expected = "past the end")]
636    fn reading_past_the_end_panics() {
637        let mut data = BatchStreamData::new();
638        let mut reader = BatchStreamReader::new(&mut data);
639        let _ = reader.read::<i32>();
640    }
641}