From 755917fe4b317e1395863405392e19a30437c489 Mon Sep 17 00:00:00 2001 From: NiseVoid Date: Sun, 28 Jan 2024 15:55:30 +0100 Subject: [PATCH] Derive PartialEq, Serialize, Deserialize and Reflect on primitives (#11514) # Objective - Implement common traits on primitives ## Solution - Derive PartialEq on types that were missing it. - Derive Copy on small types that were missing it. - Derive Serialize/Deserialize if the feature on bevy_math is enabled. - Add a lot of cursed stuff to the bevy_reflect `impls` module. --- crates/bevy_math/src/primitives/dim2.rs | 38 +++++-- crates/bevy_math/src/primitives/dim3.rs | 35 ++++-- crates/bevy_math/src/primitives/mod.rs | 2 + crates/bevy_math/src/primitives/serde.rs | 63 +++++++++++ crates/bevy_reflect/Cargo.toml | 4 +- .../src/impls/math/primitives2d.rs | 93 +++++++++++++++ .../src/impls/math/primitives3d.rs | 106 ++++++++++++++++++ .../bevy_reflect/src/impls/{ => math}/rect.rs | 0 crates/bevy_reflect/src/lib.rs | 6 +- 9 files changed, 322 insertions(+), 25 deletions(-) create mode 100644 crates/bevy_math/src/primitives/serde.rs create mode 100644 crates/bevy_reflect/src/impls/math/primitives2d.rs create mode 100644 crates/bevy_reflect/src/impls/math/primitives3d.rs rename crates/bevy_reflect/src/impls/{ => math}/rect.rs (100%) diff --git a/crates/bevy_math/src/primitives/dim2.rs b/crates/bevy_math/src/primitives/dim2.rs index 8d094e3e957d0..579fdc0cd0766 100644 --- a/crates/bevy_math/src/primitives/dim2.rs +++ b/crates/bevy_math/src/primitives/dim2.rs @@ -80,7 +80,8 @@ impl std::ops::Neg for Direction2d { } /// A circle primitive -#[derive(Clone, Copy, Debug)] +#[derive(Clone, Copy, Debug, PartialEq)] +#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))] pub struct Circle { /// The radius of the circle pub radius: f32, @@ -115,7 +116,8 @@ impl Circle { } /// An ellipse primitive -#[derive(Clone, Copy, Debug)] +#[derive(Clone, Copy, Debug, PartialEq)] +#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))] pub struct Ellipse { /// Half of the width and height of the ellipse. /// @@ -160,7 +162,8 @@ impl Ellipse { /// An unbounded plane in 2D space. It forms a separating surface through the origin, /// stretching infinitely far -#[derive(Clone, Copy, Debug)] +#[derive(Clone, Copy, Debug, PartialEq)] +#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))] pub struct Plane2d { /// The normal of the plane. The plane will be placed perpendicular to this direction pub normal: Direction2d, @@ -184,7 +187,8 @@ impl Plane2d { /// An infinite line along a direction in 2D space. /// /// For a finite line: [`Segment2d`] -#[derive(Clone, Copy, Debug)] +#[derive(Clone, Copy, Debug, PartialEq)] +#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))] pub struct Line2d { /// The direction of the line. The line extends infinitely in both the given direction /// and its opposite direction @@ -194,7 +198,8 @@ impl Primitive2d for Line2d {} /// A segment of a line along a direction in 2D space. #[doc(alias = "LineSegment2d")] -#[derive(Clone, Debug)] +#[derive(Clone, Copy, Debug, PartialEq)] +#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))] pub struct Segment2d { /// The direction of the line segment pub direction: Direction2d, @@ -241,9 +246,11 @@ impl Segment2d { /// A series of connected line segments in 2D space. /// /// For a version without generics: [`BoxedPolyline2d`] -#[derive(Clone, Debug)] +#[derive(Clone, Debug, PartialEq)] +#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))] pub struct Polyline2d { /// The vertices of the polyline + #[cfg_attr(feature = "serialize", serde(with = "super::serde::array"))] pub vertices: [Vec2; N], } impl Primitive2d for Polyline2d {} @@ -270,7 +277,8 @@ impl Polyline2d { /// in a `Box<[Vec2]>`. /// /// For a version without alloc: [`Polyline2d`] -#[derive(Clone, Debug)] +#[derive(Clone, Debug, PartialEq)] +#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))] pub struct BoxedPolyline2d { /// The vertices of the polyline pub vertices: Box<[Vec2]>, @@ -294,7 +302,8 @@ impl BoxedPolyline2d { } /// A triangle in 2D space -#[derive(Clone, Debug, PartialEq)] +#[derive(Clone, Copy, Debug, PartialEq)] +#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))] pub struct Triangle2d { /// The vertices of the triangle pub vertices: [Vec2; 3], @@ -367,7 +376,8 @@ impl Triangle2d { /// A rectangle primitive #[doc(alias = "Quad")] -#[derive(Clone, Copy, Debug)] +#[derive(Clone, Copy, Debug, PartialEq)] +#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))] pub struct Rectangle { /// Half of the width and height of the rectangle pub half_size: Vec2, @@ -400,9 +410,11 @@ impl Rectangle { /// A polygon with N vertices. /// /// For a version without generics: [`BoxedPolygon`] -#[derive(Clone, Debug)] +#[derive(Clone, Debug, PartialEq)] +#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))] pub struct Polygon { /// The vertices of the `Polygon` + #[cfg_attr(feature = "serialize", serde(with = "super::serde::array"))] pub vertices: [Vec2; N], } impl Primitive2d for Polygon {} @@ -429,7 +441,8 @@ impl Polygon { /// in a `Box<[Vec2]>`. /// /// For a version without alloc: [`Polygon`] -#[derive(Clone, Debug)] +#[derive(Clone, Debug, PartialEq)] +#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))] pub struct BoxedPolygon { /// The vertices of the `BoxedPolygon` pub vertices: Box<[Vec2]>, @@ -453,7 +466,8 @@ impl BoxedPolygon { } /// A polygon where all vertices lie on a circle, equally far apart. -#[derive(Clone, Copy, Debug)] +#[derive(Clone, Copy, Debug, PartialEq)] +#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))] pub struct RegularPolygon { /// The circumcircle on which all vertices lie pub circumcircle: Circle, diff --git a/crates/bevy_math/src/primitives/dim3.rs b/crates/bevy_math/src/primitives/dim3.rs index 8c12f2b789f5b..56f3f46167fb4 100644 --- a/crates/bevy_math/src/primitives/dim3.rs +++ b/crates/bevy_math/src/primitives/dim3.rs @@ -84,7 +84,8 @@ impl std::ops::Neg for Direction3d { } /// A sphere primitive -#[derive(Clone, Copy, Debug)] +#[derive(Clone, Copy, Debug, PartialEq)] +#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))] pub struct Sphere { /// The radius of the sphere pub radius: f32, @@ -120,7 +121,8 @@ impl Sphere { /// An unbounded plane in 3D space. It forms a separating surface through the origin, /// stretching infinitely far -#[derive(Clone, Copy, Debug)] +#[derive(Clone, Copy, Debug, PartialEq)] +#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))] pub struct Plane3d { /// The normal of the plane. The plane will be placed perpendicular to this direction pub normal: Direction3d, @@ -144,7 +146,8 @@ impl Plane3d { /// An infinite line along a direction in 3D space. /// /// For a finite line: [`Segment3d`] -#[derive(Clone, Copy, Debug)] +#[derive(Clone, Copy, Debug, PartialEq)] +#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))] pub struct Line3d { /// The direction of the line pub direction: Direction3d, @@ -153,7 +156,8 @@ impl Primitive3d for Line3d {} /// A segment of a line along a direction in 3D space. #[doc(alias = "LineSegment3d")] -#[derive(Clone, Debug)] +#[derive(Clone, Copy, Debug, PartialEq)] +#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))] pub struct Segment3d { /// The direction of the line pub direction: Direction3d, @@ -200,9 +204,11 @@ impl Segment3d { /// A series of connected line segments in 3D space. /// /// For a version without generics: [`BoxedPolyline3d`] -#[derive(Clone, Debug)] +#[derive(Clone, Debug, PartialEq)] +#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))] pub struct Polyline3d { /// The vertices of the polyline + #[cfg_attr(feature = "serialize", serde(with = "super::serde::array"))] pub vertices: [Vec3; N], } impl Primitive3d for Polyline3d {} @@ -229,7 +235,8 @@ impl Polyline3d { /// in a `Box<[Vec3]>`. /// /// For a version without alloc: [`Polyline3d`] -#[derive(Clone, Debug)] +#[derive(Clone, Debug, PartialEq)] +#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))] pub struct BoxedPolyline3d { /// The vertices of the polyline pub vertices: Box<[Vec3]>, @@ -253,7 +260,8 @@ impl BoxedPolyline3d { } /// A cuboid primitive, more commonly known as a box. -#[derive(Clone, Copy, Debug)] +#[derive(Clone, Copy, Debug, PartialEq)] +#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))] pub struct Cuboid { /// Half of the width, height and depth of the cuboid pub half_size: Vec3, @@ -285,7 +293,8 @@ impl Cuboid { } /// A cylinder primitive -#[derive(Clone, Copy, Debug)] +#[derive(Clone, Copy, Debug, PartialEq)] +#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))] pub struct Cylinder { /// The radius of the cylinder pub radius: f32, @@ -306,7 +315,8 @@ impl Cylinder { /// A capsule primitive. /// A capsule is defined as a surface at a distance (radius) from a line -#[derive(Clone, Copy, Debug)] +#[derive(Clone, Copy, Debug, PartialEq)] +#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))] pub struct Capsule { /// The radius of the capsule pub radius: f32, @@ -327,7 +337,8 @@ impl Capsule { } /// A cone primitive. -#[derive(Clone, Copy, Debug)] +#[derive(Clone, Copy, Debug, PartialEq)] +#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))] pub struct Cone { /// The radius of the base pub radius: f32, @@ -339,7 +350,8 @@ impl Primitive3d for Cone {} /// A conical frustum primitive. /// A conical frustum can be created /// by slicing off a section of a cone. -#[derive(Clone, Copy, Debug)] +#[derive(Clone, Copy, Debug, PartialEq)] +#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))] pub struct ConicalFrustum { /// The radius of the top of the frustum pub radius_top: f32, @@ -370,6 +382,7 @@ pub enum TorusKind { /// A torus primitive, often representing a ring or donut shape #[derive(Clone, Copy, Debug, PartialEq)] +#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))] pub struct Torus { /// The radius of the tube of the torus #[doc( diff --git a/crates/bevy_math/src/primitives/mod.rs b/crates/bevy_math/src/primitives/mod.rs index a6567e6330c33..db5f380897572 100644 --- a/crates/bevy_math/src/primitives/mod.rs +++ b/crates/bevy_math/src/primitives/mod.rs @@ -6,6 +6,8 @@ mod dim2; pub use dim2::*; mod dim3; pub use dim3::*; +#[cfg(feature = "serialize")] +mod serde; /// A marker trait for 2D primitives pub trait Primitive2d {} diff --git a/crates/bevy_math/src/primitives/serde.rs b/crates/bevy_math/src/primitives/serde.rs new file mode 100644 index 0000000000000..79abb778cd39d --- /dev/null +++ b/crates/bevy_math/src/primitives/serde.rs @@ -0,0 +1,63 @@ +//! This module defines serialization/deserialization for const generic arrays. +//! Unlike serde's default behavior, it supports arbitrarily large arrays. +//! The code is based on this github comment: +//! + +pub(crate) mod array { + use serde::{ + de::{SeqAccess, Visitor}, + ser::SerializeTuple, + Deserialize, Deserializer, Serialize, Serializer, + }; + use std::marker::PhantomData; + + pub fn serialize( + data: &[T; N], + ser: S, + ) -> Result { + let mut s = ser.serialize_tuple(N)?; + for item in data { + s.serialize_element(item)?; + } + s.end() + } + + struct GenericArrayVisitor(PhantomData); + + impl<'de, T, const N: usize> Visitor<'de> for GenericArrayVisitor + where + T: Deserialize<'de>, + { + type Value = [T; N]; + + fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { + formatter.write_str(&format!("an array of length {}", N)) + } + + #[inline] + fn visit_seq(self, mut seq: A) -> Result + where + A: SeqAccess<'de>, + { + let mut data = Vec::with_capacity(N); + for _ in 0..N { + match (seq.next_element())? { + Some(val) => data.push(val), + None => return Err(serde::de::Error::invalid_length(N, &self)), + } + } + match data.try_into() { + Ok(arr) => Ok(arr), + Err(_) => unreachable!(), + } + } + } + + pub fn deserialize<'de, D, T, const N: usize>(deserializer: D) -> Result<[T; N], D::Error> + where + D: Deserializer<'de>, + T: Deserialize<'de>, + { + deserializer.deserialize_tuple(N, GenericArrayVisitor::(PhantomData)) + } +} diff --git a/crates/bevy_reflect/Cargo.toml b/crates/bevy_reflect/Cargo.toml index 4202293037d1b..89fa54ffa1801 100644 --- a/crates/bevy_reflect/Cargo.toml +++ b/crates/bevy_reflect/Cargo.toml @@ -12,7 +12,9 @@ readme = "README.md" [features] default = [] # When enabled, provides Bevy-related reflection implementations -bevy = ["glam", "smallvec", "bevy_math", "smol_str"] +bevy = ["smallvec", "bevy_math", "smol_str"] +glam = ["dep:glam"] +bevy_math = ["glam", "dep:bevy_math"] smallvec = [] # When enabled, allows documentation comments to be accessed via reflection documentation = ["bevy_reflect_derive/documentation"] diff --git a/crates/bevy_reflect/src/impls/math/primitives2d.rs b/crates/bevy_reflect/src/impls/math/primitives2d.rs new file mode 100644 index 0000000000000..92725d4d4d5af --- /dev/null +++ b/crates/bevy_reflect/src/impls/math/primitives2d.rs @@ -0,0 +1,93 @@ +use crate as bevy_reflect; +use crate::{ReflectDeserialize, ReflectSerialize}; +use bevy_math::{primitives::*, Vec2}; +use bevy_reflect_derive::{impl_reflect_struct, impl_reflect_value}; + +impl_reflect_value!(::bevy_math::primitives::Direction2d( + Debug, + PartialEq, + Serialize, + Deserialize +)); + +impl_reflect_struct!( + #[reflect(Debug, PartialEq, Serialize, Deserialize)] + #[type_path = "bevy_math::primitives"] + struct Circle { + radius: f32, + } +); + +impl_reflect_struct!( + #[reflect(Debug, PartialEq, Serialize, Deserialize)] + #[type_path = "bevy_math::primitives"] + struct Ellipse { + pub half_size: Vec2, + } +); + +impl_reflect_struct!( + #[reflect(Debug, PartialEq, Serialize, Deserialize)] + #[type_path = "bevy_math::primitives"] + struct Plane2d { + normal: Direction2d, + } +); + +impl_reflect_struct!( + #[reflect(Debug, PartialEq, Serialize, Deserialize)] + #[type_path = "bevy_math::primitives"] + struct Line2d { + direction: Direction2d, + } +); + +impl_reflect_struct!( + #[reflect(Debug, PartialEq, Serialize, Deserialize)] + #[type_path = "bevy_math::primitives"] + struct Segment2d { + direction: Direction2d, + half_length: f32, + } +); + +impl_reflect_struct!( + #[reflect(Debug, PartialEq)] + #[type_path = "bevy_math::primitives"] + struct Polyline2d { + vertices: [Vec2; N], + } +); + +impl_reflect_struct!( + #[reflect(Debug, PartialEq, Serialize, Deserialize)] + #[type_path = "bevy_math::primitives"] + struct Triangle2d { + vertices: [Vec2; 3], + } +); + +impl_reflect_struct!( + #[reflect(Debug, PartialEq, Serialize, Deserialize)] + #[type_path = "bevy_math::primitives"] + struct Rectangle { + half_size: Vec2, + } +); + +impl_reflect_struct!( + #[reflect(Debug, PartialEq)] + #[type_path = "bevy_math::primitives"] + struct Polygon { + vertices: [Vec2; N], + } +); + +impl_reflect_struct!( + #[reflect(Debug, PartialEq, Serialize, Deserialize)] + #[type_path = "bevy_math::primitives"] + struct RegularPolygon { + circumcircle: Circle, + sides: usize, + } +); diff --git a/crates/bevy_reflect/src/impls/math/primitives3d.rs b/crates/bevy_reflect/src/impls/math/primitives3d.rs new file mode 100644 index 0000000000000..7120781949dad --- /dev/null +++ b/crates/bevy_reflect/src/impls/math/primitives3d.rs @@ -0,0 +1,106 @@ +use crate as bevy_reflect; +use crate::{ReflectDeserialize, ReflectSerialize}; +use bevy_math::{primitives::*, Vec3}; +use bevy_reflect_derive::{impl_reflect_struct, impl_reflect_value}; + +impl_reflect_value!(::bevy_math::primitives::Direction3d( + Debug, + PartialEq, + Serialize, + Deserialize +)); + +impl_reflect_struct!( + #[reflect(Debug, PartialEq, Serialize, Deserialize)] + #[type_path = "bevy_math::primitives"] + struct Sphere { + radius: f32, + } +); + +impl_reflect_struct!( + #[reflect(Debug, PartialEq, Serialize, Deserialize)] + #[type_path = "bevy_math::primitives"] + struct Plane3d { + normal: Direction3d, + } +); + +impl_reflect_struct!( + #[reflect(Debug, PartialEq, Serialize, Deserialize)] + #[type_path = "bevy_math::primitives"] + struct Line3d { + direction: Direction3d, + } +); + +impl_reflect_struct!( + #[reflect(Debug, PartialEq, Serialize, Deserialize)] + #[type_path = "bevy_math::primitives"] + struct Segment3d { + direction: Direction3d, + half_length: f32, + } +); + +impl_reflect_struct!( + #[reflect(Debug, PartialEq)] + #[type_path = "bevy_math::primitives"] + struct Polyline3d { + vertices: [Vec3; N], + } +); + +impl_reflect_struct!( + #[reflect(Debug, PartialEq, Serialize, Deserialize)] + #[type_path = "bevy_math::primitives"] + struct Cuboid { + half_size: Vec3, + } +); + +impl_reflect_struct!( + #[reflect(Debug, PartialEq, Serialize, Deserialize)] + #[type_path = "bevy_math::primitives"] + struct Cylinder { + radius: f32, + half_height: f32, + } +); + +impl_reflect_struct!( + #[reflect(Debug, PartialEq, Serialize, Deserialize)] + #[type_path = "bevy_math::primitives"] + struct Capsule { + radius: f32, + half_length: f32, + } +); + +impl_reflect_struct!( + #[reflect(Debug, PartialEq, Serialize, Deserialize)] + #[type_path = "bevy_math::primitives"] + struct Cone { + radius: f32, + height: f32, + } +); + +impl_reflect_struct!( + #[reflect(Debug, PartialEq, Serialize, Deserialize)] + #[type_path = "bevy_math::primitives"] + struct ConicalFrustum { + radius_top: f32, + radius_bottom: f32, + height: f32, + } +); + +impl_reflect_struct!( + #[reflect(Debug, PartialEq, Serialize, Deserialize)] + #[type_path = "bevy_math::primitives"] + struct Torus { + minor_radius: f32, + major_radius: f32, + } +); diff --git a/crates/bevy_reflect/src/impls/rect.rs b/crates/bevy_reflect/src/impls/math/rect.rs similarity index 100% rename from crates/bevy_reflect/src/impls/rect.rs rename to crates/bevy_reflect/src/impls/math/rect.rs diff --git a/crates/bevy_reflect/src/lib.rs b/crates/bevy_reflect/src/lib.rs index f04622b58b1f9..8989ecaea9ce7 100644 --- a/crates/bevy_reflect/src/lib.rs +++ b/crates/bevy_reflect/src/lib.rs @@ -482,7 +482,11 @@ mod impls { #[cfg(feature = "glam")] mod glam; #[cfg(feature = "bevy_math")] - mod rect; + mod math { + mod primitives2d; + mod primitives3d; + mod rect; + } #[cfg(feature = "smallvec")] mod smallvec; #[cfg(feature = "smol_str")]