fix: don't store empty chunks
should be fixed
This commit is contained in:
parent
35b91efd38
commit
d3d13323b2
@ -198,7 +198,7 @@ pub struct CustomNameImpl {
|
|||||||
}
|
}
|
||||||
impl DataComponentImpl for CustomNameImpl {
|
impl DataComponentImpl for CustomNameImpl {
|
||||||
fn write_data(&self) -> NbtTag {
|
fn write_data(&self) -> NbtTag {
|
||||||
NbtTag::String(self.name.clone())
|
NbtTag::String(self.name.clone().into())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_hash(&self) -> i32 {
|
fn get_hash(&self) -> i32 {
|
||||||
@ -228,7 +228,7 @@ impl ItemModelImpl {
|
|||||||
}
|
}
|
||||||
impl DataComponentImpl for ItemModelImpl {
|
impl DataComponentImpl for ItemModelImpl {
|
||||||
fn write_data(&self) -> NbtTag {
|
fn write_data(&self) -> NbtTag {
|
||||||
NbtTag::String(self.id.clone())
|
NbtTag::String(self.id.clone().into())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_hash(&self) -> i32 {
|
fn get_hash(&self) -> i32 {
|
||||||
@ -250,7 +250,7 @@ impl EnchantmentsImpl {
|
|||||||
let data = &data.extract_compound()?.child_tags;
|
let data = &data.extract_compound()?.child_tags;
|
||||||
let mut enc = Vec::with_capacity(data.len());
|
let mut enc = Vec::with_capacity(data.len());
|
||||||
for (name, level) in data {
|
for (name, level) in data {
|
||||||
enc.push((Enchantment::from_name(name.as_str())?, level.extract_int()?));
|
enc.push((Enchantment::from_name(name.as_ref())?, level.extract_int()?));
|
||||||
}
|
}
|
||||||
Some(Self {
|
Some(Self {
|
||||||
enchantment: Cow::from(enc),
|
enchantment: Cow::from(enc),
|
||||||
@ -987,14 +987,14 @@ impl<T: IDSetContent + 'static> IDSet<T> {
|
|||||||
if tag.starts_with("#") {
|
if tag.starts_with("#") {
|
||||||
Some(Self::Tag(Cow::Owned(tag.strip_prefix("#")?.to_string())))
|
Some(Self::Tag(Cow::Owned(tag.strip_prefix("#")?.to_string())))
|
||||||
} else {
|
} else {
|
||||||
Some(Self::IDs(Cow::Owned([T::from_str(tag.as_str())?].to_vec())))
|
Some(Self::IDs(Cow::Owned([T::from_str(tag.as_ref())?].to_vec())))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
NbtTag::List(nbt_tags) => {
|
NbtTag::List(nbt_tags) => {
|
||||||
let mut ids = Vec::<&T>::new();
|
let mut ids = Vec::<&T>::new();
|
||||||
for nbt in nbt_tags {
|
for nbt in nbt_tags {
|
||||||
if let NbtTag::String(id) = nbt
|
if let NbtTag::String(id) = nbt
|
||||||
&& let Some(instance) = T::from_str(id.as_str())
|
&& let Some(instance) = T::from_str(id.as_ref())
|
||||||
{
|
{
|
||||||
ids.push(instance);
|
ids.push(instance);
|
||||||
}
|
}
|
||||||
@ -1013,8 +1013,10 @@ impl<T: IDSetContent + 'static> IDSet<T> {
|
|||||||
compound.put_string(key, tag);
|
compound.put_string(key, tag);
|
||||||
}
|
}
|
||||||
Self::IDs(arr) => {
|
Self::IDs(arr) => {
|
||||||
let id_vec: Vec<NbtTag> =
|
let id_vec: Vec<NbtTag> = arr
|
||||||
arr.iter().map(|x| NbtTag::String(x.to_string())).collect();
|
.iter()
|
||||||
|
.map(|x| NbtTag::String(x.to_string().into()))
|
||||||
|
.collect();
|
||||||
compound.put_list(key, id_vec);
|
compound.put_list(key, id_vec);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1466,7 +1468,7 @@ impl StoredEnchantmentsImpl {
|
|||||||
let data = &data.extract_compound()?.child_tags;
|
let data = &data.extract_compound()?.child_tags;
|
||||||
let mut enc = Vec::with_capacity(data.len());
|
let mut enc = Vec::with_capacity(data.len());
|
||||||
for (name, level) in data {
|
for (name, level) in data {
|
||||||
enc.push((Enchantment::from_name(name.as_str())?, level.extract_int()?));
|
enc.push((Enchantment::from_name(name.as_ref())?, level.extract_int()?));
|
||||||
}
|
}
|
||||||
Some(Self {
|
Some(Self {
|
||||||
enchantment: Cow::from(enc),
|
enchantment: Cow::from(enc),
|
||||||
|
|||||||
@ -59,9 +59,9 @@ fn create_large_compound(depth: usize) -> NbtCompound {
|
|||||||
);
|
);
|
||||||
|
|
||||||
let list = vec![
|
let list = vec![
|
||||||
NbtTag::String("one".to_string()),
|
NbtTag::String("one".into()),
|
||||||
NbtTag::String("two".to_string()),
|
NbtTag::String("two".into()),
|
||||||
NbtTag::String("three".to_string()),
|
NbtTag::String("three".into()),
|
||||||
];
|
];
|
||||||
compound.put_list("list_string", list);
|
compound.put_list("list_string", list);
|
||||||
|
|
||||||
|
|||||||
@ -31,7 +31,7 @@ macro_rules! nbt_compound_tag {
|
|||||||
///
|
///
|
||||||
#[derive(Clone, Debug, Default, PartialEq)]
|
#[derive(Clone, Debug, Default, PartialEq)]
|
||||||
pub struct NbtCompound {
|
pub struct NbtCompound {
|
||||||
pub child_tags: HashMap<String, NbtTag>,
|
pub child_tags: HashMap<Box<str>, NbtTag>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl NbtCompound {
|
impl NbtCompound {
|
||||||
@ -83,7 +83,7 @@ impl NbtCompound {
|
|||||||
let name = get_nbt_string(reader)?;
|
let name = get_nbt_string(reader)?;
|
||||||
let tag = NbtTag::deserialize_data(reader, tag_id)?;
|
let tag = NbtTag::deserialize_data(reader, tag_id)?;
|
||||||
|
|
||||||
compound.child_tags.insert(name, tag);
|
compound.child_tags.insert(name.into(), tag);
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(compound)
|
Ok(compound)
|
||||||
@ -105,13 +105,13 @@ impl NbtCompound {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn put(&mut self, name: &str, value: impl Into<NbtTag>) {
|
pub fn put(&mut self, name: &str, value: impl Into<NbtTag>) {
|
||||||
if !self.child_tags.iter().any(|(key, _)| key == name) {
|
if !self.child_tags.contains_key(name) {
|
||||||
self.child_tags.insert(name.to_string(), value.into());
|
self.child_tags.insert(name.into(), value.into());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn put_string(&mut self, name: &str, value: String) {
|
pub fn put_string(&mut self, name: &str, value: String) {
|
||||||
self.put(name, NbtTag::String(value));
|
self.put(name, NbtTag::String(value.into()));
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn put_list(&mut self, name: &str, value: Vec<NbtTag>) {
|
pub fn put_list(&mut self, name: &str, value: Vec<NbtTag>) {
|
||||||
@ -238,9 +238,19 @@ impl FromIterator<(String, NbtTag)> for NbtCompound {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl FromIterator<(Box<str>, NbtTag)> for NbtCompound {
|
||||||
|
fn from_iter<T: IntoIterator<Item = (Box<str>, NbtTag)>>(iter: T) -> Self {
|
||||||
|
let mut compound = Self::new();
|
||||||
|
for (key, value) in iter {
|
||||||
|
compound.child_tags.insert(key, value);
|
||||||
|
}
|
||||||
|
compound
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl IntoIterator for NbtCompound {
|
impl IntoIterator for NbtCompound {
|
||||||
type Item = (String, NbtTag);
|
type Item = (Box<str>, NbtTag);
|
||||||
type IntoIter = IntoIter<String, NbtTag>;
|
type IntoIter = IntoIter<Box<str>, NbtTag>;
|
||||||
|
|
||||||
fn into_iter(self) -> Self::IntoIter {
|
fn into_iter(self) -> Self::IntoIter {
|
||||||
self.child_tags.into_iter()
|
self.child_tags.into_iter()
|
||||||
@ -249,6 +259,14 @@ impl IntoIterator for NbtCompound {
|
|||||||
|
|
||||||
impl Extend<(String, NbtTag)> for NbtCompound {
|
impl Extend<(String, NbtTag)> for NbtCompound {
|
||||||
fn extend<T: IntoIterator<Item = (String, NbtTag)>>(&mut self, iter: T) {
|
fn extend<T: IntoIterator<Item = (String, NbtTag)>>(&mut self, iter: T) {
|
||||||
|
for (key, value) in iter {
|
||||||
|
self.put(&key, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Extend<(Box<str>, NbtTag)> for NbtCompound {
|
||||||
|
fn extend<T: IntoIterator<Item = (Box<str>, NbtTag)>>(&mut self, iter: T) {
|
||||||
self.child_tags.extend(iter);
|
self.child_tags.extend(iter);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -287,8 +305,8 @@ impl<'de> Deserialize<'de> for NbtCompound {
|
|||||||
mut map: A,
|
mut map: A,
|
||||||
) -> Result<Self::Value, A::Error> {
|
) -> Result<Self::Value, A::Error> {
|
||||||
let mut compound = NbtCompound::new();
|
let mut compound = NbtCompound::new();
|
||||||
while let Some((key, value)) = map.next_entry::<String, NbtTag>()? {
|
while let Some((key, value)) = map.next_entry::<Box<str>, NbtTag>()? {
|
||||||
compound.put(&key, value);
|
compound.child_tags.insert(key, value);
|
||||||
}
|
}
|
||||||
Ok(compound)
|
Ok(compound)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -174,7 +174,7 @@ impl<'de, R: Read + Seek> de::Deserializer<'de> for &mut Deserializer<R> {
|
|||||||
NbtTag::Long(value) => visitor.visit_i64::<Error>(value)?,
|
NbtTag::Long(value) => visitor.visit_i64::<Error>(value)?,
|
||||||
NbtTag::Float(value) => visitor.visit_f32::<Error>(value)?,
|
NbtTag::Float(value) => visitor.visit_f32::<Error>(value)?,
|
||||||
NbtTag::Double(value) => visitor.visit_f64::<Error>(value)?,
|
NbtTag::Double(value) => visitor.visit_f64::<Error>(value)?,
|
||||||
NbtTag::String(value) => visitor.visit_string::<Error>(value)?,
|
NbtTag::String(value) => visitor.visit_string::<Error>(value.into())?,
|
||||||
_ => unreachable!(),
|
_ => unreachable!(),
|
||||||
};
|
};
|
||||||
Ok(result)
|
Ok(result)
|
||||||
|
|||||||
@ -119,7 +119,7 @@ impl Nbt {
|
|||||||
let mut bytes = Vec::new();
|
let mut bytes = Vec::new();
|
||||||
let mut writer = WriteAdaptor::new(&mut bytes);
|
let mut writer = WriteAdaptor::new(&mut bytes);
|
||||||
writer.write_u8_be(COMPOUND_ID).unwrap();
|
writer.write_u8_be(COMPOUND_ID).unwrap();
|
||||||
NbtTag::String(self.name)
|
NbtTag::String(self.name.into())
|
||||||
.serialize_data(&mut writer)
|
.serialize_data(&mut writer)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
self.root_tag.serialize_content(&mut writer).unwrap();
|
self.root_tag.serialize_content(&mut writer).unwrap();
|
||||||
@ -418,7 +418,7 @@ mod test {
|
|||||||
// These tags will be wrapped during serialization.
|
// These tags will be wrapped during serialization.
|
||||||
vec.push(NbtTag::Int(-1823));
|
vec.push(NbtTag::Int(-1823));
|
||||||
vec.push(NbtTag::Int(123));
|
vec.push(NbtTag::Int(123));
|
||||||
vec.push(NbtTag::String("Not an int".to_string()));
|
vec.push(NbtTag::String("Not an int".into()));
|
||||||
vec.push(NbtTag::Byte(2));
|
vec.push(NbtTag::Byte(2));
|
||||||
|
|
||||||
// This compound will not, since the list is already a list of compound tags.
|
// This compound will not, since the list is already a list of compound tags.
|
||||||
|
|||||||
@ -125,8 +125,8 @@ mod tests {
|
|||||||
compound.put_long("long_value", 123456789);
|
compound.put_long("long_value", 123456789);
|
||||||
compound.put_float("float_value", 123.456);
|
compound.put_float("float_value", 123.456);
|
||||||
compound.put_double("double_value", 123456.789);
|
compound.put_double("double_value", 123456.789);
|
||||||
compound.put_bool("bool_value", true);
|
compound.put("bool_value", true);
|
||||||
compound.put("string_value", NbtTag::String("test string".to_string()));
|
compound.put("string_value", NbtTag::String("test string".into()));
|
||||||
|
|
||||||
// Create a nested compound
|
// Create a nested compound
|
||||||
let mut nested = NbtCompound::new();
|
let mut nested = NbtCompound::new();
|
||||||
@ -268,7 +268,7 @@ mod tests {
|
|||||||
|
|
||||||
// Create a compound with repetitive data (should compress well)
|
// Create a compound with repetitive data (should compress well)
|
||||||
for _i in 0..1000 {
|
for _i in 0..1000 {
|
||||||
compound.put("repeated_key", NbtTag::String("this is a test string that will be repeated many times to demonstrate compression".to_string()));
|
compound.put("repeated_key", NbtTag::String("this is a test string that will be repeated many times to demonstrate compression".into()));
|
||||||
}
|
}
|
||||||
|
|
||||||
let uncompressed = compound.child_tags.len() * 100; // rough estimate
|
let uncompressed = compound.child_tags.len() * 100; // rough estimate
|
||||||
|
|||||||
@ -55,7 +55,7 @@ impl DynamicOps for NbtOps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn create_string(&self, data: &str) -> Self::Value {
|
fn create_string(&self, data: &str) -> Self::Value {
|
||||||
NbtTag::String(data.to_string())
|
NbtTag::String(data.into())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn create_list<I>(&self, values: I) -> Self::Value
|
fn create_list<I>(&self, values: I) -> Self::Value
|
||||||
@ -284,7 +284,7 @@ impl DynamicOps for NbtOps {
|
|||||||
compound
|
compound
|
||||||
.child_tags
|
.child_tags
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.filter(|s| s.0 != key)
|
.filter(|s| s.0.as_ref() != key)
|
||||||
.collect(),
|
.collect(),
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
@ -705,14 +705,14 @@ mod test {
|
|||||||
let mut collector = ListCollector::new_collector();
|
let mut collector = ListCollector::new_collector();
|
||||||
|
|
||||||
collector = collector.accept(NbtTag::Byte(99));
|
collector = collector.accept(NbtTag::Byte(99));
|
||||||
collector = collector.accept(NbtTag::String("99".to_string()));
|
collector = collector.accept(NbtTag::String("99".into()));
|
||||||
collector = collector.accept(NbtTag::LongArray(vec![1, 2, 3]));
|
collector = collector.accept(NbtTag::LongArray(vec![1, 2, 3]));
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
collector.result(),
|
collector.result(),
|
||||||
NbtTag::List(vec![
|
NbtTag::List(vec![
|
||||||
NbtTag::Byte(99),
|
NbtTag::Byte(99),
|
||||||
NbtTag::String("99".to_string()),
|
NbtTag::String("99".into()),
|
||||||
NbtTag::LongArray(vec![1, 2, 3])
|
NbtTag::LongArray(vec![1, 2, 3])
|
||||||
])
|
])
|
||||||
);
|
);
|
||||||
|
|||||||
@ -21,7 +21,7 @@ pub enum NbtTag {
|
|||||||
Float(f32) = FLOAT_ID,
|
Float(f32) = FLOAT_ID,
|
||||||
Double(f64) = DOUBLE_ID,
|
Double(f64) = DOUBLE_ID,
|
||||||
ByteArray(Vec<i8>) = BYTE_ARRAY_ID,
|
ByteArray(Vec<i8>) = BYTE_ARRAY_ID,
|
||||||
String(String) = STRING_ID,
|
String(Box<str>) = STRING_ID,
|
||||||
List(Vec<Self>) = LIST_ID,
|
List(Vec<Self>) = LIST_ID,
|
||||||
Compound(NbtCompound) = COMPOUND_ID,
|
Compound(NbtCompound) = COMPOUND_ID,
|
||||||
IntArray(Vec<i32>) = INT_ARRAY_ID,
|
IntArray(Vec<i32>) = INT_ARRAY_ID,
|
||||||
@ -296,7 +296,7 @@ impl NbtTag {
|
|||||||
}
|
}
|
||||||
Ok(Self::ByteArray(byte_array))
|
Ok(Self::ByteArray(byte_array))
|
||||||
}
|
}
|
||||||
STRING_ID => Ok(Self::String(get_nbt_string(reader)?)),
|
STRING_ID => Ok(Self::String(get_nbt_string(reader)?.into())),
|
||||||
LIST_ID => {
|
LIST_ID => {
|
||||||
let tag_type_id = reader.get_u8_be()?;
|
let tag_type_id = reader.get_u8_be()?;
|
||||||
let len = reader.get_i32_be()?;
|
let len = reader.get_i32_be()?;
|
||||||
@ -464,7 +464,7 @@ impl NbtTag {
|
|||||||
|
|
||||||
impl From<&str> for NbtTag {
|
impl From<&str> for NbtTag {
|
||||||
fn from(value: &str) -> Self {
|
fn from(value: &str) -> Self {
|
||||||
Self::String(value.to_string())
|
Self::String(value.into())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -559,7 +559,7 @@ impl<'de> Deserialize<'de> for NbtTag {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn visit_str<E: serde::de::Error>(self, v: &str) -> Result<Self::Value, E> {
|
fn visit_str<E: serde::de::Error>(self, v: &str) -> Result<Self::Value, E> {
|
||||||
Ok(NbtTag::String(v.to_string()))
|
Ok(NbtTag::String(v.into()))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn visit_seq<A: serde::de::SeqAccess<'de>>(
|
fn visit_seq<A: serde::de::SeqAccess<'de>>(
|
||||||
|
|||||||
@ -745,7 +745,7 @@ mod test {
|
|||||||
NbtTag::List(vec![
|
NbtTag::List(vec![
|
||||||
NbtTag::Int(1),
|
NbtTag::Int(1),
|
||||||
NbtTag::Float(2.0),
|
NbtTag::Float(2.0),
|
||||||
NbtTag::String("69".to_string())
|
NbtTag::String("69".into())
|
||||||
]),
|
]),
|
||||||
NbtOps,
|
NbtOps,
|
||||||
is_error
|
is_error
|
||||||
|
|||||||
@ -517,6 +517,9 @@ impl ChunkSections {
|
|||||||
.store(mask, std::sync::atomic::Ordering::Relaxed);
|
.store(mask, std::sync::atomic::Ordering::Relaxed);
|
||||||
|
|
||||||
// If no more ticking sections, we could potentially deallocate, but that might be overkill and cause jitter.
|
// If no more ticking sections, we could potentially deallocate, but that might be overkill and cause jitter.
|
||||||
|
if mask == 0 {
|
||||||
|
*random_tick_sections_guard = None;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return replaced_block_state_id;
|
return replaced_block_state_id;
|
||||||
|
|||||||
@ -106,6 +106,11 @@ impl<V: Hash + Eq + Copy + Default, const DIM: usize> HeterogeneousPaletteData<V
|
|||||||
self.palette.swap_remove(original_index);
|
self.palette.swap_remove(original_index);
|
||||||
self.counts.swap_remove(original_index);
|
self.counts.swap_remove(original_index);
|
||||||
|
|
||||||
|
if self.palette.capacity() > 16 && self.palette.len() < self.palette.capacity() / 2 {
|
||||||
|
self.palette.shrink_to_fit();
|
||||||
|
self.counts.shrink_to_fit();
|
||||||
|
}
|
||||||
|
|
||||||
// If we are indexed, we need to update all indices because swap_remove changed indices
|
// If we are indexed, we need to update all indices because swap_remove changed indices
|
||||||
if !upgraded && let PaletteStorage::Indexed(indices) = &mut self.storage {
|
if !upgraded && let PaletteStorage::Indexed(indices) = &mut self.storage {
|
||||||
for row in indices.iter_mut() {
|
for row in indices.iter_mut() {
|
||||||
|
|||||||
@ -1,18 +1,22 @@
|
|||||||
use super::ChunkPos;
|
use super::ChunkPos;
|
||||||
use crate::level::SyncChunk;
|
use crate::level::SyncChunk;
|
||||||
use crossbeam::channel::{Receiver, Sender};
|
use crossbeam::channel::{Receiver, Sender};
|
||||||
use std::sync::Mutex;
|
use std::sync::Arc;
|
||||||
|
use std::sync::{Mutex, Weak};
|
||||||
use tokio::sync::oneshot;
|
use tokio::sync::oneshot;
|
||||||
|
|
||||||
|
#[expect(clippy::type_complexity)]
|
||||||
pub struct ChunkListener {
|
pub struct ChunkListener {
|
||||||
single: Mutex<Vec<(ChunkPos, oneshot::Sender<SyncChunk>)>>,
|
single: Mutex<Vec<(ChunkPos, oneshot::Sender<SyncChunk>)>>,
|
||||||
global: Mutex<Vec<Sender<(ChunkPos, SyncChunk)>>>,
|
global: Mutex<Vec<Sender<(ChunkPos, Weak<crate::chunk::ChunkData>)>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for ChunkListener {
|
impl Default for ChunkListener {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self::new()
|
Self::new()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ChunkListener {
|
impl ChunkListener {
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub const fn new() -> Self {
|
pub const fn new() -> Self {
|
||||||
@ -21,16 +25,19 @@ impl ChunkListener {
|
|||||||
global: Mutex::new(Vec::new()),
|
global: Mutex::new(Vec::new()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn add_single_chunk_listener(&self, pos: ChunkPos) -> oneshot::Receiver<SyncChunk> {
|
pub fn add_single_chunk_listener(&self, pos: ChunkPos) -> oneshot::Receiver<SyncChunk> {
|
||||||
let (tx, rx) = oneshot::channel();
|
let (tx, rx) = oneshot::channel();
|
||||||
self.single.lock().unwrap().push((pos, tx));
|
self.single.lock().unwrap().push((pos, tx));
|
||||||
rx
|
rx
|
||||||
}
|
}
|
||||||
pub fn add_global_chunk_listener(&self) -> Receiver<(ChunkPos, SyncChunk)> {
|
|
||||||
|
pub fn add_global_chunk_listener(&self) -> Receiver<(ChunkPos, Weak<crate::chunk::ChunkData>)> {
|
||||||
let (tx, rx) = crossbeam::channel::unbounded();
|
let (tx, rx) = crossbeam::channel::unbounded();
|
||||||
self.global.lock().unwrap().push(tx);
|
self.global.lock().unwrap().push(tx);
|
||||||
rx
|
rx
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn process_new_chunk(&self, pos: ChunkPos, chunk: &SyncChunk) {
|
pub fn process_new_chunk(&self, pos: ChunkPos, chunk: &SyncChunk) {
|
||||||
{
|
{
|
||||||
let mut single = self.single.lock().unwrap();
|
let mut single = self.single.lock().unwrap();
|
||||||
@ -44,22 +51,16 @@ impl ChunkListener {
|
|||||||
len -= 1;
|
len -= 1;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if single[i].1.is_closed() {
|
|
||||||
// let listener_pos = single[i].0;
|
|
||||||
single.remove(i);
|
|
||||||
// log::debug!("single listener dropped {listener_pos:?}");
|
|
||||||
len -= 1;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
i += 1;
|
i += 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
|
let weak = Arc::downgrade(chunk);
|
||||||
let mut global = self.global.lock().unwrap();
|
let mut global = self.global.lock().unwrap();
|
||||||
let mut i = 0;
|
let mut i = 0;
|
||||||
let mut len = global.len();
|
let mut len = global.len();
|
||||||
while i < len {
|
while i < len {
|
||||||
if matches!(global[i].send((pos, chunk.clone())), Ok(())) {
|
if matches!(global[i].send((pos, weak.clone())), Ok(())) {
|
||||||
// log::debug!("global listener {i} send {pos:?}");
|
// log::debug!("global listener {i} send {pos:?}");
|
||||||
} else {
|
} else {
|
||||||
// log::debug!("one global listener dropped");
|
// log::debug!("one global listener dropped");
|
||||||
|
|||||||
@ -528,7 +528,61 @@ impl GenerationSchedule {
|
|||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
fn unload_chunk(&mut self) {
|
fn garbage_collect_dependencies(&mut self) {
|
||||||
|
// Garbage collect stranded dependencies
|
||||||
|
let mut stranded = Vec::new();
|
||||||
|
for (pos, holder) in &self.chunk_map {
|
||||||
|
if holder.target_stage == StagedChunkEnum::None
|
||||||
|
&& holder.dependency_stage != StagedChunkEnum::None
|
||||||
|
{
|
||||||
|
stranded.push(*pos);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for pos in stranded {
|
||||||
|
let holder = self.chunk_map.get_mut(&pos).unwrap();
|
||||||
|
if !holder.occupied.is_null() && self.graph.nodes.contains_key(holder.occupied) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut cur_edge = holder.occupied_by;
|
||||||
|
let mut prev_edge = EdgeKey::null();
|
||||||
|
let mut change_head = None;
|
||||||
|
let mut has_valid_task = false;
|
||||||
|
|
||||||
|
while !cur_edge.is_null() {
|
||||||
|
let edge = self.graph.edges.get(cur_edge).unwrap();
|
||||||
|
if self.graph.nodes.contains_key(edge.to) {
|
||||||
|
prev_edge = cur_edge;
|
||||||
|
cur_edge = edge.next;
|
||||||
|
has_valid_task = true;
|
||||||
|
} else {
|
||||||
|
let next = edge.next;
|
||||||
|
self.graph.edges.remove(cur_edge);
|
||||||
|
cur_edge = next;
|
||||||
|
if prev_edge.is_null() {
|
||||||
|
change_head = Some(next);
|
||||||
|
} else {
|
||||||
|
self.graph.edges.get_mut(prev_edge).unwrap().next = next;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let Some(next) = change_head {
|
||||||
|
holder.occupied_by = next;
|
||||||
|
}
|
||||||
|
|
||||||
|
if !has_valid_task {
|
||||||
|
holder.dependency_stage = StagedChunkEnum::None;
|
||||||
|
self.unload_chunks.insert(pos);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn process_unload_queue(&mut self) {
|
||||||
|
if self.unload_chunks.is_empty() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
let mut unload_chunks = HashSetType::default();
|
let mut unload_chunks = HashSetType::default();
|
||||||
swap(&mut unload_chunks, &mut self.unload_chunks);
|
swap(&mut unload_chunks, &mut self.unload_chunks);
|
||||||
let mut chunks = Vec::with_capacity(unload_chunks.len());
|
let mut chunks = Vec::with_capacity(unload_chunks.len());
|
||||||
@ -568,6 +622,8 @@ impl GenerationSchedule {
|
|||||||
self.chunk_map.remove(&pos);
|
self.chunk_map.remove(&pos);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
self.unload_chunks.insert(pos);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if chunks.is_empty() {
|
if chunks.is_empty() {
|
||||||
@ -593,7 +649,14 @@ impl GenerationSchedule {
|
|||||||
if let Some(chunk) = holder.chunk.take() {
|
if let Some(chunk) = holder.chunk.take() {
|
||||||
let should_save = match &chunk {
|
let should_save = match &chunk {
|
||||||
Chunk::Level(sync_chunk) => sync_chunk.is_dirty(),
|
Chunk::Level(sync_chunk) => sync_chunk.is_dirty(),
|
||||||
Chunk::Proto(_) => save_proto_chunk,
|
Chunk::Proto(proto) => {
|
||||||
|
save_proto_chunk
|
||||||
|
&& !matches!(
|
||||||
|
proto.stage,
|
||||||
|
crate::chunk_system::chunk_state::StagedChunkEnum::Empty
|
||||||
|
| crate::chunk_system::chunk_state::StagedChunkEnum::None
|
||||||
|
)
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
if should_save {
|
if should_save {
|
||||||
@ -773,6 +836,7 @@ impl GenerationSchedule {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
holder.current_stage = StagedChunkEnum::Full;
|
||||||
holder.chunk = Some(Chunk::Level(chunk));
|
holder.chunk = Some(Chunk::Level(chunk));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -783,10 +847,9 @@ impl GenerationSchedule {
|
|||||||
}
|
}
|
||||||
holder.occupied = NodeKey::null();
|
holder.occupied = NodeKey::null();
|
||||||
|
|
||||||
// If this neighbor chunk was only loaded for a dependency and
|
// If this chunk was only loaded for a dependency or cancelled
|
||||||
// is no longer needed, clear dependency_stage and queue unload.
|
// and is no longer needed, clear dependency_stage and queue unload.
|
||||||
if holder.target_stage == StagedChunkEnum::None
|
if holder.target_stage == StagedChunkEnum::None
|
||||||
&& new_pos != pos
|
|
||||||
&& holder.current_stage >= holder.dependency_stage
|
&& holder.current_stage >= holder.dependency_stage
|
||||||
{
|
{
|
||||||
holder.dependency_stage = StagedChunkEnum::None;
|
holder.dependency_stage = StagedChunkEnum::None;
|
||||||
@ -822,14 +885,14 @@ impl GenerationSchedule {
|
|||||||
{
|
{
|
||||||
self.drop_node(holder.occupied);
|
self.drop_node(holder.occupied);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Clear dependency_stage and queue unload if no longer needed
|
// Clear dependency_stage and queue unload if no longer needed
|
||||||
if holder.target_stage == StagedChunkEnum::None
|
if holder.target_stage == StagedChunkEnum::None
|
||||||
&& holder.current_stage >= holder.dependency_stage
|
&& holder.current_stage >= holder.dependency_stage
|
||||||
{
|
{
|
||||||
holder.dependency_stage = StagedChunkEnum::None;
|
holder.dependency_stage = StagedChunkEnum::None;
|
||||||
self.unload_chunks.insert(new_pos);
|
self.unload_chunks.insert(new_pos);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
holder.occupied = NodeKey::null();
|
holder.occupied = NodeKey::null();
|
||||||
@ -927,13 +990,16 @@ impl GenerationSchedule {
|
|||||||
);
|
);
|
||||||
loop {
|
loop {
|
||||||
if level.should_unload.swap(false, Relaxed) {
|
if level.should_unload.swap(false, Relaxed) {
|
||||||
self.unload_chunk();
|
self.garbage_collect_dependencies();
|
||||||
|
self.process_unload_queue();
|
||||||
}
|
}
|
||||||
if level.should_save.swap(false, Relaxed) {
|
if level.should_save.swap(false, Relaxed) {
|
||||||
self.save_all_chunk(false);
|
self.save_all_chunk(false);
|
||||||
}
|
}
|
||||||
if level.shut_down_chunk_system.load(Relaxed) {
|
if level.shut_down_chunk_system.load(Relaxed) {
|
||||||
info!("Saving chunks before shutdown...");
|
info!("Saving chunks before shutdown...");
|
||||||
|
self.garbage_collect_dependencies();
|
||||||
|
self.process_unload_queue();
|
||||||
self.save_all_chunk(true);
|
self.save_all_chunk(true);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -941,6 +1007,11 @@ impl GenerationSchedule {
|
|||||||
// 1. Get latest world state (player moves, etc)
|
// 1. Get latest world state (player moves, etc)
|
||||||
self.resort_work(self.send_level.get());
|
self.resort_work(self.send_level.get());
|
||||||
|
|
||||||
|
// Process unload queue continuously if there are chunks to unload
|
||||||
|
if !self.unload_chunks.is_empty() {
|
||||||
|
self.process_unload_queue();
|
||||||
|
}
|
||||||
|
|
||||||
// 2. Process all pending chunk results from workers
|
// 2. Process all pending chunk results from workers
|
||||||
while let Ok((pos, data)) = self.recv_chunk.try_recv() {
|
while let Ok((pos, data)) = self.recv_chunk.try_recv() {
|
||||||
self.receive_chunk(pos, data);
|
self.receive_chunk(pos, data);
|
||||||
|
|||||||
@ -103,7 +103,11 @@ pub fn place_template(
|
|||||||
|
|
||||||
if let Some(template_nbt) = &block.nbt {
|
if let Some(template_nbt) = &block.nbt {
|
||||||
for (key, value) in &template_nbt.child_tags {
|
for (key, value) in &template_nbt.child_tags {
|
||||||
if key != "x" && key != "y" && key != "z" && key != "id" {
|
if key.as_ref() != "x"
|
||||||
|
&& key.as_ref() != "y"
|
||||||
|
&& key.as_ref() != "z"
|
||||||
|
&& key.as_ref() != "id"
|
||||||
|
{
|
||||||
block_entity_nbt
|
block_entity_nbt
|
||||||
.child_tags
|
.child_tags
|
||||||
.insert(key.clone(), value.clone());
|
.insert(key.clone(), value.clone());
|
||||||
|
|||||||
@ -178,7 +178,7 @@ impl StructureTemplate {
|
|||||||
.iter()
|
.iter()
|
||||||
.filter_map(|(key, value)| {
|
.filter_map(|(key, value)| {
|
||||||
if let NbtTag::String(v) = value {
|
if let NbtTag::String(v) = value {
|
||||||
Some((key.clone(), v.clone()))
|
Some((key.to_string(), v.to_string()))
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|||||||
@ -25,7 +25,7 @@ use pumpkin_data::{Block, block_properties::has_random_ticks, fluid::Fluid};
|
|||||||
use pumpkin_util::math::{position::BlockPos, vector2::Vector2};
|
use pumpkin_util::math::{position::BlockPos, vector2::Vector2};
|
||||||
use pumpkin_util::world_seed::Seed;
|
use pumpkin_util::world_seed::Seed;
|
||||||
use rustc_hash::{FxHashMap, FxHashSet};
|
use rustc_hash::{FxHashMap, FxHashSet};
|
||||||
use std::sync::{Arc, Mutex};
|
use std::sync::{Arc, Mutex, Weak};
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
use std::{
|
use std::{
|
||||||
path::PathBuf,
|
path::PathBuf,
|
||||||
@ -39,7 +39,7 @@ use tracing::{debug, error, info, trace, warn};
|
|||||||
use tokio::{
|
use tokio::{
|
||||||
select,
|
select,
|
||||||
sync::{
|
sync::{
|
||||||
mpsc::{self, UnboundedReceiver},
|
mpsc::{self, Receiver},
|
||||||
oneshot,
|
oneshot,
|
||||||
},
|
},
|
||||||
task::JoinHandle,
|
task::JoinHandle,
|
||||||
@ -226,7 +226,7 @@ impl Level {
|
|||||||
x: pos.x,
|
x: pos.x,
|
||||||
z: pos.y,
|
z: pos.y,
|
||||||
data: tokio::sync::Mutex::new(FxHashMap::default()),
|
data: tokio::sync::Mutex::new(FxHashMap::default()),
|
||||||
dirty: AtomicBool::new(true),
|
dirty: AtomicBool::new(false),
|
||||||
});
|
});
|
||||||
|
|
||||||
level.loaded_entity_chunks.insert(pos, arc_chunk.clone());
|
level.loaded_entity_chunks.insert(pos, arc_chunk.clone());
|
||||||
@ -247,7 +247,7 @@ impl Level {
|
|||||||
x: pos.x,
|
x: pos.x,
|
||||||
z: pos.y,
|
z: pos.y,
|
||||||
data: tokio::sync::Mutex::new(FxHashMap::default()),
|
data: tokio::sync::Mutex::new(FxHashMap::default()),
|
||||||
dirty: AtomicBool::new(true),
|
dirty: AtomicBool::new(false),
|
||||||
});
|
});
|
||||||
|
|
||||||
level_clone
|
level_clone
|
||||||
@ -511,7 +511,7 @@ impl Level {
|
|||||||
self.chunk_watchers.get(chunk).is_some()
|
self.chunk_watchers.get(chunk).is_some()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn clean_memory(&self) {
|
pub fn clean_memory(self: &Arc<Self>) -> Vec<Vector2<i32>> {
|
||||||
self.chunk_watchers.retain(|_, watcher| *watcher != 0);
|
self.chunk_watchers.retain(|_, watcher| *watcher != 0);
|
||||||
|
|
||||||
let entity_chunks_to_remove: Vec<_> = self
|
let entity_chunks_to_remove: Vec<_> = self
|
||||||
@ -521,8 +521,8 @@ impl Level {
|
|||||||
.map(|entry| *entry.key())
|
.map(|entry| *entry.key())
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
for pos in entity_chunks_to_remove {
|
if !entity_chunks_to_remove.is_empty() {
|
||||||
self.loaded_entity_chunks.remove(&pos);
|
self.clean_entity_chunks(&entity_chunks_to_remove);
|
||||||
}
|
}
|
||||||
|
|
||||||
// if the difference is too big, we can shrink the loaded chunks
|
// if the difference is too big, we can shrink the loaded chunks
|
||||||
@ -534,6 +534,7 @@ impl Level {
|
|||||||
if self.loaded_entity_chunks.capacity() - self.loaded_entity_chunks.len() >= 4096 {
|
if self.loaded_entity_chunks.capacity() - self.loaded_entity_chunks.len() >= 4096 {
|
||||||
self.loaded_entity_chunks.shrink_to_fit();
|
self.loaded_entity_chunks.shrink_to_fit();
|
||||||
}
|
}
|
||||||
|
entity_chunks_to_remove
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn get_or_fetch_chunk<R, F: Fn(&SyncChunk) -> R>(
|
pub async fn get_or_fetch_chunk<R, F: Fn(&SyncChunk) -> R>(
|
||||||
@ -590,8 +591,8 @@ impl Level {
|
|||||||
pub fn receive_entity_chunks(
|
pub fn receive_entity_chunks(
|
||||||
self: &Arc<Self>,
|
self: &Arc<Self>,
|
||||||
chunks: Vec<Vector2<i32>>,
|
chunks: Vec<Vector2<i32>>,
|
||||||
) -> UnboundedReceiver<(SyncEntityChunk, bool)> {
|
) -> Receiver<(Weak<ChunkEntityData>, bool)> {
|
||||||
let (sender, receiver) = mpsc::unbounded_channel();
|
let (sender, receiver) = mpsc::channel(64);
|
||||||
let level = self.clone();
|
let level = self.clone();
|
||||||
|
|
||||||
self.spawn_task(async move {
|
self.spawn_task(async move {
|
||||||
@ -602,7 +603,7 @@ impl Level {
|
|||||||
.iter()
|
.iter()
|
||||||
.filter(|pos| {
|
.filter(|pos| {
|
||||||
level.loaded_entity_chunks.get(pos).is_none_or(|chunk| {
|
level.loaded_entity_chunks.get(pos).is_none_or(|chunk| {
|
||||||
let _ = sender.send((chunk.clone(), false));
|
let _ = sender.try_send((Arc::downgrade(chunk.value()), false));
|
||||||
false // Don't fetch
|
false // Don't fetch
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@ -624,7 +625,7 @@ impl Level {
|
|||||||
LoadedData::Loaded(chunk) => {
|
LoadedData::Loaded(chunk) => {
|
||||||
let pos = Vector2::new(chunk.x, chunk.z);
|
let pos = Vector2::new(chunk.x, chunk.z);
|
||||||
level.loaded_entity_chunks.insert(pos, chunk.clone());
|
level.loaded_entity_chunks.insert(pos, chunk.clone());
|
||||||
let _ = sender.send((chunk, false));
|
let _ = sender.send((Arc::downgrade(&chunk), true)).await;
|
||||||
}
|
}
|
||||||
LoadedData::Missing(pos) | LoadedData::Error((pos, _)) => {
|
LoadedData::Missing(pos) | LoadedData::Error((pos, _)) => {
|
||||||
let sender_clone = sender.clone();
|
let sender_clone = sender.clone();
|
||||||
@ -642,7 +643,8 @@ impl Level {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if let Ok(chunk) = rx.await {
|
if let Ok(chunk) = rx.await {
|
||||||
let _ = sender_clone.send((chunk, true));
|
let _ =
|
||||||
|
sender_clone.send((Arc::downgrade(&chunk), true)).await;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@ -41,6 +41,9 @@ impl<'a, T: std::hash::Hash + Eq> ChunkTickScheduler<&'a T> {
|
|||||||
.queued_ticks
|
.queued_ticks
|
||||||
.remove(&(next_tick.position, next_tick.value));
|
.remove(&(next_tick.position, next_tick.value));
|
||||||
}
|
}
|
||||||
|
if inner.queued_ticks.is_empty() {
|
||||||
|
*inner_guard = None;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
res
|
res
|
||||||
}
|
}
|
||||||
|
|||||||
@ -524,7 +524,7 @@ macro_rules! impl_block_entity_for_cooking {
|
|||||||
if let Some(recipes_compound) = nbt.get_compound("RecipesUsed") {
|
if let Some(recipes_compound) = nbt.get_compound("RecipesUsed") {
|
||||||
for (recipe_id, tag) in &recipes_compound.child_tags {
|
for (recipe_id, tag) in &recipes_compound.child_tags {
|
||||||
if let pumpkin_nbt::tag::NbtTag::Int(count) = tag {
|
if let pumpkin_nbt::tag::NbtTag::Int(count) = tag {
|
||||||
recipes_used_map.insert(recipe_id.clone(), *count as u32);
|
recipes_used_map.insert(recipe_id.to_string(), *count as u32);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -160,7 +160,7 @@ impl From<Text> for NbtTag {
|
|||||||
.lock()
|
.lock()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.iter()
|
.iter()
|
||||||
.map(|s| Self::String(s.clone()))
|
.map(|s| Self::String(s.clone().into()))
|
||||||
.collect(),
|
.collect(),
|
||||||
);
|
);
|
||||||
Self::Compound(nbt)
|
Self::Compound(nbt)
|
||||||
|
|||||||
@ -156,7 +156,9 @@ pub fn snbt_colorful_display(tag: &NbtTag, depth: usize) -> Result<TextComponent
|
|||||||
let item_display = snbt_colorful_display(item, depth + 1)
|
let item_display = snbt_colorful_display(item, depth + 1)
|
||||||
.map_err(|string| format!("Error displaying item.{key}: {string}"))?;
|
.map_err(|string| format!("Error displaying item.{key}: {string}"))?;
|
||||||
content = content
|
content = content
|
||||||
.add_child(TextComponent::text(key.clone()).color_named(NamedColor::Aqua))
|
.add_child(
|
||||||
|
TextComponent::text(key.to_string()).color_named(NamedColor::Aqua),
|
||||||
|
)
|
||||||
.add_child(TextComponent::text(": "))
|
.add_child(TextComponent::text(": "))
|
||||||
.add_child(item_display);
|
.add_child(item_display);
|
||||||
|
|
||||||
|
|||||||
@ -661,7 +661,7 @@ impl SnbtParser<'_, '_> {
|
|||||||
} else if literal.eq_ignore_ascii_case("false") {
|
} else if literal.eq_ignore_ascii_case("false") {
|
||||||
Some(NbtTag::Byte(0))
|
Some(NbtTag::Byte(0))
|
||||||
} else {
|
} else {
|
||||||
Some(NbtTag::String(literal))
|
Some(NbtTag::String(literal.into()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -716,7 +716,7 @@ impl SnbtParser<'_, '_> {
|
|||||||
})?;
|
})?;
|
||||||
|
|
||||||
Some(NbtTag::Compound(NbtCompound {
|
Some(NbtTag::Compound(NbtCompound {
|
||||||
child_tags: entries,
|
child_tags: entries.into_iter().map(|(k, v)| (k.into(), v)).collect(),
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -844,7 +844,7 @@ impl SnbtParser<'_, '_> {
|
|||||||
),
|
),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Literal::String(string) => NbtTag::String(string),
|
Literal::String(string) => NbtTag::String(string.into()),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -222,28 +222,25 @@ fn floats() {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn quoted_string_literals() {
|
fn quoted_string_literals() {
|
||||||
assert_parse_ok!("''", NbtTag::String(String::new()));
|
assert_parse_ok!("''", NbtTag::String("".into()));
|
||||||
assert_parse_ok!("\"\"", NbtTag::String(String::new()));
|
assert_parse_ok!("\"\"", NbtTag::String("".into()));
|
||||||
|
|
||||||
assert_parse_ok!("\"'hello'\"", NbtTag::String("'hello'".to_string()));
|
assert_parse_ok!("\"'hello'\"", NbtTag::String("'hello'".into()));
|
||||||
assert_parse_ok!("'\"hello\"'", NbtTag::String("\"hello\"".to_string()));
|
assert_parse_ok!("'\"hello\"'", NbtTag::String("\"hello\"".into()));
|
||||||
assert_parse_ok!("'\\\\'", NbtTag::String("\\".to_string()));
|
assert_parse_ok!("'\\\\'", NbtTag::String("\\".into()));
|
||||||
assert_parse_ok_but_trailing!("'\"'\"", "\"");
|
assert_parse_ok_but_trailing!("'\"'\"", "\"");
|
||||||
|
|
||||||
assert_parse_err!("'\\'", "Invalid string contents", 3, ["\"", "'", "\\"]);
|
assert_parse_err!("'\\'", "Invalid string contents", 3, ["\"", "'", "\\"]);
|
||||||
|
|
||||||
assert_parse_ok!("'\\b'", NbtTag::String("\u{8}".to_string()));
|
assert_parse_ok!("'\\b'", NbtTag::String("\u{8}".into()));
|
||||||
assert_parse_ok!("'hello\\sword'", NbtTag::String("hello word".to_string()));
|
assert_parse_ok!("'hello\\sword'", NbtTag::String("hello word".into()));
|
||||||
assert_parse_ok!(
|
assert_parse_ok!("'hello\\tword\n'", NbtTag::String("hello\tword\n".into()));
|
||||||
"'hello\\tword\n'",
|
assert_parse_ok!("'\\f\\r'", NbtTag::String("\u{c}\r".into()));
|
||||||
NbtTag::String("hello\tword\n".to_string())
|
|
||||||
);
|
|
||||||
assert_parse_ok!("'\\f\\r'", NbtTag::String("\u{c}\r".to_string()));
|
|
||||||
|
|
||||||
assert_parse_ok!("'hello \\x65!'", NbtTag::String("hello e!".to_string()));
|
assert_parse_ok!("'hello \\x65!'", NbtTag::String("hello e!".into()));
|
||||||
assert_parse_ok!(
|
assert_parse_ok!(
|
||||||
"'\\x53\\x65\\u0063\\U00000072\\x65\\x74\\x21'",
|
"'\\x53\\x65\\u0063\\U00000072\\x65\\x74\\x21'",
|
||||||
NbtTag::String("Secret!".to_string())
|
NbtTag::String("Secret!".into())
|
||||||
);
|
);
|
||||||
|
|
||||||
assert_parse_err!(
|
assert_parse_err!(
|
||||||
@ -255,7 +252,7 @@ fn quoted_string_literals() {
|
|||||||
|
|
||||||
assert_parse_ok!(
|
assert_parse_ok!(
|
||||||
"'\\uD83C\\uDF83 or \\U0001F383'",
|
"'\\uD83C\\uDF83 or \\U0001F383'",
|
||||||
NbtTag::String("🎃 or 🎃".to_string())
|
NbtTag::String("🎃 or 🎃".into())
|
||||||
);
|
);
|
||||||
|
|
||||||
// TODO: make tests for when \N is implemented
|
// TODO: make tests for when \N is implemented
|
||||||
@ -263,19 +260,19 @@ fn quoted_string_literals() {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn unquoted_string_literals() {
|
fn unquoted_string_literals() {
|
||||||
assert_parse_ok!("abc", NbtTag::String("abc".to_string()));
|
assert_parse_ok!("abc", NbtTag::String("abc".into()));
|
||||||
assert_parse_ok!(
|
assert_parse_ok!(
|
||||||
"abc-def_ghi+jkl.mno",
|
"abc-def_ghi+jkl.mno",
|
||||||
NbtTag::String("abc-def_ghi+jkl.mno".to_string())
|
NbtTag::String("abc-def_ghi+jkl.mno".into())
|
||||||
);
|
);
|
||||||
assert_parse_ok!("_1234", NbtTag::String("_1234".to_string()));
|
assert_parse_ok!("_1234", NbtTag::String("_1234".into()));
|
||||||
assert_parse_ok!("x+1", NbtTag::String("x+1".to_string()));
|
assert_parse_ok!("x+1", NbtTag::String("x+1".into()));
|
||||||
assert_parse_ok_but_trailing!("x*1", "*1");
|
assert_parse_ok_but_trailing!("x*1", "*1");
|
||||||
|
|
||||||
assert_parse_ok!("true", NbtTag::Byte(1));
|
assert_parse_ok!("true", NbtTag::Byte(1));
|
||||||
assert_parse_ok!("false", NbtTag::Byte(0));
|
assert_parse_ok!("false", NbtTag::Byte(0));
|
||||||
assert_parse_ok!("maybe", NbtTag::String("maybe".to_string()));
|
assert_parse_ok!("maybe", NbtTag::String("maybe".into()));
|
||||||
assert_parse_ok!("bool", NbtTag::String("bool".to_string()));
|
assert_parse_ok!("bool", NbtTag::String("bool".into()));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@ -340,36 +337,36 @@ fn maps() {
|
|||||||
assert_parse_ok!(
|
assert_parse_ok!(
|
||||||
"{x:5}",
|
"{x:5}",
|
||||||
NbtTag::Compound(NbtCompound {
|
NbtTag::Compound(NbtCompound {
|
||||||
child_tags: HashMap::from([("x".to_string(), NbtTag::Int(5))])
|
child_tags: HashMap::from([("x".into(), NbtTag::Int(5))])
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
assert_parse_ok!(
|
assert_parse_ok!(
|
||||||
"{ a:1b,B :2uS , c:3L }",
|
"{ a:1b,B :2uS , c:3L }",
|
||||||
NbtTag::Compound(NbtCompound {
|
NbtTag::Compound(NbtCompound {
|
||||||
child_tags: HashMap::from([
|
child_tags: HashMap::from([
|
||||||
("a".to_string(), NbtTag::Byte(1)),
|
("a".into(), NbtTag::Byte(1)),
|
||||||
("B".to_string(), NbtTag::Short(2)),
|
("B".into(), NbtTag::Short(2)),
|
||||||
("c".to_string(), NbtTag::Long(3))
|
("c".into(), NbtTag::Long(3))
|
||||||
])
|
])
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
assert_parse_ok!(
|
assert_parse_ok!(
|
||||||
"{ a:1b, \"a\":2b, 'a':\"hi\" }",
|
"{ a:1b, \"a\":2b, 'a':\"hi\" }",
|
||||||
nbt_compound_tag! {
|
nbt_compound_tag! {
|
||||||
"a": NbtTag::String("hi".to_string())
|
"a": NbtTag::String("hi".into())
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
assert_parse_ok!(
|
assert_parse_ok!(
|
||||||
"{ elem: 'this', next: { elem: 'is', next: { elem: 'a', next: { elem: 'linked', next: 'list' } } } }",
|
"{ elem: 'this', next: { elem: 'is', next: { elem: 'a', next: { elem: 'linked', next: 'list' } } } }",
|
||||||
nbt_compound_tag! {
|
nbt_compound_tag! {
|
||||||
"elem": NbtTag::String("this".to_string()),
|
"elem": NbtTag::String("this".into()),
|
||||||
"next": nbt_compound_tag! {
|
"next": nbt_compound_tag! {
|
||||||
"elem": NbtTag::String("is".to_string()),
|
"elem": NbtTag::String("is".into()),
|
||||||
"next": nbt_compound_tag! {
|
"next": nbt_compound_tag! {
|
||||||
"elem": NbtTag::String("a".to_string()),
|
"elem": NbtTag::String("a".into()),
|
||||||
"next": nbt_compound_tag! {
|
"next": nbt_compound_tag! {
|
||||||
"elem": NbtTag::String("linked".to_string()),
|
"elem": NbtTag::String("linked".into()),
|
||||||
"next": NbtTag::String("list".to_string()),
|
"next": NbtTag::String("list".into()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -418,22 +415,22 @@ fn lists() {
|
|||||||
assert_parse_ok!("[ ]", NbtTag::List(Vec::new()));
|
assert_parse_ok!("[ ]", NbtTag::List(Vec::new()));
|
||||||
assert_parse_ok!(
|
assert_parse_ok!(
|
||||||
"[5, _]",
|
"[5, _]",
|
||||||
NbtTag::List(vec![NbtTag::Int(5), NbtTag::String("_".to_string())])
|
NbtTag::List(vec![NbtTag::Int(5), NbtTag::String("_".into())])
|
||||||
);
|
);
|
||||||
assert_parse_ok!(
|
assert_parse_ok!(
|
||||||
"[a, [true, c], [4s, uuid(f-0-0-0-0), f, [[], 7f, [\"\\n\", [200Ub, {x: 1e1}]]]]]",
|
"[a, [true, c], [4s, uuid(f-0-0-0-0), f, [[], 7f, [\"\\n\", [200Ub, {x: 1e1}]]]]]",
|
||||||
NbtTag::List(vec![
|
NbtTag::List(vec![
|
||||||
NbtTag::String("a".to_string()),
|
NbtTag::String("a".into()),
|
||||||
NbtTag::List(vec![NbtTag::Byte(1), NbtTag::String("c".to_string())]),
|
NbtTag::List(vec![NbtTag::Byte(1), NbtTag::String("c".into())]),
|
||||||
NbtTag::List(vec![
|
NbtTag::List(vec![
|
||||||
NbtTag::Short(4),
|
NbtTag::Short(4),
|
||||||
NbtTag::IntArray(vec![15, 0, 0, 0]),
|
NbtTag::IntArray(vec![15, 0, 0, 0]),
|
||||||
NbtTag::String("f".to_string()),
|
NbtTag::String("f".into()),
|
||||||
NbtTag::List(vec![
|
NbtTag::List(vec![
|
||||||
NbtTag::List(Vec::new()),
|
NbtTag::List(Vec::new()),
|
||||||
NbtTag::Float(7.0),
|
NbtTag::Float(7.0),
|
||||||
NbtTag::List(vec![
|
NbtTag::List(vec![
|
||||||
NbtTag::String("\n".to_string()),
|
NbtTag::String("\n".into()),
|
||||||
NbtTag::List(vec![
|
NbtTag::List(vec![
|
||||||
NbtTag::Byte(-56),
|
NbtTag::Byte(-56),
|
||||||
nbt_compound_tag! {
|
nbt_compound_tag! {
|
||||||
|
|||||||
@ -142,7 +142,7 @@ pub struct ChunkManager {
|
|||||||
chunks_per_tick: usize,
|
chunks_per_tick: usize,
|
||||||
center: Vector2<i32>,
|
center: Vector2<i32>,
|
||||||
view_distance: u8,
|
view_distance: u8,
|
||||||
chunk_listener: Receiver<(Vector2<i32>, SyncChunk)>,
|
chunk_listener: Receiver<(Vector2<i32>, Weak<ChunkData>)>,
|
||||||
chunk_sent: HashMap<Vector2<i32>, Weak<ChunkData>>,
|
chunk_sent: HashMap<Vector2<i32>, Weak<ChunkData>>,
|
||||||
chunk_queue: BinaryHeap<HeapNode>,
|
chunk_queue: BinaryHeap<HeapNode>,
|
||||||
entity_chunk_queue: VecDeque<(Vector2<i32>, Weak<ChunkEntityData>)>,
|
entity_chunk_queue: VecDeque<(Vector2<i32>, Weak<ChunkEntityData>)>,
|
||||||
@ -159,7 +159,7 @@ impl ChunkManager {
|
|||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn new(
|
pub fn new(
|
||||||
chunks_per_tick: usize,
|
chunks_per_tick: usize,
|
||||||
chunk_listener: Receiver<(Vector2<i32>, SyncChunk)>,
|
chunk_listener: Receiver<(Vector2<i32>, Weak<ChunkData>)>,
|
||||||
world: Arc<World>,
|
world: Arc<World>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
Self {
|
Self {
|
||||||
@ -201,14 +201,15 @@ impl ChunkManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn pull_new_chunks(&mut self) {
|
pub fn pull_new_chunks(&mut self) {
|
||||||
while let Ok((pos, chunk)) = self.chunk_listener.try_recv() {
|
while let Ok((pos, chunk_weak)) = self.chunk_listener.try_recv() {
|
||||||
let dst = Self::chebyshev(pos, self.center);
|
let dst = Self::chebyshev(pos, self.center);
|
||||||
if dst > i32::from(self.view_distance) {
|
if dst > i32::from(self.view_distance) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if self.should_enqueue_chunk(pos, &chunk) {
|
if let Some(chunk) = chunk_weak.upgrade()
|
||||||
self.chunk_queue
|
&& self.should_enqueue_chunk(pos, &chunk)
|
||||||
.push(HeapNode(dst, pos, Arc::downgrade(&chunk)));
|
{
|
||||||
|
self.chunk_queue.push(HeapNode(dst, pos, chunk_weak));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -801,7 +802,7 @@ impl Player {
|
|||||||
// Remove chunks with no watchers from the cache
|
// Remove chunks with no watchers from the cache
|
||||||
if !chunks_to_clean.is_empty() {
|
if !chunks_to_clean.is_empty() {
|
||||||
level.clean_entity_chunks(&chunks_to_clean);
|
level.clean_entity_chunks(&chunks_to_clean);
|
||||||
world.remove_entities_in_chunks(&chunks_to_clean).await;
|
world.remove_entities_in_chunks(&chunks_to_clean);
|
||||||
}
|
}
|
||||||
// Remove left over entries from all possiblily loaded chunks
|
// Remove left over entries from all possiblily loaded chunks
|
||||||
level.clean_memory();
|
level.clean_memory();
|
||||||
|
|||||||
@ -115,7 +115,7 @@ pub async fn update_position(player: &Arc<Player>) {
|
|||||||
|
|
||||||
if !chunks_to_clean.is_empty() {
|
if !chunks_to_clean.is_empty() {
|
||||||
world.level.clean_entity_chunks(&chunks_to_clean);
|
world.level.clean_entity_chunks(&chunks_to_clean);
|
||||||
world.remove_entities_in_chunks(&chunks_to_clean).await;
|
world.remove_entities_in_chunks(&chunks_to_clean);
|
||||||
}
|
}
|
||||||
|
|
||||||
if !loading_chunks.is_empty() {
|
if !loading_chunks.is_empty() {
|
||||||
|
|||||||
@ -848,9 +848,11 @@ impl World {
|
|||||||
let entity_elapsed = entity_start.elapsed();
|
let entity_elapsed = entity_start.elapsed();
|
||||||
|
|
||||||
let block_entity_start = tokio::time::Instant::now();
|
let block_entity_start = tokio::time::Instant::now();
|
||||||
|
let active_chunks = self.active_chunks.load();
|
||||||
let block_entities: Vec<Arc<dyn BlockEntity>> = self
|
let block_entities: Vec<Arc<dyn BlockEntity>> = self
|
||||||
.block_entities
|
.block_entities
|
||||||
.iter()
|
.iter()
|
||||||
|
.filter(|e| active_chunks.contains(&e.key().chunk_position()))
|
||||||
.map(|e| e.value().clone())
|
.map(|e| e.value().clone())
|
||||||
.collect();
|
.collect();
|
||||||
let block_entity_count = block_entities.len();
|
let block_entity_count = block_entities.len();
|
||||||
@ -943,6 +945,10 @@ impl World {
|
|||||||
// Auto-save logic
|
// Auto-save logic
|
||||||
if level_time.world_age % 100 == 0 {
|
if level_time.world_age % 100 == 0 {
|
||||||
self.level.should_unload.store(true, Relaxed);
|
self.level.should_unload.store(true, Relaxed);
|
||||||
|
let cleaned_chunks = self.level.clean_memory();
|
||||||
|
if !cleaned_chunks.is_empty() {
|
||||||
|
self.remove_entities_in_chunks(&cleaned_chunks);
|
||||||
|
}
|
||||||
// If autosave is configured and this tick will trigger an autosave, don't double notify
|
// If autosave is configured and this tick will trigger an autosave, don't double notify
|
||||||
if self.level.autosave_ticks == 0 {
|
if self.level.autosave_ticks == 0 {
|
||||||
self.level.level_channel.notify();
|
self.level.level_channel.notify();
|
||||||
@ -2661,9 +2667,14 @@ impl World {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let Some((chunk, _first_load)) = recv_result else {
|
let Some((chunk_weak, first_load)) = recv_result else {
|
||||||
break;
|
break;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let Some(chunk) = chunk_weak.upgrade() else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
|
||||||
let position = Vector2::new(chunk.x, chunk.z);
|
let position = Vector2::new(chunk.x, chunk.z);
|
||||||
|
|
||||||
if !level.is_chunk_watched(&position) {
|
if !level.is_chunk_watched(&position) {
|
||||||
@ -2692,24 +2703,27 @@ impl World {
|
|||||||
|
|
||||||
ids_to_remove.push(VarInt(base_entity.entity_id));
|
ids_to_remove.push(VarInt(base_entity.entity_id));
|
||||||
|
|
||||||
let mut nbt = NbtCompound::new();
|
if first_load {
|
||||||
entity.write_nbt(&mut nbt).await;
|
let mut nbt = NbtCompound::new();
|
||||||
if let Some(old_chunk) = base_entity.first_loaded_chunk_position.load() {
|
entity.write_nbt(&mut nbt).await;
|
||||||
let old_chunk = old_chunk.to_vec2_i32();
|
if let Some(old_chunk) = base_entity.first_loaded_chunk_position.load()
|
||||||
let chunk = world.level.get_entity_chunk(old_chunk).await;
|
{
|
||||||
chunk.mark_dirty(true);
|
let old_chunk = old_chunk.to_vec2_i32();
|
||||||
let base_entity = entity.get_entity();
|
let chunk = world.level.get_entity_chunk(old_chunk).await;
|
||||||
let current_chunk_coordinate =
|
chunk.mark_dirty(true);
|
||||||
base_entity.block_pos.load().chunk_position();
|
let base_entity = entity.get_entity();
|
||||||
|
let current_chunk_coordinate =
|
||||||
|
base_entity.block_pos.load().chunk_position();
|
||||||
|
|
||||||
let mut data = chunk.data.lock().await;
|
let mut data = chunk.data.lock().await;
|
||||||
if old_chunk == current_chunk_coordinate {
|
if old_chunk == current_chunk_coordinate {
|
||||||
data.insert(*uuid, nbt);
|
data.insert(*uuid, nbt);
|
||||||
continue;
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// The chunk has changed, lets remove the entity from the old chunk
|
||||||
|
data.remove(uuid);
|
||||||
}
|
}
|
||||||
|
|
||||||
// The chunk has changed, lets remove the entity from the old chunk
|
|
||||||
data.remove(uuid);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if !ids_to_remove.is_empty() {
|
if !ids_to_remove.is_empty() {
|
||||||
@ -2744,9 +2758,11 @@ impl World {
|
|||||||
.await;
|
.await;
|
||||||
entity.init_data_tracker().await;
|
entity.init_data_tracker().await;
|
||||||
|
|
||||||
entities_to_add.push(entity);
|
if first_load {
|
||||||
|
entities_to_add.push(entity);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if !entities_to_add.is_empty() {
|
if first_load && !entities_to_add.is_empty() {
|
||||||
world.entities.rcu(|current_entities| {
|
world.entities.rcu(|current_entities| {
|
||||||
let mut new_entities = (**current_entities).clone();
|
let mut new_entities = (**current_entities).clone();
|
||||||
new_entities.extend(entities_to_add.iter().cloned());
|
new_entities.extend(entities_to_add.iter().cloned());
|
||||||
@ -3215,7 +3231,7 @@ impl World {
|
|||||||
self.remove_entity_data(base_entity).await;
|
self.remove_entity_data(base_entity).await;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn remove_entities_in_chunks(&self, chunks: &[Vector2<i32>]) {
|
pub fn remove_entities_in_chunks(&self, chunks: &[Vector2<i32>]) {
|
||||||
let chunks_set: FxHashSet<_> = chunks.iter().copied().collect();
|
let chunks_set: FxHashSet<_> = chunks.iter().copied().collect();
|
||||||
let mut entities_to_remove = Vec::new();
|
let mut entities_to_remove = Vec::new();
|
||||||
|
|
||||||
@ -3236,8 +3252,12 @@ impl World {
|
|||||||
|
|
||||||
for entity in entities_to_remove {
|
for entity in entities_to_remove {
|
||||||
self.spawn_state.load().remove_entity(self, entity.as_ref());
|
self.spawn_state.load().remove_entity(self, entity.as_ref());
|
||||||
self.remove_entity_data(entity.get_entity()).await;
|
// Important: We do NOT call remove_entity_data here because we want the entities
|
||||||
|
// to persist in the chunk data on disk. We only remove them from the active world (RAM).
|
||||||
}
|
}
|
||||||
|
|
||||||
|
self.block_entities
|
||||||
|
.retain(|pos, _| !chunks_set.contains(&pos.chunk_position()));
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn set_block_breaking(&self, from: &Entity, location: BlockPos, progress: i32) {
|
pub async fn set_block_breaking(&self, from: &Entity, location: BlockPos, progress: i32) {
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user