Skip to main content

nil_core/
chat.rs

1// Copyright (C) Call of Nil contributors
2// SPDX-License-Identifier: AGPL-3.0-only
3
4use crate::player::PlayerId;
5use bon::Builder;
6use derive_more::From;
7use jiff::Zoned;
8use serde::{Deserialize, Serialize};
9use std::borrow::Cow;
10use std::collections::VecDeque;
11use std::num::NonZeroUsize;
12use std::sync::Arc;
13use strum::EnumIs;
14use uuid::Uuid;
15
16#[derive(Clone, Debug, Deserialize, Serialize)]
17#[derive_const(Default)]
18#[serde(rename_all = "camelCase")]
19#[cfg_attr(feature = "typescript", derive(ts_rs::TS))]
20pub struct Chat {
21  history: ChatHistory,
22}
23
24impl Chat {
25  #[inline]
26  pub fn history(&self) -> ChatHistory {
27    self.history.clone()
28  }
29
30  pub(crate) fn push(&mut self, message: ChatMessage) {
31    self.history.push(message);
32  }
33}
34
35#[derive(Clone, Debug, Deserialize, Serialize)]
36#[serde(rename_all = "camelCase")]
37#[cfg_attr(feature = "typescript", derive(ts_rs::TS))]
38pub struct ChatHistory {
39  #[cfg_attr(feature = "typescript", ts(as = "Vec<ChatMessage>"))]
40  queue: VecDeque<ChatMessage>,
41  size: NonZeroUsize,
42}
43
44impl ChatHistory {
45  pub const MIN: NonZeroUsize = NonZeroUsize::new(100).unwrap();
46  pub const MAX: NonZeroUsize = NonZeroUsize::new(500).unwrap();
47
48  const fn new(size: usize) -> Self {
49    let size = size.clamp(Self::MIN.get(), Self::MAX.get());
50    let size = unsafe { NonZeroUsize::new_unchecked(size) };
51    Self { queue: VecDeque::new(), size }
52  }
53
54  fn push(&mut self, message: ChatMessage) {
55    self.prune();
56    self.queue.push_back(message);
57  }
58
59  fn prune(&mut self) {
60    let size = self.size.get();
61    let len = self.queue.len();
62    if len >= size {
63      self.queue.drain(..=(len - size));
64    }
65  }
66}
67
68impl const Default for ChatHistory {
69  fn default() -> Self {
70    Self::new(Self::MIN.get())
71  }
72}
73
74#[derive(Builder, Clone, Debug, Deserialize, Serialize)]
75#[serde(rename_all = "camelCase")]
76#[cfg_attr(feature = "typescript", derive(ts_rs::TS))]
77pub struct ChatMessage {
78  #[builder(start_fn, into)]
79  content: ChatMessageContent,
80
81  #[builder(skip)]
82  id: ChatMessageId,
83
84  #[builder(default)]
85  kind: ChatMessageKind,
86
87  #[builder(default, into)]
88  author: ChatMessageAuthor,
89
90  #[builder(default = Zoned::now())]
91  time: Zoned,
92}
93
94impl ChatMessage {
95  #[inline]
96  pub fn id(&self) -> ChatMessageId {
97    self.id
98  }
99
100  #[inline]
101  pub fn kind(&self) -> ChatMessageKind {
102    self.kind
103  }
104}
105
106impl From<ChatMessage> for ChatMessageAuthor {
107  fn from(message: ChatMessage) -> Self {
108    message.author
109  }
110}
111
112#[derive(Clone, Copy, Debug, PartialEq, Eq, Deserialize, Serialize)]
113#[cfg_attr(feature = "typescript", derive(ts_rs::TS))]
114pub struct ChatMessageId(Uuid);
115
116impl ChatMessageId {
117  #[must_use]
118  pub fn new() -> Self {
119    Self(Uuid::now_v7())
120  }
121}
122
123impl Default for ChatMessageId {
124  fn default() -> Self {
125    Self::new()
126  }
127}
128
129#[derive(Clone, Copy, Debug, EnumIs, Deserialize, Serialize)]
130#[derive_const(Default)]
131#[serde(rename_all = "kebab-case")]
132#[cfg_attr(feature = "typescript", derive(ts_rs::TS))]
133pub enum ChatMessageKind {
134  #[default]
135  Default,
136}
137
138#[derive(Clone, Debug, Default, Deserialize, Serialize)]
139#[serde(tag = "kind", rename_all = "kebab-case")]
140#[cfg_attr(feature = "typescript", derive(ts_rs::TS))]
141pub enum ChatMessageAuthor {
142  #[default]
143  System,
144  Player {
145    id: PlayerId,
146  },
147}
148
149impl From<PlayerId> for ChatMessageAuthor {
150  fn from(id: PlayerId) -> Self {
151    Self::Player { id }
152  }
153}
154
155impl From<&PlayerId> for ChatMessageAuthor {
156  fn from(id: &PlayerId) -> Self {
157    Self::Player { id: id.clone() }
158  }
159}
160
161#[derive(Debug, From, Deserialize, Serialize)]
162#[from(String, &str, Arc<str>, Box<str>, Cow<'_, str>)]
163#[cfg_attr(feature = "typescript", derive(ts_rs::TS))]
164pub struct ChatMessageContent(Arc<str>);
165
166impl Clone for ChatMessageContent {
167  fn clone(&self) -> Self {
168    Self(Arc::clone(&self.0))
169  }
170}