1use std::{
8 fmt::{Debug, Display},
9 marker::PhantomData,
10 ops::{Add, AddAssign},
11};
12
13use crate::Sealed;
14
15pub trait ResourceType: Sealed + Debug {
21 const NAME: &'static str;
23}
24
25#[macro_export]
35macro_rules! resource_type {
36
37 ($(#[$outer:meta])*
38 $name:ident) => {
39 $(#[$outer])*
40 #[derive(Debug)]
41 pub struct $name;
42 impl $crate::Sealed for $name {}
43 impl $crate::ResourceType for $name {
44 const NAME: &'static str = stringify!($name);
45 }
46 };
47}
48
49#[derive(Debug, Clone)]
51pub struct InsufficientResourceError<Resource: ResourceType> {
52 pub requested_amount: u32,
54 pub available_amount: u32,
56 phantom: PhantomData<Resource>,
57}
58
59impl<Resource: ResourceType> InsufficientResourceError<Resource> {
60 pub fn new(requested_amount: u32, available_amount: u32) -> Self {
62 Self {
63 requested_amount,
64 available_amount,
65 phantom: PhantomData,
66 }
67 }
68}
69
70impl<Resource: ResourceType> Display for InsufficientResourceError<Resource> {
71 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
72 write!(
73 f,
74 "Insufficient {:?}: requested {}, but only {} available",
75 Resource::NAME,
76 self.requested_amount,
77 self.available_amount
78 )
79 }
80}
81
82#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)]
85pub struct Resource<Content: ResourceType> {
86 amount: u32,
88 phantom: PhantomData<Content>,
89}
90
91pub fn resource<Content: ResourceType>(amount: u32) -> Resource<Content> {
94 Resource::new(amount)
95}
96
97impl<Content: ResourceType> Resource<Content> {
98 pub fn empty() -> Self {
100 Self {
101 amount: 0,
102 phantom: PhantomData,
103 }
104 }
105
106 pub(crate) fn new(amount: u32) -> Self {
107 Self {
108 amount,
109 phantom: PhantomData,
110 }
111 }
112
113 pub fn amount(&self) -> u32 {
115 self.amount
116 }
117
118 pub fn split(self, amount: u32) -> Result<(Self, Self), Self> {
121 if let Some(remaining) = self.amount.checked_sub(amount) {
122 Ok((Self::new(remaining), Self::new(amount)))
123 } else {
124 Err(self)
125 }
126 }
127
128 pub fn split_off(&mut self, amount: u32) -> Result<Self, InsufficientResourceError<Content>> {
131 if let Some(remaining) = self.amount.checked_sub(amount) {
132 self.amount = remaining;
133 Ok(Resource::new(amount))
134 } else {
135 Err(InsufficientResourceError::new(amount, self.amount))
136 }
137 }
138
139 pub fn add_bundle<const AMOUNT: u32>(&mut self, bundle: Bundle<Content, AMOUNT>) {
141 self.amount += bundle.amount();
142 }
143
144 pub fn bundle<const AMOUNT: u32>(&mut self) -> Result<Bundle<Content, AMOUNT>, InsufficientResourceError<Content>> {
146 if let Some(remaining) = self.amount.checked_sub(AMOUNT) {
147 self.amount = remaining;
148 Ok(Bundle::new())
149 } else {
150 Err(InsufficientResourceError::new(AMOUNT, self.amount))
151 }
152 }
153}
154
155impl<Content: ResourceType> Display for Resource<Content> {
156 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
157 write!(f, "{:?} x {}", Content::NAME, self.amount)
158 }
159}
160
161impl<Content: ResourceType> PartialOrd<u32> for Resource<Content> {
162 fn partial_cmp(&self, other: &u32) -> Option<std::cmp::Ordering> {
163 Some(self.amount.cmp(other))
164 }
165}
166
167impl<Content: ResourceType> PartialEq<u32> for Resource<Content> {
168 fn eq(&self, other: &u32) -> bool {
169 self.amount == *other
170 }
171}
172
173impl<Content: ResourceType> PartialOrd<Resource<Content>> for u32 {
174 fn partial_cmp(&self, other: &Resource<Content>) -> Option<std::cmp::Ordering> {
175 Some(self.cmp(&other.amount))
176 }
177}
178
179impl<Content: ResourceType> PartialEq<Resource<Content>> for u32 {
180 fn eq(&self, other: &Resource<Content>) -> bool {
181 *self == other.amount
182 }
183}
184
185impl<Content: ResourceType> AddAssign for Resource<Content> {
186 fn add_assign(&mut self, rhs: Self) {
187 self.amount += rhs.amount
188 }
189}
190
191impl<Content: ResourceType> Add for Resource<Content> {
192 type Output = Self;
193
194 fn add(mut self, rhs: Self) -> Self::Output {
195 self += rhs;
196 self
197 }
198}
199
200pub struct Bundle<Content: ResourceType, const AMOUNT: u32> {
203 dummy: PhantomData<Content>,
204}
205
206pub fn bundle<Content: ResourceType, const AMOUNT: u32>() -> Bundle<Content, AMOUNT> {
209 Bundle::new()
210}
211
212impl<Content: ResourceType, const AMOUNT: u32> Bundle<Content, AMOUNT> {
213 pub const AMOUNT: u32 = AMOUNT;
215
216 pub(crate) fn new() -> Self {
217 Self { dummy: PhantomData }
218 }
219
220 pub fn amount(&self) -> u32 {
222 AMOUNT
223 }
224
225 pub fn split<const AMOUNT1: u32, const AMOUNT2: u32>(self) -> (Bundle<Content, AMOUNT1>, Bundle<Content, AMOUNT2>)
228 where
229 [(); AMOUNT as usize - (AMOUNT1 as usize + AMOUNT2 as usize)]:,
231 [(); (AMOUNT1 as usize + AMOUNT2 as usize) - AMOUNT as usize]:,
232 {
233 (Bundle::new(), Bundle::new())
234 }
235
236 pub fn to_resource(self) -> Resource<Content> {
238 Resource::new(AMOUNT)
239 }
240}
241
242impl<Content: ResourceType, const AMOUNT: u32> AddAssign<Bundle<Content, AMOUNT>> for Resource<Content> {
243 fn add_assign(&mut self, bundle: Bundle<Content, AMOUNT>) {
244 let _ = bundle;
245 self.amount += AMOUNT;
246 }
247}
248
249impl<Content: ResourceType, const AMOUNT: u32> Add<Bundle<Content, AMOUNT>> for Resource<Content> {
250 type Output = Self;
251
252 fn add(mut self, rhs: Bundle<Content, AMOUNT>) -> Self::Output {
253 self += rhs;
254 self
255 }
256}
257
258impl<Content: ResourceType, const AMOUNT: u32> Add<Resource<Content>> for Bundle<Content, AMOUNT> {
259 type Output = Resource<Content>;
260
261 fn add(self, mut rhs: Resource<Content>) -> Self::Output {
262 rhs += self;
263 rhs
264 }
265}
266
267impl<Content: ResourceType, const AMOUNT_LHS: u32, const AMOUNT_RHS: u32> Add<Bundle<Content, AMOUNT_RHS>>
268 for Bundle<Content, AMOUNT_LHS>
269where
270 [(); { AMOUNT_LHS + AMOUNT_RHS } as usize]:,
271{
272 type Output = Bundle<Content, { AMOUNT_LHS + AMOUNT_RHS }>;
273
274 fn add(self, rhs: Bundle<Content, AMOUNT_RHS>) -> Self::Output {
275 let _ = rhs;
276 Bundle::new()
277 }
278}
279
280impl<Content: ResourceType, const AMOUNT: u32> From<Bundle<Content, AMOUNT>> for Resource<Content> {
281 fn from(bundle: Bundle<Content, AMOUNT>) -> Self {
282 let _ = bundle;
283 Resource::new(AMOUNT)
284 }
285}