 d70595b667
			
		
	
	
		d70595b667
		
			
		
	
	
	
	
		
			
			# Objective - Fixes #6370 - Closes #6581 ## Solution - Added the following lints to the workspace: - `std_instead_of_core` - `std_instead_of_alloc` - `alloc_instead_of_core` - Used `cargo +nightly fmt` with [item level use formatting](https://rust-lang.github.io/rustfmt/?version=v1.6.0&search=#Item%5C%3A) to split all `use` statements into single items. - Used `cargo clippy --workspace --all-targets --all-features --fix --allow-dirty` to _attempt_ to resolve the new linting issues, and intervened where the lint was unable to resolve the issue automatically (usually due to needing an `extern crate alloc;` statement in a crate root). - Manually removed certain uses of `std` where negative feature gating prevented `--all-features` from finding the offending uses. - Used `cargo +nightly fmt` with [crate level use formatting](https://rust-lang.github.io/rustfmt/?version=v1.6.0&search=#Crate%5C%3A) to re-merge all `use` statements matching Bevy's previous styling. - Manually fixed cases where the `fmt` tool could not re-merge `use` statements due to conditional compilation attributes. ## Testing - Ran CI locally ## Migration Guide The MSRV is now 1.81. Please update to this version or higher. ## Notes - This is a _massive_ change to try and push through, which is why I've outlined the semi-automatic steps I used to create this PR, in case this fails and someone else tries again in the future. - Making this change has no impact on user code, but does mean Bevy contributors will be warned to use `core` and `alloc` instead of `std` where possible. - This lint is a critical first step towards investigating `no_std` options for Bevy. --------- Co-authored-by: François Mockers <francois.mockers@vleue.com>
		
			
				
	
	
		
			179 lines
		
	
	
		
			4.7 KiB
		
	
	
	
		
			Rust
		
	
	
	
	
	
			
		
		
	
	
			179 lines
		
	
	
		
			4.7 KiB
		
	
	
	
		
			Rust
		
	
	
	
	
	
| use core::{
 | |
|     cmp::Ordering,
 | |
|     hash::{Hash, Hasher},
 | |
|     ops::Neg,
 | |
| };
 | |
| 
 | |
| #[cfg(feature = "bevy_reflect")]
 | |
| use bevy_reflect::Reflect;
 | |
| 
 | |
| /// A wrapper for floats that implements [`Ord`], [`Eq`], and [`Hash`] traits.
 | |
| ///
 | |
| /// This is a work around for the fact that the IEEE 754-2008 standard,
 | |
| /// implemented by Rust's [`f32`] type,
 | |
| /// doesn't define an ordering for [`NaN`](f32::NAN),
 | |
| /// and `NaN` is not considered equal to any other `NaN`.
 | |
| ///
 | |
| /// Wrapping a float with `FloatOrd` breaks conformance with the standard
 | |
| /// by sorting `NaN` as less than all other numbers and equal to any other `NaN`.
 | |
| #[derive(Debug, Copy, Clone)]
 | |
| #[cfg_attr(
 | |
|     feature = "bevy_reflect",
 | |
|     derive(Reflect),
 | |
|     reflect(Debug, PartialEq, Hash)
 | |
| )]
 | |
| pub struct FloatOrd(pub f32);
 | |
| 
 | |
| impl PartialOrd for FloatOrd {
 | |
|     fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
 | |
|         Some(self.cmp(other))
 | |
|     }
 | |
| 
 | |
|     fn lt(&self, other: &Self) -> bool {
 | |
|         !other.le(self)
 | |
|     }
 | |
|     // If `self` is NaN, it is equal to another NaN and less than all other floats, so return true.
 | |
|     // If `self` isn't NaN and `other` is, the float comparison returns false, which match the `FloatOrd` ordering.
 | |
|     // Otherwise, a standard float comparison happens.
 | |
|     fn le(&self, other: &Self) -> bool {
 | |
|         self.0.is_nan() || self.0 <= other.0
 | |
|     }
 | |
|     fn gt(&self, other: &Self) -> bool {
 | |
|         !self.le(other)
 | |
|     }
 | |
|     fn ge(&self, other: &Self) -> bool {
 | |
|         other.le(self)
 | |
|     }
 | |
| }
 | |
| 
 | |
| impl Ord for FloatOrd {
 | |
|     #[allow(clippy::comparison_chain)]
 | |
|     fn cmp(&self, other: &Self) -> Ordering {
 | |
|         if self > other {
 | |
|             Ordering::Greater
 | |
|         } else if self < other {
 | |
|             Ordering::Less
 | |
|         } else {
 | |
|             Ordering::Equal
 | |
|         }
 | |
|     }
 | |
| }
 | |
| 
 | |
| impl PartialEq for FloatOrd {
 | |
|     fn eq(&self, other: &Self) -> bool {
 | |
|         if self.0.is_nan() {
 | |
|             other.0.is_nan()
 | |
|         } else {
 | |
|             self.0 == other.0
 | |
|         }
 | |
|     }
 | |
| }
 | |
| 
 | |
