use std::sync::{RwLock, Arc, RwLockReadGuard, RwLockWriteGuard};
use std::collections::{HashMap, HashSet};
use std::collections::hash_map::Entry;
use std::fmt::{Debug, Formatter};
use hecs::{World as EcsWorld, EntityBuilder, Entity, EntityRef};
use uuid::Uuid;
use crate::entity::{GlobalEntities, EntityType};
use crate::block::{GlobalBlocks, BlockState};
use crate::biome::GlobalBiomes;
use crate::heightmap::GlobalHeightmaps;
use crate::pos::{EntityPos, BlockPos};
use crate::debug;
use super::source::{LevelSource, ChunkLoadRequest, ChunkSaveRequest, LevelSourceError, ProtoChunk};
use super::chunk::{Chunk, ChunkHeight, ChunkResult, ChunkError};
pub struct LevelEnv {
pub blocks: GlobalBlocks,
pub biomes: GlobalBiomes,
pub entities: GlobalEntities,
pub heightmaps: GlobalHeightmaps
}
impl LevelEnv {
pub fn new(
blocks: GlobalBlocks,
biomes: GlobalBiomes,
entities: GlobalEntities,
heightmaps: GlobalHeightmaps
) -> Self {
LevelEnv {
blocks,
biomes,
entities,
heightmaps
}
}
}
impl Debug for LevelEnv {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
f.debug_struct("LevelEnv")
.field("states_count", &self.blocks.states_count())
.field("blocks_count", &self.blocks.blocks_count())
.field("entity_types_count", &self.entities.entity_types_count())
.field("heightmaps_count", &self.heightmaps.heightmaps_count())
.finish()
}
}
pub struct Level {
id: String,
env: Arc<LevelEnv>,
source: Box<dyn LevelSource>,
loading_chunks: HashSet<(i32, i32)>,
height: ChunkHeight,
pub chunks: ChunkStorage,
pub entities: EntityStorage
}
impl Level {
pub fn new<S>(id: String, env: Arc<LevelEnv>, height: ChunkHeight, source: S) -> Self
where
S: LevelSource + 'static,
{
assert_ne!(env.blocks.states_count(), 0, "The given environment has no state, a level requires at least one block state.");
assert_ne!(env.biomes.biomes_count(), 0, "The given environment has no biome, a level requires at least one biome.");
Level {
id,
height,
source: Box::new(source),
loading_chunks: HashSet::new(),
chunks: ChunkStorage {
chunks: HashMap::new()
},
entities: EntityStorage {
ecs: EcsWorld::new(),
builder: EntityBuilder::new()
},
env,
}
}
pub fn get_id(&self) -> &String {
&self.id
}
pub fn get_env(&self) -> &Arc<LevelEnv> {
&self.env
}
pub fn get_height(&self) -> ChunkHeight {
self.height
}
pub fn request_chunk_load(&mut self, cx: i32, cz: i32) -> bool {
if !self.loading_chunks.contains(&(cx, cz)) {
match self.source.request_chunk_load(ChunkLoadRequest {
env: Arc::clone(&self.env),
height: self.height,
cx,
cz
}) {
Ok(_) => {
self.loading_chunks.insert((cx, cz));
true
}
Err(_) => false
}
} else {
false
}
}
pub fn load_chunks_with_callback<F>(&mut self, mut callback: F)
where
F: FnMut(i32, i32, Result<&Arc<RwLock<Chunk>>, LevelSourceError>),
{
while let Some(res) = self.source.poll_chunk() {
match res {
Ok(ProtoChunk {
inner: chunk,
mut proto_entities,
dirty
}) => {
let mut chunk = *chunk;
let (cx, cz) = chunk.get_position();
self.loading_chunks.remove(&(cx, cz));
let mut entities_handles = Vec::with_capacity(proto_entities.len());
for (entity_builder, _) in &mut proto_entities {
unsafe {
let entity = self.entities.add_entity_unchecked(entity_builder);
chunk.add_entity_unchecked(entity);
entities_handles.push(entity);
}
}
for (i, (_, passengers)) in proto_entities.into_iter().enumerate() {
if !passengers.is_empty() {
let mut base_entity = self.entities.ecs.get_mut::<BaseEntity>(entities_handles[i]).unwrap();
let mut passengers_handles = Vec::with_capacity(passengers.len());
for passenger_proto_index in passengers {
passengers_handles.push(entities_handles[passenger_proto_index]);
}
base_entity.passengers = Some(passengers_handles);
}
}
let chunk_arc = self.chunks.insert_chunk(chunk);
callback(cx, cz, Ok(chunk_arc));
if dirty {
self.request_chunk_save(cx, cz);
}
},
Err((err, chunk_info)) => {
debug!("Failed to load chunk at {}/{}: {}", chunk_info.cx, chunk_info.cz, err);
self.loading_chunks.remove(&(chunk_info.cx, chunk_info.cz));
callback(chunk_info.cx, chunk_info.cz, Err(err));
}
}
}
}
#[inline]
pub fn load_chunks(&mut self) {
self.load_chunks_with_callback(|_, _, _| {});
}
#[inline]
pub fn get_loading_chunks_count(&self) -> usize {
self.loading_chunks.len()
}
pub fn request_chunk_save(&mut self, cx: i32, cz: i32) -> bool {
if let Some(chunk) = self.chunks.get_chunk_arc(cx, cz) {
self.source.request_chunk_save(ChunkSaveRequest {
cx,
cz,
chunk
}).is_ok()
} else {
false
}
}
pub fn spawn_entity(&mut self, entity_type: &'static EntityType, pos: EntityPos) -> Option<Entity> {
if !self.env.entities.has_entity_type(entity_type) {
return None;
}
let chunk = self.chunks.get_chunk_at_block_mut(BlockPos::from(&pos));
let entity = unsafe { self.entities.spawn_entity_unchecked(entity_type, pos) };
if let Some(mut chunk) = chunk {
unsafe {
chunk.add_entity_unchecked(entity);
}
}
Some(entity)
}
}
pub struct ChunkStorage {
chunks: HashMap<(i32, i32), Arc<RwLock<Chunk>>>,
}
impl ChunkStorage {
pub fn get_chunks_count(&self) -> usize {
self.chunks.len()
}
pub fn insert_chunk(&mut self, chunk: Chunk) -> &Arc<RwLock<Chunk>> {
let pos = chunk.get_position();
let arc = Arc::new(RwLock::new(chunk));
match self.chunks.entry(pos) {
Entry::Occupied(mut o) => {
o.insert(arc);
o.into_mut()
},
Entry::Vacant(v) => {
v.insert(arc)
}
}
}
pub fn get_chunk_arc(&self, cx: i32, cz: i32) -> Option<Arc<RwLock<Chunk>>> {
self.chunks.get(&(cx, cz)).map(Arc::clone)
}
pub fn is_chunk_loaded(&self, cx: i32, cz: i32) -> bool {
self.chunks.contains_key(&(cx, cz))
}
pub fn get_chunk(&self, cx: i32, cz: i32) -> Option<RwLockReadGuard<Chunk>> {
self.chunks.get(&(cx, cz)).map(|arc| arc.read().unwrap())
}
pub fn get_chunk_mut(&self, cx: i32, cz: i32) -> Option<RwLockWriteGuard<Chunk>> {
self.chunks.get(&(cx, cz)).map(|arc| arc.write().unwrap())
}
#[inline]
pub fn get_chunk_at(&self, x: i32, z: i32) -> Option<RwLockReadGuard<Chunk>> {
self.get_chunk(x >> 4, z >> 4)
}
#[inline]
pub fn get_chunk_at_mut(&self, x: i32, z: i32) -> Option<RwLockWriteGuard<Chunk>> {
self.get_chunk_mut(x >> 4, z >> 4)
}
#[inline]
pub fn get_chunk_at_block(&self, block_pos: BlockPos) -> Option<RwLockReadGuard<Chunk>> {
self.get_chunk_at(block_pos.x, block_pos.z)
}
#[inline]
pub fn get_chunk_at_block_mut(&self, block_pos: BlockPos) -> Option<RwLockWriteGuard<Chunk>> {
self.get_chunk_at_mut(block_pos.x, block_pos.z)
}
pub fn set_block_at(&self, x: i32, y: i32, z: i32, block: &'static BlockState) -> ChunkResult<()> {
if let Some(mut chunk) = self.get_chunk_at_mut(x, z) {
chunk.set_block_at(x, y, z, block)
} else {
Err(ChunkError::ChunkUnloaded)
}
}
pub fn get_block_at(&self, x: i32, y: i32, z: i32) -> ChunkResult<&'static BlockState> {
if let Some(chunk) = self.get_chunk_at(x, z) {
chunk.get_block_at(x, y, z)
} else {
Err(ChunkError::ChunkUnloaded)
}
}
}
pub struct EntityStorage {
pub ecs: EcsWorld,
builder: EntityBuilder,
}
impl EntityStorage {
pub unsafe fn spawn_entity_unchecked(&mut self, entity_type: &'static EntityType, pos: EntityPos) -> Entity {
self.builder.add(BaseEntity::new(entity_type, Uuid::new_v4(), pos));
for &component in entity_type.codecs {
component.default(&mut self.builder);
}
self.ecs.spawn(self.builder.build())
}
pub unsafe fn add_entity_unchecked(&mut self, entity_builder: &mut EntityBuilder) -> Entity {
self.ecs.spawn(entity_builder.build())
}
pub fn remove_entity(&mut self, entity: Entity) -> bool {
self.ecs.despawn(entity).is_ok()
}
pub fn get_entity_ref(&self, entity: Entity) -> Option<EntityRef> {
self.ecs.entity(entity).ok()
}
}
pub struct BaseEntity {
pub entity_type: &'static EntityType,
pub uuid: Uuid,
pub pos: EntityPos,
passengers: Option<Vec<Entity>>
}
impl BaseEntity {
pub fn new(entity_type: &'static EntityType, uuid: Uuid, pos: EntityPos) -> Self {
Self {
entity_type,
uuid,
pos,
passengers: None
}
}
}