// 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()
}
}