Skip to main content

spru_util/counter/
mod.rs

1mod range_type;
2pub use range_type::RangeType;
3use spru::common::error::AnyResult;
4
5use std::{
6    cmp,
7    ops::{self, RangeBounds},
8};
9
10use derive_where::derive_where;
11use num_traits::Signed;
12use tagset::tagset;
13use telety::telety;
14
15use crate::{Strictness, bounds, cloned};
16
17/// Types able to be used as a counter value
18pub trait CounterType: cmp::PartialOrd + ops::Sub<Output = Self> + num_traits::One + Clone {}
19
20impl<T> CounterType for T where
21    T: cmp::PartialOrd + ops::Sub<Output = Self> + num_traits::One + Clone
22{
23}
24
25/// A numerical counter, for points, life totals, round numbers, etc.
26/// You can also apply minimum and/or maximum bounds.
27#[derive(Debug, Default, Clone, serde::Serialize, serde::Deserialize)]
28pub struct Counter<T> {
29    value: T,
30    bounds: RangeType<T>,
31}
32
33impl<T: bounds::AddSigned> Counter<T> {
34    pub fn value(&self) -> &T {
35        &self.value
36    }
37
38    pub fn bounds(&self) -> &RangeType<T> {
39        &self.bounds
40    }
41}
42
43pub fn create<T>(value: T) -> Create<T> {
44    create_bounded(value, RangeType::default())
45}
46
47pub fn create_bounded<T>(value: T, bounds: impl Into<RangeType<T>>) -> Create<T> {
48    let bounds = bounds.into();
49
50    cloned::create(Counter { value, bounds })
51}
52
53pub fn default<T: Default>() -> Create<T> {
54    create(T::default())
55}
56
57pub fn add<T: bounds::AddSigned>(value: T::Signed, strictness: Strictness) -> Add<T> {
58    Add { value, strictness }
59}
60
61pub fn add_checked<T: bounds::AddSigned>(value: T::Signed) -> Add<T> {
62    add(value, Strictness::AllOrError)
63}
64
65pub fn add_saturating<T: bounds::AddSigned>(value: T::Signed) -> Add<T> {
66    add(value, Strictness::BestEffort)
67}
68
69pub fn destroy<T>() -> Destroy<T> {
70    cloned::destroy()
71}
72
73#[telety(crate::counter)]
74#[tagset(cloned::Create<Counter<T>>)]
75#[tagset(Add<T>)]
76#[tagset(cloned::Destroy<Counter<T>>)]
77#[tagset(reserved(..8))]
78pub struct Actions<T: bounds::AddSigned>;
79
80pub type Create<T> = cloned::Create<Counter<T>>;
81
82#[derive_where(Debug, Clone; T::Signed)]
83#[derive(serde::Serialize, serde::Deserialize, spru::action::Update)]
84#[must_use]
85pub struct Add<T: bounds::AddSigned> {
86    #[serde(bound(
87        serialize = "T::Signed: serde::Serialize",
88        deserialize = "T::Signed: serde::Deserialize<'de>"
89    ))]
90    value: T::Signed,
91    strictness: Strictness,
92}
93
94impl<T> Add<T>
95where
96    T: CounterType + Ord + bounds::AddSigned,
97{
98    fn sum(&self, value: &Counter<T>) -> Result<T, self::Error<T>> {
99        match self.strictness {
100            Strictness::BestEffort => Ok(value
101                .bounds
102                .constrain(value.value.saturating_add(self.value))),
103            Strictness::AllOrError => value
104                .value
105                .checked_add(self.value)
106                .filter(|v| value.bounds.contains(v))
107                .ok_or(Error::InvalidModifier {
108                    value: value.value,
109                    modifier: self.value,
110                }),
111        }
112    }
113}
114
115impl<T> spru::action::Update for Add<T>
116where
117    T: CounterType + Ord + bounds::AddSigned + 'static,
118{
119    type T = Counter<T>;
120    type Undo = Self;
121
122    fn update(&self, value: &mut Self::T) -> AnyResult<impl Into<Option<Self::Undo>>> {
123        let sum = self.sum(value)?;
124
125        // Absolute value of diff (T)
126        let diff = sum.max(value.value) - sum.min(value.value);
127
128        // Convert to U and correct sign
129        let diff = diff.into_signed() * -self.value.signum();
130
131        value.value = sum;
132
133        Ok(Self {
134            value: diff,
135            strictness: Strictness::AllOrError,
136        })
137    }
138}
139
140pub type Destroy<T> = cloned::Destroy<Counter<T>>;
141
142#[derive(Debug, Clone, crate::FromInfallible, thiserror::Error)]
143pub enum Error<T: bounds::AddSigned> {
144    #[error("Could not modify value of {value} by {modifier}")]
145    InvalidModifier { value: T, modifier: T::Signed },
146}
147
148#[cfg(test)]
149mod test {
150    use spru::action::{Create, Update as _};
151
152    use super::*;
153
154    #[test]
155    fn update_unbounded() {
156        let (mut counter, _) = create(3u32).create().unwrap();
157
158        add_checked(-5i32)
159            .update(&mut counter)
160            .map(Into::into)
161            .unwrap_err();
162
163        let undo = add_checked(-2)
164            .update(&mut counter)
165            .map(Into::into)
166            .unwrap();
167
168        assert_eq!(counter.value(), &1);
169
170        undo.unwrap().update(&mut counter).map(Into::into).unwrap();
171
172        assert_eq!(counter.value(), &3);
173
174        let undo = add_saturating(-5)
175            .update(&mut counter)
176            .map(Into::into)
177            .unwrap();
178
179        assert_eq!(counter.value(), &0);
180
181        undo.unwrap().update(&mut counter).map(Into::into).unwrap();
182
183        assert_eq!(counter.value(), &3);
184    }
185
186    #[test]
187    fn update_bounded() {
188        let (mut counter, _) = create_bounded(3u32, 2..).create().unwrap();
189
190        let undo = add_saturating(-5)
191            .update(&mut counter)
192            .map(Into::into)
193            .unwrap();
194
195        assert_eq!(counter.value(), &2);
196
197        undo.unwrap().update(&mut counter).map(Into::into).unwrap();
198
199        assert_eq!(counter.value(), &3);
200    }
201}