Clean up ident module and add lifetime (#108)

This lifetime will be useful for zero-copy decoding later.
This commit is contained in:
Ryan Johnson 2022-10-11 01:10:49 -07:00 committed by GitHub
parent c758f70c33
commit 23fdc41610
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 219 additions and 153 deletions

View file

@ -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>,
} }

View file

@ -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,

View file

@ -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)
} }
} }

View file

@ -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(),
);
}
} }

View file

@ -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

View file

@ -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,
} }

View file

@ -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>
} }

View file

@ -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"))
} }
} }