Skip to content

Commit 6338fc6

Browse files
authored
serde support for valence_nbt + fixes. (#352)
## Description - Reorganize `valence_nbt` and feature flag the different parts. SNBT and binary serialization are each behind their own flags. - Add optional serde support to `valence_nbt` behind the `serde` flag. This is useful for end users working with `valence_nbt` and allows us to simplify some code in `valence_biome` and `valence_dimension`. Note that this includes a `Serializer` and `Deserializer` for `Compound` and _not_ the binary and SNBT formats. In other words, it's not possible to go directly from an arbitrary data format to binary NBT and vice versa, but it _is_ possible to go to and from `Compound` and finish the (de)serialization this way. I consider this an acceptable compromise because writing fast and correct serialization routines for `Compound` becomes more difficult when serde is in the way. Besides, the intermediate `Compound` often needs to be created anyway. - Fixed unsound uses of `std::mem::transmute` in `valence_nbt` and elsewhere. Using `transmute` to cast between slice types is unsound because slices are `#[repr(Rust)]` and the layouts are not guaranteed. `slice::from_raw_parts` is used as the replacement.
1 parent 975014e commit 6338fc6

File tree

18 files changed

+1415
-248
lines changed

18 files changed

+1415
-248
lines changed

Cargo.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ num-integer = "0.1.45"
4747
owo-colors = "3.5.0"
4848
parking_lot = "0.12.1"
4949
paste = "1.0.11"
50+
pretty_assertions = "1.3.0"
5051
proc-macro2 = "1.0.56"
5152
quote = "1.0.26"
5253
rand = "0.8.5"
@@ -84,7 +85,7 @@ valence_dimension.path = "crates/valence_dimension"
8485
valence_entity.path = "crates/valence_entity"
8586
valence_instance.path = "crates/valence_instance"
8687
valence_inventory.path = "crates/valence_inventory"
87-
valence_nbt = { path = "crates/valence_nbt", features = ["uuid"] }
88+
valence_nbt = { path = "crates/valence_nbt" }
8889
valence_network.path = "crates/valence_network"
8990
valence_player_list.path = "crates/valence_player_list"
9091
valence_registry.path = "crates/valence_registry"

crates/valence_anvil/src/lib.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ pub enum ReadChunkError {
5656
#[error(transparent)]
5757
Io(#[from] io::Error),
5858
#[error(transparent)]
59-
Nbt(#[from] valence_nbt::Error),
59+
Nbt(#[from] valence_nbt::binary::Error),
6060
#[error("invalid chunk sector offset")]
6161
BadSectorOffset,
6262
#[error("invalid chunk size")]
@@ -180,7 +180,7 @@ impl AnvilWorld {
180180
b => return Err(ReadChunkError::UnknownCompressionScheme(b)),
181181
};
182182

183-
let (data, _) = valence_nbt::from_binary_slice(&mut nbt_slice)?;
183+
let (data, _) = Compound::from_binary(&mut nbt_slice)?;
184184

185185
if !nbt_slice.is_empty() {
186186
return Err(ReadChunkError::IncompleteNbtRead);

crates/valence_core/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ serde_json.workspace = true
2424
thiserror.workspace = true
2525
tracing.workspace = true
2626
uuid = { workspace = true, features = ["serde"] }
27-
valence_nbt.workspace = true
27+
valence_nbt = { workspace = true, features = ["binary"] }
2828
valence_core_macros.workspace = true
2929
url.workspace = true
3030
base64.workspace = true

crates/valence_core/src/protocol/impls.rs

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
//! [`Encode`] and [`Decode`] impls on foreign types.
22
3+
use core::slice;
34
use std::borrow::Cow;
45
use std::collections::{BTreeSet, HashSet};
56
use std::hash::{BuildHasher, Hash};
@@ -27,8 +28,8 @@ impl Encode for bool {
2728

2829
fn encode_slice(slice: &[bool], mut w: impl Write) -> Result<()> {
2930
// SAFETY: Bools have the same layout as u8.
31+
let bytes = unsafe { slice::from_raw_parts(slice.as_ptr() as *const u8, slice.len()) };
3032
// Bools are guaranteed to have the correct bit pattern.
31-
let bytes: &[u8] = unsafe { mem::transmute(slice) };
3233
Ok(w.write_all(bytes)?)
3334
}
3435
}
@@ -64,7 +65,7 @@ impl Encode for i8 {
6465

6566
fn encode_slice(slice: &[i8], mut w: impl Write) -> Result<()> {
6667
// SAFETY: i8 has the same layout as u8.
67-
let bytes: &[u8] = unsafe { mem::transmute(slice) };
68+
let bytes = unsafe { slice::from_raw_parts(slice.as_ptr() as *const u8, slice.len()) };
6869
Ok(w.write_all(bytes)?)
6970
}
7071
}
@@ -471,7 +472,6 @@ impl<const N: usize, T: Encode> Encode for [T; N] {
471472
impl<'a, const N: usize, T: Decode<'a>> Decode<'a> for [T; N] {
472473
fn decode(r: &mut &'a [u8]) -> Result<Self> {
473474
// TODO: rewrite using std::array::try_from_fn when stabilized?
474-
// TODO: specialization for [f64; 3] improved performance.
475475

476476
let mut data: [MaybeUninit<T>; N] = unsafe { MaybeUninit::uninit().assume_init() };
477477

@@ -539,9 +539,12 @@ impl<'a> Decode<'a> for &'a [u8] {
539539

540540
impl<'a> Decode<'a> for &'a [i8] {
541541
fn decode(r: &mut &'a [u8]) -> Result<Self> {
542-
let unsigned_bytes = <&[u8]>::decode(r)?;
543-
let signed_bytes: &[i8] = unsafe { mem::transmute(unsigned_bytes) };
544-
Ok(signed_bytes)
542+
let bytes = <&[u8]>::decode(r)?;
543+
544+
// SAFETY: i8 and u8 have the same layout.
545+
let bytes = unsafe { slice::from_raw_parts(bytes.as_ptr() as *const i8, bytes.len()) };
546+
547+
Ok(bytes)
545548
}
546549
}
547550

@@ -765,12 +768,12 @@ impl<'a> Decode<'a> for Uuid {
765768

766769
impl Encode for Compound {
767770
fn encode(&self, w: impl Write) -> Result<()> {
768-
Ok(valence_nbt::to_binary_writer(w, self, "")?)
771+
Ok(self.to_binary(w, "")?)
769772
}
770773
}
771774

772775
impl Decode<'_> for Compound {
773776
fn decode(r: &mut &[u8]) -> Result<Self> {
774-
Ok(valence_nbt::from_binary_slice(r)?.0)
777+
Ok(Self::from_binary(r)?.0)
775778
}
776779
}

crates/valence_nbt/Cargo.toml

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,20 @@ version = "0.5.0"
1010
edition.workspace = true
1111

1212
[features]
13+
binary = ["dep:byteorder", "dep:cesu8"]
14+
snbt = []
1315
# When enabled, the order of fields in compounds are preserved.
1416
preserve_order = ["dep:indexmap"]
17+
serde = ["dep:serde", "dep:thiserror", "indexmap?/serde"]
1518

1619
[dependencies]
17-
byteorder.workspace = true
18-
cesu8.workspace = true
20+
byteorder = { workspace = true, optional = true }
21+
cesu8 = { workspace = true, optional = true }
1922
indexmap = { workspace = true, optional = true }
23+
serde = { workspace = true, features = ["derive"], optional = true }
24+
thiserror = { workspace = true, optional = true }
2025
uuid = { workspace = true, optional = true }
26+
27+
[dev-dependencies]
28+
pretty_assertions.workspace = true
29+
serde_json.workspace = true

crates/valence_nbt/README.md

Lines changed: 3 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -5,48 +5,10 @@ format.
55

66
[Named Binary Tag]: https://minecraft.fandom.com/wiki/NBT_format
77

8-
# Examples
9-
10-
Encode NBT data to its binary form. We are using the [`compound!`] macro to
11-
conveniently construct [`Compound`] values.
12-
13-
```rust
14-
use valence_nbt::{compound, to_binary_writer, List};
15-
16-
let c = compound! {
17-
"byte" => 5_i8,
18-
"string" => "hello",
19-
"list_of_float" => List::Float(vec![
20-
3.1415,
21-
2.7182,
22-
1.4142
23-
]),
24-
};
25-
26-
let mut buf = vec![];
27-
28-
to_binary_writer(&mut buf, &c, "").unwrap();
29-
```
30-
31-
Decode NBT data from its binary form.
32-
33-
```rust
34-
use valence_nbt::{compound, from_binary_slice};
35-
36-
let some_bytes = [10, 0, 0, 3, 0, 3, 105, 110, 116, 0, 0, 222, 173, 0];
37-
38-
let expected_value = compound! {
39-
"int" => 0xdead
40-
};
41-
42-
let (nbt, root_name) = from_binary_slice(&mut some_bytes.as_slice()).unwrap();
43-
44-
assert_eq!(nbt, expected_value);
45-
assert_eq!(root_name, "");
46-
```
47-
488
# Features
49-
9+
- `binary`: Adds support for serializing and deserializing in Java edition's binary format.
10+
- `snbt`: Adds support for serializing and deserializing in "stringified" format.
5011
- `preserve_order`: Causes the order of fields in [`Compound`]s to be
5112
preserved during insertion and deletion at a slight cost to performance.
5213
The iterators on `Compound` can then implement [`DoubleEndedIterator`].
14+
- `serde` Adds support for [`serde`](https://docs.rs/serde/latest/serde/)

crates/valence_nbt/src/binary.rs

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
//! Support for serializing and deserializing compounds in Java edition's binary
2+
//! format.
3+
//!
4+
//! # Examples
5+
//!
6+
//! ```
7+
//! use valence_nbt::{compound, Compound, List};
8+
//!
9+
//! let c = compound! {
10+
//! "byte" => 5_i8,
11+
//! "string" => "hello",
12+
//! "list_of_float" => List::Float(vec![
13+
//! 3.1415,
14+
//! 2.7182,
15+
//! 1.4142
16+
//! ]),
17+
//! };
18+
//!
19+
//! let mut buf = vec![];
20+
//!
21+
//! c.to_binary(&mut buf, "").unwrap();
22+
//! ```
23+
//!
24+
//! Decode NBT data from its binary form.
25+
//!
26+
//! ```
27+
//! use valence_nbt::{compound, Compound};
28+
//!
29+
//! let some_bytes = [10, 0, 0, 3, 0, 3, 105, 110, 116, 0, 0, 222, 173, 0];
30+
//!
31+
//! let expected_value = compound! {
32+
//! "int" => 0xdead
33+
//! };
34+
//!
35+
//! let (nbt, root_name) = Compound::from_binary(&mut some_bytes.as_slice()).unwrap();
36+
//!
37+
//! assert_eq!(nbt, expected_value);
38+
//! assert_eq!(root_name, "");
39+
//! ```
40+
41+
mod decode;
42+
mod encode;
43+
mod error;
44+
mod modified_utf8;
45+
#[cfg(test)]
46+
mod tests;
47+
48+
pub use error::*;

0 commit comments

Comments
 (0)