
# Objective Now that #13432 has been merged, it's important we update our reflected types to properly opt into this feature. If we do not, then this could cause issues for users downstream who want to make use of reflection-based cloning. ## Solution This PR is broken into 4 commits: 1. Add `#[reflect(Clone)]` on all types marked `#[reflect(opaque)]` that are also `Clone`. This is mandatory as these types would otherwise cause the cloning operation to fail for any type that contains it at any depth. 2. Update the reflection example to suggest adding `#[reflect(Clone)]` on opaque types. 3. Add `#[reflect(clone)]` attributes on all fields marked `#[reflect(ignore)]` that are also `Clone`. This prevents the ignored field from causing the cloning operation to fail. Note that some of the types that contain these fields are also `Clone`, and thus can be marked `#[reflect(Clone)]`. This makes the `#[reflect(clone)]` attribute redundant. However, I think it's safer to keep it marked in the case that the `Clone` impl/derive is ever removed. I'm open to removing them, though, if people disagree. 4. Finally, I added `#[reflect(Clone)]` on all types that are also `Clone`. While not strictly necessary, it enables us to reduce the generated output since we can just call `Clone::clone` directly instead of calling `PartialReflect::reflect_clone` on each variant/field. It also means we benefit from any optimizations or customizations made in the `Clone` impl, including directly dereferencing `Copy` values and increasing reference counters. Along with that change I also took the liberty of adding any missing registrations that I saw could be applied to the type as well, such as `Default`, `PartialEq`, and `Hash`. There were hundreds of these to edit, though, so it's possible I missed quite a few. That last commit is **_massive_**. There were nearly 700 types to update. So it's recommended to review the first three before moving onto that last one. Additionally, I can break the last commit off into its own PR or into smaller PRs, but I figured this would be the easiest way of doing it (and in a timely manner since I unfortunately don't have as much time as I used to for code contributions). ## Testing You can test locally with a `cargo check`: ``` cargo check --workspace --all-features ```
586 lines
16 KiB
Rust
586 lines
16 KiB
Rust
use core::ops::Neg;
|
||
|
||
use crate::Dir2;
|
||
#[cfg(feature = "bevy_reflect")]
|
||
use bevy_reflect::Reflect;
|
||
#[cfg(all(feature = "serialize", feature = "bevy_reflect"))]
|
||
use bevy_reflect::{ReflectDeserialize, ReflectSerialize};
|
||
|
||
/// A compass enum with 4 directions.
|
||
/// ```text
|
||
/// N (North)
|
||
/// ▲
|
||
/// │
|
||
/// │
|
||
/// W (West) ┼─────► E (East)
|
||
/// │
|
||
/// │
|
||
/// ▼
|
||
/// S (South)
|
||
/// ```
|
||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
|
||
#[cfg_attr(
|
||
feature = "bevy_reflect",
|
||
derive(Reflect),
|
||
reflect(Debug, PartialEq, Hash, Clone)
|
||
)]
|
||
#[cfg_attr(
|
||
all(feature = "serialize", feature = "bevy_reflect"),
|
||
reflect(Deserialize, Serialize)
|
||
)]
|
||
pub enum CompassQuadrant {
|
||
/// Corresponds to [`Dir2::Y`] and [`Dir2::NORTH`]
|
||
North,
|
||
/// Corresponds to [`Dir2::X`] and [`Dir2::EAST`]
|
||
East,
|
||
/// Corresponds to [`Dir2::NEG_X`] and [`Dir2::SOUTH`]
|
||
South,
|
||
/// Corresponds to [`Dir2::NEG_Y`] and [`Dir2::WEST`]
|
||
West,
|
||
}
|
||
|
||
impl CompassQuadrant {
|
||
/// Converts a standard index to a [`CompassQuadrant`].
|
||
///
|
||
/// Starts at 0 for [`CompassQuadrant::North`] and increments clockwise.
|
||
pub const fn from_index(index: usize) -> Option<Self> {
|
||
match index {
|
||
0 => Some(Self::North),
|
||
1 => Some(Self::East),
|
||
2 => Some(Self::South),
|
||
3 => Some(Self::West),
|
||
_ => None,
|
||
}
|
||
}
|
||
|
||
/// Converts a [`CompassQuadrant`] to a standard index.
|
||
///
|
||
/// Starts at 0 for [`CompassQuadrant::North`] and increments clockwise.
|
||
pub const fn to_index(self) -> usize {
|
||
match self {
|
||
Self::North => 0,
|
||
Self::East => 1,
|
||
Self::South => 2,
|
||
Self::West => 3,
|
||
}
|
||
}
|
||
|
||
/// Returns the opposite [`CompassQuadrant`], located 180 degrees from `self`.
|
||
///
|
||
/// This can also be accessed via the `-` operator, using the [`Neg`] trait.
|
||
pub const fn opposite(&self) -> CompassQuadrant {
|
||
match self {
|
||
Self::North => Self::South,
|
||
Self::East => Self::West,
|
||
Self::South => Self::North,
|
||
Self::West => Self::East,
|
||
}
|
||
}
|
||
}
|
||
|
||
/// A compass enum with 8 directions.
|
||
/// ```text
|
||
/// N (North)
|
||
/// ▲
|
||
/// NW │ NE
|
||
/// ╲ │ ╱
|
||
/// W (West) ┼─────► E (East)
|
||
/// ╱ │ ╲
|
||
/// SW │ SE
|
||
/// ▼
|
||
/// S (South)
|
||
/// ```
|
||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
|
||
#[cfg_attr(
|
||
feature = "bevy_reflect",
|
||
derive(Reflect),
|
||
reflect(Debug, PartialEq, Hash, Clone)
|
||
)]
|
||
#[cfg_attr(
|
||
all(feature = "serialize", feature = "bevy_reflect"),
|
||
reflect(Deserialize, Serialize)
|
||
)]
|
||
pub enum CompassOctant {
|
||
/// Corresponds to [`Dir2::Y`] and [`Dir2::NORTH`]
|
||
North,
|
||
/// Corresponds to [`Dir2::NORTH_EAST`]
|
||
NorthEast,
|
||
/// Corresponds to [`Dir2::X`] and [`Dir2::EAST`]
|
||
East,
|
||
/// Corresponds to [`Dir2::SOUTH_EAST`]
|
||
SouthEast,
|
||
/// Corresponds to [`Dir2::NEG_X`] and [`Dir2::SOUTH`]
|
||
South,
|
||
/// Corresponds to [`Dir2::SOUTH_WEST`]
|
||
SouthWest,
|
||
/// Corresponds to [`Dir2::NEG_Y`] and [`Dir2::WEST`]
|
||
West,
|
||
/// Corresponds to [`Dir2::NORTH_WEST`]
|
||
NorthWest,
|
||
}
|
||
|
||
impl CompassOctant {
|
||
/// Converts a standard index to a [`CompassOctant`].
|
||
///
|
||
/// Starts at 0 for [`CompassOctant::North`] and increments clockwise.
|
||
pub const fn from_index(index: usize) -> Option<Self> {
|
||
match index {
|
||
0 => Some(Self::North),
|
||
1 => Some(Self::NorthEast),
|
||
2 => Some(Self::East),
|
||
3 => Some(Self::SouthEast),
|
||
4 => Some(Self::South),
|
||
5 => Some(Self::SouthWest),
|
||
6 => Some(Self::West),
|
||
7 => Some(Self::NorthWest),
|
||
_ => None,
|
||
}
|
||
}
|
||
|
||
/// Converts a [`CompassOctant`] to a standard index.
|
||
///
|
||
/// Starts at 0 for [`CompassOctant::North`] and increments clockwise.
|
||
pub const fn to_index(self) -> usize {
|
||
match self {
|
||
Self::North => 0,
|
||
Self::NorthEast => 1,
|
||
Self::East => 2,
|
||
Self::SouthEast => 3,
|
||
Self::South => 4,
|
||
Self::SouthWest => 5,
|
||
Self::West => 6,
|
||
Self::NorthWest => 7,
|
||
}
|
||
}
|
||
|
||
/// Returns the opposite [`CompassOctant`], located 180 degrees from `self`.
|
||
///
|
||
/// This can also be accessed via the `-` operator, using the [`Neg`] trait.
|
||
pub const fn opposite(&self) -> CompassOctant {
|
||
match self {
|
||
Self::North => Self::South,
|
||
Self::NorthEast => Self::SouthWest,
|
||
Self::East => Self::West,
|
||
Self::SouthEast => Self::NorthWest,
|
||
Self::South => Self::North,
|
||
Self::SouthWest => Self::NorthEast,
|
||
Self::West => Self::East,
|
||
Self::NorthWest => Self::SouthEast,
|
||
}
|
||
}
|
||
}
|
||
|
||
impl From<CompassQuadrant> for Dir2 {
|
||
fn from(q: CompassQuadrant) -> Self {
|
||
match q {
|
||
CompassQuadrant::North => Dir2::NORTH,
|
||
CompassQuadrant::East => Dir2::EAST,
|
||
CompassQuadrant::South => Dir2::SOUTH,
|
||
CompassQuadrant::West => Dir2::WEST,
|
||
}
|
||
}
|
||
}
|
||
|
||
impl From<Dir2> for CompassQuadrant {
|
||
/// Converts a [`Dir2`] to a [`CompassQuadrant`] in a lossy manner.
|
||
/// Converting back to a [`Dir2`] is not guaranteed to yield the same value.
|
||
fn from(dir: Dir2) -> Self {
|
||
let angle = dir.to_angle().to_degrees();
|
||
|
||
match angle {
|
||
-135.0..=-45.0 => Self::South,
|
||
-45.0..=45.0 => Self::East,
|
||
45.0..=135.0 => Self::North,
|
||
135.0..=180.0 | -180.0..=-135.0 => Self::West,
|
||
_ => unreachable!(),
|
||
}
|
||
}
|
||
}
|
||
|
||
impl From<CompassOctant> for Dir2 {
|
||
fn from(o: CompassOctant) -> Self {
|
||
match o {
|
||
CompassOctant::North => Dir2::NORTH,
|
||
CompassOctant::NorthEast => Dir2::NORTH_EAST,
|
||
CompassOctant::East => Dir2::EAST,
|
||
CompassOctant::SouthEast => Dir2::SOUTH_EAST,
|
||
CompassOctant::South => Dir2::SOUTH,
|
||
CompassOctant::SouthWest => Dir2::SOUTH_WEST,
|
||
CompassOctant::West => Dir2::WEST,
|
||
CompassOctant::NorthWest => Dir2::NORTH_WEST,
|
||
}
|
||
}
|
||
}
|
||
|
||
impl From<Dir2> for CompassOctant {
|
||
/// Converts a [`Dir2`] to a [`CompassOctant`] in a lossy manner.
|
||
/// Converting back to a [`Dir2`] is not guaranteed to yield the same value.
|
||
fn from(dir: Dir2) -> Self {
|
||
let angle = dir.to_angle().to_degrees();
|
||
|
||
match angle {
|
||
-112.5..=-67.5 => Self::South,
|
||
-67.5..=-22.5 => Self::SouthEast,
|
||
-22.5..=22.5 => Self::East,
|
||
22.5..=67.5 => Self::NorthEast,
|
||
67.5..=112.5 => Self::North,
|
||
112.5..=157.5 => Self::NorthWest,
|
||
157.5..=180.0 | -180.0..=-157.5 => Self::West,
|
||
-157.5..=-112.5 => Self::SouthWest,
|
||
_ => unreachable!(),
|
||
}
|
||
}
|
||
}
|
||
|
||
impl Neg for CompassQuadrant {
|
||
type Output = CompassQuadrant;
|
||
|
||
fn neg(self) -> Self::Output {
|
||
self.opposite()
|
||
}
|
||
}
|
||
|
||
impl Neg for CompassOctant {
|
||
type Output = CompassOctant;
|
||
|
||
fn neg(self) -> Self::Output {
|
||
self.opposite()
|
||
}
|
||
}
|
||
|
||
#[cfg(test)]
|
||
mod test_compass_quadrant {
|
||
use crate::{CompassQuadrant, Dir2, Vec2};
|
||
|
||
#[test]
|
||
fn test_cardinal_directions() {
|
||
let tests = [
|
||
(
|
||
Dir2::new(Vec2::new(1.0, 0.0)).unwrap(),
|
||
CompassQuadrant::East,
|
||
),
|
||
(
|
||
Dir2::new(Vec2::new(0.0, 1.0)).unwrap(),
|
||
CompassQuadrant::North,
|
||
),
|
||
(
|
||
Dir2::new(Vec2::new(-1.0, 0.0)).unwrap(),
|
||
CompassQuadrant::West,
|
||
),
|
||
(
|
||
Dir2::new(Vec2::new(0.0, -1.0)).unwrap(),
|
||
CompassQuadrant::South,
|
||
),
|
||
];
|
||
|
||
for (dir, expected) in tests {
|
||
assert_eq!(CompassQuadrant::from(dir), expected);
|
||
}
|
||
}
|
||
|
||
#[test]
|
||
fn test_north_pie_slice() {
|
||
let tests = [
|
||
(
|
||
Dir2::new(Vec2::new(-0.1, 0.9)).unwrap(),
|
||
CompassQuadrant::North,
|
||
),
|
||
(
|
||
Dir2::new(Vec2::new(0.1, 0.9)).unwrap(),
|
||
CompassQuadrant::North,
|
||
),
|
||
];
|
||
|
||
for (dir, expected) in tests {
|
||
assert_eq!(CompassQuadrant::from(dir), expected);
|
||
}
|
||
}
|
||
|
||
#[test]
|
||
fn test_east_pie_slice() {
|
||
let tests = [
|
||
(
|
||
Dir2::new(Vec2::new(0.9, 0.1)).unwrap(),
|
||
CompassQuadrant::East,
|
||
),
|
||
(
|
||
Dir2::new(Vec2::new(0.9, -0.1)).unwrap(),
|
||
CompassQuadrant::East,
|
||
),
|
||
];
|
||
|
||
for (dir, expected) in tests {
|
||
assert_eq!(CompassQuadrant::from(dir), expected);
|
||
}
|
||
}
|
||
|
||
#[test]
|
||
fn test_south_pie_slice() {
|
||
let tests = [
|
||
(
|
||
Dir2::new(Vec2::new(-0.1, -0.9)).unwrap(),
|
||
CompassQuadrant::South,
|
||
),
|
||
(
|
||
Dir2::new(Vec2::new(0.1, -0.9)).unwrap(),
|
||
CompassQuadrant::South,
|
||
),
|
||
];
|
||
|
||
for (dir, expected) in tests {
|
||
assert_eq!(CompassQuadrant::from(dir), expected);
|
||
}
|
||
}
|
||
|
||
#[test]
|
||
fn test_west_pie_slice() {
|
||
let tests = [
|
||
(
|
||
Dir2::new(Vec2::new(-0.9, -0.1)).unwrap(),
|
||
CompassQuadrant::West,
|
||
),
|
||
(
|
||
Dir2::new(Vec2::new(-0.9, 0.1)).unwrap(),
|
||
CompassQuadrant::West,
|
||
),
|
||
];
|
||
|
||
for (dir, expected) in tests {
|
||
assert_eq!(CompassQuadrant::from(dir), expected);
|
||
}
|
||
}
|
||
|
||
#[test]
|
||
fn out_of_bounds_indexes_return_none() {
|
||
assert_eq!(CompassQuadrant::from_index(4), None);
|
||
assert_eq!(CompassQuadrant::from_index(5), None);
|
||
assert_eq!(CompassQuadrant::from_index(usize::MAX), None);
|
||
}
|
||
|
||
#[test]
|
||
fn compass_indexes_are_reversible() {
|
||
for i in 0..4 {
|
||
let quadrant = CompassQuadrant::from_index(i).unwrap();
|
||
assert_eq!(quadrant.to_index(), i);
|
||
}
|
||
}
|
||
|
||
#[test]
|
||
fn opposite_directions_reverse_themselves() {
|
||
for i in 0..4 {
|
||
let quadrant = CompassQuadrant::from_index(i).unwrap();
|
||
assert_eq!(-(-quadrant), quadrant);
|
||
}
|
||
}
|
||
}
|
||
|
||
#[cfg(test)]
|
||
mod test_compass_octant {
|
||
use crate::{CompassOctant, Dir2, Vec2};
|
||
|
||
#[test]
|
||
fn test_cardinal_directions() {
|
||
let tests = [
|
||
(
|
||
Dir2::new(Vec2::new(-0.5, 0.5)).unwrap(),
|
||
CompassOctant::NorthWest,
|
||
),
|
||
(
|
||
Dir2::new(Vec2::new(0.0, 1.0)).unwrap(),
|
||
CompassOctant::North,
|
||
),
|
||
(
|
||
Dir2::new(Vec2::new(0.5, 0.5)).unwrap(),
|
||
CompassOctant::NorthEast,
|
||
),
|
||
(Dir2::new(Vec2::new(1.0, 0.0)).unwrap(), CompassOctant::East),
|
||
(
|
||
Dir2::new(Vec2::new(0.5, -0.5)).unwrap(),
|
||
CompassOctant::SouthEast,
|
||
),
|
||
(
|
||
Dir2::new(Vec2::new(0.0, -1.0)).unwrap(),
|
||
CompassOctant::South,
|
||
),
|
||
(
|
||
Dir2::new(Vec2::new(-0.5, -0.5)).unwrap(),
|
||
CompassOctant::SouthWest,
|
||
),
|
||
(
|
||
Dir2::new(Vec2::new(-1.0, 0.0)).unwrap(),
|
||
CompassOctant::West,
|
||
),
|
||
];
|
||
|
||
for (dir, expected) in tests {
|
||
assert_eq!(CompassOctant::from(dir), expected);
|
||
}
|
||
}
|
||
|
||
#[test]
|
||
fn test_north_pie_slice() {
|
||
let tests = [
|
||
(
|
||
Dir2::new(Vec2::new(-0.1, 0.9)).unwrap(),
|
||
CompassOctant::North,
|
||
),
|
||
(
|
||
Dir2::new(Vec2::new(0.1, 0.9)).unwrap(),
|
||
CompassOctant::North,
|
||
),
|
||
];
|
||
|
||
for (dir, expected) in tests {
|
||
assert_eq!(CompassOctant::from(dir), expected);
|
||
}
|
||
}
|
||
|
||
#[test]
|
||
fn test_north_east_pie_slice() {
|
||
let tests = [
|
||
(
|
||
Dir2::new(Vec2::new(0.4, 0.6)).unwrap(),
|
||
CompassOctant::NorthEast,
|
||
),
|
||
(
|
||
Dir2::new(Vec2::new(0.6, 0.4)).unwrap(),
|
||
CompassOctant::NorthEast,
|
||
),
|
||
];
|
||
|
||
for (dir, expected) in tests {
|
||
assert_eq!(CompassOctant::from(dir), expected);
|
||
}
|
||
}
|
||
|
||
#[test]
|
||
fn test_east_pie_slice() {
|
||
let tests = [
|
||
(Dir2::new(Vec2::new(0.9, 0.1)).unwrap(), CompassOctant::East),
|
||
(
|
||
Dir2::new(Vec2::new(0.9, -0.1)).unwrap(),
|
||
CompassOctant::East,
|
||
),
|
||
];
|
||
|
||
for (dir, expected) in tests {
|
||
assert_eq!(CompassOctant::from(dir), expected);
|
||
}
|
||
}
|
||
|
||
#[test]
|
||
fn test_south_east_pie_slice() {
|
||
let tests = [
|
||
(
|
||
Dir2::new(Vec2::new(0.4, -0.6)).unwrap(),
|
||
CompassOctant::SouthEast,
|
||
),
|
||
(
|
||
Dir2::new(Vec2::new(0.6, -0.4)).unwrap(),
|
||
CompassOctant::SouthEast,
|
||
),
|
||
];
|
||
|
||
for (dir, expected) in tests {
|
||
assert_eq!(CompassOctant::from(dir), expected);
|
||
}
|
||
}
|
||
|
||
#[test]
|
||
fn test_south_pie_slice() {
|
||
let tests = [
|
||
(
|
||
Dir2::new(Vec2::new(-0.1, -0.9)).unwrap(),
|
||
CompassOctant::South,
|
||
),
|
||
(
|
||
Dir2::new(Vec2::new(0.1, -0.9)).unwrap(),
|
||
CompassOctant::South,
|
||
),
|
||
];
|
||
|
||
for (dir, expected) in tests {
|
||
assert_eq!(CompassOctant::from(dir), expected);
|
||
}
|
||
}
|
||
|
||
#[test]
|
||
fn test_south_west_pie_slice() {
|
||
let tests = [
|
||
(
|
||
Dir2::new(Vec2::new(-0.4, -0.6)).unwrap(),
|
||
CompassOctant::SouthWest,
|
||
),
|
||
(
|
||
Dir2::new(Vec2::new(-0.6, -0.4)).unwrap(),
|
||
CompassOctant::SouthWest,
|
||
),
|
||
];
|
||
|
||
for (dir, expected) in tests {
|
||
assert_eq!(CompassOctant::from(dir), expected);
|
||
}
|
||
}
|
||
|
||
#[test]
|
||
fn test_west_pie_slice() {
|
||
let tests = [
|
||
(
|
||
Dir2::new(Vec2::new(-0.9, -0.1)).unwrap(),
|
||
CompassOctant::West,
|
||
),
|
||
(
|
||
Dir2::new(Vec2::new(-0.9, 0.1)).unwrap(),
|
||
CompassOctant::West,
|
||
),
|
||
];
|
||
|
||
for (dir, expected) in tests {
|
||
assert_eq!(CompassOctant::from(dir), expected);
|
||
}
|
||
}
|
||
|
||
#[test]
|
||
fn test_north_west_pie_slice() {
|
||
let tests = [
|
||
(
|
||
Dir2::new(Vec2::new(-0.4, 0.6)).unwrap(),
|
||
CompassOctant::NorthWest,
|
||
),
|
||
(
|
||
Dir2::new(Vec2::new(-0.6, 0.4)).unwrap(),
|
||
CompassOctant::NorthWest,
|
||
),
|
||
];
|
||
|
||
for (dir, expected) in tests {
|
||
assert_eq!(CompassOctant::from(dir), expected);
|
||
}
|
||
}
|
||
|
||
#[test]
|
||
fn out_of_bounds_indexes_return_none() {
|
||
assert_eq!(CompassOctant::from_index(8), None);
|
||
assert_eq!(CompassOctant::from_index(9), None);
|
||
assert_eq!(CompassOctant::from_index(usize::MAX), None);
|
||
}
|
||
|
||
#[test]
|
||
fn compass_indexes_are_reversible() {
|
||
for i in 0..8 {
|
||
let octant = CompassOctant::from_index(i).unwrap();
|
||
assert_eq!(octant.to_index(), i);
|
||
}
|
||
}
|
||
|
||
#[test]
|
||
fn opposite_directions_reverse_themselves() {
|
||
for i in 0..8 {
|
||
let octant = CompassOctant::from_index(i).unwrap();
|
||
assert_eq!(-(-octant), octant);
|
||
}
|
||
}
|
||
}
|