diff --git a/Cargo.toml b/Cargo.toml index 312eb6ebd..e0b8092c0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -43,7 +43,7 @@ resolver = "2" # When changing any of these values, be sure to also update the non-workspace packages # as listed above, if applicable. edition = "2021" -rust-version = "1.81" +rust-version = "1.82" license = "MIT OR Apache-2.0" authors = ["Kevin Reid "] # TODO: add homepage = "..." when we have one diff --git a/all-is-cubes-base/src/math/color.rs b/all-is-cubes-base/src/math/color.rs index 816ede090..e96e5982f 100644 --- a/all-is-cubes-base/src/math/color.rs +++ b/all-is-cubes-base/src/math/color.rs @@ -107,8 +107,22 @@ impl Rgb { /// No other range checks are performed. #[inline] #[track_caller] - pub fn new(r: f32, g: f32, b: f32) -> Self { - Self::try_from(vec3(r, g, b)).expect("Color components may not be NaN") + pub const fn new(r: f32, g: f32, b: f32) -> Self { + match Self::try_new(vec3(r, g, b)) { + Ok(color) => color, + Err(_) => panic!("color components may not be NaN"), + } + } + + const fn try_new(value: Vector3D) -> Result { + match ( + new_nn_f32(value.x), + new_nn_f32(value.y), + new_nn_f32(value.z), + ) { + (Ok(r), Ok(g), Ok(b)) => Ok(Self(vec3(r, g, b))), + (Err(e), _, _) | (_, Err(e), _) | (_, _, Err(e)) => Err(e), + } } /// Constructs a color from components that have already been checked for not being @@ -124,7 +138,8 @@ impl Rgb { /// Constructs a shade of gray (components all equal). Panics if any component is NaN. /// No other range checks are performed. #[inline] - pub fn from_luminance(luminance: f32) -> Self { + #[track_caller] + pub const fn from_luminance(luminance: f32) -> Self { Self::new(luminance, luminance, luminance) } @@ -224,8 +239,11 @@ impl Rgba { /// No other range checks are performed. #[inline] #[track_caller] - pub fn new(r: f32, g: f32, b: f32, a: f32) -> Self { - Rgb::new(r, g, b).with_alpha(NotNan::new(a).expect("Alpha may not be NaN")) + pub const fn new(r: f32, g: f32, b: f32, a: f32) -> Self { + match new_nn_f32(a) { + Ok(a) => Rgb::new(r, g, b).with_alpha(a), + Err(_) => panic!("alpha may not be NaN"), + } } /// Constructs a color from components that have already been checked for not being @@ -244,7 +262,8 @@ impl Rgba { /// Constructs a shade of gray (components all equal). Panics if any component is NaN. /// No other range checks are performed. #[inline] - pub fn from_luminance(luminance: f32) -> Self { + #[track_caller] + pub const fn from_luminance(luminance: f32) -> Self { Rgb::new(luminance, luminance, luminance).with_alpha_one() } @@ -784,6 +803,19 @@ const CONST_LINEAR_LOOKUP_TABLE: &[f32; 256] = &[ 0.9882353, 0.99215686, 0.99607843, 1.0, ]; +// Const replacement for `NotNan::new()` +const fn new_nn_f32(value: f32) -> Result, FloatIsNan> { + #![allow(clippy::eq_op)] + // This condition is true if and only if the value is NaN + // TODO: Replace this with `.is_nan()` after Rust 1.83 is released with `const_float_classify`. + if value != value { + Err(FloatIsNan) + } else { + // SAFETY: We just checked the only safety condition. + Ok(unsafe { NotNan::new_unchecked(value) }) + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/all-is-cubes-base/src/raycast/tests.rs b/all-is-cubes-base/src/raycast/tests.rs index c0a194cfc..51d66a593 100644 --- a/all-is-cubes-base/src/raycast/tests.rs +++ b/all-is-cubes-base/src/raycast/tests.rs @@ -27,8 +27,7 @@ impl TestStep { fn matches(self, step: &RaycastStep) -> bool { self.cube == step.cube_ahead() && self.face == step.face() - && self.t_distance.map_or(true, |td| step.t_distance() == td) - && self.t_distance.map_or(true, |td| step.t_distance() == td) + && self.t_distance.is_none_or(|td| step.t_distance() == td) } } diff --git a/all-is-cubes-desktop/src/glue/rerun_mesh.rs b/all-is-cubes-desktop/src/glue/rerun_mesh.rs index 5b4f952d1..ac664c24f 100644 --- a/all-is-cubes-desktop/src/glue/rerun_mesh.rs +++ b/all-is-cubes-desktop/src/glue/rerun_mesh.rs @@ -48,8 +48,6 @@ impl From> for Vertex { position: v.position.to_f32().to_array().into(), color: match v.coloring { mesh::Coloring::Solid(color) => color.to_srgb8().into(), - #[allow(unreachable_patterns)] // TODO: Remove this arm after Rust 1.82 - mesh::Coloring::Texture { .. } => unreachable!(), }, } } diff --git a/all-is-cubes-mesh/src/block_mesh/viz.rs b/all-is-cubes-mesh/src/block_mesh/viz.rs index ea02d75bc..1a2065573 100644 --- a/all-is-cubes-mesh/src/block_mesh/viz.rs +++ b/all-is-cubes-mesh/src/block_mesh/viz.rs @@ -213,15 +213,14 @@ impl Viz { state .mesh_vertex_positions .extend(vertex_positions.iter().copied()); - state.mesh_vertex_colors.extend( - iter::repeat(rg::components::Color(color_fn().into())).take(vertex_positions.len()), - ); - state.mesh_vertex_normals.extend( - iter::repeat(rg::components::Vector3D(rg::convert_vec( - normal.normal_vector::(), - ))) - .take(vertex_positions.len()), - ); + state.mesh_vertex_colors.extend(iter::repeat_n( + rg::components::Color(color_fn().into()), + vertex_positions.len(), + )); + state.mesh_vertex_normals.extend(iter::repeat_n( + rg::components::Vector3D(rg::convert_vec(normal.normal_vector::())), + vertex_positions.len(), + )); state.mesh_triangle_indices.extend( relative_indices_iter .clone() diff --git a/all-is-cubes-mesh/src/space_mesh.rs b/all-is-cubes-mesh/src/space_mesh.rs index 5cbb2abc1..d618e13ad 100644 --- a/all-is-cubes-mesh/src/space_mesh.rs +++ b/all-is-cubes-mesh/src/space_mesh.rs @@ -258,7 +258,7 @@ impl SpaceMesh { if let Some(adj_block_index) = space_data_source(adjacent_cube) { if block_meshes .get_block_mesh(adj_block_index, adjacent_cube, false) - .map_or(false, |bm| bm.face_vertices[face.opposite()].fully_opaque) + .is_some_and(|bm| bm.face_vertices[face.opposite()].fully_opaque) { // Don't draw obscured faces, but do record that we depended on them. bitset_set_and_get( diff --git a/all-is-cubes-port/src/gltf.rs b/all-is-cubes-port/src/gltf.rs index 43bb50436..8f359b5e3 100644 --- a/all-is-cubes-port/src/gltf.rs +++ b/all-is-cubes-port/src/gltf.rs @@ -251,7 +251,7 @@ impl GltfWriter { for (frame_number, state) in self.frame_states.iter().enumerate() { for &instance in &state.visible_mesh_instances { let timeline = timelines.entry(instance).or_default(); - if !timeline.last().map_or(true, |&(_, vis)| vis) { + if !timeline.last().is_none_or(|&(_, vis)| vis) { // Node needs to be made visible. timeline.push((frame_number, true)); } @@ -267,7 +267,7 @@ impl GltfWriter { match timelines.entry(instance) { Entry::Occupied(mut e) => { let timeline = e.get_mut(); - if timeline.last().map_or(true, |&(_, vis)| vis) { + if timeline.last().is_none_or(|&(_, vis)| vis) { // Node needs to be made invisible. timeline.push((frame_number, false)); } diff --git a/all-is-cubes-ui/src/ui_content/pages.rs b/all-is-cubes-ui/src/ui_content/pages.rs index b1984bb34..c88a39f68 100644 --- a/all-is-cubes-ui/src/ui_content/pages.rs +++ b/all-is-cubes-ui/src/ui_content/pages.rs @@ -53,8 +53,6 @@ pub(super) fn new_paused_page( // TODO: quit_fn should be an async function, but we don't have a way to // kick off a “Quitting...” task yet. move || match quit_fn() { - #[allow(unreachable_patterns, reason = "TODO: Remove this arm after Rust 1.82")] - Ok(s) => match s {}, Err(_cancelled) => { // TODO: display message indicating failure diff --git a/all-is-cubes-wasm/Cargo.toml b/all-is-cubes-wasm/Cargo.toml index 3d75c5692..7115b4057 100644 --- a/all-is-cubes-wasm/Cargo.toml +++ b/all-is-cubes-wasm/Cargo.toml @@ -6,7 +6,7 @@ name = "all-is-cubes-wasm" version = "0.8.0" authors = ["Kevin Reid "] edition = "2021" -rust-version = "1.81" +rust-version = "1.82" description = "Web client for the recursive voxel game All is Cubes." # TODO: add homepage = "..." when we have one repository = "https://github.com/kpreid/all-is-cubes" diff --git a/all-is-cubes/src/behavior.rs b/all-is-cubes/src/behavior.rs index 8056fd16a..9f5e77a67 100644 --- a/all-is-cubes/src/behavior.rs +++ b/all-is-cubes/src/behavior.rs @@ -191,7 +191,7 @@ impl BehaviorSet { } }, ) - .filter(move |qi| type_filter.map_or(true, |t| (*qi.behavior).type_id() == t)) + .filter(move |qi| type_filter.is_none_or(|t| (*qi.behavior).type_id() == t)) } pub(crate) fn step( diff --git a/all-is-cubes/src/block/attributes.rs b/all-is-cubes/src/block/attributes.rs index 69b7c3bcd..f1384927e 100644 --- a/all-is-cubes/src/block/attributes.rs +++ b/all-is-cubes/src/block/attributes.rs @@ -137,6 +137,7 @@ impl BlockAttributes { Self::DEFAULT } + #[mutants::skip] // currently used only as an optimization, and hard to test usefully pub(crate) fn rotationally_symmetric(&self) -> bool { let Self { display_name: _, @@ -150,12 +151,12 @@ impl BlockAttributes { inventory.rotationally_symmetric() && rotation_rule.rotationally_symmetric() - && !tick_action + && tick_action .as_ref() - .is_some_and(|a| !a.rotationally_symmetric()) - && !activation_action + .is_none_or(|a| a.rotationally_symmetric()) + && activation_action .as_ref() - .is_some_and(|a| !a.rotationally_symmetric()) + .is_none_or(|a| a.rotationally_symmetric()) } pub(crate) fn rotate(self, rotation: GridRotation) -> BlockAttributes { diff --git a/all-is-cubes/src/block/eval/evaluated.rs b/all-is-cubes/src/block/eval/evaluated.rs index d5345f0ae..5af6d729c 100644 --- a/all-is-cubes/src/block/eval/evaluated.rs +++ b/all-is-cubes/src/block/eval/evaluated.rs @@ -289,7 +289,7 @@ impl EvaluatedBlock { let inventory = inv::Inventory::from_slots( itertools::Itertools::zip_longest( contents, - core::iter::repeat(inv::Slot::Empty).take(config.size), + core::iter::repeat_n(inv::Slot::Empty, config.size), ) .map(|z| z.into_left()) .collect::>(), diff --git a/all-is-cubes/src/content/palette.rs b/all-is-cubes/src/content/palette.rs index f5dee5acf..98d0d118e 100644 --- a/all-is-cubes/src/content/palette.rs +++ b/all-is-cubes/src/content/palette.rs @@ -14,7 +14,6 @@ // // 0xBB is the sRGB value approximating linear value 0.5. -#![allow(unknown_lints)] // TODO: remove after Rust 1.82 is released #![allow(clippy::too_long_first_doc_paragraph, reason = "false positive on SVG")] use crate::math::{rgb_const, Rgb}; diff --git a/all-is-cubes/src/drawing.rs b/all-is-cubes/src/drawing.rs index aaca36c83..f3c274f27 100644 --- a/all-is-cubes/src/drawing.rs +++ b/all-is-cubes/src/drawing.rs @@ -72,7 +72,6 @@ pub fn rectangle_to_aab(rectangle: Rectangle, transform: Gridgid, max_brush: Gri // way, since it is precisely about identifying the volume occupied by drawing a // 2D-pixel. - #![allow(unknown_lints)] // TODO: remove after Rust 1.82 is released #![allow(clippy::too_long_first_doc_paragraph)] // TODO: find better phrasing if rectangle.size.width == 0 || rectangle.size.height == 0 { diff --git a/all-is-cubes/src/space/light/queue.rs b/all-is-cubes/src/space/light/queue.rs index 940479f82..a54d4b622 100644 --- a/all-is-cubes/src/space/light/queue.rs +++ b/all-is-cubes/src/space/light/queue.rs @@ -211,9 +211,10 @@ impl LightUpdateQueue { /// Removes and returns the highest priority queue entry. #[inline] + #[mutants::skip] // if it fails to pop, causes hangs pub fn pop(&mut self) -> Option { if let Some(sweep) = &mut self.sweep { - if peek_priority(&self.queue).map_or(true, |p| self.sweep_priority > p) { + if peek_priority(&self.queue).is_none_or(|p| self.sweep_priority > p) { if let Some(cube) = sweep.next() { return Some(LightUpdateRequest { cube, diff --git a/all-is-cubes/src/space/light/updater.rs b/all-is-cubes/src/space/light/updater.rs index b96263385..b3829a47f 100644 --- a/all-is-cubes/src/space/light/updater.rs +++ b/all-is-cubes/src/space/light/updater.rs @@ -113,11 +113,12 @@ impl LightStorage { } } + #[mutants::skip] // lots of ways for this to still work when modified pub(crate) fn light_needs_update_in_region(&mut self, region: GridAab, priority: Priority) { let Some(region) = region.intersection_cubes(self.contents.bounds()) else { return; }; - if !region.volume().is_some_and(|v| v <= 400) { + if region.volume().is_none_or(|v| v > 400) { self.light_update_queue.sweep(region, priority); } else { for cube in region.interior_iter() {