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