dungen_minion_rooms_abstract 0.3.2

Abstract room support for dungen_minion.
Documentation
// External includes.

// Standard includes.
use std::cmp::{Eq, PartialEq};
use std::hash::{Hash, Hasher};
use std::sync::atomic::{AtomicUsize, Ordering};

// Internal includes.
use super::{
    MapId, PortalCollection, SubMapCollection, TileType, LOWEST_POSSIBLY_INVALID_MAP, VALID_MAPS,
};
use crate::geometry::{
    CardinalRotation, HasHeight, HasWidth, IsPosition, Length, PlacedShape, Position,
};

lazy_static! {
    static ref ATOMIC_ID: AtomicUsize = AtomicUsize::new(0);
}

/// Call this to get an ID for a new [`Map`](trait.Map.html).
#[must_use]
pub fn get_new_map_id() -> MapId {
    if *LOWEST_POSSIBLY_INVALID_MAP.read() < ATOMIC_ID.load(Ordering::Acquire) {
        let mut lowest_possibly_invalid_map = LOWEST_POSSIBLY_INVALID_MAP.write();
        let valid_maps = VALID_MAPS.write();
        while *lowest_possibly_invalid_map < valid_maps.len() {
            let mut valid_map = valid_maps[*lowest_possibly_invalid_map].write();
            if !(*valid_map) {
                *valid_map = true;
                ATOMIC_ID.fetch_max(*lowest_possibly_invalid_map, Ordering::SeqCst);
                return *lowest_possibly_invalid_map;
            }

            *lowest_possibly_invalid_map += 1;
        }
    }
    // We don't actually care in what order we receive the Id; only that we get a different one.
    // "Ordering::Relaxed" is sufficient for that.
    ATOMIC_ID.fetch_add(1, Ordering::Relaxed)
}

/// The defining trait for a map..
pub trait Map: PlacedShape + PortalCollection + Send + Sync + SubMapCollection {
    /// A helper method to allow structs implementing `Map` to be `Clone`'ed.
    ///
    /// [https://users.rust-lang.org/t/solved-is-it-possible-to-clone-a-boxed-trait-object/1714/5](https://users.rust-lang.org/t/solved-is-it-possible-to-clone-a-boxed-trait-object/1714/5)
    fn box_clone(&self) -> Box<dyn Map>;

    /// Determines if the local `Position` is valid. Provides a default implementation.
    fn is_local_position_valid(&self, position: Position) -> bool {
        // As this returns if a coordinate is less than zero, sign loss is not a problem.
        #[allow(clippy::cast_sign_loss)]
        !(position.x() < 0
            || position.y() < 0
            || position.x() as Length >= self.size().width()
            || position.y() as Length >= self.size().height())
    }

    /// Provides a very-likely unique u64 Id for a Map.
    fn map_id(&self) -> MapId;

    /// Rotates the map according to the given rotation, relative to its `self.area().position()`.
    fn rotate(&mut self, rotation: CardinalRotation);

    /// Gets an option for an immutable reference to the `TileType` at the given `Position`. Returns None if the `Position` is out of bounds, or there is no tile at that location.
    ///
    /// This method has a default implementation.
    fn tile_type_at(&self, position: Position) -> Option<TileType> {
        let local_position = position - *self.position();
        if self.is_local_position_valid(local_position) {
            self.tile_type_at_local(local_position)
        } else {
            None
        }
    }

    /// Gets an option for a mutable reference to the `TileType` at the given `Position`. Returns None if the `Position` is out of bounds, or there is no tile at that location.
    ///
    /// This method has a default implementation.
    fn tile_type_at_mut(&mut self, position: Position) -> Option<&mut TileType> {
        let local_position = position - *self.position();
        if self.is_local_position_valid(local_position) {
            self.tile_type_at_local_mut(local_position)
        } else {
            None
        }
    }

    /// Gets an option for an immutable reference to the `TileType` at the given local `Position`. Returns None if the local `Position` is out of bounds, or there is no tile at that location.
    ///
    /// Uses [`TileTypeStandardCmp`](struct.TileTypeStandardCmp.html) to determine which sub-map tile has priority, if any.
    fn tile_type_at_local(&self, pos: Position) -> Option<TileType>;

    /// Gets an option for a mutable reference to the `TileType` at the given local `Position`. Returns None if the `Position` is out of bounds, or there is no tile at that location.
    fn tile_type_at_local_mut(&mut self, pos: Position) -> Option<&mut TileType>;

    /// Sets the `TileType` at the given local `Position`, if it is valid, and returns the previous `TileType`, if any. The `Map` will expand to meet a local `Position` larger than its [`Size`](geometry/struct.Size.html).
    fn tile_type_at_local_set(&mut self, pos: Position, tile_type: TileType) -> Option<TileType>;

    /// Gets an option for an immutable reference to the `TileType` at the given local `Position`. Returns None if the local `Position` is out of bounds, or there is no tile at that location.
    ///
    /// Uses a comparison function to determine which sub-map tile has priority, if any.
    fn tile_type_at_local_sort_by(
        &self,
        pos: Position,
        sort_best: &dyn Fn(&Option<TileType>, &Option<TileType>) -> std::cmp::Ordering,
    ) -> Option<TileType>;
}

impl Clone for Box<dyn Map> {
    fn clone(&self) -> Box<dyn Map> {
        self.box_clone()
    }
}

impl Eq for Box<dyn Map> {}

impl Hash for Box<dyn Map> {
    fn hash<H: Hasher>(&self, state: &mut H) {
        self.map_id().hash(state);
    }
}

impl PartialEq for Box<dyn Map> {
    fn eq(&self, other: &Self) -> bool {
        self.map_id() == other.map_id()
    }
}