use std::{ borrow::Cow, hash::{Hash, Hasher}, }; use bevy::{ app::{Plugin, PostUpdate}, ecs::{ component::Component, entity::Entity, query::Added, reflect::ReflectComponent, system::{Commands, Query, Res}, }, reflect::{std_traits::ReflectDefault, Reflect}, utils::AHasher, }; use dcs_grpc::dcs::{ common::v0::Coalition, net::v0::{net_service_client::NetServiceClient, SendChatRequest}, }; use crate::{components::GrpcBaseUrl, TokioResource}; pub struct TextPlugin; impl Plugin for TextPlugin { fn build(&self, app: &mut bevy::prelude::App) { app.add_systems(PostUpdate, send_text_message); } } fn send_text_message( mut commands: Commands, query: Query<(Entity, &TextMessage), Added>, url: Res, tokio: Res, ) { let url = url.to_string(); for (ent, msg) in query.iter() { if msg.as_str().is_empty() { commands.entity(ent).remove::().despawn(); continue; } let message = msg.clone(); let url = url.clone(); tokio.0.spawn(async move { let Ok(mut client) = NetServiceClient::connect(url.to_string()).await else { return; }; let request = SendChatRequest { message: message.to_string(), coalition: Coalition::All as i32, // target_player_id: player_id, }; println!("> {}", message); client.send_chat(request).await.ok(); }); commands.entity(ent).remove::().despawn(); } } #[derive(Reflect, Component, Clone)] #[reflect(Component, Default, Debug)] pub struct TextMessage { hash: u64, message: Cow<'static, str>, } impl Default for TextMessage { fn default() -> Self { TextMessage::new("") } } impl TextMessage { /// Creates a new [`TextMessage`] from any string-like type. /// /// The internal hash will be computed immediately. pub fn new(message: impl Into>) -> Self { let message = message.into(); let mut message = TextMessage { message, hash: 0 }; message.update_hash(); message } /// Sets the entity's message. /// /// The internal hash will be re-computed. #[inline(always)] pub fn set(&mut self, message: impl Into>) { *self = TextMessage::new(message); } /// Updates the message of the entity in place. /// /// This will allocate a new string if the message was previously /// created from a borrow. #[inline(always)] pub fn mutate(&mut self, f: F) { f(self.message.to_mut()); self.update_hash(); } /// Gets the message of the entity as a `&str`. #[inline(always)] pub fn as_str(&self) -> &str { &self.message } fn update_hash(&mut self) { let mut hasher = AHasher::default(); self.message.hash(&mut hasher); self.hash = hasher.finish(); } } impl std::fmt::Display for TextMessage { #[inline(always)] fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { std::fmt::Display::fmt(&self.message, f) } } impl std::fmt::Debug for TextMessage { #[inline(always)] fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { std::fmt::Debug::fmt(&self.message, f) } }