Skip to content

Commit

Permalink
Merge pull request #162 from Bryntet/make-block-breaking
Browse files Browse the repository at this point in the history
Block breaking and placing
  • Loading branch information
lukas0008 authored Oct 21, 2024
2 parents 60f1bb6 + b0009d1 commit 746b656
Show file tree
Hide file tree
Showing 8 changed files with 206 additions and 122 deletions.
24 changes: 22 additions & 2 deletions pumpkin-core/src/math/position.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,33 @@
use super::vector3::Vector3;
use std::fmt;

use crate::math::vector2::Vector2;
use num_traits::Euclid;
use serde::{Deserialize, Serialize};

use super::vector3::Vector3;

#[derive(Clone, Copy)]
/// Aka Block Position
pub struct WorldPosition(pub Vector3<i32>);

impl WorldPosition {
pub fn chunk_and_chunk_relative_position(&self) -> (Vector2<i32>, Vector3<i32>) {
let (z_chunk, z_rem) = self.0.z.div_rem_euclid(&16);
let (x_chunk, x_rem) = self.0.x.div_rem_euclid(&16);
let chunk_coordinate = Vector2 {
x: x_chunk,
z: z_chunk,
};

// Since we divide by 16 remnant can never exceed u8
let relative = Vector3 {
x: x_rem,
z: z_rem,

y: self.0.y,
};
(chunk_coordinate, relative)
}
}
impl Serialize for WorldPosition {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
Expand Down
1 change: 1 addition & 0 deletions pumpkin-world/src/chunk/anvil.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ use crate::level::SaveFile;

use super::{ChunkData, ChunkReader, ChunkReadingError, CompressionError};

#[derive(Clone)]
pub struct AnvilChunkReader {}

impl Default for AnvilChunkReader {
Expand Down
1 change: 0 additions & 1 deletion pumpkin-world/src/chunk/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,6 @@ pub struct ChunkData {
pub blocks: ChunkBlocks,
pub position: Vector2<i32>,
}

pub struct ChunkBlocks {
// TODO make this a Vec that doesn't store the upper layers that only contain air

Expand Down
14 changes: 12 additions & 2 deletions pumpkin-world/src/coordinates.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
use std::ops::Deref;

use crate::{WORLD_LOWEST_Y, WORLD_MAX_Y};
use derive_more::derive::{AsMut, AsRef, Display, Into};
use num_traits::{PrimInt, Signed, Unsigned};
use pumpkin_core::math::vector2::Vector2;
use pumpkin_core::math::vector3::Vector3;
use serde::{Deserialize, Serialize};

use crate::{WORLD_LOWEST_Y, WORLD_MAX_Y};

#[derive(
Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, AsRef, AsMut, Into, Display,
)]
Expand Down Expand Up @@ -130,3 +130,13 @@ impl ChunkRelativeXZBlockCoordinates {
}
}
}

impl From<Vector3<i32>> for ChunkRelativeBlockCoordinates {
fn from(value: Vector3<i32>) -> Self {
Self {
x: (value.x as u8).into(),
z: (value.z as u8).into(),
y: value.y.into(),
}
}
}
152 changes: 85 additions & 67 deletions pumpkin-world/src/level.rs
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
use std::{collections::HashMap, path::PathBuf, sync::Arc};

use parking_lot::{Mutex, RwLock};
use pumpkin_core::math::vector2::Vector2;
use rayon::prelude::*;
use tokio::sync::mpsc;

use crate::{
chunk::{
anvil::AnvilChunkReader, ChunkData, ChunkParsingError, ChunkReader, ChunkReadingError,
},
world_gen::{get_world_gen, Seed, WorldGenerator},
};
use pumpkin_core::math::vector2::Vector2;
use tokio::sync::mpsc;
use tokio::sync::{Mutex, RwLock};

type RAMChunkStorage = Arc<RwLock<HashMap<Vector2<i32>, Arc<RwLock<ChunkData>>>>>;

