1
0
Fork 0
mirror of https://github.com/azalea-rs/simdnbt.git synced 2025-08-02 15:36:03 +00:00

improve docs

This commit is contained in:
mat 2023-08-27 23:03:41 -05:00
parent 9d1c9715eb
commit d0ccb6f406
10 changed files with 170 additions and 11 deletions

1
.gitignore vendored
View file

@ -1,2 +1,3 @@
/target
/Cargo.lock
.vscode

View file

@ -21,7 +21,7 @@ hematite-nbt = { version = "0.5.2", default-features = false }
[profile.release]
lto = true
debug = true
# debug = true
[profile.bench]
lto = true

View file

@ -2,7 +2,7 @@
an unnecessarily fast nbt decoder. like seriously you probably don't need this unless you're trying to win benchmarks.
at the moment, simdnbt does not actually make use of simd instructions (the name is a parody of simdjson). there's one place where i know i could take advantage of simd but it just hasn't been implemented yet (swapping the endianness of integer arrays).
at the moment, simdnbt does not actually make use of simd instructions (the name is a play on simdjson). there's one place where i know i could take advantage of simd but it just hasn't been implemented yet (swapping the endianness of integer arrays).
simdnbt might be the fastest nbt decoder currently in existence. however to achieve this silly speed, it takes a couple of shortcuts:
1. it requires a reference to the original data (to avoid cloning)

View file

