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::vec::VecExt;
14use serde::{Deserialize, Serialize};
15use std::collections::HashMap;
16use std::sync::Arc;
17use support::SupportReport;
18use uuid::Uuid;
19
20pub trait Report {
21  fn id(&self) -> ReportId;
22  fn round(&self) -> RoundId;
23  fn time(&self) -> &Zoned;
24}
25
26#[derive(Clone, Debug, Default, Deserialize, Serialize)]
27pub struct ReportManager {
28  reports: HashMap<ReportId, ReportKind>,
29  players: HashMap<PlayerId, Vec<ReportId>>,
30}
31
32impl ReportManager {
33  pub(crate) fn manage<I>(&mut self, report: ReportKind, players: I)
34  where
35    I: IntoIterator<Item = PlayerId>,
36  {
37    let id = report.as_dyn().id();
38    let players = players.into_iter().unique().collect_vec();
39
40    if !players.is_empty() {
41      for player in players {
42        self
43          .players
44          .entry(player)
45          .or_default()
46          .push(id);
47      }
48
49      debug_assert!(!self.reports.contains_key(&id));
50      self.reports.insert(id, report);
51    }
52  }
53
54  pub fn report(&self, id: ReportId) -> Result<&ReportKind> {
55    self
56      .reports
57      .get(&id)
58      .ok_or(Error::ReportNotFound(id))
59  }
60
61  pub fn reports_by<F>(&self, f: F) -> impl Iterator<Item = &ReportKind>
62  where
63    F: Fn((ReportId, &ReportKind)) -> bool,
64  {
65    self
66      .reports
67      .iter()
68      .filter(move |(id, report)| f((**id, report)))
69      .map(|(_, report)| report)
70  }
71
72  pub fn reports_of(&self, player: &PlayerId) -> impl Iterator<Item = ReportId> {
73    self
74      .players
75      .get(player)
76      .map(Vec::as_slice)
77      .unwrap_or_default()
78      .iter()
79      .copied()
80  }
81
82  pub(crate) fn forward(&mut self, id: ReportId, recipient: PlayerId) -> bool {
83    self.reports.contains_key(&id)
84      && self
85        .players
86        .entry(recipient)
87        .or_default()
88        .push_unique(id)
89        .is_none()
90  }
91}
92
93#[derive(Clone, Debug, Deserialize, Serialize)]
94#[serde(tag = "kind", rename_all = "kebab-case")]
95#[remain::sorted]
96pub enum ReportKind {
97  Battle { report: Arc<BattleReport> },
98  Support { report: Arc<SupportReport> },
99}
100
101impl ReportKind {
102  pub fn as_dyn(&self) -> &dyn Report {
103    match self {
104      Self::Battle { report } => report.as_ref(),
105      Self::Support { report } => report.as_ref(),
106    }
107  }
108}
109
110#[must_use]
111#[derive(
112  Clone,
113  Copy,
114  Debug,
115  derive_more::Display,
116  PartialEq,
117  Eq,
118  PartialOrd,
119  Ord,
120  Hash,
121  Deserialize,
122  Serialize,
123)]
124pub struct ReportId(Uuid);
125
126impl ReportId {
127  #[inline]
128  pub fn new() -> Self {
129    Self(Uuid::now_v7())
130  }
131}
132
133impl Default for ReportId {
134  fn default() -> Self {
135    Self::new()
136  }
137}