Skip to main content

nil_core/report/
mod.rs

1// Copyright (C) Call of Nil contributors
2// SPDX-License-Identifier: AGPL-3.0-only
3
4pub mod battle;
5pub mod support;
6
7use crate::error::{Error, Result};
8use crate::player::PlayerId;
9use crate::round::RoundId;
10use battle::BattleReport;
11use itertools::Itertools;
12use jiff::Zoned;
13use nil_util::iter::IterExt;
14use nil_util::vec::VecExt;
15use serde::{Deserialize, Serialize};
16use std::collections::HashMap;
17use std::sync::Arc;
18use support::SupportReport;
19use uuid::Uuid;
20
21pub trait Report {
22  fn id(&self) -> ReportId;
23  fn round(&self) -> RoundId;
24  fn time(&self) -> &Zoned;
25}
26
27#[derive(Clone, Debug, Default, Deserialize, Serialize)]
28pub struct ReportManager {
29  reports: HashMap<ReportId, ReportKind>,
30  players: HashMap<PlayerId, Vec<ReportId>>,
31}
32
33impl ReportManager {
34  pub(crate) fn manage<I>(&mut self, report: ReportKind, players: I)
35  where
36    I: IntoIterator<Item = PlayerId>,
37  {
38    let id = report.as_dyn().id();
39    let players = players.into_iter().unique().collect_vec();
40
41    if !players.is_empty() {
42      for player in players {
43        self
44          .players
45          .entry(player)
46          .or_default()
47          .push(id);
48      }
49
50      debug_assert!(!self.reports.contains_key(&id));
51      self.reports.insert(id, report);
52    }
53  }
54
55  pub fn report(&self, id: ReportId) -> Result<&ReportKind> {
56    self
57      .reports
58      .get(&id)
59      .ok_or(Error::ReportNotFound(id))
60  }
61
62  pub fn reports_by<F>(&self, f: F) -> impl Iterator<Item = &ReportKind>
63  where
64    F: Fn((ReportId, &ReportKind)) -> bool,
65  {
66    self
67      .reports
68      .iter()
69      .filter(move |(id, report)| f((**id, report)))
70      .map(|(_, report)| report)
71  }
72
73  pub fn reports_of(&self, player: &PlayerId) -> impl Iterator<Item = ReportId> {
74    self
75      .players
76      .get(player)
77      .map(Vec::as_slice)
78      .unwrap_or_default()
79      .iter()
80      .copied()
81  }
82
83  /// Forwards a report to a player.
84  pub(crate) fn forward(&mut self, id: ReportId, recipient: PlayerId) -> bool {
85    self.reports.contains_key(&id)
86      && self
87        .players
88        .entry(recipient)
89        .or_default()
90        .push_unique(id)
91        .is_none()
92  }
93
94  /// Removes a report from a player's list of reports.
95  pub(crate) fn remove_of(&mut self, id: ReportId, player: &PlayerId) {
96    if let Some(reports) = self.players.get_mut(player) {
97      reports.retain(|report| *report != id);
98    }
99  }
100
101  /// Removes reports that are not associated with any player.
102  pub(crate) fn prune(&mut self) {
103    let reports = self
104      .players
105      .values()
106      .flat_map(|ids| ids.iter().copied())
107      .collect_set();
108
109    self
110      .reports
111      .retain(|id, _| reports.contains(id));
112  }
113}
114
115#[derive(Clone, Debug, Deserialize, Serialize)]
116#[serde(tag = "kind", rename_all = "kebab-case")]
117#[cfg_attr(feature = "typescript", derive(ts_rs::TS))]
118#[remain::sorted]
119pub enum ReportKind {
120  Battle { report: Arc<BattleReport> },
121  Support { report: Arc<SupportReport> },
122}
123
124impl ReportKind {
125  pub fn as_dyn(&self) -> &dyn Report {
126    match self {
127      Self::Battle { report } => report.as_ref(),
128      Self::Support { report } => report.as_ref(),
129    }
130  }
131
132  #[inline]
133  pub fn id(&self) -> ReportId {
134    self.as_dyn().id()
135  }
136}
137
138#[must_use]
139#[derive(
140  Clone,
141  Copy,
142  Debug,
143  derive_more::Display,
144  PartialEq,
145  Eq,
146  PartialOrd,
147  Ord,
148  Hash,
149  Deserialize,
150  Serialize,
151)]
152#[cfg_attr(feature = "typescript", derive(ts_rs::TS))]
153pub struct ReportId(Uuid);
154
155impl ReportId {
156  #[inline]
157  pub fn new() -> Self {
158    Self(Uuid::now_v7())
159  }
160}
161
162impl Default for ReportId {
163  fn default() -> Self {
164    Self::new()
165  }
166}