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:
parent
9d1c9715eb
commit
d0ccb6f406
10 changed files with 170 additions and 11 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -1,2 +1,3 @@
|
|||
/target
|
||||
/Cargo.lock
|
||||
.vscode
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
109
examples/hypixel.rs
Normal 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));
|
||||
}
|
||||
}
|
65
src/lib.rs
65
src/lib.rs
|
@ -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
BIN
tests/realworld.nbt
Normal file
Binary file not shown.
Loading…
Add table
Reference in a new issue