From 45a772df73582609cbeb57d69397122bff72db94 Mon Sep 17 00:00:00 2001 From: Kevin Reid Date: Wed, 1 May 2024 17:29:32 -0700 Subject: [PATCH] Split out new crate `all-is-cubes-base`. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit It contains the majority of code from the `math` and `util` modules of `all-is-cubes`. This new crate will enable us to use `raycast` in the build script of `all-is-cubes` to generate fully pre-calculated light propagation data. It may also assist in further splitting of `all-is-cubes` into separately compilable parts, if I find such divisions worth making. I chose the name “base” to be sufficiently abstract that it will not become unsuitable for its purpose (being the crate that all others eventually depend on) regardless of exactly what it evolves to contain. I also considered “all-is-cubes-core”, but I feel that only the crate containing `Block` and `Space` deserves that name if I were to use it. Also, because it's now possible and convenient, the macros `notnan`, `rgb_const`, and `rgba_const` have been moved to `all_is_cubes::math`. (This is only possible because macro re-exports can be exported from specific modules, even though `macro_rules` declarations can only be exported from the crate root.) --- CHANGELOG.md | 2 + Cargo.lock | 30 +- Cargo.toml | 3 + all-is-cubes-base/Cargo.toml | 54 +++ all-is-cubes-base/LICENSE-APACHE | 1 + all-is-cubes-base/LICENSE-MIT | 1 + all-is-cubes-base/README.md | 21 + all-is-cubes-base/src/lib.rs | 32 ++ all-is-cubes-base/src/math.rs | 176 +++++++++ .../src/math/aab.rs | 13 +- .../src/math/axis.rs | 9 +- .../src/math/color.rs | 84 +++- .../src/math/coord.rs | 0 .../src/math/cube.rs | 5 +- .../src/math/face.rs | 52 ++- .../src/math/grid_aab.rs | 57 +-- .../src/math/grid_iter.rs | 6 +- .../src/math/matrix.rs | 7 +- .../src/math/rigid.rs | 1 + .../src/math/rotation.rs | 16 +- all-is-cubes-base/src/math/serde_impls.rs | 85 +++++ .../src/math/vol.rs | 16 +- .../src}/resolution.rs | 31 +- all-is-cubes-base/src/serde-warning.md | 10 + .../src/time.rs | 0 all-is-cubes-base/src/util.rs | 339 +++++++++++++++++ .../src/util/custom_format.rs | 71 +--- all-is-cubes-content/src/animation.rs | 4 +- all-is-cubes-content/src/atrium.rs | 12 +- all-is-cubes-content/src/blocks.rs | 6 +- all-is-cubes-content/src/city/exhibits.rs | 6 +- all-is-cubes-content/src/fractal.rs | 5 +- all-is-cubes-content/src/landscape.rs | 3 +- all-is-cubes-gpu/src/in_wgpu.rs | 4 +- .../src/in_wgpu/shader_testing.rs | 4 +- all-is-cubes-gpu/src/in_wgpu/space.rs | 6 +- all-is-cubes-mesh/src/block_mesh/analyze.rs | 4 +- all-is-cubes-mesh/src/block_mesh/planar.rs | 3 +- .../src/dynamic/chunked_mesh/tests.rs | 4 +- all-is-cubes-mesh/src/tests.rs | 5 +- all-is-cubes-port/src/stl.rs | 3 +- all-is-cubes-ui/src/apps/input.rs | 3 +- all-is-cubes-ui/src/ui_content/options.rs | 3 +- all-is-cubes-wasm/Cargo.lock | 36 +- all-is-cubes/Cargo.toml | 17 +- all-is-cubes/src/behavior.rs | 2 +- all-is-cubes/src/block.rs | 30 +- all-is-cubes/src/block/block_def.rs | 2 +- all-is-cubes/src/block/evaluation.rs | 2 +- all-is-cubes/src/block/modifier/move.rs | 2 +- all-is-cubes/src/block/tests.rs | 4 +- all-is-cubes/src/block/text.rs | 4 +- all-is-cubes/src/camera/graphics_options.rs | 5 +- all-is-cubes/src/camera/renderer.rs | 2 +- all-is-cubes/src/camera/tests.rs | 2 +- all-is-cubes/src/character.rs | 2 +- all-is-cubes/src/content.rs | 2 +- all-is-cubes/src/content/palette.rs | 46 +-- all-is-cubes/src/drawing.rs | 17 +- all-is-cubes/src/inv/icons.rs | 3 +- all-is-cubes/src/inv/inventory.rs | 2 +- all-is-cubes/src/inv/tool.rs | 2 +- all-is-cubes/src/lib.rs | 8 +- all-is-cubes/src/linking.rs | 6 +- all-is-cubes/src/math.rs | 184 +-------- all-is-cubes/src/op.rs | 2 +- all-is-cubes/src/raytracer.rs | 5 +- all-is-cubes/src/raytracer/accum.rs | 2 +- all-is-cubes/src/raytracer/surface.rs | 2 +- all-is-cubes/src/rerun_glue.rs | 53 +-- all-is-cubes/src/save/conversion.rs | 74 ---- all-is-cubes/src/save/schema.rs | 14 - all-is-cubes/src/save/tests.rs | 2 +- all-is-cubes/src/space.rs | 5 +- all-is-cubes/src/space/light/data.rs | 1 + all-is-cubes/src/space/light/tests.rs | 2 +- all-is-cubes/src/space/palette.rs | 2 +- all-is-cubes/src/space/space_txn.rs | 4 +- all-is-cubes/src/time.rs | 4 +- all-is-cubes/src/transaction.rs | 6 +- all-is-cubes/src/transaction/generic.rs | 2 +- all-is-cubes/src/universe.rs | 2 +- all-is-cubes/src/universe/handle.rs | 2 +- all-is-cubes/src/universe/members.rs | 2 +- all-is-cubes/src/universe/universe_txn.rs | 6 +- all-is-cubes/src/util.rs | 359 +----------------- all-is-cubes/src/util/status_text.rs | 74 ++++ test-renderers/src/test_cases.rs | 12 +- tools/xtask/src/xtask.rs | 3 +- 89 files changed, 1245 insertions(+), 972 deletions(-) create mode 100644 all-is-cubes-base/Cargo.toml create mode 120000 all-is-cubes-base/LICENSE-APACHE create mode 120000 all-is-cubes-base/LICENSE-MIT create mode 100644 all-is-cubes-base/README.md create mode 100644 all-is-cubes-base/src/lib.rs create mode 100644 all-is-cubes-base/src/math.rs rename {all-is-cubes => all-is-cubes-base}/src/math/aab.rs (97%) rename {all-is-cubes => all-is-cubes-base}/src/math/axis.rs (94%) rename {all-is-cubes => all-is-cubes-base}/src/math/color.rs (90%) rename {all-is-cubes => all-is-cubes-base}/src/math/coord.rs (100%) rename {all-is-cubes => all-is-cubes-base}/src/math/cube.rs (98%) rename {all-is-cubes => all-is-cubes-base}/src/math/face.rs (95%) rename {all-is-cubes => all-is-cubes-base}/src/math/grid_aab.rs (95%) rename {all-is-cubes => all-is-cubes-base}/src/math/grid_iter.rs (97%) rename {all-is-cubes => all-is-cubes-base}/src/math/matrix.rs (97%) rename {all-is-cubes => all-is-cubes-base}/src/math/rigid.rs (99%) rename {all-is-cubes => all-is-cubes-base}/src/math/rotation.rs (97%) create mode 100644 all-is-cubes-base/src/math/serde_impls.rs rename {all-is-cubes => all-is-cubes-base}/src/math/vol.rs (98%) rename {all-is-cubes/src/block => all-is-cubes-base/src}/resolution.rs (84%) create mode 100644 all-is-cubes-base/src/serde-warning.md rename all-is-cubes/src/time/deadline.rs => all-is-cubes-base/src/time.rs (100%) create mode 100644 all-is-cubes-base/src/util.rs rename {all-is-cubes => all-is-cubes-base}/src/util/custom_format.rs (56%) create mode 100644 all-is-cubes/src/util/status_text.rs 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 e0c684225..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,7 +713,7 @@ pub struct VolLengthError { bounds: GridAab, } -cfg_should_impl_error! { +crate::util::cfg_should_impl_error! { impl std::error::Error for VolLengthError {} } diff --git a/all-is-cubes/src/block/resolution.rs b/all-is-cubes-base/src/resolution.rs similarity index 84% rename from all-is-cubes/src/block/resolution.rs rename to all-is-cubes-base/src/resolution.rs index c27961702..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,7 +187,7 @@ impl<'de> serde::Deserialize<'de> for Resolution { #[derive(Debug, Clone, Copy, Eq, Hash, PartialEq)] pub struct IntoResolutionError(N); -cfg_should_impl_error! { +crate::util::cfg_should_impl_error! { impl std::error::Error for IntoResolutionError {} } 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-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 dd3426fcb..0c0ac2061 100644 --- a/all-is-cubes/src/behavior.rs +++ b/all-is-cubes/src/behavior.rs @@ -773,7 +773,7 @@ pub struct BehaviorTransactionConflict { key: Key, } -cfg_should_impl_error! { +crate::util::cfg_should_impl_error! { impl std::error::Error for BehaviorTransactionConflict {} } 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 d74b1d3b0..7d44c2460 100644 --- a/all-is-cubes/src/block/block_def.rs +++ b/all-is-cubes/src/block/block_def.rs @@ -388,7 +388,7 @@ pub struct BlockDefConflict { pub(crate) new: bool, } -cfg_should_impl_error! { +crate::util::cfg_should_impl_error! { impl std::error::Error for BlockDefConflict {} } diff --git a/all-is-cubes/src/block/evaluation.rs b/all-is-cubes/src/block/evaluation.rs index 7b2139c46..7c46b2e1c 100644 --- a/all-is-cubes/src/block/evaluation.rs +++ b/all-is-cubes/src/block/evaluation.rs @@ -253,7 +253,7 @@ pub(in crate::block) enum InEvalError { Handle(HandleError), } -cfg_should_impl_error! { +crate::util::cfg_should_impl_error! { impl std::error::Error for EvalBlockError { fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { match self { diff --git a/all-is-cubes/src/block/modifier/move.rs b/all-is-cubes/src/block/modifier/move.rs index 677d01191..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; 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 96d413339..a8d687703 100644 --- a/all-is-cubes/src/camera/renderer.rs +++ b/all-is-cubes/src/camera/renderer.rs @@ -55,7 +55,7 @@ pub enum RenderError { // TODO: add errors for out of memory, lost GPU, etc. } -cfg_should_impl_error! { +crate::util::cfg_should_impl_error! { impl std::error::Error for RenderError { fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { match self { 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 424c7be99..b04225280 100644 --- a/all-is-cubes/src/character.rs +++ b/all-is-cubes/src/character.rs @@ -791,7 +791,7 @@ pub enum CharacterTransactionConflict { Behaviors(behavior::BehaviorTransactionConflict), } -cfg_should_impl_error! { +crate::util::cfg_should_impl_error! { impl std::error::Error for CharacterTransactionConflict { fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { match self { 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 f51f0c660..e70496e92 100644 --- a/all-is-cubes/src/inv/inventory.rs +++ b/all-is-cubes/src/inv/inventory.rs @@ -480,7 +480,7 @@ pub enum InventoryConflict { ReplaceSameSlot { slot: usize }, } -cfg_should_impl_error! { +crate::util::cfg_should_impl_error! { impl std::error::Error for InventoryConflict {} } diff --git a/all-is-cubes/src/inv/tool.rs b/all-is-cubes/src/inv/tool.rs index d5ac075e1..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_should_impl_error! { +crate::util::cfg_should_impl_error! { impl std::error::Error for ToolError { fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { match self { diff --git a/all-is-cubes/src/lib.rs b/all-is-cubes/src/lib.rs index a1e8e3b05..eae97a198 100644 --- a/all-is-cubes/src/lib.rs +++ b/all-is-cubes/src/lib.rs @@ -154,12 +154,6 @@ extern crate std; #[macro_use] extern crate alloc; -#[macro_use] -pub mod util; - -#[macro_use] -pub mod math; - pub mod behavior; pub mod block; pub mod camera; @@ -174,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; @@ -186,6 +181,7 @@ pub mod space; pub mod time; pub mod transaction; pub mod universe; +pub mod util; /// Re-export the version of the `arcstr` string type library we're using. pub use arcstr; diff --git a/all-is-cubes/src/linking.rs b/all-is-cubes/src/linking.rs index 86ccd9aa8..1c4b3f7f8 100644 --- a/all-is-cubes/src/linking.rs +++ b/all-is-cubes/src/linking.rs @@ -307,7 +307,7 @@ pub struct ProviderError { missing: Box<[Name]>, } -cfg_should_impl_error! { +crate::util::cfg_should_impl_error! { impl std::error::Error for ProviderError {} } @@ -321,7 +321,7 @@ pub struct GenError { for_object: Option, } -cfg_should_impl_error! { +crate::util::cfg_should_impl_error! { impl std::error::Error for GenError { fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { Some(&self.detail) @@ -421,7 +421,7 @@ impl InGenError { } } -cfg_should_impl_error! { +crate::util::cfg_should_impl_error! { impl std::error::Error for InGenError { fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { match self { 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 715994f89..0e150c918 100644 --- a/all-is-cubes/src/op.rs +++ b/all-is-cubes/src/op.rs @@ -113,7 +113,7 @@ impl VisitHandles for Operation { #[non_exhaustive] pub(crate) enum OperationError {} -cfg_should_impl_error! { +crate::util::cfg_should_impl_error! { impl std::error::Error for OperationError {} } 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 d19027672..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}; diff --git a/all-is-cubes/src/space.rs b/all-is-cubes/src/space.rs index 08e0d9129..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,7 +1163,7 @@ pub enum SetCubeError { TooManyBlocks(), } -cfg_should_impl_error! { +crate::util::cfg_should_impl_error! { impl std::error::Error for SetCubeError {} } 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 5243c7459..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; diff --git a/all-is-cubes/src/space/palette.rs b/all-is-cubes/src/space/palette.rs index ca43ebb39..c2c456d57 100644 --- a/all-is-cubes/src/space/palette.rs +++ b/all-is-cubes/src/space/palette.rs @@ -573,7 +573,7 @@ pub enum PaletteError { }, } -cfg_should_impl_error! { +crate::util::cfg_should_impl_error! { impl std::error::Error for PaletteError {} } diff --git a/all-is-cubes/src/space/space_txn.rs b/all-is-cubes/src/space/space_txn.rs index 24810b11a..fecefe6ee 100644 --- a/all-is-cubes/src/space/space_txn.rs +++ b/all-is-cubes/src/space/space_txn.rs @@ -373,7 +373,7 @@ pub enum SpaceTransactionConflict { Behaviors(behavior::BehaviorTransactionConflict), } -cfg_should_impl_error! { +crate::util::cfg_should_impl_error! { impl std::error::Error for SpaceTransactionConflict { fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { match self { @@ -584,7 +584,7 @@ pub struct CubeConflict { pub(crate) new: bool, } -cfg_should_impl_error! {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/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 aca1252ba..f94d1cdd8 100644 --- a/all-is-cubes/src/transaction.rs +++ b/all-is-cubes/src/transaction.rs @@ -220,7 +220,7 @@ where } } -cfg_should_impl_error! { +crate::util::cfg_should_impl_error! { impl std::error::Error for ExecuteError where Txn: Merge, @@ -278,7 +278,7 @@ pub struct PreconditionFailed { pub(crate) problem: &'static str, } -cfg_should_impl_error! { +crate::util::cfg_should_impl_error! { impl std::error::Error for PreconditionFailed {} } @@ -345,7 +345,7 @@ impl CommitError { } } -cfg_should_impl_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 { diff --git a/all-is-cubes/src/transaction/generic.rs b/all-is-cubes/src/transaction/generic.rs index f897a1489..55bfac810 100644 --- a/all-is-cubes/src/transaction/generic.rs +++ b/all-is-cubes/src/transaction/generic.rs @@ -14,7 +14,7 @@ pub struct MapConflict { pub conflict: C, } -cfg_should_impl_error! { +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 015dadf5f..760f7f59e 100644 --- a/all-is-cubes/src/universe.rs +++ b/all-is-cubes/src/universe.rs @@ -782,7 +782,7 @@ pub enum InsertErrorKind { AlreadyInserted, } -cfg_should_impl_error! { +crate::util::cfg_should_impl_error! { impl std::error::Error for InsertError {} } diff --git a/all-is-cubes/src/universe/handle.rs b/all-is-cubes/src/universe/handle.rs index 0c010a82f..f725e703e 100644 --- a/all-is-cubes/src/universe/handle.rs +++ b/all-is-cubes/src/universe/handle.rs @@ -515,7 +515,7 @@ pub enum HandleError { NotReady(Name), } -cfg_should_impl_error! { +crate::util::cfg_should_impl_error! { impl std::error::Error for HandleError {} } diff --git a/all-is-cubes/src/universe/members.rs b/all-is-cubes/src/universe/members.rs index 85625b61b..5dfaa62d3 100644 --- a/all-is-cubes/src/universe/members.rs +++ b/all-is-cubes/src/universe/members.rs @@ -374,7 +374,7 @@ macro_rules! member_enums_and_impls { )* } - cfg_should_impl_error! { + crate::util::cfg_should_impl_error! { impl std::error::Error for AnyTransactionConflict { fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { match self { diff --git a/all-is-cubes/src/universe/universe_txn.rs b/all-is-cubes/src/universe/universe_txn.rs index f9a345f1d..4ccd1c41b 100644 --- a/all-is-cubes/src/universe/universe_txn.rs +++ b/all-is-cubes/src/universe/universe_txn.rs @@ -234,7 +234,7 @@ pub enum UniverseConflict { Behaviors(behavior::BehaviorTransactionConflict), } -cfg_should_impl_error! { +crate::util::cfg_should_impl_error! { impl std::error::Error for UniverseConflict { fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { match self { @@ -784,7 +784,7 @@ pub enum MemberConflict { Modify(ModifyMemberConflict), } -cfg_should_impl_error! { +crate::util::cfg_should_impl_error! { impl std::error::Error for MemberConflict { fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { match self { @@ -805,7 +805,7 @@ cfg_should_impl_error! { #[derive(Clone, Debug, Eq, PartialEq)] pub struct ModifyMemberConflict(AnyTransactionConflict); -cfg_should_impl_error! { +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 c51b0b9ce..bafe39985 100644 --- a/all-is-cubes/src/util.rs +++ b/all-is-cubes/src/util.rs @@ -1,353 +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`. - /// 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 - } - } - - } -} - -/// 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",