mirror of
https://github.com/italicsjenga/valence.git
synced 2024-12-23 22:41:30 +11:00
Clean up ident module and add lifetime (#108)
This lifetime will be useful for zero-copy decoding later.
This commit is contained in:
parent
c758f70c33
commit
23fdc41610
12
src/biome.rs
12
src/biome.rs
|
@ -26,7 +26,7 @@ pub struct BiomeId(pub(crate) u16);
|
||||||
pub struct Biome {
|
pub struct Biome {
|
||||||
/// The unique name for this biome. The name can be
|
/// The unique name for this biome. The name can be
|
||||||
/// seen in the F3 debug menu.
|
/// seen in the F3 debug menu.
|
||||||
pub name: Ident,
|
pub name: Ident<'static>,
|
||||||
pub precipitation: BiomePrecipitation,
|
pub precipitation: BiomePrecipitation,
|
||||||
pub sky_color: u32,
|
pub sky_color: u32,
|
||||||
pub water_fog_color: u32,
|
pub water_fog_color: u32,
|
||||||
|
@ -36,7 +36,7 @@ pub struct Biome {
|
||||||
pub grass_color: Option<u32>,
|
pub grass_color: Option<u32>,
|
||||||
pub grass_color_modifier: BiomeGrassColorModifier,
|
pub grass_color_modifier: BiomeGrassColorModifier,
|
||||||
pub music: Option<BiomeMusic>,
|
pub music: Option<BiomeMusic>,
|
||||||
pub ambient_sound: Option<Ident>,
|
pub ambient_sound: Option<Ident<'static>>,
|
||||||
pub additions_sound: Option<BiomeAdditionsSound>,
|
pub additions_sound: Option<BiomeAdditionsSound>,
|
||||||
pub mood_sound: Option<BiomeMoodSound>,
|
pub mood_sound: Option<BiomeMoodSound>,
|
||||||
pub particle: Option<BiomeParticle>,
|
pub particle: Option<BiomeParticle>,
|
||||||
|
@ -202,20 +202,20 @@ pub enum BiomeGrassColorModifier {
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct BiomeMusic {
|
pub struct BiomeMusic {
|
||||||
pub replace_current_music: bool,
|
pub replace_current_music: bool,
|
||||||
pub sound: Ident,
|
pub sound: Ident<'static>,
|
||||||
pub min_delay: i32,
|
pub min_delay: i32,
|
||||||
pub max_delay: i32,
|
pub max_delay: i32,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct BiomeAdditionsSound {
|
pub struct BiomeAdditionsSound {
|
||||||
pub sound: Ident,
|
pub sound: Ident<'static>,
|
||||||
pub tick_chance: f64,
|
pub tick_chance: f64,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct BiomeMoodSound {
|
pub struct BiomeMoodSound {
|
||||||
pub sound: Ident,
|
pub sound: Ident<'static>,
|
||||||
pub tick_delay: i32,
|
pub tick_delay: i32,
|
||||||
pub offset: f64,
|
pub offset: f64,
|
||||||
pub block_search_extent: i32,
|
pub block_search_extent: i32,
|
||||||
|
@ -224,5 +224,5 @@ pub struct BiomeMoodSound {
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct BiomeParticle {
|
pub struct BiomeParticle {
|
||||||
pub probability: f32,
|
pub probability: f32,
|
||||||
pub kind: Ident,
|
pub kind: Ident<'static>,
|
||||||
}
|
}
|
||||||
|
|
|
@ -505,7 +505,7 @@ impl<C: Config> Client<C> {
|
||||||
/// Plays a sound to the client at a given position.
|
/// Plays a sound to the client at a given position.
|
||||||
pub fn play_sound(
|
pub fn play_sound(
|
||||||
&mut self,
|
&mut self,
|
||||||
name: Ident,
|
name: Ident<'static>,
|
||||||
category: SoundCategory,
|
category: SoundCategory,
|
||||||
pos: Vec3<f64>,
|
pos: Vec3<f64>,
|
||||||
volume: f32,
|
volume: f32,
|
||||||
|
@ -514,7 +514,7 @@ impl<C: Config> Client<C> {
|
||||||
self.send_packet(CustomSoundEffect {
|
self.send_packet(CustomSoundEffect {
|
||||||
name,
|
name,
|
||||||
category,
|
category,
|
||||||
position: pos.iter().map(|x| *x as i32 * 8).collect(),
|
position: pos.as_() * 8,
|
||||||
volume,
|
volume,
|
||||||
pitch,
|
pitch,
|
||||||
seed: 0,
|
seed: 0,
|
||||||
|
|
|
@ -17,11 +17,11 @@ use crate::{ident, LIBRARY_NAMESPACE};
|
||||||
pub struct DimensionId(pub(crate) u16);
|
pub struct DimensionId(pub(crate) u16);
|
||||||
|
|
||||||
impl DimensionId {
|
impl DimensionId {
|
||||||
pub(crate) fn dimension_type_name(self) -> Ident {
|
pub(crate) fn dimension_type_name(self) -> Ident<'static> {
|
||||||
ident!("{LIBRARY_NAMESPACE}:dimension_type_{}", self.0)
|
ident!("{LIBRARY_NAMESPACE}:dimension_type_{}", self.0)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn dimension_name(self) -> Ident {
|
pub(crate) fn dimension_name(self) -> Ident<'static> {
|
||||||
ident!("{LIBRARY_NAMESPACE}:dimension_{}", self.0)
|
ident!("{LIBRARY_NAMESPACE}:dimension_{}", self.0)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
286
src/ident.rs
286
src/ident.rs
|
@ -1,29 +1,33 @@
|
||||||
//! Namespaced identifiers.
|
//! Resource identifiers.
|
||||||
|
|
||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
use std::hash;
|
use std::cmp::Ordering;
|
||||||
use std::io::Write;
|
use std::io::Write;
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
|
use std::{fmt, hash};
|
||||||
|
|
||||||
use ascii::{AsAsciiStr, AsciiChar, AsciiStr, IntoAsciiString};
|
use ascii::{AsAsciiStr, AsciiChar, AsciiStr, IntoAsciiString};
|
||||||
|
use hash::Hash;
|
||||||
use serde::de::Visitor;
|
use serde::de::Visitor;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{de, Deserialize, Deserializer, Serialize};
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
use crate::nbt;
|
use crate::nbt;
|
||||||
use crate::protocol::{encode_string_bounded, BoundedString, Decode, Encode};
|
use crate::protocol::{Decode, Encode};
|
||||||
|
|
||||||
/// An identifier is a string split into a "namespace" part and a "path" part.
|
/// A resource identifier is a string divided into a "namespace" part and a
|
||||||
/// For instance `minecraft:apple` and `apple` are both valid identifiers.
|
/// "path" part. For instance `minecraft:apple` and `valence:frobnicator` are
|
||||||
|
/// both valid identifiers.
|
||||||
///
|
///
|
||||||
/// If the namespace part is left off (the part before and including the colon)
|
/// If the namespace part is left off (the part before and including the colon)
|
||||||
/// the namespace is considered to be "minecraft" for the purposes of equality.
|
/// the namespace is considered to be "minecraft" for the purposes of equality,
|
||||||
|
/// ordering, and hashing.
|
||||||
///
|
///
|
||||||
/// A string must match the regex `^([a-z0-9_-]+:)?[a-z0-9_\/.-]+$` to be a
|
/// A string must match the regex `^([a-z0-9_-]+:)?[a-z0-9_\/.-]+$` to be a
|
||||||
/// valid identifier.
|
/// valid identifier.
|
||||||
#[derive(Clone, Eq)]
|
#[derive(Clone, Eq)]
|
||||||
pub struct Ident {
|
pub struct Ident<'a> {
|
||||||
ident: Cow<'static, AsciiStr>,
|
string: Cow<'a, AsciiStr>,
|
||||||
/// The index of the ':' character in the string.
|
/// The index of the ':' character in the string.
|
||||||
/// If there is no namespace then it is `usize::MAX`.
|
/// If there is no namespace then it is `usize::MAX`.
|
||||||
///
|
///
|
||||||
|
@ -32,90 +36,98 @@ pub struct Ident {
|
||||||
colon_idx: usize,
|
colon_idx: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The error type which is created when an [`Ident`] cannot be parsed from a
|
/// The error type created when an [`Ident`] cannot be parsed from a
|
||||||
/// string.
|
/// string. Contains the offending string.
|
||||||
#[derive(Clone, Debug, Error)]
|
#[derive(Clone, Debug, Error)]
|
||||||
#[error("invalid identifier \"{src}\"")]
|
#[error("invalid resource identifier \"{0}\"")]
|
||||||
pub struct ParseError {
|
pub struct IdentParseError<'a>(pub Cow<'a, str>);
|
||||||
src: Cow<'static, str>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Ident {
|
impl<'a> Ident<'a> {
|
||||||
/// Parses a new identifier from a string.
|
/// Parses a new identifier from a string.
|
||||||
///
|
///
|
||||||
/// An error is returned if the string is not a valid identifier.
|
/// An error is returned containing the input string if it is not a valid
|
||||||
pub fn new(str: impl Into<Cow<'static, str>>) -> Result<Ident, ParseError> {
|
/// resource identifier.
|
||||||
|
pub fn new(string: impl Into<Cow<'a, str>>) -> Result<Ident<'a>, IdentParseError<'a>> {
|
||||||
#![allow(bindings_with_variant_name)]
|
#![allow(bindings_with_variant_name)]
|
||||||
|
|
||||||
let cow = match str.into() {
|
let cow = match string.into() {
|
||||||
Cow::Borrowed(s) => {
|
Cow::Borrowed(s) => {
|
||||||
Cow::Borrowed(s.as_ascii_str().map_err(|_| ParseError { src: s.into() })?)
|
Cow::Borrowed(s.as_ascii_str().map_err(|_| IdentParseError(s.into()))?)
|
||||||
}
|
}
|
||||||
Cow::Owned(s) => Cow::Owned(s.into_ascii_string().map_err(|e| ParseError {
|
Cow::Owned(s) => Cow::Owned(
|
||||||
src: e.into_source().into(),
|
s.into_ascii_string()
|
||||||
})?),
|
.map_err(|e| IdentParseError(e.into_source().into()))?,
|
||||||
|
),
|
||||||
};
|
};
|
||||||
|
|
||||||
let s = cow.as_ref();
|
let str = cow.as_ref();
|
||||||
|
|
||||||
let check_namespace = |s: &AsciiStr| {
|
let check_namespace = |s: &AsciiStr| {
|
||||||
!s.is_empty()
|
!s.is_empty()
|
||||||
&& s.chars()
|
&& s.chars()
|
||||||
.all(|c| matches!(c.as_char(), 'a'..='z' | '0'..='9' | '_' | '-'))
|
.all(|c| matches!(c.as_char(), 'a'..='z' | '0'..='9' | '_' | '-'))
|
||||||
};
|
};
|
||||||
let check_name = |s: &AsciiStr| {
|
let check_path = |s: &AsciiStr| {
|
||||||
!s.is_empty()
|
!s.is_empty()
|
||||||
&& s.chars()
|
&& s.chars()
|
||||||
.all(|c| matches!(c.as_char(), 'a'..='z' | '0'..='9' | '_' | '/' | '.' | '-'))
|
.all(|c| matches!(c.as_char(), 'a'..='z' | '0'..='9' | '_' | '/' | '.' | '-'))
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Some(colon_idx) = s.chars().position(|c| c == AsciiChar::Colon) {
|
match str.chars().position(|c| c == AsciiChar::Colon) {
|
||||||
if check_namespace(&s[..colon_idx]) && check_name(&s[colon_idx + 1..]) {
|
Some(colon_idx)
|
||||||
|
if check_namespace(&str[..colon_idx]) && check_path(&str[colon_idx + 1..]) =>
|
||||||
|
{
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
ident: cow,
|
string: cow,
|
||||||
colon_idx,
|
colon_idx,
|
||||||
})
|
})
|
||||||
} else {
|
|
||||||
Err(ParseError {
|
|
||||||
src: ascii_cow_to_str_cow(cow),
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
} else if check_name(s) {
|
None if check_path(str) => Ok(Self {
|
||||||
Ok(Self {
|
string: cow,
|
||||||
ident: cow,
|
|
||||||
colon_idx: usize::MAX,
|
colon_idx: usize::MAX,
|
||||||
})
|
}),
|
||||||
} else {
|
_ => Err(IdentParseError(ascii_cow_to_str_cow(cow))),
|
||||||
Err(ParseError {
|
|
||||||
src: ascii_cow_to_str_cow(cow),
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the namespace part of this namespaced identifier.
|
/// Returns the namespace part of this resource identifier.
|
||||||
///
|
///
|
||||||
/// If this identifier was constructed from a string without a namespace,
|
/// If this identifier was constructed from a string without a namespace,
|
||||||
/// then `None` is returned.
|
/// then "minecraft" is returned.
|
||||||
pub fn namespace(&self) -> Option<&str> {
|
pub fn namespace(&self) -> &str {
|
||||||
if self.colon_idx == usize::MAX {
|
if self.colon_idx != usize::MAX {
|
||||||
None
|
self.string[..self.colon_idx].as_str()
|
||||||
} else {
|
} else {
|
||||||
Some(self.ident[..self.colon_idx].as_str())
|
"minecraft"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the path part of this namespaced identifier.
|
/// Returns the path part of this resource identifier.
|
||||||
pub fn path(&self) -> &str {
|
pub fn path(&self) -> &str {
|
||||||
if self.colon_idx == usize::MAX {
|
if self.colon_idx == usize::MAX {
|
||||||
self.ident.as_str()
|
self.string.as_str()
|
||||||
} else {
|
} else {
|
||||||
self.ident[self.colon_idx + 1..].as_str()
|
self.string[self.colon_idx + 1..].as_str()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the underlying string as a `str`.
|
/// Returns the underlying string as a `str`.
|
||||||
pub fn as_str(&self) -> &str {
|
pub fn as_str(&self) -> &str {
|
||||||
self.ident.as_str()
|
self.string.as_str()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Consumes the identifier and returns the underlying string.
|
||||||
|
pub fn into_inner(self) -> Cow<'a, str> {
|
||||||
|
ascii_cow_to_str_cow(self.string)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Used as the argument to `#[serde(deserialize_with = "...")]` when you
|
||||||
|
/// don't want to borrow data from the `'de` lifetime.
|
||||||
|
pub fn deserialize_to_owned<'de, D>(deserializer: D) -> Result<Ident<'static>, D::Error>
|
||||||
|
where
|
||||||
|
D: Deserializer<'de>,
|
||||||
|
{
|
||||||
|
Ident::new(String::deserialize(deserializer)?).map_err(de::Error::custom)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -126,129 +138,138 @@ fn ascii_cow_to_str_cow(cow: Cow<AsciiStr>) -> Cow<str> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ParseError {
|
impl<'a> fmt::Debug for Ident<'a> {
|
||||||
/// Gets the string that caused the parse error.
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
pub fn into_source(self) -> Cow<'static, str> {
|
f.debug_tuple("Ident").field(&self.as_str()).finish()
|
||||||
self.src
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl std::fmt::Debug for Ident {
|
impl<'a> FromStr for Ident<'a> {
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
type Err = IdentParseError<'a>;
|
||||||
f.debug_tuple("Identifier").field(&self.as_str()).finish()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl FromStr for Ident {
|
|
||||||
type Err = ParseError;
|
|
||||||
|
|
||||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
Ident::new(s.to_owned())
|
Ident::new(s.to_owned())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<Ident> for String {
|
impl<'a> From<Ident<'a>> for String {
|
||||||
fn from(id: Ident) -> Self {
|
fn from(id: Ident) -> Self {
|
||||||
id.ident.into_owned().into()
|
id.string.into_owned().into()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<Ident> for Cow<'static, str> {
|
impl<'a> From<Ident<'a>> for Cow<'a, str> {
|
||||||
fn from(id: Ident) -> Self {
|
fn from(id: Ident<'a>) -> Self {
|
||||||
ascii_cow_to_str_cow(id.ident)
|
ascii_cow_to_str_cow(id.string)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<Ident> for nbt::Value {
|
impl<'a> AsRef<str> for Ident<'a> {
|
||||||
fn from(id: Ident) -> Self {
|
|
||||||
Self::String(id.into())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl AsRef<str> for Ident {
|
|
||||||
fn as_ref(&self) -> &str {
|
fn as_ref(&self) -> &str {
|
||||||
self.as_str()
|
self.as_str()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TryFrom<String> for Ident {
|
impl<'a, 'b> PartialEq<Ident<'b>> for Ident<'a> {
|
||||||
type Error = ParseError;
|
fn eq(&self, other: &Ident<'b>) -> bool {
|
||||||
|
(self.namespace(), self.path()) == (other.namespace(), other.path())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, 'b> PartialOrd<Ident<'b>> for Ident<'a> {
|
||||||
|
fn partial_cmp(&self, other: &Ident<'b>) -> Option<Ordering> {
|
||||||
|
(self.namespace(), self.path()).partial_cmp(&(other.namespace(), other.path()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> Hash for Ident<'a> {
|
||||||
|
fn hash<H: hash::Hasher>(&self, state: &mut H) {
|
||||||
|
self.namespace().hash(state);
|
||||||
|
self.path().hash(state);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> fmt::Display for Ident<'a> {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
write!(f, "{}:{}", self.namespace(), self.path())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> TryFrom<String> for Ident<'a> {
|
||||||
|
type Error = IdentParseError<'a>;
|
||||||
|
|
||||||
fn try_from(value: String) -> Result<Self, Self::Error> {
|
fn try_from(value: String) -> Result<Self, Self::Error> {
|
||||||
Ident::new(value)
|
Ident::new(value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TryFrom<&'static str> for Ident {
|
impl<'a> TryFrom<&'a str> for Ident<'a> {
|
||||||
type Error = ParseError;
|
type Error = IdentParseError<'a>;
|
||||||
|
|
||||||
fn try_from(value: &'static str) -> Result<Self, Self::Error> {
|
fn try_from(value: &'a str) -> Result<Self, Self::Error> {
|
||||||
Ident::new(value)
|
Ident::new(value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl std::fmt::Display for Ident {
|
impl<'a> From<Ident<'a>> for nbt::Value {
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
fn from(id: Ident<'a>) -> Self {
|
||||||
write!(f, "{}", self.as_str())
|
String::from(id).into()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Equality for identifiers respects the fact that "minecraft:apple" and
|
impl<'a> Encode for Ident<'a> {
|
||||||
/// "apple" have the same meaning.
|
|
||||||
impl PartialEq for Ident {
|
|
||||||
fn eq(&self, other: &Self) -> bool {
|
|
||||||
self.namespace().unwrap_or("minecraft") == other.namespace().unwrap_or("minecraft")
|
|
||||||
&& self.path() == other.path()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl hash::Hash for Ident {
|
|
||||||
fn hash<H: hash::Hasher>(&self, state: &mut H) {
|
|
||||||
self.namespace().unwrap_or("minecraft").hash(state);
|
|
||||||
self.path().hash(state);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Encode for Ident {
|
|
||||||
fn encode(&self, w: &mut impl Write) -> anyhow::Result<()> {
|
fn encode(&self, w: &mut impl Write) -> anyhow::Result<()> {
|
||||||
encode_string_bounded(self.as_str(), 0, 32767, w)
|
self.as_str().encode(w)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Decode for Ident {
|
impl<'a> Decode for Ident<'a> {
|
||||||
fn decode(r: &mut &[u8]) -> anyhow::Result<Self> {
|
fn decode(r: &mut &[u8]) -> anyhow::Result<Self> {
|
||||||
let string = BoundedString::<0, 32767>::decode(r)?.0;
|
Ok(Ident::new(String::decode(r)?)?)
|
||||||
Ok(Ident::new(string)?)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Serialize for Ident {
|
impl<'a> Serialize for Ident<'a> {
|
||||||
fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
|
fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
|
||||||
self.as_str().serialize(serializer)
|
self.as_str().serialize(serializer)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'de> Deserialize<'de> for Ident {
|
/// This uses borrowed data from the `'de` lifetime. If you just want owned
|
||||||
fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
|
/// data, see [`Ident::deserialize_to_owned`].
|
||||||
deserializer.deserialize_str(IdentifierVisitor)
|
impl<'de> Deserialize<'de> for Ident<'de> {
|
||||||
|
fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
|
||||||
|
deserializer.deserialize_string(IdentVisitor)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// An implementation of `serde::de::Visitor` for Minecraft identifiers.
|
struct IdentVisitor;
|
||||||
struct IdentifierVisitor;
|
|
||||||
|
|
||||||
impl<'de> Visitor<'de> for IdentifierVisitor {
|
impl<'de> Visitor<'de> for IdentVisitor {
|
||||||
type Value = Ident;
|
type Value = Ident<'de>;
|
||||||
|
|
||||||
fn expecting(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
write!(f, "a valid Minecraft identifier")
|
write!(f, "a valid Minecraft resource identifier")
|
||||||
}
|
}
|
||||||
|
|
||||||
fn visit_str<E: serde::de::Error>(self, s: &str) -> Result<Self::Value, E> {
|
fn visit_str<E: de::Error>(self, s: &str) -> Result<Self::Value, E> {
|
||||||
|
dbg!("foo");
|
||||||
|
|
||||||
Ident::from_str(s).map_err(E::custom)
|
Ident::from_str(s).map_err(E::custom)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn visit_string<E: serde::de::Error>(self, s: String) -> Result<Self::Value, E> {
|
fn visit_borrowed_str<E>(self, v: &'de str) -> Result<Self::Value, E>
|
||||||
|
where
|
||||||
|
E: de::Error,
|
||||||
|
{
|
||||||
|
dbg!("bar");
|
||||||
|
|
||||||
|
Ident::new(v).map_err(E::custom)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn visit_string<E: de::Error>(self, s: String) -> Result<Self::Value, E> {
|
||||||
|
dbg!("baz");
|
||||||
|
|
||||||
Ident::new(s).map_err(E::custom)
|
Ident::new(s).map_err(E::custom)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -268,15 +289,15 @@ impl<'de> Visitor<'de> for IdentifierVisitor {
|
||||||
/// use valence::ident;
|
/// use valence::ident;
|
||||||
///
|
///
|
||||||
/// let namespace = "my_namespace";
|
/// let namespace = "my_namespace";
|
||||||
/// let apple = ident!("{namespace}:apple");
|
/// let path = ident!("{namespace}:my_path");
|
||||||
///
|
///
|
||||||
/// assert_eq!(apple.namespace(), Some("my_namespace"));
|
/// assert_eq!(path.namespace(), "my_namespace");
|
||||||
/// assert_eq!(apple.path(), "apple");
|
/// assert_eq!(path.path(), "my_path");
|
||||||
/// ```
|
/// ```
|
||||||
#[macro_export]
|
#[macro_export]
|
||||||
macro_rules! ident {
|
macro_rules! ident {
|
||||||
($($arg:tt)*) => {{
|
($($arg:tt)*) => {{
|
||||||
let errmsg = "invalid identifier in `ident` macro";
|
let errmsg = "invalid resource identifier in `ident` macro";
|
||||||
#[allow(clippy::redundant_closure_call)]
|
#[allow(clippy::redundant_closure_call)]
|
||||||
(|args: ::std::fmt::Arguments| match args.as_str() {
|
(|args: ::std::fmt::Arguments| match args.as_str() {
|
||||||
Some(s) => $crate::ident::Ident::new(s).expect(errmsg),
|
Some(s) => $crate::ident::Ident::new(s).expect(errmsg),
|
||||||
|
@ -288,12 +309,15 @@ macro_rules! ident {
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use std::collections::hash_map::DefaultHasher;
|
use std::collections::hash_map::DefaultHasher;
|
||||||
use std::hash::{Hash, Hasher};
|
use std::hash::Hasher;
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn parse_valid() {
|
fn parse_valid() {
|
||||||
ident!("minecraft:whatever");
|
ident!("minecraft:whatever");
|
||||||
ident!("_what-ever55_:.whatever/whatever123456789_");
|
ident!("_what-ever55_:.whatever/whatever123456789_");
|
||||||
|
ident!("valence:frobnicator");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -329,4 +353,26 @@ mod tests {
|
||||||
|
|
||||||
assert_eq!(h1.finish(), h2.finish());
|
assert_eq!(h1.finish(), h2.finish());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn check_borrowed(id: Ident) {
|
||||||
|
if let Cow::Owned(_) = id.into_inner() {
|
||||||
|
panic!("not borrowed!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn literal_is_borrowed() {
|
||||||
|
check_borrowed(ident!("akjghsjkhebf"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn visit_borrowed_str_works() {
|
||||||
|
let data = String::from("valence:frobnicator");
|
||||||
|
|
||||||
|
check_borrowed(
|
||||||
|
IdentVisitor
|
||||||
|
.visit_borrowed_str::<de::value::Error>(data.as_ref())
|
||||||
|
.unwrap(),
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,6 +13,7 @@
|
||||||
//!
|
//!
|
||||||
//! [`send_packet`]: crate::client::Client::send_packet
|
//! [`send_packet`]: crate::client::Client::send_packet
|
||||||
|
|
||||||
|
use std::borrow::Cow;
|
||||||
use std::io::{Read, Write};
|
use std::io::{Read, Write};
|
||||||
use std::mem;
|
use std::mem;
|
||||||
|
|
||||||
|
@ -344,18 +345,36 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Encode for String {
|
impl Encode for str {
|
||||||
fn encode(&self, w: &mut impl Write) -> anyhow::Result<()> {
|
fn encode(&self, w: &mut impl Write) -> anyhow::Result<()> {
|
||||||
encode_string_bounded(self, 0, 32767, w)
|
encode_string_bounded(self, 0, 32767, w)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Encode for String {
|
||||||
|
fn encode(&self, w: &mut impl Write) -> anyhow::Result<()> {
|
||||||
|
self.as_str().encode(w)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Decode for String {
|
impl Decode for String {
|
||||||
fn decode(r: &mut &[u8]) -> anyhow::Result<Self> {
|
fn decode(r: &mut &[u8]) -> anyhow::Result<Self> {
|
||||||
decode_string_bounded(0, 32767, r)
|
decode_string_bounded(0, 32767, r)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<'a> Encode for Cow<'a, str> {
|
||||||
|
fn encode(&self, w: &mut impl Write) -> anyhow::Result<()> {
|
||||||
|
self.as_ref().encode(w)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Decode for Cow<'static, str> {
|
||||||
|
fn decode(r: &mut &[u8]) -> anyhow::Result<Self> {
|
||||||
|
Ok(String::decode(r)?.into())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// A string with a minimum and maximum character length known at compile time.
|
/// A string with a minimum and maximum character length known at compile time.
|
||||||
///
|
///
|
||||||
/// If the string is not in bounds, an error is generated while
|
/// If the string is not in bounds, an error is generated while
|
||||||
|
|
|
@ -281,7 +281,7 @@ pub mod play {
|
||||||
|
|
||||||
def_struct! {
|
def_struct! {
|
||||||
PluginMessageC2s {
|
PluginMessageC2s {
|
||||||
channel: Ident,
|
channel: Ident<'static>,
|
||||||
data: RawBytes,
|
data: RawBytes,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -407,7 +407,7 @@ pub mod play {
|
||||||
def_struct! {
|
def_struct! {
|
||||||
PlaceRecipe {
|
PlaceRecipe {
|
||||||
window_id: i8,
|
window_id: i8,
|
||||||
recipe: Ident,
|
recipe: Ident<'static>,
|
||||||
make_all: bool,
|
make_all: bool,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -520,7 +520,7 @@ pub mod play {
|
||||||
|
|
||||||
def_struct! {
|
def_struct! {
|
||||||
SetSeenRecipe {
|
SetSeenRecipe {
|
||||||
recipe_id: Ident,
|
recipe_id: Ident<'static>,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -542,7 +542,7 @@ pub mod play {
|
||||||
|
|
||||||
def_enum! {
|
def_enum! {
|
||||||
SeenAdvancements: VarInt {
|
SeenAdvancements: VarInt {
|
||||||
OpenedTab: Ident = 0,
|
OpenedTab: Ident<'static> = 0,
|
||||||
ClosedScreen = 1,
|
ClosedScreen = 1,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -610,9 +610,9 @@ pub mod play {
|
||||||
def_struct! {
|
def_struct! {
|
||||||
ProgramJigsawBlock {
|
ProgramJigsawBlock {
|
||||||
location: BlockPos,
|
location: BlockPos,
|
||||||
name: Ident,
|
name: Ident<'static>,
|
||||||
target: Ident,
|
target: Ident<'static>,
|
||||||
pool: Ident,
|
pool: Ident<'static>,
|
||||||
final_state: String,
|
final_state: String,
|
||||||
joint_type: String,
|
joint_type: String,
|
||||||
}
|
}
|
||||||
|
|
|
@ -62,7 +62,7 @@ pub mod login {
|
||||||
def_struct! {
|
def_struct! {
|
||||||
LoginPluginRequest {
|
LoginPluginRequest {
|
||||||
message_id: VarInt,
|
message_id: VarInt,
|
||||||
channel: Ident,
|
channel: Ident<'static>,
|
||||||
data: RawBytes,
|
data: RawBytes,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -277,7 +277,7 @@ pub mod play {
|
||||||
|
|
||||||
def_struct! {
|
def_struct! {
|
||||||
CustomSoundEffect {
|
CustomSoundEffect {
|
||||||
name: Ident,
|
name: Ident<'static>,
|
||||||
category: SoundCategory,
|
category: SoundCategory,
|
||||||
position: Vec3<i32>,
|
position: Vec3<i32>,
|
||||||
volume: f32,
|
volume: f32,
|
||||||
|
@ -388,13 +388,13 @@ pub mod play {
|
||||||
is_hardcore: bool,
|
is_hardcore: bool,
|
||||||
gamemode: GameMode,
|
gamemode: GameMode,
|
||||||
previous_gamemode: GameMode,
|
previous_gamemode: GameMode,
|
||||||
dimension_names: Vec<Ident>,
|
dimension_names: Vec<Ident<'static>>,
|
||||||
/// Contains information about dimensions, biomes, and chats.
|
/// Contains information about dimensions, biomes, and chats.
|
||||||
registry_codec: Compound,
|
registry_codec: Compound,
|
||||||
/// The name of the dimension type being spawned into.
|
/// The name of the dimension type being spawned into.
|
||||||
dimension_type_name: Ident,
|
dimension_type_name: Ident<'static>,
|
||||||
/// The name of the dimension being spawned into.
|
/// The name of the dimension being spawned into.
|
||||||
dimension_name: Ident,
|
dimension_name: Ident<'static>,
|
||||||
/// Hash of the world's seed used for client biome noise.
|
/// Hash of the world's seed used for client biome noise.
|
||||||
hashed_seed: i64,
|
hashed_seed: i64,
|
||||||
/// No longer used by the client.
|
/// No longer used by the client.
|
||||||
|
@ -409,7 +409,7 @@ pub mod play {
|
||||||
/// If this is a superflat world.
|
/// If this is a superflat world.
|
||||||
/// Superflat worlds have different void fog and horizon levels.
|
/// Superflat worlds have different void fog and horizon levels.
|
||||||
is_flat: bool,
|
is_flat: bool,
|
||||||
last_death_location: Option<(Ident, BlockPos)>,
|
last_death_location: Option<(Ident<'static>, BlockPos)>,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -529,15 +529,15 @@ pub mod play {
|
||||||
|
|
||||||
def_struct! {
|
def_struct! {
|
||||||
Respawn {
|
Respawn {
|
||||||
dimension_type_name: Ident,
|
dimension_type_name: Ident<'static>,
|
||||||
dimension_name: Ident,
|
dimension_name: Ident<'static>,
|
||||||
hashed_seed: u64,
|
hashed_seed: u64,
|
||||||
game_mode: GameMode,
|
game_mode: GameMode,
|
||||||
previous_game_mode: GameMode,
|
previous_game_mode: GameMode,
|
||||||
is_debug: bool,
|
is_debug: bool,
|
||||||
is_flat: bool,
|
is_flat: bool,
|
||||||
copy_metadata: bool,
|
copy_metadata: bool,
|
||||||
last_death_location: Option<(Ident, BlockPos)>,
|
last_death_location: Option<(Ident<'static>, BlockPos)>,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -708,7 +708,7 @@ pub mod play {
|
||||||
|
|
||||||
def_struct! {
|
def_struct! {
|
||||||
EntityAttributesProperty {
|
EntityAttributesProperty {
|
||||||
key: Ident,
|
key: Ident<'static>,
|
||||||
value: f64,
|
value: f64,
|
||||||
modifiers: Vec<EntityAttributesModifiers>
|
modifiers: Vec<EntityAttributesModifiers>
|
||||||
}
|
}
|
||||||
|
|
11
src/text.rs
11
src/text.rs
|
@ -5,7 +5,7 @@ use std::fmt;
|
||||||
use std::io::Write;
|
use std::io::Write;
|
||||||
|
|
||||||
use serde::de::Visitor;
|
use serde::de::Visitor;
|
||||||
use serde::{Deserialize, Deserializer, Serialize, Serializer};
|
use serde::{de, Deserialize, Deserializer, Serialize, Serializer};
|
||||||
|
|
||||||
use crate::ident::Ident;
|
use crate::ident::Ident;
|
||||||
use crate::protocol::{BoundedString, Decode, Encode};
|
use crate::protocol::{BoundedString, Decode, Encode};
|
||||||
|
@ -378,14 +378,15 @@ enum ClickEvent {
|
||||||
enum HoverEvent {
|
enum HoverEvent {
|
||||||
ShowText(Box<Text>),
|
ShowText(Box<Text>),
|
||||||
ShowItem {
|
ShowItem {
|
||||||
id: Ident,
|
#[serde(deserialize_with = "Ident::deserialize_to_owned")]
|
||||||
|
id: Ident<'static>,
|
||||||
count: Option<i32>,
|
count: Option<i32>,
|
||||||
// TODO: tag
|
// TODO: tag
|
||||||
},
|
},
|
||||||
ShowEntity {
|
ShowEntity {
|
||||||
name: Box<Text>,
|
name: Box<Text>,
|
||||||
#[serde(rename = "type")]
|
#[serde(rename = "type", deserialize_with = "Ident::deserialize_to_owned")]
|
||||||
kind: Ident,
|
kind: Ident<'static>,
|
||||||
// TODO: id (hyphenated entity UUID as a string)
|
// TODO: id (hyphenated entity UUID as a string)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -513,7 +514,7 @@ impl<'de> Visitor<'de> for ColorVisitor {
|
||||||
write!(f, "a hex color of the form #rrggbb")
|
write!(f, "a hex color of the form #rrggbb")
|
||||||
}
|
}
|
||||||
|
|
||||||
fn visit_str<E: serde::de::Error>(self, s: &str) -> Result<Self::Value, E> {
|
fn visit_str<E: de::Error>(self, s: &str) -> Result<Self::Value, E> {
|
||||||
color_from_str(s).ok_or_else(|| E::custom("invalid hex color"))
|
color_from_str(s).ok_or_else(|| E::custom("invalid hex color"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue