Save level.dat
This commit is contained in:
parent
cc83e090f9
commit
fcf85df518
@ -7,7 +7,7 @@ pub use gamemode::GameMode;
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(PartialEq, Serialize, Deserialize)]
|
||||
#[derive(PartialEq, Serialize, Deserialize, Clone)]
|
||||
pub enum Difficulty {
|
||||
Peaceful,
|
||||
Easy,
|
||||
|
||||
@ -4,6 +4,7 @@ version.workspace = true
|
||||
edition.workspace = true
|
||||
|
||||
[dependencies]
|
||||
pumpkin-nbt = { path = "../pumpkin-nbt" }
|
||||
pumpkin-core = { path = "../pumpkin-core" }
|
||||
pumpkin-config = { path = "../pumpkin-config" }
|
||||
pumpkin-macros = { path = "../pumpkin-macros" }
|
||||
|
||||
@ -5,7 +5,7 @@ use std::{
|
||||
|
||||
use flate2::bufread::{GzDecoder, ZlibDecoder};
|
||||
|
||||
use crate::level::SaveFile;
|
||||
use crate::level::LevelFolder;
|
||||
|
||||
use super::{ChunkData, ChunkReader, ChunkReadingError, CompressionError};
|
||||
|
||||
@ -87,7 +87,7 @@ impl Compression {
|
||||
impl ChunkReader for AnvilChunkReader {
|
||||
fn read_chunk(
|
||||
&self,
|
||||
save_file: &SaveFile,
|
||||
save_file: &LevelFolder,
|
||||
at: &pumpkin_core::math::vector2::Vector2<i32>,
|
||||
) -> Result<super::ChunkData, ChunkReadingError> {
|
||||
let region = (at.x >> 5, at.z >> 5);
|
||||
@ -168,14 +168,14 @@ mod tests {
|
||||
|
||||
use crate::{
|
||||
chunk::{anvil::AnvilChunkReader, ChunkReader, ChunkReadingError},
|
||||
level::SaveFile,
|
||||
level::LevelFolder,
|
||||
};
|
||||
|
||||
#[test]
|
||||
fn not_existing() {
|
||||
let region_path = PathBuf::from("not_existing");
|
||||
let result = AnvilChunkReader::new().read_chunk(
|
||||
&SaveFile {
|
||||
&LevelFolder {
|
||||
root_folder: PathBuf::from(""),
|
||||
region_folder: region_path,
|
||||
},
|
||||
|
||||
@ -9,7 +9,7 @@ use thiserror::Error;
|
||||
use crate::{
|
||||
block::BlockState,
|
||||
coordinates::{ChunkRelativeBlockCoordinates, Height},
|
||||
level::SaveFile,
|
||||
level::LevelFolder,
|
||||
WORLD_HEIGHT,
|
||||
};
|
||||
|
||||
@ -22,7 +22,7 @@ const CHUNK_VOLUME: usize = CHUNK_AREA * WORLD_HEIGHT;
|
||||
pub trait ChunkReader: Sync + Send {
|
||||
fn read_chunk(
|
||||
&self,
|
||||
save_file: &SaveFile,
|
||||
save_file: &LevelFolder,
|
||||
at: &Vector2<i32>,
|
||||
) -> Result<ChunkData, ChunkReadingError>;
|
||||
}
|
||||
|
||||
@ -2,7 +2,6 @@ use std::{path::PathBuf, sync::Arc};
|
||||
|
||||
use dashmap::{DashMap, Entry};
|
||||
use num_traits::Zero;
|
||||
use pumpkin_config::BASIC_CONFIG;
|
||||
use pumpkin_core::math::vector2::Vector2;
|
||||
use rayon::iter::{IntoParallelRefIterator, ParallelIterator};
|
||||
use tokio::{
|
||||
@ -15,7 +14,7 @@ use crate::{
|
||||
anvil::AnvilChunkReader, ChunkData, ChunkParsingError, ChunkReader, ChunkReadingError,
|
||||
},
|
||||
generation::{get_world_gen, Seed, WorldGenerator},
|
||||
world_info::{anvil::AnvilInfoReader, WorldInfo, WorldInfoReader},
|
||||
world_info::{anvil::AnvilLevelInfo, LevelData, WorldInfoReader, WorldInfoWriter},
|
||||
};
|
||||
|
||||
/// The `Level` module provides functionality for working with chunks within or outside a Minecraft world.
|
||||
@ -29,8 +28,9 @@ use crate::{
|
||||
/// For more details on world generation, refer to the `WorldGenerator` module.
|
||||
pub struct Level {
|
||||
pub seed: Seed,
|
||||
pub world_info: Option<WorldInfo>,
|
||||
save_file: Option<SaveFile>,
|
||||
pub level_info: LevelData,
|
||||
world_info_writer: Arc<dyn WorldInfoWriter>,
|
||||
level_folder: LevelFolder,
|
||||
loaded_chunks: Arc<DashMap<Vector2<i32>, Arc<RwLock<ChunkData>>>>,
|
||||
chunk_watchers: Arc<DashMap<Vector2<i32>, usize>>,
|
||||
chunk_reader: Arc<dyn ChunkReader>,
|
||||
@ -38,60 +38,53 @@ pub struct Level {
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct SaveFile {
|
||||
pub struct LevelFolder {
|
||||
pub root_folder: PathBuf,
|
||||
pub region_folder: PathBuf,
|
||||
}
|
||||
|
||||
fn get_or_create_seed() -> Seed {
|
||||
// TODO: if there is a seed in the config (!= 0) use it. Otherwise make a random one
|
||||
Seed::from(BASIC_CONFIG.seed.as_str())
|
||||
}
|
||||
|
||||
impl Level {
|
||||
pub fn from_root_folder(root_folder: PathBuf) -> Self {
|
||||
// If we are using an already existing world we want to read the seed from the level.dat, If not we want to check if there is a seed in the config, if not lets create a random one
|
||||
if root_folder.exists() {
|
||||
let region_folder = root_folder.join("region");
|
||||
assert!(
|
||||
region_folder.exists(),
|
||||
"World region folder does not exist, despite there being a root folder."
|
||||
);
|
||||
let save_file = SaveFile {
|
||||
if !region_folder.exists() {
|
||||
std::fs::create_dir_all(®ion_folder).expect("Failed to create Region folder");
|
||||
}
|
||||
let level_folder = LevelFolder {
|
||||
root_folder,
|
||||
region_folder,
|
||||
};
|
||||
|
||||
// TODO: Load info correctly based on world format type
|
||||
let world_info_reader = AnvilInfoReader::new();
|
||||
let info = world_info_reader
|
||||
.read_world_info(&save_file)
|
||||
.expect("Unable to get world info!"); // TODO: Improve error handling
|
||||
let seed = Seed(info.seed as u64);
|
||||
let world_gen = get_world_gen(seed).into(); // TODO Read Seed from config.
|
||||
let level_info = AnvilLevelInfo
|
||||
.read_world_info(&level_folder)
|
||||
.unwrap_or_default(); // TODO: Improve error handling
|
||||
let seed = Seed(level_info.world_gen_settings.seed as u64);
|
||||
let world_gen = get_world_gen(seed).into();
|
||||
|
||||
Self {
|
||||
seed,
|
||||
world_gen,
|
||||
save_file: Some(save_file),
|
||||
world_info_writer: Arc::new(AnvilLevelInfo),
|
||||
level_folder,
|
||||
chunk_reader: Arc::new(AnvilChunkReader::new()),
|
||||
loaded_chunks: Arc::new(DashMap::new()),
|
||||
chunk_watchers: Arc::new(DashMap::new()),
|
||||
world_info: Some(info),
|
||||
}
|
||||
} else {
|
||||
let seed = get_or_create_seed();
|
||||
let world_gen = get_world_gen(seed).into();
|
||||
Self {
|
||||
seed,
|
||||
world_gen,
|
||||
save_file: None,
|
||||
chunk_reader: Arc::new(AnvilChunkReader::new()),
|
||||
loaded_chunks: Arc::new(DashMap::new()),
|
||||
chunk_watchers: Arc::new(DashMap::new()),
|
||||
world_info: None,
|
||||
level_info,
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn save(&self) {
|
||||
log::info!("Saving level...");
|
||||
// lets first save all chunks
|
||||
for chunk in self.loaded_chunks.iter() {
|
||||
let chunk = chunk.read().await;
|
||||
self.clean_chunk(&chunk.position);
|
||||
}
|
||||
// then lets save the world info
|
||||
self.world_info_writer
|
||||
.write_world_info(self.level_info.clone(), &self.level_folder)
|
||||
.expect("Failed to save world info");
|
||||
}
|
||||
|
||||
pub fn get_block() {}
|
||||
@ -205,10 +198,10 @@ impl Level {
|
||||
|
||||
fn load_chunk_from_save(
|
||||
chunk_reader: Arc<dyn ChunkReader>,
|
||||
save_file: SaveFile,
|
||||
save_file: &LevelFolder,
|
||||
chunk_pos: Vector2<i32>,
|
||||
) -> Result<Option<Arc<RwLock<ChunkData>>>, ChunkReadingError> {
|
||||
match chunk_reader.read_chunk(&save_file, &chunk_pos) {
|
||||
match chunk_reader.read_chunk(save_file, &chunk_pos) {
|
||||
Ok(data) => Ok(Some(Arc::new(RwLock::new(data)))),
|
||||
Err(
|
||||
ChunkReadingError::ChunkNotExist
|
||||
@ -233,7 +226,7 @@ impl Level {
|
||||
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 level_info = self.level_folder.clone();
|
||||
let world_gen = self.world_gen.clone();
|
||||
let chunk_pos = *at;
|
||||
|
||||
@ -241,9 +234,8 @@ impl Level {
|
||||
.get(&chunk_pos)
|
||||
.map(|entry| entry.value().clone())
|
||||
.unwrap_or_else(|| {
|
||||
let loaded_chunk = save_file
|
||||
.and_then(|save_file| {
|
||||
match Self::load_chunk_from_save(chunk_reader, save_file, chunk_pos) {
|
||||
let loaded_chunk =
|
||||
match Self::load_chunk_from_save(chunk_reader, &level_info, chunk_pos) {
|
||||
Ok(chunk) => chunk,
|
||||
Err(err) => {
|
||||
log::error!(
|
||||
@ -254,7 +246,6 @@ impl Level {
|
||||
None
|
||||
}
|
||||
}
|
||||
})
|
||||
.unwrap_or_else(|| {
|
||||
Arc::new(RwLock::new(world_gen.generate_chunk(chunk_pos)))
|
||||
});
|
||||
|
||||
@ -1,29 +1,30 @@
|
||||
use std::{fs::OpenOptions, io::Read};
|
||||
use std::{
|
||||
fs::OpenOptions,
|
||||
io::{Read, Write},
|
||||
time::{SystemTime, UNIX_EPOCH},
|
||||
};
|
||||
|
||||
use flate2::read::GzDecoder;
|
||||
use serde::Deserialize;
|
||||
use flate2::{read::GzDecoder, write::GzEncoder, Compression};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::level::SaveFile;
|
||||
use crate::level::LevelFolder;
|
||||
|
||||
use super::{WorldInfo, WorldInfoError, WorldInfoReader};
|
||||
use super::{LevelData, WorldInfoError, WorldInfoReader, WorldInfoWriter};
|
||||
|
||||
pub struct AnvilInfoReader {}
|
||||
const LEVEL_DAT_FILE_NAME: &str = "level.dat";
|
||||
|
||||
impl AnvilInfoReader {
|
||||
pub fn new() -> Self {
|
||||
Self {}
|
||||
}
|
||||
}
|
||||
pub struct AnvilLevelInfo;
|
||||
|
||||
impl WorldInfoReader for AnvilInfoReader {
|
||||
fn read_world_info(&self, save_file: &SaveFile) -> Result<WorldInfo, WorldInfoError> {
|
||||
let path = save_file.root_folder.join("level.dat");
|
||||
impl WorldInfoReader for AnvilLevelInfo {
|
||||
fn read_world_info(&self, level_folder: &LevelFolder) -> Result<LevelData, WorldInfoError> {
|
||||
let path = level_folder.root_folder.join(LEVEL_DAT_FILE_NAME);
|
||||
|
||||
let mut world_info_file = OpenOptions::new().read(true).open(path)?;
|
||||
|
||||
let mut buffer = Vec::new();
|
||||
world_info_file.read_to_end(&mut buffer)?;
|
||||
|
||||
// try to decompress using GZip
|
||||
let mut decoder = GzDecoder::new(&buffer[..]);
|
||||
let mut decompressed_data = Vec::new();
|
||||
decoder.read_to_end(&mut decompressed_data)?;
|
||||
@ -31,63 +32,57 @@ impl WorldInfoReader for AnvilInfoReader {
|
||||
let info = fastnbt::from_bytes::<LevelDat>(&decompressed_data)
|
||||
.map_err(|e| WorldInfoError::DeserializationError(e.to_string()))?;
|
||||
|
||||
Ok(WorldInfo {
|
||||
seed: info.data.world_gen_settings.seed,
|
||||
})
|
||||
// todo check version
|
||||
|
||||
Ok(info.data)
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for AnvilInfoReader {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
impl WorldInfoWriter for AnvilLevelInfo {
|
||||
fn write_world_info(
|
||||
&self,
|
||||
info: LevelData,
|
||||
level_folder: &LevelFolder,
|
||||
) -> Result<(), WorldInfoError> {
|
||||
let start = SystemTime::now();
|
||||
let since_the_epoch = start
|
||||
.duration_since(UNIX_EPOCH)
|
||||
.expect("Time went backwards");
|
||||
let level = LevelDat {
|
||||
data: LevelData {
|
||||
allow_commands: info.allow_commands,
|
||||
data_version: info.data_version,
|
||||
difficulty: info.difficulty,
|
||||
world_gen_settings: info.world_gen_settings,
|
||||
last_played: since_the_epoch.as_millis() as i64,
|
||||
level_name: info.level_name,
|
||||
spawn_x: info.spawn_x,
|
||||
spawn_y: info.spawn_y,
|
||||
spawn_z: info.spawn_z,
|
||||
nbt_version: info.nbt_version,
|
||||
version: info.version,
|
||||
},
|
||||
};
|
||||
// convert it into nbt
|
||||
let nbt = pumpkin_nbt::serializer::to_bytes_unnamed(&level).unwrap();
|
||||
// now compress using GZip, TODO: im not sure about the to_vec, but writer is not implemented for BytesMut, see https://github.com/tokio-rs/bytes/pull/478
|
||||
let mut encoder = GzEncoder::new(nbt.to_vec(), Compression::best());
|
||||
let compressed_data = Vec::new();
|
||||
encoder.write_all(&compressed_data)?;
|
||||
|
||||
// open file
|
||||
let path = level_folder.root_folder.join(LEVEL_DAT_FILE_NAME);
|
||||
let mut world_info_file = OpenOptions::new().write(true).open(path)?;
|
||||
// write compressed data into file
|
||||
world_info_file.write_all(&compressed_data).unwrap();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct LevelDat {
|
||||
// No idea why its formatted like this
|
||||
// This tag contains all the level data.
|
||||
#[serde(rename = "Data")]
|
||||
pub data: WorldData,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
#[serde(rename_all = "PascalCase")]
|
||||
pub struct WorldData {
|
||||
pub world_gen_settings: WorldGenSettings,
|
||||
// TODO: Implement the rest of the fields
|
||||
// Fields below this comment are being deserialized, but are not being used
|
||||
pub spawn_x: i32,
|
||||
pub spawn_y: i32,
|
||||
pub spawn_z: i32,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct WorldGenSettings {
|
||||
pub seed: i64,
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::path::PathBuf;
|
||||
|
||||
use crate::{
|
||||
level::SaveFile,
|
||||
world_info::{anvil::AnvilInfoReader, WorldInfo, WorldInfoReader},
|
||||
};
|
||||
|
||||
#[test]
|
||||
fn test_level_dat_reading() {
|
||||
let world_info = AnvilInfoReader::new();
|
||||
let root_folder = PathBuf::from("test-files").join("sample-1");
|
||||
let save_file = SaveFile {
|
||||
root_folder: root_folder.clone(),
|
||||
region_folder: root_folder,
|
||||
};
|
||||
let expected = WorldInfo {
|
||||
seed: -79717552349559436,
|
||||
};
|
||||
let info = world_info.read_world_info(&save_file).unwrap();
|
||||
|
||||
assert_eq!(info, expected);
|
||||
}
|
||||
pub data: LevelData,
|
||||
}
|
||||
|
||||
@ -1,17 +1,113 @@
|
||||
use pumpkin_config::BASIC_CONFIG;
|
||||
use pumpkin_core::Difficulty;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use thiserror::Error;
|
||||
|
||||
use crate::level::SaveFile;
|
||||
use crate::{generation::Seed, level::LevelFolder};
|
||||
|
||||
pub mod anvil;
|
||||
|
||||
pub(crate) trait WorldInfoReader {
|
||||
fn read_world_info(&self, save_file: &SaveFile) -> Result<WorldInfo, WorldInfoError>;
|
||||
fn read_world_info(&self, level_folder: &LevelFolder) -> Result<LevelData, WorldInfoError>;
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub struct WorldInfo {
|
||||
pub(crate) trait WorldInfoWriter: Sync + Send {
|
||||
fn write_world_info(
|
||||
&self,
|
||||
info: LevelData,
|
||||
level_folder: &LevelFolder,
|
||||
) -> Result<(), WorldInfoError>;
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone)]
|
||||
#[serde(rename_all = "PascalCase")]
|
||||
pub struct LevelData {
|
||||
// true if cheats are enabled.
|
||||
pub allow_commands: bool,
|
||||
// An integer displaying the data version.
|
||||
pub data_version: i32,
|
||||
// The current difficulty setting.
|
||||
pub difficulty: Difficulty,
|
||||
// the generation settings for each dimension.
|
||||
pub world_gen_settings: WorldGenSettings,
|
||||
// The Unix time in milliseconds when the level was last loaded.
|
||||
pub last_played: i64,
|
||||
// The name of the level.
|
||||
pub level_name: String,
|
||||
// The X coordinate of the world spawn.
|
||||
pub spawn_x: i32,
|
||||
// The Y coordinate of the world spawn.
|
||||
pub spawn_y: i32,
|
||||
// The Z coordinate of the world spawn.
|
||||
pub spawn_z: i32,
|
||||
#[serde(rename = "version")]
|
||||
// The NBT version of the level
|
||||
pub nbt_version: i32,
|
||||
#[serde(rename = "Version")]
|
||||
pub version: WorldVersion,
|
||||
// TODO: Implement the rest of the fields
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone)]
|
||||
pub struct WorldGenSettings {
|
||||
// the numerical seed of the world
|
||||
pub seed: i64,
|
||||
// TODO: Implement all fields
|
||||
}
|
||||
|
||||
fn get_or_create_seed() -> Seed {
|
||||
// TODO: if there is a seed in the config (!= 0) use it. Otherwise make a random one
|
||||
Seed::from(BASIC_CONFIG.seed.as_str())
|
||||
}
|
||||
|
||||
impl Default for WorldGenSettings {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
seed: get_or_create_seed().0 as i64,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone)]
|
||||
#[serde(rename_all = "PascalCase")]
|
||||
pub struct WorldVersion {
|
||||
// The version name as a string, e.g. "15w32b".
|
||||
pub name: String,
|
||||
// An integer displaying the data version.
|
||||
pub id: i32,
|
||||
// Whether the version is a snapshot or not.
|
||||
pub snapshot: bool,
|
||||
// Developing series. In 1.18 experimental snapshots, it was set to "ccpreview". In others, set to "main".
|
||||
pub series: String,
|
||||
}
|
||||
|
||||
impl Default for WorldVersion {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
name: "1.24.4".to_string(),
|
||||
id: -1,
|
||||
snapshot: false,
|
||||
series: "main".to_string(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for LevelData {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
allow_commands: true,
|
||||
// TODO
|
||||
data_version: -1,
|
||||
difficulty: Difficulty::Normal,
|
||||
world_gen_settings: Default::default(),
|
||||
last_played: -1,
|
||||
level_name: "world".to_string(),
|
||||
spawn_x: 0,
|
||||
spawn_y: 200,
|
||||
spawn_z: 0,
|
||||
nbt_version: -1,
|
||||
version: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
|
||||
Binary file not shown.
@ -32,7 +32,7 @@ impl CommandExecutor for StopExecutor {
|
||||
for player in server.get_all_players().await {
|
||||
player.kick(kick_message.clone()).await;
|
||||
}
|
||||
|
||||
server.save().await;
|
||||
std::process::exit(0)
|
||||
}
|
||||
}
|
||||
|
||||
@ -220,18 +220,18 @@ impl Player {
|
||||
);
|
||||
|
||||
// Decrement value of watched chunks
|
||||
let chunks_to_clean = world.mark_chunks_as_not_watched(&radial_chunks);
|
||||
let chunks_to_clean = world.level.mark_chunks_as_not_watched(&radial_chunks);
|
||||
|
||||
// Remove chunks with no watchers from the cache
|
||||
world.clean_chunks(&chunks_to_clean);
|
||||
world.level.clean_chunks(&chunks_to_clean);
|
||||
// Remove left over entries from all possiblily loaded chunks
|
||||
world.clean_memory(&radial_chunks);
|
||||
world.level.clean_memory(&radial_chunks);
|
||||
|
||||
log::debug!(
|
||||
"Removed player id {} ({}) ({} chunks remain cached)",
|
||||
self.gameprofile.name,
|
||||
self.client.id,
|
||||
self.world().get_cached_chunk_len()
|
||||
self.world().level.loaded_chunk_count()
|
||||
);
|
||||
|
||||
//self.world().level.list_cached();
|
||||
|
||||
@ -174,6 +174,12 @@ impl Server {
|
||||
self.server_listing.lock().await.remove_player();
|
||||
}
|
||||
|
||||
pub async fn save(&self) {
|
||||
for world in &self.worlds {
|
||||
world.save().await;
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn try_get_container(
|
||||
&self,
|
||||
player_id: EntityId,
|
||||
|
||||
@ -118,6 +118,10 @@ impl World {
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn save(&self) {
|
||||
self.level.save().await;
|
||||
}
|
||||
|
||||
/// Broadcasts a packet to all connected players within the world.
|
||||
///
|
||||
/// Sends the specified packet to every player currently logged in to the world.
|
||||
@ -523,26 +527,6 @@ impl World {
|
||||
player.set_health(20.0, 20, 20.0).await;
|
||||
}
|
||||
|
||||
pub fn mark_chunks_as_not_watched(&self, chunks: &[Vector2<i32>]) -> Vec<Vector2<i32>> {
|
||||
self.level.mark_chunks_as_not_watched(chunks)
|
||||
}
|
||||
|
||||
pub fn mark_chunks_as_watched(&self, chunks: &[Vector2<i32>]) {
|
||||
self.level.mark_chunks_as_newly_watched(chunks);
|
||||
}
|
||||
|
||||
pub fn clean_chunks(&self, chunks: &[Vector2<i32>]) {
|
||||
self.level.clean_chunks(chunks);
|
||||
}
|
||||
|
||||
pub fn clean_memory(&self, chunks_to_check: &[Vector2<i32>]) {
|
||||
self.level.clean_memory(chunks_to_check);
|
||||
}
|
||||
|
||||
pub fn get_cached_chunk_len(&self) -> usize {
|
||||
self.level.loaded_chunk_count()
|
||||
}
|
||||
|
||||
/// IMPORTANT: Chunks have to be non-empty
|
||||
fn spawn_world_chunks(
|
||||
&self,
|
||||
|
||||
@ -79,12 +79,18 @@ pub async fn update_position(player: &Arc<Player>) {
|
||||
|
||||
// Make sure the watched section and the chunk watcher updates are async atomic. We want to
|
||||
// ensure what we unload when the player disconnects is correct
|
||||
entity.world.mark_chunks_as_watched(&loading_chunks);
|
||||
let chunks_to_clean = entity.world.mark_chunks_as_not_watched(&unloading_chunks);
|
||||
entity
|
||||
.world
|
||||
.level
|
||||
.mark_chunks_as_newly_watched(&loading_chunks);
|
||||
let chunks_to_clean = entity
|
||||
.world
|
||||
.level
|
||||
.mark_chunks_as_not_watched(&unloading_chunks);
|
||||
player.watched_section.store(new_cylindrical);
|
||||
|
||||
if !chunks_to_clean.is_empty() {
|
||||
entity.world.clean_chunks(&chunks_to_clean);
|
||||
entity.world.level.clean_chunks(&chunks_to_clean);
|
||||
|
||||
// This can take a little if we are sending a bunch of packets, queue it up :p
|
||||
let client = player.client.clone();
|
||||
|
||||
Loading…
Reference in New Issue
Block a user