use std::collections::{HashMap, BTreeSet, HashSet, VecDeque};
use std::collections::hash_map::Entry;
use std::ops::{Deref, DerefMut};
use std::iter::FusedIterator;
use std::cmp::Ordering;
use std::hash::Hash;
use std::cell::Cell;
use std::sync::Arc;
use std::slice;
use std::mem;
use glam::{IVec3, Vec2, DVec3};
use indexmap::IndexMap;
use tracing::trace;
use crate::entity::{Entity, EntityCategory, EntityKind};
use crate::block_entity::BlockEntity;
use crate::biome::Biome;
use crate::chunk::{Chunk,
calc_chunk_pos, calc_chunk_pos_unchecked, calc_entity_chunk_pos,
CHUNK_HEIGHT, CHUNK_WIDTH};
use crate::geom::{BoundingBox, Face};
use crate::rand::JavaRandom;
use crate::item::ItemStack;
use crate::block;
pub mod material;
pub mod bound;
pub mod power;
pub mod loot;
pub mod interact;
pub mod place;
pub mod r#break;
pub mod r#use;
pub mod tick;
pub mod notify;
pub mod explode;
thread_local! {
static RANDOM_TICKS_PENDING: Cell<Vec<(IVec3, u8, u8)>> = const { Cell::new(Vec::new()) };
static LOADED_CHUNKS: Cell<Vec<(i32, i32)>> = const { Cell::new(Vec::new()) };
}
#[derive(Clone)]
pub struct World {
events: Option<Vec<Event>>,
dimension: Dimension,
time: u64,
rand: JavaRandom,
chunks: HashMap<(i32, i32), ChunkComponent>,
entities_count: u32,
entities: TickVec<EntityComponent>,
entities_id_map: HashMap<u32, usize>,
entities_player_map: IndexMap<u32, usize>,
block_entities: TickVec<BlockEntityComponent>,
block_entities_pos_map: HashMap<IVec3, usize>,
block_ticks_count: u64,
block_ticks: BTreeSet<BlockTick>,
block_ticks_states: HashSet<BlockTickState>,
light_updates: VecDeque<LightUpdate>,
random_ticks_seed: i32,
weather: Weather,
weather_next_time: u64,
sky_light_subtracted: u8,
}
impl World {
pub fn new(dimension: Dimension) -> Self {
Self {
events: None,
dimension,
time: 0,
rand: JavaRandom::new_seeded(),
chunks: HashMap::new(),
entities_count: 0,
entities: TickVec::new(),
entities_id_map: HashMap::new(),
entities_player_map: IndexMap::new(),
block_entities: TickVec::new(),
block_entities_pos_map: HashMap::new(),
block_ticks_count: 0,
block_ticks: BTreeSet::new(),
block_ticks_states: HashSet::new(),
light_updates: VecDeque::new(),
random_ticks_seed: JavaRandom::new_seeded().next_int(),
weather: Weather::Clear,
weather_next_time: 0,
sky_light_subtracted: 0,
}
}
pub fn swap_events(&mut self, events: Option<Vec<Event>>) -> Option<Vec<Event>> {
mem::replace(&mut self.events, events)
}
pub fn has_events(&self) -> bool {
self.events.is_some()
}
#[inline]
pub fn push_event(&mut self, event: Event) {
if let Some(events) = &mut self.events {
events.push(event);
}
}
pub fn get_dimension(&self) -> Dimension {
self.dimension
}
pub fn get_time(&self) -> u64 {
self.time
}
pub fn get_rand_mut(&mut self) -> &mut JavaRandom {
&mut self.rand
}
pub fn get_weather(&self) -> Weather {
self.weather
}
pub fn set_weather(&mut self, weather: Weather) {
if self.weather != weather {
self.push_event(Event::Weather { prev: self.weather, new: weather });
self.weather = weather;
}
}
pub fn insert_chunk_snapshot(&mut self, snapshot: ChunkSnapshot) {
self.set_chunk(snapshot.cx, snapshot.cz, snapshot.chunk);
for entity in snapshot.entities {
debug_assert_eq!(calc_entity_chunk_pos(entity.0.pos), (snapshot.cx, snapshot.cz), "incoherent entity in chunk snapshot");
self.spawn_entity_inner(entity);
}
for (pos, block_entity) in snapshot.block_entities {
debug_assert_eq!(calc_chunk_pos_unchecked(pos), (snapshot.cx, snapshot.cz), "incoherent block entity in chunk snapshot");
self.set_block_entity_inner(pos, block_entity);
}
}
pub fn take_chunk_snapshot(&self, cx: i32, cz: i32) -> Option<ChunkSnapshot> {
let chunk_comp = self.chunks.get(&(cx, cz))?;
let chunk = chunk_comp.data.as_ref()?;
Some(ChunkSnapshot {
cx,
cz,
chunk: Arc::clone(&chunk),
entities: chunk_comp.entities.values()
.filter_map(|&index| self.entities.get(index).unwrap().inner.clone())
.collect(),
block_entities: chunk_comp.block_entities.iter()
.filter_map(|(&pos, &index)| self.block_entities.get(index).unwrap().inner.clone()
.map(|e| (pos, e)))
.collect(),
})
}
pub fn remove_chunk_snapshot(&mut self, cx: i32, cz: i32) -> Option<ChunkSnapshot> {
let chunk_comp = self.chunks.remove(&(cx, cz))?;
let mut ret = None;
let entities = chunk_comp.entities.keys()
.filter_map(|&id| self.remove_entity_inner(id, false, "remove chunk snapshot").unwrap().inner)
.collect();
let block_entities = chunk_comp.block_entities.keys()
.filter_map(|&pos| self.remove_block_entity_inner(pos, false).unwrap().inner
.map(|e| (pos, e)))
.collect();
if let Some(chunk) = chunk_comp.data {
ret = Some(ChunkSnapshot {
cx,
cz,
chunk,
entities,
block_entities,
});
self.push_event(Event::Chunk { cx, cz, inner: ChunkEvent::Remove });
}
ret
}
pub fn set_chunk(&mut self, cx: i32, cz: i32, chunk: Arc<Chunk>) {
let chunk_comp = self.chunks.entry((cx, cz)).or_default();
let was_unloaded = chunk_comp.data.replace(chunk).is_none();
if was_unloaded {
for &index in chunk_comp.entities.values() {
self.entities.get_mut(index).unwrap().loaded = true;
}
for &index in chunk_comp.block_entities.values() {
self.block_entities.get_mut(index).unwrap().loaded = true;
}
}
self.push_event(Event::Chunk { cx, cz, inner: ChunkEvent::Set });
}
pub fn contains_chunk(&self, cx: i32, cz: i32) -> bool {
self.chunks.get(&(cx, cz)).is_some_and(|c| c.data.is_some())
}
pub fn get_chunk(&self, cx: i32, cz: i32) -> Option<&Chunk> {
self.chunks.get(&(cx, cz)).and_then(|c| c.data.as_deref())
}
pub fn get_chunk_mut(&mut self, cx: i32, cz: i32) -> Option<&mut Chunk> {
self.chunks.get_mut(&(cx, cz)).and_then(|c| c.data.as_mut().map(Arc::make_mut))
}
pub fn remove_chunk(&mut self, cx: i32, cz: i32) -> Option<Arc<Chunk>> {
let chunk_comp = self.chunks.get_mut(&(cx, cz))?;
let ret = chunk_comp.data.take();
if ret.is_some() {
for &index in chunk_comp.entities.values() {
self.entities.get_mut(index).unwrap().loaded = false;
}
for &index in chunk_comp.block_entities.values() {
self.block_entities.get_mut(index).unwrap().loaded = false;
}
self.push_event(Event::Chunk { cx, cz, inner: ChunkEvent::Remove });
}
ret
}
pub fn set_block(&mut self, pos: IVec3, id: u8, metadata: u8) -> Option<(u8, u8)> {
let (cx, cz) = calc_chunk_pos(pos)?;
let chunk = self.get_chunk_mut(cx, cz)?;
let (prev_id, prev_metadata) = chunk.get_block(pos);
if id != prev_id || metadata != prev_metadata {
chunk.set_block(pos, id, metadata);
chunk.recompute_height(pos);
if block::material::get_light_opacity(id) != block::material::get_light_opacity(prev_id)
|| block::material::get_light_emission(id) != block::material::get_light_emission(prev_id) {
self.schedule_light_update(pos, LightKind::Block);
self.schedule_light_update(pos, LightKind::Sky);
}
self.push_event(Event::Block {
pos,
inner: BlockEvent::Set {
id,
metadata,
prev_id,
prev_metadata,
}
});
self.push_event(Event::Chunk { cx, cz, inner: ChunkEvent::Dirty });
}
Some((prev_id, prev_metadata))
}
pub fn set_block_self_notify(&mut self, pos: IVec3, id: u8, metadata: u8) -> Option<(u8, u8)> {
let (prev_id, prev_metadata) = self.set_block(pos, id, metadata)?;
self.notify_change_unchecked(pos, prev_id, prev_metadata, id, metadata);
Some((prev_id, prev_metadata))
}
pub fn set_block_notify(&mut self, pos: IVec3, id: u8, metadata: u8) -> Option<(u8, u8)> {
let (prev_id, prev_metadata) = self.set_block_self_notify(pos, id, metadata)?;
self.notify_blocks_around(pos, id);
Some((prev_id, prev_metadata))
}
pub fn get_block(&self, pos: IVec3) -> Option<(u8, u8)> {
let (cx, cz) = calc_chunk_pos(pos)?;
let chunk = self.get_chunk(cx, cz)?;
Some(chunk.get_block(pos))
}
pub fn get_height(&self, pos: IVec3) -> Option<u8> {
let (cx, cz) = calc_chunk_pos_unchecked(pos);
let chunk = self.get_chunk(cx, cz)?;
Some(chunk.get_height(pos))
}
pub fn get_light(&self, mut pos: IVec3) -> Light {
if pos.y > 127 {
pos.y = 127;
}
let mut light = Light {
block: 0,
sky: 15,
sky_real: 0,
};
if let Some((cx, cz)) = calc_chunk_pos(pos) {
if let Some(chunk) = self.get_chunk(cx, cz) {
light.block = chunk.get_block_light(pos);
light.sky = chunk.get_sky_light(pos);
}
}
light.sky_real = light.sky.saturating_sub(self.sky_light_subtracted);
light
}
pub fn schedule_light_update(&mut self, pos: IVec3, kind: LightKind) {
self.light_updates.push_back(LightUpdate {
kind,
pos,
credit: 15,
});
}
#[inline]
pub fn get_light_update_count(&self) -> usize {
self.light_updates.len()
}
pub fn get_biome(&self, pos: IVec3) -> Option<Biome> {
let (cx, cz) = calc_chunk_pos_unchecked(pos);
let chunk = self.get_chunk(cx, cz)?;
Some(chunk.get_biome(pos))
}
#[inline(never)]
fn spawn_entity_inner(&mut self, entity: Box<Entity>) -> u32 {
let id = self.entities_count;
self.entities_count = self.entities_count.checked_add(1)
.expect("entity count overflow");
let kind = entity.kind();
trace!("spawn entity #{id} ({:?})", kind);
let (cx, cz) = calc_entity_chunk_pos(entity.0.pos);
let chunk_comp = self.chunks.entry((cx, cz)).or_default();
let entity_index = self.entities.push(EntityComponent {
inner: Some(entity),
id,
cx,
cz,
loaded: chunk_comp.data.is_some(),
kind,
});
chunk_comp.entities.insert(id, entity_index);
self.entities_id_map.insert(id, entity_index);
self.push_event(Event::Entity { id, inner: EntityEvent::Spawn });
self.push_event(Event::Chunk { cx, cz, inner: ChunkEvent::Dirty });
id
}
#[inline(always)]
pub fn spawn_entity(&mut self, entity: impl Into<Box<Entity>>) -> u32 {
self.spawn_entity_inner(entity.into())
}
pub fn contains_entity(&self, id: u32) -> bool {
self.entities_id_map.contains_key(&id)
}
#[inline]
pub fn get_entity_count(&self) -> usize {
self.entities.len()
}
pub fn get_entity(&self, id: u32) -> Option<&Entity> {
let index = *self.entities_id_map.get(&id)?;
self.entities.get(index).unwrap().inner.as_deref()
}
pub fn get_entity_mut(&mut self, id: u32) -> Option<&mut Entity> {
let index = *self.entities_id_map.get(&id)?;
self.entities.get_mut(index).unwrap().inner.as_deref_mut()
}
pub fn remove_entity(&mut self, id: u32, reason: &str) -> bool {
self.remove_entity_inner(id, true, reason).is_some()
}
fn remove_entity_inner(&mut self, id: u32, has_chunk: bool, reason: &str) -> Option<EntityComponent> {
let index = self.entities_id_map.remove(&id)?;
self.entities_player_map.remove(&id);
let comp = self.entities.remove(index);
let swapped_index = self.entities.len();
debug_assert_eq!(comp.id, id, "entity incoherent id");
trace!("remove entity #{id} ({:?}): {reason}", comp.kind);
let (cx, cz) = (comp.cx, comp.cz);
if has_chunk {
let removed_index = self.chunks.get_mut(&(cx, cz))
.expect("entity chunk is missing")
.entities
.remove(&id);
debug_assert_eq!(removed_index, Some(index), "entity is incoherent in its chunk");
}
if let Some(swapped_comp) = self.entities.get(index) {
let prev_index = self.entities_id_map.insert(swapped_comp.id, index);
debug_assert_eq!(prev_index, Some(swapped_index), "swapped entity is incoherent");
self.entities_player_map.entry(swapped_comp.id).and_modify(|i| *i = index);
let (swapped_cx, swapped_cz) = (swapped_comp.cx, swapped_comp.cz);
if has_chunk || (swapped_cx, swapped_cz) != (cx, cz) {
let removed_index = self.chunks.get_mut(&(swapped_cx, swapped_cz))
.expect("swapped entity chunk is missing")
.entities
.insert(swapped_comp.id, index);
debug_assert_eq!(removed_index, Some(swapped_index), "swapped entity is incoherent in its chunk");
}
}
self.push_event(Event::Entity { id, inner: EntityEvent::Remove });
if has_chunk {
self.push_event(Event::Chunk { cx, cz, inner: ChunkEvent::Dirty });
}
Some(comp)
}
pub fn set_entity_player(&mut self, id: u32, player: bool) -> bool {
let Some(&index) = self.entities_id_map.get(&id) else { return false };
if player {
self.entities_player_map.insert(id, index);
} else {
self.entities_player_map.remove(&id);
}
true
}
pub fn is_entity_player(&mut self, id: u32) -> bool {
self.entities_player_map.contains_key(&id)
}
#[inline]
pub fn get_entity_player_count(&self) -> usize {
self.entities_player_map.len()
}
#[inline(never)]
fn set_block_entity_inner(&mut self, pos: IVec3, block_entity: Box<BlockEntity>) {
trace!("set block entity {pos}");
let (cx, cz) = calc_chunk_pos_unchecked(pos);
match self.block_entities_pos_map.entry(pos) {
Entry::Occupied(o) => {
let index = *o.into_mut();
self.block_entities.get_mut(index).unwrap().inner = Some(block_entity);
self.block_entities.invalidate(index);
self.push_event(Event::BlockEntity { pos, inner: BlockEntityEvent::Remove });
}
Entry::Vacant(v) => {
let chunk_comp = self.chunks.entry((cx, cz)).or_default();
let block_entity_index = self.block_entities.push(BlockEntityComponent {
inner: Some(block_entity),
loaded: chunk_comp.data.is_some(),
pos,
});
chunk_comp.block_entities.insert(pos, block_entity_index);
v.insert(block_entity_index);
}
}
self.push_event(Event::BlockEntity { pos, inner: BlockEntityEvent::Set });
self.push_event(Event::Chunk { cx, cz, inner: ChunkEvent::Dirty });
}
#[inline(always)]
pub fn set_block_entity(&mut self, pos: IVec3, block_entity: impl Into<Box<BlockEntity>>) {
self.set_block_entity_inner(pos, block_entity.into());
}
pub fn contains_block_entity(&self, pos: IVec3) -> bool {
self.block_entities_pos_map.contains_key(&pos)
}
#[inline]
pub fn get_block_entity_count(&self) -> usize {
self.block_entities.len()
}
pub fn get_block_entity(&self, pos: IVec3) -> Option<&BlockEntity> {
let index = *self.block_entities_pos_map.get(&pos)?;
self.block_entities.get(index).unwrap().inner.as_deref()
}
pub fn get_block_entity_mut(&mut self, pos: IVec3) -> Option<&mut BlockEntity> {
let index = *self.block_entities_pos_map.get(&pos)?;
self.block_entities.get_mut(index).unwrap().inner.as_deref_mut()
}
pub fn remove_block_entity(&mut self, pos: IVec3) -> bool {
self.remove_block_entity_inner(pos, true).is_some()
}
fn remove_block_entity_inner(&mut self, pos: IVec3, has_chunk: bool) -> Option<BlockEntityComponent> {
let index = self.block_entities_pos_map.remove(&pos)?;
trace!("remove block entity {pos}");
let comp = self.block_entities.remove(index);
let swapped_index = self.block_entities.len();
debug_assert_eq!(comp.pos, pos, "block entity incoherent position");
let (cx, cz) = calc_chunk_pos_unchecked(pos);
if has_chunk {
let removed_index = self.chunks.get_mut(&(cx, cz))
.expect("block entity chunk is missing")
.block_entities
.remove(&pos);
debug_assert_eq!(removed_index, Some(index), "block entity is incoherent in its chunk");
}
if let Some(swapped_comp) = self.block_entities.get(index) {
let prev_index = self.block_entities_pos_map.insert(swapped_comp.pos, index);
debug_assert_eq!(prev_index, Some(swapped_index), "swapped block entity is incoherent");
let (swapped_cx, swapped_cz) = calc_chunk_pos_unchecked(swapped_comp.pos);
if has_chunk || (swapped_cx, swapped_cz) != (cx, cz) {
let removed_index = self.chunks.get_mut(&(swapped_cx, swapped_cz))
.expect("swapped block entity chunk is missing")
.block_entities
.insert(swapped_comp.pos, index);
debug_assert_eq!(removed_index, Some(swapped_index), "swapped block entity is incoherent in its chunk");
}
}
self.push_event(Event::BlockEntity { pos, inner: BlockEntityEvent::Remove });
if has_chunk {
self.push_event(Event::Chunk { cx, cz, inner: ChunkEvent::Dirty });
}
Some(comp)
}
pub fn schedule_block_tick(&mut self, pos: IVec3, id: u8, delay: u64) {
let uid = self.block_ticks_count;
self.block_ticks_count = self.block_ticks_count.checked_add(1)
.expect("scheduled ticks count overflow");
let state = BlockTickState { pos, id };
if self.block_ticks_states.insert(state) {
self.block_ticks.insert(BlockTick { time: self.time + delay, state, uid });
}
}
#[inline]
pub fn get_block_tick_count(&self) -> usize {
self.block_ticks.len()
}
#[inline]
pub fn iter_blocks_in(&self, min: IVec3, max: IVec3) -> BlocksInIter<'_> {
BlocksInIter::new(self, min, max)
}
#[inline]
pub fn iter_blocks_in_chunk(&self, cx: i32, cz: i32) -> BlocksInChunkIter<'_> {
BlocksInChunkIter::new(self, cx, cz)
}
#[inline]
pub fn iter_entities(&self) -> EntitiesIter<'_> {
EntitiesIter(self.entities.iter())
}
#[inline]
pub fn iter_entities_mut(&mut self) -> EntitiesIterMut<'_> {
EntitiesIterMut(self.entities.iter_mut())
}
#[inline]
pub fn iter_player_entities(&self) -> PlayerEntitiesIter<'_> {
PlayerEntitiesIter {
indices: Some(self.entities_player_map.values()),
entities: &self.entities,
}
}
#[inline]
pub fn iter_player_entities_mut(&mut self) -> PlayerEntitiesIterMut<'_> {
PlayerEntitiesIterMut {
indices: Some(self.entities_player_map.values()),
entities: &mut self.entities,
#[cfg(debug_assertions)]
returned_pointers: HashSet::new(),
}
}
#[inline]
pub fn iter_entities_in_chunk(&self, cx: i32, cz: i32) -> EntitiesInChunkIter<'_> {
EntitiesInChunkIter {
indices: self.chunks.get(&(cx, cz)).map(|comp| comp.entities.values()),
entities: &self.entities,
}
}
#[inline]
pub fn iter_entities_in_chunk_mut(&mut self, cx: i32, cz: i32) -> EntitiesInChunkIterMut<'_> {
EntitiesInChunkIterMut {
indices: self.chunks.get(&(cx, cz)).map(|comp| comp.entities.values()),
entities: &mut self.entities,
#[cfg(debug_assertions)]
returned_pointers: HashSet::new(),
}
}
#[inline]
pub fn iter_entities_colliding(&self, bb: BoundingBox) -> EntitiesCollidingIter<'_> {
let (start_cx, start_cz) = calc_entity_chunk_pos(bb.min - 2.0);
let (end_cx, end_cz) = calc_entity_chunk_pos(bb.max + 2.0);
EntitiesCollidingIter {
chunks: ChunkComponentsIter {
chunks: &self.chunks,
range: ChunkRange::new(start_cx, start_cz, end_cx, end_cz) },
indices: None,
entities: &self.entities,
bb,
}
}
#[inline]
pub fn iter_entities_colliding_mut(&mut self, bb: BoundingBox) -> EntitiesCollidingIterMut<'_> {
let (start_cx, start_cz) = calc_entity_chunk_pos(bb.min - 2.0);
let (end_cx, end_cz) = calc_entity_chunk_pos(bb.max + 2.0);
EntitiesCollidingIterMut {
chunks: ChunkComponentsIter {
chunks: &self.chunks,
range: ChunkRange::new(start_cx, start_cz, end_cx, end_cz) },
indices: None,
entities: &mut self.entities,
bb,
#[cfg(debug_assertions)]
returned_pointers: HashSet::new(),
}
}
pub fn has_entity_colliding(&self, bb: BoundingBox, hard: bool) -> bool {
self.iter_entities_colliding(bb)
.any(|(_, entity)| !hard || entity.kind().is_hard())
}
pub fn tick(&mut self) {
if self.time % 20 == 0 {
}
self.tick_weather();
self.tick_natural_spawn();
self.tick_sky_light();
self.time += 1;
self.tick_blocks();
self.tick_entities();
self.tick_block_entities();
self.tick_light(1000);
}
fn tick_weather(&mut self) {
if self.dimension == Dimension::Nether {
return;
}
if self.time >= self.weather_next_time {
if self.time != 0 {
let new_weather = match self.weather {
Weather::Clear => self.rand.next_choice(&[Weather::Rain, Weather::Thunder]),
_ => self.rand.next_choice(&[self.weather, Weather::Clear]),
};
self.set_weather(new_weather);
}
let bound = if self.weather == Weather::Clear { 168000 } else { 12000 };
let delay = self.rand.next_int_bounded(bound) as u64 + 12000;
self.weather_next_time = self.time + delay;
}
}
fn tick_natural_spawn(&mut self) {
const CHUNK_MAX_DIST: u32 = 8;
const SPAWN_MIN_DIST_SQUARED: f64 = 24.0 * 24.0;
let mut categories_count = [0; EntityCategory::ALL.len()];
for comp in self.entities.iter() {
if comp.loaded {
if let Some(entity) = comp.inner.as_deref() {
categories_count[entity.category() as usize] += 1;
}
}
}
let mut loaded_chunks = LOADED_CHUNKS.take();
loaded_chunks.clear();
loaded_chunks.extend(self.chunks.iter()
.filter_map(|(&pos, comp)| comp.data.is_some().then_some(pos)));
loaded_chunks.retain(|&(cx, cz)| {
self.entities_player_map.values()
.map(|&index| self.entities.get(index).unwrap())
.any(|comp| comp.cx.abs_diff(cx) <= CHUNK_MAX_DIST && comp.cz.abs_diff(cz) <= CHUNK_MAX_DIST)
});
for category in EntityCategory::ALL {
let max_world_count = category.natural_spawn_max_world_count();
if max_world_count == 0 {
continue;
}
if categories_count[category as usize] > max_world_count * self.chunks.len() / 256 {
continue;
}
for &(cx, cz) in &loaded_chunks {
let chunk = self.chunks.get(&(cx, cz)).unwrap();
let chunk_data = chunk.data.as_deref().unwrap();
let biome = chunk_data.get_biome(IVec3::ZERO);
let kinds = biome.natural_entity_kinds(category);
if kinds.is_empty() {
continue;
}
let center_pos = IVec3 {
x: cx * 16 + self.rand.next_int_bounded(16),
y: self.rand.next_int_bounded(128),
z: cz * 16 + self.rand.next_int_bounded(16),
};
let (block, _) = chunk_data.get_block(center_pos);
if block::material::get_material(block) != category.natural_spawn_material() {
continue;
}
let chance_sum = kinds.iter().map(|kind| kind.chance).sum::<u16>();
let index = self.rand.next_int_bounded(chance_sum as i32) as u16;
let mut chance_acc = 0;
let mut kind = kinds[0].kind;
for test_kind in kinds {
chance_acc += test_kind.chance;
if index < chance_acc {
kind = test_kind.kind;
break;
}
}
let max_chunk_count = kind.natural_spawn_max_chunk_count();
let mut spawn_count = 0usize;
'pack: for _ in 0..3 {
let mut spawn_pos = center_pos;
'chain: for _ in 0..4 {
spawn_pos += IVec3 {
x: self.rand.next_int_bounded(6) - self.rand.next_int_bounded(6),
y: self.rand.next_int_bounded(1) - self.rand.next_int_bounded(1),
z: self.rand.next_int_bounded(6) - self.rand.next_int_bounded(6),
};
if category == EntityCategory::WaterAnimal {
if !self.get_block_material(spawn_pos).is_fluid() {
continue;
}
if self.is_block_opaque_cube(spawn_pos + IVec3::Y) {
continue;
}
} else {
if self.is_block_opaque_cube(spawn_pos) || self.is_block_opaque_cube(spawn_pos + IVec3::Y) {
continue;
}
if !self.is_block_opaque_cube(spawn_pos - IVec3::Y) {
continue;
}
}
let spawn_pos = spawn_pos.as_dvec3() + DVec3::new(0.5, 0.0, 0.5);
let mut close_player = false;
for (_, Entity(player_base, _)) in self.iter_player_entities() {
let player_dist_sq = player_base.pos.distance_squared(spawn_pos);
if player_dist_sq < SPAWN_MIN_DIST_SQUARED {
continue 'chain;
} else if player_dist_sq <= 128.0 * 128.0 {
close_player = true;
}
}
if !close_player {
continue;
}
let mut entity = kind.new_default(spawn_pos);
entity.0.persistent = true;
entity.0.look.x = self.rand.next_float() * std::f32::consts::TAU;
entity.init_natural_spawn(self);
if !entity.can_natural_spawn(self) {
continue;
}
self.spawn_entity(entity);
spawn_count += 1;
if spawn_count >= max_chunk_count {
break 'pack;
}
}
}
}
}
LOADED_CHUNKS.set(loaded_chunks);
}
fn tick_sky_light(&mut self) {
let time_wrapped = self.time % 24000;
let mut half_turn = (time_wrapped as f32 + 1.0) / 24000.0 - 0.25;
if half_turn < 0.0 {
half_turn += 1.0;
} else if half_turn > 1.0 {
half_turn -= 1.0;
}
let celestial_angle = match self.dimension {
Dimension::Nether => 0.5,
_ => half_turn + (1.0 - ((half_turn * std::f32::consts::PI).cos() + 1.0) / 2.0 - half_turn) / 3.0,
};
let factor = (celestial_angle * std::f32::consts::TAU).cos() * 2.0 + 0.5;
let factor = factor.clamp(0.0, 1.0);
let factor = match self.weather {
Weather::Clear => 1.0,
Weather::Rain => 0.6875,
Weather::Thunder => 0.47265625,
} * factor;
self.sky_light_subtracted = ((1.0 - factor) * 11.0) as u8;
}
fn tick_blocks(&mut self) {
debug_assert_eq!(self.block_ticks.len(), self.block_ticks_states.len());
while let Some(tick) = self.block_ticks.first() {
if self.time > tick.time {
let tick = self.block_ticks.pop_first().unwrap();
assert!(self.block_ticks_states.remove(&tick.state));
if let Some((id, metadata)) = self.get_block(tick.state.pos) {
if id == tick.state.id {
self.tick_block_unchecked(tick.state.pos, id, metadata, false);
}
}
} else {
break;
}
}
let mut pending_random_ticks = RANDOM_TICKS_PENDING.take();
debug_assert!(pending_random_ticks.is_empty());
for (&(cx, cz), chunk) in &mut self.chunks {
if let Some(chunk_data) = &chunk.data {
let chunk_pos = IVec3::new(cx * CHUNK_WIDTH as i32, 0, cz * CHUNK_WIDTH as i32);
for _ in 0..80 {
self.random_ticks_seed = self.random_ticks_seed
.wrapping_mul(3)
.wrapping_add(1013904223);
let rand = self.random_ticks_seed >> 2;
let pos = IVec3::new((rand >> 0) & 15, (rand >> 16) & 127, (rand >> 8) & 15);
let (id, metadata) = chunk_data.get_block(pos);
pending_random_ticks.push((chunk_pos + pos, id, metadata));
}
}
}
for (pos, id, metadata) in pending_random_ticks.drain(..) {
self.tick_block_unchecked(pos, id, metadata, true);
}
RANDOM_TICKS_PENDING.set(pending_random_ticks);
}
fn tick_entities(&mut self) {
self.entities.reset();
while let Some((_, comp)) = self.entities.current_mut() {
if !comp.loaded {
self.entities.advance();
continue;
}
let mut entity = comp.inner.take()
.expect("entity was already being updated");
let id = comp.id;
let (prev_cx, prev_cz) = (comp.cx, comp.cz);
entity.tick(&mut *self, id);
if let Some((index, comp)) = self.entities.current_mut() {
debug_assert_eq!(comp.id, id, "entity id incoherent");
let (new_cx, new_cz) = calc_entity_chunk_pos(entity.0.pos);
comp.inner = Some(entity);
if (prev_cx, prev_cz) != (new_cx, new_cz) {
let removed_index = self.chunks.get_mut(&(prev_cx, prev_cz))
.expect("entity previous chunk is missing")
.entities.remove(&id);
debug_assert_eq!(removed_index, Some(index), "entity is incoherent in its previous chunk");
comp.cx = new_cx;
comp.cz = new_cz;
let new_chunk_comp = self.chunks.entry((new_cx, new_cz)).or_default();
let insert_success = new_chunk_comp.entities.insert(id, index).is_none();
debug_assert!(insert_success, "entity was already present in its new chunk");
comp.loaded = new_chunk_comp.data.is_some();
self.push_event(Event::Chunk { cx: prev_cx, cz: prev_cz, inner: ChunkEvent::Dirty });
self.push_event(Event::Chunk { cx: new_cx, cz: new_cz, inner: ChunkEvent::Dirty });
}
}
self.entities.advance();
}
}
fn tick_block_entities(&mut self) {
self.block_entities.reset();
while let Some((_, comp)) = self.block_entities.current_mut() {
if !comp.loaded {
self.block_entities.advance();
continue;
}
let mut block_entity = comp.inner.take()
.expect("block entity was already being updated");
let pos = comp.pos;
block_entity.tick(self, pos);
if let Some((_, comp)) = self.block_entities.current_mut() {
comp.inner = Some(block_entity);
}
self.block_entities.advance();
}
}
pub fn tick_light(&mut self, limit: usize) {
for _ in 0..limit {
let Some(update) = self.light_updates.pop_front() else { break };
let mut max_face_emission = 0;
for face in Face::ALL {
let face_pos = update.pos + face.delta();
let Some((cx, cz)) = calc_chunk_pos(face_pos) else { continue };
let Some(chunk) = self.get_chunk_mut(cx, cz) else { continue };
let face_emission = match update.kind {
LightKind::Block => chunk.get_block_light(face_pos),
LightKind::Sky => chunk.get_sky_light(face_pos),
};
max_face_emission = max_face_emission.max(face_emission);
if max_face_emission == 15 {
break;
}
}
let Some((cx, cz)) = calc_chunk_pos(update.pos) else { continue };
let Some(chunk) = self.get_chunk_mut(cx, cz) else { continue };
let (id, _) = chunk.get_block(update.pos);
let opacity = block::material::get_light_opacity(id).max(1);
let emission = match update.kind {
LightKind::Block => block::material::get_light_emission(id),
LightKind::Sky => {
let column_height = chunk.get_height(update.pos) as i32;
if update.pos.y >= column_height { 15 } else { 0 }
}
};
let new_light = emission.max(max_face_emission.saturating_sub(opacity));
let mut changed = false;
let mut sky_exposed = false;
match update.kind {
LightKind::Block => {
if chunk.get_block_light(update.pos) != new_light {
chunk.set_block_light(update.pos, new_light);
changed = true;
}
}
LightKind::Sky => {
if chunk.get_sky_light(update.pos) != new_light {
chunk.set_sky_light(update.pos, new_light);
changed = true;
sky_exposed = emission == 15;
}
}
}
if changed {
self.push_event(Event::Chunk { cx, cz, inner: ChunkEvent::Dirty });
}
if changed && update.credit >= 1 {
for face in Face::ALL {
if face == Face::PosY && sky_exposed {
continue;
}
self.light_updates.push_back(LightUpdate {
kind: update.kind,
pos: update.pos + face.delta(),
credit: update.credit - 1,
});
}
}
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum Dimension {
Overworld,
Nether,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum Weather {
Clear,
Rain,
Thunder,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct Light {
pub block: u8,
pub sky: u8,
pub sky_real: u8,
}
impl Light {
#[inline]
pub fn max(self) -> u8 {
u8::max(self.block, self.sky)
}
#[inline]
pub fn max_real(self) -> u8 {
u8::max(self.block, self.sky_real)
}
#[inline]
pub fn brightness(self) -> f32 {
const OFFSET: f32 = 0.05;
let base = 1.0 - self.max_real() as f32 / 15.0;
(1.0 - base) * (base * 3.0 + 1.0) * (1.0 - OFFSET) + OFFSET
}
}
#[derive(Clone, Copy, PartialEq, Eq)]
pub enum LightKind {
Block,
Sky,
}
#[derive(Debug, Clone, PartialEq)]
pub enum Event {
Block {
pos: IVec3,
inner: BlockEvent,
},
Entity {
id: u32,
inner: EntityEvent,
},
BlockEntity {
pos: IVec3,
inner: BlockEntityEvent,
},
Chunk {
cx: i32,
cz: i32,
inner: ChunkEvent,
},
Weather {
prev: Weather,
new: Weather,
},
Explode {
center: DVec3,
radius: f32,
},
DebugParticle {
pos: IVec3,
block: u8,
}
}
#[derive(Debug, Clone, PartialEq)]
pub enum BlockEvent {
Set {
id: u8,
metadata: u8,
prev_id: u8,
prev_metadata: u8,
},
Sound {
id: u8,
metadata: u8,
},
}
#[derive(Debug, Clone, PartialEq)]
pub enum EntityEvent {
Spawn,
Remove,
Position {
pos: DVec3,
},
Look {
look: Vec2,
},
Velocity {
vel: DVec3,
},
Pickup {
target_id: u32,
},
Damage,
Dead,
Metadata,
}
#[derive(Debug, Clone, PartialEq)]
pub enum BlockEntityEvent {
Set,
Remove,
Storage {
storage: BlockEntityStorage,
stack: ItemStack,
},
Progress {
progress: BlockEntityProgress,
value: u16,
},
}
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
pub enum BlockEntityStorage {
Standard(u8),
FurnaceInput,
FurnaceOutput,
FurnaceFuel,
}
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
pub enum BlockEntityProgress {
FurnaceSmeltTime,
FurnaceBurnMaxTime,
FurnaceBurnRemainingTime,
}
#[derive(Debug, Clone, PartialEq)]
pub enum ChunkEvent {
Set,
Remove,
Dirty,
}
#[derive(Clone)]
pub struct ChunkSnapshot {
pub cx: i32,
pub cz: i32,
pub chunk: Arc<Chunk>,
pub entities: Vec<Box<Entity>>,
pub block_entities: HashMap<IVec3, Box<BlockEntity>>,
}
impl ChunkSnapshot {
pub fn new(cx: i32, cz: i32) -> Self {
Self {
cx,
cz,
chunk: Chunk::new(),
entities: Vec::new(),
block_entities: HashMap::new(),
}
}
}
#[derive(Default, Clone)]
struct ChunkComponent {
data: Option<Arc<Chunk>>,
entities: IndexMap<u32, usize>,
block_entities: HashMap<IVec3, usize>,
}
#[derive(Debug, Clone)]
struct EntityComponent {
inner: Option<Box<Entity>>,
id: u32,
cx: i32,
cz: i32,
loaded: bool,
kind: EntityKind,
}
#[derive(Debug, Clone)]
struct BlockEntityComponent {
inner: Option<Box<BlockEntity>>,
loaded: bool,
pos: IVec3,
}
#[derive(Clone, Copy, Hash, PartialEq, Eq)]
struct BlockTickState {
pos: IVec3,
id: u8,
}
#[derive(Clone, Eq)]
struct BlockTick {
uid: u64,
time: u64,
state: BlockTickState,
}
impl PartialEq for BlockTick {
fn eq(&self, other: &Self) -> bool {
self.uid == other.uid && self.time == other.time
}
}
impl PartialOrd for BlockTick {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(Ord::cmp(self, other))
}
}
impl Ord for BlockTick {
fn cmp(&self, other: &Self) -> Ordering {
self.time.cmp(&other.time)
.then(self.uid.cmp(&other.uid))
}
}
#[derive(Clone)]
struct LightUpdate {
kind: LightKind,
pos: IVec3,
credit: u8,
}
#[derive(Clone)]
struct TickVec<T> {
inner: Vec<TickCell<T>>,
modified: bool,
index: usize,
invalidated: bool,
}
#[derive(Clone)]
struct TickCell<T> {
value: T,
prev: usize,
next: usize,
}
#[repr(transparent)]
struct TickSlice<T>([TickCell<T>]);
impl<T> TickVec<T> {
const END: usize = usize::MAX;
#[inline]
const fn new() -> Self {
Self {
inner: Vec::new(),
modified: false,
index: Self::END,
invalidated: false,
}
}
#[inline]
fn push(&mut self, value: T) -> usize {
let index = self.inner.len();
self.inner.push(TickCell {
value,
prev: Self::END,
next: Self::END,
});
self.modified = true;
index
}
fn invalidate(&mut self, index: usize) {
let cell = &mut self.inner[index];
let prev = mem::replace(&mut cell.prev, Self::END);
let next = mem::replace(&mut cell.next, Self::END);
if prev != Self::END {
self.inner[prev].next = next;
}
if next != Self::END {
self.inner[next].prev = prev;
}
if self.index == index {
self.invalidated = true;
self.index = next;
}
}
fn remove(&mut self, index: usize) -> T {
self.invalidate(index);
let cell = self.inner.swap_remove(index);
let swapped_index = self.inner.len();
if let Some(&TickCell { prev, next, .. }) = self.inner.get(index) {
if prev != Self::END {
self.inner[prev].next = index;
}
if next != Self::END {
self.inner[next].prev = index;
}
}
if self.index == swapped_index {
self.index = index;
}
self.modified = true;
cell.value
}
fn reset(&mut self) {
if self.inner.is_empty() {
self.index = Self::END;
return;
}
self.index = 0;
if mem::take(&mut self.modified) {
for i in 1..self.inner.len() {
self.inner[i - 1].next = i;
self.inner[i].prev = i - 1;
}
if let Some(cell) = self.inner.first_mut() {
cell.prev = Self::END;
}
if let Some(cell) = self.inner.last_mut() {
cell.next = Self::END;
}
}
}
fn advance(&mut self) {
if self.invalidated {
self.invalidated = false;
} else {
if let Some(cell) = self.inner.get(self.index) {
self.index = cell.next;
} else {
debug_assert_eq!(self.index, Self::END);
}
}
}
#[inline]
#[allow(unused)]
fn current(&self) -> Option<(usize, &T)> {
if self.invalidated { return None }
self.inner.get(self.index).map(|cell| (self.index, &cell.value))
}
#[inline]
fn current_mut(&mut self) -> Option<(usize, &mut T)> {
if self.invalidated { return None }
self.inner.get_mut(self.index).map(|cell| (self.index, &mut cell.value))
}
}
impl<T> TickSlice<T> {
#[inline]
fn len(&self) -> usize {
self.0.len()
}
#[inline]
#[allow(unused)]
fn is_empty(&self) -> bool {
self.0.is_empty()
}
#[inline]
fn get(&self, index: usize) -> Option<&T> {
self.0.get(index).map(|cell| &cell.value)
}
#[inline]
fn get_mut(&mut self, index: usize) -> Option<&mut T> {
self.0.get_mut(index).map(|cell| &mut cell.value)
}
#[inline]
fn iter(&self) -> TickIter<'_, T> {
TickIter { inner: self.0.iter() }
}
#[inline]
fn iter_mut(&mut self) -> TickIterMut<'_, T> {
TickIterMut { inner: self.0.iter_mut() }
}
}
impl<T> Deref for TickVec<T> {
type Target = TickSlice<T>;
#[inline]
fn deref(&self) -> &Self::Target {
let slice = &self.inner[..];
unsafe { &*(slice as *const [TickCell<T>] as *const TickSlice<T>) }
}
}
impl<T> DerefMut for TickVec<T> {
#[inline]
fn deref_mut(&mut self) -> &mut Self::Target {
let slice = &mut self.inner[..];
unsafe { &mut *(slice as *mut [TickCell<T>] as *mut TickSlice<T>) }
}
}
struct TickIter<'a, T> {
inner: slice::Iter<'a, TickCell<T>>
}
impl<T> FusedIterator for TickIter<'_, T> {}
impl<'a, T> Iterator for TickIter<'a, T> {
type Item = &'a T;
#[inline]
fn next(&mut self) -> Option<Self::Item> {
self.inner.next().map(|cell| &cell.value)
}
}
struct TickIterMut<'a, T> {
inner: slice::IterMut<'a, TickCell<T>>
}
impl<T> FusedIterator for TickIterMut<'_, T> {}
impl<'a, T> Iterator for TickIterMut<'a, T> {
type Item = &'a mut T;
#[inline]
fn next(&mut self) -> Option<Self::Item> {
self.inner.next().map(|cell| &mut cell.value)
}
}
pub struct BlocksInIter<'a> {
world: &'a World,
chunk: Option<(i32, i32, Option<&'a Chunk>)>,
start: IVec3,
end: IVec3,
cursor: IVec3,
}
impl<'a> BlocksInIter<'a> {
#[inline]
fn new(world: &'a World, mut start: IVec3, mut end: IVec3) -> Self {
debug_assert!(start.x <= end.x && start.y <= end.y && start.z <= end.z);
start.y = start.y.clamp(0, CHUNK_HEIGHT as i32 - 1);
end.y = end.y.clamp(0, CHUNK_HEIGHT as i32 - 1);
if start.x == end.x || start.y == end.y || start.z == end.z {
end = start;
}
Self {
world,
chunk: None,
start,
end,
cursor: start,
}
}
}
impl FusedIterator for BlocksInIter<'_> {}
impl Iterator for BlocksInIter<'_> {
type Item = (IVec3, u8, u8);
fn next(&mut self) -> Option<Self::Item> {
if self.cursor.x == self.end.x {
return None;
}
if self.cursor.y == self.start.y {
let (cx, cz) = calc_chunk_pos_unchecked(self.cursor);
if !matches!(self.chunk, Some((ccx, ccz, _)) if (ccx, ccz) == (cx, cz)) {
self.chunk = Some((cx, cz, self.world.get_chunk(cx, cz)));
}
}
let mut ret = (self.cursor, 0, 0);
if let Some((_, _, Some(chunk))) = self.chunk {
let (block, metadata) = chunk.get_block(self.cursor);
ret.1 = block;
ret.2 = metadata;
}
self.cursor.y += 1;
if self.cursor.y == self.end.y {
self.cursor.y = self.start.y;
self.cursor.z += 1;
if self.cursor.z == self.end.z {
self.cursor.z = self.start.z;
self.cursor.x += 1;
}
}
Some(ret)
}
}
pub struct BlocksInChunkIter<'a> {
chunk: Option<&'a Chunk>,
cursor: IVec3,
}
impl<'a> BlocksInChunkIter<'a> {
#[inline]
fn new(world: &'a World, cx: i32, cz: i32) -> Self {
Self {
chunk: world.get_chunk(cx, cz),
cursor: IVec3::new(cx * CHUNK_WIDTH as i32, 0, cz * CHUNK_WIDTH as i32),
}
}
}
impl FusedIterator for BlocksInChunkIter<'_> {}
impl Iterator for BlocksInChunkIter<'_> {
type Item = (IVec3, u8, u8);
#[inline]
fn next(&mut self) -> Option<Self::Item> {
let (block, metadata) = self.chunk?.get_block(self.cursor);
let ret = (self.cursor, block, metadata);
self.cursor.y += 1;
if self.cursor.y >= CHUNK_HEIGHT as i32 {
self.cursor.y = 0;
self.cursor.z += 1;
if self.cursor.z & 0b1111 == 0 {
self.cursor.z -= 16;
self.cursor.x += 1;
if self.cursor.x & 0b1111 == 0 {
self.chunk = None;
}
}
}
Some(ret)
}
}
pub struct EntitiesIter<'a>(TickIter<'a, EntityComponent>);
impl FusedIterator for EntitiesIter<'_> {}
impl ExactSizeIterator for EntitiesIter<'_> {}
impl<'a> Iterator for EntitiesIter<'a> {
type Item = (u32, &'a Entity);
#[inline]
fn next(&mut self) -> Option<Self::Item> {
while let Some(comp) = self.0.next() {
if let Some(ret) = comp.inner.as_deref() {
return Some((comp.id, ret));
}
}
None
}
#[inline]
fn size_hint(&self) -> (usize, Option<usize>) {
self.0.size_hint()
}
}
pub struct EntitiesIterMut<'a>(TickIterMut<'a, EntityComponent>);
impl FusedIterator for EntitiesIterMut<'_> {}
impl ExactSizeIterator for EntitiesIterMut<'_> {}
impl<'a> Iterator for EntitiesIterMut<'a> {
type Item = (u32, &'a mut Entity);
#[inline]
fn next(&mut self) -> Option<Self::Item> {
while let Some(comp) = self.0.next() {
if let Some(ret) = comp.inner.as_deref_mut() {
return Some((comp.id, ret));
}
}
None
}
#[inline]
fn size_hint(&self) -> (usize, Option<usize>) {
self.0.size_hint()
}
}
pub type PlayerEntitiesIter<'a> = EntitiesInChunkIter<'a>;
pub type PlayerEntitiesIterMut<'a> = EntitiesInChunkIterMut<'a>;
pub struct EntitiesInChunkIter<'a> {
indices: Option<indexmap::map::Values<'a, u32, usize>>,
entities: &'a TickSlice<EntityComponent>,
}
impl FusedIterator for EntitiesInChunkIter<'_> {}
impl<'a> Iterator for EntitiesInChunkIter<'a> {
type Item = (u32, &'a Entity);
#[inline]
fn next(&mut self) -> Option<Self::Item> {
while let Some(&index) = self.indices.as_mut()?.next() {
let comp = self.entities.get(index).unwrap();
if let Some(entity) = comp.inner.as_deref() {
return Some((comp.id, entity));
}
}
None
}
#[inline]
fn size_hint(&self) -> (usize, Option<usize>) {
if let Some(indices) = &self.indices {
indices.size_hint()
} else {
(0, Some(0))
}
}
}
pub struct EntitiesInChunkIterMut<'a> {
indices: Option<indexmap::map::Values<'a, u32, usize>>,
entities: &'a mut TickSlice<EntityComponent>,
#[cfg(debug_assertions)]
returned_pointers: HashSet<*mut Entity>,
}
impl FusedIterator for EntitiesInChunkIterMut<'_> {}
impl<'a> Iterator for EntitiesInChunkIterMut<'a> {
type Item = (u32, &'a mut Entity);
#[inline]
fn next(&mut self) -> Option<Self::Item> {
while let Some(&index) = self.indices.as_mut()?.next() {
let comp = self.entities.get_mut(index).unwrap();
if let Some(entity) = comp.inner.as_deref_mut() {
#[cfg(debug_assertions)] {
assert!(self.returned_pointers.insert(entity), "wrong unsafe contract");
}
let entity = unsafe { &mut *(entity as *mut Entity) };
return Some((comp.id, entity));
}
}
None
}
#[inline]
fn size_hint(&self) -> (usize, Option<usize>) {
if let Some(indices) = &self.indices {
indices.size_hint()
} else {
(0, Some(0))
}
}
}
pub struct EntitiesCollidingIter<'a> {
chunks: ChunkComponentsIter<'a>,
indices: Option<indexmap::map::Values<'a, u32, usize>>,
entities: &'a TickSlice<EntityComponent>,
bb: BoundingBox,
}
impl FusedIterator for EntitiesCollidingIter<'_> {}
impl<'a> Iterator for EntitiesCollidingIter<'a> {
type Item = (u32, &'a Entity);
#[inline]
fn next(&mut self) -> Option<Self::Item> {
loop {
if self.indices.is_none() {
self.indices = Some(self.chunks.next()?.entities.values());
}
if let Some(&index) = self.indices.as_mut().unwrap().next() {
let comp = self.entities.get(index).unwrap();
if let Some(entity) = comp.inner.as_deref() {
if entity.0.bb.intersects(self.bb) {
return Some((comp.id, entity));
}
}
} else {
self.indices = None;
}
}
}
}
pub struct EntitiesCollidingIterMut<'a> {
chunks: ChunkComponentsIter<'a>,
indices: Option<indexmap::map::Values<'a, u32, usize>>,
entities: &'a mut TickSlice<EntityComponent>,
bb: BoundingBox,
#[cfg(debug_assertions)]
returned_pointers: HashSet<*mut Entity>,
}
impl FusedIterator for EntitiesCollidingIterMut<'_> {}
impl<'a> Iterator for EntitiesCollidingIterMut<'a> {
type Item = (u32, &'a mut Entity);
#[inline]
fn next(&mut self) -> Option<Self::Item> {
loop {
if self.indices.is_none() {
self.indices = Some(self.chunks.next()?.entities.values());
}
if let Some(&index) = self.indices.as_mut().unwrap().next() {
let comp = self.entities.get_mut(index).unwrap();
if let Some(entity) = comp.inner.as_deref_mut() {
if entity.0.bb.intersects(self.bb) {
#[cfg(debug_assertions)] {
assert!(self.returned_pointers.insert(entity), "wrong unsafe contract");
}
let entity = unsafe { &mut *(entity as *mut Entity) };
return Some((comp.id, entity));
}
}
} else {
self.indices = None;
}
}
}
}
struct ChunkComponentsIter<'a> {
chunks: &'a HashMap<(i32, i32), ChunkComponent>,
range: ChunkRange,
}
impl FusedIterator for ChunkComponentsIter<'_> {}
impl<'a> Iterator for ChunkComponentsIter<'a> {
type Item = &'a ChunkComponent;
#[inline]
fn next(&mut self) -> Option<Self::Item> {
while let Some((cx, cz)) = self.range.next() {
if let Some(comp) = self.chunks.get(&(cx, cz)) {
return Some(comp);
}
}
None
}
}
struct ChunkRange {
cx: i32,
cz: i32,
start_cx: i32,
end_cx: i32,
end_cz: i32,
}
impl ChunkRange {
#[inline]
fn new(start_cx: i32, start_cz: i32, end_cx: i32, end_cz: i32) -> Self {
Self {
cx: start_cx,
cz: start_cz,
start_cx,
end_cx,
end_cz,
}
}
}
impl FusedIterator for ChunkRange {}
impl Iterator for ChunkRange {
type Item = (i32, i32);
#[inline]
fn next(&mut self) -> Option<Self::Item> {
if self.cx > self.end_cx || self.cz > self.end_cz {
return None;
}
let ret = (self.cx, self.cz);
self.cx += 1;
if self.cx > self.end_cx {
self.cx = self.start_cx;
self.cz += 1;
}
Some(ret)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn chunk_range() {
assert_eq!(ChunkRange::new(0, 0, 0, 0).collect::<Vec<_>>(), [(0, 0)]);
assert_eq!(ChunkRange::new(0, 0, 1, 0).collect::<Vec<_>>(), [(0, 0), (1, 0)]);
assert_eq!(ChunkRange::new(0, 0, 1, 1).collect::<Vec<_>>(), [(0, 0), (1, 0), (0, 1), (1, 1)]);
assert_eq!(ChunkRange::new(0, 0, -1, 0).collect::<Vec<_>>(), []);
assert_eq!(ChunkRange::new(0, 0, 0, -1).collect::<Vec<_>>(), []);
assert_eq!(ChunkRange::new(0, 0, -1, -1).collect::<Vec<_>>(), []);
}
#[test]
fn tick_vec() {
let mut v = TickVec::<char>::new();
assert_eq!(v.len(), 0);
assert!(v.is_empty());
assert_eq!(v.current(), None);
assert_eq!(v.push('a'), 0);
assert_eq!(v.push('b'), 1);
assert_eq!(v.push('c'), 2);
assert_eq!(v.current(), None);
assert_eq!(v.get(0), Some(&'a'));
assert_eq!(v.get(1), Some(&'b'));
assert_eq!(v.get(2), Some(&'c'));
assert_eq!(v.len(), 3);
assert_eq!(v.remove(0), 'a');
assert_eq!(v.get(0), Some(&'c'));
assert_eq!(v.len(), 2);
v.reset();
assert_eq!(v.current(), Some((0, &'c')));
v.advance();
assert_eq!(v.current(), Some((1, &'b')));
v.advance();
assert_eq!(v.current(), None);
v.reset();
assert_eq!(v.current(), Some((0, &'c')));
assert_eq!(v.remove(0), 'c');
assert_eq!(v.current(), None);
v.advance();
assert_eq!(v.current(), Some((0, &'b')));
assert_eq!(v.push('a'), 1);
v.advance();
assert_eq!(v.current(), None);
assert_eq!(v.get(1), Some(&'a'));
v.reset();
assert_eq!(v.remove(1), 'a');
assert_eq!(v.current(), Some((0, &'b')));
v.advance();
assert_eq!(v.current(), None);
}
}