Skip to main content

nil_core/infrastructure/
queue.rs

1// Copyright (C) Call of Nil contributors
2// SPDX-License-Identifier: AGPL-3.0-only
3
4use crate::resources::workforce::Workforce;
5use std::collections::VecDeque;
6
7pub trait InfrastructureQueue<T>
8where
9  T: InfrastructureQueueOrder,
10{
11  fn queue(&self) -> &VecDeque<T>;
12  fn queue_mut(&mut self) -> &mut VecDeque<T>;
13
14  /// Consumes workforce until it runs out or the entire queue is completed.
15  fn process(&mut self, mut workforce: Workforce) -> Vec<T> {
16    let mut orders = Vec::new();
17    loop {
18      if workforce == 0 {
19        break;
20      }
21
22      match self
23        .queue_mut()
24        .pop_front_if(|order| order.consume(&mut workforce))
25      {
26        Some(order) => orders.push(order),
27        None => break,
28      }
29    }
30
31    if !orders.is_empty() {
32      self.queue_mut().shrink_to_fit();
33    }
34
35    orders
36  }
37
38  fn iter<'a>(&'a self) -> impl Iterator<Item = &'a T>
39  where
40    T: 'a,
41  {
42    self.queue().iter()
43  }
44
45  fn len(&self) -> usize {
46    self.queue().len()
47  }
48
49  fn is_empty(&self) -> bool {
50    self.queue().is_empty()
51  }
52
53  fn sum_pending_workforce(&self) -> Workforce {
54    self
55      .iter()
56      .filter_map(InfrastructureQueueOrder::pending_workforce)
57      .map(u32::from)
58      .sum::<u32>()
59      .into()
60  }
61}
62
63pub trait InfrastructureQueueOrder {
64  fn is_done(&self) -> bool;
65  fn set_done(&mut self);
66
67  fn pending_workforce(&self) -> Option<Workforce>;
68  fn pending_workforce_mut(&mut self) -> Option<&mut Workforce>;
69
70  fn consume(&mut self, workforce: &mut Workforce) -> bool {
71    if let Some(pending) = self.pending_workforce_mut() {
72      if *pending > 0 {
73        let previous = *pending;
74        *pending -= *workforce;
75
76        // Decreases the available workforce based on the quantity consumed by this order.
77        *workforce -= previous - *pending;
78      }
79
80      if *pending == 0 {
81        self.set_done();
82      }
83    }
84
85    self.is_done()
86  }
87}
88
89#[doc(hidden)]
90#[macro_export]
91macro_rules! decl_recruit_queue {
92  ($building:ident) => {
93    paste::paste! {
94      impl [<$building RecruitQueue>] {
95        pub(crate) fn recruit(
96          &mut self,
97          request: &[<$building RecruitOrderRequest>],
98          current_resources: Option<&Resources>,
99        ) -> Result<&[<$building RecruitOrder>]> {
100          let unit = UnitBox::from(request.unit);
101          let chunk = unit.as_dyn().chunk();
102          let size = SquadSize::new(chunk.size() * request.chunks);
103          let resources = &chunk.resources() * request.chunks;
104          let workforce = chunk.workforce() * request.chunks;
105
106          if let Some(current_resources) = current_resources
107            && current_resources
108              .checked_sub(&resources)
109              .is_none()
110          {
111            return Err(Error::InsufficientResources);
112          }
113
114          self.orders.push_back([<$building RecruitOrder>] {
115            id: [<$building RecruitOrderId>]::new(),
116            squad: Squad::new(unit.id(), size),
117            resources,
118            workforce,
119            state: [<$building RecruitOrderState>]::new(workforce),
120          });
121
122          let len = self.orders.len();
123          Ok(unsafe {
124            self
125              .orders
126              .get(len.unchecked_sub(1))
127              .unwrap_unchecked()
128          })
129        }
130
131        /// Cancels a recruit order.
132        #[must_use]
133        pub(crate) fn cancel(&mut self, id: [<$building RecruitOrderId>]) -> Option<[<$building RecruitOrder>]> {
134          let position = self
135            .orders
136            .iter()
137            .position(|order| order.id == id)?;
138
139          self.orders.remove(position)
140        }
141      }
142
143      impl InfrastructureQueue<[<$building RecruitOrder>]> for [<$building RecruitQueue>] {
144        fn queue(&self) -> &VecDeque<[<$building RecruitOrder>]> {
145          &self.orders
146        }
147
148        fn queue_mut(&mut self) -> &mut VecDeque<[<$building RecruitOrder>]> {
149          &mut self.orders
150        }
151      }
152
153      #[must_use]
154      #[derive(Clone, Debug, Deserialize, Serialize)]
155      #[serde(rename_all = "camelCase")]
156      #[cfg_attr(feature = "typescript", derive(ts_rs::TS))]
157      pub struct [<$building RecruitOrder>] {
158        id: [<$building RecruitOrderId>],
159        squad: Squad,
160        resources: Resources,
161        workforce: Workforce,
162        state: [<$building RecruitOrderState>],
163      }
164
165      impl [<$building RecruitOrder>] {
166        #[inline]
167        pub fn id(&self) -> [<$building RecruitOrderId>] {
168          self.id
169        }
170
171        #[inline]
172        pub fn squad(&self) -> &Squad {
173          &self.squad
174        }
175
176        #[inline]
177        pub fn resources(&self) -> &Resources {
178          &self.resources
179        }
180      }
181
182      impl From<[<$building RecruitOrder>]> for Squad {
183        fn from(order: [<$building RecruitOrder>]) -> Self {
184          order.squad
185        }
186      }
187
188      impl InfrastructureQueueOrder for [<$building RecruitOrder>] {
189        fn is_done(&self) -> bool {
190          self.state.is_done()
191        }
192
193        fn set_done(&mut self) {
194          self.state = [<$building RecruitOrderState>]::Done;
195        }
196
197        fn pending_workforce(&self) -> Option<Workforce> {
198          self.state.pending_workforce()
199        }
200
201        fn pending_workforce_mut(&mut self) -> Option<&mut Workforce> {
202          self.state.pending_workforce_mut()
203        }
204      }
205
206      #[derive(Clone, Copy, Debug, PartialEq, Eq, Deserialize, Serialize)]
207      #[cfg_attr(feature = "typescript", derive(ts_rs::TS))]
208      pub struct [<$building RecruitOrderId>](Uuid);
209
210      impl [<$building RecruitOrderId>] {
211        #[must_use]
212        pub fn new() -> Self {
213          Self(Uuid::new_v4())
214        }
215      }
216
217      impl Default for [<$building RecruitOrderId>] {
218        fn default() -> Self {
219          Self::new()
220        }
221      }
222
223      #[derive(Clone, Debug, EnumIs, Deserialize, Serialize)]
224      #[serde(tag = "kind", rename_all = "kebab-case")]
225      #[cfg_attr(feature = "typescript", derive(ts_rs::TS))]
226      pub enum [<$building RecruitOrderState>] {
227        Pending { workforce: Workforce },
228        Done,
229      }
230
231      impl [<$building RecruitOrderState>] {
232        fn pending_workforce(&self) -> Option<Workforce> {
233          if let Self::Pending { workforce } = self { Some(*workforce) } else { None }
234        }
235
236        fn pending_workforce_mut(&mut self) -> Option<&mut Workforce> {
237          if let Self::Pending { workforce } = self { Some(workforce) } else { None }
238        }
239      }
240
241      impl [<$building RecruitOrderState>] {
242        fn new(workforce: Workforce) -> Self {
243          Self::Pending { workforce }
244        }
245      }
246
247      #[derive(Builder, Clone, Debug, Deserialize, Serialize)]
248      #[serde(rename_all = "camelCase")]
249      #[cfg_attr(feature = "typescript", derive(ts_rs::TS))]
250      pub struct [<$building RecruitOrderRequest>] {
251        #[builder(into)]
252        pub coord: Coord,
253        pub unit: [<$building UnitId>],
254        pub chunks: NonZeroU32,
255      }
256    }
257  };
258}