@ -121,7 +121,7 @@ pub fn bench_read_file(filename: &str, c: &mut Criterion) {
fn bench(c: &mut Criterion) {
// bench_read_file("hello_world.nbt", c);
// bench_read_file("bigtest.nbt", c);
bench_read_file("simple_player.dat", c);
// bench_read_file("simple_player.dat", c);
// bench_read_file("complex_player.dat", c);
// bench_read_file("level.dat", c);
// bench_read_file("stringtest.nbt", c);

109
examples/hypixel.rs Normal file
View file

@ -0,0 +1,109 @@
use std::{collections::HashMap, hint::black_box, io::Cursor};
use simdnbt::Nbt;
#[derive(Clone, PartialEq, Debug)]
pub struct Item {
pub id: i16,
pub damage: i16,
pub count: i8,
pub head_texture_id: Option<String>,
pub skyblock_id: Option<String>,
pub reforge: Option<String>,
pub display: ItemDisplay,
pub enchantments: HashMap<String, i32>,
pub timestamp: Option<String>,
}
#[derive(Clone, PartialEq, Debug)]
pub struct ItemDisplay {
pub name: String,
pub lore: Vec<String>,
pub has_glint: bool,
pub color: Option<i32>,
}
fn simdnbt_items_from_nbt(nbt: simdnbt::Nbt) -> Option<Vec<Option<Item>>> {
let mut items = Vec::new();
for item_nbt in nbt
.list("i")
.and_then(|list| list.compounds())
.unwrap_or_default()
{
// check if "id" is present, if not, skip
if !item_nbt.contains("id") {
// this just means the item isn't present
items.push(None);
continue;
}
let item_tag = item_nbt.compound("tag")?;
let item_extra_attributes = item_tag.compound("ExtraAttributes");
let item_display = item_tag.compound("display");
items.push(Some(Item {
id: item_nbt.short("id")?,
damage: item_nbt.short("Damage")?,
count: item_nbt.byte("Count")?,
head_texture_id: item_tag
.compound("SkullOwner")
.and_then(|skull_owner| skull_owner.compound("Properties"))
.and_then(|properties| properties.list("textures"))
.and_then(|textures| textures.compounds())
.and_then(|textures| textures.get(0))
.and_then(|texture| texture.string("Value"))
// the real program does some base64+json decoding here but that's unnecessary for the benchmark
.map(|value| value.to_string()),
skyblock_id: item_extra_attributes
.and_then(|e| e.string("id"))
.map(|id| id.to_string()),
reforge: item_extra_attributes
.and_then(|e| e.string("modifier"))
.map(|id| id.to_string()),
display: ItemDisplay {
name: item_display
.and_then(|d| d.string("Name"))
.map(|n| n.to_string())
.unwrap_or_default(),
lore: item_display
.and_then(|d| d.list("Lore"))
.and_then(|l| l.strings())
.map(|l| l.iter().map(|s| s.to_string()).collect())
.unwrap_or_default(),
color: item_display.and_then(|d| d.int("color")),
has_glint: item_extra_attributes
.map(|e| e.contains("ench"))
.unwrap_or_default(),
},
enchantments: item_extra_attributes
.and_then(|e| e.compound("enchantments"))
.map(|e| {
e.iter()
.map(|(k, v)| (k.to_string(), v.int().unwrap_or_default()))
.collect()
})
.unwrap_or_default(),
timestamp: item_extra_attributes
.and_then(|e| e.string("timestamp"))
.map(|t| t.to_string()),
}));
}
Some(items)
}
fn main() {
let input = black_box(include_bytes!("../tests/realworld.nbt"));
for _ in 0..100 {
let nbt = Nbt::new(&mut Cursor::new(input));
let nbt = black_box(nbt.unwrap().unwrap());
black_box(simdnbt_items_from_nbt(nbt));
}
}

View file

@ -40,8 +40,37 @@ impl<'a> Deref for Nbt<'a> {
}
}
#[inline(always)]
fn read_u32(data: &mut Cursor<&[u8]>) -> Result<u32, Error> {
let remaining_slice = &data.get_ref()[data.position() as usize..data.get_ref().len()];
if remaining_slice.len() < 4 {
return Err(Error::UnexpectedEof);
}
data.set_position(data.position() + 4);
Ok(u32::from_be_bytes([
remaining_slice[0],
remaining_slice[1],
remaining_slice[2],
remaining_slice[3],
]))
}
#[inline(always)]
fn read_u16(data: &mut Cursor<&[u8]>) -> Result<u16, Error> {
let remaining_slice = &data.get_ref()[data.position() as usize..data.get_ref().len()];
if remaining_slice.len() < 2 {
return Err(Error::UnexpectedEof);
}
data.set_position(data.position() + 2);
Ok(u16::from_be_bytes([remaining_slice[0], remaining_slice[1]]))
}
#[inline(always)]
fn read_with_u16_length<'a>(data: &mut Cursor<&'a [u8]>, width: usize) -> Result<&'a [u8], Error> {
let length = data.read_u16::<BE>()?;
let length: u16 = read_u16(data)?;
let length_in_bytes = length as usize * width;
// make sure we don't read more than the length
if data.get_ref().len() < data.position() as usize + length_in_bytes {
@ -52,8 +81,9 @@ fn read_with_u16_length<'a>(data: &mut Cursor<&'a [u8]>, width: usize) -> Result
Ok(&data.get_ref()[start_position..start_position + length_in_bytes])
}
#[inline(never)]
fn read_with_u32_length<'a>(data: &mut Cursor<&'a [u8]>, width: usize) -> Result<&'a [u8], Error> {
let length = data.read_u32::<BE>()?;
let length = read_u32(data)?;
let length_in_bytes = length as usize * width;
// make sure we don't read more than the length
if data.get_ref().len() < data.position() as usize + length_in_bytes {
@ -423,7 +453,7 @@ impl<'a> ListTag<'a> {
let tag_type = data.read_u8()?;
Ok(match tag_type {
END_ID => {
let _length = data.read_u32::<BE>()?;
data.set_position(data.position() + 4);
ListTag::Empty
}
BYTE_ID => ListTag::Byte(read_i8_array(data)?),
@ -434,7 +464,7 @@ impl<'a> ListTag<'a> {
DOUBLE_ID => ListTag::Double(read_double_array(data)?),
BYTE_ARRAY_ID => ListTag::ByteArray(read_u8_array(data)?),
STRING_ID => ListTag::String({
let length = data.read_u32::<BE>()?;
let length = read_u32(data)?;
// arbitrary number to prevent big allocations
let mut strings = Vec::with_capacity(length.min(128) as usize);
for _ in 0..length {
@ -443,7 +473,7 @@ impl<'a> ListTag<'a> {
strings
}),
LIST_ID => ListTag::List({
let length = data.read_u32::<BE>()?;
let length = read_u32(data)?;
// arbitrary number to prevent big allocations
let mut lists = Vec::with_capacity(length.min(128) as usize);
for _ in 0..length {
@ -452,7 +482,7 @@ impl<'a> ListTag<'a> {
lists
}),
COMPOUND_ID => ListTag::Compound({
let length = data.read_u32::<BE>()?;
let length = read_u32(data)?;
// arbitrary number to prevent big allocations
let mut compounds = Vec::with_capacity(length.min(128) as usize);
for _ in 0..length {
@ -461,7 +491,7 @@ impl<'a> ListTag<'a> {
compounds
}),
INT_ARRAY_ID => ListTag::IntArray({
let length = data.read_u32::<BE>()?;
let length = read_u32(data)?;
// arbitrary number to prevent big allocations
let mut arrays = Vec::with_capacity(length.min(128) as usize);
for _ in 0..length {
@ -470,7 +500,7 @@ impl<'a> ListTag<'a> {
arrays
}),
LONG_ARRAY_ID => ListTag::LongArray({
let length = data.read_u32::<BE>()?;
let length = read_u32(data)?;
// arbitrary number to prevent big allocations
let mut arrays = Vec::with_capacity(length.min(128) as usize);
for _ in 0..length {
@ -602,4 +632,23 @@ mod tests {
assert_eq!(nbt.float("foodExhaustionLevel").unwrap() as u32, 2);
assert_eq!(nbt.list("Rotation").unwrap().floats().unwrap().len(), 2);
}
// #[test]
// fn generate_inttest() {
// use byteorder::WriteBytesExt;
// let mut out = Vec::new();
// out.write_u8(COMPOUND_ID).unwrap();
// out.write_u16::<BE>(0).unwrap();
// out.write_u8(LIST_ID).unwrap();
// out.write_u16::<BE>(0).unwrap();
// out.write_u8(INT_ID).unwrap();
// out.write_i32::<BE>(1023).unwrap();
// for i in 0..1023 {
// out.write_i32::<BE>(i).unwrap();
// }
// out.write_u8(END_ID).unwrap();
// std::fs::write("tests/inttest1023.nbt", out).unwrap();
// }
}

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
tests/realworld.nbt Normal file

Binary file not shown.