0
0
mirror of https://github.com/Pumpkin-MC/Pumpkin synced 2025-04-12 17:33:04 +00:00

Use Cow's in TextComponent

We now don't always convert &str to Strings allowing us to use stack allocations
This commit is contained in:
Snowiiii
2024-08-19 20:05:18 +02:00
parent dfc0b8afe9
commit a6d9ed216d
26 changed files with 134 additions and 149 deletions

@ -7,7 +7,7 @@ use crate::{BitSet, ClientPacket, ServerPacket, VarInt, VarIntType};
use super::{deserializer, serializer, ByteBuffer, DeserializerError};
impl Serialize for BitSet<'static> {
impl<'a> Serialize for BitSet<'a> {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,

@ -11,7 +11,7 @@ pub struct CConfigAddResourcePack<'a> {
url: &'a str,
hash: &'a str, // max 40
forced: bool,
prompt_message: Option<TextComponent>,
prompt_message: Option<TextComponent<'a>>,
}
impl<'a> CConfigAddResourcePack<'a> {
@ -20,7 +20,7 @@ impl<'a> CConfigAddResourcePack<'a> {
url: &'a str,
hash: &'a str,
forced: bool,
prompt_message: Option<TextComponent>,
prompt_message: Option<TextComponent<'a>>,
) -> Self {
Self {
uuid,

@ -1,7 +1,6 @@
use pumpkin_macros::packet;
use serde::Serialize;
use crate::{bytebuf::ByteBuffer, ClientPacket};
#[derive(Serialize)]
#[packet(0x01)]

@ -4,12 +4,12 @@ use serde::Serialize;
#[derive(Serialize)]
#[packet(0x4C)]
pub struct CActionBar {
action_bar: TextComponent,
pub struct CActionBar<'a> {
action_bar: TextComponent<'a>,
}
impl CActionBar {
pub fn new(action_bar: TextComponent) -> Self {
impl<'a> CActionBar<'a> {
pub fn new(action_bar: TextComponent<'a>) -> Self {
Self { action_bar }
}
}

@ -6,19 +6,19 @@ use crate::VarInt;
#[derive(Serialize)]
#[packet(0x1E)]
pub struct CDisguisedChatMessage {
message: TextComponent,
pub struct CDisguisedChatMessage<'a> {
message: TextComponent<'a>,
chat_type: VarInt,
sender_name: TextComponent,
target_name: Option<TextComponent>,
sender_name: TextComponent<'a>,
target_name: Option<TextComponent<'a>>,
}
impl CDisguisedChatMessage {
impl<'a> CDisguisedChatMessage<'a> {
pub fn new(
message: TextComponent,
message: TextComponent<'a>,
chat_type: VarInt,
sender_name: TextComponent,
target_name: Option<TextComponent>,
sender_name: TextComponent<'a>,
target_name: Option<TextComponent<'a>>,
) -> Self {
Self {
message,

@ -1,7 +1,7 @@
use pumpkin_macros::packet;
use serde::Serialize;
use crate::VarInt;
use crate::{position::WorldPosition, VarInt};
#[derive(Serialize)]
#[packet(0x2B)]
@ -23,7 +23,7 @@ pub struct CLogin<'a> {
previous_gamemode: i8,
debug: bool,
is_flat: bool,
death_dimension_name: Option<(String, i64)>, // POSITION NOT STRING
death_dimension_name: Option<(WorldPosition, i64)>,
portal_cooldown: VarInt,
enforce_secure_chat: bool,
}
@ -47,7 +47,7 @@ impl<'a> CLogin<'a> {
previous_gamemode: i8,
debug: bool,
is_flat: bool,
death_dimension_name: Option<(String, i64)>,
death_dimension_name: Option<(WorldPosition, i64)>,
portal_cooldown: VarInt,
enforce_secure_chat: bool,
) -> Self {

@ -6,14 +6,14 @@ use crate::VarInt;
#[derive(Serialize)]
#[packet(0x33)]
pub struct COpenScreen {
pub struct COpenScreen<'a> {
window_id: VarInt,
window_type: VarInt,
window_title: TextComponent,
window_title: TextComponent<'a>,
}
impl COpenScreen {
pub fn new(window_id: VarInt, window_type: VarInt, window_title: TextComponent) -> Self {
impl<'a> COpenScreen<'a> {
pub fn new(window_id: VarInt, window_type: VarInt, window_title: TextComponent<'a>) -> Self {
Self {
window_id,
window_type,

@ -4,12 +4,12 @@ use serde::Serialize;
#[derive(Serialize)]
#[packet(0x1D)]
pub struct CPlayDisconnect {
reason: TextComponent,
pub struct CPlayDisconnect<'a> {
reason: TextComponent<'a>,
}
impl CPlayDisconnect {
pub fn new(reason: TextComponent) -> Self {
impl<'a> CPlayDisconnect<'a> {
pub fn new(reason: TextComponent<'a>) -> Self {
Self { reason }
}
}

@ -11,19 +11,18 @@ pub struct CPlayerChatMessage<'a> {
sender: UUID,
index: VarInt,
message_signature: Option<&'a [u8]>,
message: String,
message: &'a str,
timestamp: i64,
salt: i64,
previous_messages_count: VarInt,
previous_messages: &'a [PreviousMessage<'a>], // max 20
unsigned_content: Option<TextComponent>,
unsigned_content: Option<TextComponent<'a>>,
/// See `FilterType`
filter_type: VarInt,
// TODO: THIS IS A HACK, We currently don't support writing or reading bitsets
filter_type_bits: bool,
filter_type_bits: Option<BitSet<'a>>,
chat_type: VarInt,
sender_name: TextComponent,
target_name: Option<TextComponent>,
sender_name: TextComponent<'a>,
target_name: Option<TextComponent<'a>>,
}
impl<'a> CPlayerChatMessage<'a> {
@ -32,15 +31,16 @@ impl<'a> CPlayerChatMessage<'a> {
sender: UUID,
index: VarInt,
message_signature: Option<&'a [u8]>,
message: String,
message: &'a str,
timestamp: i64,
salt: i64,
previous_messages: &'a [PreviousMessage<'a>],
unsigned_content: Option<TextComponent>,
unsigned_content: Option<TextComponent<'a>>,
filter_type: VarInt,
filter_type_bits: Option<BitSet<'a>>,
chat_type: VarInt,
sender_name: TextComponent,
target_name: Option<TextComponent>,
sender_name: TextComponent<'a>,
target_name: Option<TextComponent<'a>>,
) -> Self {
Self {
sender,
@ -53,7 +53,7 @@ impl<'a> CPlayerChatMessage<'a> {
previous_messages,
unsigned_content,
filter_type,
filter_type_bits: false,
filter_type_bits,
chat_type,
sender_name,
target_name,

@ -4,12 +4,12 @@ use serde::Serialize;
#[derive(Serialize)]
#[packet(0x65)]
pub struct CTitleText {
title: TextComponent,
pub struct CTitleText<'a> {
title: TextComponent<'a>,
}
impl CTitleText {
pub fn new(title: TextComponent) -> Self {
impl<'a> CTitleText<'a> {
pub fn new(title: TextComponent<'a>) -> Self {
Self { title }
}
}

@ -4,12 +4,12 @@ use serde::Serialize;
#[derive(Serialize)]
#[packet(0x63)]
pub struct CSubtitle {
subtitle: TextComponent,
pub struct CSubtitle<'a> {
subtitle: TextComponent<'a>,
}
impl CSubtitle {
pub fn new(subtitle: TextComponent) -> Self {
impl<'a> CSubtitle<'a> {
pub fn new(subtitle: TextComponent<'a>) -> Self {
Self { subtitle }
}
}

@ -4,13 +4,13 @@ use serde::Serialize;
#[derive(Serialize)]
#[packet(0x6C)]
pub struct CSystemChatMessge {
content: TextComponent,
pub struct CSystemChatMessge<'a> {
content: TextComponent<'a>,
overlay: bool,
}
impl CSystemChatMessge {
pub fn new(content: TextComponent, overlay: bool) -> Self {
impl<'a> CSystemChatMessge<'a> {
pub fn new(content: TextComponent<'a>, overlay: bool) -> Self {
Self { content, overlay }
}
}

@ -1,6 +1,6 @@
use bytebuf::{packet_id::Packet, ByteBuffer, DeserializerError};
use bytes::Buf;
use serde::{Deserialize, Serialize, Serializer};
use serde::{Deserialize, Serialize};
use std::io::{self, Write};
use thiserror::Error;

@ -2,7 +2,7 @@ use pumpkin_macros::packet;
use crate::{
bytebuf::{ByteBuffer, DeserializerError},
ServerPacket,
ServerPacket, VarInt,
};
// derive(Deserialize)]
@ -12,7 +12,7 @@ pub struct SChatMessage {
pub timestamp: i64,
pub salt: i64,
pub signature: Option<Vec<u8>>,
// pub messagee_count: VarInt,
pub messagee_count: VarInt,
// acknowledged: BitSet,
}
@ -24,7 +24,7 @@ impl ServerPacket for SChatMessage {
timestamp: bytebuf.get_i64(),
salt: bytebuf.get_i64(),
signature: bytebuf.get_option(|v| v.get_slice().to_vec()),
//messagee_count: bytebuf.get_var_int(),
messagee_count: bytebuf.get_var_int(),
})
}
}

@ -1,4 +1,3 @@
use std::char::MAX;
use num_derive::FromPrimitive;
use num_traits::FromPrimitive;

@ -11,7 +11,7 @@ pub struct ChatType {
pub struct Decoration {
translation_key: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
style: Option<Style>,
style: Option<Style<'static>>,
parameters: Vec<String>,
}

@ -1,19 +1,21 @@
use std::borrow::Cow;
use serde::{Deserialize, Serialize};
/// Action to take on click of the text.
#[derive(Clone, PartialEq, Debug, Serialize, Deserialize)]
#[serde(tag = "action", content = "value", rename_all = "snake_case")]
pub enum ClickEvent {
pub enum ClickEvent<'a> {
/// Opens a URL
OpenUrl(String),
OpenUrl(Cow<'a, str>),
/// Works in signs, but only on the root text component
RunCommand(String),
RunCommand(Cow<'a, str>),
/// Replaces the contents of the chat box with the text, not necessarily a
/// command.
SuggestCommand(String),
SuggestCommand(Cow<'a, str>),
/// Only usable within written books. Changes the page of the book. Indexing
/// starts at 1.
ChangePage(i32),
/// Copies the given text to system clipboard
CopyToClipboard(String),
CopyToClipboard(Cow<'a, str>),
}

@ -1,3 +1,5 @@
use std::borrow::Cow;
use serde::{Deserialize, Serialize};
use crate::Text;
@ -5,17 +7,17 @@ use crate::Text;
#[derive(Clone, Debug, Serialize, Deserialize)]
#[serde(tag = "action", content = "contents", rename_all = "snake_case")]
#[allow(clippy::enum_variant_names)]
pub enum HoverEvent {
pub enum HoverEvent<'a> {
/// Displays a tooltip with the given text.
ShowText(Text),
ShowText(Text<'a>),
/// Shows an item.
ShowItem {
/// Resource identifier of the item
id: String,
id: Cow<'a, str>,
/// Number of the items in the stack
count: Option<i32>,
/// NBT information about the item (sNBT format)
tag: String,
tag: Cow<'a, str>,
},
/// Shows an entity.
ShowEntity {
@ -24,9 +26,9 @@ pub enum HoverEvent {
/// Resource identifier of the entity
#[serde(rename = "type")]
#[serde(default, skip_serializing_if = "Option::is_none")]
kind: Option<String>,
kind: Option<Cow<'a, str>>,
/// Optional custom name for the entity
#[serde(default, skip_serializing_if = "Option::is_none")]
name: Option<Text>,
name: Option<Text<'a>>,
},
}

@ -1,4 +1,5 @@
use core::str;
use std::borrow::Cow;
use click::ClickEvent;
use color::Color;
@ -12,25 +13,34 @@ pub mod color;
pub mod hover;
pub mod style;
#[derive(Clone, Default, Debug, Serialize, Deserialize)]
#[derive(Clone, Debug, Serialize, Deserialize)]
#[serde(transparent)]
pub struct Text(pub Box<TextComponent>);
pub struct Text<'a>(pub Box<TextComponent<'a>>);
// Represents a Text component
// Reference: https://wiki.vg/Text_formatting#Text_components
#[derive(Clone, Default, Debug, Deserialize)]
#[derive(Clone, Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct TextComponent {
pub struct TextComponent<'a> {
/// The actual text
#[serde(flatten)]
pub content: TextContent,
pub content: TextContent<'a>,
/// Style of the text. Bold, Italic, underline, Color...
/// Also has `ClickEvent
#[serde(flatten)]
pub style: Style,
pub style: Style<'a>,
}
impl serde::Serialize for TextComponent {
impl<'a> TextComponent<'a> {
pub fn text(text: &'a str) -> Self {
Self {
content: TextContent::Text { text: text.into() },
style: Style::default(),
}
}
}
impl<'a> serde::Serialize for TextComponent<'a> {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
@ -39,7 +49,7 @@ impl serde::Serialize for TextComponent {
}
}
impl TextComponent {
impl<'a> TextComponent<'a> {
pub fn color(mut self, color: Color) -> Self {
self.style.color = Some(color);
self
@ -87,13 +97,13 @@ impl TextComponent {
}
/// Allows for events to occur when the player clicks on text. Only work in chat.
pub fn click_event(mut self, event: ClickEvent) -> Self {
pub fn click_event(mut self, event: ClickEvent<'a>) -> Self {
self.style.click_event = Some(event);
self
}
/// Allows for a tooltip to be displayed when the player hovers their mouse over text.
pub fn hover_event(mut self, event: HoverEvent) -> Self {
pub fn hover_event(mut self, event: HoverEvent<'a>) -> Self {
self.style.hover_event = Some(event);
self
}
@ -104,9 +114,9 @@ impl TextComponent {
#[serde(rename_all = "camelCase")]
struct TempStruct<'a> {
#[serde(flatten)]
text: &'a TextContent,
text: &'a TextContent<'a>,
#[serde(flatten)]
style: &'a Style,
style: &'a Style<'a>,
}
let astruct = TempStruct {
text: &self.content,
@ -118,50 +128,24 @@ impl TextComponent {
}
}
impl From<String> for TextComponent {
fn from(value: String) -> Self {
Self {
content: TextContent::Text { text: value },
style: Style::default(),
}
}
}
impl From<&str> for TextComponent {
fn from(value: &str) -> Self {
Self {
content: TextContent::Text {
text: value.to_string(),
},
style: Style::default(),
}
}
}
#[derive(Clone, Debug, Serialize, Deserialize)]
#[serde(untagged)]
pub enum TextContent {
pub enum TextContent<'a> {
/// Raw Text
Text { text: String },
Text { text: Cow<'a, str> },
/// Translated text
Translate {
translate: String,
translate: Cow<'a, str>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
with: Vec<Text>,
with: Vec<Text<'a>>,
},
/// Displays the name of one or more entities found by a selector.
EntityNames {
selector: String,
selector: Cow<'a, str>,
#[serde(default, skip_serializing_if = "Option::is_none")]
separator: Option<Text>,
separator: Option<Cow<'a, str>>,
},
/// A keybind identifier
/// https://minecraft.fandom.com/wiki/Controls#Configurable_controls
Keybind { keybind: String },
}
impl Default for TextContent {
fn default() -> Self {
Self::Text { text: "".into() }
}
Keybind { keybind: Cow<'a, str> },
}

@ -7,7 +7,7 @@ use crate::{
};
#[derive(Clone, Debug, Serialize, Deserialize, Default)]
pub struct Style {
pub struct Style<'a> {
/// Changes the color to render the content
pub color: Option<Color>,
#[serde(default, skip_serializing_if = "Option::is_none")]
@ -33,13 +33,13 @@ pub struct Style {
pub insertion: Option<String>,
/// Allows for events to occur when the player clicks on text. Only work in chat.
#[serde(default, skip_serializing_if = "Option::is_none")]
pub click_event: Option<ClickEvent>,
pub click_event: Option<ClickEvent<'a>>,
/// Allows for a tooltip to be displayed when the player hovers their mouse over text.
#[serde(default, skip_serializing_if = "Option::is_none")]
pub hover_event: Option<HoverEvent>,
pub hover_event: Option<HoverEvent<'a>>,
}
impl Style {
impl<'a> Style<'a> {
pub fn color(mut self, color: Color) -> Self {
self.color = Some(color);
self
@ -87,13 +87,13 @@ impl Style {
}
/// Allows for events to occur when the player clicks on text. Only work in chat.
pub fn click_event(mut self, event: ClickEvent) -> Self {
pub fn click_event(mut self, event: ClickEvent<'a>) -> Self {
self.click_event = Some(event);
self
}
/// Allows for a tooltip to be displayed when the player hovers their mouse over text.
pub fn hover_event(mut self, event: HoverEvent) -> Self {
pub fn hover_event(mut self, event: HoverEvent<'a>) -> Self {
self.hover_event = Some(event);
self
}

@ -1,4 +1,3 @@
use num_traits::FromPrimitive;
use pumpkin_protocol::{
client::{
@ -199,7 +198,7 @@ impl Client {
let prompt_message = if resource_config.prompt_message.is_empty() {
None
} else {
Some(TextComponent::from(resource_config.prompt_message.clone()))
Some(TextComponent::text(&resource_config.prompt_message))
};
self.send_packet(&CConfigAddResourcePack::new(
pumpkin_protocol::uuid::UUID(uuid::Uuid::new_v3(

@ -375,7 +375,7 @@ impl Client {
.unwrap_or_else(|_| self.close());
}
ConnectionState::Play => {
self.try_send_packet(&CPlayDisconnect::new(TextComponent::from(reason)))
self.try_send_packet(&CPlayDisconnect::new(TextComponent::text(reason)))
.unwrap_or_else(|_| self.close());
}
_ => {

@ -5,7 +5,8 @@ use pumpkin_entity::EntityId;
use pumpkin_protocol::{
client::play::{
Animation, CBlockUpdate, CEntityAnimation, CEntityVelocity, CHeadRot, CHurtAnimation,
CSystemChatMessge, CUpdateEntityPos, CUpdateEntityPosRot, CUpdateEntityRot,
CPlayerChatMessage, CUpdateEntityPos, CUpdateEntityPosRot,
CUpdateEntityRot, FilterType,
},
position::WorldPosition,
server::play::{
@ -209,38 +210,30 @@ impl Client {
}
pub fn handle_chat_message(&mut self, server: &mut Server, chat_message: SChatMessage) {
dbg!("got message");
let message = chat_message.message;
// TODO: filter message & validation
let gameprofile = self.gameprofile.as_ref().unwrap();
dbg!("got message");
// yeah a "raw system message", the ugly way to do that, but it works
server.broadcast_packet(
self,
&CSystemChatMessge::new(
TextComponent::from(format!("{}: {}", gameprofile.name, message)),
false,
),
);
/* server.broadcast_packet(
self,
CPlayerChatMessage::new(
&CPlayerChatMessage::new(
pumpkin_protocol::uuid::UUID(gameprofile.id),
0.into(),
None,
message.clone(),
chat_message.messagee_count,
chat_message.signature.as_deref(),
&message,
chat_message.timestamp,
chat_message.salt,
&[],
Some(TextComponent::from(message.clone())),
Some(TextComponent::text(&message)),
pumpkin_protocol::VarInt(FilterType::PassThrough as i32),
0.into(),
TextComponent::from(gameprofile.name.clone()),
None,
1.into(),
TextComponent::text(&gameprofile.name.clone()),
None,
),
)
*/
/* server.broadcast_packet(
self,
&CDisguisedChatMessage::new(
@ -272,12 +265,13 @@ impl Client {
pub fn handle_interact(&mut self, server: &mut Server, interact: SInteract) {
let action = ActionType::from_i32(interact.typ.0).unwrap();
if action == ActionType::Attack {
let attacker_player = self.player.as_ref().unwrap();
let entity_id = interact.entity_id;
// TODO: do validation and stuff
let config = &server.advanced_config.pvp;
if config.enabled {
let attacked_client = server.get_by_entityid(self, entity_id.0 as EntityId);
let attacker_player = self.player.as_mut().unwrap();
attacker_player.sneaking = interact.sneaking;
if let Some(mut client) = attacked_client {
let token = client.token.clone();
let player = client.player.as_mut().unwrap();
@ -299,9 +293,10 @@ impl Client {
player.velocity.y as f32,
player.velocity.z as f32,
);
attacker_player.velocity = attacker_player.velocity.multiply(0.6, 1.0, 0.6);
player.velocity = velo;
client.send_packet(packet);
// attacker_player.velocity = attacker_player.velocity.multiply(0.6, 1.0, 0.6);
}
if config.hurt_animation {
// TODO

@ -18,7 +18,7 @@ impl<'a> Command<'a> for GamemodeCommand {
if args.len() != 2 {
player.send_system_message(
TextComponent::from("Usage: /gamemode <mode>")
TextComponent::text("Usage: /gamemode <mode>")
.color_named(pumpkin_text::color::NamedColor::Red),
);
return;
@ -28,21 +28,26 @@ impl<'a> Command<'a> for GamemodeCommand {
match mode_str.parse() {
Ok(mode) => {
player.set_gamemode(mode);
player.send_system_message(format!("Set own game mode to {:?}", mode).into());
player.send_system_message(TextComponent::text(&format!(
"Set own game mode to {:?}",
mode
)));
}
Err(_) => {
// try to parse from number
if let Ok(i) = mode_str.parse::<u8>() {
if let Some(mode) = GameMode::from_u8(i) {
player.set_gamemode(mode);
player
.send_system_message(format!("Set own game mode to {:?}", mode).into());
player.send_system_message(TextComponent::text(&format!(
"Set own game mode to {:?}",
mode
)));
return;
}
}
player.send_system_message(
TextComponent::from("Invalid gamemode")
TextComponent::text("Invalid gamemode")
.color_named(pumpkin_text::color::NamedColor::Red),
);
}

@ -79,5 +79,5 @@ pub fn handle_command(sender: &mut CommandSender, command: &str) {
return;
}
// TODO: red color
sender.send_message("Command not Found".into());
sender.send_message(TextComponent::text("Command not Found"));
}

@ -15,6 +15,6 @@ impl<'a> Command<'a> for PumpkinCommand {
fn on_execute(sender: &mut super::CommandSender<'a>, _command: String) {
let version = env!("CARGO_PKG_VERSION");
let description = env!("CARGO_PKG_DESCRIPTION");
sender.send_message(TextComponent::from(format!("Pumpkin {version}, {description} (Minecraft {CURRENT_MC_VERSION}, Protocol {CURRENT_MC_PROTOCOL})")).color_named(NamedColor::Green))
sender.send_message(TextComponent::text(&format!("Pumpkin {version}, {description} (Minecraft {CURRENT_MC_VERSION}, Protocol {CURRENT_MC_PROTOCOL})")).color_named(NamedColor::Green))
}
}