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(
113  Clone, Copy, Debug, derive_more::Display, PartialEq, Eq, PartialOrd, Ord, Deserialize, Serialize,
114)]
115#[cfg_attr(feature = "typescript", derive(ts_rs::TS))]
116pub struct ChatMessageId(Uuid);
117
118impl ChatMessageId {
119  #[must_use]
120  pub fn new() -> Self {
121    Self(Uuid::now_v7())
122  }
123}
124
125impl Default for ChatMessageId {
126  fn default() -> Self {
127    Self::new()
128  }
129}
130
131#[derive(Clone, Copy, Debug, EnumIs, Deserialize, Serialize)]
132#[derive_const(Default)]
133#[serde(rename_all = "kebab-case")]
134#[cfg_attr(feature = "typescript", derive(ts_rs::TS))]
135pub enum ChatMessageKind {
136  #[default]
137  Default,
138}
139
140#[derive(Clone, Debug, Default, Deserialize, Serialize)]
141#[serde(tag = "kind", rename_all = "kebab-case")]
142#[cfg_attr(feature = "typescript", derive(ts_rs::TS))]
143pub enum ChatMessageAuthor {
144  #[default]
145  System,
146  Player {
147    id: PlayerId,
148  },
149}
150
151impl From<PlayerId> for ChatMessageAuthor {
152  fn from(id: PlayerId) -> Self {
153    Self::Player { id }
154  }
155}
156
157impl From<&PlayerId> for ChatMessageAuthor {
158  fn from(id: &PlayerId) -> Self {
159    Self::Player { id: id.clone() }
160  }
161}
162
163#[derive(Debug, From, Deserialize, Serialize)]
164#[from(String, &str, Arc<str>, Box<str>, Cow<'_, str>)]
165#[cfg_attr(feature = "typescript", derive(ts_rs::TS))]
166pub struct ChatMessageContent(Arc<str>);
167
168impl Clone for ChatMessageContent {
169  fn clone(&self) -> Self {
170    Self(Arc::clone(&self.0))
171  }
172}