From c51cf1ecfdcf4ee1f696ddcba093609d93efd3d1 Mon Sep 17 00:00:00 2001 From: RTTV Date: Sat, 16 Sep 2023 11:11:10 -0400 Subject: [PATCH] ima start focusing on school now --- Cargo.lock | 91 +- Cargo.toml | 5 +- src/assets.rs | 2 +- src/elements/array.rs | 18 +- src/elements/chunk.rs | 162 +-- src/elements/compound.rs | 6 +- src/elements/element.rs | 2089 ++++++++++++++++++++++++++++++++ src/elements/element_action.rs | 69 +- src/elements/element_type.rs | 1178 ------------------ src/elements/list.rs | 4 +- src/elements/string.rs | 41 +- src/main.rs | 172 ++- src/selected_text.rs | 2 +- src/shader.rs | 43 + src/shader.wgsl | 39 - src/tab.rs | 53 +- src/text_shader.rs | 73 ++ src/text_shader.wgsl | 69 -- src/tree_travel.rs | 435 ++++--- src/vertex_buffer_builder.rs | 2 +- src/window.rs | 25 +- src/workbench.rs | 296 ++--- src/workbench_action.rs | 93 +- 23 files changed, 2932 insertions(+), 2035 deletions(-) create mode 100644 src/elements/element.rs delete mode 100644 src/elements/element_type.rs create mode 100644 src/shader.rs delete mode 100644 src/shader.wgsl create mode 100644 src/text_shader.rs delete mode 100644 src/text_shader.wgsl diff --git a/Cargo.lock b/Cargo.lock index 168dac7..378cb2c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -55,6 +55,15 @@ dependencies = [ "version_check", ] +[[package]] +name = "aho-corasick" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c378d78423fdad8089616f827526ee33c19f2fddbd5de1629152c9593ba4783" +dependencies = [ + "memchr", +] + [[package]] name = "allocator-api2" version = "0.2.16" @@ -942,6 +951,25 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "naga" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbcc2e0513220fd2b598e6068608d4462db20322c0e77e47f6f488dfcfc279cb" +dependencies = [ + "bit-set", + "bitflags 1.3.2", + "codespan-reporting", + "hexf-parse", + "indexmap 1.9.3", + "log", + "num-traits", + "rustc-hash", + "termcolor", + "thiserror", + "unicode-xid", +] + [[package]] name = "naga" version = "0.13.0" @@ -962,6 +990,18 @@ dependencies = [ "unicode-xid", ] +[[package]] +name = "naga-to-tokenstream" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3bc1416a94f763ebf4eb907b6137428fc855cbe29c69ee7b3f4d11fe8a945bd" +dependencies = [ + "naga 0.12.3", + "proc-macro2", + "quote", + "syn 1.0.109", +] + [[package]] name = "nbtworkbench" version = "0.1.6" @@ -973,8 +1013,10 @@ dependencies = [ "hashbrown 0.14.0", "notify", "pollster", + "static_assertions", "uuid", "wgpu", + "wgsl-inline", "winit", "winres", "zune-inflate", @@ -1348,6 +1390,35 @@ dependencies = [ "bitflags 1.3.2", ] +[[package]] +name = "regex" +version = "1.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "697061221ea1b4a94a624f67d0ae2bfe4e22b8a17b6a192afb11046542cc8c47" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2f401f4955220693b56f8ec66ee9c78abffd8d1c4f23dc41a23839eb88f0795" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbb5fb1acd8a1a18b3dd5be62d25485eb770e05afb408a9627d14d451bae12da" + [[package]] name = "renderdoc-sys" version = "1.0.0" @@ -1862,7 +1933,7 @@ dependencies = [ "cfg-if", "js-sys", "log", - "naga", + "naga 0.13.0", "parking_lot", "profiling", "raw-window-handle", @@ -1887,7 +1958,7 @@ dependencies = [ "bitflags 2.4.0", "codespan-reporting", "log", - "naga", + "naga 0.13.0", "parking_lot", "profiling", "raw-window-handle", @@ -1924,7 +1995,7 @@ dependencies = [ "libloading 0.8.0", "log", "metal", - "naga", + "naga 0.13.0", "objc", "parking_lot", "profiling", @@ -1951,6 +2022,20 @@ dependencies = [ "web-sys", ] +[[package]] +name = "wgsl-inline" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42ff9070ca90e917123d4a2737fa3b55599db74b11ce4837af3b77578a518dc6" +dependencies = [ + "naga 0.12.3", + "naga-to-tokenstream", + "proc-macro2", + "quote", + "regex", + "syn 1.0.109", +] + [[package]] name = "widestring" version = "1.0.2" diff --git a/Cargo.toml b/Cargo.toml index 58d9cbb..5ec2d23 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "nbtworkbench" -version = "0.1.6" +version = "0.1.7" edition = "2021" description = "A modern NBT Editor written in rust. Optimized beyond belief" license-file = "LICENSE" @@ -29,7 +29,6 @@ codegen-units = 1 [profile.flame] inherits = "release" -lto = "fat" strip = false debug = true @@ -55,3 +54,5 @@ hashbrown = { version = "0.14.0", features = ["raw"] } notify = "6.0.1" uuid = { version = "1.4.1", features = ["v4"] } compact_str = "0.7.1" +wgsl-inline = "0.1.3" # minify no work for some reason +static_assertions = "1.1.0" diff --git a/src/assets.rs b/src/assets.rs index d1a7172..f24a886 100644 --- a/src/assets.rs +++ b/src/assets.rs @@ -142,7 +142,7 @@ pub const TOOLTIP_Z: u8 = 240; pub fn icon() -> Vec { #[cfg(debug_assertions)] let start = unsafe { core::arch::x86_64::_rdtsc() }; - let original = match (SystemTime::now().duration_since(SystemTime::UNIX_EPOCH).unwrap().as_micros() & 7) as u8 { + let original = match (SystemTime::now().duration_since(SystemTime::UNIX_EPOCH).expect("time cant go backwards").as_micros() & 7) as u8 { // its a good random only because its used once 0 => OTHERSIDE_MUSIC_DISC_ICON, 1 => PIGSTEP_MUSIC_DISC_ICON, diff --git a/src/elements/array.rs b/src/elements/array.rs index 5e07de2..7662406 100644 --- a/src/elements/array.rs +++ b/src/elements/array.rs @@ -68,11 +68,9 @@ macro_rules! array { let vec = alloc(Layout::array::(len).unwrap_unchecked()).cast::(); for idx in 0..len { let mut element = NbtElement { - data: ManuallyDrop::new(NbtElementData { - $element_field: core::mem::transmute(<$t>::from_be_bytes( - decoder.data.add(idx * core::mem::size_of::<$t>()).cast::<[u8; core::mem::size_of::<$t>()]>().read(), - )), - }), + $element_field: core::mem::transmute(<$t>::from_be_bytes( + decoder.data.add(idx * core::mem::size_of::<$t>()).cast::<[u8; core::mem::size_of::<$t>()]>().read(), + )), }; element.id.id = $id; vec.add(idx).write(element); @@ -100,7 +98,7 @@ macro_rules! array { impl $name { #[inline(always)] fn transmute(element: &NbtElement) -> $t { - unsafe { element.data.deref().$element_field.deref().value } + unsafe { element.deref().$element_field.deref().value } } #[inline] @@ -239,10 +237,10 @@ macro_rules! array { } let ghost_tail_mod = if let Some((id, x, y, _)) = ctx.ghost && ctx.pos() + (0, 16 - *remaining_scroll * 16 - 8) == (x, y) && id == $id { - false - } else { - true - }; + false + } else { + true + }; builder.draw_texture(ctx.pos() - (16, 0), CONNECTION_UV, (16, (!(idx == self.len() - 1 && ghost_tail_mod)) as usize * 7 + 9)); if !tail { diff --git a/src/elements/chunk.rs b/src/elements/chunk.rs index 71d7d7a..e3c2d07 100644 --- a/src/elements/chunk.rs +++ b/src/elements/chunk.rs @@ -8,9 +8,9 @@ use std::thread::Scope; use compact_str::{format_compact, CompactString, ToCompactString}; use zune_inflate::{DeflateDecoder, DeflateOptions}; -use crate::assets::*; -use crate::elements::compound::{CompoundMapIter, CompoundMapIterMut, NbtCompound}; -use crate::elements::element_type::NbtElement; +use crate::assets::{BASE_TEXT_Z, BASE_Z, CHUNK_UV, CONNECTION_UV, HEADER_SIZE, LINE_NUMBER_CONNECTOR_Z, LINE_NUMBER_SEPARATOR_UV, REGION_UV}; +use crate::elements::compound::NbtCompound; +use crate::elements::element::NbtElement; use crate::elements::list::{ValueIterator, ValueMutIterator}; use crate::encoder::UncheckedBufWriter; use crate::tab::FileFormat; @@ -136,6 +136,7 @@ impl NbtRegion { let (format, element) = thread.join().ok()??; region.insert_unchecked( pos, + region.len(), NbtElement::Chunk(NbtChunk::from_compound(core::mem::transmute(element), ((pos >> 5) as u8 & 31, pos as u8 & 31), format, timestamp)), ); } @@ -250,22 +251,20 @@ impl NbtRegion { /// * Index is outside the range of `NbtRegion` #[inline] pub fn insert(&mut self, idx: usize, mut value: NbtElement) -> Result<(), NbtElement> { - unsafe { - if value.id() == NbtChunk::ID { - let mut pos = ((value.data.chunk.x as u16) << 5) | (value.data.chunk.z as u16); - let (map, chunks) = &mut *self.chunks; - while !chunks[pos as usize].is_null() && pos < chunks.len() as u16 { - pos += 1; - } - value.data.deref_mut().chunk.x = (pos >> 5) as u8 & 31; - value.data.deref_mut().chunk.z = pos as u8 & 31; - if pos < chunks.len() as u16 && idx <= map.len() && chunks[pos as usize].is_null() { - let (height, true_height) = (value.height(), value.true_height()); - map.insert(idx, pos); - chunks[map[idx] as usize] = value; - self.increment(height, true_height); - return Ok(()); - } + if let Some(chunk) = value.as_chunk_mut() { + let mut pos = ((chunk.x as u16) << 5) | (chunk.z as u16); + let (map, chunks) = &mut *self.chunks; + while !chunks[pos as usize].is_null() && pos < chunks.len() as u16 { + pos += 1; + } + chunk.x = (pos >> 5) as u8 & 31; + chunk.z = pos as u8 & 31; + if pos < chunks.len() as u16 && idx <= map.len() && chunks[pos as usize].is_null() { + let (height, true_height) = (value.height(), value.true_height()); + map.insert(idx, pos); + chunks[map[idx] as usize] = value; + self.increment(height, true_height); + return Ok(()); } } @@ -280,10 +279,10 @@ impl NbtRegion { /// /// * `pos` is between 0..=1023 #[inline] - pub unsafe fn insert_unchecked(&mut self, pos: usize, value: NbtElement) { + pub unsafe fn insert_unchecked(&mut self, pos: usize, idx: usize, value: NbtElement) { self.increment(value.height(), value.true_height()); let (map, chunks) = &mut *self.chunks; - map.push(pos as u16); + map.insert(idx, pos as u16); unsafe { chunks.as_mut_ptr().cast::().add(pos).write(value); } @@ -456,8 +455,8 @@ impl NbtRegion { #[inline] pub fn drop(&mut self, mut key: Option, mut element: NbtElement, y: &mut usize, depth: usize, target_depth: usize, mut line_number: usize, indices: &mut Vec) -> DropFn { - if *y < 16 && *y >= 8 && depth == target_depth && element.id() == NbtChunk::ID { - let (_x, z) = unsafe { (element.data.chunk.x, element.data.chunk.z) }; + if *y < 16 && *y >= 8 && depth == target_depth && let Some(chunk) = element.as_chunk() { + let (_x, z) = (chunk.x, chunk.z); let before = (self.height(), self.true_height()); indices.push(0); if let Err(element) = self.insert(0, element) { @@ -465,8 +464,8 @@ impl NbtRegion { } self.open = true; return DropFn::Dropped(self.height as usize - before.0, self.true_height as usize - before.1, Some(z.to_compact_string()), line_number + 1); - } else if self.height() == 1 && *y < 24 && *y >= 16 && depth == target_depth && element.id() == NbtChunk::ID { - let (_x, z) = unsafe { (element.data.chunk.x, element.data.chunk.z) }; + } else if self.height() == 1 && *y < 24 && *y >= 16 && depth == target_depth && let Some(chunk) = element.as_chunk() { + let (_x, z) = (chunk.x, chunk.z); let before = self.true_height(); indices.push(self.len()); if let Err(element) = self.insert(self.len(), element) { @@ -489,14 +488,14 @@ impl NbtRegion { for (idx, value) in self.children_mut().enumerate() { *ptr = idx; let heights = (element.height(), element.true_height()); - if *y < 8 && depth == target_depth && element.id() == NbtChunk::ID { - let (_x, z) = unsafe { (element.data.chunk.x, element.data.chunk.z) }; + if *y < 8 && depth == target_depth && let Some(chunk) = element.as_chunk() { + let (_x, z) = (chunk.x, chunk.z); if let Err(element) = self.insert(idx, element) { return DropFn::InvalidType(key, element); } return DropFn::Dropped(heights.0, heights.1, Some(z.to_compact_string()), line_number + 1); - } else if *y >= value.height() * 16 - 8 && *y < value.height() * 16 && depth == target_depth && element.id() == NbtChunk::ID { - let (_x, z) = unsafe { (element.data.chunk.x, element.data.chunk.z) }; + } else if *y >= value.height() * 16 - 8 && *y < value.height() * 16 && depth == target_depth && let Some(chunk) = element.as_chunk() { + let (_x, z) = (chunk.x, chunk.z); *ptr = idx + 1; let true_height = element.true_height(); if let Err(element) = self.insert(idx + 1, element) { @@ -578,10 +577,8 @@ impl NbtRegion { impl Debug for NbtRegion { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { let mut r#struct = f.debug_struct("Region"); - for child in self.children() { - unsafe { - r#struct.field(&format!("x: {:02}, z: {:02}", child.data.chunk.x, child.data.chunk.z), &child.data.chunk); - } + for chunk in self.children().map(|x| unsafe { x.as_chunk_unchecked() }) { + r#struct.field(&format!("x: {:02}, z: {:02}", chunk.x, chunk.z), &chunk); } r#struct.finish() } @@ -590,7 +587,7 @@ impl Debug for NbtRegion { #[repr(C)] #[allow(clippy::module_name_repetitions)] pub struct NbtChunk { - pub inner: Box, + inner: Box, last_modified: u32, // need to restrict this file format to only use GZIP, ZLIB and Uncompressed compression: FileFormat, @@ -656,77 +653,6 @@ impl NbtChunk { } } - #[inline] - pub fn increment(&mut self, amount: usize, true_amount: usize) { - self.inner.increment(amount, true_amount); - } - - #[inline] - pub fn decrement(&mut self, amount: usize, true_amount: usize) { - self.inner.decrement(amount, true_amount); - } - - #[inline] - #[must_use] - pub const fn height(&self) -> usize { - self.inner.height() - } - - #[inline] - #[must_use] - pub const fn true_height(&self) -> usize { - self.inner.true_height() - } - - #[inline] - pub fn toggle(&mut self) -> Option<()> { - self.inner.toggle() - } - - #[inline] - #[must_use] - pub const fn open(&self) -> bool { - self.inner.open() - } - - #[inline] - #[must_use] - pub fn len(&self) -> usize { - self.inner.len() - } - - #[inline] - #[must_use] - pub fn is_empty(&self) -> bool { - self.inner.is_empty() - } - - /// # Errors - /// - /// * Never - #[inline] - pub fn insert_full(&mut self, idx: usize, key: CompactString, value: NbtElement) { - self.inner.insert(idx, key, value); - } - - #[inline] - #[must_use] - pub fn remove_idx(&mut self, idx: usize) -> Option<(CompactString, NbtElement)> { - self.inner.remove_idx(idx) - } - - #[inline] - #[must_use] - pub fn get(&self, idx: usize) -> Option<(&str, &NbtElement)> { - self.inner.get(idx) - } - - #[inline] - #[must_use] - pub fn get_mut(&mut self, idx: usize) -> Option<(&str, &mut NbtElement)> { - self.inner.get_mut(idx) - } - #[inline] #[must_use] pub fn value(&self) -> String { @@ -862,25 +788,22 @@ impl NbtChunk { } #[inline] - #[must_use] - pub fn children(&self) -> CompoundMapIter<'_> { - self.inner.children() + pub fn render_icon(pos: impl Into<(usize, usize)>, z: u8, builder: &mut VertexBufferBuilder) { + builder.draw_texture_z(pos, z, CHUNK_UV, (16, 16)); } +} - #[inline] - #[must_use] - pub fn children_mut(&mut self) -> CompoundMapIterMut<'_> { - self.inner.children_mut() - } +impl Deref for NbtChunk { + type Target = NbtCompound; - #[inline] - pub fn shut(&mut self) { - self.inner.shut(); + fn deref(&self) -> &Self::Target { + &self.inner } +} - #[inline] - pub fn render_icon(pos: impl Into<(usize, usize)>, z: u8, builder: &mut VertexBufferBuilder) { - builder.draw_texture_z(pos, z, CHUNK_UV, (16, 16)); +impl DerefMut for NbtChunk { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.inner } } @@ -902,6 +825,7 @@ impl Display for NbtChunk { } } +#[allow(clippy::missing_fields_in_debug)] impl Debug for NbtChunk { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { write!(f, "{} | {}", self.x, self.z)?; diff --git a/src/elements/compound.rs b/src/elements/compound.rs index 96cd485..925ce5a 100644 --- a/src/elements/compound.rs +++ b/src/elements/compound.rs @@ -10,10 +10,10 @@ use compact_str::{format_compact, CompactString, ToCompactString}; use fxhash::FxHasher; use hashbrown::raw::RawTable; -use crate::assets::*; +use crate::assets::{BASE_TEXT_Z, BASE_Z, COMPOUND_ROOT_UV, COMPOUND_UV, CONNECTION_UV, HEADER_SIZE, LINE_NUMBER_CONNECTOR_Z, LINE_NUMBER_SEPARATOR_UV}; use crate::decoder::Decoder; use crate::elements::chunk::NbtChunk; -use crate::elements::element_type::NbtElement; +use crate::elements::element::NbtElement; use crate::encoder::UncheckedBufWriter; use crate::{DropFn, OptionExt, RenderContext, StrExt, VertexBufferBuilder}; @@ -864,11 +864,13 @@ impl CompoundMap { } #[must_use] + #[inline] pub fn iter(&self) -> CompoundMapIter<'_> { CompoundMapIter(self.entries.iter()) } #[must_use] + #[inline] pub fn iter_mut(&mut self) -> CompoundMapIterMut<'_> { CompoundMapIterMut(self.entries.iter_mut()) } diff --git a/src/elements/element.rs b/src/elements/element.rs new file mode 100644 index 0000000..90b6feb --- /dev/null +++ b/src/elements/element.rs @@ -0,0 +1,2089 @@ +use core::fmt::DebugList; +use std::alloc::{alloc, dealloc, Layout}; +use std::fmt::{Debug, Display, Error, Formatter}; +use std::intrinsics::likely; +use std::mem::{ManuallyDrop, MaybeUninit}; +use std::ops::Deref; +use std::thread::Scope; +use std::time::SystemTime; +use std::{fmt, fmt::Write}; + +use compact_str::{format_compact, CompactString, ToCompactString}; +use hashbrown::raw::RawTable; + +use crate::assets::{BASE_Z, BYTE_ARRAY_UV, BYTE_UV, CONNECTION_UV, DOUBLE_UV, FLOAT_UV, INT_ARRAY_UV, INT_UV, LONG_ARRAY_UV, LONG_UV, SHORT_UV}; +use crate::decoder::Decoder; +use crate::elements::chunk::{NbtChunk, NbtRegion}; +use crate::elements::compound::{CompoundMap, CompoundMapIter, Entry, NbtCompound}; +use crate::elements::element_action::ElementAction; +use crate::elements::list::{NbtList, ValueIterator, ValueMutIterator}; +use crate::elements::string::NbtString; +use crate::encoder::UncheckedBufWriter; +use crate::panic_unchecked; +use crate::tab::FileFormat; +use crate::{array, primitive, DropFn, RenderContext, StrExt, VertexBufferBuilder}; + +primitive!(BYTE_UV, { Some('b') }, NbtByte, i8, 1); +primitive!(SHORT_UV, { Some('s') }, NbtShort, i16, 2); +primitive!(INT_UV, { None:: }, NbtInt, i32, 3); +primitive!(LONG_UV, { Some('L') }, NbtLong, i64, 4); +primitive!(FLOAT_UV, { Some('f') }, NbtFloat, f32, 5); +primitive!(DOUBLE_UV, { Some('d') }, NbtDouble, f64, 6); +array!(byte, NbtByteArray, i8, 7, 1, 'B', BYTE_ARRAY_UV, BYTE_UV); +array!(int, NbtIntArray, i32, 11, 3, 'I', INT_ARRAY_UV, INT_UV); +array!(long, NbtLongArray, i64, 12, 4, 'L', LONG_ARRAY_UV, LONG_UV); + +#[repr(C)] +#[derive(Copy, Clone)] +pub struct NbtElementDiscriminant { + _pad: [MaybeUninit; 23], + pub id: u8, +} + +#[repr(C)] +pub union NbtElement { + chunk: ManuallyDrop, + region: ManuallyDrop, + byte: ManuallyDrop, + short: ManuallyDrop, + int: ManuallyDrop, + long: ManuallyDrop, + float: ManuallyDrop, + double: ManuallyDrop, + byte_array: ManuallyDrop, + string: ManuallyDrop, + list: ManuallyDrop, + compound: ManuallyDrop, + int_array: ManuallyDrop, + long_array: ManuallyDrop, + id: NbtElementDiscriminant, +} + +impl Clone for NbtElement { + #[inline(never)] + fn clone(&self) -> Self { + unsafe { + let mut element = match self.id() { + NbtChunk::ID => Self { chunk: self.chunk.clone() }, + NbtRegion::ID => Self { region: self.region.clone() }, + NbtByte::ID => Self { byte: self.byte }, + NbtShort::ID => Self { short: self.short }, + NbtInt::ID => Self { int: self.int }, + NbtLong::ID => Self { long: self.long }, + NbtFloat::ID => Self { float: self.float }, + NbtDouble::ID => Self { double: self.double }, + NbtByteArray::ID => Self { byte_array: self.byte_array.clone() }, + NbtString::ID => Self { string: self.string.clone() }, + NbtList::ID => Self { list: self.list.clone() }, + NbtCompound::ID => Self { compound: self.compound.clone() }, + NbtIntArray::ID => Self { int_array: self.int_array.clone() }, + NbtLongArray::ID => Self { long_array: self.long_array.clone() }, + _ => core::hint::unreachable_unchecked(), + }; + element.id.id = self.id.id; + element + } + } +} + +#[allow(non_snake_case)] +impl NbtElement { + #[must_use] + #[inline] + pub const fn Byte(x: NbtByte) -> Self { + let mut this = Self { byte: ManuallyDrop::new(x) }; + this.id.id = NbtByte::ID; + this + } + + #[must_use] + #[inline] + pub const fn Short(x: NbtShort) -> Self { + let mut this = Self { short: ManuallyDrop::new(x) }; + this.id.id = NbtShort::ID; + this + } + + #[must_use] + #[inline] + pub const fn Int(x: NbtInt) -> Self { + let mut this = Self { int: ManuallyDrop::new(x) }; + this.id.id = NbtInt::ID; + this + } + + #[must_use] + #[inline] + pub const fn Long(x: NbtLong) -> Self { + let mut this = Self { long: ManuallyDrop::new(x) }; + this.id.id = NbtLong::ID; + this + } + + #[must_use] + #[inline] + pub const fn Float(x: NbtFloat) -> Self { + let mut this = Self { float: ManuallyDrop::new(x) }; + this.id.id = NbtFloat::ID; + this + } + + #[must_use] + #[inline] + pub const fn Double(x: NbtDouble) -> Self { + let mut this = Self { double: ManuallyDrop::new(x) }; + this.id.id = NbtDouble::ID; + this + } + + #[must_use] + #[inline] + pub fn ByteArray(x: NbtByteArray) -> Self { + let mut this = Self { byte_array: ManuallyDrop::new(x) }; + this.id.id = NbtByteArray::ID; + this + } + + #[must_use] + #[inline] + pub fn String(x: NbtString) -> Self { + let mut this = Self { string: ManuallyDrop::new(x) }; + this.id.id = NbtString::ID; + this + } + + #[must_use] + #[inline] + pub fn List(x: NbtList) -> Self { + let mut this = Self { list: ManuallyDrop::new(x) }; + this.id.id = NbtList::ID; + this + } + + #[must_use] + #[inline] + pub fn Compound(x: NbtCompound) -> Self { + let mut this = Self { compound: ManuallyDrop::new(x) }; + this.id.id = NbtCompound::ID; + this + } + + #[must_use] + pub fn IntArray(x: NbtIntArray) -> Self { + let mut this = Self { int_array: ManuallyDrop::new(x) }; + this.id.id = NbtIntArray::ID; + this + } + + #[must_use] + #[inline] + pub fn LongArray(x: NbtLongArray) -> Self { + let mut this = Self { long_array: ManuallyDrop::new(x) }; + this.id.id = NbtLongArray::ID; + this + } + + #[must_use] + #[inline] + pub fn Chunk(x: NbtChunk) -> Self { + let mut this = Self { chunk: ManuallyDrop::new(x) }; + this.id.id = NbtChunk::ID; + this + } + + #[must_use] + #[inline] + pub fn Region(x: NbtRegion) -> Self { + let mut this = Self { region: ManuallyDrop::new(x) }; + this.id.id = NbtRegion::ID; + this + } +} + +impl NbtElement { + #[must_use] + #[allow(clippy::should_implement_trait)] // i can't, sorry :( + pub fn from_str(mut s: &str) -> Option<(Option, Self)> { + s = s.trim_start(); + + if s.is_empty() { + return None; + } + + let prefix = s.snbt_string_read().and_then(|(prefix, s2)| { + s2.trim_start().strip_prefix(':').map(|s2| { + s = s2.trim_start(); + prefix + }) + }); + let (s, element) = Self::from_str0(s).map(|(s, x)| (s.trim_start(), x))?; + if !s.is_empty() { + return None; + } + Some((prefix, element)) + } + + #[allow(clippy::too_many_lines)] + pub(in crate::elements) fn from_str0(mut s: &str) -> Option<(&str, Self)> { + if let Some(s2) = s.strip_prefix("false") { + return Some((s2, Self::Byte(NbtByte { value: 0 }))); + } + if let Some(s2) = s.strip_prefix("true") { + return Some((s2, Self::Byte(NbtByte { value: 1 }))); + } + if s.starts_with("[B;") { + return NbtByteArray::from_str0(s).map(|(s, x)| (s, Self::ByteArray(x))); + } + if s.starts_with("[I;") { + return NbtIntArray::from_str0(s).map(|(s, x)| (s, Self::IntArray(x))); + } + if s.starts_with("[L;") { + return NbtLongArray::from_str0(s).map(|(s, x)| (s, Self::LongArray(x))); + } + if s.starts_with('[') { + return NbtList::from_str0(s).map(|(s, x)| (s, Self::List(x))); + } + if s.starts_with('{') { + return NbtCompound::from_str0(s).map(|(s, x)| (s, Self::Compound(x))); + } + if s.starts_with('"') { + return NbtString::from_str0(s).map(|(s, x)| (s, Self::String(x))); + } + + if let Some(s2) = s.strip_prefix("NaN") { + s = s2.trim_start(); + if let Some(s2) = s.strip_prefix('f') { + return Some((s2.trim_start(), Self::Float(NbtFloat { value: f32::NAN }))); + } else if let Some(s2) = s.strip_prefix('d') { + return Some((s2.trim_start(), Self::Double(NbtDouble { value: f64::NAN }))); + } else { + return Some((s2.trim_start(), Self::Double(NbtDouble { value: f64::NAN }))); + } + } + + if let Some(s2) = s.strip_prefix("Infinity").or_else(|| s.strip_prefix("inf")) { + s = s2.trim_start(); + if let Some(s2) = s.strip_prefix('f') { + return Some((s2.trim_start(), Self::Float(NbtFloat { value: f32::INFINITY }))); + } else if let Some(s2) = s.strip_prefix('d') { + return Some((s2.trim_start(), Self::Double(NbtDouble { value: f64::INFINITY }))); + } else { + return Some((s2.trim_start(), Self::Double(NbtDouble { value: f64::INFINITY }))); + } + } + + if let Some(s2) = s.strip_prefix("-Infinity").or_else(|| s.strip_prefix("-inf")) { + s = s2.trim_start(); + if let Some(s2) = s.strip_prefix('f') { + return Some((s2.trim_start(), Self::Float(NbtFloat { value: f32::NEG_INFINITY }))); + } else if let Some(s2) = s.strip_prefix('d') { + return Some((s2.trim_start(), Self::Double(NbtDouble { value: f64::NEG_INFINITY }))); + } else { + return Some((s2.trim_start(), Self::Double(NbtDouble { value: f64::NEG_INFINITY }))); + } + } + + let digit_end_idx = 'a: { + let mut s = s; + let mut digit_end_idx = 0; + + if s.starts_with('+') || s.starts_with('-') { + s = &s[1..]; + digit_end_idx += 1; + } + + let int_part = s.bytes().take_while(u8::is_ascii_digit).count(); + s = &s[int_part..]; + digit_end_idx += int_part; + if int_part == 0 && !s.starts_with('.') { + break 'a 0; + } + if let Some(s2) = s.strip_prefix('.') { + digit_end_idx += 1; + s = s2; + let frac_part = s.bytes().take_while(u8::is_ascii_digit).count(); + digit_end_idx += frac_part; + } + + digit_end_idx + }; + if digit_end_idx > 0 { + let suffix = (s[digit_end_idx..]).trim_start().as_bytes().first().map(u8::to_ascii_lowercase); + return match suffix { + Some(b'b') => Some(( + &s[(digit_end_idx + 1)..], + Self::Byte(NbtByte { + value: (s[..digit_end_idx]).parse().ok()?, + }), + )), + Some(b's') => Some(( + &s[(digit_end_idx + 1)..], + Self::Short(NbtShort { + value: (s[..digit_end_idx]).parse().ok()?, + }), + )), + Some(b'l') => Some(( + &s[(digit_end_idx + 1)..], + Self::Long(NbtLong { + value: (s[..digit_end_idx]).parse().ok()?, + }), + )), + Some(b'f') => Some(( + &s[(digit_end_idx + 1)..], + Self::Float(NbtFloat { + value: (s[..digit_end_idx]).parse().ok()?, + }), + )), + Some(b'd') => Some(( + &s[(digit_end_idx + 1)..], + Self::Double(NbtDouble { + value: (s[..digit_end_idx]).parse().ok()?, + }), + )), + Some(b'|') => Some({ + let mut s = s; + let Ok(x @ 0..=31) = (s[..digit_end_idx]).parse::() else { return None }; + s = s[digit_end_idx..].trim_start().split_at(1).1.trim_start(); + let digit_end_idx = s.bytes().position(|x| !x.is_ascii_digit())?; + let Ok(z @ 0..=31) = s[..digit_end_idx].parse::() else { return None }; + s = s[digit_end_idx..].trim_start(); + let (s, inner) = NbtCompound::from_str0(s)?; + ( + s, + Self::Chunk(NbtChunk::from_compound( + inner, + (x, z), + FileFormat::Zlib, + SystemTime::UNIX_EPOCH.elapsed().unwrap_or_else(|e| e.duration()).as_secs() as u32, + )), + ) + }), + _ => Some(( + &s[digit_end_idx..], + Self::Int(NbtInt { + value: (s[..digit_end_idx]).parse().ok()?, + }), + )), + }; + } + + NbtString::from_str0(s).map(|(s, x)| (s, Self::String(x))) + } + #[inline(never)] + pub fn from_bytes(element: u8, decoder: &mut Decoder) -> Option { + Some(match element { + NbtByte::ID => Self::Byte(NbtByte::from_bytes(decoder)?), + NbtShort::ID => Self::Short(NbtShort::from_bytes(decoder)?), + NbtInt::ID => Self::Int(NbtInt::from_bytes(decoder)?), + NbtLong::ID => Self::Long(NbtLong::from_bytes(decoder)?), + NbtFloat::ID => Self::Float(NbtFloat::from_bytes(decoder)?), + NbtDouble::ID => Self::Double(NbtDouble::from_bytes(decoder)?), + NbtByteArray::ID => Self::ByteArray(NbtByteArray::from_bytes(decoder)?), + NbtString::ID => Self::String(NbtString::from_bytes(decoder)?), + NbtList::ID => Self::List(NbtList::from_bytes(decoder)?), + NbtCompound::ID => Self::Compound(NbtCompound::from_bytes(decoder)?), + NbtIntArray::ID => Self::IntArray(NbtIntArray::from_bytes(decoder)?), + NbtLongArray::ID => Self::LongArray(NbtLongArray::from_bytes(decoder)?), + _ => return None, + }) + } + + #[inline(never)] + pub fn to_bytes(&self, writer: &mut UncheckedBufWriter) { + unsafe { + match self.id() { + NbtByte::ID => self.byte.to_bytes(writer), + NbtShort::ID => self.short.to_bytes(writer), + NbtInt::ID => self.int.to_bytes(writer), + NbtLong::ID => self.long.to_bytes(writer), + NbtFloat::ID => self.float.to_bytes(writer), + NbtDouble::ID => self.double.to_bytes(writer), + NbtByteArray::ID => self.byte_array.to_bytes(writer), + NbtString::ID => self.string.to_bytes(writer), + NbtList::ID => self.list.to_bytes(writer), + NbtCompound::ID => self.compound.to_bytes(writer), + NbtIntArray::ID => self.int_array.to_bytes(writer), + NbtLongArray::ID => self.long_array.to_bytes(writer), + NbtChunk::ID => self.chunk.to_bytes(writer), + NbtRegion::ID => self.region.to_bytes(writer), + _ => core::hint::unreachable_unchecked(), + }; + } + } + + #[inline] + #[must_use] + pub const fn id(&self) -> u8 { + unsafe { self.id.id } + } + + #[inline] + #[must_use] + pub const fn is_null(&self) -> bool { + self.id() == 0 + } + + #[inline] + #[must_use] + pub fn from_id(id: u8) -> Option { + Some(match id { + NbtByte::ID => Self::Byte(NbtByte::default()), + NbtShort::ID => Self::Short(NbtShort::default()), + NbtInt::ID => Self::Int(NbtInt::default()), + NbtLong::ID => Self::Long(NbtLong::default()), + NbtFloat::ID => Self::Float(NbtFloat::default()), + NbtDouble::ID => Self::Double(NbtDouble::default()), + NbtByteArray::ID => Self::ByteArray(NbtByteArray::new()), + NbtString::ID => Self::String(NbtString::new(CompactString::new_inline(""))), + NbtList::ID => Self::List(NbtList::new(vec![], 0x00)), + NbtCompound::ID => Self::Compound(NbtCompound::new()), + NbtIntArray::ID => Self::IntArray(NbtIntArray::new()), + NbtLongArray::ID => Self::LongArray(NbtLongArray::new()), + NbtChunk::ID => Self::Chunk(NbtChunk::from_compound( + NbtCompound::new(), + (0, 0), + FileFormat::Zlib, + SystemTime::UNIX_EPOCH.elapsed().unwrap_or_else(|e| e.duration()).as_secs() as u32, + )), + _ => return None, + }) + } + + #[inline] + #[must_use] + pub fn from_file(bytes: &[u8]) -> Option { + // #[cfg(debug_assertions)] + // let start = std::time::Instant::now(); + let mut decoder = Decoder::new(bytes); + decoder.assert_len(3)?; + unsafe { + if decoder.u8() != 0x0A { + return None; + } + let skip = decoder.u16() as usize; + decoder.skip(skip); + } + let nbt = Self::Compound(NbtCompound::from_bytes(&mut decoder)?); + // #[cfg(debug_assertions)] + // println!("{}ms for file read", start.elapsed().as_nanos() as f64 / 1_000_000.0); + Some(nbt) + } + + #[inline] + #[must_use] + pub fn to_file(&self) -> Vec { + // #[cfg(debug_assertions)] + let start = std::time::Instant::now(); + let mut writer = UncheckedBufWriter::new(); + if self.id() == NbtCompound::ID { + writer.write(&[0x0A, 0x00, 0x00]); + } + self.to_bytes(&mut writer); + + // #[cfg(debug_assertions)] + println!("{}ms for file write", start.elapsed().as_nanos() as f64 / 1_000_000.0); + writer.finish() + } + + #[inline] + #[must_use] + pub fn from_mca(bytes: &[u8]) -> Option { + // #[cfg(debug_assertions)] + // let start = std::time::Instant::now(); + + // #[cfg(debug_assertions)] + // println!("{}ms for file read", start.elapsed().as_nanos() as f64 / 1_000_000.0); + NbtRegion::from_bytes(bytes).map(Self::Region) + } + + #[inline] + pub fn render(&self, remaining_scroll: &mut usize, builder: &mut VertexBufferBuilder, str: Option<&str>, tail: bool, ctx: &mut RenderContext) { + unsafe { + match self.id() { + NbtByte::ID => self.byte.render(builder, str, ctx), + NbtShort::ID => self.short.render(builder, str, ctx), + NbtInt::ID => self.int.render(builder, str, ctx), + NbtLong::ID => self.long.render(builder, str, ctx), + NbtFloat::ID => self.float.render(builder, str, ctx), + NbtDouble::ID => self.double.render(builder, str, ctx), + NbtByteArray::ID => self.byte_array.render(builder, str, remaining_scroll, tail, ctx), + NbtString::ID => self.string.render(builder, str, ctx), + NbtList::ID => self.list.render(builder, str, remaining_scroll, tail, ctx), + NbtCompound::ID => self.compound.render(builder, str, remaining_scroll, tail, ctx), + NbtIntArray::ID => self.int_array.render(builder, str, remaining_scroll, tail, ctx), + NbtLongArray::ID => self.long_array.render(builder, str, remaining_scroll, tail, ctx), + NbtChunk::ID => self.chunk.render(builder, remaining_scroll, tail, ctx), + NbtRegion::ID => { + // can't be done at all + } + _ => core::hint::unreachable_unchecked(), + } + } + } + + #[inline] + #[must_use] + pub fn len(&self) -> Option { + unsafe { + Some(match self.id() { + NbtCompound::ID => self.compound.len(), + NbtByteArray::ID | NbtIntArray::ID | NbtLongArray::ID | NbtList::ID => self.list.len(), + NbtRegion::ID => self.region.len(), + NbtChunk::ID => self.chunk.len(), + _ => return None, + }) + } + } + + #[inline] + #[must_use] + pub fn is_empty(&self) -> bool { + self.len().is_some_and(|x| x > 0) + } + + #[inline] + #[must_use] + pub fn display_name(&self) -> &'static str { + match self.id() { + NbtByte::ID => "Byte", + NbtShort::ID => "Short", + NbtInt::ID => "Int", + NbtLong::ID => "Long", + NbtFloat::ID => "Float", + NbtDouble::ID => "Double", + NbtByteArray::ID => "Byte Array", + NbtString::ID => "String", + NbtList::ID => "List", + NbtCompound::ID => "Compound", + NbtIntArray::ID => "Int Array", + NbtLongArray::ID => "Long Array", + NbtChunk::ID => "Chunk", + NbtRegion::ID => "Region", + _ => unsafe { panic_unchecked("Invalid element id") }, + } + } + + #[inline] + pub fn render_icon(id: u8, pos: impl Into<(usize, usize)>, z: u8, builder: &mut VertexBufferBuilder) { + match id { + 0 => {} + NbtByte::ID => NbtByte::render_icon(pos, z, builder), + NbtShort::ID => NbtShort::render_icon(pos, z, builder), + NbtInt::ID => NbtInt::render_icon(pos, z, builder), + NbtLong::ID => NbtLong::render_icon(pos, z, builder), + NbtFloat::ID => NbtFloat::render_icon(pos, z, builder), + NbtDouble::ID => NbtDouble::render_icon(pos, z, builder), + NbtByteArray::ID => NbtByteArray::render_icon(pos, z, builder), + NbtString::ID => NbtString::render_icon(pos, z, builder), + NbtList::ID => NbtList::render_icon(pos, z, builder), + NbtCompound::ID => NbtCompound::render_icon(pos, z, builder), + NbtIntArray::ID => NbtIntArray::render_icon(pos, z, builder), + NbtLongArray::ID => NbtLongArray::render_icon(pos, z, builder), + NbtChunk::ID => NbtChunk::render_icon(pos, z, builder), + NbtRegion::ID => NbtRegion::render_icon(pos, z, builder), + _ => unsafe { panic_unchecked("Invalid element id") }, + } + } + + #[inline] + #[must_use] + pub fn height(&self) -> usize { + unsafe { + match self.id() { + NbtByteArray::ID | NbtIntArray::ID | NbtLongArray::ID => self.byte_array.height(), + NbtList::ID => self.list.height(), + NbtCompound::ID => self.compound.height(), + NbtChunk::ID => self.chunk.height(), + NbtRegion::ID => self.region.height(), + _ => 1, + } + } + } + + #[inline] + pub fn swap(&mut self, a: usize, b: usize) { + unsafe { + match self.id() { + NbtByteArray::ID | NbtIntArray::ID | NbtLongArray::ID | NbtList::ID => self.list.elements.swap(a, b), + NbtCompound::ID => self.compound.entries.swap(a, b), + NbtChunk::ID => self.chunk.entries.swap(a, b), + NbtRegion::ID => self.region.chunks.as_mut().0.swap(a, b), + _ => {} + } + } + } + + #[inline] + #[must_use] + #[allow(clippy::type_complexity)] // a type probably shouldn't abstract what this is, like... yeah + pub fn children(&self) -> Option>> { + unsafe { + Some(match self.id() { + NbtByteArray::ID | NbtIntArray::ID | NbtLongArray::ID | NbtList::ID => Ok(self.list.children()), + NbtCompound::ID => Err(self.compound.children()), + NbtChunk::ID => Err(self.chunk.children()), + NbtRegion::ID => Ok(self.region.children()), + _ => return None, + }) + } + } + + #[inline] + pub fn set_value(&mut self, value: CompactString) -> Option { + unsafe { + Some(match self.id() { + NbtByte::ID => { + let before = self.byte.value.to_compact_string(); + self.byte.set(value.parse().ok()); + before + } + NbtShort::ID => { + let before = self.short.value.to_compact_string(); + self.short.set(value.parse().ok()); + before + } + NbtInt::ID => { + let before = self.int.value.to_compact_string(); + self.int.set(value.parse().ok()); + before + } + NbtLong::ID => { + let before = self.long.value.to_compact_string(); + self.long.set(value.parse().ok()); + before + } + NbtFloat::ID => { + let before = self.float.value.to_compact_string(); + self.float.set(value.parse().ok()); + before + } + NbtDouble::ID => { + let before = self.double.value.to_compact_string(); + self.double.set(value.parse().ok()); + before + } + NbtString::ID => core::mem::replace(self, Self::String(NbtString::new(value))).string.str.as_str().to_compact_string(), + _ => return None, + }) + } + } + + #[must_use] + pub fn true_height(&self) -> usize { + unsafe { + match self.id() { + NbtByteArray::ID | NbtIntArray::ID | NbtLongArray::ID => self.byte_array.true_height(), + NbtList::ID => self.list.true_height(), + NbtCompound::ID => self.compound.true_height(), + NbtRegion::ID => self.region.true_height(), + NbtChunk::ID => self.chunk.true_height(), + _ => 1, + } + } + } + + #[inline] + #[must_use] + pub fn toggle(&mut self) -> Option<()> { + unsafe { + match self.id() { + NbtByteArray::ID | NbtIntArray::ID | NbtLongArray::ID => self.byte_array.toggle(), + NbtList::ID => self.list.toggle(), + NbtCompound::ID => self.compound.toggle(), + NbtRegion::ID => self.region.toggle(), + NbtChunk::ID => self.chunk.toggle(), + _ => None, + } + } + } + + #[inline] + #[must_use] + pub fn open(&self) -> bool { + unsafe { + match self.id() { + NbtByteArray::ID | NbtIntArray::ID | NbtLongArray::ID => self.byte_array.open(), + NbtList::ID => self.list.open(), + NbtCompound::ID => self.compound.open(), + NbtRegion::ID => self.region.open(), + NbtChunk::ID => self.chunk.open(), + _ => false, + } + } + } + + #[inline] + pub fn increment(&mut self, amount: usize, true_amount: usize) { + unsafe { + match self.id() { + NbtByteArray::ID | NbtIntArray::ID | NbtLongArray::ID => self.byte_array.increment(amount, true_amount), + NbtList::ID => self.list.increment(amount, true_amount), + NbtCompound::ID => self.compound.increment(amount, true_amount), + NbtRegion::ID => self.region.increment(amount, true_amount), + NbtChunk::ID => self.chunk.increment(amount, true_amount), + _ => {} + } + } + } + + #[inline] + pub fn decrement(&mut self, amount: usize, true_amount: usize) { + unsafe { + match self.id() { + NbtByteArray::ID | NbtIntArray::ID | NbtLongArray::ID => self.byte_array.decrement(amount, true_amount), + NbtList::ID => self.list.decrement(amount, true_amount), + NbtCompound::ID => self.compound.decrement(amount, true_amount), + NbtRegion::ID => self.region.decrement(amount, true_amount), + NbtChunk::ID => self.chunk.decrement(amount, true_amount), + _ => {} + } + } + } + + #[inline] + #[must_use] + pub fn value(&self) -> (CompactString, bool) { + unsafe { + match self.id() { + NbtByte::ID => (self.byte.value.to_compact_string(), true), + NbtShort::ID => (self.short.value.to_compact_string(), true), + NbtInt::ID => (self.int.value.to_compact_string(), true), + NbtLong::ID => (self.long.value.to_compact_string(), true), + NbtFloat::ID => (self.float.value.to_compact_string(), true), + NbtDouble::ID => (self.double.value.to_compact_string(), true), + NbtByteArray::ID => (self.byte_array.value(), false), + NbtString::ID => (self.string.str.as_str().to_compact_string(), true), + NbtList::ID => (self.list.value(), false), + NbtCompound::ID => (self.compound.value(), false), + NbtIntArray::ID => (self.int_array.value(), false), + NbtLongArray::ID => (self.long_array.value(), false), + NbtChunk::ID => (self.chunk.z.to_compact_string(), true), + NbtRegion::ID => (self.region.value(), false), + _ => core::hint::unreachable_unchecked(), + } + } + } + + #[inline] + pub fn drop(&mut self, key: Option, element: Self, y: &mut usize, depth: usize, target_depth: usize, line_number: usize, indices: &mut Vec) -> DropFn { + unsafe { + let height = self.height() * 16; + match self.id() { + _ if *y >= height + 8 => { + *y -= height; + DropFn::Missed(key, element) + } + NbtByteArray::ID => self.byte_array.drop(key, element, y, depth, target_depth, line_number, indices), + NbtList::ID => self.list.drop(key, element, y, depth, target_depth, line_number, indices), + NbtCompound::ID => self.compound.drop(key, element, y, depth, target_depth, line_number, indices), + NbtIntArray::ID => self.int_array.drop(key, element, y, depth, target_depth, line_number, indices), + NbtLongArray::ID => self.long_array.drop(key, element, y, depth, target_depth, line_number, indices), + NbtChunk::ID => NbtCompound::drop(&mut self.chunk, key, element, y, depth, target_depth, line_number, indices), + NbtRegion::ID => self.region.drop(key, element, y, depth, target_depth, line_number, indices), + _ => { + *y = y.saturating_sub(16); + DropFn::Missed(key, element) + } + } + } + } + + #[inline] + pub fn shut(&mut self) { + unsafe { + match self.id() { + NbtByteArray::ID | NbtIntArray::ID | NbtLongArray::ID => self.byte_array.shut(), + NbtList::ID => self.list.shut(), + NbtCompound::ID => self.compound.shut(), + NbtChunk::ID => self.chunk.shut(), + NbtRegion::ID => self.region.shut(), + _ => {} + } + } + } + + #[inline] + pub fn expand<'a, 'b>(&'b mut self, scope: &'a Scope<'a, 'b>) { + unsafe { + match self.id() { + NbtByteArray::ID | NbtIntArray::ID | NbtLongArray::ID => self.byte_array.expand(), + NbtList::ID => self.list.expand(scope), + NbtCompound::ID => self.compound.expand(scope), + NbtChunk::ID => self.chunk.expand(scope), + NbtRegion::ID => self.region.expand(scope), + _ => {} + } + } + } + + /// # Errors + /// + /// * `self` cannot contain that specific variant of `Self`, i.e. `Self::NbtByte` in an `Self::NbtIntArray` + /// + /// If any changes are made to this error list then duplicate may have to be updated as it relies on this never occurring + #[inline] + pub fn insert(&mut self, idx: usize, value: Self) -> Result<(), Self> { + unsafe { + match self.id() { + NbtByteArray::ID => self.byte_array.insert(idx, value), + NbtList::ID => self.list.insert(idx, value), + NbtCompound::ID => { + self.compound.insert(idx, CompactString::new_inline("_"), value); + Ok(()) + } + NbtIntArray::ID => self.int_array.insert(idx, value), + NbtLongArray::ID => self.long_array.insert(idx, value), + NbtRegion::ID => self.region.insert(idx, value), + NbtChunk::ID => { + self.chunk.insert(idx, CompactString::new_inline("_"), value); + Ok(()) + } + _ => Err(value), + } + } + } + + #[inline] + pub fn remove(&mut self, idx: usize) -> Option<(Option, Self)> { + unsafe { + Some(match self.id() { + NbtByteArray::ID | NbtIntArray::ID | NbtLongArray::ID => (None, self.byte_array.remove(idx)), + NbtList::ID => (None, self.list.remove(idx)), + NbtCompound::ID => return self.compound.remove_idx(idx).map(|(a, b)| (Some(a), b)), + NbtRegion::ID => (None, self.region.remove(idx)), + NbtChunk::ID => return self.chunk.remove_idx(idx).map(|(a, b)| (Some(a), b)), + _ => return None, + }) + } + } + + #[inline] + #[must_use] + pub fn get(&self, idx: usize) -> Option<&Self> { + unsafe { + match self.id() { + NbtByteArray::ID | NbtIntArray::ID | NbtLongArray::ID => self.byte_array.get(idx), + NbtList::ID => self.list.get(idx), + NbtCompound::ID => self.compound.get(idx).map(|(_, x)| x), + NbtRegion::ID => self.region.get(idx), + NbtChunk::ID => self.chunk.get(idx).map(|(_, x)| x), + _ => None, + } + } + } + + #[inline] + #[must_use] + pub fn get_mut(&mut self, idx: usize) -> Option<&mut Self> { + unsafe { + match self.id() { + NbtByteArray::ID | NbtIntArray::ID | NbtLongArray::ID => self.byte_array.get_mut(idx), + NbtList::ID => self.list.get_mut(idx), + NbtCompound::ID => self.compound.get_mut(idx).map(|(_, x)| x), + NbtRegion::ID => self.region.get_mut(idx), + NbtChunk::ID => self.chunk.get_mut(idx).map(|(_, x)| x), + _ => None, + } + } + } + + #[inline] + #[must_use] + pub fn max_depth(&self) -> usize { + unsafe { + match self.id() { + NbtByteArray::ID | NbtIntArray::ID | NbtLongArray::ID => self.byte_array.max_depth(), + NbtList::ID => self.list.max_depth(), + NbtCompound::ID => self.compound.max_depth(), + NbtRegion::ID => self.region.max_depth(), + NbtChunk::ID => self.chunk.max_depth(), + _ => 0, + } + } + } + + #[inline] + #[must_use] + #[allow(clippy::match_same_arms)] + pub const fn actions(&self) -> &[ElementAction] { + unsafe { + match self.id() { + NbtByte::ID => &[ElementAction::CopyRaw, ElementAction::CopyFormatted, ElementAction::OpenInTxt], + NbtShort::ID => &[ElementAction::CopyRaw, ElementAction::CopyFormatted, ElementAction::OpenInTxt], + NbtInt::ID => &[ElementAction::CopyRaw, ElementAction::CopyFormatted, ElementAction::OpenInTxt], + NbtLong::ID => &[ElementAction::CopyRaw, ElementAction::CopyFormatted, ElementAction::OpenInTxt], + NbtFloat::ID => &[ElementAction::CopyRaw, ElementAction::CopyFormatted, ElementAction::OpenInTxt], + NbtDouble::ID => &[ElementAction::CopyRaw, ElementAction::CopyFormatted, ElementAction::OpenInTxt], + NbtByteArray::ID => &[ElementAction::CopyRaw, ElementAction::CopyFormatted, ElementAction::OpenInTxt, ElementAction::OpenArrayInHex], + NbtString::ID => &[ElementAction::CopyRaw, ElementAction::CopyFormatted, ElementAction::OpenInTxt], + NbtList::ID => &[ElementAction::CopyRaw, ElementAction::CopyFormatted, ElementAction::OpenInTxt], + NbtCompound::ID => &[ElementAction::CopyRaw, ElementAction::CopyFormatted, ElementAction::OpenInTxt], + NbtIntArray::ID => &[ElementAction::CopyRaw, ElementAction::CopyFormatted, ElementAction::OpenInTxt, ElementAction::OpenArrayInHex], + NbtLongArray::ID => &[ElementAction::CopyRaw, ElementAction::CopyFormatted, ElementAction::OpenInTxt, ElementAction::OpenArrayInHex], + NbtChunk::ID => &[ElementAction::CopyRaw, ElementAction::CopyFormatted, ElementAction::OpenInTxt], + NbtRegion::ID => &[ElementAction::CopyRaw, ElementAction::CopyFormatted, ElementAction::OpenInTxt], + _ => core::hint::unreachable_unchecked(), + } + } + } +} + +impl Display for NbtElement { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + unsafe { + match self.id() { + NbtByte::ID => write!(f, "{}", &*self.byte), + NbtShort::ID => write!(f, "{}", &*self.short), + NbtInt::ID => write!(f, "{}", &*self.int), + NbtLong::ID => write!(f, "{}", &*self.long), + NbtFloat::ID => write!(f, "{}", &*self.float), + NbtDouble::ID => write!(f, "{}", &*self.double), + NbtByteArray::ID => write!(f, "{}", &*self.byte_array), + NbtString::ID => write!(f, "{}", &*self.string), + NbtList::ID => write!(f, "{}", &*self.list), + NbtCompound::ID => write!(f, "{}", &*self.compound), + NbtIntArray::ID => write!(f, "{}", &*self.int_array), + NbtLongArray::ID => write!(f, "{}", &*self.long_array), + NbtChunk::ID => write!(f, "{}", &*self.chunk), + NbtRegion::ID => Err(Error), + _ => core::hint::unreachable_unchecked(), + } + } + } +} + +impl Debug for NbtElement { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + unsafe { + match self.id() { + NbtByte::ID => Debug::fmt(&*self.byte, f), + NbtShort::ID => Debug::fmt(&*self.short, f), + NbtInt::ID => Debug::fmt(&*self.int, f), + NbtLong::ID => Debug::fmt(&*self.long, f), + NbtFloat::ID => Debug::fmt(&*self.float, f), + NbtDouble::ID => Debug::fmt(&*self.double, f), + NbtByteArray::ID => Debug::fmt(&*self.byte_array, f), + NbtString::ID => Debug::fmt(&*self.string, f), + NbtList::ID => Debug::fmt(&*self.list, f), + NbtCompound::ID => Debug::fmt(&*self.compound, f), + NbtIntArray::ID => Debug::fmt(&*self.int_array, f), + NbtLongArray::ID => Debug::fmt(&*self.long_array, f), + NbtChunk::ID => Debug::fmt(&*self.chunk, f), + NbtRegion::ID => Err(Error), + _ => core::hint::unreachable_unchecked(), + } + } + } +} + +impl Drop for NbtElement { + fn drop(&mut self) { + unsafe { + let id = self.id(); + match id { + NbtByteArray::ID | NbtIntArray::ID | NbtLongArray::ID => { + let vec = &mut *self.byte_array.values; + if !vec.is_empty() { + dealloc(vec.as_mut_ptr().cast(), Layout::array::(vec.capacity()).unwrap_unchecked()); + } + dealloc((vec as *mut Vec).cast(), Layout::new::>()); + } + NbtString::ID => { + core::ptr::addr_of_mut!(self.string.str).drop_in_place(); + } + NbtList::ID => { + let element = self.list.element; + let list = &mut *self.list.elements; + if element > NbtDouble::ID { + for entry in &mut *list { + (entry as *mut Self).drop_in_place(); + } + } + if !list.is_empty() { + dealloc(list.as_mut_ptr().cast(), Layout::array::(list.capacity()).unwrap_unchecked()); + } + dealloc((list as *mut Vec).cast(), Layout::new::>()); + } + NbtCompound::ID => { + let map = &mut *self.compound.entries; + let CompoundMap { indices, entries } = map; + (indices as *mut RawTable).drop_in_place(); + for Entry { value, key, .. } in &mut *entries { + (value as *mut Self).drop_in_place(); + if key.is_heap_allocated() { + dealloc(key.as_mut_ptr(), Layout::array::(key.len()).unwrap_unchecked()); + } + } + if !entries.is_empty() { + dealloc(entries.as_mut_ptr().cast(), Layout::array::(entries.capacity()).unwrap_unchecked()); + } + dealloc((map as *mut CompoundMap).cast(), Layout::new::()); + } + NbtChunk::ID => { + let map = &mut *self.chunk.entries; + let CompoundMap { indices, entries } = map; + (indices as *mut RawTable).drop_in_place(); + for Entry { value, key, .. } in &mut *entries { + (value as *mut Self).drop_in_place(); + if key.is_heap_allocated() { + dealloc(key.as_mut_ptr(), Layout::array::(key.len()).unwrap_unchecked()); + } + } + if !entries.is_empty() { + dealloc(entries.as_mut_ptr().cast(), Layout::array::(entries.capacity()).unwrap_unchecked()); + } + dealloc((map as *mut CompoundMap).cast(), Layout::new::()); + } + // no real speedup from using threads, seems to be memory-bound, or dealloc-call-bound + NbtRegion::ID => { + let (map, chunks) = *core::ptr::addr_of_mut!(self.region.chunks).read(); + drop(map); + for mut chunk in core::mem::transmute::<_, [ManuallyDrop; 1024]>(chunks) { + if !chunk.is_null() { + let ptr = &mut **chunk.as_chunk_unchecked_mut(); + let map = &mut *ptr.entries; + let CompoundMap { indices, entries } = map; + (indices as *mut RawTable).drop_in_place(); + for Entry { value, key, .. } in &mut *entries { + (value as *mut Self).drop_in_place(); + if key.is_heap_allocated() { + dealloc(key.as_mut_ptr(), Layout::array::(key.len()).unwrap_unchecked()); + } + } + if !entries.is_empty() { + dealloc(entries.as_mut_ptr().cast(), Layout::array::(entries.capacity()).unwrap_unchecked()); + } + dealloc((map as *mut CompoundMap).cast(), Layout::new::()); + dealloc((ptr as *mut NbtCompound).cast(), Layout::new::()); + } + } + } + _ => {} + } + } + } +} + +#[inline] +#[must_use] +pub fn id_to_string_name(id: u8) -> (&'static str, &'static str) { + match id { + 0 => ("entry", "entries"), + NbtByte::ID => ("byte", "bytes"), + NbtShort::ID => ("short", "shorts"), + NbtInt::ID => ("int", "ints"), + NbtLong::ID => ("long", "longs"), + NbtFloat::ID => ("float", "floats"), + NbtDouble::ID => ("double", "doubles"), + NbtByteArray::ID => ("byte array", "byte arrays"), + NbtString::ID => ("string", "strings"), + NbtList::ID => ("list", "lists"), + NbtCompound::ID => ("compound", "compounds"), + NbtIntArray::ID => ("int array", "int arrays"), + NbtLongArray::ID => ("long array", "long arrays"), + NbtChunk::ID => ("chunk", "chunks"), + NbtRegion::ID => ("region", "regions"), + _ => unsafe { panic_unchecked("Invalid id") }, + } +} + +pub enum NbtPattern<'a> { + Byte(&'a NbtByte), + Short(&'a NbtShort), + Int(&'a NbtInt), + Long(&'a NbtLong), + Float(&'a NbtFloat), + Double(&'a NbtDouble), + ByteArray(&'a NbtByteArray), + String(&'a NbtString), + List(&'a NbtList), + Compound(&'a NbtCompound), + IntArray(&'a NbtIntArray), + LongArray(&'a NbtLongArray), + Chunk(&'a NbtChunk), + Region(&'a NbtRegion), +} + +pub enum NbtPatternMut<'a> { + Byte(&'a mut NbtByte), + Short(&'a mut NbtShort), + Int(&'a mut NbtInt), + Long(&'a mut NbtLong), + Float(&'a mut NbtFloat), + Double(&'a mut NbtDouble), + ByteArray(&'a mut NbtByteArray), + String(&'a mut NbtString), + List(&'a mut NbtList), + Compound(&'a mut NbtCompound), + IntArray(&'a mut NbtIntArray), + LongArray(&'a mut NbtLongArray), + Chunk(&'a mut NbtChunk), + Region(&'a mut NbtRegion), +} + +impl NbtElement { + #[must_use] + pub fn as_pattern(&self) -> NbtPattern { + match self.id() { + NbtByte::ID => NbtPattern::Byte(unsafe { self.as_byte_unchecked() }), + NbtShort::ID => NbtPattern::Short(unsafe { self.as_short_unchecked() }), + NbtInt::ID => NbtPattern::Int(unsafe { self.as_int_unchecked() }), + NbtLong::ID => NbtPattern::Long(unsafe { self.as_long_unchecked() }), + NbtFloat::ID => NbtPattern::Float(unsafe { self.as_float_unchecked() }), + NbtDouble::ID => NbtPattern::Double(unsafe { self.as_double_unchecked() }), + NbtByteArray::ID => NbtPattern::ByteArray(unsafe { self.as_byte_array_unchecked() }), + NbtString::ID => NbtPattern::String(unsafe { self.as_string_unchecked() }), + NbtList::ID => NbtPattern::List(unsafe { self.as_list_unchecked() }), + NbtCompound::ID => NbtPattern::Compound(unsafe { self.as_compound_unchecked() }), + NbtIntArray::ID => NbtPattern::IntArray(unsafe { self.as_int_array_unchecked() }), + NbtLongArray::ID => NbtPattern::LongArray(unsafe { self.as_long_array_unchecked() }), + NbtChunk::ID => NbtPattern::Chunk(unsafe { self.as_chunk_unchecked() }), + NbtRegion::ID => NbtPattern::Region(unsafe { self.as_region_unchecked() }), + _ => unsafe { panic_unchecked("variant wasn't known") }, + } + } + + pub fn as_pattern_mut(&mut self) -> NbtPatternMut { + match self.id() { + NbtByte::ID => NbtPatternMut::Byte(unsafe { self.as_byte_unchecked_mut() }), + NbtShort::ID => NbtPatternMut::Short(unsafe { self.as_short_unchecked_mut() }), + NbtInt::ID => NbtPatternMut::Int(unsafe { self.as_int_unchecked_mut() }), + NbtLong::ID => NbtPatternMut::Long(unsafe { self.as_long_unchecked_mut() }), + NbtFloat::ID => NbtPatternMut::Float(unsafe { self.as_float_unchecked_mut() }), + NbtDouble::ID => NbtPatternMut::Double(unsafe { self.as_double_unchecked_mut() }), + NbtByteArray::ID => NbtPatternMut::ByteArray(unsafe { self.as_byte_array_unchecked_mut() }), + NbtString::ID => NbtPatternMut::String(unsafe { self.as_string_unchecked_mut() }), + NbtList::ID => NbtPatternMut::List(unsafe { self.as_list_unchecked_mut() }), + NbtCompound::ID => NbtPatternMut::Compound(unsafe { self.as_compound_unchecked_mut() }), + NbtIntArray::ID => NbtPatternMut::IntArray(unsafe { self.as_int_array_unchecked_mut() }), + NbtLongArray::ID => NbtPatternMut::LongArray(unsafe { self.as_long_array_unchecked_mut() }), + NbtChunk::ID => NbtPatternMut::Chunk(unsafe { self.as_chunk_unchecked_mut() }), + NbtRegion::ID => NbtPatternMut::Region(unsafe { self.as_region_unchecked_mut() }), + _ => unsafe { panic_unchecked("variant wasn't known") }, + } + } +} + +#[allow(dead_code)] +impl NbtElement { + /// # Safety + /// + /// * `self` must be of variant `NbtElement::Byte` + #[inline] + #[must_use] + pub unsafe fn into_byte_unchecked(self) -> NbtByte { + core::ptr::addr_of!(*self.byte).read() + } + + /// # Safety + /// + /// * `self` must be of variant `NbtElement::Byte` + #[inline] + #[must_use] + pub unsafe fn as_byte_unchecked(&self) -> &NbtByte { + &self.byte + } + + /// # Safety + /// + /// * `self` must be of variant `NbtElement::Byte` + #[inline] + #[must_use] + pub unsafe fn as_byte_unchecked_mut(&mut self) -> &mut NbtByte { + &mut self.byte + } + + #[inline] + #[must_use] + pub fn into_byte(self) -> Option { + unsafe { + if self.id() == NbtByte::ID { + Some(core::ptr::addr_of!(*self.byte).read()) + } else { + None + } + } + } + + #[inline] + #[must_use] + pub fn as_byte(&self) -> Option<&NbtByte> { + unsafe { + if self.id() == NbtByte::ID { + Some(&self.byte) + } else { + None + } + } + } + + #[inline] + #[must_use] + pub fn as_byte_mut(&mut self) -> Option<&mut NbtByte> { + unsafe { + if self.id() == NbtByte::ID { + Some(&mut self.byte) + } else { + None + } + } + } +} + +#[allow(dead_code)] +impl NbtElement { + /// # Safety + /// + /// * `self` must be of variant `NbtElement::Short` + #[inline] + #[must_use] + pub unsafe fn into_short_unchecked(self) -> NbtShort { + core::ptr::addr_of!(*self.short).read() + } + + /// # Safety + /// + /// * `self` must be of variant `NbtElement::Short` + #[inline] + #[must_use] + pub unsafe fn as_short_unchecked(&self) -> &NbtShort { + &self.short + } + + /// # Safety + /// + /// * `self` must be of variant `NbtElement::Short` + #[inline] + #[must_use] + pub unsafe fn as_short_unchecked_mut(&mut self) -> &mut NbtShort { + &mut self.short + } + + #[inline] + #[must_use] + pub fn into_short(self) -> Option { + unsafe { + if self.id() == NbtShort::ID { + Some(core::ptr::addr_of!(*self.short).read()) + } else { + None + } + } + } + + #[inline] + #[must_use] + pub fn as_short(&self) -> Option<&NbtShort> { + unsafe { + if self.id() == NbtShort::ID { + Some(&self.short) + } else { + None + } + } + } + + #[inline] + #[must_use] + pub fn as_short_mut(&mut self) -> Option<&mut NbtShort> { + unsafe { + if self.id() == NbtShort::ID { + Some(&mut self.short) + } else { + None + } + } + } +} + +#[allow(dead_code)] +impl NbtElement { + /// # Safety + /// + /// * `self` must be of variant `NbtElement::Int` + #[inline] + #[must_use] + pub unsafe fn into_int_unchecked(self) -> NbtInt { + core::ptr::addr_of!(*self.int).read() + } + + /// # Safety + /// + /// * `self` must be of variant `NbtElement::Int` + #[inline] + #[must_use] + pub unsafe fn as_int_unchecked(&self) -> &NbtInt { + &self.int + } + + /// # Safety + /// + /// * `self` must be of variant `NbtElement::Int` + #[inline] + #[must_use] + pub unsafe fn as_int_unchecked_mut(&mut self) -> &mut NbtInt { + &mut self.int + } + + #[inline] + #[must_use] + pub fn into_int(self) -> Option { + unsafe { + if self.id() == NbtInt::ID { + Some(core::ptr::addr_of!(*self.int).read()) + } else { + None + } + } + } + + #[inline] + #[must_use] + pub fn as_int(&self) -> Option<&NbtInt> { + unsafe { + if self.id() == NbtInt::ID { + Some(&self.int) + } else { + None + } + } + } + + #[inline] + #[must_use] + pub fn as_int_mut(&mut self) -> Option<&mut NbtInt> { + unsafe { + if self.id() == NbtInt::ID { + Some(&mut self.int) + } else { + None + } + } + } +} + +#[allow(dead_code)] +impl NbtElement { + /// # Safety + /// + /// * `self` must be of variant `NbtElement::Long` + #[inline] + #[must_use] + pub unsafe fn into_long_unchecked(self) -> NbtLong { + core::ptr::addr_of!(*self.long).read() + } + + /// # Safety + /// + /// * `self` must be of variant `NbtElement::Long` + #[inline] + #[must_use] + pub unsafe fn as_long_unchecked(&self) -> &NbtLong { + &self.long + } + + /// # Safety + /// + /// * `self` must be of variant `NbtElement::Long` + #[inline] + #[must_use] + pub unsafe fn as_long_unchecked_mut(&mut self) -> &mut NbtLong { + &mut self.long + } + + #[inline] + #[must_use] + pub fn into_long(self) -> Option { + unsafe { + if self.id() == NbtLong::ID { + Some(core::ptr::addr_of!(*self.long).read()) + } else { + None + } + } + } + + #[inline] + #[must_use] + pub fn as_long(&self) -> Option<&NbtLong> { + unsafe { + if self.id() == NbtLong::ID { + Some(&self.long) + } else { + None + } + } + } + + #[inline] + #[must_use] + pub fn as_long_mut(&mut self) -> Option<&mut NbtLong> { + unsafe { + if self.id() == NbtLong::ID { + Some(&mut self.long) + } else { + None + } + } + } +} + +#[allow(dead_code)] +impl NbtElement { + /// # Safety + /// + /// * `self` must be of variant `NbtElement::Float` + #[inline] + #[must_use] + pub unsafe fn into_float_unchecked(self) -> NbtFloat { + core::ptr::addr_of!(*self.float).read() + } + + /// # Safety + /// + /// * `self` must be of variant `NbtElement::Float` + #[inline] + #[must_use] + pub unsafe fn as_float_unchecked(&self) -> &NbtFloat { + &self.float + } + + /// # Safety + /// + /// * `self` must be of variant `NbtElement::Float` + #[inline] + #[must_use] + pub unsafe fn as_float_unchecked_mut(&mut self) -> &mut NbtFloat { + &mut self.float + } + + #[inline] + #[must_use] + pub fn into_float(self) -> Option { + unsafe { + if self.id() == NbtFloat::ID { + Some(core::ptr::addr_of!(*self.float).read()) + } else { + None + } + } + } + + #[inline] + #[must_use] + pub fn as_float(&self) -> Option<&NbtFloat> { + unsafe { + if self.id() == NbtFloat::ID { + Some(&self.float) + } else { + None + } + } + } + + #[inline] + #[must_use] + pub fn as_float_mut(&mut self) -> Option<&mut NbtFloat> { + unsafe { + if self.id() == NbtFloat::ID { + Some(&mut self.float) + } else { + None + } + } + } +} + +#[allow(dead_code)] +impl NbtElement { + /// # Safety + /// + /// * `self` must be of variant `NbtElement::Double` + #[inline] + #[must_use] + pub unsafe fn into_double_unchecked(self) -> NbtDouble { + core::ptr::addr_of!(*self.double).read() + } + + /// # Safety + /// + /// * `self` must be of variant `NbtElement::Double` + #[inline] + #[must_use] + pub unsafe fn as_double_unchecked(&self) -> &NbtDouble { + &self.double + } + + /// # Safety + /// + /// * `self` must be of variant `NbtElement::Double` + #[inline] + #[must_use] + pub unsafe fn as_double_unchecked_mut(&mut self) -> &mut NbtDouble { + &mut self.double + } + + #[inline] + #[must_use] + pub fn into_double(self) -> Option { + unsafe { + if self.id() == NbtDouble::ID { + Some(core::ptr::addr_of!(*self.double).read()) + } else { + None + } + } + } + + #[inline] + #[must_use] + pub fn as_double(&self) -> Option<&NbtDouble> { + unsafe { + if self.id() == NbtDouble::ID { + Some(&self.double) + } else { + None + } + } + } + + #[inline] + #[must_use] + pub fn as_double_mut(&mut self) -> Option<&mut NbtDouble> { + unsafe { + if self.id() == NbtDouble::ID { + Some(&mut self.double) + } else { + None + } + } + } +} + +#[allow(dead_code)] +impl NbtElement { + /// # Safety + /// + /// * `self` must be of variant `NbtElement::ByteArray` + #[inline] + #[must_use] + pub unsafe fn into_byte_array_unchecked(self) -> NbtByteArray { + core::ptr::addr_of!(*self.byte_array).read() + } + + /// # Safety + /// + /// * `self` must be of variant `NbtElement::ByteArray` + #[inline] + #[must_use] + pub unsafe fn as_byte_array_unchecked(&self) -> &NbtByteArray { + &self.byte_array + } + + /// # Safety + /// + /// * `self` must be of variant `NbtElement::ByteArray` + #[inline] + #[must_use] + pub unsafe fn as_byte_array_unchecked_mut(&mut self) -> &mut NbtByteArray { + &mut self.byte_array + } + + #[inline] + #[must_use] + pub fn into_byte_array(self) -> Option { + unsafe { + if self.id() == NbtByteArray::ID { + Some(core::ptr::addr_of!(*self.byte_array).read()) + } else { + None + } + } + } + + #[inline] + #[must_use] + pub fn as_byte_array(&self) -> Option<&NbtByteArray> { + unsafe { + if self.id() == NbtByteArray::ID { + Some(&self.byte_array) + } else { + None + } + } + } + + #[inline] + #[must_use] + pub fn as_byte_array_mut(&mut self) -> Option<&mut NbtByteArray> { + unsafe { + if self.id() == NbtByteArray::ID { + Some(&mut self.byte_array) + } else { + None + } + } + } +} + +#[allow(dead_code)] +impl NbtElement { + /// # Safety + /// + /// * `self` must be of variant `NbtElement::String` + #[inline] + #[must_use] + pub unsafe fn into_string_unchecked(self) -> NbtString { + core::ptr::addr_of!(*self.string).read() + } + + /// # Safety + /// + /// * `self` must be of variant `NbtElement::String` + #[inline] + #[must_use] + pub unsafe fn as_string_unchecked(&self) -> &NbtString { + &self.string + } + + /// # Safety + /// + /// * `self` must be of variant `NbtElement::String` + #[inline] + #[must_use] + pub unsafe fn as_string_unchecked_mut(&mut self) -> &mut NbtString { + &mut self.string + } + + #[inline] + #[must_use] + pub fn into_string(self) -> Option { + unsafe { + if self.id() == NbtString::ID { + Some(core::ptr::addr_of!(*self.string).read()) + } else { + None + } + } + } + + #[inline] + #[must_use] + pub fn as_string(&self) -> Option<&NbtString> { + unsafe { + if self.id() == NbtString::ID { + Some(&self.string) + } else { + None + } + } + } + + #[inline] + #[must_use] + pub fn as_string_mut(&mut self) -> Option<&mut NbtString> { + unsafe { + if self.id() == NbtString::ID { + Some(&mut self.string) + } else { + None + } + } + } +} + +#[allow(dead_code)] +impl NbtElement { + /// # Safety + /// + /// * `self` must be of variant `NbtElement::List` + #[inline] + #[must_use] + pub unsafe fn into_list_unchecked(self) -> NbtList { + core::ptr::addr_of!(*self.list).read() + } + + /// # Safety + /// + /// * `self` must be of variant `NbtElement::List` + #[inline] + #[must_use] + pub unsafe fn as_list_unchecked(&self) -> &NbtList { + &self.list + } + + /// # Safety + /// + /// * `self` must be of variant `NbtElement::List` + #[inline] + #[must_use] + pub unsafe fn as_list_unchecked_mut(&mut self) -> &mut NbtList { + &mut self.list + } + + #[inline] + #[must_use] + pub fn into_list(self) -> Option { + unsafe { + if self.id() == NbtList::ID { + Some(core::ptr::addr_of!(*self.list).read()) + } else { + None + } + } + } + + #[inline] + #[must_use] + pub fn as_list(&self) -> Option<&NbtList> { + unsafe { + if self.id() == NbtList::ID { + Some(&self.list) + } else { + None + } + } + } + + #[inline] + #[must_use] + pub fn as_list_mut(&mut self) -> Option<&mut NbtList> { + unsafe { + if self.id() == NbtList::ID { + Some(&mut self.list) + } else { + None + } + } + } +} + +#[allow(dead_code)] +impl NbtElement { + /// # Safety + /// + /// * `self` must be of variant `NbtElement::Compound` + #[inline] + #[must_use] + pub unsafe fn into_compound_unchecked(self) -> NbtCompound { + core::ptr::addr_of!(*self.compound).read() + } + + /// # Safety + /// + /// * `self` must be of variant `NbtElement::Compound` + #[inline] + #[must_use] + pub unsafe fn as_compound_unchecked(&self) -> &NbtCompound { + &self.compound + } + + /// # Safety + /// + /// * `self` must be of variant `NbtElement::Compound` + #[inline] + #[must_use] + pub unsafe fn as_compound_unchecked_mut(&mut self) -> &mut NbtCompound { + &mut self.compound + } + + #[inline] + #[must_use] + pub fn into_compound(self) -> Option { + unsafe { + if self.id() == NbtCompound::ID { + Some(core::ptr::addr_of!(*self.compound).read()) + } else { + None + } + } + } + + #[inline] + #[must_use] + pub fn as_compound(&self) -> Option<&NbtCompound> { + unsafe { + if self.id() == NbtCompound::ID { + Some(&self.compound) + } else { + None + } + } + } + + #[inline] + #[must_use] + pub fn as_compound_mut(&mut self) -> Option<&mut NbtCompound> { + unsafe { + if self.id() == NbtCompound::ID { + Some(&mut self.compound) + } else { + None + } + } + } +} + +#[allow(dead_code)] +impl NbtElement { + /// # Safety + /// + /// * `self` must be of variant `NbtElement::IntArray` + #[inline] + #[must_use] + pub unsafe fn into_int_array_unchecked(self) -> NbtIntArray { + core::ptr::addr_of!(*self.int_array).read() + } + + /// # Safety + /// + /// * `self` must be of variant `NbtElement::IntArray` + #[inline] + #[must_use] + pub unsafe fn as_int_array_unchecked(&self) -> &NbtIntArray { + &self.int_array + } + + /// # Safety + /// + /// * `self` must be of variant `NbtElement::IntArray` + #[inline] + #[must_use] + pub unsafe fn as_int_array_unchecked_mut(&mut self) -> &mut NbtIntArray { + &mut self.int_array + } + + #[inline] + #[must_use] + pub fn into_int_array(self) -> Option { + unsafe { + if self.id() == NbtIntArray::ID { + Some(core::ptr::addr_of!(*self.int_array).read()) + } else { + None + } + } + } + + #[inline] + #[must_use] + pub fn as_int_array(&self) -> Option<&NbtIntArray> { + unsafe { + if self.id() == NbtIntArray::ID { + Some(&self.int_array) + } else { + None + } + } + } + + #[inline] + #[must_use] + pub fn as_int_array_mut(&mut self) -> Option<&mut NbtIntArray> { + unsafe { + if self.id() == NbtIntArray::ID { + Some(&mut self.int_array) + } else { + None + } + } + } +} + +#[allow(dead_code)] +impl NbtElement { + /// # Safety + /// + /// * `self` must be of variant `NbtElement::LongArray` + #[inline] + #[must_use] + pub unsafe fn into_long_array_unchecked(self) -> NbtLongArray { + core::ptr::addr_of!(*self.long_array).read() + } + + /// # Safety + /// + /// * `self` must be of variant `NbtElement::LongArray` + #[inline] + #[must_use] + pub unsafe fn as_long_array_unchecked(&self) -> &NbtLongArray { + &self.long_array + } + + /// # Safety + /// + /// * `self` must be of variant `NbtElement::LongArray` + #[inline] + #[must_use] + pub unsafe fn as_long_array_unchecked_mut(&mut self) -> &mut NbtLongArray { + &mut self.long_array + } + + #[inline] + #[must_use] + pub fn into_long_array(self) -> Option { + unsafe { + if self.id() == NbtLongArray::ID { + Some(core::ptr::addr_of!(*self.long_array).read()) + } else { + None + } + } + } + + #[inline] + #[must_use] + pub fn as_long_array(&self) -> Option<&NbtLongArray> { + unsafe { + if self.id() == NbtLongArray::ID { + Some(&self.long_array) + } else { + None + } + } + } + + #[inline] + #[must_use] + pub fn as_long_array_mut(&mut self) -> Option<&mut NbtLongArray> { + unsafe { + if self.id() == NbtLongArray::ID { + Some(&mut self.long_array) + } else { + None + } + } + } +} + +#[allow(dead_code)] +impl NbtElement { + /// # Safety + /// + /// * `self` must be of variant `NbtElement::Chunk` + #[inline] + #[must_use] + pub unsafe fn into_chunk_unchecked(self) -> NbtChunk { + core::ptr::addr_of!(*self.chunk).read() + } + + /// # Safety + /// + /// * `self` must be of variant `NbtElement::Chunk` + #[inline] + #[must_use] + pub unsafe fn as_chunk_unchecked(&self) -> &NbtChunk { + &self.chunk + } + + /// # Safety + /// + /// * `self` must be of variant `NbtElement::Chunk` + #[inline] + #[must_use] + pub unsafe fn as_chunk_unchecked_mut(&mut self) -> &mut NbtChunk { + &mut self.chunk + } + + #[inline] + #[must_use] + pub fn into_chunk(self) -> Option { + unsafe { + if self.id() == NbtChunk::ID { + Some(core::ptr::addr_of!(*self.chunk).read()) + } else { + None + } + } + } + + #[inline] + #[must_use] + pub fn as_chunk(&self) -> Option<&NbtChunk> { + unsafe { + if self.id() == NbtChunk::ID { + Some(&self.chunk) + } else { + None + } + } + } + + #[inline] + #[must_use] + pub fn as_chunk_mut(&mut self) -> Option<&mut NbtChunk> { + unsafe { + if self.id() == NbtChunk::ID { + Some(&mut self.chunk) + } else { + None + } + } + } +} + +#[allow(dead_code)] +impl NbtElement { + /// # Safety + /// + /// * `self` must be of variant `NbtElement::Region` + #[inline] + #[must_use] + pub unsafe fn into_region_unchecked(self) -> NbtRegion { + core::ptr::addr_of!(*self.region).read() + } + + /// # Safety + /// + /// * `self` must be of variant `NbtElement::Region` + #[inline] + #[must_use] + pub unsafe fn as_region_unchecked(&self) -> &NbtRegion { + &self.region + } + + /// # Safety + /// + /// * `self` must be of variant `NbtElement::Region` + #[inline] + #[must_use] + pub unsafe fn as_region_unchecked_mut(&mut self) -> &mut NbtRegion { + &mut self.region + } + + #[inline] + #[must_use] + pub fn into_region(self) -> Option { + unsafe { + if self.id() == NbtRegion::ID { + Some(core::ptr::addr_of!(*self.region).read()) + } else { + None + } + } + } + + #[inline] + #[must_use] + pub fn as_region(&self) -> Option<&NbtRegion> { + unsafe { + if self.id() == NbtRegion::ID { + Some(&self.region) + } else { + None + } + } + } + + #[inline] + #[must_use] + pub fn as_region_mut(&mut self) -> Option<&mut NbtRegion> { + unsafe { + if self.id() == NbtRegion::ID { + Some(&mut self.region) + } else { + None + } + } + } +} diff --git a/src/elements/element_action.rs b/src/elements/element_action.rs index 135067e..5163215 100644 --- a/src/elements/element_action.rs +++ b/src/elements/element_action.rs @@ -1,13 +1,13 @@ use compact_str::CompactString; use std::fs::OpenOptions; -use std::ops::Deref; use std::process::Command; use notify::{EventKind, PollWatcher, RecursiveMode, Watcher}; use uuid::Uuid; -use crate::assets::*; -use crate::elements::element_type::{NbtByteArray, NbtElement, NbtIntArray, NbtLongArray}; +use crate::assets::{ACTION_WHEEL_Z, COPY_FORMATTED_UV, COPY_RAW_UV, OPEN_ARRAY_IN_HEX_UV, OPEN_IN_TXT}; +use crate::elements::chunk::NbtChunk; +use crate::elements::element::NbtElement; use crate::vertex_buffer_builder::VertexBufferBuilder; use crate::{FileUpdateSubscription, FileUpdateSubscriptionType}; @@ -87,11 +87,8 @@ impl ElementAction { Self::OpenArrayInHex => { use std::io::Write; - let path = std::env::temp_dir().join(format!( - "nbtworkbench-{:0width$x}.hex", - (unsafe { core::arch::x86_64::_rdtsc() as usize }).wrapping_mul(element as *mut NbtElement as usize), - width = usize::BITS as usize / 8 - )); + let hash = (unsafe { core::arch::x86_64::_rdtsc() as usize }).wrapping_mul(element as *mut NbtElement as usize); + let path = std::env::temp_dir().join(format!("nbtworkbench-{hash:0width$x}.hex", width = usize::BITS as usize / 8)); let (tx, rx) = std::sync::mpsc::channel(); let Ok(mut watcher) = PollWatcher::new( move |event| { @@ -114,32 +111,29 @@ impl ElementAction { if let Ok(mut file) = OpenOptions::new().write(true).create(true).open(&path) { if file .write_all(&unsafe { - match element.id() { - NbtByteArray::ID => { - subscription_type = FileUpdateSubscriptionType::ByteArray; - let mut vec = Vec::with_capacity(element.data.byte_array.len()); - for child in element.data.byte_array.children() { - vec.push(child.data.byte.deref().value as u8); - } - vec + if let Some(array) = element.as_byte_array() { + subscription_type = FileUpdateSubscriptionType::ByteArray; + let mut vec = Vec::with_capacity(array.len()); + for child in array.children() { + vec.push(child.as_byte_unchecked().value as u8); } - NbtIntArray::ID => { - subscription_type = FileUpdateSubscriptionType::IntArray; - let mut vec = Vec::with_capacity(element.data.int_array.len() * 4); - for child in element.data.long_array.children() { - vec.extend(child.data.int.deref().value.to_be_bytes()); - } - vec + vec + } else if let Some(array) = element.as_int_array() { + subscription_type = FileUpdateSubscriptionType::IntArray; + let mut vec = Vec::with_capacity(array.len() * 4); + for child in array.children() { + vec.extend(child.as_int_unchecked().value.to_be_bytes()); } - NbtLongArray::ID => { - subscription_type = FileUpdateSubscriptionType::LongArray; - let mut vec = Vec::with_capacity(element.data.long_array.len() * 8); - for child in element.data.long_array.children() { - vec.extend(child.data.long.deref().value.to_be_bytes()); - } - vec + vec + } else if let Some(array) = element.as_long_array() { + subscription_type = FileUpdateSubscriptionType::LongArray; + let mut vec = Vec::with_capacity(array.len() * 8); + for child in array.children() { + vec.extend(child.as_long_unchecked().value.to_be_bytes()); } - _ => return None, + vec + } else { + return None; } }) .is_err() @@ -165,11 +159,8 @@ impl ElementAction { Self::OpenInTxt => { use std::io::Write; - let path = std::env::temp_dir().join(format!( - "nbtworkbench-{:0width$x}.txt", - (unsafe { core::arch::x86_64::_rdtsc() as usize }).wrapping_mul(element as *mut NbtElement as usize), - width = usize::BITS as usize / 8 - )); + let hash = (unsafe { core::arch::x86_64::_rdtsc() as usize }).wrapping_mul(element as *mut NbtElement as usize); + let path = std::env::temp_dir().join(format!("nbtworkbench-{hash:0width$x}.txt", width = usize::BITS as usize / 8)); let (tx, rx) = std::sync::mpsc::channel(); let Ok(mut watcher) = PollWatcher::new( move |event| { @@ -189,7 +180,11 @@ impl ElementAction { return None; }; if let Ok(mut file) = OpenOptions::new().write(true).create(true).open(&path) { - if let Some(key) = key && write!(&mut file, "{key}: ").is_err() { return None }; + if let Some(key) = key && element.id() != NbtChunk::ID { + if write!(&mut file, "{key}: ").is_err() { + return None; + } + } if write!(&mut file, "{element:#?}").is_err() { return None; } diff --git a/src/elements/element_type.rs b/src/elements/element_type.rs deleted file mode 100644 index 17bcf84..0000000 --- a/src/elements/element_type.rs +++ /dev/null @@ -1,1178 +0,0 @@ -use core::fmt::DebugList; -use std::alloc::{alloc, dealloc, Layout}; -use std::fmt::{Debug, Display, Error, Formatter}; -use std::intrinsics::likely; -use std::mem::{ManuallyDrop, MaybeUninit}; -use std::ops::Deref; -use std::thread::Scope; -use std::time::SystemTime; -use std::{fmt, fmt::Write}; - -use compact_str::{format_compact, CompactString, ToCompactString}; -use hashbrown::raw::RawTable; - -use crate::assets::*; -use crate::decoder::Decoder; -use crate::elements::chunk::{NbtChunk, NbtRegion}; -use crate::elements::compound::{CompoundMap, CompoundMapIter, Entry, NbtCompound}; -use crate::elements::element_action::ElementAction; -use crate::elements::list::{NbtList, ValueIterator, ValueMutIterator}; -use crate::elements::string::NbtString; -use crate::encoder::UncheckedBufWriter; -use crate::panic_unchecked; -use crate::tab::FileFormat; -use crate::{array, primitive, DropFn, RenderContext, StrExt, VertexBufferBuilder}; - -primitive!(BYTE_UV, { Some('b') }, NbtByte, i8, 1); -primitive!(SHORT_UV, { Some('s') }, NbtShort, i16, 2); -primitive!(INT_UV, { None:: }, NbtInt, i32, 3); -primitive!(LONG_UV, { Some('L') }, NbtLong, i64, 4); -primitive!(FLOAT_UV, { Some('f') }, NbtFloat, f32, 5); -primitive!(DOUBLE_UV, { Some('d') }, NbtDouble, f64, 6); -array!(byte, NbtByteArray, i8, 7, 1, 'B', BYTE_ARRAY_UV, BYTE_UV); -array!(int, NbtIntArray, i32, 11, 3, 'I', INT_ARRAY_UV, INT_UV); -array!(long, NbtLongArray, i64, 12, 4, 'L', LONG_ARRAY_UV, LONG_UV); - -#[repr(C)] -pub union NbtElementData { - pub chunk: ManuallyDrop, - pub region: ManuallyDrop, - pub byte: ManuallyDrop, - pub short: ManuallyDrop, - pub int: ManuallyDrop, - pub long: ManuallyDrop, - pub float: ManuallyDrop, - pub double: ManuallyDrop, - pub byte_array: ManuallyDrop, - pub string: ManuallyDrop, - pub list: ManuallyDrop, - pub compound: ManuallyDrop, - pub int_array: ManuallyDrop, - pub long_array: ManuallyDrop, -} - -#[repr(C)] -#[derive(Copy, Clone)] -pub struct NbtElementDiscriminant { - _pad: [MaybeUninit; 23], - pub id: u8, -} - -#[repr(C)] -pub union NbtElement { - pub data: ManuallyDrop, - pub id: NbtElementDiscriminant, -} - -impl Clone for NbtElement { - #[inline(never)] - fn clone(&self) -> Self { - unsafe { - let mut data = match self.id() { - NbtChunk::ID => Self { - data: ManuallyDrop::new(NbtElementData { chunk: self.data.chunk.clone() }), - }, - NbtRegion::ID => Self { - data: ManuallyDrop::new(NbtElementData { region: self.data.region.clone() }), - }, - NbtByte::ID => Self { - data: ManuallyDrop::new(NbtElementData { byte: self.data.byte }), - }, - NbtShort::ID => Self { - data: ManuallyDrop::new(NbtElementData { short: self.data.short }), - }, - NbtInt::ID => Self { - data: ManuallyDrop::new(NbtElementData { int: self.data.int }), - }, - NbtLong::ID => Self { - data: ManuallyDrop::new(NbtElementData { long: self.data.long }), - }, - NbtFloat::ID => Self { - data: ManuallyDrop::new(NbtElementData { float: self.data.float }), - }, - NbtDouble::ID => Self { - data: ManuallyDrop::new(NbtElementData { double: self.data.double }), - }, - NbtByteArray::ID => Self { - data: ManuallyDrop::new(NbtElementData { - byte_array: self.data.byte_array.clone(), - }), - }, - NbtString::ID => Self { - data: ManuallyDrop::new(NbtElementData { string: self.data.string.clone() }), - }, - NbtList::ID => Self { - data: ManuallyDrop::new(NbtElementData { list: self.data.list.clone() }), - }, - NbtCompound::ID => Self { - data: ManuallyDrop::new(NbtElementData { compound: self.data.compound.clone() }), - }, - NbtIntArray::ID => Self { - data: ManuallyDrop::new(NbtElementData { - int_array: self.data.int_array.clone(), - }), - }, - NbtLongArray::ID => Self { - data: ManuallyDrop::new(NbtElementData { - long_array: self.data.long_array.clone(), - }), - }, - _ => core::hint::unreachable_unchecked(), - }; - data.id.id = self.id.id; - data - } - } -} - -#[allow(non_snake_case)] -impl NbtElement { - #[must_use] - #[inline] - pub const fn Byte(x: NbtByte) -> Self { - let mut this = Self { - data: ManuallyDrop::new(NbtElementData { byte: ManuallyDrop::new(x) }), - }; - this.id.id = NbtByte::ID; - this - } - - #[must_use] - #[inline] - pub const fn Short(x: NbtShort) -> Self { - let mut this = Self { - data: ManuallyDrop::new(NbtElementData { short: ManuallyDrop::new(x) }), - }; - this.id.id = NbtShort::ID; - this - } - - #[must_use] - #[inline] - pub const fn Int(x: NbtInt) -> Self { - let mut this = Self { - data: ManuallyDrop::new(NbtElementData { int: ManuallyDrop::new(x) }), - }; - this.id.id = NbtInt::ID; - this - } - - #[must_use] - #[inline] - pub const fn Long(x: NbtLong) -> Self { - let mut this = Self { - data: ManuallyDrop::new(NbtElementData { long: ManuallyDrop::new(x) }), - }; - this.id.id = NbtLong::ID; - this - } - - #[must_use] - #[inline] - pub const fn Float(x: NbtFloat) -> Self { - let mut this = Self { - data: ManuallyDrop::new(NbtElementData { float: ManuallyDrop::new(x) }), - }; - this.id.id = NbtFloat::ID; - this - } - - #[must_use] - #[inline] - pub const fn Double(x: NbtDouble) -> Self { - let mut this = Self { - data: ManuallyDrop::new(NbtElementData { double: ManuallyDrop::new(x) }), - }; - this.id.id = NbtDouble::ID; - this - } - - #[must_use] - #[inline] - pub fn ByteArray(x: NbtByteArray) -> Self { - let mut this = Self { - data: ManuallyDrop::new(NbtElementData { byte_array: ManuallyDrop::new(x) }), - }; - this.id.id = NbtByteArray::ID; - this - } - - #[must_use] - #[inline] - pub fn String(x: NbtString) -> Self { - let mut this = Self { - data: ManuallyDrop::new(NbtElementData { string: ManuallyDrop::new(x) }), - }; - this.id.id = NbtString::ID; - this - } - - #[must_use] - #[inline] - pub fn List(x: NbtList) -> Self { - let mut this = Self { - data: ManuallyDrop::new(NbtElementData { list: ManuallyDrop::new(x) }), - }; - this.id.id = NbtList::ID; - this - } - - #[must_use] - #[inline] - pub fn Compound(x: NbtCompound) -> Self { - let mut this = Self { - data: ManuallyDrop::new(NbtElementData { compound: ManuallyDrop::new(x) }), - }; - this.id.id = NbtCompound::ID; - this - } - - #[must_use] - pub fn IntArray(x: NbtIntArray) -> Self { - let mut this = Self { - data: ManuallyDrop::new(NbtElementData { int_array: ManuallyDrop::new(x) }), - }; - this.id.id = NbtIntArray::ID; - this - } - - #[must_use] - #[inline] - pub fn LongArray(x: NbtLongArray) -> Self { - let mut this = Self { - data: ManuallyDrop::new(NbtElementData { long_array: ManuallyDrop::new(x) }), - }; - this.id.id = NbtLongArray::ID; - this - } - - #[must_use] - #[inline] - pub fn Chunk(x: NbtChunk) -> Self { - let mut this = Self { - data: ManuallyDrop::new(NbtElementData { chunk: ManuallyDrop::new(x) }), - }; - this.id.id = NbtChunk::ID; - this - } - - #[must_use] - #[inline] - pub fn Region(x: NbtRegion) -> Self { - let mut this = Self { - data: ManuallyDrop::new(NbtElementData { region: ManuallyDrop::new(x) }), - }; - this.id.id = NbtRegion::ID; - this - } -} - -impl NbtElement { - #[must_use] - #[allow(clippy::should_implement_trait)] // i can't, sorry :( - pub fn from_str(mut s: &str) -> Option<(Option, Self)> { - s = s.trim_start(); - - if s.is_empty() { - return None; - } - - let prefix = s.snbt_string_read().and_then(|(prefix, s2)| s2.trim_start().strip_prefix(':').map(|s2| { s = s2.trim_start(); prefix })); - let (s, element) = Self::from_str0(s).map(|(s, x)| (s.trim_start(), x))?; - if !s.is_empty() { - return None; - } - Some((prefix, element)) - } - - #[allow(clippy::too_many_lines)] - pub(in crate::elements) fn from_str0(mut s: &str) -> Option<(&str, Self)> { - if let Some(s2) = s.strip_prefix("false") { - return Some((s2, Self::Byte(NbtByte { value: 0 }))); - } - if let Some(s2) = s.strip_prefix("true") { - return Some((s2, Self::Byte(NbtByte { value: 1 }))); - } - if s.starts_with("[B;") { - return NbtByteArray::from_str0(s).map(|(s, x)| (s, Self::ByteArray(x))); - } - if s.starts_with("[I;") { - return NbtIntArray::from_str0(s).map(|(s, x)| (s, Self::IntArray(x))); - } - if s.starts_with("[L;") { - return NbtLongArray::from_str0(s).map(|(s, x)| (s, Self::LongArray(x))); - } - if s.starts_with('[') { - return NbtList::from_str0(s).map(|(s, x)| (s, Self::List(x))); - } - if s.starts_with('{') { - return NbtCompound::from_str0(s).map(|(s, x)| (s, Self::Compound(x))); - } - if s.starts_with('"') { - return NbtString::from_str0(s).map(|(s, x)| (s, Self::String(x))); - } - - if let Some(s2) = s.strip_prefix("NaN") { - s = s2.trim_start(); - if let Some(s2) = s.strip_prefix('f') { - return Some((s2.trim_start(), Self::Float(NbtFloat { value: f32::NAN }))); - } else if let Some(s2) = s.strip_prefix('d') { - return Some((s2.trim_start(), Self::Double(NbtDouble { value: f64::NAN }))); - } else { - return Some((s2.trim_start(), Self::Double(NbtDouble { value: f64::NAN }))); - } - } - - if let Some(s2) = s.strip_prefix("Infinity").or_else(|| s.strip_prefix("inf")) { - s = s2.trim_start(); - if let Some(s2) = s.strip_prefix('f') { - return Some((s2.trim_start(), Self::Float(NbtFloat { value: f32::INFINITY }))); - } else if let Some(s2) = s.strip_prefix('d') { - return Some((s2.trim_start(), Self::Double(NbtDouble { value: f64::INFINITY }))); - } else { - return Some((s2.trim_start(), Self::Double(NbtDouble { value: f64::INFINITY }))); - } - } - - if let Some(s2) = s.strip_prefix("-Infinity").or_else(|| s.strip_prefix("-inf")) { - s = s2.trim_start(); - if let Some(s2) = s.strip_prefix('f') { - return Some((s2.trim_start(), Self::Float(NbtFloat { value: f32::NEG_INFINITY }))); - } else if let Some(s2) = s.strip_prefix('d') { - return Some((s2.trim_start(), Self::Double(NbtDouble { value: f64::NEG_INFINITY }))); - } else { - return Some((s2.trim_start(), Self::Double(NbtDouble { value: f64::NEG_INFINITY }))); - } - } - - let digit_end_idx = 'a: { - let mut s = s; - let mut digit_end_idx = 0; - - if s.starts_with('+') || s.starts_with('-') { - s = &s[1..]; - digit_end_idx += 1; - } - - let int_part = s.bytes().take_while(u8::is_ascii_digit).count(); - s = &s[int_part..]; - digit_end_idx += int_part; - if int_part == 0 && !s.starts_with('.') { - break 'a 0; - } - if let Some(s2) = s.strip_prefix('.') { - digit_end_idx += 1; - s = s2; - let frac_part = s.bytes().take_while(u8::is_ascii_digit).count(); - digit_end_idx += frac_part; - } - - digit_end_idx - }; - if digit_end_idx > 0 { - let suffix = (s[digit_end_idx..]).trim_start().as_bytes().first().map(u8::to_ascii_lowercase); - return match suffix { - Some(b'b') => Some(( - &s[(digit_end_idx + 1)..], - Self::Byte(NbtByte { - value: (s[..digit_end_idx]).parse().ok()?, - }), - )), - Some(b's') => Some(( - &s[(digit_end_idx + 1)..], - Self::Short(NbtShort { - value: (s[..digit_end_idx]).parse().ok()?, - }), - )), - Some(b'l') => Some(( - &s[(digit_end_idx + 1)..], - Self::Long(NbtLong { - value: (s[..digit_end_idx]).parse().ok()?, - }), - )), - Some(b'f') => Some(( - &s[(digit_end_idx + 1)..], - Self::Float(NbtFloat { - value: (s[..digit_end_idx]).parse().ok()?, - }), - )), - Some(b'd') => Some(( - &s[(digit_end_idx + 1)..], - Self::Double(NbtDouble { - value: (s[..digit_end_idx]).parse().ok()?, - }), - )), - Some(b'|') => Some({ - let mut s = s; - let Ok(x @ 0..=31) = (s[..digit_end_idx]).parse::() else { return None }; - s = s[digit_end_idx..].trim_start().split_at(1).1.trim_start(); - let digit_end_idx = s.bytes().position(|x| !x.is_ascii_digit())?; - let Ok(z @ 0..=31) = s[..digit_end_idx].parse::() else { return None }; - s = s[digit_end_idx..].trim_start(); - let (s, inner) = NbtCompound::from_str0(s)?; - ( - s, - Self::Chunk(NbtChunk::from_compound( - inner, - (x, z), - FileFormat::Zlib, - SystemTime::UNIX_EPOCH.elapsed().unwrap_or_else(|e| e.duration()).as_secs() as u32, - )), - ) - }), - _ => Some(( - &s[digit_end_idx..], - Self::Int(NbtInt { - value: (s[..digit_end_idx]).parse().ok()?, - }), - )), - }; - } - - NbtString::from_str0(s).map(|(s, x)| (s, Self::String(x))) - } - #[inline(never)] - pub fn from_bytes(element: u8, decoder: &mut Decoder) -> Option { - Some(match element { - NbtByte::ID => Self::Byte(NbtByte::from_bytes(decoder)?), - NbtShort::ID => Self::Short(NbtShort::from_bytes(decoder)?), - NbtInt::ID => Self::Int(NbtInt::from_bytes(decoder)?), - NbtLong::ID => Self::Long(NbtLong::from_bytes(decoder)?), - NbtFloat::ID => Self::Float(NbtFloat::from_bytes(decoder)?), - NbtDouble::ID => Self::Double(NbtDouble::from_bytes(decoder)?), - NbtByteArray::ID => Self::ByteArray(NbtByteArray::from_bytes(decoder)?), - NbtString::ID => Self::String(NbtString::from_bytes(decoder)?), - NbtList::ID => Self::List(NbtList::from_bytes(decoder)?), - NbtCompound::ID => Self::Compound(NbtCompound::from_bytes(decoder)?), - NbtIntArray::ID => Self::IntArray(NbtIntArray::from_bytes(decoder)?), - NbtLongArray::ID => Self::LongArray(NbtLongArray::from_bytes(decoder)?), - _ => return None, - }) - } - - #[inline(never)] - pub fn to_bytes(&self, writer: &mut UncheckedBufWriter) { - unsafe { - match self.id() { - NbtByte::ID => self.data.byte.to_bytes(writer), - NbtShort::ID => self.data.short.to_bytes(writer), - NbtInt::ID => self.data.int.to_bytes(writer), - NbtLong::ID => self.data.long.to_bytes(writer), - NbtFloat::ID => self.data.float.to_bytes(writer), - NbtDouble::ID => self.data.double.to_bytes(writer), - NbtByteArray::ID => self.data.byte_array.to_bytes(writer), - NbtString::ID => self.data.string.to_bytes(writer), - NbtList::ID => self.data.list.to_bytes(writer), - NbtCompound::ID => self.data.compound.to_bytes(writer), - NbtIntArray::ID => self.data.int_array.to_bytes(writer), - NbtLongArray::ID => self.data.long_array.to_bytes(writer), - NbtChunk::ID => self.data.chunk.to_bytes(writer), - NbtRegion::ID => self.data.region.to_bytes(writer), - _ => core::hint::unreachable_unchecked(), - }; - } - } - - #[inline] - #[must_use] - pub const fn id(&self) -> u8 { - unsafe { self.id.id } - } - - #[inline] - #[must_use] - pub const fn is_null(&self) -> bool { - self.id() == 0 - } - - #[inline] - #[must_use] - pub fn from_id(id: u8) -> Option { - Some(match id { - NbtByte::ID => Self::Byte(NbtByte::default()), - NbtShort::ID => Self::Short(NbtShort::default()), - NbtInt::ID => Self::Int(NbtInt::default()), - NbtLong::ID => Self::Long(NbtLong::default()), - NbtFloat::ID => Self::Float(NbtFloat::default()), - NbtDouble::ID => Self::Double(NbtDouble::default()), - NbtByteArray::ID => Self::ByteArray(NbtByteArray::new()), - NbtString::ID => Self::String(NbtString::new(CompactString::new_inline(""))), - NbtList::ID => Self::List(NbtList::new(vec![], 0x00)), - NbtCompound::ID => Self::Compound(NbtCompound::new()), - NbtIntArray::ID => Self::IntArray(NbtIntArray::new()), - NbtLongArray::ID => Self::LongArray(NbtLongArray::new()), - NbtChunk::ID => Self::Chunk(NbtChunk::from_compound( - NbtCompound::new(), - (0, 0), - FileFormat::Zlib, - SystemTime::UNIX_EPOCH.elapsed().unwrap_or_else(|e| e.duration()).as_secs() as u32, - )), - _ => return None, - }) - } - - #[inline] - #[must_use] - pub fn from_file(bytes: &[u8]) -> Option { - // #[cfg(debug_assertions)] - // let start = std::time::Instant::now(); - let mut decoder = Decoder::new(bytes); - decoder.assert_len(3)?; - unsafe { - if decoder.u8() != 0x0A { - return None; - } - let skip = decoder.u16() as usize; - decoder.skip(skip); - } - let nbt = Self::Compound(NbtCompound::from_bytes(&mut decoder)?); - // #[cfg(debug_assertions)] - // println!("{}ms for file read", start.elapsed().as_nanos() as f64 / 1_000_000.0); - Some(nbt) - } - - #[inline] - #[must_use] - pub fn to_file(&self) -> Vec { - // #[cfg(debug_assertions)] - let start = std::time::Instant::now(); - let mut writer = UncheckedBufWriter::new(); - if self.id() == NbtCompound::ID { - writer.write(&[0x0A, 0x00, 0x00]); - } - self.to_bytes(&mut writer); - - // #[cfg(debug_assertions)] - println!("{}ms for file write", start.elapsed().as_nanos() as f64 / 1_000_000.0); - writer.finish() - } - - #[inline] - #[must_use] - pub fn from_mca(bytes: &[u8]) -> Option { - // #[cfg(debug_assertions)] - // let start = std::time::Instant::now(); - - // #[cfg(debug_assertions)] - // println!("{}ms for file read", start.elapsed().as_nanos() as f64 / 1_000_000.0); - NbtRegion::from_bytes(bytes).map(Self::Region) - } - - #[inline] - pub fn render(&self, remaining_scroll: &mut usize, builder: &mut VertexBufferBuilder, str: Option<&str>, tail: bool, ctx: &mut RenderContext) { - unsafe { - match self.id() { - NbtByte::ID => self.data.byte.render(builder, str, ctx), - NbtShort::ID => self.data.short.render(builder, str, ctx), - NbtInt::ID => self.data.int.render(builder, str, ctx), - NbtLong::ID => self.data.long.render(builder, str, ctx), - NbtFloat::ID => self.data.float.render(builder, str, ctx), - NbtDouble::ID => self.data.double.render(builder, str, ctx), - NbtByteArray::ID => self.data.byte_array.render(builder, str, remaining_scroll, tail, ctx), - NbtString::ID => self.data.string.render(builder, str, ctx), - NbtList::ID => self.data.list.render(builder, str, remaining_scroll, tail, ctx), - NbtCompound::ID => self.data.compound.render(builder, str, remaining_scroll, tail, ctx), - NbtIntArray::ID => self.data.int_array.render(builder, str, remaining_scroll, tail, ctx), - NbtLongArray::ID => self.data.long_array.render(builder, str, remaining_scroll, tail, ctx), - NbtChunk::ID => self.data.chunk.render(builder, remaining_scroll, tail, ctx), - NbtRegion::ID => { - // can't be done at all - } - _ => core::hint::unreachable_unchecked(), - } - } - } - - #[inline] - #[must_use] - pub fn len(&self) -> Option { - unsafe { - Some(match self.id() { - NbtCompound::ID => self.data.compound.len(), - NbtByteArray::ID | NbtIntArray::ID | NbtLongArray::ID | NbtList::ID => self.data.list.len(), - NbtRegion::ID => self.data.region.len(), - NbtChunk::ID => self.data.chunk.inner.len(), - _ => return None, - }) - } - } - - #[inline] - #[must_use] - pub fn is_empty(&self) -> bool { - self.len().is_some_and(|x| x > 0) - } - - #[inline] - #[must_use] - pub fn display_name(&self) -> &'static str { - match self.id() { - NbtByte::ID => "Byte", - NbtShort::ID => "Short", - NbtInt::ID => "Int", - NbtLong::ID => "Long", - NbtFloat::ID => "Float", - NbtDouble::ID => "Double", - NbtByteArray::ID => "Byte Array", - NbtString::ID => "String", - NbtList::ID => "List", - NbtCompound::ID => "Compound", - NbtIntArray::ID => "Int Array", - NbtLongArray::ID => "Long Array", - NbtChunk::ID => "Chunk", - NbtRegion::ID => "Region", - _ => unsafe { panic_unchecked("Invalid element id") }, - } - } - - #[inline] - pub fn render_icon(id: u8, pos: impl Into<(usize, usize)>, z: u8, builder: &mut VertexBufferBuilder) { - match id { - 0 => {} - NbtByte::ID => NbtByte::render_icon(pos, z, builder), - NbtShort::ID => NbtShort::render_icon(pos, z, builder), - NbtInt::ID => NbtInt::render_icon(pos, z, builder), - NbtLong::ID => NbtLong::render_icon(pos, z, builder), - NbtFloat::ID => NbtFloat::render_icon(pos, z, builder), - NbtDouble::ID => NbtDouble::render_icon(pos, z, builder), - NbtByteArray::ID => NbtByteArray::render_icon(pos, z, builder), - NbtString::ID => NbtString::render_icon(pos, z, builder), - NbtList::ID => NbtList::render_icon(pos, z, builder), - NbtCompound::ID => NbtCompound::render_icon(pos, z, builder), - NbtIntArray::ID => NbtIntArray::render_icon(pos, z, builder), - NbtLongArray::ID => NbtLongArray::render_icon(pos, z, builder), - NbtChunk::ID => NbtChunk::render_icon(pos, z, builder), - NbtRegion::ID => NbtRegion::render_icon(pos, z, builder), - _ => unsafe { panic_unchecked("Invalid element id") }, - } - } - - #[inline] - #[must_use] - pub fn height(&self) -> usize { - unsafe { - match self.id() { - NbtByteArray::ID | NbtIntArray::ID | NbtLongArray::ID => self.data.byte_array.height(), - NbtList::ID => self.data.list.height(), - NbtCompound::ID => self.data.compound.height(), - NbtChunk::ID => self.data.chunk.height(), - NbtRegion::ID => self.data.region.height(), - _ => 1, - } - } - } - - #[inline] - pub fn swap(&mut self, a: usize, b: usize) { - unsafe { - match self.id() { - NbtByteArray::ID | NbtIntArray::ID | NbtLongArray::ID | NbtList::ID => self.data.list.elements.swap(a, b), - NbtCompound::ID => self.data.compound.entries.swap(a, b), - NbtChunk::ID => self.data.chunk.inner.entries.swap(a, b), - NbtRegion::ID => self.data.region.chunks.as_mut().0.swap(a, b), - _ => {} - } - } - } - - #[inline] - #[must_use] - #[allow(clippy::type_complexity)] // a type probably shouldn't abstract what this is, like... yeah - pub fn children(&self) -> Option>> { - unsafe { - Some(match self.id() { - NbtByteArray::ID | NbtIntArray::ID | NbtLongArray::ID | NbtList::ID => Ok(self.data.list.children()), - NbtCompound::ID => Err(self.data.compound.children()), - NbtChunk::ID => Err(self.data.chunk.children()), - NbtRegion::ID => Ok(self.data.region.children()), - _ => return None, - }) - } - } - - #[inline] - pub fn set_value(&mut self, value: CompactString) -> Option { - unsafe { - Some(match self.id() { - NbtByte::ID => { - let before = self.data.byte.value.to_compact_string(); - self.data.byte.set(value.parse().ok()); - before - } - NbtShort::ID => { - let before = self.data.short.value.to_compact_string(); - self.data.short.set(value.parse().ok()); - before - } - NbtInt::ID => { - let before = self.data.int.value.to_compact_string(); - self.data.int.set(value.parse().ok()); - before - } - NbtLong::ID => { - let before = self.data.long.value.to_compact_string(); - self.data.long.set(value.parse().ok()); - before - } - NbtFloat::ID => { - let before = self.data.float.value.to_compact_string(); - self.data.float.set(value.parse().ok()); - before - } - NbtDouble::ID => { - let before = self.data.double.value.to_compact_string(); - self.data.double.set(value.parse().ok()); - before - } - NbtString::ID => core::mem::replace(self, Self::String(NbtString::new(value))).data.string.str.as_str().to_compact_string(), - _ => return None, - }) - } - } - - #[must_use] - pub fn true_height(&self) -> usize { - unsafe { - match self.id() { - NbtByteArray::ID | NbtIntArray::ID | NbtLongArray::ID => self.data.byte_array.true_height(), - NbtList::ID => self.data.list.true_height(), - NbtCompound::ID => self.data.compound.true_height(), - NbtRegion::ID => self.data.region.true_height(), - NbtChunk::ID => self.data.chunk.true_height(), - _ => 1, - } - } - } - - #[inline] - #[must_use] - pub fn toggle(&mut self) -> Option<()> { - unsafe { - match self.id() { - NbtByteArray::ID | NbtIntArray::ID | NbtLongArray::ID => self.data.byte_array.toggle(), - NbtList::ID => self.data.list.toggle(), - NbtCompound::ID => self.data.compound.toggle(), - NbtRegion::ID => self.data.region.toggle(), - NbtChunk::ID => self.data.chunk.toggle(), - _ => None, - } - } - } - - #[inline] - #[must_use] - pub fn open(&self) -> bool { - unsafe { - match self.id() { - NbtByteArray::ID | NbtIntArray::ID | NbtLongArray::ID => self.data.byte_array.open(), - NbtList::ID => self.data.list.open(), - NbtCompound::ID => self.data.compound.open(), - NbtRegion::ID => self.data.region.open(), - NbtChunk::ID => self.data.chunk.open(), - _ => false, - } - } - } - - #[inline] - pub fn increment(&mut self, amount: usize, true_amount: usize) { - unsafe { - match self.id() { - NbtByteArray::ID | NbtIntArray::ID | NbtLongArray::ID => self.data.byte_array.increment(amount, true_amount), - NbtList::ID => self.data.list.increment(amount, true_amount), - NbtCompound::ID => self.data.compound.increment(amount, true_amount), - NbtRegion::ID => self.data.region.increment(amount, true_amount), - NbtChunk::ID => self.data.chunk.increment(amount, true_amount), - _ => {} - } - } - } - - #[inline] - pub fn decrement(&mut self, amount: usize, true_amount: usize) { - unsafe { - match self.id() { - NbtByteArray::ID | NbtIntArray::ID | NbtLongArray::ID => self.data.byte_array.decrement(amount, true_amount), - NbtList::ID => self.data.list.decrement(amount, true_amount), - NbtCompound::ID => self.data.compound.decrement(amount, true_amount), - NbtRegion::ID => self.data.region.decrement(amount, true_amount), - NbtChunk::ID => self.data.chunk.decrement(amount, true_amount), - _ => {} - } - } - } - - #[inline] - #[must_use] - pub fn value(&self) -> (CompactString, bool) { - unsafe { - match self.id() { - NbtByte::ID => (self.data.byte.value.to_compact_string(), true), - NbtShort::ID => (self.data.short.value.to_compact_string(), true), - NbtInt::ID => (self.data.int.value.to_compact_string(), true), - NbtLong::ID => (self.data.long.value.to_compact_string(), true), - NbtFloat::ID => (self.data.float.value.to_compact_string(), true), - NbtDouble::ID => (self.data.double.value.to_compact_string(), true), - NbtByteArray::ID => (self.data.byte_array.value(), false), - NbtString::ID => (self.data.string.str.as_str().to_compact_string(), true), - NbtList::ID => (self.data.list.value(), false), - NbtCompound::ID => (self.data.compound.value(), false), - NbtIntArray::ID => (self.data.int_array.value(), false), - NbtLongArray::ID => (self.data.long_array.value(), false), - NbtChunk::ID => (self.data.chunk.z.to_compact_string(), true), - NbtRegion::ID => (self.data.region.value(), false), - _ => core::hint::unreachable_unchecked(), - } - } - } - - #[inline] - pub fn drop(&mut self, key: Option, element: Self, y: &mut usize, depth: usize, target_depth: usize, line_number: usize, indices: &mut Vec) -> DropFn { - unsafe { - let height = self.height() * 16; - match self.id() { - _ if *y >= height + 8 => { - *y -= height; - DropFn::Missed(key, element) - } - NbtByteArray::ID => self.data.byte_array.drop(key, element, y, depth, target_depth, line_number, indices), - NbtList::ID => self.data.list.drop(key, element, y, depth, target_depth, line_number, indices), - NbtCompound::ID => self.data.compound.drop(key, element, y, depth, target_depth, line_number, indices), - NbtIntArray::ID => self.data.int_array.drop(key, element, y, depth, target_depth, line_number, indices), - NbtLongArray::ID => self.data.long_array.drop(key, element, y, depth, target_depth, line_number, indices), - NbtChunk::ID => NbtCompound::drop(&mut self.data.chunk.inner, key, element, y, depth, target_depth, line_number, indices), - NbtRegion::ID => self.data.region.drop(key, element, y, depth, target_depth, line_number, indices), - _ => { - *y = y.saturating_sub(16); - DropFn::Missed(key, element) - } - } - } - } - - #[inline] - pub fn shut(&mut self) { - unsafe { - match self.id() { - NbtByteArray::ID | NbtIntArray::ID | NbtLongArray::ID => self.data.byte_array.shut(), - NbtList::ID => self.data.list.shut(), - NbtCompound::ID => self.data.compound.shut(), - NbtChunk::ID => self.data.chunk.shut(), - NbtRegion::ID => self.data.region.shut(), - _ => {} - } - } - } - - #[inline] - pub fn expand<'a, 'b>(&'b mut self, scope: &'a Scope<'a, 'b>) { - unsafe { - match self.id() { - NbtByteArray::ID | NbtIntArray::ID | NbtLongArray::ID => self.data.byte_array.expand(), - NbtList::ID => self.data.list.expand(scope), - NbtCompound::ID => self.data.compound.expand(scope), - NbtChunk::ID => self.data.chunk.inner.expand(scope), - NbtRegion::ID => self.data.region.expand(scope), - _ => {} - } - } - } - - /// # Errors - /// - /// * `self` cannot contain that specific variant of `Self`, i.e. `Self::NbtByte` in an `Self::NbtIntArray` - /// - /// If any changes are made to this error list then duplicate may have to be updated as it relies on this never occurring - #[inline] - pub fn insert(&mut self, idx: usize, value: Self) -> Result<(), Self> { - unsafe { - match self.id() { - NbtByteArray::ID => self.data.byte_array.insert(idx, value), - NbtList::ID => self.data.list.insert(idx, value), - NbtCompound::ID => { - self.data.compound.insert(idx, CompactString::new_inline("_"), value); - Ok(()) - } - NbtIntArray::ID => self.data.int_array.insert(idx, value), - NbtLongArray::ID => self.data.long_array.insert(idx, value), - NbtRegion::ID => self.data.region.insert(idx, value), - NbtChunk::ID => { - self.data.chunk.insert_full(idx, CompactString::new_inline("_"), value); - Ok(()) - } - _ => Err(value), - } - } - } - - #[inline] - pub fn remove(&mut self, idx: usize) -> Option<(Option, Self)> { - unsafe { - Some(match self.id() { - NbtByteArray::ID | NbtIntArray::ID | NbtLongArray::ID => (None, self.data.byte_array.remove(idx)), - NbtList::ID => (None, self.data.list.remove(idx)), - NbtCompound::ID => return self.data.compound.remove_idx(idx).map(|(a, b)| (Some(a), b)), - NbtRegion::ID => (None, self.data.region.remove(idx)), - NbtChunk::ID => return self.data.chunk.remove_idx(idx).map(|(a, b)| (Some(a), b)), - _ => return None, - }) - } - } - - #[inline] - #[must_use] - pub fn get(&self, idx: usize) -> Option<&Self> { - unsafe { - match self.id() { - NbtByteArray::ID | NbtIntArray::ID | NbtLongArray::ID => self.data.byte_array.get(idx), - NbtList::ID => self.data.list.get(idx), - NbtCompound::ID => self.data.compound.get(idx).map(|(_, x)| x), - NbtRegion::ID => self.data.region.get(idx), - NbtChunk::ID => self.data.chunk.get(idx).map(|(_, x)| x), - _ => None, - } - } - } - - #[inline] - #[must_use] - pub fn get_mut(&mut self, idx: usize) -> Option<&mut Self> { - unsafe { - match self.id() { - NbtByteArray::ID | NbtIntArray::ID | NbtLongArray::ID => self.data.byte_array.get_mut(idx), - NbtList::ID => self.data.list.get_mut(idx), - NbtCompound::ID => self.data.compound.get_mut(idx).map(|(_, x)| x), - NbtRegion::ID => self.data.region.get_mut(idx), - NbtChunk::ID => self.data.chunk.get_mut(idx).map(|(_, x)| x), - _ => None, - } - } - } - - /// # Safety - /// - /// * `self` must be of variant `NbtElement::Chunk` - #[inline] - #[must_use] - pub unsafe fn into_chunk_unchecked(self) -> NbtChunk { - core::mem::transmute(core::mem::transmute::<_, NbtElementData>(self).chunk) - } - - /// # Safety - /// - /// * `self` must be of variant `NbtElement::Chunk` - #[inline] - #[must_use] - pub unsafe fn as_chunk_unchecked(&self) -> &NbtChunk { - &self.data.chunk - } - - /// # Safety - /// - /// * `self` must be of variant `NbtElement::Chunk` - #[inline] - #[must_use] - pub unsafe fn as_chunk_unchecked_mut(&mut self) -> &mut NbtChunk { - &mut self.data.chunk - } - - #[inline] - #[must_use] - pub fn max_depth(&self) -> usize { - unsafe { - match self.id() { - NbtByteArray::ID | NbtIntArray::ID | NbtLongArray::ID => self.data.deref().byte_array.max_depth(), - NbtList::ID => self.data.deref().list.max_depth(), - NbtCompound::ID => self.data.deref().compound.max_depth(), - NbtRegion::ID => self.data.deref().region.max_depth(), - NbtChunk::ID => self.data.deref().chunk.deref().inner.max_depth(), - _ => 0, - } - } - } - - #[inline] - #[must_use] - #[allow(clippy::match_same_arms)] - pub const fn actions(&self) -> &[ElementAction] { - unsafe { - match self.id() { - NbtByte::ID => &[ElementAction::CopyRaw, ElementAction::CopyFormatted, ElementAction::OpenInTxt], - NbtShort::ID => &[ElementAction::CopyRaw, ElementAction::CopyFormatted, ElementAction::OpenInTxt], - NbtInt::ID => &[ElementAction::CopyRaw, ElementAction::CopyFormatted, ElementAction::OpenInTxt], - NbtLong::ID => &[ElementAction::CopyRaw, ElementAction::CopyFormatted, ElementAction::OpenInTxt], - NbtFloat::ID => &[ElementAction::CopyRaw, ElementAction::CopyFormatted, ElementAction::OpenInTxt], - NbtDouble::ID => &[ElementAction::CopyRaw, ElementAction::CopyFormatted, ElementAction::OpenInTxt], - NbtByteArray::ID => &[ElementAction::CopyRaw, ElementAction::CopyFormatted, ElementAction::OpenInTxt, ElementAction::OpenArrayInHex], - NbtString::ID => &[ElementAction::CopyRaw, ElementAction::CopyFormatted, ElementAction::OpenInTxt], - NbtList::ID => &[ElementAction::CopyRaw, ElementAction::CopyFormatted, ElementAction::OpenInTxt], - NbtCompound::ID => &[ElementAction::CopyRaw, ElementAction::CopyFormatted, ElementAction::OpenInTxt], - NbtIntArray::ID => &[ElementAction::CopyRaw, ElementAction::CopyFormatted, ElementAction::OpenInTxt, ElementAction::OpenArrayInHex], - NbtLongArray::ID => &[ElementAction::CopyRaw, ElementAction::CopyFormatted, ElementAction::OpenInTxt, ElementAction::OpenArrayInHex], - NbtChunk::ID => &[ElementAction::CopyRaw, ElementAction::CopyFormatted, ElementAction::OpenInTxt], - NbtRegion::ID => &[ElementAction::CopyRaw, ElementAction::CopyFormatted, ElementAction::OpenInTxt], - _ => core::hint::unreachable_unchecked(), - } - } - } -} - -impl Display for NbtElement { - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - unsafe { - match self.id() { - NbtByte::ID => write!(f, "{}", &*self.data.byte), - NbtShort::ID => write!(f, "{}", &*self.data.short), - NbtInt::ID => write!(f, "{}", &*self.data.int), - NbtLong::ID => write!(f, "{}", &*self.data.long), - NbtFloat::ID => write!(f, "{}", &*self.data.float), - NbtDouble::ID => write!(f, "{}", &*self.data.double), - NbtByteArray::ID => write!(f, "{}", &*self.data.byte_array), - NbtString::ID => write!(f, "{}", &*self.data.string), - NbtList::ID => write!(f, "{}", &*self.data.list), - NbtCompound::ID => write!(f, "{}", &*self.data.compound), - NbtIntArray::ID => write!(f, "{}", &*self.data.int_array), - NbtLongArray::ID => write!(f, "{}", &*self.data.long_array), - NbtChunk::ID => write!(f, "{}", &*self.data.chunk), - NbtRegion::ID => Err(Error), - _ => core::hint::unreachable_unchecked(), - } - } - } -} - -impl Debug for NbtElement { - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - unsafe { - match self.id() { - NbtByte::ID => Debug::fmt(&*self.data.byte, f), - NbtShort::ID => Debug::fmt(&*self.data.short, f), - NbtInt::ID => Debug::fmt(&*self.data.int, f), - NbtLong::ID => Debug::fmt(&*self.data.long, f), - NbtFloat::ID => Debug::fmt(&*self.data.float, f), - NbtDouble::ID => Debug::fmt(&*self.data.double, f), - NbtByteArray::ID => Debug::fmt(&*self.data.byte_array, f), - NbtString::ID => Debug::fmt(&*self.data.string, f), - NbtList::ID => Debug::fmt(&*self.data.list, f), - NbtCompound::ID => Debug::fmt(&*self.data.compound, f), - NbtIntArray::ID => Debug::fmt(&*self.data.int_array, f), - NbtLongArray::ID => Debug::fmt(&*self.data.long_array, f), - NbtChunk::ID => Debug::fmt(&*self.data.chunk, f), - NbtRegion::ID => Err(Error), - _ => core::hint::unreachable_unchecked(), - } - } - } -} - -impl Drop for NbtElement { - fn drop(&mut self) { - unsafe { - let id = self.id(); - let mut data = core::ptr::addr_of_mut!(self.data).read(); - match id { - NbtByteArray::ID | NbtIntArray::ID | NbtLongArray::ID => { - let vec = &mut *data.byte_array.values; - if !vec.is_empty() { - dealloc(vec.as_mut_ptr().cast(), Layout::array::(vec.capacity()).unwrap_unchecked()); - } - dealloc((vec as *mut Vec).cast(), Layout::new::>()); - } - NbtString::ID => { - core::ptr::addr_of_mut!(data.string.str).drop_in_place(); - } - NbtList::ID => { - let element = data.list.element; - let list = &mut *data.list.elements; - if element > NbtDouble::ID { - for entry in &mut *list { - (entry as *mut Self).drop_in_place(); - } - } - if !list.is_empty() { - dealloc(list.as_mut_ptr().cast(), Layout::array::(list.capacity()).unwrap_unchecked()); - } - dealloc((list as *mut Vec).cast(), Layout::new::>()); - } - NbtCompound::ID => { - let map = &mut *data.compound.entries; - let CompoundMap { indices, entries } = map; - (indices as *mut RawTable).drop_in_place(); - for Entry { value, key, .. } in &mut *entries { - (value as *mut Self).drop_in_place(); - if key.is_heap_allocated() { - dealloc(key.as_mut_ptr(), Layout::array::(key.len()).unwrap_unchecked()); - } - } - if !entries.is_empty() { - dealloc(entries.as_mut_ptr().cast(), Layout::array::(entries.capacity()).unwrap_unchecked()); - } - dealloc((map as *mut CompoundMap).cast(), Layout::new::()); - } - NbtChunk::ID => { - let map = &mut *data.chunk.inner.entries; - let CompoundMap { indices, entries } = map; - (indices as *mut RawTable).drop_in_place(); - for Entry { value, key, .. } in &mut *entries { - (value as *mut Self).drop_in_place(); - if key.is_heap_allocated() { - dealloc(key.as_mut_ptr(), Layout::array::(key.len()).unwrap_unchecked()); - } - } - if !entries.is_empty() { - dealloc(entries.as_mut_ptr().cast(), Layout::array::(entries.capacity()).unwrap_unchecked()); - } - dealloc((map as *mut CompoundMap).cast(), Layout::new::()); - } - // no real speedup from using threads, seems to be memory-bound, or dealloc-call-bound - NbtRegion::ID => { - let (map, chunks) = *core::ptr::addr_of_mut!(data.region.chunks).read(); - drop(map); - for mut chunk in core::mem::transmute::<_, [ManuallyDrop; 1024]>(chunks) { - if !chunk.is_null() { - let ptr = chunk.as_chunk_unchecked_mut().inner.as_mut(); - let map = &mut *ptr.entries; - let CompoundMap { indices, entries } = map; - (indices as *mut RawTable).drop_in_place(); - for Entry { value, key, .. } in &mut *entries { - (value as *mut Self).drop_in_place(); - if key.is_heap_allocated() { - dealloc(key.as_mut_ptr(), Layout::array::(key.len()).unwrap_unchecked()); - } - } - if !entries.is_empty() { - dealloc(entries.as_mut_ptr().cast(), Layout::array::(entries.capacity()).unwrap_unchecked()); - } - dealloc((map as *mut CompoundMap).cast(), Layout::new::()); - dealloc((ptr as *mut NbtCompound).cast(), Layout::new::()); - } - } - } - _ => {} - } - } - } -} - -#[inline] -#[must_use] -pub fn id_to_string_name(id: u8) -> (&'static str, &'static str) { - match id { - 0 => ("entry", "entries"), - NbtByte::ID => ("byte", "bytes"), - NbtShort::ID => ("short", "shorts"), - NbtInt::ID => ("int", "ints"), - NbtLong::ID => ("long", "longs"), - NbtFloat::ID => ("float", "floats"), - NbtDouble::ID => ("double", "doubles"), - NbtByteArray::ID => ("byte array", "byte arrays"), - NbtString::ID => ("string", "strings"), - NbtList::ID => ("list", "lists"), - NbtCompound::ID => ("compound", "compounds"), - NbtIntArray::ID => ("int array", "int arrays"), - NbtLongArray::ID => ("long array", "long arrays"), - NbtChunk::ID => ("chunk", "chunks"), - NbtRegion::ID => ("region", "regions"), - _ => unsafe { panic_unchecked("Invalid id") }, - } -} diff --git a/src/elements/list.rs b/src/elements/list.rs index 9db48a5..e440f3a 100644 --- a/src/elements/list.rs +++ b/src/elements/list.rs @@ -5,10 +5,10 @@ use std::intrinsics::likely; use std::slice::{Iter, IterMut}; use std::thread::Scope; -use crate::assets::*; +use crate::assets::{BASE_TEXT_Z, BASE_Z, CONNECTION_UV, LIST_UV}; use crate::decoder::Decoder; use crate::elements::chunk::NbtChunk; -use crate::elements::element_type::{id_to_string_name, NbtElement}; +use crate::elements::element::{id_to_string_name, NbtElement}; use crate::encoder::UncheckedBufWriter; use crate::{DropFn, OptionExt, RenderContext, StrExt, VertexBufferBuilder}; diff --git a/src/elements/string.rs b/src/elements/string.rs index 75d9bdb..1b6c5f2 100644 --- a/src/elements/string.rs +++ b/src/elements/string.rs @@ -6,7 +6,7 @@ use std::ptr::NonNull; use compact_str::CompactString; -use crate::assets::*; +use crate::assets::{BASE_TEXT_Z, BASE_Z, STRING_UV}; use crate::decoder::Decoder; use crate::encoder::UncheckedBufWriter; use crate::{RenderContext, StrExt, VertexBufferBuilder}; @@ -115,51 +115,55 @@ impl Clone for TwentyThree { } impl TwentyThree { - pub fn new(mut s: CompactString) -> Self { + #[must_use] + pub fn new(s: CompactString) -> Self { unsafe { let len = s.len(); - if len > 23 { + let res = if len > 23 { + let mut owned = s.into_string(); let res = Self { heap: ManuallyDrop::new(HeapTwentyThree { - ptr: NonNull::new_unchecked(s.as_mut_ptr()), + ptr: NonNull::new_unchecked(owned.as_mut_ptr()), len, _pad: MaybeUninit::uninit_array(), variant: 254, }), }; - core::mem::forget(s); + core::mem::forget(owned); res } else { let ptr = s.as_ptr(); - Self { + let res = Self { stack: ManuallyDrop::new(if len == 23 { StackTwentyThree { data: ptr.cast::<[u8; 23]>().read() } } else { let mut data = MaybeUninit::::uninit_array(); data.as_mut_ptr().cast::().copy_from_nonoverlapping(ptr, len); - // data.as_mut_ptr().cast::().add(len).write_bytes(0, 22 - len); data[22].write(192 + len as u8); StackTwentyThree { data: MaybeUninit::array_assume_init(data), } }), - } - } + }; + core::mem::forget(s); + res + }; + res } } + #[must_use] pub fn as_str(&self) -> &str { unsafe { let last = self.heap.variant; - if last == 254 { - core::str::from_utf8_unchecked(core::slice::from_raw_parts(self.heap.ptr.as_ptr(), self.heap.len)) + let (ptr, len) = if last == 254 { + (self.heap.ptr.as_ptr().cast_const(), self.heap.len) + } else if last < 192 { + (self.stack.data.as_ptr(), 23) } else { - if last < 192 { - core::str::from_utf8_unchecked(core::slice::from_raw_parts(self.stack.data.as_ptr(), 23)) - } else { - core::str::from_utf8_unchecked(core::slice::from_raw_parts(self.stack.data.as_ptr(), (last - 192) as usize)) - } - } + (self.stack.data.as_ptr(), (last - 192) as usize) + }; + core::str::from_utf8_unchecked(core::slice::from_raw_parts(ptr, len)) } } } @@ -175,8 +179,7 @@ impl Deref for TwentyThree { impl Drop for TwentyThree { fn drop(&mut self) { unsafe { - let last = self.heap.variant; - if last == 254 { + if self.heap.variant == 254 { dealloc(self.heap.ptr.as_ptr(), Layout::array::(self.heap.len).unwrap_unchecked()); } } diff --git a/src/main.rs b/src/main.rs index 06c40b5..16427eb 100644 --- a/src/main.rs +++ b/src/main.rs @@ -52,16 +52,18 @@ #![feature(alloc_error_hook)] #![feature(alloc_error_handler)] #![feature(iter_next_chunk)] -// #![cfg_attr(all(windows, not(debug_assertions)), windows_subsystem = "windows")] +#![feature(const_collections_with_hasher)] +#![cfg_attr(all(windows, not(debug_assertions)), windows_subsystem = "windows")] use std::convert::identity; use std::fmt::Write; use compact_str::{CompactString, ToCompactString}; use notify::PollWatcher; +use static_assertions::const_assert_eq; use uuid::Uuid; -use elements::element_type::NbtElement; +use elements::element::NbtElement; use vertex_buffer_builder::VertexBufferBuilder; use crate::assets::{ @@ -71,7 +73,7 @@ use crate::assets::{ }; use crate::elements::chunk::{NbtChunk, NbtRegion}; use crate::elements::compound::NbtCompound; -use crate::elements::element_type::{NbtByte, NbtByteArray, NbtDouble, NbtFloat, NbtInt, NbtIntArray, NbtLong, NbtLongArray, NbtShort}; +use crate::elements::element::{NbtByte, NbtByteArray, NbtDouble, NbtFloat, NbtInt, NbtIntArray, NbtLong, NbtLongArray, NbtShort}; use crate::elements::list::NbtList; use crate::elements::string::NbtString; use crate::tree_travel::Navigate; @@ -86,6 +88,8 @@ mod vertex_buffer_builder; mod window; pub mod workbench; mod workbench_action; +mod shader; +mod text_shader; #[macro_export] macro_rules! flags { @@ -195,100 +199,77 @@ pub fn sum_indices>(indices: I, mut root: &NbtElement) let mut total = 0; let mut indices = indices.peekable(); while let Some(idx) = indices.next() { - root = match root.id() { - NbtByteArray::ID | NbtIntArray::ID | NbtLongArray::ID => { - if idx >= root.data.byte_array.len() { - panic_unchecked("byte array oob") - } else { - total += 1 + idx; - break; - } + root = if let NbtByteArray::ID | NbtIntArray::ID | NbtLongArray::ID = root.id() { + total += 1 + idx; + break; + } else if let Some(list) = root.as_list() { + total += 1 + list.children().take(idx).map(NbtElement::height).sum::(); + if let Some(root) = list.get(idx) { + root + } else { + break; } - NbtList::ID => { - if idx >= root.data.list.len() { - panic_unchecked("list oob") - } else { - total += 1; - for jdx in 0..idx { - // SAFETY: n < len (is valid bounds) implies n - m (where m is a positive integer <= n) < len (is valid bounds) - total += root.data.list.get(jdx).panic_unchecked("index oob").height(); - } - // SAFETY: asserted beforehand - root.data.list.get(idx).panic_unchecked("index oob") - } + } else if let Some(compound) = root.as_compound() { + total += 1 + compound.children().take(idx).map(|(_, b)| b).map(NbtElement::height).sum::(); + if let Some((_, root)) = compound.get(idx) { + root + } else { + break; } - NbtCompound::ID => { - if idx >= root.data.compound.len() { - panic_unchecked("compound oob") - } else { - total += 1; - for jdx in 0..idx { - // SAFETY: n < len (is valid bounds) implies n - m (where m is a positive integer <= n) < len (is valid bounds) - total += root.data.compound.get(jdx).panic_unchecked("index oob").1.height(); - } - // SAFETY: asserted beforehand - root.data.compound.get(idx).panic_unchecked("index oob").1 - } + } else if let Some(chunk) = root.as_chunk() { + total += 1 + chunk.children().take(idx).map(|(_, b)| b).map(NbtElement::height).sum::(); + if let Some((_, root)) = chunk.get(idx) { + root + } else { + break; } - NbtRegion::ID => { - if idx >= root.data.region.len() { - panic_unchecked("region oob") - } else { - total += 1; - for jdx in 0..idx { - // SAFETY: n < len (is valid bounds) implies n - m (where m is a positive integer <= n) < len (is valid bounds) - total += root.data.region.get(jdx).panic_unchecked("index oob").height(); - } - // SAFETY: asserted beforehand - root.data.region.get(idx).panic_unchecked("index oob") - } + } else if let Some(region) = root.as_region() { + total += 1 + region.children().take(idx).map(NbtElement::height).sum::(); + if let Some(root) = region.get(idx) { + root + } else { + break; } - _ => { - total += root.height(); - if indices.peek().is_some() { - panic_unchecked("tried to index non-indexable") - } else { - break; - } + } else { + total += root.height(); + if indices.peek().is_some() { + panic_unchecked("tried to index non-indexable") + } else { + break; } - } + }; } total } } pub fn recache_along_indices(indices: &[usize], root: &mut NbtElement) { - unsafe { - match root.id() { - NbtRegion::ID => { - if let Some((&idx, rest)) = indices.split_first() { - recache_along_indices(rest, root.data.region.get_mut(idx).panic_unchecked("expected valid index")); - } - root.data.region.recache_depth(); - } - NbtByteArray::ID | NbtIntArray::ID | NbtLongArray::ID => { - root.data.byte_array.recache_depth(); - } - NbtList::ID => { - if let Some((&idx, rest)) = indices.split_first() { - recache_along_indices(rest, root.data.list.get_mut(idx).panic_unchecked("expected valid index")); - } - root.data.list.recache_depth(); - } - NbtCompound::ID => { - if let Some((&idx, rest)) = indices.split_first() { - recache_along_indices(rest, root.data.compound.get_mut(idx).panic_unchecked("expected valid index").1); - } - root.data.compound.recache_depth(); - } - NbtChunk::ID => { - if let Some((&idx, rest)) = indices.split_first() { - recache_along_indices(rest, root.data.chunk.inner.get_mut(idx).panic_unchecked("expected valid index").1); - } - root.data.chunk.inner.recache_depth(); - } - _ => panic_unchecked("Found non-complex element in indices"), + if let Some(region) = root.as_region_mut() { + if let Some((&idx, rest)) = indices.split_first() { + recache_along_indices(rest, unsafe { region.get_mut(idx).panic_unchecked("expected valid index") }); + } + region.recache_depth(); + } else if let Some(array) = root.as_byte_array_mut() { + array.recache_depth(); + } else if let Some(array) = root.as_int_array_mut() { + array.recache_depth(); + } else if let Some(array) = root.as_long_array_mut() { + array.recache_depth(); + } else if let Some(list) = root.as_list_mut() { + if let Some((&idx, rest)) = indices.split_first() { + recache_along_indices(rest, unsafe { list.get_mut(idx).panic_unchecked("expected valid index") }); + } + list.recache_depth(); + } else if let Some(compound) = root.as_compound_mut() { + if let Some((&idx, rest)) = indices.split_first() { + recache_along_indices(rest, unsafe { compound.get_mut(idx).panic_unchecked("expected valid index") }.1); } + compound.recache_depth(); + } else if let Some(chunk) = root.as_chunk_mut() { + if let Some((&idx, rest)) = indices.split_first() { + recache_along_indices(rest, unsafe { chunk.get_mut(idx).panic_unchecked("expected valid index") }.1); + } + chunk.recache_depth(); } } @@ -862,30 +843,15 @@ pub unsafe fn panic_unchecked(msg: &str) -> ! { core::hint::unreachable_unchecked() } -trait IntoOk { - type Ok; - - fn into_ok(self) -> Self::Ok; -} - -impl IntoOk for Result { - type Ok = T; - - fn into_ok(self) -> T { - match self { - Ok(x) => x, - Err(e) => match e {}, - } - } -} - pub mod elements { pub mod array; pub mod chunk; pub mod compound; pub mod element_action; - pub mod element_type; + pub mod element; pub mod list; pub mod primitive; pub mod string; } + +const_assert_eq!(VertexBufferBuilder::CHAR_WIDTH[b':' as usize], VertexBufferBuilder::CHAR_WIDTH[b',' as usize]); diff --git a/src/selected_text.rs b/src/selected_text.rs index d50e7d9..5d810f1 100644 --- a/src/selected_text.rs +++ b/src/selected_text.rs @@ -5,7 +5,7 @@ use std::time::{Duration, SystemTime, SystemTimeError}; use winit::event::VirtualKeyCode; -use crate::assets::*; +use crate::assets::{BASE_TEXT_Z, ELEMENT_HIGHLIGHT_Z, HEADER_SIZE, SELECTED_TEXT_Z, SELECTION_UV}; use crate::selected_text::KeyResult::{Down, Failed, Finish, ForceClose, ForceOpen, Keyfix, NothingSpecial, Revert, ShiftDown, ShiftUp, Up, Valuefix}; use crate::vertex_buffer_builder::VertexBufferBuilder; use crate::{flags, is_jump_char_boundary, is_utf8_char_boundary, LinkedQueue, OptionExt, StrExt}; diff --git a/src/shader.rs b/src/shader.rs new file mode 100644 index 0000000..bd73b23 --- /dev/null +++ b/src/shader.rs @@ -0,0 +1,43 @@ +use wgsl_inline::wgsl; + +wgsl! { + struct VertexInput { + @location(0) + position: vec3, + @location(1) + tex_coords: vec2 + } + + struct VertexOutput { + @builtin(position) + clip_position: vec4, + @location(0) + tex_coords: vec2 + } + + @vertex + fn vs_main(input: VertexInput) -> VertexOutput { + var output: VertexOutput; + output.tex_coords = input.tex_coords; + output.clip_position = vec4(input.position, 1.0); + return output; + } + + @group(0) + @binding(0) + var t_diffuse: texture_2d; + + @group(0) + @binding(1) + var s_diffuse: sampler; + + @fragment + fn fs_main(input: VertexOutput) -> @location(0) vec4 { + var out = textureSample(t_diffuse, s_diffuse, input.tex_coords); + if (out[3] == 0.0) { + discard; + } else { + return out; + } + } +} diff --git a/src/shader.wgsl b/src/shader.wgsl deleted file mode 100644 index 654392b..0000000 --- a/src/shader.wgsl +++ /dev/null @@ -1,39 +0,0 @@ -struct VertexInput { - @location(0) - position: vec3, - @location(1) - tex_coords: vec2 -} - -struct VertexOutput { - @builtin(position) - clip_position: vec4, - @location(0) - tex_coords: vec2 -} - -@vertex -fn vs_main(input: VertexInput) -> VertexOutput { - var output: VertexOutput; - output.tex_coords = input.tex_coords; - output.clip_position = vec4(input.position, 1.0); - return output; -} - -@group(0) -@binding(0) -var t_diffuse: texture_2d; - -@group(0) -@binding(1) -var s_diffuse: sampler; - -@fragment -fn fs_main(input: VertexOutput) -> @location(0) vec4 { - var out = textureSample(t_diffuse, s_diffuse, input.tex_coords); - if (out[3] == 0.0) { - discard; - } else { - return out; - } -} \ No newline at end of file diff --git a/src/tab.rs b/src/tab.rs index 01f52d9..199d171 100644 --- a/src/tab.rs +++ b/src/tab.rs @@ -5,10 +5,15 @@ use std::path::{Path, PathBuf}; use flate2::Compression; use uuid::Uuid; -use crate::assets::*; +use crate::assets::{ + BYTE_ARRAY_GHOST_UV, BYTE_ARRAY_UV, BYTE_GHOST_UV, BYTE_UV, CHUNK_GHOST_UV, CHUNK_UV, COMPOUND_GHOST_UV, COMPOUND_ROOT_UV, COMPOUND_UV, DOUBLE_GHOST_UV, DOUBLE_UV, ENABLED_FREEHAND_MODE_UV, + FLOAT_GHOST_UV, FLOAT_UV, FREEHAND_MODE_UV, GZIP_FILE_TYPE_UV, HEADER_SIZE, HELD_SCROLLBAR_UV, INT_ARRAY_GHOST_UV, INT_ARRAY_UV, INT_GHOST_UV, INT_UV, LINE_NUMBER_SEPARATOR_UV, LIST_GHOST_UV, + LIST_UV, LONG_ARRAY_GHOST_UV, LONG_ARRAY_UV, LONG_GHOST_UV, LONG_UV, MCA_FILE_TYPE_UV, NBT_FILE_TYPE_UV, REDO_UV, REGION_UV, SCROLLBAR_Z, SHORT_GHOST_UV, SHORT_UV, SNBT_FILE_TYPE_UV, + STRING_GHOST_UV, STRING_UV, UNDO_UV, UNHELD_SCROLLBAR_UV, UNKNOWN_NBT_GHOST_UV, UNKNOWN_NBT_UV, ZLIB_FILE_TYPE_UV, +}; use crate::elements::chunk::NbtRegion; use crate::elements::compound::NbtCompound; -use crate::elements::element_type::NbtElement; +use crate::elements::element::NbtElement; use crate::vertex_buffer_builder::{Vec2u, VertexBufferBuilder}; use crate::workbench_action::WorkbenchAction; use crate::{LinkedQueue, RenderContext, StrExt}; @@ -57,15 +62,11 @@ impl Tab { } pub fn save(&mut self) -> bool { - if let Some(dir) = &self.path { - if write(dir, self.compression.encode(&self.value)).is_err() { - return false; - }; - self.history_changed = false; - true - } else { - false - } + if write(self.path.as_deref().unwrap_or_else(|| self.name.as_ref().as_ref()), self.compression.encode(&self.value)).is_err() { + return false; + }; + self.history_changed = false; + true } #[allow(clippy::too_many_lines)] @@ -74,14 +75,10 @@ impl Tab { let mouse_y = ctx.mouse_y; let horizontal_scroll_before = core::mem::replace(&mut builder.horizontal_scroll, self.horizontal_scroll(held_entry)); - if self.value.id() == NbtCompound::ID { - unsafe { - self.value.data.compound.render_root(builder, &self.name, ctx); - } - } else if self.value.id() == NbtRegion::ID { - unsafe { - self.value.data.region.render_root(builder, &self.name, ctx); - } + if let Some(compound) = self.value.as_compound() { + compound.render_root(builder, &self.name, ctx); + } else if let Some(region) = self.value.as_region() { + region.render_root(builder, &self.name, ctx); } ctx.render_line_numbers(builder, &self.bookmarks); builder.horizontal_scroll = horizontal_scroll_before; @@ -92,7 +89,7 @@ impl Tab { if height > total { let offset = total * self.scroll() / height + HEADER_SIZE; let height = (total * total) / height; - let held = ((builder.window_width() - 4)..(builder.window_width())).contains(&mouse_x) && (offset..=(offset + height)).contains(&mouse_y) || held; + let held = ((builder.window_width() - 8)..(builder.window_width())).contains(&mouse_x) && (offset..=(offset + height)).contains(&mouse_y) || held; let uv = if held { HELD_SCROLLBAR_UV } else { UNHELD_SCROLLBAR_UV }; builder.draw_texture_z((builder.window_width() - 7, offset), SCROLLBAR_Z, uv, (6, 1)); if height > 2 { @@ -157,11 +154,7 @@ impl Tab { builder.draw_texture((builder.window_width() - 18, 26), freehand_uv, (16, 16)); } { - let mx = if (24..46).contains(&mouse_y) { - Some(mouse_x & !0b1111) - } else { - None - }; + let mx = if (24..46).contains(&mouse_y) { Some(mouse_x & !0b1111) } else { None }; builder.draw_texture((0, 26), if mx == Some(0) { BYTE_UV } else { BYTE_GHOST_UV }, (16, 16)); builder.draw_texture((16, 26), if mx == Some(16) { SHORT_UV } else { SHORT_GHOST_UV }, (16, 16)); builder.draw_texture((32, 26), if mx == Some(32) { INT_UV } else { INT_GHOST_UV }, (16, 16)); @@ -174,15 +167,7 @@ impl Tab { builder.draw_texture((144, 26), if mx == Some(144) { STRING_UV } else { STRING_GHOST_UV }, (16, 16)); builder.draw_texture((160, 26), if mx == Some(160) { LIST_UV } else { LIST_GHOST_UV }, (16, 16)); builder.draw_texture((176, 26), if mx == Some(176) { COMPOUND_UV } else { COMPOUND_GHOST_UV }, (16, 16)); - builder.draw_texture( - (192, 26), - if mx == Some(192) && self.value.id() == NbtRegion::ID { - CHUNK_UV - } else { - CHUNK_GHOST_UV - }, - (16, 16), - ); + builder.draw_texture((192, 26), if mx == Some(192) && self.value.id() == NbtRegion::ID { CHUNK_UV } else { CHUNK_GHOST_UV }, (16, 16)); builder.draw_texture((208, 26), if mx == Some(208) { UNKNOWN_NBT_UV } else { UNKNOWN_NBT_GHOST_UV }, (16, 16)); } } diff --git a/src/text_shader.rs b/src/text_shader.rs new file mode 100644 index 0000000..6c8348e --- /dev/null +++ b/src/text_shader.rs @@ -0,0 +1,73 @@ +use wgsl_inline::wgsl; + +wgsl! { + struct VertexInput { + @location(0) + position: vec2, + @location(1) + z_and_color: u32, + @location(2) + character: u32, + } + + struct VertexOutput { + @builtin(position) + clip_position: vec4, + @location(0) + character: u32, + @location(1) + color: vec4, + @location(2) + uv: vec2 + } + + @vertex + fn vs_main(input: VertexInput, @builtin(vertex_index) index: u32) -> VertexOutput { + var output: VertexOutput; + output.clip_position = vec4(input.position, f32(input.z_and_color & 0xFFu) / 256.0, 1.0); + output.character = input.character; + let raw_color = input.z_and_color >> 8u; + var color: vec4 = vec4(f32((raw_color >> 16u) & 0xFFu) / 255.0, f32((raw_color >> 8u) & 0xFFu) / 255.0, f32((raw_color) & 0xFFu) / 255.0, 1.0); + color += 0.055; + color /= 1.055; + output.color = vec4( + pow(color.x, 2.4), + pow(color.y, 2.4), + pow(color.z, 2.4), + pow(color.w, 2.4), + ); + switch (index % 4u) { + case 0u: { + output.uv = vec2(1.0, 0.0); + } + case 1u: { + output.uv = vec2(0.0, 0.0); + } + case 2u: { + output.uv = vec2(0.0,1.0); + } + default: { + output.uv = vec2(1.0, 1.0); + } + } + return output; + } + + @group(0) + @binding(0) + var buf: array; + + @fragment + fn fs_main(output: VertexOutput) -> @location(0) vec4 { + let x = u32(output.uv[0] * 16.0); + let y = u32(output.uv[1] * 16.0); + let offset = output.character * 32u; + let index = offset + y * 2u + x / 8u; + let a = (((buf[index / 4u] >> ((index % 4u) * 8u)) & 0xFFu) >> (7u - x % 8u)) & 1u; + if (a == 0u) { + discard; + } else { + return output.color; + } + } +} diff --git a/src/text_shader.wgsl b/src/text_shader.wgsl deleted file mode 100644 index b0bcffe..0000000 --- a/src/text_shader.wgsl +++ /dev/null @@ -1,69 +0,0 @@ -struct VertexInput { - @location(0) - position: vec2, - @location(1) - z_and_color: u32, - @location(2) - character: u32, -} - -struct VertexOutput { - @builtin(position) - clip_position: vec4, - @location(0) - character: u32, - @location(1) - color: vec4, - @location(2) - uv: vec2 -} - -@vertex -fn vs_main(input: VertexInput, @builtin(vertex_index) index: u32) -> VertexOutput { - var output: VertexOutput; - output.clip_position = vec4(input.position, f32(input.z_and_color & 0xFFu) / 256.0, 1.0); - output.character = input.character; - let raw_color = input.z_and_color >> 8u; - var color: vec4 = vec4(f32((raw_color >> 16u) & 0xFFu) / 255.0, f32((raw_color >> 8u) & 0xFFu) / 255.0, f32((raw_color) & 0xFFu) / 255.0, 1.0); - color += 0.055; - color /= 1.055; - output.color = vec4( - pow(color.x, 2.4), - pow(color.y, 2.4), - pow(color.z, 2.4), - pow(color.w, 2.4), - ); - switch (index % 4u) { - case 0u: { - output.uv = vec2(1.0, 0.0); - } - case 1u: { - output.uv = vec2(0.0, 0.0); - } - case 2u: { - output.uv = vec2(0.0,1.0); - } - default: { - output.uv = vec2(1.0, 1.0); - } - } - return output; -} - -@group(0) -@binding(0) -var buf: array; - -@fragment -fn fs_main(output: VertexOutput) -> @location(0) vec4 { - let x = u32(output.uv[0] * 16.0); - let y = u32(output.uv[1] * 16.0); - let offset = output.character * 32u; - let index = offset + y * 2u + x / 8u; - let a = (((buf[index / 4u] >> ((index % 4u) * 8u)) & 0xFFu) >> (7u - x % 8u)) & 1u; - if (a == 0u) { - discard; - } else { - return output.color; - } -} \ No newline at end of file diff --git a/src/tree_travel.rs b/src/tree_travel.rs index 78a90ed..72f78e5 100644 --- a/src/tree_travel.rs +++ b/src/tree_travel.rs @@ -1,10 +1,7 @@ use compact_str::{CompactString, ToCompactString}; use std::iter::Peekable; -use crate::elements::chunk::{NbtChunk, NbtRegion}; -use crate::elements::compound::NbtCompound; -use crate::elements::element_type::{NbtByteArray, NbtElement, NbtIntArray, NbtLongArray}; -use crate::elements::list::NbtList; +use crate::elements::element::{NbtByteArray, NbtElement, NbtIntArray, NbtLongArray, NbtPatternMut}; use crate::{panic_unchecked, OptionExt, Position}; /// Navigates a tree from the given route of an indices list, will cause UB on invalid lists. Typically used for operations which are saved and not direct clicking interaction. @@ -12,7 +9,7 @@ use crate::{panic_unchecked, OptionExt, Position}; pub struct Navigate<'a, I: IntoIterator + ExactSizeIterator> { iter: Peekable, at_head: bool, - node: Option<(usize, Option<&'a str>, &'a mut NbtElement)>, + node: Option<(usize, Option, &'a mut NbtElement)>, line_number: usize, } @@ -29,21 +26,38 @@ impl<'a, I: Iterator + ExactSizeIterator> Navigate<'a, I> { fn step(&mut self) -> Option<()> { let (_, _, node) = self.node.take()?; let idx = self.iter.next()?; - let (key, node) = unsafe { - if node.id() == NbtCompound::ID { - for (_, child) in node.data.compound.children().take(idx) { - self.line_number += child.true_height(); + let (key, node) = { + match node.as_pattern_mut() { + NbtPatternMut::Compound(compound) => { + self.line_number += 1 + compound.children().take(idx).map(|(_, b)| b).map(NbtElement::true_height).sum::(); + compound.get_mut(idx).map(|(a, b)| (Some(a.to_compact_string()), b)) } - self.line_number += 1; - node.data.compound.get_mut(idx).map(|(a, b)| (Some(a), b)) - } else { - for child in node.children().unwrap_unchecked().unwrap_unchecked().take(idx) { - self.line_number += child.true_height(); + NbtPatternMut::List(list) => { + self.line_number += 1 + list.children().take(idx).map(NbtElement::true_height).sum::(); + list.get_mut(idx).map(|b| (None, b)) } - self.line_number += 1; - node.get_mut(idx).map(|x| (None, x)) - } - .panic_unchecked("Invalid index in indices iterator") + NbtPatternMut::ByteArray(array) => { + self.line_number += 1 + idx; + array.get_mut(idx).map(|b| (None, b)) + } + NbtPatternMut::IntArray(array) => { + self.line_number += 1 + idx; + array.get_mut(idx).map(|b| (None, b)) + } + NbtPatternMut::LongArray(array) => { + self.line_number += 1 + idx; + array.get_mut(idx).map(|b| (None, b)) + } + NbtPatternMut::Region(region) => { + self.line_number += 1 + region.children().take(idx).map(NbtElement::true_height).sum::(); + region.get_mut(idx).map(|b| (None, b)) + } + NbtPatternMut::Chunk(chunk) => { + self.line_number += 1 + chunk.children().take(idx).map(|(_, b)| b).map(NbtElement::true_height).sum::(); + chunk.get_mut(idx).map(|(a, b)| (Some(a.to_compact_string()), b)) + } + _ => None, + }? }; self.node = Some((idx, key, node)); Some(()) @@ -68,11 +82,11 @@ impl<'a, I: Iterator + ExactSizeIterator> Navigate<'a, I> { let (idx, key, element) = self.node.as_mut()?; - Some((position, *idx, *key, *element, self.line_number)) + Some((position, *idx, key.as_deref(), *element, self.line_number)) } #[must_use] - pub fn last(mut self) -> (usize, Option<&'a str>, &'a mut NbtElement, usize) { + pub fn last(mut self) -> (usize, Option, &'a mut NbtElement, usize) { while self.iter.peek().is_some() { self.step(); } @@ -105,82 +119,89 @@ impl<'a> Traverse<'a> { fn step(&mut self) -> Option<()> { let (_, _, node) = self.node.take()?; - let (idx, key, new) = unsafe { - 'm: { - match node.id() { - NbtRegion::ID => { - self.y -= 1; - for (idx, value) in node.data.region.children_mut().enumerate() { - let height = value.height(); - if self.y >= height { - self.line_number += value.true_height(); - self.y -= height; - continue; - } else { - self.cut = self.y == 0; - self.line_number += 1; - break 'm (idx, Some(value.data.chunk.z.to_compact_string()), value); - } + let (idx, key, new) = 'm: { + match node.as_pattern_mut() { + NbtPatternMut::Region(region) => { + self.y -= 1; + for (idx, value) in region.children_mut().enumerate() { + let height = value.height(); + if self.y >= height { + self.line_number += value.true_height(); + self.y -= height; + continue; + } else { + self.cut = self.y == 0; + self.line_number += 1; + break 'm (idx, None, value); } - panic_unchecked("Expected element to contain next element") - } - NbtByteArray::ID | NbtIntArray::ID | NbtLongArray::ID => { - self.cut = true; - let idx = core::mem::replace(&mut self.y, 0) - 1; - self.line_number += idx + 1; - (idx, None, node.data.byte_array.get_mut(idx).panic_unchecked("Expected element to contain next element")) } - NbtList::ID => { - self.y -= 1; - for (idx, value) in node.data.list.children_mut().enumerate() { - let height = value.height(); - if self.y >= height { - self.line_number += value.true_height(); - self.y -= height; - continue; - } else { - self.cut = self.y == 0; - self.line_number += 1; - break 'm (idx, None, value); - } + } + NbtPatternMut::Chunk(chunk) => { + self.y -= 1; + for (idx, (key, value)) in chunk.children_mut().enumerate() { + let height = value.height(); + if self.y >= height { + self.line_number += value.true_height(); + self.y -= height; + continue; + } else { + self.cut = self.y == 0; + self.line_number += 1; + break 'm (idx, Some(key.to_compact_string()), value); } - panic_unchecked("Expected element to contain next element") } - NbtCompound::ID => { - self.y -= 1; - for (idx, (key, value)) in node.data.compound.children_mut().enumerate() { - let height = value.height(); - if self.y >= height { - self.line_number += value.true_height(); - self.y -= height; - continue; - } else { - self.cut = self.y == 0; - self.line_number += 1; - break 'm (idx, Some(key.to_compact_string()), value); - } + } + NbtPatternMut::Compound(compound) => { + self.y -= 1; + for (idx, (key, value)) in compound.children_mut().enumerate() { + let height = value.height(); + if self.y >= height { + self.line_number += value.true_height(); + self.y -= height; + continue; + } else { + self.cut = self.y == 0; + self.line_number += 1; + break 'm (idx, Some(key.to_compact_string()), value); } - panic_unchecked("Expected element to contain next element") } - NbtChunk::ID => { - self.y -= 1; - for (idx, (key, value)) in node.data.chunk.inner.children_mut().enumerate() { - let height = value.height(); - if self.y >= height { - self.line_number += value.true_height(); - self.y -= height; - continue; - } else { - self.cut = self.y == 0; - self.line_number += 1; - break 'm (idx, Some(key.into()), value); - } + } + NbtPatternMut::List(list) => { + self.y -= 1; + for (idx, value) in list.children_mut().enumerate() { + let height = value.height(); + if self.y >= height { + self.line_number += value.true_height(); + self.y -= height; + continue; + } else { + self.cut = self.y == 0; + self.line_number += 1; + break 'm (idx, None, value); } - panic_unchecked("Expected element to contain next element") } - _ => panic_unchecked("Invalid type for tree traversal"), } + NbtPatternMut::ByteArray(array) => { + self.cut = true; + let idx = core::mem::replace(&mut self.y, 0) - 1; + self.line_number += idx + 1; + break 'm (idx, None, array.get_mut(idx)?); + } + NbtPatternMut::IntArray(array) => { + self.cut = true; + let idx = core::mem::replace(&mut self.y, 0) - 1; + self.line_number += idx + 1; + break 'm (idx, None, array.get_mut(idx)?); + } + NbtPatternMut::LongArray(array) => { + self.cut = true; + let idx = core::mem::replace(&mut self.y, 0) - 1; + self.line_number += idx + 1; + break 'm (idx, None, array.get_mut(idx)?); + } + _ => return None, } + unsafe { panic_unchecked("could not find value in any (skipped everything somehow)") } }; self.node = Some((idx, key, new)); @@ -188,34 +209,6 @@ impl<'a> Traverse<'a> { Some(()) } - #[allow(clippy::should_implement_trait)] // no - pub fn next(&mut self) -> Option<(Position, usize, Option, &mut NbtElement, usize)> { - if self.cut { - if self.head { - self.head = false; - return self.node.as_mut().map(|(idx, key, value)| (Position::Only, *idx, key.clone(), &mut **value, self.line_number)); - } - return None; - } - - let head = core::mem::replace(&mut self.head, false); - - if !head { - self.step(); - } - - let tail = self.cut; - let position = match (head, tail) { - (true, false) => Position::First, - (false, false) => Position::Middle, - (false, true) => Position::Last, - (true, true) => Position::Only, - }; - - let (idx, key, element) = self.node.as_mut()?; - Some((position, *idx, key.clone(), *element, self.line_number)) - } - #[must_use] pub fn last(mut self) -> Option<(usize, Option, &'a mut NbtElement, usize)> { if self.cut && !self.head { @@ -278,80 +271,80 @@ impl<'a> TraverseParents<'a> { } fn step(&mut self) -> Option<()> { - self.node = Some(unsafe { - 'm: { - let node = self.node.take()?; - match node.id() { - NbtRegion::ID => { - self.y -= 1; - for value in node.data.region.children_mut() { - let height = value.height(); - if self.y >= height { - self.y -= height; - self.line_number += value.true_height(); - continue; - } else { - self.line_number += 1; - break 'm value; - } + self.node = Some('m: { + let node = self.node.take()?; + match node.as_pattern_mut() { + NbtPatternMut::Region(region) => { + self.y -= 1; + for value in region.children_mut() { + let height = value.height(); + if self.y >= height { + self.y -= height; + self.line_number += value.true_height(); + continue; + } else { + self.line_number += 1; + break 'm value; } - panic_unchecked("Expected element to contain next element") - } - NbtByteArray::ID | NbtIntArray::ID | NbtLongArray::ID => { - self.line_number += self.y; - node.data - .byte_array - .get_mut(core::mem::replace(&mut self.y, 0) - 1) - .panic_unchecked("Expected element to contain next element") } - NbtList::ID => { - self.y -= 1; - for value in node.data.list.children_mut() { - let height = value.height(); - if self.y >= height { - self.y -= height; - self.line_number += value.true_height(); - continue; - } else { - self.line_number += 1; - break 'm value; - } + } + NbtPatternMut::List(list) => { + self.y -= 1; + for value in list.children_mut() { + let height = value.height(); + if self.y >= height { + self.y -= height; + self.line_number += value.true_height(); + continue; + } else { + self.line_number += 1; + break 'm value; } - panic_unchecked("Expected element to contain next element") } - NbtCompound::ID => { - self.y -= 1; - for (_, value) in node.data.compound.children_mut() { - let height = value.height(); - if self.y >= height { - self.y -= height; - self.line_number += value.true_height(); - continue; - } else { - self.line_number += 1; - break 'm value; - } + } + NbtPatternMut::Compound(compound) => { + self.y -= 1; + for (_, value) in compound.children_mut() { + let height = value.height(); + if self.y >= height { + self.y -= height; + self.line_number += value.true_height(); + continue; + } else { + self.line_number += 1; + break 'm value; } - panic_unchecked("Expected element to contain next element") } - NbtChunk::ID => { - self.y -= 1; - for (_, value) in node.data.chunk.inner.children_mut() { - let height = value.height(); - if self.y >= height { - self.y -= height; - self.line_number += value.true_height(); - continue; - } else { - self.line_number += 1; - break 'm value; - } + } + NbtPatternMut::Chunk(chunk) => { + self.y -= 1; + for (_, value) in chunk.children_mut() { + let height = value.height(); + if self.y >= height { + self.y -= height; + self.line_number += value.true_height(); + continue; + } else { + self.line_number += 1; + break 'm value; } - panic_unchecked("Expected element to contain next element") } - _ => panic_unchecked("Invalid type for tree traversal"), } + NbtPatternMut::ByteArray(array) => { + self.line_number += self.y; + break 'm array.get_mut(core::mem::replace(&mut self.y, 0) - 1)?; + } + NbtPatternMut::IntArray(array) => { + self.line_number += self.y; + break 'm array.get_mut(core::mem::replace(&mut self.y, 0) - 1)?; + } + NbtPatternMut::LongArray(array) => { + self.line_number += self.y; + break 'm array.get_mut(core::mem::replace(&mut self.y, 0) - 1)?; + } + _ => return None, } + unsafe { panic_unchecked("Skipped everything in TraverseParents (somehow)") } }); Some(()) @@ -359,60 +352,50 @@ impl<'a> TraverseParents<'a> { fn extras(&self) -> (usize, Option, bool) { let node = unsafe { self.node.as_ref().panic_unchecked("Expected a value for parent element") }; - unsafe { - match node.id() { - NbtRegion::ID => { - let mut remaining_y = self.y - 1; - for (idx, element) in node.data.region.children().enumerate() { - if remaining_y >= element.height() { - remaining_y -= element.height(); - continue; - } else { - return (idx, Some(element.as_chunk_unchecked().x.to_compact_string()), remaining_y == 0); - } - } - panic_unchecked("Expected parent element to contain next element") + if let Some(region) = node.as_region() { + let mut remaining_y = self.y - 1; + for (idx, element) in region.children().enumerate() { + if remaining_y >= element.height() { + remaining_y -= element.height(); + continue; + } else { + return (idx, None, remaining_y == 0); } - NbtByteArray::ID | NbtIntArray::ID | NbtLongArray::ID => (self.y - 1, None, true), - NbtCompound::ID => { - let mut remaining_y = self.y - 1; - for (idx, (key, element)) in node.data.compound.children().enumerate() { - if remaining_y >= element.height() { - remaining_y -= element.height(); - continue; - } else { - return (idx, Some(key.into()), remaining_y == 0); - } - } - panic_unchecked("Expected parent element to contain next element") + } + } else if let Some(list) = node.as_list() { + let mut remaining_y = self.y - 1; + for (idx, element) in list.children().enumerate() { + if remaining_y >= element.height() { + remaining_y -= element.height(); + continue; + } else { + return (idx, None, remaining_y == 0); } - NbtChunk::ID => { - let mut remaining_y = self.y - 1; - for (idx, (key, element)) in node.data.chunk.inner.children().enumerate() { - if remaining_y >= element.height() { - remaining_y -= element.height(); - continue; - } else { - return (idx, Some(key.into()), remaining_y == 0); - } - } - panic_unchecked("Expected parent element to contain next element") + } + } else if let Some(chunk) = node.as_chunk() { + let mut remaining_y = self.y - 1; + for (idx, (key, element)) in chunk.children().enumerate() { + if remaining_y >= element.height() { + remaining_y -= element.height(); + continue; + } else { + return (idx, Some(key.to_compact_string()), remaining_y == 0); } - NbtList::ID => { - let mut remaining_y = self.y - 1; - for (idx, element) in node.data.list.children().enumerate() { - if remaining_y >= element.height() { - remaining_y -= element.height(); - continue; - } else { - return (idx, None, remaining_y == 0); - } - } - panic_unchecked("Expected parent element to contain next element") + } + } else if let Some(compound) = node.as_compound() { + let mut remaining_y = self.y - 1; + for (idx, (key, element)) in compound.children().enumerate() { + if remaining_y >= element.height() { + remaining_y -= element.height(); + continue; + } else { + return (idx, Some(key.to_compact_string()), remaining_y == 0); } - _ => panic_unchecked("Expected parent element to be complex"), } + } else if let NbtByteArray::ID | NbtIntArray::ID | NbtLongArray::ID = node.id() { + return (self.y - 1, None, true); } + unsafe { panic_unchecked("Expected parent element to be complex") } } #[allow(clippy::should_implement_trait)] // no diff --git a/src/vertex_buffer_builder.rs b/src/vertex_buffer_builder.rs index 2eaad08..69e0178 100644 --- a/src/vertex_buffer_builder.rs +++ b/src/vertex_buffer_builder.rs @@ -2,7 +2,7 @@ use std::ops::BitAnd; use winit::dpi::PhysicalSize; -use crate::assets::*; +use crate::assets::{BASE_TEXT_Z, BASE_Z, TOOLTIP_UV, TOOLTIP_Z}; use crate::StrExt; pub struct VertexBufferBuilder { diff --git a/src/window.rs b/src/window.rs index 9a4ce53..262920a 100644 --- a/src/window.rs +++ b/src/window.rs @@ -1,3 +1,4 @@ +use std::borrow::Cow; use std::num::NonZeroU64; use std::time::SystemTime; @@ -13,7 +14,7 @@ use winit::platform::windows::WindowBuilderExtWindows; use winit::window::{Icon, Window, WindowBuilder}; use zune_inflate::DeflateOptions; -use crate::assets::*; +use crate::assets::HEADER_SIZE; use crate::vertex_buffer_builder::VertexBufferBuilder; use crate::workbench::Workbench; use crate::{assets, OptionExt}; @@ -26,14 +27,14 @@ pub fn run() -> ! { let window = WindowBuilder::new() .with_title("NBT Workbench") .with_transparent(std::env::args().any(|x| x.eq("--transparent"))) - .with_inner_size(PhysicalSize::new(250, WINDOW_HEIGHT as u32)) - .with_min_inner_size(PhysicalSize::new(WINDOW_WIDTH as u32, (HEADER_SIZE + 16) as u32)) + .with_inner_size(PhysicalSize::new(WINDOW_WIDTH as u32, WINDOW_HEIGHT as u32)) + .with_min_inner_size(PhysicalSize::new(520, (HEADER_SIZE + 16) as u32)) .with_window_icon(Some(Icon::from_rgba(assets::icon(), assets::ICON_WIDTH as u32, assets::ICON_HEIGHT as u32).expect("valid format"))) .with_drag_and_drop(true) .build(&event_loop) .expect("Window was constructable"); let mut state = State::new(&window); - let mut workbench = Workbench::new(&window); + let mut workbench = Workbench::new(|title| window.set_title(title)); event_loop.run(move |event, _, _| match event { Event::RedrawRequested(window_id) if window_id == window.id() => match state.render(&mut workbench, &window) { @@ -195,7 +196,10 @@ impl State { ], label: Some("Diffuse Bind Group"), }); - let shader = device.create_shader_module(include_wgsl!("shader.wgsl")); + let shader = device.create_shader_module(ShaderModuleDescriptor { + label: Some("Shader"), + source: ShaderSource::Wgsl(Cow::Borrowed(crate::shader::SOURCE)), + }); let render_pipeline_layout = device.create_pipeline_layout(&PipelineLayoutDescriptor { label: Some("Render Pipeline Layout"), bind_group_layouts: &[&texture_bind_group_layout], @@ -288,7 +292,10 @@ impl State { }), }], }); - let text_shader = device.create_shader_module(include_wgsl!("text_shader.wgsl")); + let text_shader = device.create_shader_module(ShaderModuleDescriptor { + label: Some("Text Shader"), + source: ShaderSource::Wgsl(Cow::Borrowed(crate::text_shader::SOURCE)), + }); let text_render_pipeline_payout = device.create_pipeline_layout(&PipelineLayoutDescriptor { label: Some("Text Render Pipeline Layout"), bind_group_layouts: &[&unicode_bind_group_layout], @@ -388,18 +395,18 @@ impl State { WindowEvent::Moved(_) => false, WindowEvent::CloseRequested => false, WindowEvent::Destroyed => false, - WindowEvent::DroppedFile(file) => workbench.on_open_file(file, window).is_some(), + WindowEvent::DroppedFile(file) => workbench.on_open_file(file, |title| window.set_title(title)).is_some(), WindowEvent::HoveredFile(_) => false, WindowEvent::HoveredFileCancelled => false, WindowEvent::ReceivedCharacter(_) => false, WindowEvent::Focused(_) => false, - WindowEvent::KeyboardInput { input, .. } => workbench.on_key_input(*input, window), + WindowEvent::KeyboardInput { input, .. } => workbench.on_key_input(*input, |title| window.set_title(title)), WindowEvent::ModifiersChanged(_) => false, WindowEvent::CursorMoved { position, .. } => workbench.on_mouse_move(*position), WindowEvent::CursorEntered { .. } => false, WindowEvent::CursorLeft { .. } => false, WindowEvent::MouseWheel { delta, .. } => workbench.on_scroll(*delta), - WindowEvent::MouseInput { state, button, .. } => workbench.on_mouse_input(*state, *button, window), + WindowEvent::MouseInput { state, button, .. } => workbench.on_mouse_input(*state, *button, |title| window.set_title(title)), WindowEvent::TouchpadPressure { .. } => false, WindowEvent::AxisMotion { .. } => false, WindowEvent::Touch(_) => false, diff --git a/src/workbench.rs b/src/workbench.rs index 80e4624..7cb82f0 100644 --- a/src/workbench.rs +++ b/src/workbench.rs @@ -14,14 +14,16 @@ use fxhash::{FxBuildHasher, FxHashSet}; use uuid::Uuid; use winit::dpi::PhysicalPosition; use winit::event::{ElementState, KeyboardInput, MouseButton, MouseScrollDelta, VirtualKeyCode}; -use winit::window::Window; use zune_inflate::DeflateDecoder; -use crate::assets::*; +use crate::assets::{ + ACTION_WHEEL_Z, BASE_TEXT_Z, BASE_Z, DARK_STRIPE_UV, EDITED_UV, HEADER_SIZE, HELD_ENTRY_Z, HORIZONTAL_SEPARATOR_UV, HOVERED_STRIPE_UV, HOVERED_WIDGET_UV, JUST_OVERLAPPING_BASE_TEXT_Z, + LIGHT_STRIPE_UV, SELECTED_ACTION_WHEEL, SELECTED_WIDGET_UV, TRAY_UV, UNEDITED_UV, UNSELECTED_ACTION_WHEEL, UNSELECTED_WIDGET_UV, +}; use crate::elements::chunk::{NbtChunk, NbtRegion}; use crate::elements::compound::NbtCompound; -use crate::elements::element_type::NbtElement; -use crate::elements::element_type::{NbtByte, NbtByteArray, NbtDouble, NbtFloat, NbtInt, NbtIntArray, NbtLong, NbtLongArray, NbtShort}; +use crate::elements::element::NbtElement; +use crate::elements::element::{NbtByte, NbtByteArray, NbtDouble, NbtFloat, NbtInt, NbtIntArray, NbtLong, NbtLongArray, NbtShort}; use crate::elements::list::{NbtList, ValueIterator}; use crate::elements::string::NbtString; use crate::selected_text::{KeyResult, SelectedText}; @@ -57,7 +59,7 @@ pub struct Workbench { impl Workbench { #[inline] #[must_use] - pub fn new(window: &Window) -> Self { + pub fn new(f: impl Fn(&str) + Copy) -> Self { let mut workbench = Self { tabs: vec![], tab: 0, @@ -75,7 +77,7 @@ impl Workbench { action_wheel: None, subscription: None, }; - if let Some(x) = &std::env::args().nth(1).and_then(|x| PathBuf::from_str(&x).ok()) && workbench.on_open_file(x, window).is_some() { + if let Some(x) = &std::env::args().nth(1).and_then(|x| PathBuf::from_str(&x).ok()) && workbench.on_open_file(x, f).is_some() { // yay! } else { workbench.tabs.push(Tab { @@ -100,7 +102,7 @@ impl Workbench { uuid: Uuid::new_v4(), freehand_mode: false, }); - window.set_title(if cfg!(debug_assertions) { + f(if cfg!(debug_assertions) { "test.nbt - NBT Workbench" } else { "new.nbt - NBT Workbench" @@ -111,7 +113,7 @@ impl Workbench { #[inline] #[allow(clippy::equatable_if_let)] - pub fn on_open_file(&mut self, path: &Path, window: &Window) -> Option<()> { + pub fn on_open_file(&mut self, path: &Path, f: impl Fn(&str)) -> Option<()> { let buf = read(path).ok()?; let (nbt, compressed) = { if path.extension().and_then(OsStr::to_str) == Some("mca") { @@ -138,7 +140,7 @@ impl Workbench { self.selected_text = None; }; self.tabs.push(entry); - self.set_tab(self.tabs.len() - 1, window); + self.set_tab(self.tabs.len() - 1, f); Some(()) } } @@ -182,7 +184,7 @@ impl Workbench { #[inline] #[allow(clippy::collapsible_if)] - pub fn on_mouse_input(&mut self, state: ElementState, button: MouseButton, window: &Window) -> bool { + pub fn on_mouse_input(&mut self, state: ElementState, button: MouseButton, f: impl Fn(&str)) -> bool { let horizontal_scroll = self.horizontal_scroll(); let shift = self.held_keys.contains(&VirtualKeyCode::LShift) | self.held_keys.contains(&VirtualKeyCode::RShift); let x = self.mouse_x; @@ -194,7 +196,7 @@ impl Workbench { self.scrollbar_offset = None; self.held_mouse_keys.remove(&button); if y < 19 && x > 2 && y > 3 { - self.click_tab(button, window); + self.click_tab(button, f); } else if y >= HEADER_SIZE { let left_margin = self.left_margin(); 'a: { @@ -355,39 +357,41 @@ impl Workbench { let (_, _, parent, mut line_number) = Navigate::new(rest.iter().copied(), &mut tab.value).last(); let mut old_key = None; if let Some(key) = key { - if parent.id() == NbtCompound::ID { - unsafe { - old_key = parent.data.compound.update_key(last, key); - } - } else if parent.id() == NbtChunk::ID { - unsafe { - old_key = parent.data.chunk.inner.update_key(last, key); - } - } else if parent.id() == NbtRegion::ID { - unsafe { - old_key = Some({ - let Some((x, z)) = key.split_once('|') else { - return; - }; - let Ok(x @ 0..=31) = x.trim_end().parse() else { - return; - }; - let Ok(z @ 0..=31) = z.trim_start().parse() else { - return; - }; - let pos = ((x as u16) << 5) | z as u16; - let (_, chunks) = &*parent.data.region.chunks; - if !chunks[pos as usize].is_null() { - return; - } - let mut element = parent.data.region.remove(last); - let old_key = format_compact!("{}|{}", element.as_chunk_unchecked().x, element.as_chunk_unchecked().z); + if let Some(compound) = parent.as_compound_mut() { + old_key = compound.update_key(last, key); + } else if let Some(chunk) = parent.as_chunk_mut() { + old_key = chunk.update_key(last, key); + } else if let Some(region) = parent.as_region_mut() { + old_key = Some({ + let Some((x, z)) = key.split_once('|') else { + return; + }; + let Ok(x @ 0..=31) = x.trim_end().parse() else { + return; + }; + let Ok(z @ 0..=31) = z.trim_start().parse() else { + return; + }; + let pos = ((x as u16) << 5) | z as u16; + let (_, chunks) = &*region.chunks; + if !chunks[pos as usize].is_null() { + return; + } + let mut element = region.remove(last); + let old_key = format_compact!("{}|{}", unsafe { element.as_chunk_unchecked().x }, unsafe { element.as_chunk_unchecked().z }); + // all values inside a region are chunks + unsafe { element.as_chunk_unchecked_mut().x = x; + } + unsafe { element.as_chunk_unchecked_mut().z = z; - parent.data.region.insert_unchecked(last, element); - old_key - }); - } + } + // chunk is asserted above + unsafe { + region.insert_unchecked(pos as usize, last, element); + } + old_key + }); } } let (diff, true_diff) = (value.height(), value.true_height()); @@ -433,10 +437,10 @@ impl Workbench { let (&last, rest) = unsafe { subscription.indices.split_last().panic_unchecked("always has at least one element") }; let (_, _, parent, mut line_number) = Navigate::new(rest.iter().copied(), &mut tab.value).last(); let old_key; - if parent.id() == NbtCompound::ID { - old_key = unsafe { Some(parent.data.compound.get_mut(last).panic_unchecked("valid index").0.to_compact_string()) }; - } else if parent.id() == NbtChunk::ID { - old_key = unsafe { Some(parent.data.chunk.inner.get_mut(last).panic_unchecked("valid index").0.to_compact_string()) }; + if let Some(compound) = parent.as_compound_mut() { + old_key = unsafe { Some(compound.get_mut(last).panic_unchecked("valid index").0.to_compact_string()) }; + } else if let Some(chunk) = parent.as_chunk_mut() { + old_key = unsafe { Some(chunk.get_mut(last).panic_unchecked("valid index").0.to_compact_string()) }; } else { old_key = None; } @@ -614,16 +618,11 @@ impl Workbench { Position::Only | Position::Last => { indices.push(idx + 1); let duplicate = unsafe { element.get(idx).panic_unchecked("it exists mate, let's stop playing around") }.clone(); - match element.id() { - NbtCompound::ID => unsafe { - element - .data - .compound - .insert(idx + 1, key.panic_unchecked("it's a compound, it **has** a key for every value"), duplicate); - }, - // this is the same thing, literally the same thing, i refuse to believe that it can't be added - _ => { - let _ = element.insert(idx + 1, duplicate); + if let Some(compound) = element.as_compound_mut() { + compound.insert(idx + 1, unsafe { key.panic_unchecked("it's a compound, it **has** a key for every value") }, duplicate); + } else { + if element.insert(idx + 1, duplicate).is_err() { + return false; } } } @@ -674,7 +673,7 @@ impl Workbench { return false; } } else { - if write!(&mut buf, "{}{}{element}", key.unwrap_or(CompactString::new_inline("")), if key_exists { ": " } else { "" }).is_err() { + if write!(&mut buf, "{}{}{element}", key.unwrap_or(CompactString::new_inline("")), if key_exists { ":" } else { "" }).is_err() { return false; } } @@ -852,7 +851,7 @@ impl Workbench { } #[inline] - fn click_tab(&mut self, button: MouseButton, window: &Window) { + fn click_tab(&mut self, button: MouseButton, f: impl Fn(&str)) { let mouse_x = self.mouse_x + self.tab_scroll; if mouse_x < 2 { return; @@ -866,7 +865,7 @@ impl Workbench { if button == MouseButton::Middle { let tab = self.tabs.remove(idx); std::thread::spawn(move || drop(tab)); - self.set_tab(idx.saturating_sub(1), window); + self.set_tab(idx.saturating_sub(1), f); } else if idx == self.tab && x > width - 16 && x < width { if button == MouseButton::Left { tab.compression = tab.compression.cycle(); @@ -876,7 +875,7 @@ impl Workbench { } else if idx == self.tab && x + 1 >= width - 32 && x < width - 16 { tab.save(); } else if button == MouseButton::Left { - self.set_tab(idx, window); + self.set_tab(idx, f); } return; @@ -964,26 +963,21 @@ impl Workbench { } let mut indices = vec![]; - let mut iter = Traverse::new(y, &mut tab.value); + let mut iter = TraverseParents::new(y, &mut tab.value); while let Some((position, idx, key, value, _)) = iter.next() { - match position { - Position::First | Position::Only => {} // do nothing - Position::Middle => { - indices.push(idx); - } - Position::Last => { - indices.push(idx); - self.selected_text = SelectedText::new( - indices.len() * 16 + 32 + 4 + left_margin, - self.mouse_x + horizontal_scroll, - y * 16 + HEADER_SIZE, - key.map(|x| (x.into_string().into_boxed_str(), true)), - Some(value.value()).map(|(a, b)| (a.into_string().into_boxed_str(), b)), - value.id() == NbtChunk::ID, - indices, - ); - return self.selected_text.is_some(); - } + indices.push(idx); + if let Position::Last | Position::Only = position { + let child = unsafe { value.get(idx).panic_unchecked("Child didn't exist somehow") }; + self.selected_text = SelectedText::new( + indices.len() * 16 + 32 + 4 + left_margin, + self.mouse_x + horizontal_scroll, + y * 16 + HEADER_SIZE, + key.or_else(|| child.as_chunk().map(|chunk| chunk.x.to_compact_string())).map(|x| (x.into_string().into_boxed_str(), true)), + Some(child.value()).map(|(a, b)| (a.into_string().into_boxed_str(), b)), + child.id() == NbtChunk::ID, + indices, + ); + return self.selected_text.is_some(); } } false @@ -1076,10 +1070,9 @@ impl Workbench { if child_idx == 0 || parent.len().is_none_or(|x| x == 0) { return; } - let original_key = match parent.id() { - NbtCompound::ID => unsafe { Some(parent.data.compound.get(child_idx).panic_unchecked("Index obviously exists").0.to_compact_string()) }, - _ => None, - }; + let original_key = parent + .as_compound_mut() + .map(|compound| unsafe { compound.get(child_idx).panic_unchecked("Index obviously exists").0.to_compact_string() }); *y -= unsafe { parent.get(child_idx - 1).panic_unchecked("if i exist, the one before me exists") }.height() * 16; parent.swap(child_idx, child_idx - 1); let from = indices.clone(); @@ -1113,10 +1106,9 @@ impl Workbench { if parent.len().is_none_or(|x| x == child_idx + 1) { return; } - let original_key = match parent.id() { - NbtCompound::ID => unsafe { Some(parent.data.compound.get(child_idx).panic_unchecked("Index obviously exists").0.to_compact_string()) }, - _ => None, - }; + let original_key = parent + .as_compound_mut() + .map(|compound| unsafe { compound.get(child_idx).panic_unchecked("Index obviously exists").0.to_compact_string() }); *y += unsafe { parent.get(child_idx + 1).panic_unchecked("checked above") }.height() * 16; parent.swap(child_idx, child_idx + 1); let from = indices.clone(); @@ -1178,21 +1170,25 @@ impl Workbench { let (k, v, indices, new_y) = if ctrl && last_index > 0 { let mut indices = indices.into_vec(); - let tail = Navigate::new(indices.iter().copied().take(original_indices_len - 1), &mut tab.value).last().2; *indices.last_mut().panic_unchecked("indices cannot be empty, we're in it") = 0; - let (k, v) = tail.children().panic_unchecked("we are a child, some must exist").map_or_else( - |mut iter| { - let (k, v) = iter.next().panic_unchecked("we are a child, some must exist"); - (Some((k.to_compact_string(), true)), Some(v.value())) - }, - |iter| match iter { - iter @ ValueIterator::Generic(_) => (None, Some(iter.enumerate().next().panic_unchecked("we're the child, it can't be empty").1.value())), - iter @ ValueIterator::Region(_, _) => { - let chunk = unsafe { iter.enumerate().next().panic_unchecked("we're the child, it can't be empty").1.as_chunk_unchecked() }; - (Some((chunk.x.to_compact_string(), true)), Some((chunk.z.to_compact_string(), true))) - } - }, - ); + let (k, v) = Navigate::new(indices.iter().copied().take(original_indices_len - 1), &mut tab.value) + .last() + .2 + .children() + .panic_unchecked("we are a child, some must exist") + .map_or_else( + |mut iter| { + let (k, v) = iter.next().panic_unchecked("we are a child, some must exist"); + (Some((k.to_compact_string(), true)), Some(v.value())) + }, + |iter| match iter { + iter @ ValueIterator::Generic(_) => (None, Some(iter.enumerate().next().panic_unchecked("we're the child, it can't be empty").1.value())), + iter @ ValueIterator::Region(_, _) => { + let chunk = unsafe { iter.enumerate().next().panic_unchecked("we're the child, it can't be empty").1.as_chunk_unchecked() }; + (Some((chunk.x.to_compact_string(), true)), Some((chunk.z.to_compact_string(), true))) + } + }, + ); let new_y = sum_indices(indices.iter().copied(), &tab.value) * 16; (k, v, indices, new_y) } else { @@ -1200,12 +1196,21 @@ impl Workbench { let mut indices = vec![]; // SAFETY: total is -1'd meaning that it's original range of 1..=root.height() is now ..root.height(), which is in range let (k, v) = 'w: { - let mut iter = Traverse::new(total, &mut tab.value); - let _ = iter.next(); + let mut iter = TraverseParents::new(total, &mut tab.value); while let Some((position, idx, key, value, _)) = iter.next() { indices.push(idx); - if position == Position::Last { - break 'w (key.map(|k| (k, true)), Some(value.value())); + if let Position::Last | Position::Only = position { + break 'w ( + key.or_else(|| { + value + .as_region() + .and_then(|region| region.get(idx)) + .and_then(NbtElement::as_chunk) + .map(|chunk| chunk.x.to_compact_string()) + }) + .map(|k| (k, true)), + value.get(idx).map(NbtElement::value), + ); } } panic_unchecked("Iterator was empty, somehow") @@ -1308,7 +1313,14 @@ impl Workbench { indices.push(idx); if let Position::Last | Position::Only = position { break 'w ( - key.map(|k| (k, true)), + key.or_else(|| { + value + .as_region() + .and_then(|region| region.get(idx)) + .and_then(NbtElement::as_chunk) + .map(|chunk| chunk.x.to_compact_string()) + }) + .map(|k| (k, true)), Some(value.get(idx).map(NbtElement::value).panic_unchecked("We are literally given an `idx`")), indices, y + 16 - HEADER_SIZE, @@ -1438,8 +1450,8 @@ impl Workbench { let (key, value) = { let element = Navigate::new(rem.iter().copied(), &mut tab.value).last().2; if key { - if element.id() == NbtCompound::ID { - let idx = element.data.compound.entries.idx_of(&value); + if let Some(compound) = element.as_compound_mut() { + let idx = compound.entries.idx_of(&value); if let Some(idx) = idx { return if idx == last { self.selected_text = None; @@ -1448,9 +1460,9 @@ impl Workbench { ignore_already_present_key }; } - (Some(element.data.compound.update_key(last, value.clone()).unwrap_or(value)), None) - } else if element.id() == NbtChunk::ID { - let idx = element.data.chunk.inner.entries.idx_of(&value); + (Some(compound.update_key(last, value.clone()).unwrap_or(value)), None) + } else if let Some(chunk) = element.as_chunk_mut() { + let idx = chunk.entries.idx_of(&value); if let Some(idx) = idx { return if idx == last { self.selected_text = None; @@ -1459,23 +1471,23 @@ impl Workbench { ignore_already_present_key }; } - (Some(element.data.chunk.inner.update_key(last, value.clone()).unwrap_or(value)), None) - } else if element.id() == NbtRegion::ID { + (Some(chunk.update_key(last, value.clone()).unwrap_or(value)), None) + } else if let Some(region) = element.as_region_mut() { let (Ok(x @ 0..=31), Ok(z @ 0..=31)) = (value.parse::(), valuefix.panic_unchecked("A chunk has a key and value").parse::()) else { return ignore_already_present_key; }; let (old_x, old_z) = { - let chunk = element.data.region.get_mut(last).panic_unchecked("Last index was valid").as_chunk_unchecked_mut(); + let chunk = region.get_mut(last).panic_unchecked("Last index was valid").as_chunk_unchecked_mut(); (core::mem::replace(&mut chunk.x, x), core::mem::replace(&mut chunk.z, z)) }; let new_idx = ((x as usize) << 5) | (z as usize); - if !element.data.region.chunks.deref().1[new_idx].is_null() || (old_x == x && old_z == z) { - let chunk = element.data.region.get_mut(last).panic_unchecked("Last index was valid").as_chunk_unchecked_mut(); + if !region.chunks.deref().1[new_idx].is_null() || (old_x == x && old_z == z) { + let chunk = region.get_mut(last).panic_unchecked("Last index was valid").as_chunk_unchecked_mut(); chunk.x = old_x; chunk.z = old_z; return ignore_already_present_key; } - let (map, chunks) = &mut *element.data.region.chunks; + let (map, chunks) = &mut *region.chunks; let from = core::mem::replace(&mut map[last], new_idx as u16) as usize; chunks.swap(from, new_idx); (Some(old_x.to_compact_string()), Some(old_z.to_compact_string())) @@ -1483,22 +1495,22 @@ impl Workbench { panic_unchecked("Expected key-value indices chain tail to be of type compound") } } else { - if element.id() == NbtRegion::ID { + if let Some(region) = element.as_region_mut() { let (Ok(x @ 0..=31), Ok(z @ 0..=31)) = (keyfix.panic_unchecked("A chunk always has a key and value").parse::(), value.parse::()) else { return ignore_already_present_key; }; let (old_x, old_z) = { - let chunk = element.data.region.get_mut(last).panic_unchecked("Last index was valid").as_chunk_unchecked_mut(); + let chunk = region.get_mut(last).panic_unchecked("Last index was valid").as_chunk_unchecked_mut(); (core::mem::replace(&mut chunk.x, x), core::mem::replace(&mut chunk.z, z)) }; let new_idx = ((x as usize) << 5) | (z as usize); - if !element.data.region.chunks.deref().1[new_idx].is_null() || (old_x == x && old_z == z) { - let chunk = element.data.region.get_mut(last).panic_unchecked("Last index was valid").as_chunk_unchecked_mut(); + if !region.chunks.deref().1[new_idx].is_null() || (old_x == x && old_z == z) { + let chunk = region.get_mut(last).panic_unchecked("Last index was valid").as_chunk_unchecked_mut(); chunk.x = old_x; chunk.z = old_z; return ignore_already_present_key; }; - let (map, chunks) = &mut *element.data.region.chunks; + let (map, chunks) = &mut *region.chunks; let from = core::mem::replace(&mut map[last], new_idx as u16) as usize; chunks.swap(from, new_idx); (Some(old_x.to_compact_string()), Some(old_z.to_compact_string())) @@ -1520,9 +1532,11 @@ impl Workbench { self.selected_text = None; } else { let buf = PathBuf::from(value); - return if let Some(name) = buf.file_name().and_then(OsStr::to_str) { - tab.name = name.into(); - tab.path = Some(buf); + return if let Some(name) = buf.file_name().and_then(OsStr::to_str).map(ToOwned::to_owned) { + let old_name = core::mem::replace(&mut tab.name, name.into_boxed_str()); + tab.undos.push(WorkbenchAction::Rename { indices: Box::new([]), key: None, value: Some(tab.path.replace(buf).as_deref().and_then(|path| path.to_str()).map(|str| str.to_compact_string()).unwrap_or_else(|| old_name.to_compact_string())) }); + tab.redos.clear(); + tab.history_changed = true; self.selected_text = None; true } else { @@ -1536,7 +1550,7 @@ impl Workbench { #[cfg_attr(not(debug_assertions), inline)] #[allow(clippy::collapsible_if, clippy::too_many_lines, clippy::cognitive_complexity)] - pub fn on_key_input(&mut self, key: KeyboardInput, window: &Window) -> bool { + pub fn on_key_input(&mut self, key: KeyboardInput, f: impl Fn(&str)) -> bool { if key.state == ElementState::Pressed { if let Some(key) = key.virtual_keycode { self.held_keys.insert(key); @@ -1622,39 +1636,39 @@ impl Workbench { } { if key == VirtualKeyCode::Key1 { - self.set_tab(0, window); + self.set_tab(0, f); return true; } if key == VirtualKeyCode::Key2 { - self.set_tab(1, window); + self.set_tab(1, f); return true; } if key == VirtualKeyCode::Key3 { - self.set_tab(2, window); + self.set_tab(2, f); return true; } if key == VirtualKeyCode::Key4 { - self.set_tab(3, window); + self.set_tab(3, f); return true; } if key == VirtualKeyCode::Key5 { - self.set_tab(4, window); + self.set_tab(4, f); return true; } if key == VirtualKeyCode::Key6 { - self.set_tab(5, window); + self.set_tab(5, f); return true; } if key == VirtualKeyCode::Key7 { - self.set_tab(6, window); + self.set_tab(6, f); return true; } if key == VirtualKeyCode::Key8 { - self.set_tab(7, window); + self.set_tab(7, f); return true; } if key == VirtualKeyCode::Key9 { - self.set_tab(self.tabs.len().saturating_sub(1), window); + self.set_tab(self.tabs.len().saturating_sub(1), f); return true; } } @@ -1719,7 +1733,7 @@ impl Workbench { uuid: Uuid::new_v4(), freehand_mode: false, }); - self.set_tab(self.tabs.len() - 1, window); + self.set_tab(self.tabs.len() - 1, f); self.selected_text = None; return true; } @@ -1735,7 +1749,7 @@ impl Workbench { if let Some(tab) = self.tab() && !tab.history_changed { let t = self.tabs.remove(self.tab); std::thread::spawn(move || drop(t)); - self.set_tab(self.tab.saturating_sub(1), window); + self.set_tab(self.tab.saturating_sub(1), f); self.selected_text = None; return true; } @@ -1743,7 +1757,7 @@ impl Workbench { if key == VirtualKeyCode::Z && flags == flags!(Ctrl) { if let Some(tab) = self.tabs.get_mut(self.tab) { if let Some(action) = tab.undos.pop() { - tab.redos.push(action.undo(&mut tab.value, &mut tab.bookmarks, &mut self.subscription)); + tab.redos.push(action.undo(&mut tab.value, &mut tab.bookmarks, &mut self.subscription, &mut tab.path, &mut tab.name)); self.selected_text = None; return true; } @@ -1752,7 +1766,7 @@ impl Workbench { if key == VirtualKeyCode::Y && flags == flags!(Ctrl) { if let Some(tab) = self.tabs.get_mut(self.tab) { if let Some(action) = tab.redos.pop() { - tab.undos.push(action.undo(&mut tab.value, &mut tab.bookmarks, &mut self.subscription)); + tab.undos.push(action.undo(&mut tab.value, &mut tab.bookmarks, &mut self.subscription, &mut tab.path, &mut tab.name)); self.selected_text = None; return true; } @@ -1858,10 +1872,10 @@ impl Workbench { } #[inline] - fn set_tab(&mut self, idx: usize, window: &Window) { + fn set_tab(&mut self, idx: usize, f: impl Fn(&str)) { self.tab = idx.min(self.tabs.len().saturating_sub(1)); if let Some(tab) = self.tab() { - window.set_title(format!("{} - NBT Workbench", tab.name).as_str()); + f(format!("{} - NBT Workbench", tab.name).as_str()); } } diff --git a/src/workbench_action.rs b/src/workbench_action.rs index a1661b7..9c5b6f4 100644 --- a/src/workbench_action.rs +++ b/src/workbench_action.rs @@ -1,9 +1,8 @@ +use std::path::PathBuf; use compact_str::{CompactString, ToCompactString}; -use crate::assets::*; -use crate::elements::chunk::{NbtChunk, NbtRegion}; -use crate::elements::compound::NbtCompound; -use crate::elements::element_type::NbtElement; +use crate::assets::{ADD_TAIL_UV, ADD_UV, MOVE_TAIL_UV, MOVE_UV, REMOVE_TAIL_UV, REMOVE_UV, RENAME_TAIL_UV, RENAME_UV, REPLACE_TAIL_UV, REPLACE_UV}; +use crate::elements::element::NbtElement; use crate::vertex_buffer_builder::VertexBufferBuilder; use crate::{encompasses, encompasses_or_equal, panic_unchecked, FileUpdateSubscription, Position}; use crate::{Navigate, OptionExt}; @@ -34,14 +33,14 @@ pub enum WorkbenchAction { impl WorkbenchAction { #[cfg_attr(debug_assertions, inline(never))] - pub fn undo(self, root: &mut NbtElement, bookmarks: &mut Vec, subscription: &mut Option) -> Self { - unsafe { self.undo0(root, bookmarks, subscription).panic_unchecked("Failed to undo action") } + pub fn undo(self, root: &mut NbtElement, bookmarks: &mut Vec, subscription: &mut Option, path: &mut Option, name: &mut Box) -> Self { + unsafe { self.undo0(root, bookmarks, subscription, path, name).panic_unchecked("Failed to undo action") } } #[cfg_attr(not(debug_assertions), inline(always))] #[cfg_attr(debug_assertions, inline(never))] #[allow(clippy::collapsible_else_if, clippy::too_many_lines, clippy::cognitive_complexity)] - unsafe fn undo0(self, root: &mut NbtElement, bookmarks: &mut Vec, subscription: &mut Option) -> Option { + unsafe fn undo0(self, root: &mut NbtElement, bookmarks: &mut Vec, subscription: &mut Option, path: &mut Option, name: &mut Box) -> Option { Some(match self { Self::Remove { element: (key, value), indices } => { let (&last, rem) = indices.split_last()?; @@ -54,11 +53,12 @@ impl WorkbenchAction { element.increment(height, true_height); } Position::Last | Position::Only => { - match element.id() { - NbtCompound::ID => { - element.data.compound.insert(last, key?, value); - } - _ => element.insert(last, value).ok().panic_unchecked("Should've been able to add to element"), + if let Some(compound) = element.as_compound_mut() { + compound.insert(last, key?, value); + } else if let Some(chunk) = element.as_chunk_mut() { + chunk.insert(last, key?, value); + } else { + let _ = element.insert(last, value); } let start = bookmarks.binary_search(&line_number).unwrap_or_else(std::convert::identity); for bookmark in bookmarks.iter_mut().skip(start) { @@ -111,30 +111,43 @@ impl WorkbenchAction { Self::Remove { element, indices } } Self::Rename { indices, key, value } => { - let (&last, rem) = indices.split_last()?; - let mut override_value = None; - let key = if let Some(key) = key { - let parent = Navigate::new(rem.iter().copied(), root).last().2; - Some(if parent.id() == NbtRegion::ID { - let (map, chunks) = &mut *parent.data.region.chunks; - let from = map.get(last).copied()? as usize; - let to = ((key.parse::().ok()? as usize) << 5) | (value.as_ref()?.parse::().ok()? as usize); - chunks.swap(from, to); - override_value = Some((from & 31).to_compact_string()); - (from >> 5).to_compact_string() - } else if parent.id() == NbtCompound::ID { - parent.data.compound.update_key(last, key)? + if let Some((&last, rem)) = indices.split_last() { + let mut override_value = None; + let key = if let Some(key) = key { + let parent = Navigate::new(rem.iter().copied(), root).last().2; + Some(if let Some(region) = parent.as_region_mut() { + let (map, chunks) = &mut *region.chunks; + let from = map.get(last).copied()? as usize; + let to = ((key.parse::().ok()? as usize) << 5) | (value.as_ref()?.parse::().ok()? as usize); + chunks.swap(from, to); + override_value = Some((from & 31).to_compact_string()); + (from >> 5).to_compact_string() + } else if let Some(compound) = parent.as_compound_mut() { + compound.update_key(last, key)? + } else if let Some(chunk) = parent.as_chunk_mut() { + chunk.update_key(last, key)? + } else { + key + }) } else { - parent.data.chunk.inner.update_key(last, key)? - }) + None + }; + let value = override_value.or_else(|| value.and_then(|value| Navigate::new(indices.iter().copied(), root).last().2.set_value(value))); + if key.is_some() || value.is_some() { + crate::recache_along_indices(rem, root); + } + Self::Rename { indices, key, value } } else { - None - }; - let value = override_value.or_else(|| value.and_then(|value| Navigate::new(indices.iter().copied(), root).last().2.set_value(value))); - if key.is_some() || value.is_some() { - crate::recache_along_indices(rem, root); + let old = PathBuf::from(value?.into_string()); + let old_name = old.file_name().and_then(|str| str.to_str())?.to_owned().into_boxed_str(); + let value = if let Some(new) = path.replace(old) { + new.to_str()?.to_compact_string() + } else { + name.to_compact_string() + }; + *name = old_name; + Self::Rename { indices, key, value: Some(value) } } - Self::Rename { indices, key, value } } Self::Move { mut from, to, original_key } => { let mut changed_subscription_indices = false; @@ -187,8 +200,10 @@ impl WorkbenchAction { element.increment(height, true_height); } Position::Last | Position::Only => { - if element.id() == NbtCompound::ID { - element.data.compound.insert(last, original_key?, mov); + if let Some(compound) = element.as_compound_mut() { + compound.insert(last, original_key?, mov); + } else if let Some(chunk) = element.as_chunk_mut() { + chunk.insert(last, original_key?, mov); } else { if element.insert(last, mov).is_err() { return None; @@ -226,10 +241,10 @@ impl WorkbenchAction { let (old_key, old_value) = element.remove(last).panic_unchecked("index is always valid"); let old_true_height = old_value.true_height(); element.decrement(old_value.height(), old_value.true_height()); - if element.id() == NbtCompound::ID { - element.data.compound.insert(last, key?, value); - } else if element.id() == NbtChunk::ID { - element.data.chunk.inner.insert(last, key?, value); + if let Some(compound) = element.as_compound_mut() { + compound.insert(last, key?, value); + } else if let Some(chunk) = element.as_chunk_mut() { + chunk.insert(last, key?, value); } else { if element.insert(last, value).is_err() { panic_unchecked("oh crap");