aboutsummaryrefslogtreecommitdiff
path: root/azalea-nbt
diff options
context:
space:
mode:
authormat <github@matdoes.dev>2021-12-20 15:22:02 -0600
committermat <github@matdoes.dev>2021-12-20 15:22:02 -0600
commit6ae94b96e6d51e3bf251d4a01f17fa7d41c9500f (patch)
tree3153984562016e703eefd71b4b1de4151d47fdde /azalea-nbt
parentcf88c7b7956398eba2e0d6ed86dbf3268fdb512b (diff)
downloadazalea-drasl-6ae94b96e6d51e3bf251d4a01f17fa7d41c9500f.tar.xz
start adding nbt to the protocol
Diffstat (limited to 'azalea-nbt')
-rw-r--r--azalea-nbt/Cargo.toml6
-rw-r--r--azalea-nbt/benches/my_benchmark.rs14
-rw-r--r--azalea-nbt/src/decode.rs84
-rw-r--r--azalea-nbt/src/error.rs10
-rw-r--r--azalea-nbt/tests/tests.rs61
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);
}