| impl Eq for FloatOrd {}
 | |
| 
 | |
| impl Hash for FloatOrd {
 | |
|     fn hash<H: Hasher>(&self, state: &mut H) {
 | |
|         if self.0.is_nan() {
 | |
|             // Ensure all NaN representations hash to the same value
 | |
|             state.write(&f32::to_ne_bytes(f32::NAN));
 | |
|         } else if self.0 == 0.0 {
 | |
|             // Ensure both zeroes hash to the same value
 | |
|             state.write(&f32::to_ne_bytes(0.0f32));
 | |
|         } else {
 | |
|             state.write(&f32::to_ne_bytes(self.0));
 | |
|         }
 | |
|     }
 | |
| }
 | |
| 
 | |
| impl Neg for FloatOrd {
 | |
|     type Output = FloatOrd;
 | |
| 
 | |
|     fn neg(self) -> Self::Output {
 | |
|         FloatOrd(-self.0)
 | |
|     }
 | |
| }
 | |
| 
 | |
| #[cfg(test)]
 | |
| mod tests {
 | |
|     use std::hash::DefaultHasher;
 | |
| 
 | |
|     use super::*;
 | |
| 
 | |
|     const NAN: FloatOrd = FloatOrd(f32::NAN);
 | |
|     const ZERO: FloatOrd = FloatOrd(0.0);
 | |
|     const ONE: FloatOrd = FloatOrd(1.0);
 | |
| 
 | |
|     #[test]
 | |
|     fn float_ord_eq() {
 | |
|         assert_eq!(NAN, NAN);
 | |
| 
 | |
|         assert_ne!(NAN, ZERO);
 | |
|         assert_ne!(ZERO, NAN);
 | |
| 
 | |
|         assert_eq!(ZERO, ZERO);
 | |
|     }
 | |
| 
 | |
|     #[test]
 | |
|     fn float_ord_cmp() {
 | |
|         assert_eq!(NAN.cmp(&NAN), Ordering::Equal);
 | |
| 
 | |
|         assert_eq!(NAN.cmp(&ZERO), Ordering::Less);
 | |
|         assert_eq!(ZERO.cmp(&NAN), Ordering::Greater);
 | |
| 
 | |
|         assert_eq!(ZERO.cmp(&ZERO), Ordering::Equal);
 | |
|         assert_eq!(ONE.cmp(&ZERO), Ordering::Greater);
 | |
|         assert_eq!(ZERO.cmp(&ONE), Ordering::Less);
 | |
|     }
 | |
| 
 | |
|     #[test]
 | |
|     #[allow(clippy::nonminimal_bool)]
 | |
|     fn float_ord_cmp_operators() {
 | |
|         assert!(!(NAN < NAN));
 | |
|         assert!(NAN < ZERO);
 | |
|         assert!(!(ZERO < NAN));
 | |
|         assert!(!(ZERO < ZERO));
 | |
|         assert!(ZERO < ONE);
 | |
|         assert!(!(ONE < ZERO));
 | |
| 
 | |
|         assert!(!(NAN > NAN));
 | |
|         assert!(!(NAN > ZERO));
 | |
|         assert!(ZERO > NAN);
 | |
|         assert!(!(ZERO > ZERO));
 | |
|         assert!(!(ZERO > ONE));
 | |
|         assert!(ONE > ZERO);
 | |
| 
 | |
|         assert!(NAN <= NAN);
 | |
|         assert!(NAN <= ZERO);
 | |
|         assert!(!(ZERO <= NAN));
 | |
|         assert!(ZERO <= ZERO);
 | |
|         assert!(ZERO <= ONE);
 | |
|         assert!(!(ONE <= ZERO));
 | |
| 
 | |
|         assert!(NAN >= NAN);
 | |
|         assert!(!(NAN >= ZERO));
 | |
|         assert!(ZERO >= NAN);
 | |
|         assert!(ZERO >= ZERO);
 | |
|         assert!(!(ZERO >= ONE));
 | |
|         assert!(ONE >= ZERO);
 | |
|     }
 | |
| 
 | |
|     #[test]
 | |
|     fn float_ord_hash() {
 | |
|         let hash = |num| {
 | |
|             let mut h = DefaultHasher::new();
 | |
|             FloatOrd(num).hash(&mut h);
 | |
|             h.finish()
 | |
|         };
 | |
| 
 | |
|         assert_ne!((-0.0f32).to_bits(), 0.0f32.to_bits());
 | |
|         assert_eq!(hash(-0.0), hash(0.0));
 | |
| 
 | |
|         let nan_1 = f32::from_bits(0b0111_1111_1000_0000_0000_0000_0000_0001);
 | |
|         assert!(nan_1.is_nan());
 | |
|         let nan_2 = f32::from_bits(0b0111_1111_1000_0000_0000_0000_0000_0010);
 | |
|         assert!(nan_2.is_nan());
 | |
|         assert_ne!(nan_1.to_bits(), nan_2.to_bits());
 | |
|         assert_eq!(hash(nan_1), hash(nan_2));
 | |
|     }
 | |
| }
 |