diff options
-rw-r--r-- | src/chat.rs | 1079 | ||||
-rw-r--r-- | src/lib.rs | 1 | ||||
-rw-r--r-- | src/test_macros.rs | 12 | ||||
-rw-r--r-- | src/types.rs | 314 | ||||
-rw-r--r-- | src/uuid.rs | 15 | ||||
-rw-r--r-- | src/v1_15_2.rs | 437 |
6 files changed, 1293 insertions, 565 deletions
diff --git a/src/chat.rs b/src/chat.rs new file mode 100644 index 0000000..be4d5b5 --- /dev/null +++ b/src/chat.rs @@ -0,0 +1,1079 @@ +use std::{fmt, str}; +use serde::{Serialize, Deserialize, Deserializer, de, Serializer}; +use serde::de::{Visitor, Error, IntoDeserializer, MapAccess}; +use std::collections::BTreeMap; +use serde::ser::SerializeMap; +use serde_json::Value; +use crate::{SerializeResult, DeserializeResult}; + +pub type BoxedChat = Box<Chat>; + +#[derive(Clone, Debug, PartialEq)] +pub enum Chat { + Text(TextComponent), + Translation(TranslationComponent), + Keybind(KeybindComponent), + Score(ScoreComponent), +} + +impl Chat { + pub fn base(&self) -> &BaseComponent { + use Chat::*; + + match self { + Text(body) => &body.base, + Translation(body) => &body.base, + Keybind(body) => &body.base, + Score(body) => &body.base, + } + } + + pub fn siblings(&self) -> &Vec<BoxedChat> { + &self.base().extra + } + + pub fn boxed(self) -> BoxedChat { + Box::new(self) + } + + pub fn from_text(text: &str) -> Chat { + Chat::Text(TextComponent { + base: BaseComponent::default(), + text: text.to_owned(), + }) + } + + pub fn from_traditional(orig: &str, translate_colorcodes: bool) -> Chat { + TraditionalParser::new(orig, translate_colorcodes).parse() + } + + pub fn to_traditional(&self) -> Option<String> { + use Chat::*; + + match self { + Text(body) => Some(body.to_traditional()), + _ => None + } + } +} + +struct TraditionalParser { + source: Vec<char>, + at: usize, + translate_colorcodes: bool, + + // state + text: String, + color: Option<ColorCode>, + bold: bool, + italic: bool, + underlined: bool, + strikethrough: bool, + obfuscated: bool, + + // all the parts we've already seen + done: Vec<TextComponent>, +} + +impl TraditionalParser { + + fn new(source: &str, translate_colorcodes: bool) -> TraditionalParser { + Self { + source: source.chars().collect(), + at: 0, + translate_colorcodes, + + text: String::new(), + color: None, + bold: false, + italic: false, + underlined: false, + strikethrough: false, + obfuscated: false, + + done: Vec::new(), + } + } + + fn parse(mut self) -> Chat { + loop { + if let Some(formatter) = self.consume_formatter() { + self.handle_formatter(formatter) + } else if let Some(next) = self.consume_char() { + self.push_next(next) + } else { + return self.finalize() + } + } + } + + fn handle_formatter(&mut self, formatter: Formatter) { + use Formatter::*; + + if self.has_text() { + self.finish_current(); + } + + match formatter { + Color(color) => { + self.finish_current(); + self.color = Some(color); + } + Obfuscated => self.obfuscated = true, + Bold => self.bold = true, + Strikethrough => self.strikethrough = true, + Underline => self.underlined = true, + Italic => self.italic = true, + _ => {} + } + } + + fn push_next(&mut self, next: char) { + self.text.push(next); + } + + fn finish_current(&mut self) { + if self.has_text() { + let current = TextComponent { + text: self.text.clone(), + base: BaseComponent { + color: self.color.clone(), + bold: self.bold, + italic: self.italic, + underlined: self.underlined, + strikethrough: self.strikethrough, + obfuscated: self.obfuscated, + hover_event: None, + click_event: None, + insertion: None, + extra: Vec::default() + } + }; + self.text.clear(); + self.done.push(current); + } + + self.reset_style(); + } + + fn reset_style(&mut self) { + self.bold = false; + self.italic = false; + self.underlined = false; + self.strikethrough = false; + self.obfuscated = false; + self.color = None; + } + + fn has_text(&self) -> bool { + return !self.text.is_empty() + } + + fn is_on_formatter(&self) -> bool { + self.source.get(self.at).map(move |c| { + let c = *c; + c == SECTION_SYMBOL || (self.translate_colorcodes && c == '&') + }).unwrap_or(false) + } + + fn consume_char(&mut self) -> Option<char> { + if let Some(c) = self.source.get(self.at) { + self.at += 1; + Some(*c) + } else { + None + } + } + + fn consume_formatter(&mut self) -> Option<Formatter> { + if self.is_on_formatter() { + self.consume_char()?; + let c = self.consume_char()?; + let out = Formatter::from_code(&c); + if out.is_none() { + self.at -= 1; + } + + out + } else { + None + } + } + + fn finalize(mut self) -> Chat { + self.finish_current(); + self.simplify(); + let n_components = self.done.len(); + if n_components == 1 { + return Chat::Text(self.done.remove(0)); + } + + let mut top_level = TextComponent { + text: String::default(), + base: BaseComponent::default(), + }; + + if n_components > 0 { + top_level.base.extra.extend( + self.done.into_iter() + .map(move |component| Chat::Text(component).boxed())); + } + + Chat::Text(top_level) + } + + fn simplify(&mut self) { + let mut updated = Vec::with_capacity(self.done.len()); + let mut last: Option<TextComponent> = None; + while !self.done.is_empty() { + let cur = self.done.remove(0); + if let Some(mut la) = last.take() { + if la.base.has_same_style_as(&cur.base) { + la.text.extend(cur.text.chars()); + last = Some(la); + continue; + } else { + updated.push(la); + } + } + + last = Some(cur); + } + + if let Some(l) = last.take() { + updated.push(l); + } + + self.done = updated; + } +} + +#[derive(Serialize, Clone, Debug, PartialEq)] +pub struct BaseComponent { + #[serde(skip_serializing_if = "should_skip_flag_field")] + pub bold: bool, + #[serde(skip_serializing_if = "should_skip_flag_field")] + pub italic: bool, + #[serde(skip_serializing_if = "should_skip_flag_field")] + pub underlined: bool, + #[serde(skip_serializing_if = "should_skip_flag_field")] + pub strikethrough: bool, + #[serde(skip_serializing_if = "should_skip_flag_field")] + pub obfuscated: bool, + #[serde(skip_serializing_if = "Option::is_none")] + pub color: Option<ColorCode>, + #[serde(skip_serializing_if = "Option::is_none")] + pub insertion: Option<String>, + #[serde(skip_serializing_if = "Option::is_none")] + pub click_event: Option<ChatClickEvent>, + #[serde(skip_serializing_if = "Option::is_none")] + pub hover_event: Option<ChatHoverEvent>, + #[serde(skip_serializing_if = "Vec::is_empty")] + pub extra: Vec<BoxedChat>, +} + +fn should_skip_flag_field(flag: &bool) -> bool { + !*flag +} + +impl BaseComponent { + + fn has_same_style_as(&self, other: &Self) -> bool { + other.bold == self.bold && + other.italic == self.italic && + other.underlined == self.underlined && + other.strikethrough == self.strikethrough && + other.obfuscated == self.obfuscated && + other.color.eq(&self.color) + } +} + +impl Into<BaseComponent> for JsonComponentBase { + fn into(self) -> BaseComponent { + BaseComponent { + bold: self.bold.unwrap_or(false), + italic: self.italic.unwrap_or(false), + underlined: self.underlined.unwrap_or(false), + strikethrough: self.strikethrough.unwrap_or(false), + obfuscated: self.obfuscated.unwrap_or(false), + color: self.color, + insertion: self.insertion, + click_event: self.click_event, + hover_event: self.hover_event, + extra: self.extra.into_iter().map(move |elem| elem.boxed()).collect(), + } + } +} + +#[derive(Deserialize)] +struct JsonComponentBase { + pub bold: Option<bool>, + pub italic: Option<bool>, + pub underlined: Option<bool>, + pub strikethrough: Option<bool>, + pub obfuscated: Option<bool>, + pub color: Option<ColorCode>, + pub insertion: Option<String>, + #[serde(rename = "clickEvent")] + pub click_event: Option<ChatClickEvent>, + #[serde(rename = "hoverEvent")] + pub hover_event: Option<ChatHoverEvent>, + #[serde(default = "Vec::default")] + pub extra: Vec<Chat>, + + #[serde(flatten)] + _additional: BTreeMap<String, serde_json::Value> +} + +impl Default for BaseComponent { + fn default() -> Self { + Self { + bold: false, + italic: false, + underlined: false, + strikethrough: false, + obfuscated: false, + color: None, + insertion: None, + click_event: None, + hover_event: None, + extra: Vec::default(), + } + } +} + +#[derive(Deserialize, Serialize, Clone, Debug, PartialEq)] +pub struct TextComponent { + pub text: String, + + #[serde(flatten)] + #[serde(skip_deserializing)] + pub base: BaseComponent, +} + +impl TextComponent { + pub fn to_traditional(&self) -> String { + let b = &self.base; + let text = &self.text; + + let (mut buf, has_formatters) = if !text.is_empty() { + let formatters = self.traditional_formatters(false); + let has_formatters = formatters.is_some(); + let mut buf = formatters.unwrap_or_else(|| String::new()); + buf.extend(text.chars()); + (buf, has_formatters) + } else { + (String::default(), false) + }; + + let mut last_had_formatters = has_formatters; + for extra in b.extra.iter() { + if let Chat::Text(child) = extra.as_ref() { + match child.traditional_formatters(last_had_formatters) { + Some(child_fmts) => { + last_had_formatters = true; + buf.extend(child_fmts.chars()) + }, + None => { + last_had_formatters = false; + } + } + + buf.extend(child.text.chars()); + } + } + + buf + } + + fn traditional_formatters(&self, prev_colored: bool) -> Option<String> { + let b = &self.base; + let mut buf = String::default(); + + if let Some(c) = b.color { + buf.push(SECTION_SYMBOL); + buf.push(c.code()); + } + + let mut apply_formatter = |b: bool, formatter: Formatter| { + if b { + buf.push(SECTION_SYMBOL); + buf.push(formatter.code()); + } + }; + + apply_formatter(b.bold, Formatter::Bold); + apply_formatter(b.italic, Formatter::Italic); + apply_formatter(b.strikethrough, Formatter::Strikethrough); + apply_formatter(b.underlined, Formatter::Underline); + apply_formatter(b.obfuscated, Formatter::Obfuscated); + + if buf.is_empty() { + if prev_colored { + buf.push(SECTION_SYMBOL); + buf.push('r'); + Some(buf) + } else { + None + } + } else { + Some(buf) + } + } +} + +#[derive(Deserialize, Serialize, Clone, Debug, PartialEq)] +pub struct TranslationComponent { + pub translate: String, + pub with: Vec<BoxedChat>, + + #[serde(flatten)] + #[serde(skip_deserializing)] + pub base: BaseComponent, +} + +#[derive(Deserialize, Serialize, Clone, Debug, PartialEq)] +pub struct KeybindComponent { + pub keybind: String, + + #[serde(flatten)] + #[serde(skip_deserializing)] + pub base: BaseComponent +} +#[derive(Deserialize, Serialize, Clone, Debug, PartialEq)] +pub struct ScoreComponent { + pub score: ScoreComponentObjective, + + #[serde(flatten)] + #[serde(skip_deserializing)] + pub base: BaseComponent +} + +#[derive(Deserialize, Serialize, Clone, Debug, PartialEq)] +pub struct ScoreComponentObjective { + pub name: String, + pub objective: Option<String>, + pub value: Option<String>, +} + +#[derive(Clone, Debug, PartialEq)] +pub enum ChatClickEvent { + OpenUrl(String), + RunCommand(String), + SuggestCommand(String), + ChangePage(i32) +} + +impl Serialize for ChatClickEvent { + fn serialize<S>(&self, serializer: S) -> Result<<S as Serializer>::Ok, <S as Serializer>::Error> where + S: Serializer + { + let mut m = serializer.serialize_map(Some(2))?; + + use ChatClickEvent::*; + + m.serialize_entry("action", match self { + OpenUrl(_) => "open_url", + RunCommand(_) => "run_command", + SuggestCommand(_) => "suggest_command", + ChangePage(_) => "change_page", + })?; + + m.serialize_key("value")?; + + match self { + OpenUrl(body) => m.serialize_value(body), + RunCommand(body) => m.serialize_value(body), + SuggestCommand(body) => m.serialize_value(body), + ChangePage(body) => m.serialize_value(body), + }?; + + m.end() + } +} + +impl<'de> Deserialize<'de> for ChatClickEvent { + fn deserialize<D>(deserializer: D) -> Result<Self, <D as Deserializer<'de>>::Error> where + D: Deserializer<'de> + { + struct V; + + impl<'de> Visitor<'de> for V { + type Value = ChatClickEvent; + + fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "an event object for ChatClickEvent") + } + + fn visit_map<A>(self, mut map: A) -> Result<Self::Value, <A as MapAccess<'de>>::Error> where + A: MapAccess<'de> + { + let mut action: Option<&str> = None; + let mut value: Option<Value> = None; + while action.is_none() || value.is_none() { + if let Some(key) = map.next_key()? { + match key { + "action" => { + action = map.next_value()?; + if action.is_none() { + return Err(A::Error::custom("none for value key=action")); + } + }, + "value" => { + value = map.next_value()?; + if value.is_none() { + return Err(A::Error::custom("none for value key=value")); + } + }, + other => { + return Err(A::Error::custom(format!("unexpected key in event {}", other))); + } + } + } else { + return Err(A::Error::custom(format!("event needs action and value"))); + } + } + + use ChatClickEvent::*; + let v = value.expect("set this in while loop"); + match action.expect("set this in while loop") { + "open_url" => match v.as_str() { + Some(url) => Ok(OpenUrl(url.to_owned())), + None => Err(A::Error::custom(format!("open_url requires string body, got {}", v))) + }, + "run_command" => match v.as_str() { + Some(cmd) => Ok(RunCommand(cmd.to_owned())), + None => Err(A::Error::custom(format!("run_command requires string body, got {}", v))) + }, + "suggest_command" => match v.as_str() { + Some(cmd) => Ok(SuggestCommand(cmd.to_owned())), + None => Err(A::Error::custom(format!("suggest_command requires string body, got {}", v))) + }, + "change_page" => match v.as_i64() { + Some(v) => Ok(ChangePage(v as i32)), + None => Err(A::Error::custom(format!("change_page requires integer body, got {}", v))) + }, + other => Err(A::Error::custom(format!("invalid click action kind {}", other))) + } + } + } + + deserializer.deserialize_map(V) + } +} + +#[derive(Clone, Debug, PartialEq)] +pub enum ChatHoverEvent { + ShowText(BoxedChat), + ShowItem(Value), + ShowEntity(Value) +} + +impl Serialize for ChatHoverEvent { + fn serialize<S>(&self, serializer: S) -> Result<<S as Serializer>::Ok, <S as Serializer>::Error> where + S: Serializer + { + let mut m = serializer.serialize_map(Some(2))?; + + use ChatHoverEvent::*; + + m.serialize_entry("action", match self { + ShowText(_) => "show_text", + ShowItem(_) => "show_item", + ShowEntity(_) => "show_entity", + })?; + + m.serialize_key("value")?; + + match self { + ShowText(body) => m.serialize_value(body), + ShowItem(body) => m.serialize_value(body), + ShowEntity(body) => m.serialize_value(body), + }?; + + m.end() + } +} + +impl<'de> Deserialize<'de> for ChatHoverEvent { + fn deserialize<D>(deserializer: D) -> Result<Self, <D as Deserializer<'de>>::Error> where + D: Deserializer<'de> + { + struct V; + + impl<'de> Visitor<'de> for V { + type Value = ChatHoverEvent; + + fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "an event object for ChatClickEvent") + } + + //noinspection ALL + fn visit_map<A>(self, mut map: A) -> Result<Self::Value, <A as MapAccess<'de>>::Error> where + A: MapAccess<'de> + { + let mut action: Option<&str> = None; + let mut value: Option<Value> = None; + while action.is_none() || value.is_none() { + if let Some(key) = map.next_key()? { + match key { + "action" => { + action = map.next_value()?; + if action.is_none() { + return Err(A::Error::custom("none for value key=action")); + } + }, + "value" => { + value = map.next_value()?; + if value.is_none() { + return Err(A::Error::custom("none for value key=value")); + } + }, + other => { + return Err(A::Error::custom(format!("unexpected key in event {}", other))); + } + } + } else { + return Err(A::Error::custom(format!("event needs action and value"))); + } + } + + use ChatHoverEvent::*; + let v = value.expect("set this in while loop"); + match action.expect("set this in while loop") { + "show_text" => Ok(ShowText( + Chat::deserialize(v.into_deserializer()) + .map_err(move |err| A::Error::custom( + format!("error deserializing text to show {:?}", err)))? + .boxed())), + "show_item" => Ok(ShowItem(v)), + "show_entity" => Ok(ShowEntity(v)), + other => Err(A::Error::custom(format!("invalid hover action kind {}", other))) + } + } + } + + deserializer.deserialize_map(V) + } +} + +pub const SECTION_SYMBOL: char = '§'; + +#[derive(PartialOrd, PartialEq, Debug, Copy, Clone)] +pub enum ColorCode { + Black, + DarkBlue, + DarkGreen, + DarkAqua, + DarkRed, + DarkPurple, + Gold, + Gray, + DarkGray, + Blue, + Green, + Aqua, + Red, + LightPurple, + Yellow, + White, +} + +impl ColorCode { + pub fn from_code(i: &char) -> Option<Self> { + match i { + '0' => Some(ColorCode::Black), + '1' => Some(ColorCode::DarkBlue), + '2' => Some(ColorCode::DarkGreen), + '3' => Some(ColorCode::DarkAqua), + '4' => Some(ColorCode::DarkRed), + '5' => Some(ColorCode::DarkPurple), + '6' => Some(ColorCode::Gold), + '7' => Some(ColorCode::Gray), + '8' => Some(ColorCode::DarkGray), + '9' => Some(ColorCode::Blue), + 'a' => Some(ColorCode::Green), + 'b' => Some(ColorCode::Aqua), + 'c' => Some(ColorCode::Red), + 'd' => Some(ColorCode::LightPurple), + 'e' => Some(ColorCode::Yellow), + 'f' => Some(ColorCode::White), + _ => None, + } + } + + pub fn code(&self) -> char { + match self { + ColorCode::Black => '0', + ColorCode::DarkBlue => '1', + ColorCode::DarkGreen => '2', + ColorCode::DarkAqua => '3', + ColorCode::DarkRed => '4', + ColorCode::DarkPurple => '5', + ColorCode::Gold => '6', + ColorCode::Gray => '7', + ColorCode::DarkGray => '8', + ColorCode::Blue => '9', + ColorCode::Green => 'a', + ColorCode::Aqua => 'b', + ColorCode::Red => 'c', + ColorCode::LightPurple => 'd', + ColorCode::Yellow => 'e', + ColorCode::White => 'f', + } + } + + pub fn from_name(name: &str) -> Option<Self> { + match name.to_ascii_lowercase().as_str() { + "black" => Some(ColorCode::Black), + "dark_blue" => Some(ColorCode::DarkBlue), + "dark_green" => Some(ColorCode::DarkGreen), + "dark_aqua" => Some(ColorCode::DarkAqua), + "dark_red" => Some(ColorCode::DarkRed), + "dark_purple" => Some(ColorCode::DarkPurple), + "gold" => Some(ColorCode::Gold), + "gray" => Some(ColorCode::Gray), + "dark_gray" => Some(ColorCode::DarkGray), + "blue" => Some(ColorCode::Blue), + "green" => Some(ColorCode::Green), + "aqua" => Some(ColorCode::Aqua), + "red" => Some(ColorCode::Red), + "light_purple" => Some(ColorCode::LightPurple), + "yellow" => Some(ColorCode::Yellow), + "white" => Some(ColorCode::White), + _ => None, + } + } + + pub fn name(&self) -> &str { + match self { + ColorCode::Black => "black", + ColorCode::DarkBlue => "dark_blue", + ColorCode::DarkGreen => "dark_green", + ColorCode::DarkAqua => "dark_aqua", + ColorCode::DarkRed => "dark_red", + ColorCode::DarkPurple => "dark_purple", + ColorCode::Gold => "gold", + ColorCode::Gray => "gray", + ColorCode::DarkGray => "dark_gray", + ColorCode::Blue => "blue", + ColorCode::Green => "green", + ColorCode::Aqua => "aqua", + ColorCode::Red => "red", + ColorCode::LightPurple => "light_purple", + ColorCode::Yellow => "yellow", + ColorCode::White => "white", + } + } +} + +impl fmt::Display for ColorCode { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}{}", SECTION_SYMBOL, self.code()) + } +} + +impl Serialize for ColorCode { + fn serialize<S>(&self, serializer: S) -> Result<<S as Serializer>::Ok, <S as Serializer>::Error> where + S: Serializer { + serializer.serialize_str(self.name()) + } +} + +impl<'de> Deserialize<'de> for ColorCode { + fn deserialize<D>(deserializer: D) -> Result<Self, <D as Deserializer<'de>>::Error> where + D: Deserializer<'de> + { + struct V; + + impl<'de> Visitor<'de> for V { + type Value = ColorCode; + + fn expecting(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(fmt, "a string representing a color code") + } + + fn visit_str<E>(self, v: &str) -> Result<Self::Value, E> where + E: Error, { + if let Some(code) = ColorCode::from_name(v) { + Ok(code) + } else { + Err(E::custom(format!("invalid color code name {}", v))) + } + } + } + + deserializer.deserialize_str(V) + } +} + +#[derive(PartialOrd, PartialEq, Debug, Copy, Clone)] +pub enum Formatter { + Color(ColorCode), + Obfuscated, + Bold, + Strikethrough, + Underline, + Italic, + Reset, +} + +impl Formatter { + pub fn from_code(i: &char) -> Option<Self> { + match i.to_ascii_lowercase() { + 'k' => Some(Formatter::Obfuscated), + 'l' => Some(Formatter::Bold), + 'm' => Some(Formatter::Strikethrough), + 'n' => Some(Formatter::Underline), + 'o' => Some(Formatter::Italic), + 'r' => Some(Formatter::Reset), + _ => ColorCode::from_code(i).map(Formatter::Color), + } + } + + pub fn code(&self) -> char { + match self { + Formatter::Color(c) => c.code(), + Formatter::Obfuscated => 'k', + Formatter::Bold => 'l', + Formatter::Strikethrough => 'm', + Formatter::Underline => 'n', + Formatter::Italic => 'o', + Formatter::Reset => 'r', + } + } + + pub fn from_name(name: &str) -> Option<Self> { + match name.to_ascii_lowercase().as_str() { + "obfuscated" => Some(Formatter::Obfuscated), + "bold" => Some(Formatter::Bold), + "strikethrough" => Some(Formatter::Strikethrough), + "underline" => Some(Formatter::Underline), + "italic" => Some(Formatter::Italic), + "reset" => Some(Formatter::Reset), + _ => ColorCode::from_name(name).map(Formatter::Color), + } + } + + pub fn name(&self) -> &str { + match self { + Formatter::Obfuscated => "obfuscated", + Formatter::Bold => "bold", + Formatter::Strikethrough => "strikethrough", + Formatter::Underline => "underline", + Formatter::Italic => "italic", + Formatter::Reset => "reset", + Formatter::Color(c) => c.name(), + } + } +} + +impl fmt::Display for Formatter { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}{}", SECTION_SYMBOL, self.code()) + } +} + + +impl<'de> Deserialize<'de> for Chat { + fn deserialize<D>(deserializer: D) -> Result<Self, <D as Deserializer<'de>>::Error> where + D: Deserializer<'de> + { + struct V; + + impl<'de> Visitor<'de> for V { + type Value = Chat; + + fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(formatter, "any primitive or a JSON object specifying the component") + } + + fn visit_bool<E>(self, value: bool) -> Result<Self::Value, E> where E: de::Error { + self.visit_string(value.to_string()) + } + + fn visit_i64<E>(self, value: i64) -> Result<Self::Value, E> where E: de::Error { + self.visit_string(value.to_string()) + } + + fn visit_f64<E>(self, value: f64) -> Result<Self::Value, E> where E: de::Error { + self.visit_string(value.to_string()) + } + + fn visit_string<E>(self, value: String) -> Result<Self::Value, E> where E: de::Error { + Ok(Chat::Text(TextComponent { + base: BaseComponent::default(), + text: value, + })) + } + + fn visit_map<M>(self, map: M) -> Result<Self::Value, M::Error> where M: de::MapAccess<'de> { + let mut base: JsonComponentBase = de::Deserialize::deserialize(de::value::MapAccessDeserializer::new(map))?; + let additional = &mut base._additional; + + // string component + if let Some(raw_text) = additional.remove("text") { + return if let Some(text) = raw_text.as_str() { + additional.clear(); + Ok(Chat::Text(TextComponent { + text: text.to_owned(), + base: base.into(), + })) + } else { + Err(M::Error::custom(format!("have text but it's not a string - {:?}", raw_text))) + }; + } + + // translate + if let Some(raw_translate) = additional.remove("translate") { + return if let Some(translate) = raw_translate.as_str() { + // need "with" + if let Some(raw_with) = additional.remove("with") { + if let Some(withs) = raw_with.as_array() { + let mut withs_out = Vec::with_capacity(withs.len()); + for with in withs { + withs_out.push(with.deserialize_any(V) + .map_err(move |err| M::Error::custom( + format!("unable to parse one of the translation with entries :: {}", err)))? + .boxed()); + } + Ok(Chat::Translation(TranslationComponent{ + base: base.into(), + translate: translate.to_owned(), + with: withs_out, + })) + } else { + Err(M::Error::custom(format!("have with but it's not an array - {:?}", raw_with))) + } + } else { + Err(M::Error::custom("have 'translate' but missing 'with', cannot parse")) + } + } else { + Err(M::Error::custom(format!("have translate but it's not a string - {:?}", raw_translate))) + } + } + + // keybind + if let Some(raw_keybind) = additional.remove("keybind") { + return if let Some(keybind) = raw_keybind.as_str() { + Ok(Chat::Keybind(KeybindComponent{ + keybind: keybind.to_owned(), + base: base.into() + })) + } else { + Err(M::Error::custom(format!("have keybind but it's not a string! {:?}", raw_keybind))) + } + } + + // score + if let Some(raw_score) = additional.remove("score") { + let score = ScoreComponentObjective::deserialize(raw_score.into_deserializer()) + .map_err(move |err| M::Error::custom( + format!("failed to deserialize scoreboard objective for score chat component :: {:?}", err)))?; + + return Ok(Chat::Score(ScoreComponent{ + score, + base: base.into(), + })); + } + + // selector (SKIP) + + Err(M::Error::custom("not able to parse chat component, not a valid chat component kind")) + } + } + + deserializer.deserialize_any(V) + } +} + +impl Serialize for Chat { + fn serialize<S>(&self, serializer: S) -> Result<<S as Serializer>::Ok, <S as Serializer>::Error> where + S: Serializer + { + use Chat::*; + + match self { + Text(body) => body.serialize(serializer), + Translation(body) => body.serialize(serializer), + Keybind(body) => body.serialize(serializer), + Score(body) => body.serialize(serializer) + } + } +} + +impl super::Serialize for Chat { + fn mc_serialize<S: super::Serializer>(&self, to: &mut S) -> SerializeResult { + serde_json::to_string(self) + .map_err(move |err| super::SerializeErr::FailedJsonEncode( + format!("error while encoding chat :: {:?} -> {:?}", self, err)))? + .mc_serialize(to) + } +} + +impl super::Deserialize for Chat { + fn mc_deserialize(data: &[u8]) -> DeserializeResult<'_, Self> { + String::mc_deserialize(data)?.try_map(move |raw| { + serde_json::from_str(raw.as_str()).map_err(move |err| + super::DeserializeErr::FailedJsonDeserialize(format!( + "failed to serialize chat as JSON :: {:?}", err + ))) + }) + } +} + +#[cfg(test)] +use super::protocol::TestRandom; + +#[cfg(test)] +impl TestRandom for Chat { + fn test_gen_random() -> Self { + let str = String::test_gen_random(); + Chat::from_text(str.as_str()) + } +} + +#[cfg(test)] +pub mod tests { + + use super::*; + + #[test] + fn test_from_traditional_simple() { + let out = Chat::from_traditional("&cthis &cis red, and &rthis is &e&lyellow", true); + assert_eq!(out, Chat::Text(TextComponent{ + text: String::default(), + base: { + let mut b = BaseComponent::default(); + b.extra = vec!( + Chat::Text(TextComponent{ + text: "this is red, and ".to_owned(), + base: { + let mut b = BaseComponent::default(); + b.color = Some(ColorCode::Red); + b + }, + }).boxed(), + Chat::Text(TextComponent{ + text: "this is ".to_owned(), + base: BaseComponent::default(), + }).boxed(), + Chat::Text(TextComponent{ + text: "yellow".to_owned(), + base: { + let mut b = BaseComponent::default(); + b.color = Some(ColorCode::Yellow); + b.bold = true; + b + } + }).boxed() + ); + b + } + })); + + let traditional = out.to_traditional().expect("is text"); + assert_eq!(traditional.as_str(), "§cthis is red, and §rthis is §e§lyellow"); + println!("{}", serde_json::to_string_pretty(&out).expect("should serialize fine")); + } +}
\ No newline at end of file @@ -20,3 +20,4 @@ pub use serialize::*; #[cfg(test)] mod test_macros; +mod chat; diff --git a/src/test_macros.rs b/src/test_macros.rs index 8283d30..254c3bb 100644 --- a/src/test_macros.rs +++ b/src/test_macros.rs @@ -19,9 +19,15 @@ macro_rules! packet_test_cases { data: bytes.as_slice(), }; - let deserialized = - <$pnam>::mc_deserialize(raw_packet).expect("deserialize succeeds"); - assert_eq!(packet, deserialized); + let deserialized = match <$pnam>::mc_deserialize(raw_packet) { + Err(err) => { + eprintln!("expected: {:?}", packet); + panic!("error: {:?}", err); + }, + Ok(out) => out + }; + assert_eq!(packet, deserialized, "deserialize(serialize(packet)) == packet"); + assert_eq!(packet.clone(), deserialized.clone(), "deserialized.clone() == packet.clone()") } } diff --git a/src/types.rs b/src/types.rs index 2082b56..cda622a 100644 --- a/src/types.rs +++ b/src/types.rs @@ -4,6 +4,8 @@ use crate::utils::*; use crate::uuid::UUID4; use crate::*; +pub use super::chat::*; + #[cfg(test)] use crate::protocol::TestRandom; @@ -611,318 +613,6 @@ impl TestRandom for FixedInt { } } -// chat -#[derive(serde::Serialize, serde::Deserialize, Debug, PartialEq, Clone)] -pub struct Chat { - pub text: String, - #[serde(skip_serializing_if = "Option::is_none")] - pub bold: Option<bool>, - #[serde(skip_serializing_if = "Option::is_none")] - pub italic: Option<bool>, - #[serde(skip_serializing_if = "Option::is_none")] - pub underlined: Option<bool>, - #[serde(skip_serializing_if = "Option::is_none")] - pub strikethrough: Option<bool>, - #[serde(skip_serializing_if = "Option::is_none")] - pub obfuscated: Option<bool>, - #[serde(skip_serializing_if = "Option::is_none")] - pub color: Option<String>, - #[serde(skip_serializing_if = "Option::is_none")] - pub extra: Option<Vec<Chat>>, -} - -impl ToString for Chat { - fn to_string(&self) -> String { - self.extra - .as_ref() - .into_iter() - .flat_map(|v| v.into_iter()) - .map(|item| item.to_string()) - .fold(self.text.clone(), |acc, v| acc + v.as_str()) - } -} - -const SECTION_SYMBOL: char = '§'; - -#[derive(PartialOrd, PartialEq, Debug, Copy, Clone)] -pub enum ColorCode { - Black, - DarkBlue, - DarkGreen, - DarkAqua, - DarkRed, - DarkPurple, - Gold, - Gray, - DarkGray, - Blue, - Green, - Aqua, - Red, - LightPurple, - Yellow, - White, -} - -impl ColorCode { - pub fn from_code(i: &char) -> Option<Self> { - match i { - '0' => Some(ColorCode::Black), - '1' => Some(ColorCode::DarkBlue), - '2' => Some(ColorCode::DarkGreen), - '3' => Some(ColorCode::DarkAqua), - '4' => Some(ColorCode::DarkRed), - '5' => Some(ColorCode::DarkPurple), - '6' => Some(ColorCode::Gold), - '7' => Some(ColorCode::Gray), - '8' => Some(ColorCode::DarkGray), - '9' => Some(ColorCode::Blue), - 'a' => Some(ColorCode::Green), - 'b' => Some(ColorCode::Aqua), - 'c' => Some(ColorCode::Red), - 'd' => Some(ColorCode::LightPurple), - 'e' => Some(ColorCode::Yellow), - 'f' => Some(ColorCode::White), - _ => None, - } - } - - pub fn to_code(&self) -> char { - match self { - ColorCode::Black => '0', - ColorCode::DarkBlue => '1', - ColorCode::DarkGreen => '2', - ColorCode::DarkAqua => '3', - ColorCode::DarkRed => '4', - ColorCode::DarkPurple => '5', - ColorCode::Gold => '6', - ColorCode::Gray => '7', - ColorCode::DarkGray => '8', - ColorCode::Blue => '9', - ColorCode::Green => 'a', - ColorCode::Aqua => 'b', - ColorCode::Red => 'c', - ColorCode::LightPurple => 'd', - ColorCode::Yellow => 'e', - ColorCode::White => 'f', - } - } - - pub fn from_name(name: &str) -> Option<Self> { - match name.to_ascii_lowercase().as_str() { - "black" => Some(ColorCode::Black), - "dark_blue" => Some(ColorCode::DarkBlue), - "dark_green" => Some(ColorCode::DarkGreen), - "dark_aqua" => Some(ColorCode::DarkAqua), - "dark_red" => Some(ColorCode::DarkRed), - "dark_purple" => Some(ColorCode::DarkPurple), - "gold" => Some(ColorCode::Gold), - "gray" => Some(ColorCode::Gray), - "dark_gray" => Some(ColorCode::DarkGray), - "blue" => Some(ColorCode::Blue), - "green" => Some(ColorCode::Green), - "aqua" => Some(ColorCode::Aqua), - "red" => Some(ColorCode::Red), - "light_purple" => Some(ColorCode::LightPurple), - "yellow" => Some(ColorCode::Yellow), - "white" => Some(ColorCode::White), - _ => None, - } - } - - pub fn name(&self) -> &str { - match self { - ColorCode::Black => "black", - ColorCode::DarkBlue => "dark_blue", - ColorCode::DarkGreen => "dark_green", - ColorCode::DarkAqua => "dark_aqua", - ColorCode::DarkRed => "dark_red", - ColorCode::DarkPurple => "dark_purple", - ColorCode::Gold => "gold", - ColorCode::Gray => "gray", - ColorCode::DarkGray => "dark_gray", - ColorCode::Blue => "blue", - ColorCode::Green => "green", - ColorCode::Aqua => "aqua", - ColorCode::Red => "red", - ColorCode::LightPurple => "light_purple", - ColorCode::Yellow => "yellow", - ColorCode::White => "white", - } - } -} - -#[derive(PartialOrd, PartialEq, Debug, Copy, Clone)] -pub enum Formatter { - Color(ColorCode), - Obfuscated, - Bold, - Strikethrough, - Underline, - Italic, - Reset, -} - -impl Formatter { - pub fn from_code(i: &char) -> Option<Self> { - match i.to_ascii_lowercase() { - 'k' => Some(Formatter::Obfuscated), - 'l' => Some(Formatter::Bold), - 'm' => Some(Formatter::Strikethrough), - 'n' => Some(Formatter::Underline), - 'o' => Some(Formatter::Italic), - 'r' => Some(Formatter::Reset), - _ => ColorCode::from_code(i).map(Formatter::Color), - } - } - - pub fn code(&self) -> char { - match self { - Formatter::Color(c) => c.to_code(), - Formatter::Obfuscated => 'k', - Formatter::Bold => 'l', - Formatter::Strikethrough => 'm', - Formatter::Underline => 'n', - Formatter::Italic => 'o', - Formatter::Reset => 'r', - } - } - - pub fn from_name(name: &str) -> Option<Self> { - match name.to_ascii_lowercase().as_str() { - "obfuscated" => Some(Formatter::Obfuscated), - "bold" => Some(Formatter::Bold), - "strikethrough" => Some(Formatter::Strikethrough), - "underline" => Some(Formatter::Underline), - "italic" => Some(Formatter::Italic), - "reset" => Some(Formatter::Reset), - _ => ColorCode::from_name(name).map(Formatter::Color), - } - } - - pub fn name(&self) -> &str { - match self { - Formatter::Obfuscated => "obfuscated", - Formatter::Bold => "bold", - Formatter::Strikethrough => "strikethrough", - Formatter::Underline => "underline", - Formatter::Italic => "italic", - Formatter::Reset => "reset", - Formatter::Color(c) => c.name(), - } - } -} - -impl ToString for Formatter { - fn to_string(&self) -> String { - vec![SECTION_SYMBOL, self.code()].into_iter().collect() - } -} - -impl Chat { - pub fn to_traditional(&self) -> String { - self.to_traditional_parts(Vec::<Formatter>::new().as_ref(), None) - } - - fn to_traditional_parts( - &self, - formatters: &Vec<Formatter>, - color: Option<ColorCode>, - ) -> String { - let mut own_formatters = formatters.clone(); - Self::update_formatter(&mut own_formatters, Formatter::Bold, &self.bold); - Self::update_formatter(&mut own_formatters, Formatter::Italic, &self.italic); - Self::update_formatter(&mut own_formatters, Formatter::Underline, &self.underlined); - Self::update_formatter( - &mut own_formatters, - Formatter::Strikethrough, - &self.strikethrough, - ); - Self::update_formatter(&mut own_formatters, Formatter::Obfuscated, &self.obfuscated); - - let own_color_option = self - .color - .as_ref() - .map(String::as_str) - .and_then(ColorCode::from_name) - .or(color); - - let own_color = own_color_option - .map(Formatter::Color) - .map(|f| f.to_string()); - - let own_formatter = own_formatters - .clone() - .into_iter() - .map(|f| f.to_string()) - .fold(String::new(), |acc, v| acc + v.as_str()); - - let own_color_str = match own_color { - Some(v) => v, - None => String::new(), - }; - - let own_out = own_formatter + own_color_str.as_str() + self.text.as_str(); - - self.extra - .as_ref() - .into_iter() - .flat_map(|v| v.into_iter()) - .map(|child| child.to_traditional_parts(&own_formatters, own_color_option)) - .fold(own_out, |acc, next| acc + next.as_str()) - } - - fn update_formatter(to: &mut Vec<Formatter>, formatter: Formatter, v: &Option<bool>) { - if !to.contains(&formatter) && v.unwrap_or(false) { - to.push(formatter) - } - } - - pub fn from_text(text: &str) -> Chat { - Chat { - text: text.to_owned(), - bold: None, - italic: None, - underlined: None, - strikethrough: None, - obfuscated: None, - color: None, - extra: None, - } - } -} - -impl Serialize for Chat { - fn mc_serialize<S: Serializer>(&self, to: &mut S) -> SerializeResult { - serde_json::to_string(self) - .map_err(move |err| { - SerializeErr::FailedJsonEncode(format!("failed to serialize chat {:?}", err)) - })? - .mc_serialize(to) - } -} - -impl Deserialize for Chat { - fn mc_deserialize(data: &[u8]) -> DeserializeResult<'_, Self> { - String::mc_deserialize(data)?.try_map(move |str| { - serde_json::from_str(str.as_str()).map_err(move |err| { - DeserializeErr::FailedJsonDeserialize(format!( - "failed to deserialize chat {:?}", - err - )) - }) - }) - } -} - -#[cfg(test)] -impl TestRandom for Chat { - fn test_gen_random() -> Self { - let str = String::test_gen_random(); - Chat::from_text(str.as_str()) - } -} - #[derive(Default)] pub struct BytesSerializer { data: Vec<u8>, diff --git a/src/uuid.rs b/src/uuid.rs index 019fbd2..bdd9fbe 100644 --- a/src/uuid.rs +++ b/src/uuid.rs @@ -192,4 +192,19 @@ mod tests { fn test_debug_uuid() { println!("got uuid {:?}", UUID4::random()); } + + #[test] + fn test_to_json() { + let id = UUID4::random(); + let str = serde_json::to_string(&id).expect("should serialize fine"); + assert_eq!(str, format!("\"{}\"", id.to_string())) + } + + #[test] + fn test_from_json() { + let id = UUID4::random(); + let json = format!("\"{}\"", id.to_string()); + let deserialized: UUID4 = serde_json::from_str(json.as_str()).expect("should read fine"); + assert_eq!(deserialized, id); + } } diff --git a/src/v1_15_2.rs b/src/v1_15_2.rs index eba809d..ffc8490 100644 --- a/src/v1_15_2.rs +++ b/src/v1_15_2.rs @@ -3,6 +3,7 @@ use std::fmt::Debug; #[cfg(test)] use crate::protocol::TestRandom; +use std::cell::Cell; #[derive(Debug, PartialEq, Eq, Clone, Copy)] pub enum PacketDirection { @@ -37,7 +38,7 @@ impl State { Login => "Login", Play => "Play", } - .to_owned() + .to_owned() } } @@ -1403,7 +1404,7 @@ impl Deserialize for UpdateScoreSpec { "invalid update score action {}", other )) - .into(), + .into(), }?; Deserialized::ok( @@ -1457,7 +1458,7 @@ impl Serialize for TitleActionSpec { Hide => 0x04, Reset => 0x05, }) - .mc_serialize(to)?; + .mc_serialize(to)?; match self { SetTitle(body) => to.serialize_other(body), @@ -1916,8 +1917,8 @@ pub struct PlayerInfoAction<A: Clone + PartialEq + Debug> { } impl<A> Serialize for PlayerInfoAction<A> -where - A: Serialize + Clone + PartialEq + Debug, + where + A: Serialize + Clone + PartialEq + Debug, { fn mc_serialize<S: Serializer>(&self, to: &mut S) -> SerializeResult { to.serialize_other(&self.uuid)?; @@ -1926,8 +1927,8 @@ where } impl<A> Deserialize for PlayerInfoAction<A> -where - A: Deserialize + Clone + PartialEq + Debug, + where + A: Deserialize + Clone + PartialEq + Debug, { fn mc_deserialize(data: &[u8]) -> DeserializeResult<'_, Self> { let Deserialized { value: uuid, data } = UUID4::mc_deserialize(data)?; @@ -1981,7 +1982,7 @@ impl PlayerInfoActionList { UpdateDisplayName(_) => 0x03, Remove(_) => 0x04, } - .into() + .into() } pub fn len(&self) -> usize { @@ -2185,7 +2186,7 @@ impl WorldBorderAction { SetWarningTime(_) => 0x04, SetWarningBlocks(_) => 0x05, } - .into() + .into() } } @@ -2465,7 +2466,7 @@ impl InteractKind { Attack => 0x01, InteractAt(_) => 0x02, } - .into() + .into() } } @@ -2658,7 +2659,7 @@ impl Recipe { CampfireCooking(_) => "minecraft:campfire_cooking", StoneCutting(_) => "minecraft:stonecutting", } - .to_owned() + .to_owned() } fn serialize_body<S: Serializer>(&self, to: &mut S) -> SerializeResult { @@ -2754,10 +2755,10 @@ impl Deserialize for RecipeSpec { other ))), }? - .map(move |recipe_body| RecipeSpec { - id: recipe_id, - recipe: recipe_body, - })) + .map(move |recipe_body| RecipeSpec { + id: recipe_id, + recipe: recipe_body, + })) } } @@ -2917,26 +2918,11 @@ impl Serialize for ChunkData { impl Deserialize for ChunkData { fn mc_deserialize(data: &[u8]) -> DeserializeResult<'_, Self> { - let Deserialized { - value: chunk_x, - data, - } = i32::mc_deserialize(data)?; - let Deserialized { - value: chunk_z, - data, - } = i32::mc_deserialize(data)?; - let Deserialized { - value: is_full_chunk, - data, - } = bool::mc_deserialize(data)?; - let Deserialized { - value: primary_bit_mask, - data, - } = VarInt::mc_deserialize(data)?; - let Deserialized { - value: heightmaps, - mut data, - } = NamedNbtTag::mc_deserialize(data)?; + let Deserialized { value: chunk_x, data } = i32::mc_deserialize(data)?; + let Deserialized { value: chunk_z, data } = i32::mc_deserialize(data)?; + let Deserialized { value: is_full_chunk, data } = bool::mc_deserialize(data)?; + let Deserialized { value: primary_bit_mask, data } = VarInt::mc_deserialize(data)?; + let Deserialized { value: heightmaps, mut data } = NamedNbtTag::mc_deserialize(data)?; let biomes = if is_full_chunk { let mut biomes: [i32; 1024] = [0i32; 1024]; for elem in &mut biomes { @@ -2948,37 +2934,25 @@ impl Deserialize for ChunkData { } else { None }; - let Deserialized { - value: chunk_data, - data, - } = VarIntCountedArray::<u8>::mc_deserialize(data)?; - let Deserialized { - value: n_block_entities_raw, - mut data, - } = VarInt::mc_deserialize(data)?; + let Deserialized { value: chunk_data, data } = VarIntCountedArray::<u8>::mc_deserialize(data)?; + let Deserialized { value: n_block_entities_raw, mut data } = VarInt::mc_deserialize(data)?; let n_block_entities = n_block_entities_raw.0 as usize; let mut block_entities = Vec::with_capacity(n_block_entities); for _ in 0..n_block_entities { - let Deserialized { - value: entity, - data: rest, - } = NamedNbtTag::mc_deserialize(data)?; + let Deserialized { value: entity, data: rest } = NamedNbtTag::mc_deserialize(data)?; data = rest; block_entities.push(entity); } - Deserialized::ok( - ChunkData { - chunk_x, - chunk_z, - primary_bit_mask, - heightmaps, - biomes, - data: chunk_data, - block_entities, - }, - data, - ) + Deserialized::ok(ChunkData { + chunk_x, + chunk_z, + primary_bit_mask, + heightmaps, + biomes, + data: chunk_data, + block_entities, + }, data) } } @@ -2997,204 +2971,88 @@ impl TestRandom for ChunkData { } } -const LIGHT_DATA_LENGTH: usize = 2048; -const LIGHT_DATA_SECTIONS: usize = 18; - -type LightingData = Vec<Option<[u8; LIGHT_DATA_LENGTH]>>; - -#[derive(Clone, PartialEq, Debug)] -pub struct LightingUpdateSpec { - pub skylight_data: LightingData, - pub blocklight_data: LightingData, - - _cached_skylight_update_mask: Option<VarInt>, - _cached_blocklight_update_mask: Option<VarInt>, - _cached_skylight_reset_mask: Option<VarInt>, - _cached_blocklight_reset_mask: Option<VarInt>, -} - -impl Serialize for LightingUpdateSpec { - fn mc_serialize<S: Serializer>(&self, to: &mut S) -> SerializeResult { - let ( - (skylight_update_mask, skylight_reset_mask), - (blocklight_update_mask, blocklight_reset_mask), - ) = self.get_masks(); - to.serialize_other(&skylight_update_mask)?; - to.serialize_other(&blocklight_update_mask)?; - to.serialize_other(&skylight_reset_mask)?; - to.serialize_other(&blocklight_reset_mask)?; - Self::write_lighting_data(to, &self.skylight_data)?; - Self::write_lighting_data(to, &self.blocklight_data) - } -} - -impl Deserialize for LightingUpdateSpec { - fn mc_deserialize(data: &[u8]) -> DeserializeResult<'_, Self> { - let Deserialized { - value: skylight_update_mask, - data, - } = VarInt::mc_deserialize(data)?; - let Deserialized { - value: blocklight_update_mask, - data, - } = VarInt::mc_deserialize(data)?; - let Deserialized { - value: skylight_reset_mask, - data, - } = VarInt::mc_deserialize(data)?; - let Deserialized { - value: blocklight_reset_mask, - data, - } = VarInt::mc_deserialize(data)?; - let Deserialized { - value: skylight_data, - data, - } = Self::read_lighting_data_arrays(&skylight_update_mask, data)?; - let Deserialized { - value: blocklight_data, - data, - } = Self::read_lighting_data_arrays(&blocklight_update_mask, data)?; +pub const LIGHT_DATA_LENGTH: usize = 2048; +pub const LIGHT_DATA_SECTIONS: usize = 18; - Deserialized::ok( - Self { - skylight_data, - blocklight_data, +pub struct LightingData { + pub data: Vec<Option<[u8; LIGHT_DATA_LENGTH]>>, - _cached_skylight_update_mask: Some(skylight_update_mask), - _cached_blocklight_update_mask: Some(blocklight_update_mask), - _cached_skylight_reset_mask: Some(skylight_reset_mask), - _cached_blocklight_reset_mask: Some(blocklight_reset_mask), - }, - data, - ) - } + _update_mask: Cell<Option<VarInt>>, + _reset_mask: Cell<Option<VarInt>>, } -impl LightingUpdateSpec { - pub fn compute_masks(&mut self) { - let this = &mut *self; - let skylight_data = &this.skylight_data; - let blocklight_data = &this.blocklight_data; - Self::replace_optionals_if_needed( - ( - &mut this._cached_skylight_update_mask, - &mut this._cached_skylight_reset_mask, - ), - move || Self::compute_masks_for(skylight_data), - ); - Self::replace_optionals_if_needed( - ( - &mut this._cached_blocklight_update_mask, - &mut this._cached_blocklight_reset_mask, - ), - move || Self::compute_masks_for(blocklight_data), - ); - } - - pub fn get_masks(&self) -> ((VarInt, VarInt), (VarInt, VarInt)) { - ( - Self::read_or_compute( - ( - &self._cached_skylight_update_mask, - &self._cached_skylight_reset_mask, - ), - || Self::compute_masks_for(&self.skylight_data), - ), - Self::read_or_compute( - ( - &self._cached_blocklight_update_mask, - &self._cached_blocklight_reset_mask, - ), - || Self::compute_masks_for(&self.skylight_data), - ), - ) - } - - fn read_lighting_data_arrays<'a>( - mask: &VarInt, - mut data: &'a [u8], - ) -> DeserializeResult<'a, LightingData> { - let mut out = vec![None; LIGHT_DATA_SECTIONS]; +impl LightingData { + fn deserialize(update_mask: VarInt, reset_mask: VarInt, mut data: &[u8]) -> DeserializeResult<Self> { + let mut out = Vec::with_capacity(LIGHT_DATA_SECTIONS); for i in 0..LIGHT_DATA_SECTIONS { - if (mask.0 & (1 << i)) != 0 { - let Deserialized { - value: length, - data: rest, - } = VarInt::mc_deserialize(data)?; - if (length.0 as usize) != LIGHT_DATA_LENGTH { - return DeserializeErr::CannotUnderstandValue(format!( - "all lighting update arrays are supposed to be 2048, got length of {}", - length - )) - .into(); + if update_mask.0 & (1 << i) != 0 { + if data.len() < LIGHT_DATA_LENGTH { + return Err(DeserializeErr::Eof); } - if rest.len() < LIGHT_DATA_LENGTH { - return DeserializeErr::Eof.into(); - } - - let mut data_entry = [0u8; LIGHT_DATA_LENGTH]; - let (data_src, rest) = rest.split_at(LIGHT_DATA_LENGTH); - data_entry.copy_from_slice(data_src); - out[i] = Some(data_entry); + let (section, rest) = data.split_at(LIGHT_DATA_LENGTH); + let mut to_vec = [0u8; LIGHT_DATA_LENGTH]; + to_vec.copy_from_slice(section); + out.push(Some(to_vec)); data = rest; + } else { + out.push(None) } } - Deserialized::ok(out, data) + let result = Self { + data: out, + _update_mask: Cell::new(Some(update_mask)), + _reset_mask: Cell::new(Some(reset_mask)), + }; + + Deserialized::ok(result, data) } - fn compute_masks_for(data: &LightingData) -> (VarInt, VarInt) { - let mut update_mask = 0; - let mut reset_mask = 0; - for i in 0..LIGHT_DATA_SECTIONS { - match data[i] { - Some(_) => { - update_mask |= 1 << i; - } + fn update_mask(&self) -> &VarInt { + Self::lazily_get(&self._update_mask, || self.compute_update_mask()) + } + + fn reset_mask(&self) -> &VarInt { + Self::lazily_get(&self._reset_mask, || self.compute_reset_mask()) + } + + fn lazily_get<T, F>(option: &Cell<Option<T>>, provider: F) -> &T where F: FnOnce() -> T { + unsafe { + let ptr = option.as_ptr().as_mut().expect("non-null"); + match ptr { + Some(data) => data, None => { - reset_mask |= 1 << i; + ptr.replace(provider()); + ptr.as_ref().expect("it's there") } } } + } - (VarInt(update_mask), VarInt(reset_mask)) + fn compute_update_mask(&self) -> VarInt { + self.compute_has_mask(true) } - fn replace_optionals_if_needed<T1, T2, F>( - targets: (&mut Option<T1>, &mut Option<T2>), - update: F, - ) where - F: FnOnce() -> (T1, T2), - { - let (a, b) = targets; - if a.is_none() || b.is_none() { - let (v_a, v_b) = update(); - *a = Some(v_a); - *b = Some(v_b); - } + fn compute_reset_mask(&self) -> VarInt { + self.compute_has_mask(false) } - fn read_or_compute<T1, T2, F>(targets: (&Option<T1>, &Option<T2>), update: F) -> (T1, T2) - where - F: FnOnce() -> (T1, T2), - T1: Copy, - T2: Copy, - { - let (a, b) = targets; - if a.is_none() || b.is_none() { - let (v_a, v_b) = update(); - (v_a, v_b) - } else { - (a.unwrap(), b.unwrap()) + fn compute_has_mask(&self, has: bool) -> VarInt { + let mut out: u32 = 0; + for i in 0..LIGHT_DATA_SECTIONS { + if self.data[i].is_some() == has { + out |= 1 << i; + } } + + VarInt(out as i32) } - fn write_lighting_data<S: Serializer>(to: &mut S, data: &LightingData) -> SerializeResult { - for i in 0..LIGHT_DATA_SECTIONS { - if let Some(entry) = &data[i] { - to.serialize_other(&VarInt(LIGHT_DATA_LENGTH as i32))?; - to.serialize_bytes(&entry[..])?; + fn serialize_data<S: Serializer>(&self, to: &mut S) -> SerializeResult { + for item in &self.data { + if let Some(contents) = item { + to.serialize_bytes(&contents[..])?; } } @@ -3202,36 +3060,57 @@ impl LightingUpdateSpec { } } -#[cfg(test)] -impl TestRandom for LightingUpdateSpec { - fn test_gen_random() -> Self { - let (skylight_update_mask, skylight_reset_mask, skylight_data) = - Self::gen_random_lighting_data(); - let (blocklight_update_mask, blocklight_reset_mask, blocklight_data) = - Self::gen_random_lighting_data(); +impl std::fmt::Debug for LightingData { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!( + f, + "LightingData(update={:018b}, reset={:018b}, size={}, bytes={})", + self.update_mask().0, + self.reset_mask().0, + self.data.iter().filter(move |v| v.is_some()).count(), + self.data.iter() + .filter_map(move |v| v. + map(move |arr| arr.len())) + .sum::<usize>()) + } +} - Self { - skylight_data, - blocklight_data, +impl std::fmt::Display for LightingData { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + <dyn std::fmt::Debug>::fmt(self, f) + } +} - _cached_skylight_update_mask: Some(skylight_update_mask), - _cached_blocklight_update_mask: Some(blocklight_update_mask), - _cached_skylight_reset_mask: Some(skylight_reset_mask), - _cached_blocklight_reset_mask: Some(blocklight_reset_mask), +impl Clone for LightingData { + fn clone(&self) -> Self { + Self { + data: self.data.clone(), + _update_mask: Cell::new(Some(self.update_mask().clone())), + _reset_mask: Cell::new(Some(self.reset_mask().clone())), } } } +impl PartialEq for LightingData { + fn eq(&self, other: &Self) -> bool { + self.data.eq(&other.data) && + self.update_mask().eq(other.update_mask()) && + self.reset_mask().eq(other.reset_mask()) + } +} + #[cfg(test)] -impl LightingUpdateSpec { +impl LightingData { fn gen_random_mask() -> i32 { let rand: u32 = rand::random(); (rand & ((1 << 19) - 1)) as i32 } +} - fn gen_random_lighting_data() -> (VarInt, VarInt, LightingData) { +#[cfg(test)] +impl TestRandom for LightingData { + fn test_gen_random() -> Self { let set_mask = Self::gen_random_mask(); - let reset_mask = !set_mask & ((1 << 19) - 1); let mut data = vec![None; LIGHT_DATA_SECTIONS]; for i in 0..LIGHT_DATA_SECTIONS { if (set_mask & (1 << i)) != 0 { @@ -3243,7 +3122,65 @@ impl LightingUpdateSpec { } } - (VarInt(set_mask), VarInt(reset_mask), data) + let cache_masks = rand::random::<bool>(); + + if cache_masks { + Self { + data, + _update_mask: Cell::new(Some(VarInt(set_mask))), + _reset_mask: Cell::new(None), + } + } else { + Self { + data, + _update_mask: Cell::new(None), + _reset_mask: Cell::new(None), + } + } + } +} + +#[derive(Clone, PartialEq, Debug)] +pub struct LightingUpdateSpec { + pub skylight_data: LightingData, + pub blocklight_data: LightingData, +} + +impl Serialize for LightingUpdateSpec { + fn mc_serialize<S: Serializer>(&self, to: &mut S) -> SerializeResult { + self.skylight_data.update_mask().mc_serialize(to)?; + self.blocklight_data.update_mask().mc_serialize(to)?; + self.skylight_data.reset_mask().mc_serialize(to)?; + self.blocklight_data.reset_mask().mc_serialize(to)?; + self.skylight_data.serialize_data(to)?; + self.blocklight_data.serialize_data(to) + } +} + +impl Deserialize for LightingUpdateSpec { + fn mc_deserialize(data: &[u8]) -> DeserializeResult<'_, Self> { + let Deserialized { value: skylight_update_mask, data } = VarInt::mc_deserialize(data)?; + let Deserialized { value: blocklight_update_mask, data } = VarInt::mc_deserialize(data)?; + let Deserialized { value: skylight_reset_mask, data } = VarInt::mc_deserialize(data)?; + let Deserialized { value: blocklight_reset_mask, data } = VarInt::mc_deserialize(data)?; + + let Deserialized { value: skylight_data, data } = LightingData::deserialize(skylight_update_mask, skylight_reset_mask, data)?; + let Deserialized { value: blocklight_data, data } = LightingData::deserialize(blocklight_update_mask, blocklight_reset_mask, data)?; + + Deserialized::ok(Self { + skylight_data, + blocklight_data, + }, data) + } +} + +#[cfg(test)] +impl TestRandom for LightingUpdateSpec { + fn test_gen_random() -> Self { + Self { + skylight_data: LightingData::test_gen_random(), + blocklight_data: LightingData::test_gen_random(), + } } } |