mindustry logic execution, map- and schematic- parsing and rendering
Diffstat (limited to 'src/item/storage.rs')
| -rw-r--r-- | src/item/storage.rs | 425 |
1 files changed, 425 insertions, 0 deletions
diff --git a/src/item/storage.rs b/src/item/storage.rs new file mode 100644 index 0000000..ec7f219 --- /dev/null +++ b/src/item/storage.rs @@ -0,0 +1,425 @@ +use std::fmt; +use std::iter::{Enumerate, FusedIterator}; +use std::marker::PhantomData; +use std::slice; + +use crate::item; + +#[derive(Clone, Debug, Eq)] +/// stores data +pub struct Storage<T> { + base: Vec<u32>, + total: u64, + holds: PhantomData<T>, +} + +pub type ItemStorage = Storage<item::Type>; + +impl<T> Default for Storage<T> { + fn default() -> Self { + Self { + base: Vec::default(), + total: 0, + holds: PhantomData, + } + } +} + +impl<T> Storage<T> +where + u16: From<T>, +{ + #[must_use] + /// create a new storage + /// + /// ``` + /// # use mindus::item::storage::ItemStorage; + /// // ItemStorage is a alias to Storage<Item> + /// let s = ItemStorage::new(); + /// ``` + pub fn new() -> Self { + Self::default() + } + + #[must_use] + /// total items + pub const fn get_total(&self) -> u64 { + self.total + } + + #[must_use] + /// check if its empty + /// + /// ``` + /// # use mindus::item::storage::ItemStorage; + /// # use mindus::item; + /// + /// let mut s = ItemStorage::new(); + /// assert!(s.is_empty()); + /// s.set(item::Type::Copper, 500); + /// assert!(!s.is_empty()); + /// s.sub(item::Type::Copper, 500, 0); + /// assert!(s.is_empty()); + /// ``` + pub const fn is_empty(&self) -> bool { + self.total == 0 + } + + /// get item count of certain element + /// + /// ``` + /// # use mindus::item::storage::ItemStorage; + /// # use mindus::item; + /// + /// let mut s = ItemStorage::new(); + /// assert!(s.get(item::Type::Coal) == 0); + /// s.set(item::Type::Coal, 500); + /// assert!(s.get(item::Type::Titanium) == 0); + /// assert!(s.get(item::Type::Coal) == 500); + /// s.sub(item::Type::Coal, 500, 0); + /// assert!(s.get(item::Type::Coal) == 0); + /// ``` + #[must_use] + pub fn get(&self, ty: T) -> u32 { + self.base.get(u16::from(ty) as usize).copied().unwrap_or(0) + } + /// set item count of certain element + /// + /// ``` + /// # use mindus::item::storage::ItemStorage; + /// # use mindus::item; + /// + /// let mut s = ItemStorage::new(); + /// s.set(item::Type::Coal, 500); + /// s.set(item::Type::Copper, 500); + /// assert!(s.get(item::Type::Copper) == 500); + /// ``` + pub fn set(&mut self, ty: T, count: u32) -> u32 { + let idx = u16::from(ty) as usize; + match self.base.get_mut(idx) { + None => { + self.base.resize(idx + 1, 0); + self.base[idx] = count; + self.total += u64::from(count); + 0 + } + Some(curr) => { + let prev = *curr; + self.total = self.total - u64::from(prev) + u64::from(count); + *curr = count; + prev + } + } + } + + /// add to a certain elements item count, capping. + /// + /// ``` + /// # use mindus::item::storage::ItemStorage; + /// # use mindus::item; + /// + /// let mut s = ItemStorage::new(); + /// s.add(item::Type::Coal, 500, 500); + /// assert!(s.get(item::Type::Coal) == 500); + /// s.add(item::Type::Coal, 500, 10000); + /// assert!(s.get(item::Type::Coal) == 1000); + /// s.add(item::Type::Coal, 500, 1250); + /// assert!(s.get(item::Type::Coal) == 1250); + /// ``` + pub fn add(&mut self, ty: T, add: u32, max: u32) -> (u32, u32) { + let idx = u16::from(ty) as usize; + match self.base.get_mut(idx) { + None => { + let actual = add.min(max); + self.base.resize(idx + 1, 0); + self.base[idx] = actual; + self.total += u64::from(add); + (actual, actual) + } + Some(curr) => { + if *curr < max { + let actual = add.min(max - *curr); + *curr += actual; + self.total += u64::from(actual); + (actual, *curr) + } else { + (0, *curr) + } + } + } + } + + /// like [`Storage::add`] but fails + pub fn try_add(&mut self, ty: T, add: u32, max: u32) -> Result<(u32, u32), TryAddError> { + let idx = u16::from(ty) as usize; + match self.base.get_mut(idx) { + None => { + if add <= max { + self.base.resize(idx + 1, 0); + self.base[idx] = add; + self.total += u64::from(add); + Ok((add, add)) + } else { + Err(TryAddError { have: 0, add, max }) + } + } + Some(curr) => { + if *curr <= max && max - *curr <= add { + *curr += add; + self.total += u64::from(add); + Ok((add, *curr)) + } else { + Err(TryAddError { + have: *curr, + add, + max, + }) + } + } + } + } + + pub fn sub(&mut self, ty: T, sub: u32, min: u32) -> (u32, u32) { + match self.base.get_mut(u16::from(ty) as usize) { + None => (0, 0), + Some(curr) => { + if *curr > min { + let actual = sub.min(*curr - min); + *curr -= actual; + self.total -= u64::from(actual); + (actual, *curr) + } else { + (0, *curr) + } + } + } + } + + pub fn try_sub(&mut self, ty: T, sub: u32, min: u32) -> Result<(u32, u32), TrySubError> { + let idx = u16::from(ty) as usize; + match self.base.get_mut(idx) { + None => Err(TrySubError { have: 0, sub, min }), + Some(curr) => { + if *curr >= min && *curr - min >= sub { + *curr -= sub; + self.total -= u64::from(sub); + Ok((sub, *curr)) + } else { + Err(TrySubError { + have: *curr, + sub, + min, + }) + } + } + } + } + + pub fn add_all(&mut self, other: &Storage<T>, max_each: u32) -> (u64, u64) { + let mut added = 0u64; + if max_each > 0 && other.total > 0 { + let mut iter = other.base.iter().enumerate(); + // resize our vector only once and if necessary + let (last, add_last) = iter.rfind(|(_, n)| **n != 0).unwrap(); + if self.base.len() <= last { + self.base.resize(last + 1, 0); + } + // process items by increasing ID + for (idx, add) in iter { + let curr = self.base[idx]; + if curr < max_each { + let actual = (*add).min(max_each - curr); + self.base[idx] += actual; + added += u64::from(actual); + } + } + // process the final element (which we've retrieved first) + let curr = self.base[last]; + if curr < max_each { + let actual = (*add_last).min(max_each - curr); + self.base[last] += actual; + added += u64::from(actual); + } + // update total + self.total += added; + } + (added, self.total) + } + + pub fn pull_all(&mut self, other: &mut Storage<T>, max_each: u32) -> (u64, u64, u64) { + let mut added = 0u64; + if max_each > 0 && other.total > 0 { + let mut iter = other.base.iter_mut().enumerate(); + // resize our vector only once and if necessary + let (last, add_last) = iter.rfind(|(_, n)| **n != 0).unwrap(); + if self.base.len() <= last { + self.base.resize(last + 1, 0); + } + // process items by increasing ID + for (idx, add) in iter { + let curr = self.base[idx]; + if curr < max_each { + let actual = (*add).min(max_each - curr); + self.base[idx] += actual; + *add -= actual; + added += u64::from(actual); + } + } + // process the final element (which we've retrieved first) + let curr = self.base[last]; + if curr < max_each { + let actual = (*add_last).min(max_each - curr); + self.base[last] += actual; + *add_last -= actual; + added += u64::from(actual); + } + // update totals + self.total += added; + other.total -= added; + } + (added, self.total, other.total) + } + + pub fn sub_all(&mut self, other: &Storage<T>, min_each: u32) -> (u64, u64) { + let mut subbed = 0u64; + if self.total > 0 && other.total > 0 { + // no need for resizing, we only remove + // process items by increasing ID + for (idx, sub) in other.base.iter().enumerate() { + if let Some(curr) = self.base.get(idx) { + if *curr > min_each { + let actual = (*sub).min(*curr - min_each); + self.base[idx] -= actual; + subbed += u64::from(actual); + } + } else { + break; + } + } + // update total + self.total -= subbed; + } + (subbed, self.total) + } + + pub fn diff_all(&mut self, other: &mut Storage<T>, min_each: u32) -> (u64, u64, u64) { + let mut subbed = 0u64; + if self.total > 0 && other.total > 0 { + // no need for resizing, we only remove + // consider only indexes present in both + let end = self.base.len().min(other.base.len()); + let lhs = &mut self.base[..end]; + let rhs = &mut other.base[..end]; + // process items by increasing ID + for (l, r) in lhs.iter_mut().zip(rhs) { + if *l > min_each && *r > min_each { + let actual = (*l - min_each).min(*r - min_each); + *l -= actual; + *r -= actual; + subbed -= u64::from(actual); + } + } + // update totals + self.total -= subbed; + other.total -= subbed; + } + (subbed, self.total, other.total) + } + + #[must_use] + pub fn iter(&self) -> Iter<'_> { + Iter { + base: self.base.iter().enumerate(), + all: true, + } + } + + #[must_use] + pub fn iter_nonzero(&self) -> Iter<'_> { + Iter { + base: self.base.iter().enumerate(), + all: false, + } + } + + pub fn clear(&mut self) { + self.base.clear(); + } +} + +// manual because padding with zeros doesn't affect equality +impl<T> PartialEq for Storage<T> { + fn eq(&self, other: &Self) -> bool { + let mut li = self.base.iter().fuse(); + let mut ri = other.base.iter().fuse(); + loop { + match (li.next(), ri.next()) { + (None, None) => return true, + (l, r) => { + if l.unwrap_or(&0) != r.unwrap_or(&0) { + return false; + } + } + } + } + } +} + +impl<T> fmt::Display for Storage<T> +where + u16: From<T>, +{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let mut iter = self.iter_nonzero(); + if let Some((ty, cnt)) = iter.next() { + write!(f, "{cnt} {ty}")?; + for (ty, cnt) in iter { + write!(f, ", {cnt} {ty}")?; + } + } + Ok(()) + } +} + +#[derive(Clone, Copy, Debug, Eq, PartialEq, thiserror::Error)] +#[error("adding {add} to current {have} would exceed {max}")] +pub struct TryAddError { + pub have: u32, + pub add: u32, + pub max: u32, +} + +#[derive(Clone, Copy, Debug, Eq, PartialEq, thiserror::Error)] +#[error("removing {sub} from current {have} would drop below {min}")] +pub struct TrySubError { + pub have: u32, + pub sub: u32, + pub min: u32, +} + +#[derive(Clone, Debug)] +pub struct Iter<'l> { + base: Enumerate<slice::Iter<'l, u32>>, + all: bool, +} + +impl<'l> Iterator for Iter<'l> { + type Item = (item::Type, u32); + + fn next(&mut self) -> Option<Self::Item> { + for (idx, cnt) in self.base.by_ref() { + if *cnt > 0 || self.all { + if let Ok(ty) = item::Type::try_from(idx as u16) { + return Some((ty, *cnt)); + } + } + } + None + } + + fn size_hint(&self) -> (usize, Option<usize>) { + (0, self.base.size_hint().1) + } +} + +impl<'l> FusedIterator for Iter<'l> where Enumerate<slice::Iter<'l, u32>>: FusedIterator {} |