2022-04-29 17:48:41 +10:00
|
|
|
pub mod meta;
|
|
|
|
pub mod types;
|
|
|
|
|
2022-04-15 07:55:45 +10:00
|
|
|
use std::collections::hash_map::Entry;
|
|
|
|
use std::collections::HashMap;
|
|
|
|
use std::iter::FusedIterator;
|
|
|
|
|
2022-04-29 17:48:41 +10:00
|
|
|
use rayon::iter::ParallelIterator;
|
2022-04-15 07:55:45 +10:00
|
|
|
use uuid::Uuid;
|
|
|
|
|
|
|
|
use crate::chunk::ChunkPos;
|
2022-04-29 17:48:41 +10:00
|
|
|
use crate::glm::DVec3;
|
|
|
|
use crate::slotmap::{Key, SlotMap};
|
|
|
|
use crate::{Aabb, Id, WorldId};
|
2022-04-15 07:55:45 +10:00
|
|
|
|
|
|
|
pub struct EntityStore {
|
2022-04-29 17:48:41 +10:00
|
|
|
sm: SlotMap<Entity>,
|
2022-04-15 07:55:45 +10:00
|
|
|
uuid_to_entity: HashMap<Uuid, EntityId>,
|
|
|
|
/// Maps chunk positions to the set of all entities with bounding volumes
|
|
|
|
/// intersecting that chunk.
|
|
|
|
partition: HashMap<(WorldId, ChunkPos), Vec<EntityId>>,
|
|
|
|
}
|
|
|
|
|
2022-04-29 17:48:41 +10:00
|
|
|
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
|
|
|
|
pub struct EntityId(Key);
|
|
|
|
|
|
|
|
impl Id for EntityId {
|
|
|
|
fn idx(self) -> usize {
|
|
|
|
self.0.index() as usize
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl EntityId {
|
|
|
|
pub(crate) fn to_network_id(self) -> i32 {
|
|
|
|
// TODO: is ID 0 reserved?
|
|
|
|
self.0.index() as i32
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub struct Entity {
|
|
|
|
data: EntityData,
|
|
|
|
old_type: EntityType,
|
|
|
|
new_position: DVec3,
|
|
|
|
old_position: DVec3,
|
|
|
|
new_world: Option<WorldId>,
|
|
|
|
old_world: Option<WorldId>,
|
|
|
|
uuid: Uuid,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Entity {
|
|
|
|
pub fn data(&self) -> &EntityData {
|
|
|
|
&self.data
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn typ(&self) -> EntityType {
|
|
|
|
self.data.typ()
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Changes the type of this entity.
|
|
|
|
pub fn change_type(&mut self, new_type: EntityType) {
|
|
|
|
todo!(); // TODO
|
|
|
|
}
|
|
|
|
|
|
|
|
fn hitbox(&self) -> Aabb<f64, 3> {
|
|
|
|
// TODO
|
|
|
|
Aabb::default()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub use types::{EntityData, EntityType};
|
|
|
|
|
2022-04-15 07:55:45 +10:00
|
|
|
impl EntityStore {
|
|
|
|
pub(crate) fn new() -> Self {
|
|
|
|
Self {
|
2022-04-29 17:48:41 +10:00
|
|
|
sm: SlotMap::new(),
|
2022-04-15 07:55:45 +10:00
|
|
|
uuid_to_entity: HashMap::new(),
|
|
|
|
partition: HashMap::new(),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-04-29 17:48:41 +10:00
|
|
|
/// Returns the number of live entities.
|
|
|
|
pub fn count(&self) -> usize {
|
|
|
|
self.sm.count()
|
2022-04-15 07:55:45 +10:00
|
|
|
}
|
|
|
|
|
2022-04-29 17:48:41 +10:00
|
|
|
/// Spawns a new entity with the default data. The new entity'd [`EntityId`]
|
|
|
|
/// is returned.
|
|
|
|
///
|
|
|
|
/// To actually see the new entity, set its position to somewhere nearby and
|
|
|
|
/// [change its type](EntityData::change_type) to something visible.
|
|
|
|
pub fn create(&mut self) -> EntityId {
|
2022-04-15 07:55:45 +10:00
|
|
|
loop {
|
|
|
|
let uuid = Uuid::from_bytes(rand::random());
|
2022-04-29 17:48:41 +10:00
|
|
|
if let Some(entity) = self.create_with_uuid(uuid) {
|
|
|
|
return entity;
|
2022-04-15 07:55:45 +10:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Like [`create`](Entities::create), but requires specifying the new
|
|
|
|
/// entity's UUID. This is useful for deserialization.
|
|
|
|
///
|
|
|
|
/// The provided UUID must not conflict with an existing entity UUID. If it
|
|
|
|
/// does, `None` is returned and the entity is not spawned.
|
2022-04-29 17:48:41 +10:00
|
|
|
pub fn create_with_uuid(&mut self, uuid: Uuid) -> Option<EntityId> {
|
2022-04-15 07:55:45 +10:00
|
|
|
match self.uuid_to_entity.entry(uuid) {
|
|
|
|
Entry::Occupied(_) => None,
|
|
|
|
Entry::Vacant(ve) => {
|
2022-04-29 17:48:41 +10:00
|
|
|
let entity = EntityId(self.sm.insert(Entity {
|
|
|
|
data: EntityData::Marker(types::Marker::new()),
|
|
|
|
old_type: EntityType::Marker,
|
|
|
|
new_position: DVec3::default(),
|
|
|
|
old_position: DVec3::default(),
|
|
|
|
new_world: None,
|
|
|
|
old_world: None,
|
|
|
|
uuid,
|
|
|
|
}));
|
2022-04-15 07:55:45 +10:00
|
|
|
|
|
|
|
ve.insert(entity);
|
|
|
|
|
2022-04-29 17:48:41 +10:00
|
|
|
// TODO: insert into partition.
|
2022-04-15 07:55:45 +10:00
|
|
|
|
|
|
|
Some(entity)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-04-29 17:48:41 +10:00
|
|
|
/// Gets the [`EntityId`] of the entity with the given UUID in an efficient
|
|
|
|
/// manner.
|
|
|
|
///
|
|
|
|
/// Returns `None` if there is no entity with the provided UUID. Returns
|
|
|
|
/// `Some` otherwise.
|
|
|
|
pub fn get_with_uuid(&self, uuid: Uuid) -> Option<EntityId> {
|
|
|
|
self.uuid_to_entity.get(&uuid).cloned()
|
|
|
|
}
|
2022-04-15 07:55:45 +10:00
|
|
|
|
2022-04-29 17:48:41 +10:00
|
|
|
pub fn delete(&mut self, entity: EntityId) -> bool {
|
|
|
|
if let Some(e) = self.sm.remove(entity.0) {
|
2022-04-15 07:55:45 +10:00
|
|
|
self.uuid_to_entity
|
2022-04-29 17:48:41 +10:00
|
|
|
.remove(&e.uuid)
|
2022-04-15 07:55:45 +10:00
|
|
|
.expect("UUID should have been in UUID map");
|
|
|
|
|
2022-04-29 17:48:41 +10:00
|
|
|
// TODO: remove entity from partition.
|
2022-04-15 07:55:45 +10:00
|
|
|
true
|
|
|
|
} else {
|
|
|
|
false
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-04-29 17:48:41 +10:00
|
|
|
pub fn retain(&mut self, mut f: impl FnMut(EntityId, &mut Entity) -> bool) {
|
|
|
|
self.sm.retain(|k, v| f(EntityId(k), v))
|
2022-04-15 07:55:45 +10:00
|
|
|
}
|
|
|
|
|
2022-04-29 17:48:41 +10:00
|
|
|
pub fn get(&self, entity: EntityId) -> Option<&Entity> {
|
|
|
|
self.sm.get(entity.0)
|
2022-04-15 07:55:45 +10:00
|
|
|
}
|
|
|
|
|
2022-04-29 17:48:41 +10:00
|
|
|
pub fn get_mut(&mut self, entity: EntityId) -> Option<&mut Entity> {
|
|
|
|
self.sm.get_mut(entity.0)
|
2022-04-15 07:55:45 +10:00
|
|
|
}
|
|
|
|
|
2022-04-29 17:48:41 +10:00
|
|
|
pub fn iter(&self) -> impl FusedIterator<Item = (EntityId, &Entity)> + Clone + '_ {
|
|
|
|
self.sm.iter().map(|(k, v)| (EntityId(k), v))
|
2022-04-15 07:55:45 +10:00
|
|
|
}
|
|
|
|
|
2022-04-29 17:48:41 +10:00
|
|
|
pub fn iter_mut(&mut self) -> impl FusedIterator<Item = (EntityId, &mut Entity)> + '_ {
|
|
|
|
self.sm.iter_mut().map(|(k, v)| (EntityId(k), v))
|
2022-04-15 07:55:45 +10:00
|
|
|
}
|
|
|
|
|
2022-04-29 17:48:41 +10:00
|
|
|
pub fn par_iter(&self) -> impl ParallelIterator<Item = (EntityId, &Entity)> + Clone + '_ {
|
|
|
|
self.sm.par_iter().map(|(k, v)| (EntityId(k), v))
|
2022-04-15 07:55:45 +10:00
|
|
|
}
|
|
|
|
|
2022-04-29 17:48:41 +10:00
|
|
|
pub fn par_iter_mut(&mut self) -> impl ParallelIterator<Item = (EntityId, &mut Entity)> + '_ {
|
|
|
|
self.sm.par_iter_mut().map(|(k, v)| (EntityId(k), v))
|
2022-04-15 07:55:45 +10:00
|
|
|
}
|
|
|
|
|
2022-04-29 17:48:41 +10:00
|
|
|
pub(crate) fn from_network_id(&self, network_id: i32) -> Option<EntityId> {
|
|
|
|
self.sm.key_at_index(network_id as usize).map(EntityId)
|
2022-04-15 07:55:45 +10:00
|
|
|
}
|
|
|
|
|
|
|
|
fn partition_insert(&mut self, entity: EntityId, world: WorldId, aabb: Aabb<f64, 3>) {
|
|
|
|
let min_corner = ChunkPos::from_xz(aabb.min().xz());
|
|
|
|
let max_corner = ChunkPos::from_xz(aabb.max().xz());
|
|
|
|
|
|
|
|
for z in min_corner.z..=max_corner.z {
|
|
|
|
for x in min_corner.x..=max_corner.x {
|
|
|
|
self.partition_insert_at(entity, world, ChunkPos { x, z })
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn partition_insert_at(&mut self, entity: EntityId, world: WorldId, pos: ChunkPos) {
|
|
|
|
match self.partition.entry((world, pos)) {
|
|
|
|
Entry::Occupied(mut oe) => {
|
|
|
|
debug_assert!(
|
|
|
|
!oe.get_mut().contains(&entity),
|
|
|
|
"spatial partition: entity already present"
|
|
|
|
);
|
|
|
|
oe.get_mut().push(entity);
|
|
|
|
}
|
|
|
|
Entry::Vacant(ve) => {
|
|
|
|
ve.insert(vec![entity]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn partition_remove(&mut self, entity: EntityId, world: WorldId, aabb: Aabb<f64, 3>) {
|
|
|
|
let min_corner = ChunkPos::from_xz(aabb.min().xz());
|
|
|
|
let max_corner = ChunkPos::from_xz(aabb.max().xz());
|
|
|
|
|
|
|
|
for z in min_corner.z..=max_corner.z {
|
|
|
|
for x in min_corner.x..=max_corner.x {
|
|
|
|
self.partition_remove_at(entity, world, ChunkPos::new(x, z));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn partition_remove_at(&mut self, entity: EntityId, world: WorldId, pos: ChunkPos) {
|
|
|
|
let errmsg = "spatial partition: entity removal failed";
|
|
|
|
match self.partition.entry((world, pos)) {
|
|
|
|
Entry::Occupied(mut oe) => {
|
|
|
|
let v = oe.get_mut();
|
|
|
|
let idx = v.iter().position(|e| *e == entity).expect(errmsg);
|
|
|
|
v.swap_remove(idx);
|
|
|
|
|
|
|
|
if v.is_empty() {
|
|
|
|
oe.remove();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
Entry::Vacant(_) => panic!("{errmsg}"),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn partition_modify(
|
|
|
|
&mut self,
|
|
|
|
entity: EntityId,
|
|
|
|
old_world: WorldId,
|
|
|
|
old_aabb: Aabb<f64, 3>,
|
|
|
|
new_world: WorldId,
|
|
|
|
new_aabb: Aabb<f64, 3>,
|
|
|
|
) {
|
|
|
|
if old_world != new_world {
|
|
|
|
self.partition_remove(entity, old_world, old_aabb);
|
|
|
|
self.partition_insert(entity, new_world, new_aabb);
|
|
|
|
} else {
|
|
|
|
let old_min_corner = ChunkPos::from_xz(old_aabb.min().xz());
|
|
|
|
let old_max_corner = ChunkPos::from_xz(old_aabb.max().xz());
|
|
|
|
let new_min_corner = ChunkPos::from_xz(new_aabb.min().xz());
|
|
|
|
let new_max_corner = ChunkPos::from_xz(new_aabb.max().xz());
|
|
|
|
|
|
|
|
for z in new_min_corner.z..=new_max_corner.z {
|
|
|
|
for x in new_min_corner.x..=new_max_corner.x {
|
|
|
|
if x < old_min_corner.x
|
|
|
|
|| x > old_max_corner.x
|
|
|
|
|| z < old_min_corner.z
|
|
|
|
|| z > old_max_corner.z
|
|
|
|
{
|
|
|
|
self.partition_insert_at(entity, old_world, ChunkPos::new(x, z));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
for z in old_min_corner.z..=old_max_corner.z {
|
|
|
|
for x in old_min_corner.x..=old_max_corner.x {
|
|
|
|
if x < new_min_corner.x
|
|
|
|
|| x > new_max_corner.x
|
|
|
|
|| z < new_min_corner.z
|
|
|
|
|| z > new_max_corner.z
|
|
|
|
{
|
|
|
|
self.partition_remove_at(entity, old_world, ChunkPos::new(x, z))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Returns an iterator over all entities with bounding volumes intersecting
|
|
|
|
/// the given AABB in an arbitrary order.
|
|
|
|
pub fn intersecting_aabb(
|
|
|
|
&self,
|
|
|
|
world: WorldId,
|
|
|
|
aabb: Aabb<f64, 3>,
|
|
|
|
) -> impl FusedIterator<Item = EntityId> + '_ {
|
|
|
|
let min_corner = ChunkPos::from_xz(aabb.min().xz());
|
|
|
|
let max_corner = ChunkPos::from_xz(aabb.max().xz());
|
|
|
|
(min_corner.z..=max_corner.z).flat_map(move |z| {
|
|
|
|
(min_corner.x..=max_corner.x).flat_map(move |x| {
|
|
|
|
self.partition
|
|
|
|
.get(&(world, ChunkPos::new(x, z)))
|
|
|
|
.into_iter()
|
|
|
|
.flat_map(move |v| {
|
|
|
|
v.iter().cloned().filter(move |&e| {
|
2022-04-29 17:48:41 +10:00
|
|
|
self.get(e)
|
|
|
|
.expect("spatial partition contains deleted entity")
|
|
|
|
.hitbox()
|
2022-04-15 07:55:45 +10:00
|
|
|
.collides_with_aabb(&aabb)
|
|
|
|
})
|
|
|
|
})
|
|
|
|
})
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2022-04-29 17:48:41 +10:00
|
|
|
pub(crate) fn update(&mut self) {
|
|
|
|
for (_, e) in self.iter_mut() {
|
|
|
|
e.old_position = e.new_position;
|
|
|
|
e.old_world = e.new_world;
|
2022-04-15 07:55:45 +10:00
|
|
|
|
2022-04-29 17:48:41 +10:00
|
|
|
// TODO: update entity old_type.
|
|
|
|
// TODO: clear changed bits in metadata.
|
|
|
|
}
|
2022-04-15 07:55:45 +10:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-04-29 17:48:41 +10:00
|
|
|
//#[cfg(test)]
|
|
|
|
//mod tests {
|
|
|
|
// use appearance::Player;
|
|
|
|
//
|
|
|
|
// use super::*;
|
|
|
|
// use crate::glm;
|
|
|
|
//
|
|
|
|
// // TODO: better test: spawn a bunch of random entities, spawn a random
|
|
|
|
// AABB, // assert collides_with_aabb consistency.
|
|
|
|
//
|
|
|
|
// #[test]
|
|
|
|
// fn space_partition() {
|
|
|
|
// let mut entities = EntityStore::new();
|
|
|
|
//
|
|
|
|
// let ids = [(16.0, 16.0, 16.0), (8.0, 8.0, 8.0), (10.0, 50.0, 10.0)]
|
|
|
|
// .into_iter()
|
|
|
|
// .map(|(x, y, z)| entities.create(Player::new(glm::vec3(x, y, z),
|
|
|
|
// WorldId::NULL))) .collect::<Vec<_>>();
|
|
|
|
//
|
|
|
|
// let outside = *ids.last().unwrap();
|
|
|
|
//
|
|
|
|
// assert!(entities
|
|
|
|
// .intersecting_aabb(
|
|
|
|
// WorldId::NULL,
|
|
|
|
// Aabb::new(glm::vec3(8.0, 8.0, 8.0), glm::vec3(16.0, 16.0,
|
|
|
|
// 16.0)), )
|
|
|
|
// .all(|id| ids.contains(&id) && id != outside));
|
|
|
|
// }
|
|
|
|
//}
|