Implement a global map of entities

Implement a global map of entities, which allows referencing by either
position or ID and updating the positions of existent entities, and put
the character in there.
This commit is contained in:
Griffin Smith 2019-07-08 20:58:51 -04:00
parent 20f1ccb460
commit 5af2429ecb
14 changed files with 465 additions and 36 deletions

8
src/types/collision.rs Normal file
View file

@ -0,0 +1,8 @@
/// Describes a kind of game collision
pub enum Collision {
/// Stop moving - you can't move there!
Stop,
/// Moving into an entity at the given position indicates combat
Combat,
}

242
src/types/entity_map.rs Normal file
View file

@ -0,0 +1,242 @@
use crate::types::Position;
use crate::types::Positioned;
use crate::types::PositionedMut;
use std::collections::hash_map::HashMap;
use std::collections::BTreeMap;
use std::iter::FromIterator;
pub type EntityID = u32;
#[derive(Debug)]
pub struct EntityMap<A> {
by_position: BTreeMap<Position, Vec<EntityID>>,
by_id: HashMap<EntityID, A>,
last_id: EntityID,
}
// impl<A: Debug> ArbitraryF1<A> for EntityMap<A> {
// type Parameters = ();
// fn lift1_with<AS>(base: AS, _: Self::Parameters) -> BoxedStrategy<Self>
// where
// AS: Strategy<Value = A> + 'static,
// {
// unimplemented!()
// }
// // type Strategy = strategy::Just<Self>;
// // fn arbitrary_with(params : Self::Parameters) -> Self::Strategy;
// }
// impl<A: Arbitrary> Arbitrary for EntityMap<A> {
// type Parameters = A::Parameters;
// type Strategy = BoxedStrategy<Self>;
// fn arbitrary_with(params: Self::Parameters) -> Self::Strategy {
// let a_strat: A::Strategy = Arbitrary::arbitrary_with(params);
// ArbitraryF1::lift1::<A::Strategy>(a_strat)
// }
// }
const BY_POS_INVARIANT: &'static str =
"Invariant: All references in EntityMap.by_position should point to existent references in by_id";
impl<A> EntityMap<A> {
pub fn new() -> EntityMap<A> {
EntityMap {
by_position: BTreeMap::new(),
by_id: HashMap::new(),
last_id: 0,
}
}
pub fn len(&self) -> usize {
self.by_id.len()
}
/// Returns a list of all entities at the given position
pub fn at<'a>(&'a self, pos: Position) -> Vec<&'a A> {
// self.by_position.get(&pos).iter().flat_map(|eids| {
// eids.iter()
// .map(|eid| self.by_id.get(eid).expect(BY_POS_INVARIANT))
// })
// gross.
match self.by_position.get(&pos) {
None => Vec::new(),
Some(eids) => {
let mut res = Vec::new();
for eid in eids {
res.push(self.by_id.get(eid).expect(BY_POS_INVARIANT));
}
res
}
}
}
/// Remove all entities at the given position
pub fn remove_all_at(&mut self, pos: Position) {
self.by_position.remove(&pos).map(|eids| {
eids.iter()
.map(|eid| self.by_id.remove(&eid).expect(BY_POS_INVARIANT));
});
}
pub fn get<'a>(&'a self, id: EntityID) -> Option<&'a A> {
self.by_id.get(&id)
}
pub fn entities<'a>(&'a self) -> impl Iterator<Item = &'a A> {
self.by_id.values()
}
pub fn entities_mut<'a>(&'a mut self) -> impl Iterator<Item = &'a mut A> {
self.by_id.values_mut()
}
pub fn ids(&self) -> impl Iterator<Item = &EntityID> {
self.by_id.keys()
}
fn next_id(&mut self) -> EntityID {
self.last_id += 1;
self.last_id
}
}
impl<A: Positioned> EntityMap<A> {
pub fn insert(&mut self, entity: A) -> EntityID {
let pos = entity.position();
let entity_id = self.next_id();
self.by_id.entry(entity_id).or_insert(entity);
self.by_position
.entry(pos)
.or_insert(Vec::new())
.push(entity_id);
entity_id
}
}
impl<A: Positioned> FromIterator<A> for EntityMap<A> {
fn from_iter<I: IntoIterator<Item = A>>(iter: I) -> Self {
let mut em = EntityMap::new();
for ent in iter {
em.insert(ent);
}
em
}
}
impl<A: PositionedMut> EntityMap<A> {
pub fn update_position(
&mut self,
entity_id: EntityID,
new_position: Position,
) {
let mut old_pos = None;
if let Some(entity) = self.by_id.get_mut(&entity_id) {
if entity.position() == new_position {
return;
}
old_pos = Some(entity.position());
entity.set_position(new_position);
}
old_pos.map(|p| {
self.by_position
.get_mut(&p)
.map(|es| es.retain(|e| *e != entity_id));
self.by_position
.entry(new_position)
.or_insert(Vec::new())
.push(entity_id);
});
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::types::PositionedMut;
use proptest::prelude::*;
use proptest_derive::Arbitrary;
#[derive(Debug, Arbitrary, PartialEq, Eq, Clone)]
struct TestEntity {
position: Position,
name: String,
}
impl Positioned for TestEntity {
fn position(&self) -> Position {
self.position
}
}
impl PositionedMut for TestEntity {
fn set_position(&mut self, pos: Position) {
self.position = pos
}
}
fn gen_entity_map() -> BoxedStrategy<EntityMap<TestEntity>> {
any::<Vec<TestEntity>>()
.prop_map(|ents| {
ents.iter()
.map(|e| e.clone())
.collect::<EntityMap<TestEntity>>()
})
.boxed()
}
proptest! {
#![proptest_config(ProptestConfig::with_cases(10))]
#[test]
fn test_entity_map_len(items: Vec<TestEntity>) {
let mut map = EntityMap::new();
assert_eq!(map.len(), 0);
for ent in &items {
map.insert(ent);
}
assert_eq!(map.len(), items.len());
}
#[test]
fn test_entity_map_getset(
mut em in gen_entity_map(),
ent: TestEntity
) {
em.insert(ent.clone());
assert!(em.at(ent.position).iter().any(|e| **e == ent))
}
#[test]
fn test_entity_map_set_iter_contains(
mut em in gen_entity_map(),
ent: TestEntity
) {
em.insert(ent.clone());
assert!(em.entities().any(|e| *e == ent))
}
#[test]
fn test_update_position(
mut em in gen_entity_map(),
ent: TestEntity,
new_position: Position,
) {
let original_position = ent.position();
let entity_id = em.insert(ent.clone());
em.update_position(entity_id, new_position);
if new_position != original_position {
assert_eq!(em.at(original_position).len(), 0);
}
assert_eq!(
em.get(entity_id).map(|e| e.position()),
Some(new_position)
);
assert_eq!(
em.at(new_position).iter().map(|e| e.name.clone()).collect::<Vec<_>>(),
vec![ent.name]
)
}
}
}

View file

@ -1,7 +1,11 @@
use std::cmp::Ordering;
use std::ops;
use std::rc::Rc;
pub mod collision;
pub mod command;
pub mod direction;
pub mod entity_map;
pub use collision::Collision;
pub use direction::Direction;
pub use direction::Direction::{Down, Left, Right, Up};
use proptest_derive::Arbitrary;
@ -43,13 +47,16 @@ impl BoundingBox {
}
}
pub fn from_corners(top_left: Position, lower_right: Position) -> BoundingBox {
pub fn from_corners(
top_left: Position,
lower_right: Position,
) -> BoundingBox {
BoundingBox {
position: top_left,
dimensions: Dimensions {
w: (lower_right.x - top_left.x) as u16,
h: (lower_right.y - top_left.y) as u16,
}
},
}
}
@ -70,7 +77,11 @@ impl BoundingBox {
/// Moves the top right corner of the bounding box by the offset specified
/// by the given position, keeping the lower right corner in place
pub fn move_tr_corner(self, offset: Position) -> BoundingBox {
self + offset - Dimensions { w: offset.x as u16, h: offset.y as u16 }
self + offset
- Dimensions {
w: offset.x as u16,
h: offset.y as u16,
}
}
}
@ -94,7 +105,7 @@ impl ops::Sub<Dimensions> for BoundingBox {
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Arbitrary)]
#[derive(Clone, Copy, Debug, PartialEq, Eq, Arbitrary, Hash, Ord)]
pub struct Position {
/// x (horizontal) position
#[proptest(strategy = "std::ops::Range::<i16>::from(0..100)")]
@ -105,6 +116,10 @@ pub struct Position {
pub y: i16,
}
pub fn pos(x: i16, y: i16) -> Position {
Position { x, y }
}
pub const ORIGIN: Position = Position { x: 0, y: 0 };
pub const UNIT_POSITION: Position = Position { x: 1, y: 1 };
@ -241,6 +256,47 @@ pub trait Positioned {
}
}
pub trait PositionedMut: Positioned {
fn set_position(&mut self, pos: Position);
}
// impl<A, I> Positioned for A where A : Deref<Target = I>, I: Positioned {
// fn position(&self) -> Position {
// self.position()
// }
// }
impl<T: Positioned> Positioned for Box<T> {
fn position(&self) -> Position {
(**self).position()
}
}
impl<'a, T: Positioned> Positioned for &'a T {
fn position(&self) -> Position {
(**self).position()
}
}
impl<'a, T: Positioned> Positioned for &'a mut T {
fn position(&self) -> Position {
(**self).position()
}
}
impl<'a, T: Positioned> Positioned for Rc<T> {
fn position(&self) -> Position {
(**self).position()
}
}
impl<'a, T: PositionedMut> PositionedMut for &'a mut T {
fn set_position(&mut self, pos: Position) {
(**self).set_position(pos)
}
}
#[macro_export]
macro_rules! positioned {
($name:ident) => {
positioned!($name, position);
@ -254,6 +310,20 @@ macro_rules! positioned {
};
}
#[macro_export]
macro_rules! positioned_mut {
($name:ident) => {
positioned_mut!($name, position);
};
($name:ident, $attr:ident) => {
impl crate::types::PositionedMut for $name {
fn set_position(&mut self, pos: Position) {
self.$attr = pos;
}
}
};
}
/// A number of ticks
#[derive(Clone, Copy, Debug, PartialEq, Eq, Arbitrary)]
pub struct Ticks(pub u16);