1use std::{
8 fmt::{Debug, Display},
9 iter::Sum,
10 marker::PhantomData,
11 ops::{Add, AddAssign},
12};
13
14use crate::Sealed;
15
16pub trait ResourceType: Sealed + Debug {
24 const NAME: &'static str;
26}
27
28#[macro_export]
38macro_rules! resource_type {
39
40 ($(#[$outer:meta])*
41 $name:ident) => {
42 $(#[$outer])*
43 #[derive(Debug)]
44 pub struct $name;
45 impl $crate::Sealed for $name {}
46 impl $crate::ResourceType for $name {
47 const NAME: &'static str = stringify!($name);
48 }
49 };
50}
51
52#[derive(Debug, Clone)]
54pub struct InsufficientResourceError<Resource: ResourceType> {
55 pub requested_amount: u32,
57 pub available_amount: u32,
59 phantom: PhantomData<Resource>,
60}
61
62impl<Resource: ResourceType> InsufficientResourceError<Resource> {
63 pub const fn new(requested_amount: u32, available_amount: u32) -> Self {
65 Self {
66 requested_amount,
67 available_amount,
68 phantom: PhantomData,
69 }
70 }
71}
72
73impl<Resource: ResourceType> Display for InsufficientResourceError<Resource> {
74 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
75 write!(
76 f,
77 "Insufficient {:?}: requested {}, but only {} available",
78 Resource::NAME,
79 self.requested_amount,
80 self.available_amount
81 )
82 }
83}
84
85#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)]
88#[must_use = "This resource is being dropped without being used. If this is intentional, use the `let _ = resource;` pattern to silence this warning."]
89pub struct Resource<Content: ResourceType> {
90 pub(crate) amount: u32,
92 phantom: PhantomData<Content>,
93}
94
95pub const fn resource<Content: ResourceType>(amount: u32) -> Resource<Content> {
98 Resource::new(amount)
99}
100
101pub const fn resource_amount_mut<Content: ResourceType>(
104 resource: &mut Resource<Content>,
105) -> &mut u32 {
106 resource.amount_mut()
107}
108
109impl<Content: ResourceType> Resource<Content> {
110 pub const fn new_empty() -> Self {
112 Self {
113 amount: 0,
114 phantom: PhantomData,
115 }
116 }
117
118 pub(crate) const fn new(amount: u32) -> Self {
119 Self {
120 amount,
121 phantom: PhantomData,
122 }
123 }
124
125 pub const fn amount(&self) -> u32 {
127 self.amount
128 }
129
130 const fn amount_mut(&mut self) -> &mut u32 {
131 &mut self.amount
132 }
133
134 pub const fn split(self, amount: u32) -> Result<(Self, Self), Self> {
137 if let Some(remaining) = self.amount.checked_sub(amount) {
138 Ok((Self::new(remaining), Self::new(amount)))
139 } else {
140 Err(self)
141 }
142 }
143
144 pub const fn split_off(
147 &mut self,
148 amount: u32,
149 ) -> Result<Self, InsufficientResourceError<Content>> {
150 if let Some(remaining) = self.amount.checked_sub(amount) {
151 self.amount = remaining;
152 Ok(Resource::new(amount))
153 } else {
154 Err(InsufficientResourceError::new(amount, self.amount))
155 }
156 }
157
158 pub const fn split_off_max(&mut self, amount: u32) -> Self {
161 if let Some(remaining) = self.amount.checked_sub(amount) {
162 self.amount = remaining;
163 Resource::new(amount)
164 } else {
165 let all = self.amount;
166 self.amount = 0;
167 Resource::new(all)
168 }
169 }
170
171 pub const fn empty(&mut self) -> Self {
173 let amount = self.amount;
174 self.amount = 0;
175 Resource::new(amount)
176 }
177
178 pub const fn empty_except(&mut self, amount: u32) -> Self {
180 let to_empty = self.amount.saturating_sub(amount);
181 self.amount -= to_empty;
182 Resource::new(to_empty)
183 }
184
185 pub const fn empty_into(&mut self, other: &mut Self) {
187 other.amount += self.amount;
188 self.amount = 0;
189 }
190
191 pub fn add(&mut self, other: impl Into<Self>) {
193 self.amount += other.into().amount();
194 }
195
196 pub const fn add_bundle<const AMOUNT: u32>(&mut self, bundle: Bundle<Content, AMOUNT>) {
198 self.amount += bundle.amount();
199 }
200
201 pub const fn bundle<const AMOUNT: u32>(
203 &mut self,
204 ) -> Result<Bundle<Content, AMOUNT>, InsufficientResourceError<Content>> {
205 if let Some(remaining) = self.amount.checked_sub(AMOUNT) {
206 self.amount = remaining;
207 Ok(Bundle::new())
208 } else {
209 Err(InsufficientResourceError::new(AMOUNT, self.amount))
210 }
211 }
212}
213
214impl<Content: ResourceType> Display for Resource<Content> {
215 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
216 write!(
217 f,
218 "{amount} {content}",
219 amount = self.amount,
220 content = Content::NAME
221 )
222 }
223}
224
225impl<Content: ResourceType> PartialOrd<u32> for Resource<Content> {
226 fn partial_cmp(&self, other: &u32) -> Option<std::cmp::Ordering> {
227 Some(self.amount.cmp(other))
228 }
229}
230
231impl<Content: ResourceType> PartialEq<u32> for Resource<Content> {
232 fn eq(&self, other: &u32) -> bool {
233 self.amount == *other
234 }
235}
236
237impl<Content: ResourceType> PartialOrd<Resource<Content>> for u32 {
238 fn partial_cmp(&self, other: &Resource<Content>) -> Option<std::cmp::Ordering> {
239 Some(self.cmp(&other.amount))
240 }
241}
242
243impl<Content: ResourceType> PartialEq<Resource<Content>> for u32 {
244 fn eq(&self, other: &Resource<Content>) -> bool {
245 *self == other.amount
246 }
247}
248
249impl<Content: ResourceType> AddAssign for Resource<Content> {
250 fn add_assign(&mut self, rhs: Self) {
251 self.amount += rhs.amount
252 }
253}
254
255impl<Content: ResourceType> Add for Resource<Content> {
256 type Output = Self;
257
258 fn add(mut self, rhs: Self) -> Self::Output {
259 self += rhs;
260 self
261 }
262}
263
264impl<Content: ResourceType> Sum for Resource<Content> {
265 fn sum<I: Iterator<Item = Self>>(iter: I) -> Self {
266 iter.fold(Resource::new_empty(), |cur, next| cur + next)
267 }
268}
269
270#[derive(Debug)]
273#[must_use = "This bundle is being dropped without being used. If this is intentional, use the `let _ = bundle;` pattern to silence this warning."]
274pub struct Bundle<Content: ResourceType, const AMOUNT: u32> {
275 dummy: PhantomData<Content>,
276}
277
278pub fn bundle<Content: ResourceType, const AMOUNT: u32>() -> Bundle<Content, AMOUNT> {
281 Bundle::new()
282}
283
284pub struct Assert<const OK: bool>;
286pub trait IsTrue {}
288impl IsTrue for Assert<true> {}
289
290impl<Content: ResourceType, const AMOUNT: u32> Bundle<Content, AMOUNT> {
291 pub const AMOUNT: u32 = AMOUNT;
293
294 pub(crate) const fn new() -> Self {
295 Self { dummy: PhantomData }
296 }
297
298 pub const fn amount(&self) -> u32 {
300 AMOUNT
301 }
302
303 pub const fn split<const AMOUNT1: u32, const AMOUNT2: u32>(
306 self,
307 ) -> (Bundle<Content, AMOUNT1>, Bundle<Content, AMOUNT2>)
308 where
309 Assert<{ AMOUNT1 + AMOUNT2 == AMOUNT }>: IsTrue,
310 {
311 (Bundle::new(), Bundle::new())
312 }
313
314 pub const fn to_resource(self) -> Resource<Content> {
316 Resource::new(AMOUNT)
317 }
318}
319
320impl<Content: ResourceType, const AMOUNT: u32> AddAssign<Bundle<Content, AMOUNT>>
321 for Resource<Content>
322{
323 fn add_assign(&mut self, bundle: Bundle<Content, AMOUNT>) {
324 let _ = bundle;
325 self.amount += AMOUNT;
326 }
327}
328
329impl<Content: ResourceType, const AMOUNT: u32> Add<Bundle<Content, AMOUNT>> for Resource<Content> {
330 type Output = Self;
331
332 fn add(mut self, rhs: Bundle<Content, AMOUNT>) -> Self::Output {
333 self += rhs;
334 self
335 }
336}
337
338impl<Content: ResourceType, const AMOUNT: u32> Add<Resource<Content>> for Bundle<Content, AMOUNT> {
339 type Output = Resource<Content>;
340
341 fn add(self, mut rhs: Resource<Content>) -> Self::Output {
342 rhs += self;
343 rhs
344 }
345}
346
347impl<Content: ResourceType, const AMOUNT_LHS: u32, const AMOUNT_RHS: u32>
348 Add<Bundle<Content, AMOUNT_RHS>> for Bundle<Content, AMOUNT_LHS>
349where
350 [(); { AMOUNT_LHS + AMOUNT_RHS } as usize]:,
351{
352 type Output = Bundle<Content, { AMOUNT_LHS + AMOUNT_RHS }>;
353
354 fn add(self, rhs: Bundle<Content, AMOUNT_RHS>) -> Self::Output {
355 let _ = rhs;
356 Bundle::new()
357 }
358}
359
360impl<Content: ResourceType, const AMOUNT: u32> From<Bundle<Content, AMOUNT>> for Resource<Content> {
361 fn from(bundle: Bundle<Content, AMOUNT>) -> Self {
362 let _ = bundle;
363 Resource::new(AMOUNT)
364 }
365}