/// The `Level` module provides functionality for working with chunks within or outside a Minecraft world.
///
Expand All @@ -23,12 +23,12 @@ use crate::{
/// For more details on world generation, refer to the `WorldGenerator` module.
pub struct Level {
save_file: Option<SaveFile>,
loaded_chunks: Arc<RwLock<HashMap<Vector2<i32>, Arc<ChunkData>>>>,
loaded_chunks: RAMChunkStorage,
chunk_watchers: Arc<Mutex<HashMap<Vector2<i32>, usize>>>,
chunk_reader: Box<dyn ChunkReader>,
world_gen: Box<dyn WorldGenerator>,
chunk_reader: Arc<Box<dyn ChunkReader>>,
world_gen: Arc<Box<dyn WorldGenerator>>,
}

#[derive(Clone)]
pub struct SaveFile {
#[expect(dead_code)]
root_folder: PathBuf,
Expand All @@ -37,7 +37,7 @@ pub struct SaveFile {

impl Level {
pub fn from_root_folder(root_folder: PathBuf) -> Self {
let world_gen = get_world_gen(Seed(0)); // TODO Read Seed from config.
let world_gen = get_world_gen(Seed(0)).into(); // TODO Read Seed from config.

if root_folder.exists() {
let region_folder = root_folder.join("region");
Expand All @@ -52,7 +52,7 @@ impl Level {
root_folder,
region_folder,
}),
chunk_reader: Box::new(AnvilChunkReader::new()),
chunk_reader: Arc::new(Box::new(AnvilChunkReader::new())),
loaded_chunks: Arc::new(RwLock::new(HashMap::new())),
chunk_watchers: Arc::new(Mutex::new(HashMap::new())),
}
Expand All @@ -64,7 +64,7 @@ impl Level {
Self {
world_gen,
save_file: None,
chunk_reader: Box::new(AnvilChunkReader::new()),
chunk_reader: Arc::new(Box::new(AnvilChunkReader::new())),
loaded_chunks: Arc::new(RwLock::new(HashMap::new())),
chunk_watchers: Arc::new(Mutex::new(HashMap::new())),
}
Expand All @@ -76,8 +76,8 @@ impl Level {
/// Marks chunks as "watched" by a unique player. When no players are watching a chunk,
/// it is removed from memory. Should only be called on chunks the player was not watching
/// before
pub fn mark_chunk_as_newly_watched(&self, chunks: &[Vector2<i32>]) {
let mut watchers = self.chunk_watchers.lock();
pub async fn mark_chunk_as_newly_watched(&self, chunks: &[Vector2<i32>]) {
let mut watchers = self.chunk_watchers.lock().await;
for chunk in chunks {
match watchers.entry(*chunk) {
std::collections::hash_map::Entry::Occupied(mut occupied) => {
Expand All @@ -93,9 +93,9 @@ impl Level {

/// Marks chunks no longer "watched" by a unique player. When no players are watching a chunk,
/// it is removed from memory. Should only be called on chunks the player was watching before
pub fn mark_chunk_as_not_watched_and_clean(&self, chunks: &[Vector2<i32>]) {
pub async fn mark_chunk_as_not_watched_and_clean(&self, chunks: &[Vector2<i32>]) {
let dropped_chunks = {
let mut watchers = self.chunk_watchers.lock();
let mut watchers = self.chunk_watchers.lock().await;
chunks
.iter()
.filter(|chunk| match watchers.entry(**chunk) {
Expand All @@ -119,7 +119,7 @@ impl Level {
})
.collect::<Vec<_>>()
};
let mut loaded_chunks = self.loaded_chunks.write();
let mut loaded_chunks = self.loaded_chunks.write().await;
let dropped_chunk_data = dropped_chunks
.iter()
.filter_map(|chunk| {
Expand All @@ -130,66 +130,84 @@ impl Level {
self.write_chunks(dropped_chunk_data);
}

pub fn write_chunks(&self, _chunks_to_write: Vec<(Vector2<i32>, Arc<ChunkData>)>) {
pub fn write_chunks(&self, _chunks_to_write: Vec<(Vector2<i32>, Arc<RwLock<ChunkData>>)>) {
//TODO
}

/// Reads/Generates many chunks in a world
/// MUST be called from a tokio runtime thread
///
/// Note: The order of the output chunks will almost never be in the same order as the order of input chunks
pub fn fetch_chunks(
&self,
chunks: &[Vector2<i32>],
channel: mpsc::Sender<Arc<RwLock<ChunkData>>>,
) {
for chunk in chunks {
{
let chunk_location = *chunk;
let channel = channel.clone();
let loaded_chunks = self.loaded_chunks.clone();
let chunk_reader = self.chunk_reader.clone();
let save_file = self.save_file.clone();
let world_gen = self.world_gen.clone();
tokio::spawn(async move {
let loaded_chunks_read = loaded_chunks.read().await;
let possibly_loaded_chunk = loaded_chunks_read.get(&chunk_location).cloned();
drop(loaded_chunks_read);
match possibly_loaded_chunk {
Some(chunk) => {
let chunk = chunk.clone();
channel.send(chunk).await.unwrap();
}
None => {
let chunk_data = match save_file {
Some(save_file) => {
match chunk_reader.read_chunk(&save_file, &chunk_location) {
Ok(data) => Ok(Arc::new(RwLock::new(data))),
Err(
ChunkReadingError::ChunkNotExist
| ChunkReadingError::ParsingError(
ChunkParsingError::ChunkNotGenerated,
),
) => {
// This chunk was not generated yet.
let chunk = Arc::new(RwLock::new(
world_gen.generate_chunk(chunk_location),
));
let mut loaded_chunks = loaded_chunks.write().await;
loaded_chunks.insert(chunk_location, chunk.clone());
drop(loaded_chunks);
Ok(chunk)
}
Err(err) => Err(err),
}
}
None => {
// There is no savefile yet -> generate the chunks
let chunk = Arc::new(RwLock::new(
world_gen.generate_chunk(chunk_location),
));

pub fn fetch_chunks(&self, chunks: &[Vector2<i32>], channel: mpsc::Sender<Arc<ChunkData>>) {
chunks.into_par_iter().for_each(|at| {
let channel = channel.clone();

let maybe_chunk = {
let loaded_chunks = self.loaded_chunks.read();
loaded_chunks.get(at).cloned()
}
.or_else(|| {
let chunk_data = match &self.save_file {
Some(save_file) => {
match self.chunk_reader.read_chunk(save_file, at) {
Ok(data) => Ok(Arc::new(data)),
Err(
ChunkReadingError::ChunkNotExist
| ChunkReadingError::ParsingError(
ChunkParsingError::ChunkNotGenerated,
),
) => {
// This chunk was not generated yet.
let chunk = Arc::new(self.world_gen.generate_chunk(*at));
Ok(chunk)
let mut loaded_chunks = loaded_chunks.write().await;
loaded_chunks.insert(chunk_location, chunk.clone());
Ok(chunk)
}
};
match chunk_data {
Ok(data) => channel.send(data).await.unwrap(),
Err(err) => {
log::warn!(
"Failed to read chunk {:?}: {:?}",
chunk_location,
err
);
}
}
Err(err) => Err(err),
}
}
None => {
// There is no savefile yet -> generate the chunks
let chunk = Arc::new(self.world_gen.generate_chunk(*at));
Ok(chunk)
}
};
match chunk_data {
Ok(data) => Some(data),
Err(err) => {
// TODO: Panic here?
log::warn!("Failed to read chunk {:?}: {:?}", at, err);
None
}
}
});
match maybe_chunk {
Some(chunk) => {
channel
.blocking_send(chunk.clone())
.expect("Failed sending ChunkData.");
}
None => {
log::error!("Unable to send chunk {:?}!", at);
}
};
})
});
}
}
}
}
Loading

0 comments on commit 746b656

Please sign in to comment.