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, Default, Deserialize, Serialize)]
17#[serde(rename_all = "camelCase")]
18pub struct Chat {
19  history: ChatHistory,
20}
21
22impl Chat {
23  #[inline]
24  pub fn history(&self) -> ChatHistory {
25    self.history.clone()
26  }
27
28  pub(crate) fn push(&mut self, message: ChatMessage) {
29    self.history.push(message);
30  }
31}
32
33#[derive(Clone, Debug, Deserialize, Serialize)]
34#[serde(rename_all = "camelCase")]
35pub struct ChatHistory {
36  queue: VecDeque<ChatMessage>,
37  size: NonZeroUsize,
38}
39
40impl ChatHistory {
41  pub const MIN: NonZeroUsize = NonZeroUsize::new(100).unwrap();
42  pub const MAX: NonZeroUsize = NonZeroUsize::new(500).unwrap();
43
44  fn new(size: usize) -> Self {
45    let size = size.clamp(Self::MIN.get(), Self::MAX.get());
46    let size = unsafe { NonZeroUsize::new_unchecked(size) };
47    Self { queue: VecDeque::new(), size }
48  }
49
50  fn push(&mut self, message: ChatMessage) {
51    self.trim();
52    self.queue.push_back(message);
53  }
54
55  fn trim(&mut self) {
56    let size = self.size.get();
57    loop {
58      let len = self.queue.len();
59      if len.saturating_sub(1) >= size {
60        self.queue.pop_front();
61      } else {
62        break;
63      }
64    }
65  }
66}
67
68impl 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")]
76pub struct ChatMessage {
77  #[builder(start_fn, into)]
78  content: ChatMessageContent,
79
80  #[builder(skip)]
81  id: ChatMessageId,
82
83  #[builder(default)]
84  kind: ChatMessageKind,
85
86  #[builder(default, into)]
87  author: ChatMessageAuthor,
88
89  #[builder(default = Zoned::now())]
90  time: Zoned,
91}
92
93impl ChatMessage {
94  #[inline]
95  pub fn id(&self) -> ChatMessageId {
96    self.id
97  }
98
99  #[inline]
100  pub fn kind(&self) -> ChatMessageKind {
101    self.kind
102  }
103}
104
105impl From<ChatMessage> for ChatMessageAuthor {
106  fn from(message: ChatMessage) -> Self {
107    message.author
108  }
109}
110
111#[derive(Clone, Copy, Debug, PartialEq, Eq, Deserialize, Serialize)]
112pub struct ChatMessageId(Uuid);
113
114impl ChatMessageId {
115  #[must_use]
116  pub fn new() -> Self {
117    Self(Uuid::now_v7())
118  }
119}
120
121impl Default for ChatMessageId {
122  fn default() -> Self {
123    Self::new()
124  }
125}
126
127#[derive(Clone, Copy, Debug, Default, EnumIs, Deserialize, Serialize)]
128#[serde(rename_all = "kebab-case")]
129pub enum ChatMessageKind {
130  #[default]
131  Default,
132}
133
134#[derive(Clone, Debug, Default, Deserialize, Serialize)]
135#[serde(tag = "kind", rename_all = "kebab-case")]
136pub enum ChatMessageAuthor {
137  #[default]
138  System,
139  Player {
140    id: PlayerId,
141  },
142}
143
144impl From<PlayerId> for ChatMessageAuthor {
145  fn from(id: PlayerId) -> Self {
146    Self::Player { id }
147  }
148}
149
150impl From<&PlayerId> for ChatMessageAuthor {
151  fn from(id: &PlayerId) -> Self {
152    Self::Player { id: id.clone() }
153  }
154}
155
156#[derive(Debug, From, Deserialize, Serialize)]
157#[from(String, &str, Arc<str>, Box<str>, Cow<'_, str>)]
158pub struct ChatMessageContent(Arc<str>);
159
160impl Clone for ChatMessageContent {
161  fn clone(&self) -> Self {
162    Self(Arc::clone(&self.0))
163  }
164}