diff options
| author | mat <github@matdoes.dev> | 2021-12-20 15:22:02 -0600 |
|---|---|---|
| committer | mat <github@matdoes.dev> | 2021-12-20 15:22:02 -0600 |
| commit | 6ae94b96e6d51e3bf251d4a01f17fa7d41c9500f (patch) | |
| tree | 3153984562016e703eefd71b4b1de4151d47fdde /azalea-nbt | |
| parent | cf88c7b7956398eba2e0d6ed86dbf3268fdb512b (diff) | |
| download | azalea-drasl-6ae94b96e6d51e3bf251d4a01f17fa7d41c9500f.tar.xz | |
start adding nbt to the protocol
Diffstat (limited to 'azalea-nbt')
| -rw-r--r-- | azalea-nbt/Cargo.toml | 6 | ||||
| -rw-r--r-- | azalea-nbt/benches/my_benchmark.rs | 14 | ||||
| -rw-r--r-- | azalea-nbt/src/decode.rs | 84 | ||||
| -rw-r--r-- | azalea-nbt/src/error.rs | 10 | ||||
| -rw-r--r-- | azalea-nbt/tests/tests.rs | 61 |
5 files changed, 106 insertions, 69 deletions
diff --git a/azalea-nbt/Cargo.toml b/azalea-nbt/Cargo.toml index bdc4208c..5d28d136 100644 --- a/azalea-nbt/Cargo.toml +++ b/azalea-nbt/Cargo.toml @@ -6,13 +6,17 @@ version = "0.1.0" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +async-compression = {version = "^0.3.8", features = ["tokio", "zlib", "gzip"]} +async-recursion = "^0.3.2" byteorder = "^1.4.3" flate2 = "^1.0.22" num-derive = "^0.3.3" num-traits = "^0.2.14" +tokio = "^1.15.0" [dev-dependencies] -criterion = {version = "^0.3.5", features = ["html_reports"]} +criterion = {version = "^0.3.5", features = ["html_reports", "async_tokio"]} +tokio = {version = "^1.15.0", features = ["fs"]} [profile.release] lto = true diff --git a/azalea-nbt/benches/my_benchmark.rs b/azalea-nbt/benches/my_benchmark.rs index 46a2f851..1e642716 100644 --- a/azalea-nbt/benches/my_benchmark.rs +++ b/azalea-nbt/benches/my_benchmark.rs @@ -19,15 +19,19 @@ fn bench_serialize(filename: &str, c: &mut Criterion) { let mut decoded_src_stream = std::io::Cursor::new(decoded_src.clone()); file.seek(SeekFrom::Start(0)).unwrap(); - let nbt = Tag::read_gzip(&mut file).unwrap(); + // run Tag::read(&mut decoded_src_stream) asynchronously + let nbt = tokio::runtime::Runtime::new() + .unwrap() + .block_on(async { Tag::read(&mut decoded_src_stream).await.unwrap() }); let mut group = c.benchmark_group(filename); group.throughput(Throughput::Bytes(decoded_src.len() as u64)); group.bench_function("Decode", |b| { - b.iter(|| { - decoded_src_stream.seek(SeekFrom::Start(0)).unwrap(); - Tag::read(&mut decoded_src_stream).unwrap(); - }) + b.to_async(tokio::runtime::Runtime::new().unwrap()) + .iter(|| { + decoded_src_stream.seek(SeekFrom::Start(0)).unwrap(); + Tag::read(&mut decoded_src_stream) + }) }); group.bench_function("Encode", |b| { b.iter(|| { diff --git a/azalea-nbt/src/decode.rs b/azalea-nbt/src/decode.rs index fb1b78b6..b21bd9a5 100644 --- a/azalea-nbt/src/decode.rs +++ b/azalea-nbt/src/decode.rs @@ -1,54 +1,63 @@ use crate::Error; use crate::Tag; -use byteorder::{ReadBytesExt, BE}; -use flate2::read::{GzDecoder, ZlibDecoder}; -use std::{collections::HashMap, io::Read}; +use async_compression::tokio::bufread::{GzipDecoder, ZlibDecoder}; +use async_recursion::async_recursion; +use std::collections::HashMap; +use tokio::io::AsyncBufRead; +use tokio::io::{AsyncRead, AsyncReadExt}; #[inline] -fn read_string(stream: &mut impl Read) -> Result<String, Error> { - let length = stream.read_u16::<BE>().map_err(|_| Error::InvalidTag)?; +async fn read_string<R>(stream: &mut R) -> Result<String, Error> +where + R: AsyncRead + std::marker::Unpin, +{ + let length = stream.read_u16().await.map_err(|_| Error::InvalidTag)?; let mut buf = Vec::with_capacity(length as usize); for _ in 0..length { - buf.push(stream.read_u8().map_err(|_| Error::InvalidTag)?); + buf.push(stream.read_u8().await.map_err(|_| Error::InvalidTag)?); } String::from_utf8(buf).map_err(|_| Error::InvalidTag) } impl Tag { - fn read_known(stream: &mut impl Read, id: u8) -> Result<Tag, Error> { + #[async_recursion] + async fn read_known<R>(stream: &mut R, id: u8) -> Result<Tag, Error> + where + R: AsyncRead + std::marker::Unpin + std::marker::Send, + { let tag = match id { // Signifies the end of a TAG_Compound. It is only ever used inside // a TAG_Compound, and is not named despite being in a TAG_Compound 0 => Tag::End, // A single signed byte - 1 => Tag::Byte(stream.read_i8().map_err(|_| Error::InvalidTag)?), + 1 => Tag::Byte(stream.read_i8().await.map_err(|_| Error::InvalidTag)?), // A single signed, big endian 16 bit integer - 2 => Tag::Short(stream.read_i16::<BE>().map_err(|_| Error::InvalidTag)?), + 2 => Tag::Short(stream.read_i16().await.map_err(|_| Error::InvalidTag)?), // A single signed, big endian 32 bit integer - 3 => Tag::Int(stream.read_i32::<BE>().map_err(|_| Error::InvalidTag)?), + 3 => Tag::Int(stream.read_i32().await.map_err(|_| Error::InvalidTag)?), // A single signed, big endian 64 bit integer - 4 => Tag::Long(stream.read_i64::<BE>().map_err(|_| Error::InvalidTag)?), + 4 => Tag::Long(stream.read_i64().await.map_err(|_| Error::InvalidTag)?), // A single, big endian IEEE-754 single-precision floating point // number (NaN possible) - 5 => Tag::Float(stream.read_f32::<BE>().map_err(|_| Error::InvalidTag)?), + 5 => Tag::Float(stream.read_f32().await.map_err(|_| Error::InvalidTag)?), // A single, big endian IEEE-754 double-precision floating point // number (NaN possible) - 6 => Tag::Double(stream.read_f64::<BE>().map_err(|_| Error::InvalidTag)?), + 6 => Tag::Double(stream.read_f64().await.map_err(|_| Error::InvalidTag)?), // A length-prefixed array of signed bytes. The prefix is a signed // integer (thus 4 bytes) 7 => { - let length = stream.read_i32::<BE>().map_err(|_| Error::InvalidTag)?; + let length = stream.read_i32().await.map_err(|_| Error::InvalidTag)?; let mut bytes = Vec::with_capacity(length as usize); for _ in 0..length { - bytes.push(stream.read_i8().map_err(|_| Error::InvalidTag)?); + bytes.push(stream.read_i8().await.map_err(|_| Error::InvalidTag)?); } Tag::ByteArray(bytes) } // A length-prefixed modified UTF-8 string. The prefix is an // unsigned short (thus 2 bytes) signifying the length of the // string in bytes - 8 => Tag::String(read_string(stream)?), + 8 => Tag::String(read_string(stream).await?), // A list of nameless tags, all of the same type. The list is // prefixed with the Type ID of the items it contains (thus 1 // byte), and the length of the list as a signed integer (a further @@ -58,11 +67,11 @@ impl Tag { // another reference implementation by Mojang uses 1 instead; // parsers should accept any type if the length is <= 0). 9 => { - let type_id = stream.read_u8().map_err(|_| Error::InvalidTag)?; - let length = stream.read_i32::<BE>().map_err(|_| Error::InvalidTag)?; + let type_id = stream.read_u8().await.map_err(|_| Error::InvalidTag)?; + let length = stream.read_i32().await.map_err(|_| Error::InvalidTag)?; let mut list = Vec::with_capacity(length as usize); for _ in 0..length { - list.push(Tag::read_known(stream, type_id)?); + list.push(Tag::read_known(stream, type_id).await?); } Tag::List(list) } @@ -71,12 +80,12 @@ impl Tag { // we default to capacity 4 because it'll probably not be empty let mut map = HashMap::with_capacity(4); loop { - let tag_id = stream.read_u8().unwrap_or(0); + let tag_id = stream.read_u8().await.unwrap_or(0); if tag_id == 0 { break; } - let name = read_string(stream)?; - let tag = Tag::read_known(stream, tag_id)?; + let name = read_string(stream).await?; + let tag = Tag::read_known(stream, tag_id).await?; map.insert(name, tag); } Tag::Compound(map) @@ -85,20 +94,20 @@ impl Tag { // signed integer (thus 4 bytes) and indicates the number of 4 byte // integers. 11 => { - let length = stream.read_i32::<BE>().map_err(|_| Error::InvalidTag)?; + let length = stream.read_i32().await.map_err(|_| Error::InvalidTag)?; let mut ints = Vec::with_capacity(length as usize); for _ in 0..length { - ints.push(stream.read_i32::<BE>().map_err(|_| Error::InvalidTag)?); + ints.push(stream.read_i32().await.map_err(|_| Error::InvalidTag)?); } Tag::IntArray(ints) } // A length-prefixed array of signed longs. The prefix is a signed // integer (thus 4 bytes) and indicates the number of 8 byte longs. 12 => { - let length = stream.read_i32::<BE>().map_err(|_| Error::InvalidTag)?; + let length = stream.read_i32().await.map_err(|_| Error::InvalidTag)?; let mut longs = Vec::with_capacity(length as usize); for _ in 0..length { - longs.push(stream.read_i64::<BE>().map_err(|_| Error::InvalidTag)?); + longs.push(stream.read_i64().await.map_err(|_| Error::InvalidTag)?); } Tag::LongArray(longs) } @@ -107,18 +116,27 @@ impl Tag { Ok(tag) } - pub fn read(stream: &mut impl Read) -> Result<Tag, Error> { + pub async fn read<R>(stream: &mut R) -> Result<Tag, Error> + where + R: AsyncRead + std::marker::Unpin + std::marker::Send, + { // default to compound tag - Tag::read_known(stream, 10) + Tag::read_known(stream, 10).await } - pub fn read_zlib(stream: &mut impl Read) -> Result<Tag, Error> { + pub async fn read_zlib<R>(stream: &mut R) -> Result<Tag, Error> + where + R: AsyncBufRead + std::marker::Unpin + std::marker::Send, + { let mut gz = ZlibDecoder::new(stream); - Tag::read(&mut gz) + Tag::read(&mut gz).await } - pub fn read_gzip(stream: &mut impl Read) -> Result<Tag, Error> { - let mut gz = GzDecoder::new(stream); - Tag::read(&mut gz) + pub async fn read_gzip<R>(stream: &mut R) -> Result<Tag, Error> + where + R: AsyncBufRead + std::marker::Unpin + std::marker::Send, + { + let mut gz = GzipDecoder::new(stream); + Tag::read(&mut gz).await } } diff --git a/azalea-nbt/src/error.rs b/azalea-nbt/src/error.rs index 3ada7cf7..05ff15e0 100644 --- a/azalea-nbt/src/error.rs +++ b/azalea-nbt/src/error.rs @@ -4,3 +4,13 @@ pub enum Error { InvalidTag, WriteError, } + +impl std::fmt::Display for Error { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + match self { + Error::InvalidTagType(id) => write!(f, "Invalid tag type: {}", id), + Error::InvalidTag => write!(f, "Invalid tag"), + Error::WriteError => write!(f, "Write error"), + } + } +} diff --git a/azalea-nbt/tests/tests.rs b/azalea-nbt/tests/tests.rs index dd2bb6dd..75b3a646 100644 --- a/azalea-nbt/tests/tests.rs +++ b/azalea-nbt/tests/tests.rs @@ -3,12 +3,13 @@ use std::{ collections::HashMap, io::{Cursor, Read}, }; +use tokio::{fs::File, io::AsyncReadExt}; -#[test] -fn test_decode_hello_world() { +#[tokio::test] +async fn test_decode_hello_world() { // read hello_world.nbt - let mut file = std::fs::File::open("tests/hello_world.nbt").unwrap(); - let tag = Tag::read(&mut file).unwrap(); + let mut file = File::open("tests/hello_world.nbt").await.unwrap(); + let tag = Tag::read(&mut file).await.unwrap(); assert_eq!( tag, Tag::Compound(HashMap::from_iter(vec![( @@ -21,14 +22,14 @@ fn test_decode_hello_world() { ); } -#[test] -fn test_roundtrip_hello_world() { - let mut file = std::fs::File::open("tests/hello_world.nbt").unwrap(); +#[tokio::test] +async fn test_roundtrip_hello_world() { + let mut file = File::open("tests/hello_world.nbt").await.unwrap(); let mut original = Vec::new(); - file.read_to_end(&mut original).unwrap(); + file.read_to_end(&mut original).await.unwrap(); let mut original_stream = Cursor::new(original.clone()); - let tag = Tag::read(&mut original_stream).unwrap(); + let tag = Tag::read(&mut original_stream).await.unwrap(); // write hello_world.nbt let mut result = Cursor::new(Vec::new()); @@ -37,26 +38,26 @@ fn test_roundtrip_hello_world() { assert_eq!(result.into_inner(), original); } -#[test] -fn test_bigtest() { +#[tokio::test] +async fn test_bigtest() { // read bigtest.nbt - let mut file = std::fs::File::open("tests/bigtest.nbt").unwrap(); + let mut file = File::open("tests/bigtest.nbt").await.unwrap(); let mut original = Vec::new(); - file.read_to_end(&mut original).unwrap(); + file.read_to_end(&mut original).await.unwrap(); let mut original_stream = Cursor::new(original.clone()); - let original_tag = Tag::read_gzip(&mut original_stream).unwrap(); + let original_tag = Tag::read_gzip(&mut original_stream).await.unwrap(); let mut result = Vec::new(); original_tag.write(&mut result).unwrap(); - let decoded_tag = Tag::read(&mut Cursor::new(result)).unwrap(); + let decoded_tag = Tag::read(&mut Cursor::new(result)).await.unwrap(); assert_eq!(decoded_tag, original_tag); } -#[test] -fn test_stringtest() { +#[tokio::test] +async fn test_stringtest() { let correct_tag = Tag::Compound(HashMap::from_iter(vec![( "😃".to_string(), Tag::List(vec![ @@ -83,41 +84,41 @@ fn test_stringtest() { file.read_to_end(&mut original).unwrap(); let mut original_stream = Cursor::new(original.clone()); - let original_tag = Tag::read_gzip(&mut original_stream).unwrap(); + let original_tag = Tag::read_gzip(&mut original_stream).await.unwrap(); assert_eq!(original_tag, correct_tag); } -#[test] -fn test_complex_player() { - let mut file = std::fs::File::open("tests/complex_player.dat").unwrap(); +#[tokio::test] +async fn test_complex_player() { + let mut file = File::open("tests/complex_player.dat").await.unwrap(); let mut original = Vec::new(); - file.read_to_end(&mut original).unwrap(); + file.read_to_end(&mut original).await.unwrap(); let mut original_stream = Cursor::new(original.clone()); - let original_tag = Tag::read_gzip(&mut original_stream).unwrap(); + let original_tag = Tag::read_gzip(&mut original_stream).await.unwrap(); let mut result = Vec::new(); original_tag.write(&mut result).unwrap(); - let decoded_tag = Tag::read(&mut Cursor::new(result)).unwrap(); + let decoded_tag = Tag::read(&mut Cursor::new(result)).await.unwrap(); assert_eq!(decoded_tag, original_tag); } -#[test] -fn test_simple_player() { - let mut file = std::fs::File::open("tests/simple_player.dat").unwrap(); +#[tokio::test] +async fn test_simple_player() { + let mut file = File::open("tests/simple_player.dat").await.unwrap(); let mut original = Vec::new(); - file.read_to_end(&mut original).unwrap(); + file.read_to_end(&mut original).await.unwrap(); let mut original_stream = Cursor::new(original.clone()); - let original_tag = Tag::read_gzip(&mut original_stream).unwrap(); + let original_tag = Tag::read_gzip(&mut original_stream).await.unwrap(); let mut result = Vec::new(); original_tag.write(&mut result).unwrap(); - let decoded_tag = Tag::read(&mut Cursor::new(result)).unwrap(); + let decoded_tag = Tag::read(&mut Cursor::new(result)).await.unwrap(); assert_eq!(decoded_tag, original_tag); } |
