mirage-engine 0.1.1

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::mesh::{Geometry, MeshError};

/// The set of every mesh type a game draws, named as
/// [`Game::Meshes`](crate::Game::Meshes).
///
/// Written by [`meshes!`](crate::meshes); `()` for a game that draws
/// nothing. Its catalog holds every value every one of its types catalogs.
pub trait Meshes: Catalog + Hash + Eq + Clone + 'static {
    /// Builds the mesh this value wraps, its part type dropped, or the
    /// errors in how it was built.
    #[doc(hidden)]
    fn build(&self, assets: &Assets) -> Result<Geometry, Vec<MeshError>>;

    /// The name of the mesh type this value wraps, for startup errors.
    #[doc(hidden)]
    fn name(&self) -> &'static str;
}

impl Catalog for () {
    /// Nothing to build.
    fn catalog() -> Vec<Self> {
        Vec::new()
    }
}

impl Meshes for () {
    /// Nothing; no draw of a game with no meshes compiles, so this never
    /// runs.
    fn build(&self, _assets: &Assets) -> Result<Geometry, Vec<MeshError>> {
        Ok(Geometry::empty())
    }

    fn name(&self) -> &'static str {
        "()"
    }
}

/// 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::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,
            ) -> ::core::result::Result<$crate::mesh::Geometry, ::std::vec::Vec<$crate::mesh::MeshError>> {
                match self {
                    $(Self::$mesh(mesh) => $crate::Mesh::build(mesh, assets).erased()),+
                }
            }

            fn name(&self) -> &'static str {
                match self {
                    $(Self::$mesh(_) => ::core::stringify!($mesh)),+
                }
            }
        }
    };
}

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

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

    #[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)
            .expect("nothing is wrong with a cube");

        assert_eq!(cube.part_count(), 1);
        assert_eq!(Shape::from(Cube).name(), "Cube");
        assert_eq!(Shape::from(Sphere { subdivisions: 1 }).name(), "Sphere");
    }

    #[test]
    fn the_set_of_no_meshes_builds_nothing() {
        assert!(<()>::catalog().is_empty());
        assert_eq!(
            ().build(&Assets::default())
                .expect("nothing is wrong with nothing")
                .part_count(),
            0
        );
    }
}