mindustry logic execution, map- and schematic- parsing and rendering
Diffstat (limited to 'src/item/storage.rs')
-rw-r--r--src/item/storage.rs425
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 {}