mirror of
https://github.com/Pumpkin-MC/Pumpkin.git
synced 2026-08-30 20:14:23 +00:00
feat(codec): implement Encode and Decode for some pumpkin-util items and convenience codec macros (#2111)
* added identifier codec * added codec impls for `Vector2<T>` and `Vector3<T>` * added codec impl for `BlockPos` * added codec impls for `BlockBox` and `EulerAngle` * fixed formatting and clippy * added `BlockPos` test * fixed clippy and formatting * fixed codec mapping macros and started using them in tests * fixed formatting
This commit is contained in:
1
Cargo.lock
generated
1
Cargo.lock
generated
@@ -3008,6 +3008,7 @@ dependencies = [
|
||||
"num-traits",
|
||||
"p384",
|
||||
"proc-macro2",
|
||||
"pumpkin-codecs",
|
||||
"pumpkin-nbt",
|
||||
"quote",
|
||||
"rsa",
|
||||
|
||||
@@ -133,6 +133,75 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
/// A wrapper around a `Vec` that cannot have it be empty, similar to Minecraft's `ExtraCodecs.nonEmptyList`.
|
||||
pub struct NonEmptyVec<T>(Vec<T>);
|
||||
|
||||
impl<T> From<NonEmptyVec<T>> for Vec<T> {
|
||||
/// Returns the wrapped `Vec` of this `NonEmptyVec`.
|
||||
fn from(value: NonEmptyVec<T>) -> Self {
|
||||
value.0
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Encode for NonEmptyVec<T>
|
||||
where
|
||||
T: Encode,
|
||||
{
|
||||
fn encode<O: DynamicOps>(&self, ops: &'static O, prefix: O::Value) -> DataResult<O::Value> {
|
||||
if self.0.is_empty() {
|
||||
DataResult::new_error("List must have contents")
|
||||
} else {
|
||||
self.0.encode(ops, prefix)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Decode for NonEmptyVec<T>
|
||||
where
|
||||
T: Decode,
|
||||
{
|
||||
fn decode<O: DynamicOps>(input: O::Value, ops: &'static O) -> DataResult<(Self, O::Value)> {
|
||||
Vec::<T>::decode(input, ops).flat_map(|(v, c)| Self::flat_try_from(v).map(|v| (v, c)))
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> FlatTryFrom<Vec<T>> for NonEmptyVec<T> {
|
||||
fn flat_try_from(value: Vec<T>) -> DataResult<Self> {
|
||||
if value.is_empty() {
|
||||
DataResult::new_error("List must have contents")
|
||||
} else {
|
||||
DataResult::new_success(Self(value))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Utility functions
|
||||
|
||||
/// Tries to check a list to have a fixed size `size`, returning the appropriate
|
||||
/// [`DataResult`].
|
||||
///
|
||||
/// # Arguments
|
||||
/// - `list`: The list to validate.
|
||||
/// - `size`: The required size.
|
||||
///
|
||||
/// # Returns
|
||||
/// A successful result if `list.len() == size`; otherwise, it returns a partial/non-result.
|
||||
/// If this result is partial, it will also be `size` elements long.
|
||||
pub fn validate_fixed_size<T>(list: Vec<T>, size: usize) -> DataResult<Vec<T>> {
|
||||
if list.len() == size {
|
||||
DataResult::new_success(list)
|
||||
} else {
|
||||
let message = format!("Input is not a list of {size} elements");
|
||||
if list.len() > size {
|
||||
DataResult::new_partial_error(message, list.into_iter().take(size).collect())
|
||||
} else {
|
||||
DataResult::new_error(message)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use crate::assert_decode;
|
||||
|
||||
@@ -99,7 +99,10 @@ where
|
||||
mod test {
|
||||
use crate::codec::*;
|
||||
use crate::json_ops::JsonOps;
|
||||
use crate::{assert_decode, assert_encode_success};
|
||||
use crate::{
|
||||
FlatTryFrom, assert_decode, assert_encode_success, comap_flat_map_codec_impl,
|
||||
flat_xmap_codec_impl,
|
||||
};
|
||||
use serde_json::json;
|
||||
use std::collections::HashMap;
|
||||
use std::fmt::{Display, Formatter};
|
||||
@@ -140,32 +143,25 @@ mod test {
|
||||
}
|
||||
}
|
||||
|
||||
impl Encode for StringInteger {
|
||||
fn encode<O: DynamicOps>(
|
||||
&self,
|
||||
ops: &'static O,
|
||||
prefix: O::Value,
|
||||
) -> DataResult<O::Value> {
|
||||
impl From<&StringInteger> for String {
|
||||
fn from(value: &StringInteger) -> Self {
|
||||
// This will always succeed.
|
||||
self.0.to_string().encode(ops, prefix)
|
||||
value.0.to_string()
|
||||
}
|
||||
}
|
||||
|
||||
impl Decode for StringInteger {
|
||||
fn decode<O: DynamicOps>(
|
||||
input: O::Value,
|
||||
ops: &'static O,
|
||||
) -> DataResult<(Self, O::Value)> {
|
||||
String::decode(input, ops).flat_map(|s| {
|
||||
// Try to parse an integer.
|
||||
s.0.parse().map_or_else(
|
||||
|_| DataResult::new_error("Could not parse String"),
|
||||
|i| DataResult::new_success((Self(i), s.1)),
|
||||
)
|
||||
})
|
||||
impl FlatTryFrom<String> for StringInteger {
|
||||
fn flat_try_from(value: String) -> DataResult<Self> {
|
||||
// Try to parse an integer.
|
||||
value.parse().map_or_else(
|
||||
|_| DataResult::new_error("Could not parse String"),
|
||||
|i| DataResult::new_success(Self(i)),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
comap_flat_map_codec_impl!(String => StringInteger, StringInteger::flat_try_from, String::from);
|
||||
|
||||
let mut map = HashMap::<StringInteger, bool>::new();
|
||||
|
||||
// Calculate the map for the first 20 numbers.
|
||||
@@ -198,41 +194,31 @@ mod test {
|
||||
#[derive(Debug, Clone, PartialOrd, PartialEq, Eq, Hash)]
|
||||
struct Letter(String);
|
||||
|
||||
fn check_letter(string: String) -> DataResult<Letter> {
|
||||
// Try to parse a single letter.
|
||||
if string.len() == 1 {
|
||||
DataResult::new_success(Letter(string))
|
||||
} else {
|
||||
DataResult::new_error(format!("Not a letter: {string}"))
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for Letter {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{}", self.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl Encode for Letter {
|
||||
fn encode<O: DynamicOps>(
|
||||
&self,
|
||||
ops: &'static O,
|
||||
prefix: O::Value,
|
||||
) -> DataResult<O::Value> {
|
||||
// This will always succeed.
|
||||
check_letter(self.0.clone()).flat_map(|l| l.0.encode(ops, prefix))
|
||||
impl FlatTryFrom<String> for Letter {
|
||||
fn flat_try_from(value: String) -> DataResult<Self> {
|
||||
// Try to parse a single letter.
|
||||
if value.len() == 1 {
|
||||
DataResult::new_success(Self(value))
|
||||
} else {
|
||||
DataResult::new_error(format!("Not a letter: {value}"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Decode for Letter {
|
||||
fn decode<O: DynamicOps>(
|
||||
input: O::Value,
|
||||
ops: &'static O,
|
||||
) -> DataResult<(Self, O::Value)> {
|
||||
String::decode(input, ops).flat_map(|(s, v)| check_letter(s).map(|l| (l, v)))
|
||||
impl FlatTryFrom<&Letter> for String {
|
||||
fn flat_try_from(value: &Letter) -> DataResult<Self> {
|
||||
Letter::flat_try_from(value.0.clone()).map(|l| l.0)
|
||||
}
|
||||
}
|
||||
|
||||
flat_xmap_codec_impl!(String => Letter, Letter::flat_try_from, String::flat_try_from);
|
||||
|
||||
type LetterData = HashMap<Letter, u64>;
|
||||
|
||||
let mut map = LetterData::new();
|
||||
|
||||
@@ -140,3 +140,121 @@ impl<T: Decode> FieldDecode for T {
|
||||
decoded_option.map(|o| o.unwrap_or(default))
|
||||
}
|
||||
}
|
||||
|
||||
/// Provides a fallible/infallible encode conversion from a first type to a second type via `backward`.
|
||||
///
|
||||
/// You probably do not need to use this directly.
|
||||
#[macro_export]
|
||||
macro_rules! encode_impl {
|
||||
(infallible $second_type:ty, $backward:path) => {
|
||||
impl $crate::Encode for $second_type {
|
||||
fn encode<O: $crate::DynamicOps>(
|
||||
&self,
|
||||
ops: &'static O,
|
||||
prefix: O::Value,
|
||||
) -> $crate::DataResult<O::Value> {
|
||||
$backward(self).encode(ops, prefix)
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
(fallible $second_type:ty, $backward:path) => {
|
||||
impl $crate::Encode for $second_type {
|
||||
fn encode<O: $crate::DynamicOps>(
|
||||
&self,
|
||||
ops: &'static O,
|
||||
prefix: O::Value,
|
||||
) -> $crate::DataResult<O::Value> {
|
||||
$backward(self).flat_map(|m| m.encode(ops, prefix))
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/// Provides a fallible/infallible decode conversion from a first type to a second type via `forward`.
|
||||
///
|
||||
/// You probably do not need to use this directly.
|
||||
#[macro_export]
|
||||
macro_rules! decode_impl {
|
||||
(infallible $first_type:ty, $second_type:ty, $forward:path) => {
|
||||
impl $crate::Decode for $second_type {
|
||||
fn decode<O: $crate::DynamicOps>(
|
||||
input: O::Value,
|
||||
ops: &'static O,
|
||||
) -> $crate::DataResult<(Self, O::Value)> {
|
||||
<$first_type>::decode(input, ops).map(|(s, p)| ($forward(s), p))
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
(fallible $first_type:ty, $second_type:ty, $forward:path) => {
|
||||
impl $crate::Decode for $second_type {
|
||||
fn decode<O: $crate::DynamicOps>(
|
||||
input: O::Value,
|
||||
ops: &'static O,
|
||||
) -> $crate::DataResult<(Self, O::Value)> {
|
||||
<$first_type>::decode(input, ops).flat_map(|(s, p)| $forward(s).map(|m| (m, p)))
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/// Provides easy `xmap`-like `Encode` and `Decode` implementations of a *second type*
|
||||
/// by using the already-existing implementations of a *first type*.
|
||||
///
|
||||
/// The macro is written as `xmap_codec_impl!(first => second, forward, backward)`,
|
||||
/// where:
|
||||
/// - `forward` is the infallible conversion `fn(first) -> second` (for decoding).
|
||||
/// - `backward` is the infallible conversion `fn(&second) -> first` (for encoding).
|
||||
#[macro_export]
|
||||
macro_rules! xmap_codec_impl {
|
||||
($first_type:ty => $second_type:ty, $forward:path, $backward:path) => {
|
||||
$crate::encode_impl!(infallible $second_type, $backward);
|
||||
$crate::decode_impl!(infallible $first_type, $second_type, $forward);
|
||||
};
|
||||
}
|
||||
|
||||
/// Provides easy `comapFlatMap`-like `Encode` and `Decode` implementations of a *second type*
|
||||
/// by using the already-existing implementations of a *first type*.
|
||||
///
|
||||
/// The macro is written as `comap_flat_map_codec_impl!(first => second, forward, backward)`,
|
||||
/// where:
|
||||
/// - `forward` is the fallible conversion `fn(first) -> DataResult<second>` (for decoding).
|
||||
/// - `backward` is the infallible conversion `fn(&second) -> first` (for encoding).
|
||||
#[macro_export]
|
||||
macro_rules! comap_flat_map_codec_impl {
|
||||
($first_type:ty => $second_type:ty, $forward:path, $backward:path) => {
|
||||
$crate::encode_impl!(infallible $second_type, $backward);
|
||||
$crate::decode_impl!(fallible $first_type, $second_type, $forward);
|
||||
};
|
||||
}
|
||||
|
||||
/// Provides easy `flatComapMap`-like `Encode` and `Decode` implementations of a *second type*
|
||||
/// by using the already-existing implementations of a *first type*.
|
||||
///
|
||||
/// The macro is written as `flat_comap_map_codec_impl!(first => second, forward, backward)`,
|
||||
/// where:
|
||||
/// - `forward` is the infallible conversion `fn(first) -> second` (for decoding).
|
||||
/// - `backward` is the fallible conversion `fn(&second) -> DataResult<first>` (for encoding).
|
||||
#[macro_export]
|
||||
macro_rules! flat_comap_map_codec_impl {
|
||||
($first_type:ty => $second_type:ty, $forward:path, $backward:path) => {
|
||||
$crate::encode_impl!(fallible $second_type, $backward);
|
||||
$crate::decode_impl!(infallible $first_type, $second_type, $forward);
|
||||
};
|
||||
}
|
||||
|
||||
/// Provides easy `flatXmap`-like `Encode` and `Decode` implementations of a *second type*
|
||||
/// by using the already-existing implementations of a *first type*.
|
||||
///
|
||||
/// The macro is written as `flat_xmap_codec_impl!(first => second, forward, backward)`,
|
||||
/// where:
|
||||
/// - `forward` is the fallible conversion `fn(first) -> DataResult<second>` (for decoding).
|
||||
/// - `backward` is the fallible conversion `fn(&second) -> DataResult<first>` (for encoding).
|
||||
#[macro_export]
|
||||
macro_rules! flat_xmap_codec_impl {
|
||||
($first_type:ty => $second_type:ty, $forward:path, $backward:path) => {
|
||||
$crate::encode_impl!(fallible $second_type, $backward);
|
||||
$crate::decode_impl!(fallible $first_type, $second_type, $forward);
|
||||
};
|
||||
}
|
||||
|
||||
1
pumpkin-codegen/Cargo.lock
generated
1
pumpkin-codegen/Cargo.lock
generated
@@ -830,6 +830,7 @@ dependencies = [
|
||||
"num-traits",
|
||||
"p384",
|
||||
"proc-macro2",
|
||||
"pumpkin-codecs",
|
||||
"pumpkin-nbt",
|
||||
"quote",
|
||||
"rsa",
|
||||
|
||||
@@ -7,6 +7,7 @@ license.workspace = true
|
||||
|
||||
[dependencies]
|
||||
pumpkin-nbt.workspace = true
|
||||
pumpkin-codecs.workspace = true
|
||||
serde.workspace = true
|
||||
serde_json.workspace = true
|
||||
bytes.workspace = true
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
use std::{borrow::Cow, fmt::Display};
|
||||
|
||||
use pumpkin_codecs::{DataResult, FlatTryFrom, comap_flat_map_codec_impl};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use thiserror::Error;
|
||||
|
||||
@@ -382,9 +383,23 @@ impl<'de> Deserialize<'de> for Identifier {
|
||||
}
|
||||
}
|
||||
|
||||
comap_flat_map_codec_impl!(String => Identifier, Identifier::flat_try_from, ToString::to_string);
|
||||
|
||||
impl FlatTryFrom<String> for Identifier {
|
||||
fn flat_try_from(value: String) -> DataResult<Self> {
|
||||
Self::parse(&value).map_or_else(
|
||||
|_| DataResult::new_error(format!("Not a valid resource location: {value}")),
|
||||
DataResult::new_success,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use crate::identifier::{Identifier, IdentifierError};
|
||||
use pumpkin_codecs::json_ops::JsonOps;
|
||||
use pumpkin_codecs::{assert_decode, assert_encode_success};
|
||||
use serde_json::json;
|
||||
|
||||
#[test]
|
||||
fn new() -> Result<(), IdentifierError> {
|
||||
@@ -416,4 +431,28 @@ mod test {
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn codec() {
|
||||
assert_encode_success!(
|
||||
Identifier::from_static("abc", "def"),
|
||||
JsonOps,
|
||||
json!("abc:def")
|
||||
);
|
||||
assert_encode_success!(
|
||||
Identifier::from_static("", "no_namespace"),
|
||||
JsonOps,
|
||||
json!(":no_namespace")
|
||||
);
|
||||
assert_encode_success!(
|
||||
Identifier::vanilla_static("example"),
|
||||
JsonOps,
|
||||
json!("minecraft:example")
|
||||
);
|
||||
|
||||
assert_decode!(Identifier, json!("abc:def"), JsonOps, is_success);
|
||||
assert_decode!(Identifier, json!("vanilla"), JsonOps, is_success);
|
||||
assert_decode!(Identifier, json!("2 + 3"), JsonOps, is_error);
|
||||
assert_decode!(Identifier, json!("a._b-c:/4_-/5.9"), JsonOps, is_success);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,8 @@ use crate::{
|
||||
vector3::{Axis, Vector3},
|
||||
},
|
||||
};
|
||||
use pumpkin_codecs::codec::list::validate_fixed_size;
|
||||
use pumpkin_codecs::{DataResult, FlatTryFrom, IntStream, comap_flat_map_codec_impl};
|
||||
|
||||
/// Represents an axis-aligned 3D block bounding box in integer coordinates.
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
@@ -262,3 +264,34 @@ impl BlockBox {
|
||||
Some(result)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&BlockBox> for IntStream {
|
||||
fn from(value: &BlockBox) -> Self {
|
||||
let Vector3 {
|
||||
x: min_x,
|
||||
y: min_y,
|
||||
z: min_z,
|
||||
} = value.min;
|
||||
let Vector3 {
|
||||
x: max_x,
|
||||
y: max_y,
|
||||
z: max_z,
|
||||
} = value.max;
|
||||
Self(vec![min_x, min_y, min_z, max_x, max_y, max_z])
|
||||
}
|
||||
}
|
||||
|
||||
impl FlatTryFrom<IntStream> for BlockBox {
|
||||
fn flat_try_from(value: IntStream) -> DataResult<Self> {
|
||||
validate_fixed_size(value.0, 6).map(|v| {
|
||||
let [min_x, min_y, min_z, max_x, max_y, max_z]: [i32; 6] =
|
||||
v.try_into().unwrap_or_else(|_| unreachable!());
|
||||
Self {
|
||||
min: Vector3::new(min_x, min_y, min_z),
|
||||
max: Vector3::new(max_x, max_y, max_z),
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
comap_flat_map_codec_impl!(IntStream => BlockBox, BlockBox::flat_try_from, IntStream::from);
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
use pumpkin_codecs::codec::list::validate_fixed_size;
|
||||
use pumpkin_codecs::{DataResult, FlatTryFrom, comap_flat_map_codec_impl};
|
||||
use pumpkin_nbt::tag::NbtTag;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
@@ -81,3 +83,25 @@ impl From<NbtTag> for EulerAngle {
|
||||
Self::ZERO
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&EulerAngle> for Vec<f32> {
|
||||
fn from(value: &EulerAngle) -> Self {
|
||||
let EulerAngle { pitch, yaw, roll } = value;
|
||||
vec![*pitch, *yaw, *roll]
|
||||
}
|
||||
}
|
||||
|
||||
impl FlatTryFrom<Vec<f32>> for EulerAngle {
|
||||
fn flat_try_from(value: Vec<f32>) -> DataResult<Self> {
|
||||
validate_fixed_size(value, 6).map(|v| {
|
||||
let [x, y, z]: [f32; 3] = v.try_into().unwrap_or_else(|_| unreachable!());
|
||||
Self {
|
||||
pitch: x,
|
||||
yaw: y,
|
||||
roll: z,
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
comap_flat_map_codec_impl!(Vec<f32> => EulerAngle, EulerAngle::flat_try_from, Vec::<f32>::from);
|
||||
|
||||
@@ -404,6 +404,41 @@ pub fn java_string_hash(string: &str) -> i32 {
|
||||
result
|
||||
}
|
||||
|
||||
macro_rules! vector_codec_impl {
|
||||
($vector:ty, $number:literal, $($components:ident),+ ) => {
|
||||
impl<T> From<&$vector> for Vec<T> where T: Clone {
|
||||
fn from(value: &$vector) -> Self {
|
||||
vec![
|
||||
$( value.$components.clone(), )+
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> FlatTryFrom<Vec<T>> for $vector {
|
||||
fn flat_try_from(value: Vec<T>) -> DataResult<Self> {
|
||||
validate_fixed_size(value, 2)
|
||||
.map(|v| {
|
||||
let [ $( $components, )+ ]: [T; $number] = v.try_into().unwrap_or_else(|_| unreachable!());
|
||||
Self { $( $components, )+ }
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Encode for $vector where T: Encode + Clone {
|
||||
fn encode<O: DynamicOps>(&self, ops: &'static O, prefix: O::Value) -> DataResult<O::Value> {
|
||||
Vec::<T>::from(self).encode(ops, prefix)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Decode for $vector where T: Decode {
|
||||
fn decode<O: DynamicOps>(input: O::Value, ops: &'static O) -> DataResult<(Self, O::Value)> {
|
||||
<Vec<T>>::decode(input, ops).flat_map(|(s, p)| Self::flat_try_from(s).map(|v| (v, p)))
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
pub(crate) use vector_codec_impl;
|
||||
|
||||
/// Tests the Java-style string and array hash implementations.
|
||||
///
|
||||
/// This verifies that `java_string_hash` and `java_array_hash` produce the expected
|
||||
|
||||
@@ -7,6 +7,8 @@ use std::hash::Hash;
|
||||
|
||||
use crate::math::vector2::Vector2;
|
||||
use num_traits::Euclid;
|
||||
use pumpkin_codecs::codec::list::validate_fixed_size;
|
||||
use pumpkin_codecs::{DataResult, FlatTryFrom, IntStream, comap_flat_map_codec_impl};
|
||||
use serde::{Deserialize, Deserializer, Serialize, Serializer};
|
||||
|
||||
/// An iterator that yields all `BlockPos` positions within a cuboid region.
|
||||
@@ -631,6 +633,24 @@ impl<'de> Deserialize<'de> for BlockPos {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&BlockPos> for IntStream {
|
||||
fn from(value: &BlockPos) -> Self {
|
||||
let Vector3 { x, y, z } = value.0;
|
||||
Self(vec![x, y, z])
|
||||
}
|
||||
}
|
||||
|
||||
impl FlatTryFrom<IntStream> for BlockPos {
|
||||
fn flat_try_from(value: IntStream) -> DataResult<Self> {
|
||||
validate_fixed_size(value.0, 3).map(|v| {
|
||||
let [x, y, z]: [i32; 3] = v.try_into().unwrap_or_else(|_| unreachable!());
|
||||
Self(Vector3::new(x, y, z))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
comap_flat_map_codec_impl!(IntStream => BlockPos, BlockPos::flat_try_from, IntStream::from);
|
||||
|
||||
impl fmt::Display for BlockPos {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "{}, {}, {}", self.0.x, self.0.y, self.0.z)
|
||||
@@ -685,3 +705,61 @@ pub const fn pack_local_chunk_section(block_pos: &BlockPos) -> i16 {
|
||||
let y = get_local_cord(block_pos.0.y);
|
||||
vector3::packed_local(&Vector3::new(x, y, z))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use crate::math::position::BlockPos;
|
||||
use pumpkin_codecs::{assert_decode, assert_encode_success};
|
||||
use pumpkin_nbt::nbt_ops::NbtOps;
|
||||
use pumpkin_nbt::tag::NbtTag;
|
||||
|
||||
#[test]
|
||||
fn codec() {
|
||||
assert_encode_success!(
|
||||
BlockPos::new(1, 2, 3),
|
||||
NbtOps,
|
||||
NbtTag::IntArray(vec![1, 2, 3])
|
||||
);
|
||||
assert_encode_success!(
|
||||
BlockPos::new(-1000, 200, 4521),
|
||||
NbtOps,
|
||||
NbtTag::IntArray(vec![-1000, 200, 4521])
|
||||
);
|
||||
|
||||
assert_decode!(
|
||||
BlockPos,
|
||||
NbtTag::IntArray(vec![1, 2, 3]),
|
||||
NbtOps,
|
||||
is_success
|
||||
);
|
||||
|
||||
assert_decode!(BlockPos, NbtTag::List(vec![]), NbtOps, is_error);
|
||||
assert_decode!(
|
||||
BlockPos,
|
||||
NbtTag::List(vec![NbtTag::Int(1), NbtTag::Float(2.0), NbtTag::Int(3)]),
|
||||
NbtOps,
|
||||
is_success
|
||||
);
|
||||
assert_decode!(
|
||||
BlockPos,
|
||||
NbtTag::List(vec![
|
||||
NbtTag::Int(1),
|
||||
NbtTag::Float(2.0),
|
||||
NbtTag::String("69".to_string())
|
||||
]),
|
||||
NbtOps,
|
||||
is_error
|
||||
);
|
||||
assert_decode!(
|
||||
BlockPos,
|
||||
NbtTag::List(vec![
|
||||
NbtTag::Int(1),
|
||||
NbtTag::Float(2.0),
|
||||
NbtTag::Int(3),
|
||||
NbtTag::Byte(3)
|
||||
]),
|
||||
NbtOps,
|
||||
is_error
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
use std::ops::{Add, Div, Mul, Neg, Sub};
|
||||
|
||||
use super::vector3::Vector3;
|
||||
use crate::math::vector_codec_impl;
|
||||
use bytes::BufMut;
|
||||
use num_traits::Float;
|
||||
|
||||
use super::vector3::Vector3;
|
||||
use pumpkin_codecs::codec::list::validate_fixed_size;
|
||||
use pumpkin_codecs::{DataResult, Decode, DynamicOps, Encode, FlatTryFrom};
|
||||
|
||||
/// A 2-dimensional vector with generic numeric components.
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Hash, Eq, Default)]
|
||||
@@ -182,3 +184,5 @@ impl serde::Serialize for Vector2<f32> {
|
||||
serializer.serialize_bytes(&buf)
|
||||
}
|
||||
}
|
||||
|
||||
vector_codec_impl!(Vector2<T>, 2, x, y);
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
use bytes::BufMut;
|
||||
use std::ops::{Add, AddAssign, Div, Mul, Sub};
|
||||
|
||||
use num_traits::{Float, Num};
|
||||
|
||||
use super::position::BlockPos;
|
||||
use super::vector2::Vector2;
|
||||
use crate::math::vector_codec_impl;
|
||||
use num_traits::{Float, Num};
|
||||
use pumpkin_codecs::codec::list::validate_fixed_size;
|
||||
use pumpkin_codecs::{DataResult, Decode, DynamicOps, Encode, FlatTryFrom};
|
||||
|
||||
/// A 3-dimensional vector with components of type `T`.
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Hash, Eq, Default)]
|
||||
@@ -764,6 +766,8 @@ impl serde::Serialize for Vector3<i32> {
|
||||
}
|
||||
}
|
||||
|
||||
vector_codec_impl!(Vector3<T>, 3, x, y, z);
|
||||
|
||||
/// Packs a chunk position vector into a single 64-bit integer.
|
||||
///
|
||||
/// The packing format is:
|
||||
|
||||
Reference in New Issue
Block a user