mirage-engine 0.2.0

Mirage, an immediate-mode 3D engine for simple games on desktop and the browser
Documentation
//! The set of every mesh a game draws, and the macro that writes it.

use core::hash::Hash;

use crate::Catalog;
use crate::assets::Assets;
use crate::holds::Sealed;
use crate::mesh::Built;

/// The set of every mesh type a game draws, named as
/// [`Game::Meshes`](crate::Game::Meshes).
///
/// Written by [`meshes!`](crate::meshes) and never implemented by hand;
/// [`NoMeshes`] for a game that draws nothing. Its catalog holds every
/// value every one of its types catalogs.
pub trait Meshes: Sealed + Catalog + Hash + Eq + Clone + 'static {
    /// Builds the mesh this value wraps, its part type dropped, beside what
    /// that build did not get, under the mesh type's own name.
    #[doc(hidden)]
    fn build(&self, assets: &Assets) -> Built;
}

/// The set of a game that draws no mesh of its own.
///
/// No value of it exists, so no draw compiles for such a game. `()` is not a
/// mesh set:
///
/// ```compile_fail
/// use mirage_engine::prelude::*;
///
/// fn set<M: Meshes>() {}
///
/// set::<()>();
/// ```
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
pub enum NoMeshes {}

impl Sealed for NoMeshes {}

impl Catalog for NoMeshes {
    fn catalog() -> Vec<Self> {
        Vec::new()
    }
}

impl Meshes for NoMeshes {
    fn build(&self, _assets: &Assets) -> Built {
        match *self {}
    }
}

/// Writes the set of every mesh a game draws: an enum with one variant
/// wrapping each named type, which [`Game::Meshes`](crate::Game::Meshes)
/// names.
///
/// Each type is named by its own name and must be in scope; a type named
/// twice, and a type that implements [`Mesh`](crate::Mesh) for two part
/// vocabularies, each fail to compile here. The set holds itself as well
/// as every type it names, so a draw
/// [`into_set`](crate::mesh::Instance::into_set) turned into the set is
/// drawn too. A `pub` before `enum` makes
/// the enum public, and `///` lines before that are the enum's.
///
/// ```
/// use mirage_engine::prelude::*;
///
/// meshes! { enum Shape { Cube, Sphere } }
/// ```
#[macro_export]
macro_rules! meshes {
    ($(#[$attribute:meta])* $vis:vis enum $set:ident { $($mesh:ident),+ $(,)? }) => {
        $(#[$attribute])*
        #[derive(Clone, Eq, Hash, PartialEq)]
        $vis enum $set {
            $($mesh($mesh)),+
        }

        $(
            impl ::core::convert::From<$mesh> for $set {
                fn from(mesh: $mesh) -> Self {
                    Self::$mesh(mesh)
                }
            }

            impl $crate::Holds<$mesh> for $set {}
        )+

        impl $crate::Holds<$set> for $set {}

        impl $crate::Sealed for $set {}

        impl $crate::Catalog for $set {
            fn catalog() -> ::std::vec::Vec<Self> {
                let mut all = ::std::vec::Vec::new();
                $(all.extend(<$mesh as $crate::Catalog>::catalog().into_iter().map(Self::$mesh));)+
                all
            }
        }

        impl $crate::Meshes for $set {
            fn build(&self, assets: &$crate::Assets) -> $crate::mesh::Built {
                match self {
                    $(
                        Self::$mesh(mesh) => $crate::Mesh::build(mesh, assets)
                            .erased(::core::stringify!($mesh))
                    ),+
                }
            }
        }
    };
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::mesh::{Cube, Mesh, MeshData, Sphere};

    meshes! {
        #[derive(Debug)]
        enum Shape { Cube, Sphere }
    }

    meshes! {
        enum Shapes { Wedge }
    }

    /// A mesh whose one index reaches past corners it does not have, which
    /// the set reports under this type's name.
    #[derive(Clone, Debug, Eq, Hash, PartialEq)]
    struct Wedge;

    impl Catalog for Wedge {
        fn catalog() -> Vec<Self> {
            vec![Self]
        }
    }

    impl Mesh for Wedge {
        fn build(&self, _assets: &Assets) -> MeshData {
            MeshData::new(Vec::new(), vec![0])
        }
    }

    #[test]
    fn a_set_catalogs_every_value_of_every_type_it_names() {
        let catalog = Shape::catalog();

        assert_eq!(catalog.len(), 1 + Sphere::catalog().len());
        assert_eq!(catalog[0], Shape::Cube(Cube));
        assert!(matches!(catalog[1], Shape::Sphere(_)));
    }

    #[test]
    fn a_set_value_builds_the_mesh_it_wraps_under_its_types_name() {
        let assets = Assets::default();
        let cube = Shape::from(Cube).build(&assets);

        assert_eq!(cube.geometry.part_count(), 1);
        assert!(
            cube.unresolved.is_empty(),
            "nothing is wrong with a cube: {:?}",
            cube.unresolved
        );

        let wedge = Shapes::from(Wedge).build(&assets);
        assert_eq!(
            wedge
                .unresolved
                .error()
                .expect("the index reaches past the corners")
                .to_string(),
            "the game's assets did not resolve: the mesh `Wedge` has the index 0 past its 0 \
             vertices",
            "and a value of the set names its own type in what it did not get"
        );
    }

    #[test]
    fn the_set_of_no_meshes_catalogs_nothing() {
        assert!(NoMeshes::catalog().is_empty());
    }
}