diff --git a/CHANGELOG.md b/CHANGELOG.md index 24330091d..e12d7147c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -66,6 +66,8 @@ - `AnyURef` to `AnyHandle` - `VisitRefs` to `VisitHandles` - `RefVisitor` to `HandleVisitor` + + - The macros `rgb_const!`, `rgba_const!`, and `notnan!` have been moved to the `math` module. - `all-is-cubes-mesh` library: - The return type of `GetBlockMesh::get_block_mesh()` has changed to `Option<&BlockMesh>`. diff --git a/Cargo.lock b/Cargo.lock index 6ce04882b..7f35b5231 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -43,6 +43,7 @@ dependencies = [ name = "all-is-cubes" version = "0.7.1" dependencies = [ + "all-is-cubes-base", "allocation-counter", "arbitrary", "arcstr", @@ -70,7 +71,6 @@ dependencies = [ "once_cell", "ordered-float", "png-decoder", - "polonius-the-crab", "pretty_assertions", "rand 0.8.5", "rand_xoshiro", @@ -80,12 +80,38 @@ dependencies = [ "re_types", "serde", "serde_json", - "serde_repr", "tokio", "unicode-segmentation", "yield-progress", ] +[[package]] +name = "all-is-cubes-base" +version = "0.7.1" +dependencies = [ + "arbitrary", + "bytemuck", + "cfg-if", + "displaydoc", + "embedded-graphics", + "euclid", + "exhaust", + "futures-core", + "futures-util", + "indoc", + "itertools 0.12.1", + "manyfmt", + "mutants", + "num-traits", + "ordered-float", + "polonius-the-crab", + "pretty_assertions", + "rand 0.8.5", + "rand_xoshiro", + "re_types", + "serde", +] + [[package]] name = "all-is-cubes-content" version = "0.7.0" diff --git a/Cargo.toml b/Cargo.toml index f0eed33b0..04a6d48d4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,7 @@ [workspace] members = [ "all-is-cubes", + "all-is-cubes-base", "all-is-cubes-content", "all-is-cubes-desktop", "all-is-cubes-gpu", @@ -39,6 +40,7 @@ resolver = "2" [workspace.dependencies] all-is-cubes = { path = "all-is-cubes", version = "0.7.1", default-features = false } +all-is-cubes-base = { path = "all-is-cubes-base", version = "0.7.1", default-features = false } all-is-cubes-content = { path = "all-is-cubes-content", version = "0.7.0", default-features = false } all-is-cubes-desktop = { path = "all-is-cubes-desktop", version = "0.7.0", default-features = false } all-is-cubes-gpu = { path = "all-is-cubes-gpu", version = "0.7.0", default-features = false } @@ -65,6 +67,7 @@ clap = { version = "4.2.4", default-features = false, features = ["cargo", "depr criterion = { version = "0.5.1", features = ["async_tokio", "cargo_bench_support", "html_reports"] } displaydoc = { version = "0.2.4", default-features = false} either = { version = "1.10.0", default-features = false } +embedded-graphics = "0.8.0" exhaust = { version = "0.1.0", default-features = false } flume = { version = "0.11.0", default-features = false, features = ["async"] } futures-channel = { version = "0.3.28", default-features = false, features = ["alloc"] } diff --git a/all-is-cubes-base/Cargo.toml b/all-is-cubes-base/Cargo.toml new file mode 100644 index 000000000..348d23f6a --- /dev/null +++ b/all-is-cubes-base/Cargo.toml @@ -0,0 +1,54 @@ +[package] +name = "all-is-cubes-base" +version = "0.7.1" +authors = ["Kevin Reid "] +edition = "2021" +rust-version = "1.73" +description = "Helper library for all-is-cubes. Do not use directly." +repository = "https://github.com/kpreid/all-is-cubes" +license = "MIT OR Apache-2.0" + +[lib] +# Disable running as benchmark so that the default doesn't interfere with Criterion usage. +bench = false + +[features] +std = [] +# Adds `impl arbitrary::Arbitrary for ...` +# Note: Not using euclid/arbitrary because it's broken +arbitrary = ["dep:arbitrary", "ordered-float/arbitrary"] +rerun = ["dep:re_types"] +serde = [ + "dep:serde", + "ordered-float/serde", +] + +[dependencies] +arbitrary = { workspace = true, optional = true } +bytemuck = { workspace = true, features = ["derive"] } +cfg-if = { workspace = true } +displaydoc = { workspace = true } +embedded-graphics = { workspace = true } +# mint feature to guarantee that our callers can use mint types +# libm feature to guarantee compilability and compatibility with no_std +euclid = { version = "0.22.9", default-features = false, features = ["libm", "mint"] } +exhaust = { workspace = true, default-features = false } +futures-core = { workspace = true } +futures-util = { workspace = true } +manyfmt = { workspace = true } +mutants = { workspace = true } +num-traits = { workspace = true } +ordered-float = { workspace = true } +polonius-the-crab = { workspace = true } +rand = { workspace = true } +re_types = { workspace = true, optional = true } +serde = { workspace = true, optional = true, features = ["derive"] } + +[dev-dependencies] +indoc = { workspace = true } +itertools = { workspace = true } +pretty_assertions = { workspace = true } +rand_xoshiro = { workspace = true } + +[lints] +workspace = true diff --git a/all-is-cubes-base/LICENSE-APACHE b/all-is-cubes-base/LICENSE-APACHE new file mode 120000 index 000000000..965b606f3 --- /dev/null +++ b/all-is-cubes-base/LICENSE-APACHE @@ -0,0 +1 @@ +../LICENSE-APACHE \ No newline at end of file diff --git a/all-is-cubes-base/LICENSE-MIT b/all-is-cubes-base/LICENSE-MIT new file mode 120000 index 000000000..76219eb72 --- /dev/null +++ b/all-is-cubes-base/LICENSE-MIT @@ -0,0 +1 @@ +../LICENSE-MIT \ No newline at end of file diff --git a/all-is-cubes-base/README.md b/all-is-cubes-base/README.md new file mode 100644 index 000000000..69823c18f --- /dev/null +++ b/all-is-cubes-base/README.md @@ -0,0 +1,21 @@ +This library is an internal component of [`all-is-cubes`], +which defines some core mathematical types and functions. +Do not depend on this library; use only [`all-is-cubes`] instead. + +[`all-is-cubes`]: https://crates.io/crates/all-is-cubes/ + +License +------- + +All source code and other materials are Copyright © 2020-2024 Kevin Reid, and licensed under either of + + * Apache License, Version 2.0 + ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0) + * MIT license + ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT) + +at your option. + +Unless you explicitly state otherwise, any contribution intentionally submitted +for inclusion in the work by you, as defined in the Apache-2.0 license, shall be +dual licensed as above, without any additional terms or conditions. diff --git a/all-is-cubes-base/src/lib.rs b/all-is-cubes-base/src/lib.rs new file mode 100644 index 000000000..72c281faf --- /dev/null +++ b/all-is-cubes-base/src/lib.rs @@ -0,0 +1,32 @@ +//! This library is an internal component of [`all-is-cubes`], +//! which defines some core mathematical types and functions. +//! Do not depend on this library; use only [`all-is-cubes`] instead. +//! +//! [`all-is-cubes`]: https://crates.io/crates/all-is-cubes/ + +#![no_std] +// Crate-specific lint settings. (General settings can be found in the workspace manifest.) +#![cfg_attr( + not(any(test, feature = "arbitrary")), + warn(clippy::std_instead_of_core, clippy::std_instead_of_alloc) +)] +#![cfg_attr(not(feature = "std"), allow(clippy::arc_with_non_send_sync))] + +#[cfg(any(feature = "std", test))] +#[cfg_attr(test, macro_use)] +extern crate std; +#[macro_use] +extern crate alloc; + +/// Do not use this module directly; its contents are re-exported from `all-is-cubes`. +#[macro_use] +pub mod math; + +/// Do not use this module directly; its contents are re-exported from `all-is-cubes`. +pub mod resolution; + +/// Do not use this module directly; its contents are re-exported from `all-is-cubes`. +pub mod time; + +/// Do not use this module directly; its contents are re-exported from `all-is-cubes`. +pub mod util; diff --git a/all-is-cubes-base/src/math.rs b/all-is-cubes-base/src/math.rs new file mode 100644 index 000000000..c0a0b2232 --- /dev/null +++ b/all-is-cubes-base/src/math.rs @@ -0,0 +1,176 @@ +//! Mathematical utilities and decisions. + +use euclid::Vector3D; +use num_traits::identities::Zero; +pub use ordered_float::{FloatIsNan, NotNan}; + +/// Acts as polyfill for float methods +#[cfg(not(feature = "std"))] +#[allow(unused_imports)] +use num_traits::float::FloatCore as _; + +use crate::util::ConciseDebug; + +mod aab; +pub use aab::*; +mod axis; +pub use axis::*; +#[macro_use] +mod color; +pub use color::*; +mod coord; +pub use coord::*; +mod cube; +pub use cube::Cube; +mod face; +pub use face::*; +mod grid_aab; +pub use grid_aab::*; +mod grid_iter; +pub use grid_iter::*; +mod rigid; +pub use rigid::*; +mod matrix; +pub use matrix::*; +mod rotation; +pub use rotation::*; +#[cfg(feature = "serde")] +mod serde_impls; +mod vol; +pub use vol::*; + +// We make an assumption in several places that `usize` is at least 32 bits. +// It's likely that compilation would not succeed anyway, but let's make it explicit. +#[cfg(not(any( + target_pointer_width = "32", + target_pointer_width = "64", + target_pointer_width = "128", +)))] +compile_error!("all-is-cubes does not support platforms with less than 32-bit `usize`"); + +/// Allows writing a [`NotNan`] value as a constant expression (which is not currently +/// a feature provided by the [`ordered_float`] crate itself). +/// +/// Note that if the expression does not need to be constant, this macro may not be +/// needed; infallible construction can be written using `NotNan::from(an_integer)`, +/// `NotNan::zero()`, and `NotNan::one()`. +/// +/// ``` +/// # extern crate all_is_cubes_base as all_is_cubes; +/// use all_is_cubes::{notnan, math::NotNan}; +/// +/// const X: NotNan = notnan!(1.234); +/// ``` +/// +/// ```compile_fail +/// # extern crate all_is_cubes_base as all_is_cubes; +/// # use all_is_cubes::{notnan, math::NotNan}; +/// // Not a literal; will not compile +/// const X: NotNan = notnan!(f32::NAN); +/// ``` +/// +/// ```compile_fail +/// # extern crate all_is_cubes_base as all_is_cubes; +/// # use all_is_cubes::{notnan, math::NotNan}; +/// // Not a literal; will not compile +/// const X: NotNan = notnan!(0.0 / 0.0); +/// ``` +/// +/// ```compile_fail +/// # extern crate all_is_cubes_base as all_is_cubes; +/// # use all_is_cubes::{notnan, math::NotNan}; +/// const N0N: f32 = f32::NAN; +/// // Not a literal; will not compile +/// const X: NotNan = notnan!(N0N); +/// ``` +/// +/// ```compile_fail +/// # extern crate all_is_cubes_base as all_is_cubes; +/// # use all_is_cubes::{notnan, math::NotNan}; +/// // Not a float; will not compile +/// const X: NotNan = notnan!('a'); +/// ``` +#[doc(hidden)] +#[macro_export] // used by all-is-cubes-content +macro_rules! notnan { + ($value:literal) => { + match $value { + value => { + // Safety: Only literal values are allowed, which will either be a non-NaN + // float or (as checked below) a type mismatch. + let result = unsafe { $crate::math::NotNan::new_unchecked(value) }; + + // Ensure that the type is one which could have resulted from a float literal, + // by requiring type unification with a literal. This prohibits char, &str, etc. + let _ = if false { + // Safety: Statically never NaN, and is also never executed. + unsafe { $crate::math::NotNan::new_unchecked(0.0) } + } else { + result + }; + + result + } + } + }; +} + +/// Sort exactly two items; swap them if `a > b`. +#[inline] +#[doc(hidden)] +pub fn sort_two(a: &mut T, b: &mut T) { + if *a > *b { + core::mem::swap(a, b); + } +} + +/// Common features of objects that have a location and shape in space. +pub trait Geometry { + /// Type of coordinates; generally determines whether this object can be translated by a + /// non-integer amount. + type Coord; + + /// Translate (move) this object by the specified offset. + #[must_use] + fn translate(self, offset: Vector3D) -> Self; + + /// Represent this object as a line drawing, or wireframe. + /// + /// The generated points should be in pairs, each pair defining a line segment. + /// If there are an odd number of vertices, the caller should ignore the last. + /// + /// TODO: This should probably return an iterator instead, but defining the type + /// will be awkward until `type_alias_impl_trait` is stable. + fn wireframe_points(&self, output: &mut E) + where + E: Extend; +} + +/// One end of a line to be drawn. +/// +/// Mostly used for debugging visualizations and not for game content. +/// +/// The primary way in which these are used is [`Geometry::wireframe_points()`]. +#[derive(Clone, Copy, Debug, PartialEq)] +#[allow(clippy::exhaustive_structs)] +pub struct LineVertex { + /// Position of the vertex. + pub position: FreePoint, + + /// Color in which to draw the line. + /// + /// If [`None`], a color set by the context/parent should be used instead. + /// + /// If the ends of a line are different colors, color should be interpolated along + /// the line. + pub color: Option, +} + +impl From for LineVertex { + fn from(position: FreePoint) -> Self { + Self { + position, + color: None, + } + } +} diff --git a/all-is-cubes/src/math/aab.rs b/all-is-cubes-base/src/math/aab.rs similarity index 97% rename from all-is-cubes/src/math/aab.rs rename to all-is-cubes-base/src/math/aab.rs index 08a386031..d0c2b4715 100644 --- a/all-is-cubes/src/math/aab.rs +++ b/all-is-cubes-base/src/math/aab.rs @@ -19,7 +19,7 @@ use crate::math::{ /// Note that this has continuous coordinates, and a discrete analogue exists as /// [`GridAab`]. /// -#[doc = include_str!("../save/serde-warning.md")] +#[doc = include_str!("../serde-warning.md")] // TODO(euclid migration): Replace this with `euclid` box type? Probably not. #[derive(Copy, Clone, PartialEq)] pub struct Aab { @@ -139,6 +139,7 @@ impl Aab { /// The center of the enclosed volume. /// /// ``` + /// # extern crate all_is_cubes_base as all_is_cubes; /// use all_is_cubes::math::{Aab, FreePoint}; /// /// let aab = Aab::new(1.0, 2.0, 3.0, 4.0, 5.0, 6.0); @@ -150,7 +151,8 @@ impl Aab { /// Iterates over the eight corner points of the box. /// The ordering is deterministic but not currently declared stable. - pub(crate) fn corner_points( + #[doc(hidden)] + pub fn corner_points( self, ) -> impl DoubleEndedIterator + ExactSizeIterator + FusedIterator { let l = self.lower_bounds; @@ -216,6 +218,7 @@ impl Aab { /// at the center point of `self`. /// /// ``` + /// # extern crate all_is_cubes_base as all_is_cubes; /// use all_is_cubes::math::Aab; /// /// assert_eq!( @@ -241,8 +244,8 @@ impl Aab { } #[inline] - // Not public because this is an odd interface that primarily helps with collision. - pub(crate) fn leading_corner(&self, direction: FreeVector) -> FreeVector { + #[doc(hidden)] // Not public because this is an odd interface that primarily helps with collision. + pub fn leading_corner(&self, direction: FreeVector) -> FreeVector { let mut leading_corner = Vector3D::zero(); for axis in Axis::ALL { if direction[axis] >= 0.0 { @@ -261,6 +264,7 @@ impl Aab { /// expect, while non-integer bounds will be rounded outward: /// /// ``` + /// # extern crate all_is_cubes_base as all_is_cubes; /// use all_is_cubes::math::{Aab, GridAab}; /// /// let grid_aab = Aab::from_lower_upper([3.0, 0.5, 0.0], [5.0, 1.5, 1.0]) @@ -275,6 +279,7 @@ impl Aab { /// then they will be clamped. /// /// ``` + /// # extern crate all_is_cubes_base as all_is_cubes; /// # use all_is_cubes::math::{Aab, GridAab}; /// use all_is_cubes::math::{FreeCoordinate, GridCoordinate}; /// diff --git a/all-is-cubes/src/math/axis.rs b/all-is-cubes-base/src/math/axis.rs similarity index 94% rename from all-is-cubes/src/math/axis.rs rename to all-is-cubes-base/src/math/axis.rs index 775e9f659..d2e258e96 100644 --- a/all-is-cubes/src/math/axis.rs +++ b/all-is-cubes-base/src/math/axis.rs @@ -1,6 +1,5 @@ use core::fmt; -use crate::content::palette; use crate::math::{Face6, Rgb}; /// Enumeration of the axes of three-dimensional space. @@ -13,7 +12,7 @@ use crate::math::{Face6, Rgb}; #[allow(clippy::exhaustive_enums)] #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, exhaust::Exhaust)] #[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] -// do after tests:#[cfg_attr(feature = "save", derive(serde::Serialize, serde::Deserialize))] +// do after tests:#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[repr(u8)] #[allow(missing_docs)] pub enum Axis { @@ -35,9 +34,9 @@ impl Axis { #[mutants::skip] pub fn color(&self) -> Rgb { match self { - Axis::X => palette::UNIFORM_LUMINANCE_RED, - Axis::Y => palette::UNIFORM_LUMINANCE_GREEN, - Axis::Z => palette::UNIFORM_LUMINANCE_BLUE, + Axis::X => Rgb::UNIFORM_LUMINANCE_RED, + Axis::Y => Rgb::UNIFORM_LUMINANCE_GREEN, + Axis::Z => Rgb::UNIFORM_LUMINANCE_BLUE, } } diff --git a/all-is-cubes/src/math/color.rs b/all-is-cubes-base/src/math/color.rs similarity index 90% rename from all-is-cubes/src/math/color.rs rename to all-is-cubes-base/src/math/color.rs index 82e1f50fd..6bf1fac7f 100644 --- a/all-is-cubes/src/math/color.rs +++ b/all-is-cubes-base/src/math/color.rs @@ -93,6 +93,17 @@ impl Rgb { /// Note that brighter values may exist; the color system “supports HDR”. pub const ONE: Rgb = Rgb(vec3(NN1, NN1, NN1)); + /// Pure red that is as bright as it can be, + /// while being a sRGB color that is the same luminance as the other colors in this set. + pub const UNIFORM_LUMINANCE_RED: Rgb = Rgb::from_srgb8([0x9E, 0x00, 0x00]); + /// Pure green that is as bright as it can be, + /// while being a sRGB color that is the same luminance as the other colors in this set. + pub const UNIFORM_LUMINANCE_GREEN: Rgb = Rgb::from_srgb8([0x00, 0x59, 0x00]); + /// Pure blue that is as bright as it can be, + /// while being a sRGB color that is the same luminance as the other colors in this set. + /// (That turns out to be 100% blue, `#0000FF`.) + pub const UNIFORM_LUMINANCE_BLUE: Rgb = Rgb::from_srgb8([0x00, 0x00, 0xFF]); + /// Constructs a color from components. Panics if any component is NaN. /// No other range checks are performed. #[inline] @@ -158,6 +169,7 @@ impl Rgb { /// (“grayscale”) value. This will be equal to 1 if all components are 1. /// /// ``` + /// # extern crate all_is_cubes_base as all_is_cubes; /// use all_is_cubes::math::Rgb; /// /// assert_eq!(0.0, Rgb::ZERO.luminance()); @@ -548,6 +560,44 @@ impl<'a> arbitrary::Arbitrary<'a> for Rgba { } } +/// Implementations necessary for `all_is_cubes::drawing` to be able to use these types +mod eg { + use embedded_graphics::pixelcolor::{self, RgbColor as _}; + + use super::*; + impl pixelcolor::PixelColor for Rgb { + type Raw = (); + } + impl pixelcolor::PixelColor for Rgba { + type Raw = (); + } + /// Adapt [`embedded_graphics`]'s most general color type to ours. + impl From for Rgb { + #[inline] + fn from(color: pixelcolor::Rgb888) -> Rgb { + Rgba::from_srgb8([color.r(), color.g(), color.b(), u8::MAX]).to_rgb() + } + } +} + +#[cfg(feature = "rerun")] +mod rerun { + use super::*; + use re_types::datatypes; + + impl From for datatypes::Rgba32 { + fn from(value: Rgb) -> Self { + value.with_alpha_one().into() + } + } + impl From for datatypes::Rgba32 { + fn from(value: Rgba) -> Self { + let [r, g, b, a] = value.to_srgb8(); + datatypes::Rgba32::from_unmultiplied_rgba(r, g, b, a) + } + } +} + #[inline] fn component_to_srgb(c: NotNan) -> f32 { // Source: (version as of Feb 3, 2020) @@ -728,7 +778,8 @@ const CONST_LINEAR_LOOKUP_TABLE: [f32; 256] = [ mod tests { use super::*; use alloc::vec::Vec; - use itertools::Itertools; + use exhaust::Exhaust as _; + use itertools::Itertools as _; // TODO: Add tests of the color not-NaN mechanisms. @@ -834,4 +885,35 @@ mod tests { assert_eq!(CONST_LINEAR_LOOKUP_TABLE.to_vec(), generated_table); } + + #[test] + fn check_uniform_luminance() { + fn optimize(channel: usize) -> [u8; 4] { + // Blue is the primary color whose maximum intensity is darkest; + // therefore it is the standard by which we check the other. + let reference_luminance = Rgb::UNIFORM_LUMINANCE_BLUE.luminance(); + let (_color, srgb, luminance_difference) = u8::exhaust() + .map(|srgb_byte| { + let mut srgb = [0, 0, 0, 255]; + srgb[channel] = srgb_byte; + let color = Rgba::from_srgb8(srgb); + (color, srgb, (color.luminance() - reference_luminance).abs()) + }) + .min_by(|a, b| a.2.total_cmp(&b.2)) + .unwrap(); + println!("best luminance difference = {luminance_difference}"); + srgb + } + + println!("red:"); + assert_eq!( + Rgb::UNIFORM_LUMINANCE_RED.with_alpha_one().to_srgb8(), + optimize(0) + ); + println!("green:"); + assert_eq!( + Rgb::UNIFORM_LUMINANCE_GREEN.with_alpha_one().to_srgb8(), + optimize(1) + ); + } } diff --git a/all-is-cubes/src/math/coord.rs b/all-is-cubes-base/src/math/coord.rs similarity index 100% rename from all-is-cubes/src/math/coord.rs rename to all-is-cubes-base/src/math/coord.rs diff --git a/all-is-cubes/src/math/cube.rs b/all-is-cubes-base/src/math/cube.rs similarity index 98% rename from all-is-cubes/src/math/cube.rs rename to all-is-cubes-base/src/math/cube.rs index 1fb9136ad..d12db3c32 100644 --- a/all-is-cubes/src/math/cube.rs +++ b/all-is-cubes-base/src/math/cube.rs @@ -69,6 +69,7 @@ impl Cube { /// returns [`None`]. /// /// ``` + /// # extern crate all_is_cubes_base as all_is_cubes; /// use all_is_cubes::math::{FreePoint, Cube}; /// /// assert_eq!(Cube::containing(FreePoint::new(1.0, 1.5, -2.5)), Some(Cube::new(1, 1, -3))); @@ -132,6 +133,7 @@ impl Cube { /// Returns the bounding box in floating-point coordinates containing this cube. /// /// ``` + /// # extern crate all_is_cubes_base as all_is_cubes; /// use all_is_cubes::math::{Aab, Cube}; /// /// assert_eq!( @@ -147,9 +149,8 @@ impl Cube { } /// Componentwise [`GridCoordinate::checked_add()`]. - #[inline] #[must_use] - pub(crate) fn checked_add(self, v: GridVector) -> Option { + pub fn checked_add(self, v: GridVector) -> Option { Some(Self { x: self.x.checked_add(v.x)?, y: self.y.checked_add(v.y)?, diff --git a/all-is-cubes/src/math/face.rs b/all-is-cubes-base/src/math/face.rs similarity index 95% rename from all-is-cubes/src/math/face.rs rename to all-is-cubes-base/src/math/face.rs index 3ce382562..68ce97e34 100644 --- a/all-is-cubes/src/math/face.rs +++ b/all-is-cubes-base/src/math/face.rs @@ -7,6 +7,7 @@ use core::ops::{Index, IndexMut}; use euclid::Vector3D; use manyfmt::formats::Unquote; +use manyfmt::Refmt as _; /// Acts as polyfill for float methods #[cfg(not(feature = "std"))] #[allow(unused_imports)] @@ -16,19 +17,18 @@ use crate::math::{ Axis, ConciseDebug, Cube, FreeCoordinate, FreeVector, Geometry, GridCoordinate, GridPoint, GridRotation, GridVector, Gridgid, LineVertex, VectorOps, Zero, }; -use crate::util::Refmt as _; /// Identifies a face of a cube or an orthogonal unit vector. /// /// See also the similar type [`Face7`], which adds a “zero” or “within the cube” /// variant. The two enums use the same discriminant numbering. /// -#[doc = include_str!("../save/serde-warning.md")] +#[doc = include_str!("../serde-warning.md")] #[allow(clippy::upper_case_acronyms)] #[allow(clippy::exhaustive_enums)] #[derive(Clone, Copy, Debug, Hash, Eq, PartialEq, exhaust::Exhaust)] #[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] -#[cfg_attr(feature = "save", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[repr(u8)] pub enum Face6 { /// Negative X; the face whose normal vector is `(-1, 0, 0)`. @@ -51,12 +51,12 @@ pub enum Face6 { /// This is essentially `Option<`[`Face6`]`>`, except with `Face`-specific methods /// provided. The two enums use the same discriminant numbering. /// -#[doc = include_str!("../save/serde-warning.md")] +#[doc = include_str!("../serde-warning.md")] #[allow(clippy::upper_case_acronyms)] #[allow(clippy::exhaustive_enums)] #[derive(Clone, Copy, Debug, Hash, Eq, PartialEq, exhaust::Exhaust)] #[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] -#[cfg_attr(feature = "save", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[repr(u8)] pub enum Face7 { /// The interior volume of a cube, or an undefined direction. Corresponds to the vector `(0, 0, 0)`. @@ -151,6 +151,7 @@ impl Face6 { /// coordinate is positive. /// /// ``` + /// # extern crate all_is_cubes_base as all_is_cubes; /// use all_is_cubes::math::Face6; /// /// assert_eq!(Face6::PX.is_positive(), true); @@ -165,6 +166,7 @@ impl Face6 { /// coordinate is negative. /// /// ``` + /// # extern crate all_is_cubes_base as all_is_cubes; /// use all_is_cubes::math::Face6; /// /// assert_eq!(Face6::PX.is_negative(), false); @@ -218,6 +220,7 @@ impl Face6 { /// implemented by selecting the relevant component. /// /// ``` + /// # extern crate all_is_cubes_base as all_is_cubes; /// use all_is_cubes::math::{Face6, FreeVector}; /// /// let sample_vector = FreeVector::new(1.0, 2.0, 5.0_f64); @@ -329,6 +332,7 @@ impl Face7 { /// coordinate is positive. /// /// ``` + /// # extern crate all_is_cubes_base as all_is_cubes; /// use all_is_cubes::math::Face7; /// /// assert_eq!(Face7::PX.is_positive(), true); @@ -344,6 +348,7 @@ impl Face7 { /// coordinate is negative. /// /// ``` + /// # extern crate all_is_cubes_base as all_is_cubes; /// use all_is_cubes::math::Face7; /// /// assert_eq!(Face7::PX.is_negative(), false); @@ -451,6 +456,7 @@ impl Face7 { /// implemented by selecting the relevant component. /// /// ``` + /// # extern crate all_is_cubes_base as all_is_cubes; /// use all_is_cubes::math::{Face7, FreeVector}; /// /// let sample_vector = FreeVector::new(1.0, 2.0, 5.0_f64); @@ -507,6 +513,7 @@ impl TryFrom for Face6 { /// are rejected. /// /// ``` + /// # extern crate all_is_cubes_base as all_is_cubes; /// use all_is_cubes::math::{Face6, GridVector}; /// /// // A Face6 may be converted from its normal vector. @@ -533,6 +540,7 @@ impl TryFrom for Face7 { /// are rejected. /// /// ``` + /// # extern crate all_is_cubes_base as all_is_cubes; /// use all_is_cubes::math::{Face7, GridVector}; /// /// // A Face7 may be converted from its normal vector. @@ -567,6 +575,39 @@ impl TryFrom for Face7 { #[allow(clippy::exhaustive_structs)] pub struct Faceless; +#[cfg(feature = "rerun")] +impl From for re_types::view_coordinates::SignedAxis3 { + fn from(face: Face6) -> Self { + use re_types::view_coordinates::{Axis3, Sign, SignedAxis3}; + match face { + Face6::NX => SignedAxis3 { + sign: Sign::Negative, + axis: Axis3::X, + }, + Face6::NY => SignedAxis3 { + sign: Sign::Negative, + axis: Axis3::Y, + }, + Face6::NZ => SignedAxis3 { + sign: Sign::Negative, + axis: Axis3::Z, + }, + Face6::PX => SignedAxis3 { + sign: Sign::Positive, + axis: Axis3::X, + }, + Face6::PY => SignedAxis3 { + sign: Sign::Positive, + axis: Axis3::Y, + }, + Face6::PZ => SignedAxis3 { + sign: Sign::Positive, + axis: Axis3::Z, + }, + } + } +} + /// Container for values keyed by [`Face6`]s. Always holds exactly six elements. #[allow(clippy::exhaustive_structs)] #[derive(Clone, Copy, Default, Hash, PartialEq, Eq, exhaust::Exhaust)] @@ -698,6 +739,7 @@ impl FaceMap { /// This may be used for constructing a map with only one interesting entry: /// /// ``` + /// # extern crate all_is_cubes_base as all_is_cubes; /// use all_is_cubes::math::{Face6, FaceMap}; /// /// assert_eq!( diff --git a/all-is-cubes/src/math/grid_aab.rs b/all-is-cubes-base/src/math/grid_aab.rs similarity index 95% rename from all-is-cubes/src/math/grid_aab.rs rename to all-is-cubes-base/src/math/grid_aab.rs index 015f0f726..cc9b08fe9 100644 --- a/all-is-cubes/src/math/grid_aab.rs +++ b/all-is-cubes-base/src/math/grid_aab.rs @@ -7,24 +7,13 @@ use core::ops::Range; use euclid::{Size3D, Vector3D}; -use crate::block::Resolution; use crate::math::{ sort_two, Aab, Axis, Cube, Face6, FaceMap, FreeCoordinate, FreePoint, GridCoordinate, GridIter, GridPoint, GridSize, GridVector, Gridgid, VectorOps as _, Vol, }; +use crate::resolution::Resolution; -/// An axis-aligned box with integer coordinates. -/// [`GridAab`]s are used to specify the coordinate extent of [`Space`](crate::space::Space)s, and -/// regions within them. -/// -/// When we refer to “a cube” in a [`GridAab`], that is a unit cube which is identified by the -/// integer coordinates of its most negative corner, in the fashion of [`Cube`]. -/// -/// A [`GridAab`] may have a zero-size range in any direction, thus making its total volume zero. -/// The different possibilities are not considered equal; thus, points, lines, and planes may be -/// represented, which may be useful for procedural-generation purposes. -/// -#[doc = include_str!("../save/serde-warning.md")] +#[allow(missing_docs)] // documented in its all-is-cubes reexport #[derive(Clone, Copy, Eq, Hash, PartialEq)] pub struct GridAab { lower_bounds: GridPoint, @@ -40,6 +29,10 @@ impl GridAab { /// be constructed, but they're all kind of verbose: /// /// ``` + /// # mod all_is_cubes { + /// # pub mod block { pub use all_is_cubes_base::resolution::Resolution; } + /// # pub use all_is_cubes_base::math; + /// # } /// use all_is_cubes::block::Resolution; /// use all_is_cubes::math::{GridAab, Cube}; /// @@ -186,6 +179,7 @@ impl GridAab { /// (If this fallibility is undesirable, consider using a [`Vol<()>`] instead of [`GridAab.`]) /// /// ``` + /// # extern crate all_is_cubes_base as all_is_cubes; /// use all_is_cubes::math::GridAab; /// /// let a = GridAab::from_lower_size([-10, 3, 7], [100, 200, 300]); @@ -297,6 +291,7 @@ impl GridAab { /// may be at a half-block position. /// /// ``` + /// # extern crate all_is_cubes_base as all_is_cubes; /// use all_is_cubes::math::{FreePoint, GridAab}; /// /// let b = GridAab::from_lower_size([0, 0, -2], [10, 3, 4]); @@ -313,6 +308,7 @@ impl GridAab { /// and may change in later versions. If order matters, use [`Vol::iter_cubes()`] instead. /// /// ``` + /// # extern crate all_is_cubes_base as all_is_cubes; /// use all_is_cubes::math::{GridAab, Cube}; /// /// let b = GridAab::from_lower_size([10, 20, 30], [1, 2, 3]); @@ -335,7 +331,10 @@ impl GridAab { /// Returns whether the box includes the given cube position in its volume. /// /// ``` - /// let b = all_is_cubes::math::GridAab::from_lower_size([4, 4, 4], [6, 6, 6]); + /// # extern crate all_is_cubes_base as all_is_cubes; + /// use all_is_cubes::math::{GridAab, Cube}; + /// + /// let b = GridAab::from_lower_size([4, 4, 4], [6, 6, 6]); /// assert!(!b.contains_cube([3, 5, 5].into())); /// assert!(b.contains_cube([4, 5, 5].into())); /// assert!(b.contains_cube([9, 5, 5].into())); @@ -355,6 +354,7 @@ impl GridAab { /// TODO: Precisely define the behavior on zero volume boxes. /// /// ``` + /// # extern crate all_is_cubes_base as all_is_cubes; /// use all_is_cubes::math::GridAab; /// let b46 = GridAab::from_lower_size([4, 4, 4], [6, 6, 6]); /// assert!(b46.contains_box(b46)); @@ -384,6 +384,7 @@ impl GridAab { /// the box coordinates, call [`GridAab::intersection_box()`] instead. /// /// ``` + /// # extern crate all_is_cubes_base as all_is_cubes; /// use all_is_cubes::math::GridAab; /// /// // Simple example of an intersection. @@ -431,6 +432,7 @@ impl GridAab { /// is overlap, call [`GridAab::intersection_cubes()`] instead for a tighter bound. /// /// ``` + /// # extern crate all_is_cubes_base as all_is_cubes; /// use all_is_cubes::math::GridAab; /// /// // Simple example of an intersection. @@ -474,6 +476,7 @@ impl GridAab { /// If both inputs are empty, then `self` is returned. /// /// ``` + /// # extern crate all_is_cubes_base as all_is_cubes; /// use all_is_cubes::math::GridAab; /// /// let g1 = GridAab::from_lower_size([1, 2, 3], [1, 1, 1]); @@ -503,6 +506,7 @@ impl GridAab { /// If this is not desired, call [`GridAab::union_cubes()`] instead for a tighter bound. /// /// ``` + /// # extern crate all_is_cubes_base as all_is_cubes; /// use all_is_cubes::math::GridAab; /// /// let g1 = GridAab::from_lower_size([1, 2, 3], [1, 1, 1]); @@ -530,7 +534,8 @@ impl GridAab { Self::from_lower_size(lower, upper - lower) } - pub(crate) fn minkowski_sum(self, other: GridAab) -> Result { + #[doc(hidden)] // TODO: good public API? + pub fn minkowski_sum(self, other: GridAab) -> Result { // TODO: needs checked sums Self::checked_from_lower_size( self.lower_bounds() + other.lower_bounds().to_vector(), @@ -541,8 +546,10 @@ impl GridAab { /// Returns a random cube contained by the box, if there are any. /// /// ``` + /// # extern crate all_is_cubes_base as all_is_cubes; /// use all_is_cubes::math::GridAab; /// use rand::SeedableRng; + /// /// let mut rng = &mut rand_xoshiro::Xoshiro256Plus::seed_from_u64(0); /// /// let b = GridAab::from_lower_size([4, 4, 4], [6, 6, 6]); @@ -580,6 +587,7 @@ impl GridAab { /// (unless that is impossible due to numeric overflow). /// /// ``` + /// # extern crate all_is_cubes_base as all_is_cubes; /// use all_is_cubes::math::GridAab; /// /// assert_eq!( @@ -627,7 +635,9 @@ impl GridAab { /// Panics if the divisor is not positive. /// /// ``` - /// # use all_is_cubes::math::GridAab; + /// # extern crate all_is_cubes_base as all_is_cubes; + /// use all_is_cubes::math::GridAab; + /// /// assert_eq!( /// GridAab::from_lower_size([-10, -10, -10], [20, 20, 20]).divide(10), /// GridAab::from_lower_size([-1, -1, -1], [2, 2, 2]), @@ -669,6 +679,7 @@ impl GridAab { /// Panics on numeric overflow. /// /// ``` + /// # extern crate all_is_cubes_base as all_is_cubes; /// # use all_is_cubes::math::GridAab; /// assert_eq!( /// GridAab::from_lower_size([-1, 2, 3], [4, 5, 6]).multiply(10), @@ -688,8 +699,8 @@ impl GridAab { /// instead. /// /// ``` - /// use all_is_cubes::math::GridAab; - /// use all_is_cubes::math::FaceMap; + /// # extern crate all_is_cubes_base as all_is_cubes; + /// use all_is_cubes::math::{GridAab, FaceMap}; /// /// assert_eq!( /// GridAab::from_lower_upper([10, 10, 10], [20, 20, 20]) @@ -725,8 +736,8 @@ impl GridAab { /// For example, it may be used to construct the walls of a room: /// /// ``` - /// use all_is_cubes::math::GridAab; - /// use all_is_cubes::math::Face6; + /// # extern crate all_is_cubes_base as all_is_cubes; + /// use all_is_cubes::math::{GridAab, Face6}; /// /// let interior = GridAab::from_lower_upper([10, 10, 10], [20, 20, 20]); /// let left_wall = interior.abut(Face6::NX, 2)?; @@ -740,8 +751,8 @@ impl GridAab { /// Example of negative thickness: /// /// ``` - /// # use all_is_cubes::math::GridAab; - /// # use all_is_cubes::math::Face6; + /// # extern crate all_is_cubes_base as all_is_cubes; + /// # use all_is_cubes::math::{GridAab, Face6}; /// /// let b = GridAab::from_lower_upper([10, 10, 10], [20, 20, 20]); /// assert_eq!( @@ -838,8 +849,8 @@ impl fmt::Debug for RangeWithLength { #[cfg(test)] mod tests { use super::*; - use crate::block::Resolution::*; use crate::math::{GridRotation, ZMaj}; + use crate::resolution::Resolution::*; use alloc::string::ToString as _; use indoc::indoc; diff --git a/all-is-cubes/src/math/grid_iter.rs b/all-is-cubes-base/src/math/grid_iter.rs similarity index 97% rename from all-is-cubes/src/math/grid_iter.rs rename to all-is-cubes-base/src/math/grid_iter.rs index a840ce6ca..0d7db9151 100644 --- a/all-is-cubes/src/math/grid_iter.rs +++ b/all-is-cubes-base/src/math/grid_iter.rs @@ -32,7 +32,7 @@ impl GridIter { /// Returns the bounds which this iterator iterates over. /// This may be larger than the union of produced cubes, but it will not be smaller. - pub(crate) fn bounds(&self) -> GridAab { + pub fn bounds(&self) -> GridAab { GridAab::from_ranges([ self.x_range.clone(), self.y_range.clone(), @@ -40,8 +40,8 @@ impl GridIter { ]) } - // Returns whether the iterator will produce the given cube. - pub(crate) fn contains_cube(&self, cube: Cube) -> bool { + /// Returns whether the iterator will produce the given cube. + pub fn contains_cube(&self, cube: Cube) -> bool { if !self.bounds().contains_cube(cube) { return false; } diff --git a/all-is-cubes/src/math/matrix.rs b/all-is-cubes-base/src/math/matrix.rs similarity index 97% rename from all-is-cubes/src/math/matrix.rs rename to all-is-cubes-base/src/math/matrix.rs index d9d051876..b0f544480 100644 --- a/all-is-cubes/src/math/matrix.rs +++ b/all-is-cubes-base/src/math/matrix.rs @@ -36,7 +36,8 @@ pub struct GridMatrix { } impl GridMatrix { - pub(crate) const ZERO: Self = Self { + /// The zero matrix, which transforms all points to zero. + pub const ZERO: Self = Self { x: Vector3D::new(0, 0, 0), y: Vector3D::new(0, 0, 0), z: Vector3D::new(0, 0, 0), @@ -107,6 +108,7 @@ impl GridMatrix { /// Skews or scaling cannot be performed using this constructor. /// /// ``` + /// # extern crate all_is_cubes_base as all_is_cubes; /// use all_is_cubes::math::{Face7::*, GridMatrix, GridPoint}; /// /// let transform = GridMatrix::from_origin([10, 10, 10], PX, PZ, NY); @@ -152,6 +154,7 @@ impl GridMatrix { /// that cube. /// /// ``` + /// # extern crate all_is_cubes_base as all_is_cubes; /// use all_is_cubes::math::{Cube, Face7::*, GridMatrix, GridPoint}; /// /// // Translation without rotation has the usual definition. @@ -178,6 +181,7 @@ impl GridMatrix { /// [`Gridgid`]. Returns `None` if the matrix has any scaling or skew. /// /// ``` + /// # extern crate all_is_cubes_base as all_is_cubes; /// use all_is_cubes::math::{Face6::*, Gridgid, GridMatrix, GridRotation, GridVector}; /// /// assert_eq!( @@ -254,6 +258,7 @@ impl GridMatrix { } /// ``` + /// # extern crate all_is_cubes_base as all_is_cubes; /// use all_is_cubes::math::{GridMatrix, GridPoint}; /// /// let transform_1 = GridMatrix::new( diff --git a/all-is-cubes/src/math/rigid.rs b/all-is-cubes-base/src/math/rigid.rs similarity index 99% rename from all-is-cubes/src/math/rigid.rs rename to all-is-cubes-base/src/math/rigid.rs index c34f8aaea..9f2ee7bb9 100644 --- a/all-is-cubes/src/math/rigid.rs +++ b/all-is-cubes-base/src/math/rigid.rs @@ -81,6 +81,7 @@ impl Gridgid { /// the same as a [`GridAab::single_cube`] containing that cube. /// /// ``` + /// # extern crate all_is_cubes_base as all_is_cubes; /// use all_is_cubes::math::{Cube, Gridgid, GridPoint, GridRotation, GridVector}; /// /// // Translation without rotation has the usual definition. diff --git a/all-is-cubes/src/math/rotation.rs b/all-is-cubes-base/src/math/rotation.rs similarity index 97% rename from all-is-cubes/src/math/rotation.rs rename to all-is-cubes-base/src/math/rotation.rs index bce471326..d9dded65a 100644 --- a/all-is-cubes/src/math/rotation.rs +++ b/all-is-cubes-base/src/math/rotation.rs @@ -26,14 +26,14 @@ use crate::math::GridAab; /// rotation about that axis. /// * [`GridMatrix`] is more general, specifying an affine transformation. /// -#[doc = include_str!("../save/serde-warning.md")] +#[doc = include_str!("../serde-warning.md")] #[rustfmt::skip] #[allow(clippy::upper_case_acronyms)] #[allow(clippy::exhaustive_enums)] #[allow(missing_docs)] #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] #[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] -#[cfg_attr(feature = "save", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[repr(u8)] pub enum GridRotation { // TODO: shuffle or explicitly number these to choose a meaningful numbering @@ -85,6 +85,7 @@ impl GridRotation { /// The rotation that is clockwise in our Y-up right-handed coordinate system. /// /// ``` + /// # extern crate all_is_cubes_base as all_is_cubes; /// use all_is_cubes::math::{Face6::*, GridRotation}; /// /// assert_eq!(GridRotation::CLOCKWISE.transform(PX), PZ); @@ -99,6 +100,7 @@ impl GridRotation { /// The rotation that is counterclockwise in our Y-up right-handed coordinate system. /// /// ``` + /// # extern crate all_is_cubes_base as all_is_cubes; /// use all_is_cubes::math::{Face6::*, GridRotation}; /// /// assert_eq!(GridRotation::COUNTERCLOCKWISE.transform(PX), NZ); @@ -293,6 +295,10 @@ impl GridRotation { /// That is, a [`GridAab`] of that volume will be unchanged by rotation: /// /// ``` + /// # mod all_is_cubes { + /// # pub mod block { pub use all_is_cubes_base::resolution::Resolution; } + /// # pub use all_is_cubes_base::math; + /// # } /// use all_is_cubes::block::Resolution; /// use all_is_cubes::math::{GridAab, GridRotation}; /// @@ -306,6 +312,7 @@ impl GridRotation { /// *not* [`GridMatrix::transform_point`] /// (due to the lower-corner format of cube coordinates). /// ``` + /// # extern crate all_is_cubes_base as all_is_cubes; /// use all_is_cubes::math::{GridAab, Cube, GridRotation}; /// /// let rotation = GridRotation::CLOCKWISE.to_positive_octant_transform(4); @@ -402,6 +409,7 @@ impl GridRotation { /// Returns whether this is a reflection. /// /// ``` + /// # extern crate all_is_cubes_base as all_is_cubes; /// use all_is_cubes::math::{GridRotation, Face6::*}; /// /// assert!(!GridRotation::IDENTITY.is_reflection()); @@ -421,6 +429,7 @@ impl GridRotation { /// Returns the inverse of this rotation; the one which undoes this. /// /// ``` + /// # extern crate all_is_cubes_base as all_is_cubes; /// use all_is_cubes::math::GridRotation; /// /// for &rotation in &GridRotation::ALL { @@ -454,6 +463,7 @@ impl GridRotation { /// just before it would produce the identity again. /// /// ``` + /// # extern crate all_is_cubes_base as all_is_cubes; /// use all_is_cubes::math::Face6::*; /// use all_is_cubes::math::GridRotation; /// @@ -513,7 +523,9 @@ impl Mul for GridRotation { /// Multiplication is concatenation: `self * rhs` is equivalent to /// applying `rhs` and then applying `self`. + /// /// ``` + /// # extern crate all_is_cubes_base as all_is_cubes; /// use all_is_cubes::math::{Face6, Face6::*, GridRotation, GridPoint}; /// /// let transform_1 = GridRotation::from_basis([NY, PX, PZ]); diff --git a/all-is-cubes-base/src/math/serde_impls.rs b/all-is-cubes-base/src/math/serde_impls.rs new file mode 100644 index 000000000..95e89b69e --- /dev/null +++ b/all-is-cubes-base/src/math/serde_impls.rs @@ -0,0 +1,85 @@ +use serde::{Deserialize, Deserializer, Serialize, Serializer}; + +use crate::math::{Aab, Cube, GridAab, GridCoordinate}; + +#[derive(Debug, Deserialize, Serialize)] +struct AabSer { + // This one isn't an explicitly versioned enum because I expect we'll not need to change it + lower: [f64; 3], + upper: [f64; 3], +} + +#[derive(Debug, Deserialize, Serialize)] +struct GridAabSer { + // This one isn't an explicitly versioned enum because I expect we'll not need to change it + lower: [GridCoordinate; 3], + upper: [GridCoordinate; 3], +} + +impl Serialize for Cube { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + let &Cube { x, y, z } = self; + + [x, y, z].serialize(serializer) + } +} + +impl<'de> Deserialize<'de> for Cube { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + let [x, y, z] = <[i32; 3]>::deserialize(deserializer)?; + Ok(Cube::new(x, y, z)) + } +} + +impl Serialize for Aab { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + AabSer { + lower: self.lower_bounds_p().into(), + upper: self.upper_bounds_p().into(), + } + .serialize(serializer) + } +} + +impl<'de> Deserialize<'de> for Aab { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + let AabSer { lower, upper } = AabSer::deserialize(deserializer)?; + Aab::checked_from_lower_upper(lower.into(), upper.into()) + .ok_or_else(|| serde::de::Error::custom("invalid AAB")) + } +} + +impl Serialize for GridAab { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + GridAabSer { + lower: self.lower_bounds().into(), + upper: self.upper_bounds().into(), + } + .serialize(serializer) + } +} + +impl<'de> Deserialize<'de> for GridAab { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + let GridAabSer { lower, upper } = GridAabSer::deserialize(deserializer)?; + GridAab::checked_from_lower_upper(lower, upper).map_err(serde::de::Error::custom) + } +} diff --git a/all-is-cubes/src/math/vol.rs b/all-is-cubes-base/src/math/vol.rs similarity index 98% rename from all-is-cubes/src/math/vol.rs rename to all-is-cubes-base/src/math/vol.rs index 5c74bed69..ce280c7e8 100644 --- a/all-is-cubes/src/math/vol.rs +++ b/all-is-cubes-base/src/math/vol.rs @@ -25,8 +25,7 @@ use crate::math::{ #[allow(clippy::exhaustive_structs)] pub struct ZMaj; -/// A 3-dimensional array with arbitrary element type instead of [`Space`](crate::space::Space)'s -/// fixed types. +/// A container of volume data. // --- // TOOD: deprecate/replace this pub type GridArray = Vol, ZMaj>; @@ -276,9 +275,9 @@ impl Vol { self } - // TODO: good public api? // TODO: reconcile this with from_elements() — should only be implemented once. - pub(crate) fn map_container(self, f: F) -> Vol + #[doc(hidden)] // TODO: good public api? + pub fn map_container(self, f: F) -> Vol where F: FnOnce(C) -> C2, C2: Deref, @@ -316,6 +315,7 @@ impl Vol { /// The linearized element order is defined by the `O` type. /// /// ``` + /// # extern crate all_is_cubes_base as all_is_cubes; /// use all_is_cubes::math::{Vol, GridAab}; /// /// let vol = GridAab::from_lower_size([0, 0, 0], [10, 10, 10]).to_vol().unwrap(); @@ -456,7 +456,8 @@ impl<'a, V> Vol<&'a mut [V], ZMaj> { impl Vol, O> { /// Returns the linear contents viewed as a mutable slice, as if by [`Arc::make_mut()`]. - pub(crate) fn make_linear_mut(&mut self) -> &mut [V] { + #[doc(hidden)] // TODO: good public API? + pub fn make_linear_mut(&mut self) -> &mut [V] { let slice: &mut [V] = arc_make_mut_slice(&mut self.contents); debug_assert_eq!(slice.len(), self.bounds.volume().unwrap()); slice @@ -636,7 +637,8 @@ pub(crate) mod vol_arb { impl Vol<(), O> { #[cfg(feature = "arbitrary")] - pub(crate) fn arbitrary_with_max_volume( + #[doc(hidden)] + pub fn arbitrary_with_max_volume( u: &mut arbitrary::Unstructured<'_>, volume: usize, ) -> arbitrary::Result { @@ -711,8 +713,9 @@ pub struct VolLengthError { bounds: GridAab, } -#[cfg(feature = "std")] -impl std::error::Error for VolLengthError {} +crate::util::cfg_should_impl_error! { + impl std::error::Error for VolLengthError {} +} impl fmt::Display for VolLengthError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { diff --git a/all-is-cubes/src/block/resolution.rs b/all-is-cubes-base/src/resolution.rs similarity index 83% rename from all-is-cubes/src/block/resolution.rs rename to all-is-cubes-base/src/resolution.rs index 7cb84e831..cda1d3c64 100644 --- a/all-is-cubes/src/block/resolution.rs +++ b/all-is-cubes-base/src/resolution.rs @@ -1,28 +1,6 @@ use core::ops; -#[cfg(doc)] -use crate::block::{EvaluatedBlock, Modifier, Primitive}; - -/// Scale factor between a [recursive block](Primitive::Recur) and its component voxels. -/// -/// This resolution cubed is the number of voxels making up a block. -/// -/// Resolutions are always powers of 2. This ensures that the arithmetic is well-behaved -/// (no division by zero, exact floating-point representation, and the potential of -/// fixed-point representation), -/// and that it is always possible to subdivide a block further (up to the limit) without -/// shifting the existing voxel boundaries. -/// -/// Note that while quite high resolutions are permitted, this does not mean that it is -/// practical to routinely use full blocks at that resolution. For example, 64 × 64 × 64 -/// = 262,144 voxels, occupying several megabytes just for color data. -/// High resolutions are permitted for special purposes that do not necessarily use the -/// full cube volume: -/// -/// * *Thin* blocks (e.g. 128 × 128 × 1) can display high resolution text and other 2D -/// images. -/// * Multi-block structures can be defined using [`Modifier::Zoom`]; their total size -/// is limited by the resolution limit. +// Note: Public documentation for this is in its re-export from `all_is_cubes::block`. #[derive(Clone, Copy, Eq, Hash, Ord, PartialEq, PartialOrd, exhaust::Exhaust)] #[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] #[allow(missing_docs)] @@ -129,6 +107,7 @@ impl_try_from!(usize); impl From for i32 { /// ``` + /// # mod all_is_cubes { pub mod block { pub use all_is_cubes_base::resolution::Resolution; } } /// use all_is_cubes::block::Resolution; /// /// assert_eq!(64, i32::from(Resolution::R64)); @@ -187,14 +166,14 @@ impl ops::Div for Resolution { } } -#[cfg(feature = "save")] +#[cfg(feature = "serde")] impl serde::Serialize for Resolution { fn serialize(&self, serializer: S) -> Result { u16::from(*self).serialize(serializer) } } -#[cfg(feature = "save")] +#[cfg(feature = "serde")] impl<'de> serde::Deserialize<'de> for Resolution { fn deserialize>(deserializer: D) -> Result { u16::deserialize(deserializer)? @@ -208,8 +187,9 @@ impl<'de> serde::Deserialize<'de> for Resolution { #[derive(Debug, Clone, Copy, Eq, Hash, PartialEq)] pub struct IntoResolutionError(N); -#[cfg(feature = "std")] -impl std::error::Error for IntoResolutionError {} +crate::util::cfg_should_impl_error! { + impl std::error::Error for IntoResolutionError {} +} impl fmt::Display for IntoResolutionError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { diff --git a/all-is-cubes-base/src/serde-warning.md b/all-is-cubes-base/src/serde-warning.md new file mode 100644 index 000000000..21654e777 --- /dev/null +++ b/all-is-cubes-base/src/serde-warning.md @@ -0,0 +1,10 @@ +# Serialization stability warning + +This type implements [`serde::Serialize`] and [`serde::Deserialize`], but serialization +support is still experimental (as is the game data model in general). We do not guarantee that future versions of `all-is-cubes` +will be able to deserialize data which is serialized by this version. + +Additionally, the serialization schema is designed with `serde_json` in mind. It is not +guaranteed that using a different data format crate, which may use a different subset of +the information exposed via [`serde::Serialize`], will produce stable results. + diff --git a/all-is-cubes/src/time/deadline.rs b/all-is-cubes-base/src/time.rs similarity index 100% rename from all-is-cubes/src/time/deadline.rs rename to all-is-cubes-base/src/time.rs diff --git a/all-is-cubes-base/src/util.rs b/all-is-cubes-base/src/util.rs new file mode 100644 index 000000000..0f7691cce --- /dev/null +++ b/all-is-cubes-base/src/util.rs @@ -0,0 +1,339 @@ +//! Tools that we could imagine being in the Rust standard library, but aren't. + +use core::fmt; +use core::marker::PhantomData; +use core::ops::AddAssign; +use core::time::Duration; + +// Note that this is not the `maybe_sync::BoxFuture`! +use futures_core::future::BoxFuture as SyncBoxFuture; +use manyfmt::Refmt as _; + +mod custom_format; +pub use custom_format::*; + +/// Interface to start concurrent tasks. +/// +/// In the typical case, applications making use of All is Cubes libraries provide an implementation +/// of this trait to functions which can make use of it. +/// +/// Executors should generally implement `Clone`. +pub trait Executor: fmt::Debug + Send + Sync { + /// Create a set of tasks which runds the provided `future`, if possible. + /// + /// The given `task_factory` is called some number of times appropriate to the available + /// parallelism. If only single-threaded asynchronous execution is supported, it will be called + /// once. It may be called zero times; callers must be able to complete their work without the + /// assistance of these tasks. + /// + /// The future **must periodically yield** by calling [`Executor::yield_now()`]. + /// Otherwise, it may prevent other tasks, even “foreground” ones, from progressing. + /// This requirement is for the benefit of single-threaded [`Executor`]s. + fn spawn_background(&self, task_factory: &mut dyn FnMut() -> SyncBoxFuture<'static, ()>); + + /// Grants an opportunity for other tasks to execute instead of the current one. + /// + /// This should only be performed from inside of a [`Executor::spawn_background()`] task. + /// If it is called (or polled) under other circumstances, it may panic or have negative + /// effects on task scheduling. + //--- + // If Rust ever gets object-safe async fn in trait without boxing, use it here. + fn yield_now(&self) -> SyncBoxFuture<'static, ()>; +} +impl Executor for &T { + fn spawn_background(&self, task_factory: &mut dyn FnMut() -> SyncBoxFuture<'static, ()>) { + (**self).spawn_background(task_factory) + } + fn yield_now(&self) -> futures_util::future::BoxFuture<'static, ()> { + (**self).yield_now() + } +} +impl Executor for alloc::sync::Arc { + fn spawn_background(&self, task_factory: &mut dyn FnMut() -> SyncBoxFuture<'static, ()>) { + (**self).spawn_background(task_factory) + } + fn yield_now(&self) -> futures_util::future::BoxFuture<'static, ()> { + (**self).yield_now() + } +} +/// No-op executor for applications which cannot provide one. +impl Executor for () { + fn spawn_background(&self, _: &mut dyn FnMut() -> SyncBoxFuture<'static, ()>) {} + fn yield_now(&self) -> futures_util::future::BoxFuture<'static, ()> { + unreachable!( + "yield_now() should only be called from a task, \ + and this executor does not support tasks" + ) + } +} + +#[cfg(feature = "std")] +#[doc(hidden)] +pub use error_chain::ErrorChain; +#[cfg(feature = "std")] +mod error_chain { + use core::fmt; + use std::error::Error; + + /// Formatting wrapper which prints an [`Error`] together with its + /// `source()` chain, with at least one newline between each. + /// + /// The text begins with the [`fmt::Display`] format of the error. + /// + /// Design note: This is not a [`manyfmt::Fmt`] because that has a blanket implementation + /// which interferes with this one for [`Error`]. + #[doc(hidden)] // not something we wish to be stable public API + #[derive(Clone, Copy, Debug)] + #[allow(clippy::exhaustive_structs)] + pub struct ErrorChain<'a>(pub &'a (dyn Error + 'a)); + + impl fmt::Display for ErrorChain<'_> { + fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { + format_error_chain(fmt, self.0) + } + } + fn format_error_chain( + fmt: &mut fmt::Formatter<'_>, + mut error: &(dyn Error + '_), + ) -> fmt::Result { + // Write the error's own message. This is expected NOT to contain the sources itself. + write!(fmt, "{error}")?; + + while let Some(source) = error.source() { + error = source; + write!(fmt, "\n\nCaused by:\n {error}")?; + } + + Ok(()) + } +} + +cfg_if::cfg_if! { + if #[cfg(feature = "std")] { + + /// Alias for [`std::error::Error`] that is a substitute when not on `std`. + /// Used to conditionally disable `Error` trait bounds. + #[doc(hidden)] + pub use std::error::Error as ErrorIfStd; + + /// Macro that causes conditional compilation on *this* crate's `std` feature, + /// which should be used around `impl std::error::Error`s. + /// + /// This macro can be gotten rid of once `core::error::Error` is stable. + #[macro_export] + #[doc(hidden)] + macro_rules! cfg_should_impl_error { + ($($body:tt)*) => { + $($body)* + } + } + + } else { + use alloc::boxed::Box; + + /// Substitute for [`std::error::Error`] with the same supertraits but no methods. + #[doc(hidden)] + pub trait ErrorIfStd: fmt::Debug + fmt::Display {} + impl ErrorIfStd for T where T: fmt::Debug + fmt::Display {} + + impl From<&str> for Box { + fn from(s: &str) -> Self { + Box::new(alloc::string::String::from(s)) + } + } + + /// Macro that causes conditional compilation on *this* crate's `std` feature, + /// which should be used around `impl std::error::Error`s. + /// + /// This macro can be gotten rid of once `core::error::Error` is stable. + #[macro_export] + #[doc(hidden)] + macro_rules! cfg_should_impl_error { + ($($body:tt)*) => { + // ignored + } + } + + } +} +pub(crate) use cfg_should_impl_error; + +/// Equivalent of [`Iterator::map`] but applied to an [`Extend`] instead, transforming +/// the incoming elements. +/// +/// TODO: this is only used by the wireframe debug mesh mechanism and should be reconsidered +#[doc(hidden)] // pub to be used by all-is-cubes-gpu +#[derive(Debug)] +pub struct MapExtend<'a, A, B, T, F> +where + T: Extend, + F: Fn(A) -> B, +{ + target: &'a mut T, + function: F, + _input: PhantomData, +} + +impl<'a, A, B, T, F> MapExtend<'a, A, B, T, F> +where + T: Extend, + F: Fn(A) -> B, +{ + pub fn new(target: &'a mut T, function: F) -> Self { + Self { + target, + function, + _input: PhantomData, + } + } +} + +impl<'a, A, B, T, F> Extend for MapExtend<'a, A, B, T, F> +where + T: Extend, + F: Fn(A) -> B, +{ + fn extend(&mut self, iter: I) + where + I: IntoIterator, + { + self.target.extend(iter.into_iter().map(&self.function)); + } +} + +/// Aggregation of the time taken by a set of events. +/// +/// TODO: Consider including an identifier for the longest. +/// TODO: Consider generalizing this to quantities other than time? Probably not. +#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)] +#[non_exhaustive] +pub struct TimeStats { + /// The number of events aggregated into this [`TimeStats`]. + pub count: usize, + /// The sum of the durations of all events. + pub sum: Duration, + /// The minimum duration of all events, or [`None`] if there were no events. + pub min: Option, + /// The maximum duration of all events, or [`Duration::ZERO`] if there were no events. + pub max: Duration, +} + +impl TimeStats { + /// Constructs a [`TimeStats`] for a single event. + /// + /// Multiple of these may then be aggregated using the `+=` operator. + pub const fn one(duration: Duration) -> Self { + Self { + count: 1, + sum: duration, + min: Some(duration), + max: duration, + } + } + + /// Record an event based on the given previous time and current time, then update + /// the previous time value. + /// + /// Returns the duration that was recorded. + #[doc(hidden)] // for now, not making writing conveniences public + pub fn record_consecutive_interval( + &mut self, + last_marked_instant: &mut I, + now: I, + ) -> Duration { + let previous = *last_marked_instant; + *last_marked_instant = now; + + let duration = now.saturating_duration_since(previous); + *self += Self::one(duration); + duration + } +} + +impl AddAssign for TimeStats { + fn add_assign(&mut self, rhs: Self) { + *self = TimeStats { + count: self.count + rhs.count, + sum: self.sum + rhs.sum, + min: self.min.map_or(rhs.min, |value| Some(value.min(rhs.min?))), + max: self.max.max(rhs.max), + }; + } +} + +impl fmt::Display for TimeStats { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self.min { + None => write!( + f, + "(-------- .. {}) for {:3}, total {}", + self.max.refmt(&ConciseDebug), + self.count, + self.sum.refmt(&ConciseDebug), + ), + Some(min) => write!( + f, + "({} .. {}) for {:3}, total {}", + min.refmt(&ConciseDebug), + self.max.refmt(&ConciseDebug), + self.count, + self.sum.refmt(&ConciseDebug), + ), + } + } +} + +#[doc(hidden)] // for use in internal tests only +pub fn assert_send_sync() { + // We don't need to do anything in this function; the call to it having been successfully + // compiled is the assertion. +} + +// Assert `Send + Sync` only if the `std` feature is active. +#[cfg(feature = "std")] +#[doc(hidden)] // for use in internal tests only +pub fn assert_conditional_send_sync() {} +#[cfg(not(feature = "std"))] +#[doc(hidden)] // for use in internal tests only +pub fn assert_conditional_send_sync() {} + +#[cfg(test)] +mod tests { + use super::*; + + fn _assert_executor_trait_is_object_safe(_: &dyn Executor) {} + + #[test] + #[cfg(feature = "std")] + fn error_chain() { + use std::error::Error; + use std::fmt; + + #[derive(Debug)] + struct TestError1; + impl Error for TestError1 {} + impl fmt::Display for TestError1 { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "TestError1") + } + } + + #[derive(Debug)] + struct TestError2(TestError1); + impl Error for TestError2 { + fn source(&self) -> Option<&(dyn Error + 'static)> { + Some(&self.0) + } + } + impl fmt::Display for TestError2 { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "TestError2") + } + } + + assert_eq!( + format!("{}", ErrorChain(&TestError2(TestError1))), + "TestError2\n\nCaused by:\n TestError1" + ); + } +} diff --git a/all-is-cubes/src/util/custom_format.rs b/all-is-cubes-base/src/util/custom_format.rs similarity index 56% rename from all-is-cubes/src/util/custom_format.rs rename to all-is-cubes-base/src/util/custom_format.rs index e0a8674a0..1a701a8f4 100644 --- a/all-is-cubes/src/util/custom_format.rs +++ b/all-is-cubes-base/src/util/custom_format.rs @@ -7,7 +7,9 @@ use manyfmt::{Fmt, Refmt as _}; /// Format type for [`manyfmt::Fmt`] which prints the name of a type. /// The value is a `PhantomData` to avoid requiring an actual instance of the type. #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] -pub(crate) struct TypeName; +#[doc(hidden)] // too specific to be good public API ... arguably should be part of refmt itself. +#[allow(clippy::exhaustive_structs)] +pub struct TypeName; impl Fmt for PhantomData { fn fmt(&self, fmt: &mut fmt::Formatter<'_>, _: &TypeName) -> fmt::Result { write!(fmt, "{}", core::any::type_name::()) @@ -73,70 +75,3 @@ impl Fmt for Duration { write!(fmt, "{:5.2?} ms", (self.as_micros() as f32) / 1000.0) } } -impl Fmt for Duration { - fn fmt(&self, fmt: &mut fmt::Formatter<'_>, _: &StatusText) -> fmt::Result { - >::fmt(self, fmt, &ConciseDebug) - } -} - -/// Format type for [`manyfmt::Fmt`] which provides an highly condensed, ideally constant-width -/// or constant-height, user-facing format for live-updating textual status messages. -/// This format does not follow Rust [`fmt::Debug`] syntax, and when implemented -/// for standard Rust types may have quirks. Values may have multiple lines. -#[allow(clippy::exhaustive_structs)] -#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq)] -pub struct StatusText { - /// Types of information to include or exclude. - pub show: ShowStatus, -} - -impl StatusText { - #[allow(missing_docs)] - pub const ALL: Self = Self { - show: ShowStatus::all(), - }; -} - -bitflags::bitflags! { - /// Different kinds of information which [`StatusText`] may include or exclude. - /// - /// I apologize for this being so specific to All is Cubes internals. - #[derive(Clone, Copy, Debug, Hash, Eq, Ord, PartialEq, PartialOrd)] - // TODO: deserialization should be lenient and ignore unknown textual flags - #[cfg_attr(feature = "save", derive(serde::Serialize, serde::Deserialize))] - pub struct ShowStatus: u32 { - /// The “game world” universe (not the UI). - const WORLD = 1 << 0; - /// The UI universe (not the game world). - const UI = 1 << 1; - - /// Simulation; advancing time. - const STEP = 1 << 2; - /// Drawing the current state. - const RENDER = 1 << 3; - /// Information about what the cursor is targeting. - const CURSOR = 1 << 4; - - /// Things related to the processing of blocks. - const BLOCK = 1 << 5; - /// Things related to character control and physics. - const CHARACTER = 1 << 6; - /// Things related to [`Space`](crate::space::Space)s. - const SPACE = 1 << 7; - } -} - -impl ShowStatus { - pub(crate) const DEFAULT: Self = Self::WORLD - .union(Self::STEP) - .union(Self::RENDER) - .union(Self::CURSOR); -} - -impl Default for ShowStatus { - /// A partial set of flags which makes a reasonable default for introducing users to the - /// status text. - fn default() -> Self { - Self::DEFAULT - } -} diff --git a/all-is-cubes-content/src/animation.rs b/all-is-cubes-content/src/animation.rs index 5baf17841..cc42b8670 100644 --- a/all-is-cubes-content/src/animation.rs +++ b/all-is-cubes-content/src/animation.rs @@ -9,13 +9,13 @@ use rand_xoshiro::Xoshiro256Plus; use all_is_cubes::behavior; use all_is_cubes::block::{Block, BlockCollision, AIR}; +use all_is_cubes::color_block; use all_is_cubes::content::palette; -use all_is_cubes::math::{Cube, GridAab, GridArray, GridPoint, GridVector, Rgba}; +use all_is_cubes::math::{rgba_const, Cube, GridAab, GridArray, GridPoint, GridVector, Rgba}; use all_is_cubes::space::{CubeTransaction, Space, SpaceTransaction}; use all_is_cubes::time::Tick; use all_is_cubes::transaction::Merge; use all_is_cubes::universe::{HandleVisitor, UniverseTransaction, VisitHandles}; -use all_is_cubes::{color_block, rgba_const}; #[cfg(doc)] use all_is_cubes::time::TickSchedule; diff --git a/all-is-cubes-content/src/atrium.rs b/all-is-cubes-content/src/atrium.rs index 334547b0b..bac013235 100644 --- a/all-is-cubes-content/src/atrium.rs +++ b/all-is-cubes-content/src/atrium.rs @@ -8,18 +8,18 @@ use exhaust::Exhaust; use all_is_cubes::block::{self, Block, Resolution, RotationPlacementRule, Zoom, AIR}; use all_is_cubes::character::Spawn; +use all_is_cubes::color_block; use all_is_cubes::content::{free_editing_starter_inventory, palette}; use all_is_cubes::euclid::Point3D; use all_is_cubes::linking::{BlockModule, BlockProvider, InGenError}; use all_is_cubes::math::{ - Axis, Cube, Face6, FaceMap, FreeCoordinate, GridAab, GridArray, GridCoordinate, GridPoint, - GridRotation, GridVector, Gridgid, Rgb, Rgba, VectorOps, + rgb_const, Axis, Cube, Face6, FaceMap, FreeCoordinate, GridAab, GridArray, GridCoordinate, + GridPoint, GridRotation, GridVector, Gridgid, Rgb, Rgba, VectorOps, }; use all_is_cubes::space::{SetCubeError, Space, SpacePhysics, SpaceTransaction}; use all_is_cubes::transaction::{self, Transaction as _}; use all_is_cubes::universe::{Universe, UniverseTransaction}; use all_is_cubes::util::YieldProgress; -use all_is_cubes::{color_block, rgb_const}; use crate::alg::{array_of_noise, four_walls, scale_color}; use crate::Fire; @@ -461,9 +461,9 @@ impl fmt::Display for BannerColor { impl BannerColor { fn color(self) -> Rgb { match self { - BannerColor::Red => palette::UNIFORM_LUMINANCE_RED, - BannerColor::Green => palette::UNIFORM_LUMINANCE_GREEN, - BannerColor::Blue => palette::UNIFORM_LUMINANCE_BLUE, + BannerColor::Red => Rgb::UNIFORM_LUMINANCE_RED, + BannerColor::Green => Rgb::UNIFORM_LUMINANCE_GREEN, + BannerColor::Blue => Rgb::UNIFORM_LUMINANCE_BLUE, } } } diff --git a/all-is-cubes-content/src/blocks.rs b/all-is-cubes-content/src/blocks.rs index f16212e06..5e9507597 100644 --- a/all-is-cubes-content/src/blocks.rs +++ b/all-is-cubes-content/src/blocks.rs @@ -11,6 +11,7 @@ use all_is_cubes::block::{ self, AnimationHint, Atom, Block, BlockCollision, BlockDefTransaction, Primitive, Resolution::*, RotationPlacementRule, TickAction, AIR, }; +use all_is_cubes::color_block; use all_is_cubes::drawing::embedded_graphics::{ prelude::Point, primitives::{Line, PrimitiveStyle, Rectangle, StyledDrawable}, @@ -19,15 +20,14 @@ use all_is_cubes::drawing::VoxelBrush; use all_is_cubes::euclid::Vector3D; use all_is_cubes::linking::{BlockModule, BlockProvider, GenError, InGenError}; use all_is_cubes::math::{ - Cube, Face6, FreeCoordinate, GridAab, GridCoordinate, GridRotation, GridVector, Gridgid, Rgb, - Rgba, VectorOps, + rgb_const, rgba_const, Cube, Face6, FreeCoordinate, GridAab, GridCoordinate, GridRotation, + GridVector, Gridgid, Rgb, Rgba, VectorOps, }; use all_is_cubes::op::Operation; use all_is_cubes::space::{Space, SpacePhysics, SpaceTransaction}; use all_is_cubes::transaction::{self, Transaction as _}; use all_is_cubes::universe::UniverseTransaction; use all_is_cubes::util::YieldProgress; -use all_is_cubes::{color_block, rgb_const, rgba_const}; use crate::alg::{gradient_lookup, scale_color, square_radius, NoiseFnExt as _}; use crate::landscape::install_landscape_blocks; diff --git a/all-is-cubes-content/src/city/exhibits.rs b/all-is-cubes-content/src/city/exhibits.rs index 2806206c7..2db14e2b7 100644 --- a/all-is-cubes-content/src/city/exhibits.rs +++ b/all-is-cubes-content/src/city/exhibits.rs @@ -28,12 +28,12 @@ use all_is_cubes::euclid::{size3, vec3, Point3D, Rotation2D, Vector2D, Vector3D} use all_is_cubes::linking::{BlockProvider, InGenError}; use all_is_cubes::listen::ListenableSource; use all_is_cubes::math::{ - Cube, Face6, FaceMap, FreeCoordinate, GridAab, GridCoordinate, GridPoint, GridRotation, - GridVector, Gridgid, NotNan, Rgb, Rgba, VectorOps, + rgb_const, rgba_const, Cube, Face6, FaceMap, FreeCoordinate, GridAab, GridCoordinate, + GridPoint, GridRotation, GridVector, Gridgid, NotNan, Rgb, Rgba, VectorOps, }; use all_is_cubes::space::{SetCubeError, Space, SpaceBuilder, SpacePhysics, SpaceTransaction}; use all_is_cubes::transaction::{self, Transaction as _}; -use all_is_cubes::{color_block, include_image, rgb_const, rgba_const}; +use all_is_cubes::{color_block, include_image}; use crate::alg::{four_walls, voronoi_pattern}; use crate::city::exhibit::{exhibit, Context, Exhibit, ExhibitTransaction, Placement}; diff --git a/all-is-cubes-content/src/fractal.rs b/all-is-cubes-content/src/fractal.rs index 7ce889075..dcd2369d1 100644 --- a/all-is-cubes-content/src/fractal.rs +++ b/all-is-cubes-content/src/fractal.rs @@ -4,8 +4,9 @@ use all_is_cubes::content::free_editing_starter_inventory; use all_is_cubes::euclid::Point3D; use all_is_cubes::inv::Tool; use all_is_cubes::linking::{BlockProvider, InGenError}; -use all_is_cubes::math::{Cube, GridAab, GridCoordinate, GridPoint, GridVector, VectorOps}; -use all_is_cubes::rgba_const; +use all_is_cubes::math::{ + rgba_const, Cube, GridAab, GridCoordinate, GridPoint, GridVector, VectorOps, +}; use all_is_cubes::space::Space; use all_is_cubes::universe::Universe; diff --git a/all-is-cubes-content/src/landscape.rs b/all-is-cubes-content/src/landscape.rs index dce9e6a2c..7e6df1f7d 100644 --- a/all-is-cubes-content/src/landscape.rs +++ b/all-is-cubes-content/src/landscape.rs @@ -12,8 +12,7 @@ use all_is_cubes::block::{ AIR, }; use all_is_cubes::linking::{BlockModule, BlockProvider, DefaultProvision, GenError, InGenError}; -use all_is_cubes::math::{Cube, FreeCoordinate, GridAab, GridCoordinate, GridVector, Rgb}; -use all_is_cubes::notnan; +use all_is_cubes::math::{notnan, Cube, FreeCoordinate, GridAab, GridCoordinate, GridVector, Rgb}; use all_is_cubes::space::Sky; use all_is_cubes::space::{SetCubeError, Space}; use all_is_cubes::universe::UniverseTransaction; diff --git a/all-is-cubes-content/src/template.rs b/all-is-cubes-content/src/template.rs index de9e749f9..a23696ba3 100644 --- a/all-is-cubes-content/src/template.rs +++ b/all-is-cubes-content/src/template.rs @@ -513,7 +513,7 @@ mod tests { if template != UniverseTemplate::Blank { let _ = u.get_default_character().unwrap().read().unwrap(); } - u.step(false, time::DeadlineStd::Asap); + u.step(false, time::DeadlineNt::Asap); } } } diff --git a/all-is-cubes-gpu/src/in_wgpu.rs b/all-is-cubes-gpu/src/in_wgpu.rs index 512803392..d11d1d04c 100644 --- a/all-is-cubes-gpu/src/in_wgpu.rs +++ b/all-is-cubes-gpu/src/in_wgpu.rs @@ -15,8 +15,8 @@ use all_is_cubes::content::palette; use all_is_cubes::drawing::embedded_graphics::{pixelcolor::Gray8, Drawable}; use all_is_cubes::euclid::Size2D; use all_is_cubes::listen::DirtyFlag; -use all_is_cubes::math::VectorOps; -use all_is_cubes::notnan; +use all_is_cubes::math::{notnan, VectorOps}; + #[cfg(feature = "rerun")] use all_is_cubes::rerun_glue as rg; use all_is_cubes::time; diff --git a/all-is-cubes-gpu/src/in_wgpu/shader_testing.rs b/all-is-cubes-gpu/src/in_wgpu/shader_testing.rs index 06804dcb2..372d76d17 100644 --- a/all-is-cubes-gpu/src/in_wgpu/shader_testing.rs +++ b/all-is-cubes-gpu/src/in_wgpu/shader_testing.rs @@ -15,8 +15,8 @@ use wgpu::util::DeviceExt as _; use all_is_cubes::camera::{Camera, GraphicsOptions, ViewTransform, Viewport}; use all_is_cubes::euclid::{point3, Rotation3D}; use all_is_cubes::listen::ListenableSource; -use all_is_cubes::math::{Face6, FreeVector, GridSize, GridVector, Rgba}; -use all_is_cubes::{notnan, time}; +use all_is_cubes::math::{notnan, Face6, FreeVector, GridSize, GridVector, Rgba}; +use all_is_cubes::time; use all_is_cubes_mesh::{BlockVertex, Coloring}; use crate::in_wgpu::shaders::Shaders; diff --git a/all-is-cubes-gpu/src/in_wgpu/space.rs b/all-is-cubes-gpu/src/in_wgpu/space.rs index 9ff64c6aa..da00b3e89 100644 --- a/all-is-cubes-gpu/src/in_wgpu/space.rs +++ b/all-is-cubes-gpu/src/in_wgpu/space.rs @@ -13,16 +13,16 @@ use all_is_cubes::content::palette; use all_is_cubes::euclid::num::Zero as _; use all_is_cubes::listen::{Listen as _, Listener}; use all_is_cubes::math::{ - Cube, Face6, FreeCoordinate, FreePoint, Geometry as _, GridAab, GridCoordinate, GridPoint, - GridSize, GridVector, NotNan, Rgb, VectorOps, + rgba_const, Cube, Face6, FreeCoordinate, FreePoint, Geometry as _, GridAab, GridCoordinate, + GridPoint, GridSize, GridVector, NotNan, Rgb, VectorOps, }; use all_is_cubes::raycast::Ray; #[cfg(feature = "rerun")] use all_is_cubes::rerun_glue as rg; use all_is_cubes::space::{Sky, Space, SpaceChange, SpaceFluff}; +use all_is_cubes::time; use all_is_cubes::universe::{Handle, HandleError}; use all_is_cubes::util::Executor; -use all_is_cubes::{rgba_const, time}; use all_is_cubes_mesh::dynamic::{self, ChunkedSpaceMesh, RenderDataUpdate}; use all_is_cubes_mesh::{DepthOrdering, IndexSlice}; diff --git a/all-is-cubes-mesh/src/block_mesh/analyze.rs b/all-is-cubes-mesh/src/block_mesh/analyze.rs index 9f133a1bb..b05c59f6d 100644 --- a/all-is-cubes-mesh/src/block_mesh/analyze.rs +++ b/all-is-cubes-mesh/src/block_mesh/analyze.rs @@ -278,8 +278,8 @@ fn shift_nz(bits: u8) -> u8 { mod tests { use super::*; use all_is_cubes::euclid::{point2, size2}; - use all_is_cubes::math::{GridAab, ZMaj}; - use all_is_cubes::rgba_const; + use all_is_cubes::math::{rgba_const, GridAab, ZMaj}; + use all_is_cubes::universe::Universe; use alloc::sync::Arc; use alloc::vec::Vec; diff --git a/all-is-cubes-mesh/src/block_mesh/planar.rs b/all-is-cubes-mesh/src/block_mesh/planar.rs index 5ec1a7623..5f9aecabc 100644 --- a/all-is-cubes-mesh/src/block_mesh/planar.rs +++ b/all-is-cubes-mesh/src/block_mesh/planar.rs @@ -6,9 +6,8 @@ use core::ops::Range; use all_is_cubes::block::Resolution; use all_is_cubes::euclid::{Point2D, Scale, Transform3D, Vector2D}; use all_is_cubes::math::{ - Axis, Cube, Face6, FreeCoordinate, FreePoint, GridCoordinate, Rgba, VectorOps, + rgba_const, Axis, Cube, Face6, FreeCoordinate, FreePoint, GridCoordinate, Rgba, VectorOps, }; -use all_is_cubes::rgba_const; use crate::texture::{self, TexelUnit, TextureCoordinate, TilePoint}; use crate::{BlockVertex, Coloring, IndexVec, Viz}; diff --git a/all-is-cubes-mesh/src/dynamic/chunked_mesh/tests.rs b/all-is-cubes-mesh/src/dynamic/chunked_mesh/tests.rs index 936329ddb..f4f7a2e61 100644 --- a/all-is-cubes-mesh/src/dynamic/chunked_mesh/tests.rs +++ b/all-is-cubes-mesh/src/dynamic/chunked_mesh/tests.rs @@ -6,13 +6,13 @@ use std::sync::{Arc, Mutex}; use all_is_cubes::block::{self, Block, BlockDef, BlockDefTransaction, AIR}; use all_is_cubes::camera::{Camera, Flaws, GraphicsOptions, TransparencyOption, Viewport}; use all_is_cubes::chunking::ChunkPos; +use all_is_cubes::color_block; use all_is_cubes::content::make_some_blocks; use all_is_cubes::listen::Listener as _; +use all_is_cubes::math::{notnan, GridPoint, NotNan}; use all_is_cubes::math::{Cube, FreePoint, GridAab, GridCoordinate}; -use all_is_cubes::math::{GridPoint, NotNan}; use all_is_cubes::space::{BlockIndex, Space, SpaceChange, SpaceTransaction}; use all_is_cubes::universe::{Handle, Universe}; -use all_is_cubes::{color_block, notnan}; use all_is_cubes::{time, transaction}; use crate::texture::NoTextures; diff --git a/all-is-cubes-mesh/src/tests.rs b/all-is-cubes-mesh/src/tests.rs index fdd248549..f79a0a088 100644 --- a/all-is-cubes-mesh/src/tests.rs +++ b/all-is-cubes-mesh/src/tests.rs @@ -6,16 +6,17 @@ use pretty_assertions::assert_eq; use all_is_cubes::block::{self, Block, Resolution::*, AIR}; use all_is_cubes::camera::{Flaws, TransparencyOption}; +use all_is_cubes::color_block; use all_is_cubes::content::{make_some_blocks, make_some_voxel_blocks}; use all_is_cubes::euclid::{point3, Point3D, Vector3D}; -use all_is_cubes::math::{Cube, Rgb, VectorOps}; use all_is_cubes::math::{ + notnan, Face6::{self, *}, FaceMap, FreeCoordinate, GridAab, GridRotation, Rgba, }; +use all_is_cubes::math::{Cube, Rgb, VectorOps}; use all_is_cubes::space::{Space, SpacePhysics}; use all_is_cubes::universe::Universe; -use all_is_cubes::{color_block, notnan}; use crate::testing::{mesh_blocks_and_space, Allocator, TexPoint, TextureMt}; use crate::texture::TexelUnit; diff --git a/all-is-cubes-port/src/stl.rs b/all-is-cubes-port/src/stl.rs index e7b2dfab9..d244df8d2 100644 --- a/all-is-cubes-port/src/stl.rs +++ b/all-is-cubes-port/src/stl.rs @@ -8,8 +8,7 @@ use stl_io::Triangle; use all_is_cubes::block; use all_is_cubes::camera::GraphicsOptions; use all_is_cubes::euclid::Vector3D; -use all_is_cubes::math::{Cube, FreeCoordinate, VectorOps}; -use all_is_cubes::notnan; +use all_is_cubes::math::{notnan, Cube, FreeCoordinate, VectorOps}; use all_is_cubes::space::Space; use all_is_cubes::universe::PartialUniverse; use all_is_cubes::util::YieldProgress; diff --git a/all-is-cubes-ui/src/apps/input.rs b/all-is-cubes-ui/src/apps/input.rs index 3a85a44fe..a0f390df1 100644 --- a/all-is-cubes-ui/src/apps/input.rs +++ b/all-is-cubes-ui/src/apps/input.rs @@ -8,8 +8,7 @@ use all_is_cubes::camera::{ use all_is_cubes::character::Character; use all_is_cubes::euclid::{Point2D, Vector2D}; use all_is_cubes::listen::{ListenableCell, ListenableSource}; -use all_is_cubes::math::{FreeCoordinate, FreeVector, VectorOps}; -use all_is_cubes::notnan; +use all_is_cubes::math::{notnan, FreeCoordinate, FreeVector, VectorOps}; use all_is_cubes::time::Tick; use all_is_cubes::universe::{Handle, Universe}; diff --git a/all-is-cubes-ui/src/ui_content/options.rs b/all-is-cubes-ui/src/ui_content/options.rs index f00d648fb..ed7e6c0a9 100644 --- a/all-is-cubes-ui/src/ui_content/options.rs +++ b/all-is-cubes-ui/src/ui_content/options.rs @@ -3,8 +3,7 @@ use core::fmt; use all_is_cubes::arcstr::{self, literal}; use all_is_cubes::camera::{self, AntialiasingOption, GraphicsOptions}; -use all_is_cubes::math::Face6; -use all_is_cubes::notnan; +use all_is_cubes::math::{notnan, Face6}; use crate::apps::ControlMessage; use crate::ui_content::hud::HudInputs; diff --git a/all-is-cubes-wasm/Cargo.lock b/all-is-cubes-wasm/Cargo.lock index 511e90ac4..53a5c2e07 100644 --- a/all-is-cubes-wasm/Cargo.lock +++ b/all-is-cubes-wasm/Cargo.lock @@ -25,6 +25,7 @@ dependencies = [ name = "all-is-cubes" version = "0.7.1" dependencies = [ + "all-is-cubes-base", "arcstr", "arrayvec", "base64", @@ -38,7 +39,6 @@ dependencies = [ "exhaust", "flate2", "futures-core", - "futures-util", "hashbrown", "indoc", "itertools 0.12.1", @@ -49,15 +49,34 @@ dependencies = [ "once_cell", "ordered-float", "png-decoder", - "polonius-the-crab", "rand 0.8.5", "rand_xoshiro", "serde", - "serde_repr", "unicode-segmentation", "yield-progress", ] +[[package]] +name = "all-is-cubes-base" +version = "0.7.1" +dependencies = [ + "bytemuck", + "cfg-if", + "displaydoc", + "embedded-graphics", + "euclid", + "exhaust", + "futures-core", + "futures-util", + "manyfmt", + "mutants", + "num-traits", + "ordered-float", + "polonius-the-crab", + "rand 0.8.5", + "serde", +] + [[package]] name = "all-is-cubes-content" version = "0.7.0" @@ -1619,17 +1638,6 @@ dependencies = [ "serde", ] -[[package]] -name = "serde_repr" -version = "0.1.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b2e6b945e9d3df726b65d6ee24060aff8e3533d431f677a9695db04eff9dfdb" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.52", -] - [[package]] name = "simd-adler32" version = "0.3.7" diff --git a/all-is-cubes/Cargo.toml b/all-is-cubes/Cargo.toml index 01f7b65a5..da210fd07 100644 --- a/all-is-cubes/Cargo.toml +++ b/all-is-cubes/Cargo.toml @@ -53,19 +53,24 @@ default = ["std"] # Adds std-dependent functionality such as thread safety. std = [ "dep:once_cell", + "all-is-cubes-base/std", "arcstr/std", # not required but nicer behavior "yield-progress/sync", ] # Adds `impl arbitrary::Arbitrary for ...` # Note: Not using euclid/arbitrary because it's broken -arbitrary = ["dep:arbitrary", "ordered-float/arbitrary"] +arbitrary = [ + "dep:arbitrary", + "all-is-cubes-base/arbitrary", + "ordered-float/arbitrary", +] # Adds serde implementations. save = [ "std", # TODO: Eliminate dependency on std (requires replacing flate2) "dep:base64", # encoded compressed data "dep:flate2", # compression "dep:serde", - "dep:serde_repr", + "all-is-cubes-base/serde", "bitflags/serde", # serialize for util::ShowStatus "bytemuck/extern_crate_std", # impl Error for CheckedCastError "ordered-float/serde", @@ -76,6 +81,7 @@ rerun = [ "dep:re_log_types", "dep:re_sdk", "dep:re_types", + "all-is-cubes-base/rerun", ] # Adds automatic parallelism to some algorithms. # If disabled, `std::thread` is never used. @@ -83,6 +89,7 @@ rerun = [ auto-threads = ["std", "dep:rayon"] [dependencies] +all-is-cubes-base = { workspace = true } arbitrary = { workspace = true, optional = true } arcstr = { version = "1.1.5", default-features = false, features = ["serde", "substr"] } arrayvec = { workspace = true } @@ -95,11 +102,10 @@ downcast-rs = { version = "1.2.0", default-features = false } # mint feature to guarantee that our callers can use mint types # libm feature to guarantee compilability and compatibility with no_std euclid = { version = "0.22.9", default-features = false, features = ["libm", "mint"] } -embedded-graphics = "0.8.0" +embedded-graphics = { workspace = true } exhaust = { workspace = true, default-features = false } flate2 = { version = "1.0.26", optional = true } futures-core = { workspace = true } -futures-util = { workspace = true } hashbrown = { workspace = true } indoc = { workspace = true } itertools = { workspace = true } @@ -109,7 +115,6 @@ mutants = { workspace = true } num-traits = { workspace = true } once_cell = { workspace = true, optional = true } ordered-float = { workspace = true } -polonius-the-crab = { workspace = true } png-decoder = { version = "0.1.1" } rand = { workspace = true } rand_xoshiro = { workspace = true } @@ -122,7 +127,6 @@ re_types = { workspace = true, optional = true } # rc feature needed because we are [de]serializing `Arc`s # alloc feature needed for #[serde(flatten)] — https://github.com/serde-rs/serde/issues/1935 serde = { workspace = true, optional = true, features = ["alloc", "derive", "rc"] } -serde_repr = { version = "0.1.12", optional = true, default-features = false } unicode-segmentation = { workspace = true } yield-progress = { workspace = true } @@ -133,6 +137,7 @@ euclid = { version = "0.22.9", default-features = false, features = ["libm", "mi [dev-dependencies] allocation-counter = { workspace = true } criterion = { workspace = true } +futures-util = { workspace = true } pretty_assertions = { workspace = true } serde_json = { workspace = true } # Using tokio for async test-running. diff --git a/all-is-cubes/src/behavior.rs b/all-is-cubes/src/behavior.rs index a3bd84fd6..0c0ac2061 100644 --- a/all-is-cubes/src/behavior.rs +++ b/all-is-cubes/src/behavior.rs @@ -773,8 +773,9 @@ pub struct BehaviorTransactionConflict { key: Key, } -#[cfg(feature = "std")] -impl std::error::Error for BehaviorTransactionConflict {} +crate::util::cfg_should_impl_error! { + impl std::error::Error for BehaviorTransactionConflict {} +} #[cfg(test)] pub(crate) use testing::*; @@ -904,8 +905,8 @@ mod tests { }); let character = u.insert_anonymous(character); - u.step(false, time::DeadlineStd::Whenever); - u.step(false, time::DeadlineStd::Whenever); + u.step(false, time::DeadlineNt::Whenever); + u.step(false, time::DeadlineNt::Whenever); // Until we have a way to query the behavior set, the best test we can do is to // read its effects. @@ -933,7 +934,7 @@ mod tests { .count(), 1 ); - u.step(false, time::DeadlineStd::Whenever); + u.step(false, time::DeadlineNt::Whenever); assert_eq!( character .read() diff --git a/all-is-cubes/src/block.rs b/all-is-cubes/src/block.rs index faaffc3c8..987a7409d 100644 --- a/all-is-cubes/src/block.rs +++ b/all-is-cubes/src/block.rs @@ -32,7 +32,7 @@ use crate::universe::{Handle, HandleVisitor, VisitHandles}; /// or four [`f32`] literal color components. /// /// ``` -/// use all_is_cubes::{block::Block, color_block, math::Rgb, rgb_const}; +/// use all_is_cubes::{block::Block, color_block, math::{Rgb, rgb_const}}; /// /// assert_eq!( /// color_block!(rgb_const!(1.0, 0.5, 0.0)), @@ -61,11 +61,11 @@ macro_rules! color_block { }}; ($r:literal, $g:literal, $b:literal $(,)?) => { - $crate::color_block!($crate::rgb_const!($r, $g, $b)) + $crate::color_block!($crate::math::rgb_const!($r, $g, $b)) }; ($r:literal, $g:literal, $b:literal, $a:literal $(,)?) => { - $crate::color_block!($crate::rgba_const!($r, $g, $b, $a)) + $crate::color_block!($crate::math::rgba_const!($r, $g, $b, $a)) }; } @@ -88,8 +88,28 @@ pub use evaluation::*; mod modifier; pub use modifier::*; -mod resolution; -pub use resolution::*; +/// Scale factor between a [`Block`] and its component voxels. +/// +/// This resolution cubed is the number of voxels making up a block. +/// +/// Resolutions are always powers of 2. This ensures that the arithmetic is well-behaved +/// (no division by zero, exact floating-point representation, and the potential of +/// fixed-point representation), +/// and that it is always possible to subdivide a block further (up to the limit) without +/// shifting the existing voxel boundaries. +/// +/// Note that while quite high resolutions are permitted, this does not mean that it is +/// practical to routinely use full blocks at that resolution. For example, 64 × 64 × 64 +/// = 262,144 voxels, occupying several megabytes just for color data. +/// High resolutions are permitted for special purposes that do not necessarily use the +/// full cube volume: +/// +/// * *Thin* blocks (e.g. 128 × 128 × 1) can display high resolution text and other 2D +/// images. +/// * Multi-block structures can be defined using [`Modifier::Zoom`]; their total size +/// is limited by the resolution limit. +pub use all_is_cubes_base::resolution::Resolution; +pub use all_is_cubes_base::resolution::*; pub mod text; diff --git a/all-is-cubes/src/block/block_def.rs b/all-is-cubes/src/block/block_def.rs index 6f8cb7d30..7d44c2460 100644 --- a/all-is-cubes/src/block/block_def.rs +++ b/all-is-cubes/src/block/block_def.rs @@ -388,8 +388,9 @@ pub struct BlockDefConflict { pub(crate) new: bool, } -#[cfg(feature = "std")] -impl std::error::Error for BlockDefConflict {} +crate::util::cfg_should_impl_error! { + impl std::error::Error for BlockDefConflict {} +} impl fmt::Display for BlockDefConflict { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { diff --git a/all-is-cubes/src/block/evaluation.rs b/all-is-cubes/src/block/evaluation.rs index 98a45d18a..7c46b2e1c 100644 --- a/all-is-cubes/src/block/evaluation.rs +++ b/all-is-cubes/src/block/evaluation.rs @@ -253,13 +253,14 @@ pub(in crate::block) enum InEvalError { Handle(HandleError), } -#[cfg(feature = "std")] -impl std::error::Error for EvalBlockError { - fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { - match self { - EvalBlockError::BudgetExceeded { .. } => None, - EvalBlockError::PriorBudgetExceeded { .. } => None, - EvalBlockError::Handle(e) => Some(e), +crate::util::cfg_should_impl_error! { + impl std::error::Error for EvalBlockError { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + match self { + EvalBlockError::BudgetExceeded { .. } => None, + EvalBlockError::PriorBudgetExceeded { .. } => None, + EvalBlockError::Handle(e) => Some(e), + } } } } diff --git a/all-is-cubes/src/block/modifier/move.rs b/all-is-cubes/src/block/modifier/move.rs index a6eaba523..a913875b7 100644 --- a/all-is-cubes/src/block/modifier/move.rs +++ b/all-is-cubes/src/block/modifier/move.rs @@ -203,7 +203,7 @@ mod tests { use super::*; use crate::block::{Composite, EvaluatedBlock, Resolution::*}; use crate::content::make_some_blocks; - use crate::math::{FaceMap, GridPoint, OpacityCategory, Rgb, Rgba}; + use crate::math::{notnan, rgba_const, FaceMap, GridPoint, OpacityCategory, Rgb, Rgba}; use crate::space::Space; use crate::time; use crate::universe::Universe; @@ -340,7 +340,7 @@ mod tests { // TODO: We need a "step until idle" function, or for the UniverseStepInfo to convey how many blocks were updated / are waiting // TODO: Some tests will want to look at the partial results for _ in 0..257 { - universe.step(false, time::DeadlineStd::Whenever); + universe.step(false, time::DeadlineNt::Whenever); } checker(&space.read().unwrap(), &block); } diff --git a/all-is-cubes/src/block/tests.rs b/all-is-cubes/src/block/tests.rs index 872d6c22e..98ee69f1a 100644 --- a/all-is-cubes/src/block/tests.rs +++ b/all-is-cubes/src/block/tests.rs @@ -17,8 +17,8 @@ use crate::block::{ use crate::content::make_some_blocks; use crate::listen::{self, NullListener, Sink}; use crate::math::{ - Cube, Face6, FaceMap, GridAab, GridCoordinate, GridPoint, GridRotation, GridVector, NotNan, - OpacityCategory, Rgb, Rgba, Vol, + notnan, Cube, Face6, FaceMap, GridAab, GridCoordinate, GridPoint, GridRotation, GridVector, + NotNan, OpacityCategory, Rgb, Rgba, Vol, }; use crate::space::{Space, SpaceTransaction}; use crate::time::DeadlineNt; diff --git a/all-is-cubes/src/block/text.rs b/all-is-cubes/src/block/text.rs index 21678ad60..08d865171 100644 --- a/all-is-cubes/src/block/text.rs +++ b/all-is-cubes/src/block/text.rs @@ -11,7 +11,9 @@ use euclid::vec3; use crate::block::{self, Block, BlockAttributes, Evoxel, MinEval, Resolution}; use crate::content::palette; use crate::drawing::{rectangle_to_aab, DrawingPlane}; -use crate::math::{FaceMap, GridAab, GridCoordinate, GridVector, Gridgid, Rgb, Rgba, Vol}; +use crate::math::{ + rgba_const, FaceMap, GridAab, GridCoordinate, GridVector, Gridgid, Rgb, Rgba, Vol, +}; use crate::space::{self, SpaceTransaction}; use crate::universe; diff --git a/all-is-cubes/src/camera/graphics_options.rs b/all-is-cubes/src/camera/graphics_options.rs index bd3949487..870d217d7 100644 --- a/all-is-cubes/src/camera/graphics_options.rs +++ b/all-is-cubes/src/camera/graphics_options.rs @@ -3,7 +3,7 @@ use core::fmt; use num_traits::One; use ordered_float::NotNan; -use crate::math::{FreeCoordinate, Rgb, Rgba}; +use crate::math::{notnan, FreeCoordinate, Rgb, Rgba}; use crate::util::ShowStatus; #[cfg(doc)] @@ -455,9 +455,8 @@ impl AntialiasingOption { #[cfg(test)] mod tests { - use crate::math::OpacityCategory; - use super::*; + use crate::math::{rgba_const, OpacityCategory}; use pretty_assertions::assert_eq; #[test] diff --git a/all-is-cubes/src/camera/renderer.rs b/all-is-cubes/src/camera/renderer.rs index b336b365f..a8d687703 100644 --- a/all-is-cubes/src/camera/renderer.rs +++ b/all-is-cubes/src/camera/renderer.rs @@ -55,11 +55,12 @@ pub enum RenderError { // TODO: add errors for out of memory, lost GPU, etc. } -#[cfg(feature = "std")] -impl std::error::Error for RenderError { - fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { - match self { - RenderError::Read(e) => Some(e), +crate::util::cfg_should_impl_error! { + impl std::error::Error for RenderError { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + match self { + RenderError::Read(e) => Some(e), + } } } } diff --git a/all-is-cubes/src/camera/tests.rs b/all-is-cubes/src/camera/tests.rs index d08c06cdf..793d99d5c 100644 --- a/all-is-cubes/src/camera/tests.rs +++ b/all-is-cubes/src/camera/tests.rs @@ -6,7 +6,7 @@ use crate::camera::{ look_at_y_up, Camera, ExposureOption, FrustumPoints, GraphicsOptions, LightingOption, ViewTransform, Viewport, }; -use crate::math::{Aab, NotNan}; +use crate::math::{notnan, rgba_const, Aab, NotNan}; #[test] fn camera_bad_viewport_doesnt_panic() { diff --git a/all-is-cubes/src/character.rs b/all-is-cubes/src/character.rs index ff0123c16..b04225280 100644 --- a/all-is-cubes/src/character.rs +++ b/all-is-cubes/src/character.rs @@ -791,14 +791,15 @@ pub enum CharacterTransactionConflict { Behaviors(behavior::BehaviorTransactionConflict), } -#[cfg(feature = "std")] -impl std::error::Error for CharacterTransactionConflict { - fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { - match self { - CharacterTransactionConflict::SetSpace => None, - CharacterTransactionConflict::Body(_) => None, - CharacterTransactionConflict::Inventory(e) => Some(e), - CharacterTransactionConflict::Behaviors(e) => Some(e), +crate::util::cfg_should_impl_error! { + impl std::error::Error for CharacterTransactionConflict { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + match self { + CharacterTransactionConflict::SetSpace => None, + CharacterTransactionConflict::Body(_) => None, + CharacterTransactionConflict::Inventory(e) => Some(e), + CharacterTransactionConflict::Behaviors(e) => Some(e), + } } } } diff --git a/all-is-cubes/src/character/exposure.rs b/all-is-cubes/src/character/exposure.rs index 6f8ec2cb3..8a153fa39 100644 --- a/all-is-cubes/src/character/exposure.rs +++ b/all-is-cubes/src/character/exposure.rs @@ -185,7 +185,7 @@ mod tests { // .fill_uniform(space.bounds().expand(FaceMap::repeat(-1)), AIR) // .unwrap(); - space.evaluate_light::(0, |_| {}); + space.evaluate_light::(0, |_| {}); space }); let mut character = Character::spawn_default(space); diff --git a/all-is-cubes/src/content.rs b/all-is-cubes/src/content.rs index a9ef13b9f..d61354431 100644 --- a/all-is-cubes/src/content.rs +++ b/all-is-cubes/src/content.rs @@ -18,7 +18,7 @@ use crate::arcstr::{literal, ArcStr}; use crate::block::{Block, Resolution, Resolution::R16, RotationPlacementRule}; use crate::color_block; use crate::inv::{Slot, Tool}; -use crate::math::{Face6, FaceMap, FreeCoordinate, GridAab, GridCoordinate, Rgb, Rgba}; +use crate::math::{rgb_const, Face6, FaceMap, FreeCoordinate, GridAab, GridCoordinate, Rgb, Rgba}; use crate::raycast::Raycaster; use crate::space::{SetCubeError, Space}; use crate::transaction::Transactional as _; diff --git a/all-is-cubes/src/content/palette.rs b/all-is-cubes/src/content/palette.rs index 320a3aaa6..efff421ca 100644 --- a/all-is-cubes/src/content/palette.rs +++ b/all-is-cubes/src/content/palette.rs @@ -14,7 +14,7 @@ // // 0xBB is the sRGB value approximating linear value 0.5. -use crate::math::Rgb; +use crate::math::{rgb_const, Rgb}; /// Define a color constant and preview it in the documentation. macro_rules! palette_entry { @@ -134,47 +134,3 @@ palette! { DEBUG_CHUNK_MAJOR = srgb[0x00 0x00 0xE8 0xFF]; DEBUG_CHUNK_MINOR = srgb[0x00 0xE8 0xE8 0xFF]; } - -palette! { - UNIFORM_LUMINANCE_RED = srgb[0x9E 0x00 0x00]; - UNIFORM_LUMINANCE_GREEN = srgb[0x00 0x59 0x00]; - UNIFORM_LUMINANCE_BLUE = srgb[0x00 0x00 0xFF]; -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::math::Rgba; - use exhaust::Exhaust as _; - - #[test] - fn uniform_luminance_check() { - fn optimize(channel: usize) -> [u8; 4] { - // Blue is the primary color whose maximum intensity is darkest; - // therefore it is the standard by which we check the other. - let reference_luminance = UNIFORM_LUMINANCE_BLUE.luminance(); - let (_color, srgb, luminance_difference) = u8::exhaust() - .map(|srgb_byte| { - let mut srgb = [0, 0, 0, 255]; - srgb[channel] = srgb_byte; - let color = Rgba::from_srgb8(srgb); - (color, srgb, (color.luminance() - reference_luminance).abs()) - }) - .min_by(|a, b| a.2.total_cmp(&b.2)) - .unwrap(); - println!("best luminance difference = {luminance_difference}"); - srgb - } - - println!("red:"); - assert_eq!( - UNIFORM_LUMINANCE_RED.with_alpha_one().to_srgb8(), - optimize(0) - ); - println!("green:"); - assert_eq!( - UNIFORM_LUMINANCE_GREEN.with_alpha_one().to_srgb8(), - optimize(1) - ); - } -} diff --git a/all-is-cubes/src/drawing.rs b/all-is-cubes/src/drawing.rs index 3b49afad2..17d97c793 100644 --- a/all-is-cubes/src/drawing.rs +++ b/all-is-cubes/src/drawing.rs @@ -21,7 +21,7 @@ use core::marker::PhantomData; use core::ops::Range; use embedded_graphics::geometry::{Dimensions, Point, Size}; -use embedded_graphics::pixelcolor::{PixelColor, Rgb888, RgbColor}; +use embedded_graphics::pixelcolor::{PixelColor, Rgb888}; use embedded_graphics::prelude::{DrawTarget, Drawable, Pixel}; use embedded_graphics::primitives::Rectangle; @@ -260,15 +260,6 @@ impl Dimensions for DrawingPlane<'_, SpaceTransaction, C> { } } -/// Adapt [`embedded_graphics`]'s most general color type to ours. -// TODO: Also adapt the other types, so that if someone wants to use them they can. -impl From for Rgb { - #[inline] - fn from(color: Rgb888) -> Rgb { - Rgba::from_srgb8([color.r(), color.g(), color.b(), u8::MAX]).to_rgb() - } -} - /// Allows “drawing” blocks onto a [`DrawingPlane`], a two-dimensional coordinate system /// established within a [`Space`]. /// @@ -289,18 +280,12 @@ impl<'a> VoxelColor<'a> for &'a Block { } } -impl PixelColor for Rgb { - type Raw = (); -} impl<'a> VoxelColor<'a> for Rgb { fn into_blocks(self) -> VoxelBrush<'a> { VoxelBrush::single(Block::from(self)) } } -impl PixelColor for Rgba { - type Raw = (); -} impl<'a> VoxelColor<'a> for Rgba { fn into_blocks(self) -> VoxelBrush<'a> { VoxelBrush::single(Block::from(self)) diff --git a/all-is-cubes/src/inv/icons.rs b/all-is-cubes/src/inv/icons.rs index b99b5ca70..a34029d66 100644 --- a/all-is-cubes/src/inv/icons.rs +++ b/all-is-cubes/src/inv/icons.rs @@ -17,7 +17,8 @@ use crate::content::load_image::{default_srgb, include_image, space_from_image}; use crate::drawing::VoxelBrush; use crate::linking::{BlockModule, BlockProvider}; use crate::math::{ - Face6, FreeCoordinate, GridCoordinate, GridRotation, GridVector, Gridgid, Rgba, VectorOps, + rgb_const, rgba_const, Face6, FreeCoordinate, GridCoordinate, GridRotation, GridVector, + Gridgid, Rgba, VectorOps, }; use crate::space::Space; use crate::universe::UniverseTransaction; diff --git a/all-is-cubes/src/inv/inventory.rs b/all-is-cubes/src/inv/inventory.rs index 246f0f6ee..e70496e92 100644 --- a/all-is-cubes/src/inv/inventory.rs +++ b/all-is-cubes/src/inv/inventory.rs @@ -480,8 +480,9 @@ pub enum InventoryConflict { ReplaceSameSlot { slot: usize }, } -#[cfg(feature = "std")] -impl std::error::Error for InventoryConflict {} +crate::util::cfg_should_impl_error! { + impl std::error::Error for InventoryConflict {} +} /// Description of a change to an [`Inventory`] for use in listeners. #[derive(Clone, Debug, Eq, Hash, PartialEq)] diff --git a/all-is-cubes/src/inv/tool.rs b/all-is-cubes/src/inv/tool.rs index 559e9e2a2..d5f35ff3e 100644 --- a/all-is-cubes/src/inv/tool.rs +++ b/all-is-cubes/src/inv/tool.rs @@ -474,7 +474,7 @@ pub enum ToolError { Internal(String), } -#[cfg(feature = "std")] +crate::util::cfg_should_impl_error! { impl std::error::Error for ToolError { fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { match self { @@ -487,6 +487,7 @@ impl std::error::Error for ToolError { } } } +} impl ToolError { /// Return [`Fluff`] to accompany this error. diff --git a/all-is-cubes/src/lib.rs b/all-is-cubes/src/lib.rs index 2246ea23f..eae97a198 100644 --- a/all-is-cubes/src/lib.rs +++ b/all-is-cubes/src/lib.rs @@ -154,9 +154,6 @@ extern crate std; #[macro_use] extern crate alloc; -#[macro_use] -pub mod math; - pub mod behavior; pub mod block; pub mod camera; @@ -171,6 +168,7 @@ pub mod intalloc; pub mod inv; pub mod linking; pub mod listen; +pub mod math; pub mod op; pub mod physics; pub mod raycast; diff --git a/all-is-cubes/src/linking.rs b/all-is-cubes/src/linking.rs index 0bdcfde0e..1c4b3f7f8 100644 --- a/all-is-cubes/src/linking.rs +++ b/all-is-cubes/src/linking.rs @@ -307,8 +307,9 @@ pub struct ProviderError { missing: Box<[Name]>, } -#[cfg(feature = "std")] -impl std::error::Error for ProviderError {} +crate::util::cfg_should_impl_error! { + impl std::error::Error for ProviderError {} +} /// An error resulting from “world generation”: failure to calculate/create/place objects /// (due to bad parameters or unforeseen edge cases), failure to successfully store them @@ -320,10 +321,11 @@ pub struct GenError { for_object: Option, } -#[cfg(feature = "std")] -impl std::error::Error for GenError { - fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { - Some(&self.detail) +crate::util::cfg_should_impl_error! { + impl std::error::Error for GenError { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + Some(&self.detail) + } } } @@ -419,17 +421,18 @@ impl InGenError { } } -#[cfg(feature = "std")] -impl std::error::Error for InGenError { - fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { - match self { - InGenError::Other(e) => e.source(), - InGenError::Gen(e) => e.source(), - InGenError::Insert(e) => e.source(), - InGenError::Provider(e) => e.source(), - InGenError::SetCube(e) => e.source(), - InGenError::UniverseTransaction(e) => e.source(), - InGenError::SpaceTransaction(e) => e.source(), +crate::util::cfg_should_impl_error! { + impl std::error::Error for InGenError { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + match self { + InGenError::Other(e) => e.source(), + InGenError::Gen(e) => e.source(), + InGenError::Insert(e) => e.source(), + InGenError::Provider(e) => e.source(), + InGenError::SetCube(e) => e.source(), + InGenError::UniverseTransaction(e) => e.source(), + InGenError::SpaceTransaction(e) => e.source(), + } } } } diff --git a/all-is-cubes/src/math.rs b/all-is-cubes/src/math.rs index acf21d758..1777ac47b 100644 --- a/all-is-cubes/src/math.rs +++ b/all-is-cubes/src/math.rs @@ -1,127 +1,32 @@ //! Mathematical utilities and decisions. -use euclid::Vector3D; -use num_traits::identities::Zero; -pub use ordered_float::{FloatIsNan, NotNan}; - /// Acts as polyfill for float methods #[cfg(not(feature = "std"))] #[allow(unused_imports)] use num_traits::float::FloatCore as _; -use crate::util::ConciseDebug; - -mod aab; -pub use aab::*; -mod axis; -pub use axis::*; -#[macro_use] -mod color; -pub use color::*; -mod coord; -pub use coord::*; -mod cube; -pub use cube::Cube; -mod face; -pub use face::*; -mod grid_aab; -pub use grid_aab::*; -mod grid_iter; -pub use grid_iter::*; -mod rigid; -pub use rigid::*; -mod matrix; -pub use matrix::*; -mod rotation; -pub use rotation::*; -mod vol; -pub use vol::*; +// Most of the content of this module is implemented in `all_is_cubes_base::math`. +#[doc(inline)] +pub use all_is_cubes_base::math::*; -// We make an assumption in several places that `usize` is at least 32 bits. -// It's likely that compilation would not succeed anyway, but let's make it explicit. -#[cfg(not(any( - target_pointer_width = "32", - target_pointer_width = "64", - target_pointer_width = "128", -)))] -compile_error!("all-is-cubes does not support platforms with less than 32-bit `usize`"); +// A crate defining a macro can't export it except at the root, +// but when we re-export, we can put them in their right place (this module). +#[doc(inline)] +pub use all_is_cubes_base::{notnan, rgb_const, rgba_const}; -/// Allows writing a [`NotNan`] value as a constant expression (which is not currently -/// a feature provided by the [`ordered_float`] crate itself). -/// -/// Note that if the expression does not need to be constant, this macro may not be -/// needed; infallible construction can be written using `NotNan::from(an_integer)`, -/// `NotNan::zero()`, and `NotNan::one()`. -/// -/// ``` -/// use all_is_cubes::{notnan, math::NotNan}; -/// -/// const X: NotNan = notnan!(1.234); -/// ``` +/// An axis-aligned box with integer coordinates. +/// [`GridAab`]s are used to specify the coordinate extent of [`Space`](crate::space::Space)s, and +/// regions within them. /// -/// ```compile_fail -/// # use all_is_cubes::{notnan, math::NotNan}; -/// // Not a literal; will not compile -/// const X: NotNan = notnan!(f32::NAN); -/// ``` +/// When we refer to “a cube” in a [`GridAab`], that is a unit cube which is identified by the +/// integer coordinates of its most negative corner, in the fashion of [`Cube`]. /// -/// ```compile_fail -/// # use all_is_cubes::{notnan, math::NotNan}; -/// // Not a literal; will not compile -/// const X: NotNan = notnan!(0.0 / 0.0); -/// ``` +/// A [`GridAab`] may have a zero-size range in any direction, thus making its total volume zero. +/// The different possibilities are not considered equal; thus, points, lines, and planes may be +/// represented, which may be useful for procedural-generation purposes. /// -/// ```compile_fail -/// # use all_is_cubes::{notnan, math::NotNan}; -/// const N0N: f32 = f32::NAN; -/// // Not a literal; will not compile -/// const X: NotNan = notnan!(N0N); -/// ``` -/// -/// ```compile_fail -/// # use all_is_cubes::{notnan, math::NotNan}; -/// // Not a float; will not compile -/// const X: NotNan = notnan!('a'); -/// ``` -#[doc(hidden)] -#[macro_export] // used by all-is-cubes-content -macro_rules! notnan { - ($value:literal) => { - match $value { - value => { - // Safety: Only literal values are allowed, which will either be a non-NaN - // float or (as checked below) a type mismatch. - let result = unsafe { $crate::math::NotNan::new_unchecked(value) }; - - // Ensure that the type is one which could have resulted from a float literal, - // by requiring type unification with a literal. This prohibits char, &str, etc. - let _ = if false { - // Safety: Statically never NaN, and is also never executed. - unsafe { $crate::math::NotNan::new_unchecked(0.0) } - } else { - result - }; - - result - } - } - }; -} - -#[inline] -pub(crate) fn smoothstep(x: f64) -> f64 { - let x = x.clamp(0.0, 1.0); - 3. * x.powi(2) - 2. * x.powi(3) -} - -/// Sort exactly two items; swap them if `a > b`. -#[inline] -#[doc(hidden)] -pub fn sort_two(a: &mut T, b: &mut T) { - if *a > *b { - core::mem::swap(a, b); - } -} +#[doc = include_str!("save/serde-warning.md")] +pub use all_is_cubes_base::math::GridAab; #[cfg(not(feature = "std"))] /// Identical to [`num_traits::Euclid`] except that its signatures are compatible with @@ -140,57 +45,10 @@ impl Euclid for T { } } -/// Common features of objects that have a location and shape in space. -pub trait Geometry { - /// Type of coordinates; generally determines whether this object can be translated by a - /// non-integer amount. - type Coord; - - /// Translate (move) this object by the specified offset. - #[must_use] - fn translate(self, offset: Vector3D) -> Self; - - /// Represent this object as a line drawing, or wireframe. - /// - /// The generated points should be in pairs, each pair defining a line segment. - /// If there are an odd number of vertices, the caller should ignore the last. - /// - /// TODO: This should probably return an iterator instead, but defining the type - /// will be awkward until `type_alias_impl_trait` is stable. - fn wireframe_points(&self, output: &mut E) - where - E: Extend; -} - -/// One end of a line to be drawn. -/// -/// Used for debugging visualizations and not for game content, with the current exception -/// of [`Cursor`](crate::character::Cursor). -/// -/// The primary way in which these are used in this crate is -/// [`Geometry::wireframe_points()`]. -#[derive(Clone, Copy, Debug, PartialEq)] -#[non_exhaustive] -pub struct LineVertex { - /// Position of the vertex. - pub position: FreePoint, - - /// Color in which to draw the line. - /// - /// If [`None`], a color set by the context/parent should be used instead. - /// - /// If the ends of a line are different colors, color should be interpolated along - /// the line. - pub color: Option, -} - -impl From for LineVertex { - fn from(position: FreePoint) -> Self { - Self { - position, - color: None, - } - } +#[inline] +pub(crate) fn smoothstep(x: f64) -> f64 { + let x = x.clamp(0.0, 1.0); + 3. * x.powi(2) - 2. * x.powi(3) } #[cfg(test)] diff --git a/all-is-cubes/src/op.rs b/all-is-cubes/src/op.rs index 87eecfb16..0e150c918 100644 --- a/all-is-cubes/src/op.rs +++ b/all-is-cubes/src/op.rs @@ -113,8 +113,9 @@ impl VisitHandles for Operation { #[non_exhaustive] pub(crate) enum OperationError {} -#[cfg(feature = "std")] -impl std::error::Error for OperationError {} +crate::util::cfg_should_impl_error! { + impl std::error::Error for OperationError {} +} #[cfg(test)] mod tests { diff --git a/all-is-cubes/src/raytracer.rs b/all-is-cubes/src/raytracer.rs index 828bbe583..9129d1c7e 100644 --- a/all-is-cubes/src/raytracer.rs +++ b/all-is-cubes/src/raytracer.rs @@ -31,8 +31,8 @@ use crate::camera::{Camera, GraphicsOptions, TransparencyOption}; #[allow(unused_imports)] use crate::math::Euclid as _; use crate::math::{ - area_usize, smoothstep, Cube, Face6, Face7, FreeCoordinate, FreePoint, FreeVector, GridAab, - GridMatrix, Intensity, Rgb, Rgba, VectorOps, Vol, + area_usize, rgb_const, smoothstep, Cube, Face6, Face7, FreeCoordinate, FreePoint, FreeVector, + GridAab, GridMatrix, Intensity, Rgb, Rgba, VectorOps, Vol, }; use crate::raycast::Ray; use crate::space::{BlockIndex, BlockSky, PackedLight, Sky, Space, SpaceBlockData}; @@ -668,6 +668,7 @@ mod rayon_helper { #[cfg(test)] mod tests { use super::*; + use crate::math::rgba_const; #[test] fn apply_transmittance_identity() { diff --git a/all-is-cubes/src/raytracer/accum.rs b/all-is-cubes/src/raytracer/accum.rs index ebe2c3b16..b59ab632e 100644 --- a/all-is-cubes/src/raytracer/accum.rs +++ b/all-is-cubes/src/raytracer/accum.rs @@ -4,7 +4,7 @@ use euclid::Vector3D; use ordered_float::NotNan; use crate::camera::GraphicsOptions; -use crate::math::{Intensity, Rgb, Rgba}; +use crate::math::{notnan, rgb_const, Intensity, Rgb, Rgba}; use crate::space::SpaceBlockData; /// Borrowed data which may be used to customize the result of raytracing. diff --git a/all-is-cubes/src/raytracer/surface.rs b/all-is-cubes/src/raytracer/surface.rs index 7c4f2eb6e..a43408b87 100644 --- a/all-is-cubes/src/raytracer/surface.rs +++ b/all-is-cubes/src/raytracer/surface.rs @@ -350,7 +350,7 @@ mod tests { use super::*; use crate::block::{Block, Resolution::*, AIR}; use crate::camera::GraphicsOptions; - use crate::math::GridAab; + use crate::math::{rgba_const, GridAab}; use crate::space::Space; use crate::universe::Universe; use alloc::vec::Vec; diff --git a/all-is-cubes/src/rerun_glue.rs b/all-is-cubes/src/rerun_glue.rs index 4ebb3c83a..2ea001c4f 100644 --- a/all-is-cubes/src/rerun_glue.rs +++ b/all-is-cubes/src/rerun_glue.rs @@ -1,7 +1,7 @@ use alloc::vec::Vec; use core::fmt; -use crate::math::{self, Axis, Rgb}; +use crate::math::{self, rgba_const, Axis, Rgb}; // To support concise conditional debugging, this module re-exports many items from rerun. pub use re_log_types::{entity_path, EntityPath}; @@ -264,54 +264,3 @@ pub fn convert_camera_to_pinhole( pub fn milliseconds(d: core::time::Duration) -> archetypes::Scalar { archetypes::Scalar::new(d.as_secs_f64() * 1000.0) } - -impl From for view_coordinates::SignedAxis3 { - fn from(face: math::Face6) -> Self { - use math::Face6; - use view_coordinates::{Axis3, Sign, SignedAxis3}; - match face { - Face6::NX => SignedAxis3 { - sign: Sign::Negative, - axis: Axis3::X, - }, - Face6::NY => SignedAxis3 { - sign: Sign::Negative, - axis: Axis3::Y, - }, - Face6::NZ => SignedAxis3 { - sign: Sign::Negative, - axis: Axis3::Z, - }, - Face6::PX => SignedAxis3 { - sign: Sign::Positive, - axis: Axis3::X, - }, - Face6::PY => SignedAxis3 { - sign: Sign::Positive, - axis: Axis3::Y, - }, - Face6::PZ => SignedAxis3 { - sign: Sign::Positive, - axis: Axis3::Z, - }, - } - } -} - -impl From for components::Color { - fn from(value: Rgb) -> Self { - value.with_alpha_one().into() - } -} -// impl From for components::Color { -// fn from(value: math::Rgba) -> Self { -// let [r, g, b, a] = value.to_srgb8(); -// components::Color::from_unmultiplied_rgba(r, g, b, a) -// } -// } -impl From for datatypes::Rgba32 { - fn from(value: math::Rgba) -> Self { - let [r, g, b, a] = value.to_srgb8(); - datatypes::Rgba32::from_unmultiplied_rgba(r, g, b, a) - } -} diff --git a/all-is-cubes/src/save/conversion.rs b/all-is-cubes/src/save/conversion.rs index afbfbf7cd..e2abf147a 100644 --- a/all-is-cubes/src/save/conversion.rs +++ b/all-is-cubes/src/save/conversion.rs @@ -501,80 +501,6 @@ mod block { // `character::Character` and `character::Spawn` serialization are inside their module // for the sake of private fields. -mod math { - use super::*; - use crate::math::{Aab, Cube, GridAab}; - - impl Serialize for Cube { - fn serialize(&self, serializer: S) -> Result - where - S: Serializer, - { - let &Cube { x, y, z } = self; - - [x, y, z].serialize(serializer) - } - } - - impl<'de> Deserialize<'de> for Cube { - fn deserialize(deserializer: D) -> Result - where - D: Deserializer<'de>, - { - let [x, y, z] = <[i32; 3]>::deserialize(deserializer)?; - Ok(Cube::new(x, y, z)) - } - } - - impl Serialize for Aab { - fn serialize(&self, serializer: S) -> Result - where - S: Serializer, - { - schema::AabSer { - lower: self.lower_bounds_p().into(), - upper: self.upper_bounds_p().into(), - } - .serialize(serializer) - } - } - - impl<'de> Deserialize<'de> for Aab { - fn deserialize(deserializer: D) -> Result - where - D: Deserializer<'de>, - { - let schema::AabSer { lower, upper } = schema::AabSer::deserialize(deserializer)?; - Aab::checked_from_lower_upper(lower.into(), upper.into()) - .ok_or_else(|| serde::de::Error::custom("invalid AAB")) - } - } - - impl Serialize for GridAab { - fn serialize(&self, serializer: S) -> Result - where - S: Serializer, - { - schema::GridAabSer { - lower: self.lower_bounds().into(), - upper: self.upper_bounds().into(), - } - .serialize(serializer) - } - } - - impl<'de> Deserialize<'de> for GridAab { - fn deserialize(deserializer: D) -> Result - where - D: Deserializer<'de>, - { - let schema::GridAabSer { lower, upper } = - schema::GridAabSer::deserialize(deserializer)?; - GridAab::checked_from_lower_upper(lower, upper).map_err(serde::de::Error::custom) - } - } -} - mod inv { use super::*; use crate::inv::{Inventory, Slot, Tool}; diff --git a/all-is-cubes/src/save/schema.rs b/all-is-cubes/src/save/schema.rs index 184c2d0a9..72aa351f9 100644 --- a/all-is-cubes/src/save/schema.rs +++ b/all-is-cubes/src/save/schema.rs @@ -334,20 +334,6 @@ pub(crate) enum ToolSer { //------------------------------------------------------------------------------------------------// // Schema corresponding to the `math` module -#[derive(Debug, Deserialize, Serialize)] -pub(crate) struct AabSer { - // This one isn't an enum because I expect we'll not need to change it - pub(crate) lower: [f64; 3], - pub(crate) upper: [f64; 3], -} - -#[derive(Debug, Deserialize, Serialize)] -pub(crate) struct GridAabSer { - // This one isn't an enum because I expect we'll not need to change it - pub(crate) lower: [GridCoordinate; 3], - pub(crate) upper: [GridCoordinate; 3], -} - type RgbSer = [NotNan; 3]; type RgbaSer = [NotNan; 4]; diff --git a/all-is-cubes/src/save/tests.rs b/all-is-cubes/src/save/tests.rs index c6bf23464..3d0bdfc5d 100644 --- a/all-is-cubes/src/save/tests.rs +++ b/all-is-cubes/src/save/tests.rs @@ -18,7 +18,7 @@ use crate::character::{Character, Spawn}; use crate::content::make_some_blocks; use crate::drawing::VoxelBrush; use crate::inv::{EphemeralOpaque, Inventory, Tool}; -use crate::math::{Cube, Face6, GridAab, GridRotation, Rgb, Rgba}; +use crate::math::{notnan, Cube, Face6, GridAab, GridRotation, Rgb, Rgba}; use crate::save::compress::{GzSerde, Leu16}; use crate::space::{self, BlockIndex, LightPhysics, Space, SpacePhysics}; use crate::time::{self, Tick}; @@ -749,7 +749,7 @@ fn space_light_queue_remembered() { ); // Then, when stepped, they are updated - let (_, _) = space2.step(None, Tick::arbitrary(), time::DeadlineStd::Whenever); + let (_, _) = space2.step(None, Tick::arbitrary(), time::DeadlineNt::Whenever); assert_eq!( [0, 1, 2].map(|x| space2.get_lighting([x, 0, 0]).status()), [Opaque, Visible, NoRays] diff --git a/all-is-cubes/src/space.rs b/all-is-cubes/src/space.rs index d1c3436bf..cc2b2725b 100644 --- a/all-is-cubes/src/space.rs +++ b/all-is-cubes/src/space.rs @@ -22,7 +22,8 @@ use crate::fluff::{self, Fluff}; use crate::inv::{EphemeralOpaque, InventoryTransaction}; use crate::listen::{Listen, Listener, Notifier}; use crate::math::{ - Cube, FreeCoordinate, GridAab, GridCoordinate, GridRotation, Gridgid, NotNan, VectorOps, Vol, + notnan, rgb_const, Cube, FreeCoordinate, GridAab, GridCoordinate, GridRotation, Gridgid, + NotNan, VectorOps, Vol, }; use crate::physics::Acceleration; use crate::time; @@ -1162,8 +1163,9 @@ pub enum SetCubeError { TooManyBlocks(), } -#[cfg(feature = "std")] -impl std::error::Error for SetCubeError {} +crate::util::cfg_should_impl_error! { + impl std::error::Error for SetCubeError {} +} impl fmt::Display for SetCubeError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { diff --git a/all-is-cubes/src/space/light/data.rs b/all-is-cubes/src/space/light/data.rs index 67f4fbeb8..6ea1ba277 100644 --- a/all-is-cubes/src/space/light/data.rs +++ b/all-is-cubes/src/space/light/data.rs @@ -256,6 +256,7 @@ impl From for PackedLight { #[cfg(test)] mod tests { use super::*; + use crate::math::notnan; use core::iter::once; fn packed_light_test_values() -> impl Iterator { diff --git a/all-is-cubes/src/space/light/tests.rs b/all-is-cubes/src/space/light/tests.rs index 17b4828ca..41a8b6cd1 100644 --- a/all-is-cubes/src/space/light/tests.rs +++ b/all-is-cubes/src/space/light/tests.rs @@ -6,7 +6,7 @@ use super::{data::LightStatus, LightUpdatesInfo, PackedLight, Priority}; use crate::block::{self, Block, AIR}; use crate::color_block; use crate::listen::{Listen as _, Listener, Sink}; -use crate::math::{Cube, Face6, FaceMap, GridPoint, Rgb, Rgba}; +use crate::math::{rgb_const, Cube, Face6, FaceMap, GridPoint, Rgb, Rgba}; use crate::space::{GridAab, LightPhysics, Sky, Space, SpaceChange, SpacePhysics}; use crate::time; @@ -98,7 +98,7 @@ fn step() { assert_eq!(space.get_lighting([1, 0, 0]), PackedLight::NO_RAYS); assert_eq!(space.get_lighting([2, 0, 0]), PackedLight::NO_RAYS); - let (info, _) = space.step(None, time::Tick::arbitrary(), time::DeadlineStd::Whenever); + let (info, _) = space.step(None, time::Tick::arbitrary(), time::DeadlineNt::Whenever); assert_eq!( info.light, LightUpdatesInfo { @@ -280,7 +280,7 @@ fn disabled_lighting_does_not_update() { .light_needs_update(Cube::new(0, 0, 0), Priority::UNINIT); assert_eq!( space - .step(None, time::Tick::arbitrary(), time::DeadlineStd::Whenever) + .step(None, time::Tick::arbitrary(), time::DeadlineNt::Whenever) .0 .light, LightUpdatesInfo::default() diff --git a/all-is-cubes/src/space/palette.rs b/all-is-cubes/src/space/palette.rs index bed67ecc5..c2c456d57 100644 --- a/all-is-cubes/src/space/palette.rs +++ b/all-is-cubes/src/space/palette.rs @@ -573,8 +573,9 @@ pub enum PaletteError { }, } -#[cfg(feature = "std")] -impl std::error::Error for PaletteError {} +crate::util::cfg_should_impl_error! { + impl std::error::Error for PaletteError {} +} impl fmt::Display for PaletteError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { diff --git a/all-is-cubes/src/space/space_txn.rs b/all-is-cubes/src/space/space_txn.rs index 752f0b7fe..fecefe6ee 100644 --- a/all-is-cubes/src/space/space_txn.rs +++ b/all-is-cubes/src/space/space_txn.rs @@ -373,12 +373,13 @@ pub enum SpaceTransactionConflict { Behaviors(behavior::BehaviorTransactionConflict), } -#[cfg(feature = "std")] -impl std::error::Error for SpaceTransactionConflict { - fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { - match self { - SpaceTransactionConflict::Cube { conflict, .. } => Some(conflict), - SpaceTransactionConflict::Behaviors(conflict) => Some(conflict), +crate::util::cfg_should_impl_error! { + impl std::error::Error for SpaceTransactionConflict { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + match self { + SpaceTransactionConflict::Cube { conflict, .. } => Some(conflict), + SpaceTransactionConflict::Behaviors(conflict) => Some(conflict), + } } } } @@ -583,8 +584,7 @@ pub struct CubeConflict { pub(crate) new: bool, } -#[cfg(feature = "std")] -impl std::error::Error for CubeConflict {} +crate::util::cfg_should_impl_error! {impl std::error::Error for CubeConflict {}} impl fmt::Display for CubeConflict { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { diff --git a/all-is-cubes/src/space/tests.rs b/all-is-cubes/src/space/tests.rs index a79068ca5..b5d45ef4e 100644 --- a/all-is-cubes/src/space/tests.rs +++ b/all-is-cubes/src/space/tests.rs @@ -420,7 +420,7 @@ fn listens_to_block_changes() { // computations like reevaluation to happen during the notification process. assert_eq!(sink.drain(), vec![]); // Instead, it only happens the next time the space is stepped. - let (_, _) = space.step(None, Tick::arbitrary(), time::DeadlineStd::Whenever); + let (_, _) = space.step(None, Tick::arbitrary(), time::DeadlineNt::Whenever); // Now we should see a notification and the evaluated block data having changed. assert_eq!(sink.drain(), vec![SpaceChange::BlockEvaluation(0)]); assert_eq!(space.get_evaluated([0, 0, 0]), &new_evaluated); @@ -450,7 +450,7 @@ fn indirect_becomes_evaluation_error() { .unwrap(); // Step the space to let it notice. - let (_, _) = space.step(None, Tick::arbitrary(), time::DeadlineStd::Whenever); + let (_, _) = space.step(None, Tick::arbitrary(), time::DeadlineNt::Whenever); // Now we should see a notification and the evaluated block data having changed. assert_eq!(sink.drain(), vec![SpaceChange::BlockEvaluation(0)]); @@ -586,11 +586,11 @@ fn block_tick_action_does_not_run_paused() { let mut clock = time::Clock::new(time::TickSchedule::per_second(10), 0); // No effect when paused - _ = space.step(None, clock.advance(true), time::DeadlineStd::Whenever); + _ = space.step(None, clock.advance(true), time::DeadlineNt::Whenever); assert_eq!(space[[0, 0, 0]], vanisher); // Operation applied when unpaused - _ = space.step(None, clock.advance(false), time::DeadlineStd::Whenever); + _ = space.step(None, clock.advance(false), time::DeadlineNt::Whenever); assert_eq!(space[[0, 0, 0]], AIR); } @@ -631,7 +631,7 @@ fn block_tick_action_timing() { 99 }); - let (_info, step_txn) = space.step(None, clock.advance(false), time::DeadlineStd::Whenever); + let (_info, step_txn) = space.step(None, clock.advance(false), time::DeadlineNt::Whenever); // TODO: the block effect isn't a returned transaction yet but it perhaps should be. // This test will need reworking at that point. assert_eq!(step_txn, UniverseTransaction::default()); @@ -681,7 +681,7 @@ fn block_tick_action_conflict() { space.set(left, &modifies_px_neighbor).unwrap(); space.set(right, &modifies_nx_neighbor).unwrap(); - let (_info, step_txn) = space.step(None, clock.advance(false), time::DeadlineStd::Whenever); + let (_info, step_txn) = space.step(None, clock.advance(false), time::DeadlineNt::Whenever); assert_eq!(step_txn, UniverseTransaction::default()); assert_eq!( @@ -707,7 +707,7 @@ fn block_tick_action_conflict() { // should take effect. space.set(right, &AIR).unwrap(); - let (_info, step_txn) = space.step(None, clock.advance(false), time::DeadlineStd::Whenever); + let (_info, step_txn) = space.step(None, clock.advance(false), time::DeadlineNt::Whenever); assert_eq!(step_txn, UniverseTransaction::default()); assert_eq!(fluff_sink.drain(), vec![]); assert_eq!( diff --git a/all-is-cubes/src/time.rs b/all-is-cubes/src/time.rs index 1bd43e697..2787b204c 100644 --- a/all-is-cubes/src/time.rs +++ b/all-is-cubes/src/time.rs @@ -5,8 +5,8 @@ use core::fmt; #[cfg(doc)] use crate::universe::Universe; -mod deadline; -pub use deadline::*; +#[doc(inline)] +pub use all_is_cubes_base::time::*; /// Specifies an amount of time passing “in game” Home in a [`Universe`] /// and its contents. diff --git a/all-is-cubes/src/transaction.rs b/all-is-cubes/src/transaction.rs index db5afba2f..f94d1cdd8 100644 --- a/all-is-cubes/src/transaction.rs +++ b/all-is-cubes/src/transaction.rs @@ -220,17 +220,18 @@ where } } -#[cfg(feature = "std")] -impl std::error::Error for ExecuteError -where - Txn: Merge, - ::Conflict: std::error::Error + 'static, -{ - fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { - match self { - ExecuteError::Merge(e) => e.source(), - ExecuteError::Check(e) => e.source(), - ExecuteError::Commit(e) => e.source(), +crate::util::cfg_should_impl_error! { + impl std::error::Error for ExecuteError + where + Txn: Merge, + ::Conflict: std::error::Error + 'static, + { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + match self { + ExecuteError::Merge(e) => e.source(), + ExecuteError::Check(e) => e.source(), + ExecuteError::Commit(e) => e.source(), + } } } } @@ -277,8 +278,9 @@ pub struct PreconditionFailed { pub(crate) problem: &'static str, } -#[cfg(feature = "std")] -impl std::error::Error for PreconditionFailed {} +crate::util::cfg_should_impl_error! { + impl std::error::Error for PreconditionFailed {} +} /// Type of “unexpected errors” from [`Transaction::commit()`]. // @@ -343,13 +345,14 @@ impl CommitError { } } -#[cfg(feature = "std")] -impl std::error::Error for CommitError { - fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { - match &self.0 { - CommitErrorKind::Leaf { error, .. } => Some(error), - CommitErrorKind::LeafMessage { .. } => None, - CommitErrorKind::Context { error, .. } => Some(error), +crate::util::cfg_should_impl_error! { + impl std::error::Error for CommitError { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + match &self.0 { + CommitErrorKind::Leaf { error, .. } => Some(error), + CommitErrorKind::LeafMessage { .. } => None, + CommitErrorKind::Context { error, .. } => Some(error), + } } } } diff --git a/all-is-cubes/src/transaction/generic.rs b/all-is-cubes/src/transaction/generic.rs index 305992fcf..55bfac810 100644 --- a/all-is-cubes/src/transaction/generic.rs +++ b/all-is-cubes/src/transaction/generic.rs @@ -14,10 +14,11 @@ pub struct MapConflict { pub conflict: C, } -#[cfg(feature = "std")] -impl std::error::Error for MapConflict { - fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { - Some(&self.conflict) +crate::util::cfg_should_impl_error! { + impl std::error::Error for MapConflict { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + Some(&self.conflict) + } } } diff --git a/all-is-cubes/src/universe.rs b/all-is-cubes/src/universe.rs index 5e5b91a8b..760f7f59e 100644 --- a/all-is-cubes/src/universe.rs +++ b/all-is-cubes/src/universe.rs @@ -782,8 +782,9 @@ pub enum InsertErrorKind { AlreadyInserted, } -#[cfg(feature = "std")] -impl std::error::Error for InsertError {} +crate::util::cfg_should_impl_error! { + impl std::error::Error for InsertError {} +} impl fmt::Display for InsertError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { diff --git a/all-is-cubes/src/universe/handle.rs b/all-is-cubes/src/universe/handle.rs index f8861612c..f725e703e 100644 --- a/all-is-cubes/src/universe/handle.rs +++ b/all-is-cubes/src/universe/handle.rs @@ -515,8 +515,9 @@ pub enum HandleError { NotReady(Name), } -#[cfg(feature = "std")] -impl std::error::Error for HandleError {} +crate::util::cfg_should_impl_error! { + impl std::error::Error for HandleError {} +} /// Read access to the referent of a [`Handle`]. /// diff --git a/all-is-cubes/src/universe/members.rs b/all-is-cubes/src/universe/members.rs index 9d36ab6e7..5dfaa62d3 100644 --- a/all-is-cubes/src/universe/members.rs +++ b/all-is-cubes/src/universe/members.rs @@ -374,12 +374,13 @@ macro_rules! member_enums_and_impls { )* } - #[cfg(feature = "std")] - impl std::error::Error for AnyTransactionConflict { - fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { - match self { - Self::Mismatch => None, - $( Self::$member_type(e) => Some(e), )* + crate::util::cfg_should_impl_error! { + impl std::error::Error for AnyTransactionConflict { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + match self { + Self::Mismatch => None, + $( Self::$member_type(e) => Some(e), )* + } } } } diff --git a/all-is-cubes/src/universe/tests.rs b/all-is-cubes/src/universe/tests.rs index e1b44bb35..15fa578a4 100644 --- a/all-is-cubes/src/universe/tests.rs +++ b/all-is-cubes/src/universe/tests.rs @@ -330,9 +330,9 @@ fn delete_wrong_universe_fails() { fn step_time() { let mut u = Universe::new(); assert_eq!(u.session_step_time, 0); - u.step(false, time::DeadlineStd::Whenever); + u.step(false, time::DeadlineNt::Whenever); assert_eq!(u.session_step_time, 1); - u.step(true, time::DeadlineStd::Whenever); + u.step(true, time::DeadlineNt::Whenever); assert_eq!(u.session_step_time, 1); } @@ -370,13 +370,13 @@ fn universe_behavior() { dbg!(&u); assert!(u.get_any(&"foo".into()).is_none()); - u.step(false, time::DeadlineStd::Whenever); + u.step(false, time::DeadlineNt::Whenever); // After stepping, the behavior should have done its thing assert!(u.get_any(&"foo".into()).is_some()); // A further step should not fail since the behavior removed itself - u.step(false, time::DeadlineStd::Whenever); + u.step(false, time::DeadlineNt::Whenever); } #[test] @@ -393,7 +393,7 @@ fn gc_implicit() { let mut u = Universe::new(); u.insert_anonymous(BlockDef::new(AIR)); assert_eq!(1, u.iter_by_type::().count()); - u.step(false, time::DeadlineStd::Whenever); + u.step(false, time::DeadlineNt::Whenever); assert_eq!(0, u.iter_by_type::().count()); } diff --git a/all-is-cubes/src/universe/universe_txn.rs b/all-is-cubes/src/universe/universe_txn.rs index 5236cafa9..4ccd1c41b 100644 --- a/all-is-cubes/src/universe/universe_txn.rs +++ b/all-is-cubes/src/universe/universe_txn.rs @@ -234,13 +234,14 @@ pub enum UniverseConflict { Behaviors(behavior::BehaviorTransactionConflict), } -#[cfg(feature = "std")] -impl std::error::Error for UniverseConflict { - fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { - match self { - UniverseConflict::DifferentUniverse(_, _) => None, - UniverseConflict::Member(mc) => Some(&mc.conflict), - UniverseConflict::Behaviors(c) => Some(c), +crate::util::cfg_should_impl_error! { + impl std::error::Error for UniverseConflict { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + match self { + UniverseConflict::DifferentUniverse(_, _) => None, + UniverseConflict::Member(mc) => Some(&mc.conflict), + UniverseConflict::Behaviors(c) => Some(c), + } } } } @@ -783,13 +784,14 @@ pub enum MemberConflict { Modify(ModifyMemberConflict), } -#[cfg(feature = "std")] -impl std::error::Error for MemberConflict { - fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { - match self { - MemberConflict::InsertVsOther => None, - MemberConflict::DeleteVsOther => None, - MemberConflict::Modify(e) => e.source(), +crate::util::cfg_should_impl_error! { + impl std::error::Error for MemberConflict { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + match self { + MemberConflict::InsertVsOther => None, + MemberConflict::DeleteVsOther => None, + MemberConflict::Modify(e) => e.source(), + } } } } @@ -803,10 +805,11 @@ impl std::error::Error for MemberConflict { #[derive(Clone, Debug, Eq, PartialEq)] pub struct ModifyMemberConflict(AnyTransactionConflict); -#[cfg(feature = "std")] -impl std::error::Error for ModifyMemberConflict { - fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { - self.0.source() +crate::util::cfg_should_impl_error! { + impl std::error::Error for ModifyMemberConflict { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + self.0.source() + } } } diff --git a/all-is-cubes/src/util.rs b/all-is-cubes/src/util.rs index 138dd94fa..bafe39985 100644 --- a/all-is-cubes/src/util.rs +++ b/all-is-cubes/src/util.rs @@ -1,325 +1,36 @@ //! Tools that we could imagine being in the Rust standard library, but aren't. -use core::fmt; -use core::marker::PhantomData; -use core::ops::AddAssign; -use core::time::Duration; - -// Note that this is not the `maybe_sync::BoxFuture`! -use futures_core::future::BoxFuture as SyncBoxFuture; - -mod custom_format; -pub use custom_format::*; - -#[doc(hidden)] // public to allow our other crates to match, only -pub mod maybe_sync; - #[doc(no_inline)] pub use yield_progress::{Builder as YieldProgressBuilder, YieldProgress}; #[doc(no_inline)] pub use manyfmt::{refmt, Fmt, Refmt}; -#[doc(hidden)] -pub fn yield_progress_for_testing() -> YieldProgress { - // Theoretically we should use Tokio's yield function, but it shouldn't matter for - // tests and I don't want the dependency here. - yield_progress::Builder::new().build() -} - -/// Interface to start concurrent tasks. -/// -/// In the typical case, applications making use of All is Cubes libraries provide an implementation -/// of this trait to functions which can make use of it. -/// -/// Executors should generally implement `Clone`. -pub trait Executor: fmt::Debug + Send + Sync { - /// Create a set of tasks which runds the provided `future`, if possible. - /// - /// The given `task_factory` is called some number of times appropriate to the available - /// parallelism. If only single-threaded asynchronous execution is supported, it will be called - /// once. It may be called zero times; callers must be able to complete their work without the - /// assistance of these tasks. - /// - /// The future **must periodically yield** by calling [`Executor::yield_now()`]. - /// Otherwise, it may prevent other tasks, even “foreground” ones, from progressing. - /// This requirement is for the benefit of single-threaded [`Executor`]s. - fn spawn_background(&self, task_factory: &mut dyn FnMut() -> SyncBoxFuture<'static, ()>); - - /// Grants an opportunity for other tasks to execute instead of the current one. - /// - /// This should only be performed from inside of a [`Executor::spawn_background()`] task. - /// If it is called (or polled) under other circumstances, it may panic or have negative - /// effects on task scheduling. - //--- - // If Rust ever gets object-safe async fn in trait without boxing, use it here. - fn yield_now(&self) -> SyncBoxFuture<'static, ()>; -} -impl Executor for &T { - fn spawn_background(&self, task_factory: &mut dyn FnMut() -> SyncBoxFuture<'static, ()>) { - (**self).spawn_background(task_factory) - } - fn yield_now(&self) -> futures_util::future::BoxFuture<'static, ()> { - (**self).yield_now() - } -} -impl Executor for alloc::sync::Arc { - fn spawn_background(&self, task_factory: &mut dyn FnMut() -> SyncBoxFuture<'static, ()>) { - (**self).spawn_background(task_factory) - } - fn yield_now(&self) -> futures_util::future::BoxFuture<'static, ()> { - (**self).yield_now() - } -} -/// No-op executor for applications which cannot provide one. -impl Executor for () { - fn spawn_background(&self, _: &mut dyn FnMut() -> SyncBoxFuture<'static, ()>) {} - fn yield_now(&self) -> futures_util::future::BoxFuture<'static, ()> { - unreachable!( - "yield_now() should only be called from a task, \ - and this executor does not support tasks" - ) - } -} - +// Unfortunately, we can't use a glob re-export here or `ErrorIfStd` ends up visible when it +// shouldn't be, mysteriously. So, explicit everything instead, with their various visibilities +// and cfgs. #[cfg(feature = "std")] #[doc(hidden)] -pub use error_chain::ErrorChain; -#[cfg(feature = "std")] -mod error_chain { - use core::fmt; - use std::error::Error; - - /// Formatting wrapper which prints an [`Error`] together with its - /// `source()` chain, with at least one newline between each. - /// - /// The text begins with the [`fmt::Display`] format of the error. - /// - /// Design note: This is not a [`manyfmt::Fmt`] because that has a blanket implementation - /// which interferes with this one for [`Error`]. - #[doc(hidden)] // not something we wish to be stable public API - #[derive(Clone, Copy, Debug)] - #[allow(clippy::exhaustive_structs)] - pub struct ErrorChain<'a>(pub &'a (dyn Error + 'a)); - - impl fmt::Display for ErrorChain<'_> { - fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { - format_error_chain(fmt, self.0) - } - } - fn format_error_chain( - fmt: &mut fmt::Formatter<'_>, - mut error: &(dyn Error + '_), - ) -> fmt::Result { - // Write the error's own message. This is expected NOT to contain the sources itself. - write!(fmt, "{error}")?; - - while let Some(source) = error.source() { - error = source; - write!(fmt, "\n\nCaused by:\n {error}")?; - } - - Ok(()) - } -} - -cfg_if::cfg_if! { - if #[cfg(feature = "std")] { - /// Alias for [`std::error::Error`] that is a substitute when not on `std`. - #[doc(hidden)] - pub use std::error::Error as ErrorIfStd; - } else { - use alloc::boxed::Box; - - /// Substitute for [`std::error::Error`] with the same supertraits but no methods. - #[doc(hidden)] - pub trait ErrorIfStd: fmt::Debug + fmt::Display {} - impl ErrorIfStd for T where T: fmt::Debug + fmt::Display {} - - impl From<&str> for Box { - fn from(s: &str) -> Self { - Box::new(alloc::string::String::from(s)) - } - } - } -} - -/// Equivalent of [`Iterator::map`] but applied to an [`Extend`] instead, transforming -/// the incoming elements. -/// -/// TODO: this is only used by the wireframe debug mesh mechanism and should be reconsidered -#[doc(hidden)] // pub to be used by all-is-cubes-gpu -#[derive(Debug)] -pub struct MapExtend<'a, A, B, T, F> -where - T: Extend, - F: Fn(A) -> B, -{ - target: &'a mut T, - function: F, - _input: PhantomData, -} - -impl<'a, A, B, T, F> MapExtend<'a, A, B, T, F> -where - T: Extend, - F: Fn(A) -> B, -{ - pub fn new(target: &'a mut T, function: F) -> Self { - Self { - target, - function, - _input: PhantomData, - } - } -} - -impl<'a, A, B, T, F> Extend for MapExtend<'a, A, B, T, F> -where - T: Extend, - F: Fn(A) -> B, -{ - fn extend(&mut self, iter: I) - where - I: IntoIterator, - { - self.target.extend(iter.into_iter().map(&self.function)); - } -} - -/// Aggregation of the time taken by a set of events. -/// -/// TODO: Consider including an identifier for the longest. -/// TODO: Consider generalizing this to quantities other than time? Probably not. -#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)] -#[non_exhaustive] -pub struct TimeStats { - /// The number of events aggregated into this [`TimeStats`]. - pub count: usize, - /// The sum of the durations of all events. - pub sum: Duration, - /// The minimum duration of all events, or [`None`] if there were no events. - pub min: Option, - /// The maximum duration of all events, or [`Duration::ZERO`] if there were no events. - pub max: Duration, -} - -impl TimeStats { - /// Constructs a [`TimeStats`] for a single event. - /// - /// Multiple of these may then be aggregated using the `+=` operator. - pub const fn one(duration: Duration) -> Self { - Self { - count: 1, - sum: duration, - min: Some(duration), - max: duration, - } - } - - /// Record an event based on the given previous time and current time, then update - /// the previous time value. - /// - /// Returns the duration that was recorded. - #[doc(hidden)] // for now, not making writing conveniences public - pub fn record_consecutive_interval( - &mut self, - last_marked_instant: &mut I, - now: I, - ) -> Duration { - let previous = *last_marked_instant; - *last_marked_instant = now; - - let duration = now.saturating_duration_since(previous); - *self += Self::one(duration); - duration - } -} - -impl AddAssign for TimeStats { - fn add_assign(&mut self, rhs: Self) { - *self = TimeStats { - count: self.count + rhs.count, - sum: self.sum + rhs.sum, - min: self.min.map_or(rhs.min, |value| Some(value.min(rhs.min?))), - max: self.max.max(rhs.max), - }; - } -} - -impl fmt::Display for TimeStats { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self.min { - None => write!( - f, - "(-------- .. {}) for {:3}, total {}", - self.max.refmt(&ConciseDebug), - self.count, - self.sum.refmt(&ConciseDebug), - ), - Some(min) => write!( - f, - "({} .. {}) for {:3}, total {}", - min.refmt(&ConciseDebug), - self.max.refmt(&ConciseDebug), - self.count, - self.sum.refmt(&ConciseDebug), - ), - } - } -} - -#[doc(hidden)] // for use in internal tests only -pub fn assert_send_sync() { - // We don't need to do anything in this function; the call to it having been successfully - // compiled is the assertion. -} - -// Assert `Send + Sync` only if the `std` feature is active. -#[cfg(feature = "std")] -#[doc(hidden)] // for use in internal tests only -pub fn assert_conditional_send_sync() {} -#[cfg(not(feature = "std"))] -#[doc(hidden)] // for use in internal tests only -pub fn assert_conditional_send_sync() {} - -#[cfg(test)] -mod tests { - use super::*; - - fn _assert_executor_trait_is_object_safe(_: &dyn Executor) {} +pub use all_is_cubes_base::util::ErrorChain; +#[doc(hidden)] +pub use all_is_cubes_base::util::{ + assert_conditional_send_sync, assert_send_sync, ErrorIfStd, MapExtend, TypeName, +}; +pub use all_is_cubes_base::util::{ConciseDebug, Executor, TimeStats}; - #[test] - #[cfg(feature = "std")] - fn error_chain() { - use std::error::Error; - use std::fmt; +// macros can only be exported from their defining crate at the root, but we can fix the path here +#[doc(hidden)] +pub use all_is_cubes_base::cfg_should_impl_error; - #[derive(Debug)] - struct TestError1; - impl Error for TestError1 {} - impl fmt::Display for TestError1 { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "TestError1") - } - } +#[doc(hidden)] // public to allow our other crates to match, only +pub mod maybe_sync; - #[derive(Debug)] - struct TestError2(TestError1); - impl Error for TestError2 { - fn source(&self) -> Option<&(dyn Error + 'static)> { - Some(&self.0) - } - } - impl fmt::Display for TestError2 { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "TestError2") - } - } +mod status_text; +pub use status_text::*; - assert_eq!( - format!("{}", ErrorChain(&TestError2(TestError1))), - "TestError2\n\nCaused by:\n TestError1" - ); - } +#[doc(hidden)] +pub fn yield_progress_for_testing() -> YieldProgress { + // Theoretically we should use Tokio's yield function, but it shouldn't matter for + // tests and I don't want the dependency here. + yield_progress::Builder::new().build() } diff --git a/all-is-cubes/src/util/status_text.rs b/all-is-cubes/src/util/status_text.rs new file mode 100644 index 000000000..129e40d53 --- /dev/null +++ b/all-is-cubes/src/util/status_text.rs @@ -0,0 +1,74 @@ +use core::fmt; + +use manyfmt::Fmt; + +use all_is_cubes_base::util::ConciseDebug; + +/// Format type for [`manyfmt::Fmt`] which provides an highly condensed, ideally constant-width +/// or constant-height, user-facing format for live-updating textual status messages. +/// This format does not follow Rust [`fmt::Debug`] syntax, and when implemented +/// for standard Rust types may have quirks. Values may have multiple lines. +#[allow(clippy::exhaustive_structs)] +#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq)] +pub struct StatusText { + /// Types of information to include or exclude. + pub show: ShowStatus, +} + +impl StatusText { + #[allow(missing_docs)] + pub const ALL: Self = Self { + show: ShowStatus::all(), + }; +} + +bitflags::bitflags! { + /// Different kinds of information which [`StatusText`] may include or exclude. + /// + /// I apologize for this being so specific to All is Cubes internals. + #[derive(Clone, Copy, Debug, Hash, Eq, Ord, PartialEq, PartialOrd)] + // TODO: deserialization should be lenient and ignore unknown textual flags + #[cfg_attr(feature = "save", derive(serde::Serialize, serde::Deserialize))] + pub struct ShowStatus: u32 { + /// The “game world” universe (not the UI). + const WORLD = 1 << 0; + /// The UI universe (not the game world). + const UI = 1 << 1; + + /// Simulation; advancing time. + const STEP = 1 << 2; + /// Drawing the current state. + const RENDER = 1 << 3; + /// Information about what the cursor is targeting. + const CURSOR = 1 << 4; + + /// Things related to the processing of blocks. + const BLOCK = 1 << 5; + /// Things related to character control and physics. + const CHARACTER = 1 << 6; + /// Things related to [`Space`](crate::space::Space)s. + const SPACE = 1 << 7; + } +} + +impl ShowStatus { + #[doc(hidden)] // just a substitute for const trait impl + pub const DEFAULT: Self = Self::WORLD + .union(Self::STEP) + .union(Self::RENDER) + .union(Self::CURSOR); +} + +impl Default for ShowStatus { + /// A partial set of flags which makes a reasonable default for introducing users to the + /// status text. + fn default() -> Self { + Self::DEFAULT + } +} + +impl Fmt for core::time::Duration { + fn fmt(&self, fmt: &mut fmt::Formatter<'_>, _: &StatusText) -> fmt::Result { + >::fmt(self, fmt, &ConciseDebug) + } +} diff --git a/test-renderers/src/test_cases.rs b/test-renderers/src/test_cases.rs index 85e334cf5..6497e032e 100644 --- a/test-renderers/src/test_cases.rs +++ b/test-renderers/src/test_cases.rs @@ -17,18 +17,18 @@ use all_is_cubes::camera::{ ViewTransform, Viewport, }; use all_is_cubes::character::{Character, Spawn}; +use all_is_cubes::color_block; use all_is_cubes::euclid::{point3, size2, size3, vec2, vec3, Point2D, Size2D, Vector3D}; use all_is_cubes::listen::{ListenableCell, ListenableSource}; use all_is_cubes::math::{ - Axis, Cube, Face6, FreeCoordinate, GridAab, GridCoordinate, GridPoint, GridRotation, - GridVector, NotNan, Rgb, Rgba, VectorOps, Vol, + notnan, rgb_const, rgba_const, Axis, Cube, Face6, FreeCoordinate, GridAab, GridCoordinate, + GridPoint, GridRotation, GridVector, NotNan, Rgb, Rgba, VectorOps, Vol, }; use all_is_cubes::space::{self, LightPhysics, Space, SpaceBuilder}; use all_is_cubes::time; use all_is_cubes::transaction::{self, Transaction as _}; use all_is_cubes::universe::{Handle, HandleError, Universe, UniverseTransaction}; use all_is_cubes::util::yield_progress_for_testing; -use all_is_cubes::{color_block, notnan, rgb_const, rgba_const}; use all_is_cubes_content::{make_some_voxel_blocks, palette, UniverseTemplate}; use crate::{ @@ -780,9 +780,9 @@ async fn sky(mut context: RenderTestContext, face: Face6) { let [block] = make_some_voxel_blocks(&mut universe); let [r, g, b] = [ - palette::UNIFORM_LUMINANCE_RED, - palette::UNIFORM_LUMINANCE_GREEN, - palette::UNIFORM_LUMINANCE_BLUE, + Rgb::UNIFORM_LUMINANCE_RED, + Rgb::UNIFORM_LUMINANCE_GREEN, + Rgb::UNIFORM_LUMINANCE_BLUE, ]; // axis-colored sky (+x has red and -x has no red, and so on) to disambiguate // all directions diff --git a/tools/xtask/src/xtask.rs b/tools/xtask/src/xtask.rs index 46ba58568..a67603bec 100644 --- a/tools/xtask/src/xtask.rs +++ b/tools/xtask/src/xtask.rs @@ -448,8 +448,9 @@ impl Scope { /// TODO: fetch this list (or at least cross-check it) using `cargo metadata`. /// /// See also [`Config::do_for_all_workspaces`]. -const ALL_NONTEST_PACKAGES: [&str; 9] = [ +const ALL_NONTEST_PACKAGES: [&str; 10] = [ "all-is-cubes", + "all-is-cubes-base", "all-is-cubes-ui", "all-is-cubes-mesh", "all-is-cubes-gpu",