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
17pub 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#[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 let diff = sum.max(value.value) - sum.min(value.value);
127
128 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}