1
//! Read-modify-write values for a contiguous range of keys, in a range map
2
//!
3
//! Separate module mostly so that the tests have somewhere convenient to live.
4
//!
5
//! Arguably this should be in another crate.
6
//! But it brings in rangemap as a dependency which seems undesirable for tor-basic-utils.
7
//! Right now it is here because this is where its (lowest in stack) call site will be.
8

            
9
use std::ops::{Bound, RangeInclusive};
10

            
11
use itertools::{Itertools, chain};
12
use rangemap::{RangeInclusiveMap, StepFns, StepLite};
13

            
14
use tor_basic_utils::rangebounds::RangeBoundsExt;
15

            
16
/// Read-modify-write values for a contiguous range of keys, in a range map
17
///
18
/// Since the map might contain different values for various parts of the specified range,
19
/// multiple possible old values might need to be handled.
20
/// `rangemap_mutate_range` is suitable if different old values can be handled independently,
21
/// or sequentially.
22
///
23
/// Calls `update` for every range currently in the map overlapping with `range0`,
24
/// and for every gap overlapping with `range`.
25
///
26
/// If the mutated value is equal (`PartialEq`), no actual update is made.
27
///
28
/// If `update` throws `Err`, any mutations to its first argument *will* be stored in the map,
29
/// but no further calls to `update` will be made (so only part of the range
30
/// might be updated).
31
///
32
/// `update` should probably not use the provided `&RangeInclusive` argument as an input
33
/// to calculating how to update the `&mut V`.  Doing so would make the results depend
34
/// on the details of range fragmentation in the rangemap,
35
/// which would be inconsistent with the usual use of a rangemap as an optimisation
36
/// of an abstract data structure which stores a separate value for each key.
37
///
38
/// Note that `update` doesn't get a mutable reference *into the map*.
39
/// so if it mutates its argument and then panics, the update might not be applied.
40
// RangeInclusiveMap doesn't provide any in-place update API, and an in-place update is also
41
// incompatible with passing `&mut Option<V>`.
42
//
43
// Other APIs that were considered, include:
44
//
45
//  * FnMut(Option<V>) -> Option<V>
46
//
47
//    This seems less idiomatic.
48
//
49
//  * Don't coalesce equal values, skipping a comparison.
50
//
51
//    `RangeInclusiveMap` already coalesces adjacent identical ranges.
52
//    Comparing the old and new value is O(sizeof(V)), whereas RangeInclusiveMap::insert
53
//    is at least O(log N) and will often involves that comparison against adjacent ranges.
54
//
55
//    In theory this optimisation might be done by `RangeInclusiveMap` already, but it's
56
//    not mentioned.  Probably, Eq is quite cheap.
57
16
pub fn rangemap_mutate_range<K, V, StepFnsT, E>(
58
16
    map: &mut RangeInclusiveMap<K, V, StepFnsT>,
59
16
    range0: &RangeInclusive<K>,
60
16
    mut update: impl FnMut(&mut Option<V>, &RangeInclusive<K>) -> Result<(), E>,
61
16
) -> Result<(), E>
62
16
where
63
16
    K: Ord + Clone + StepLite,
64
16
    V: PartialEq + Clone,
65
16
    StepFnsT: StepFns<K>,
66
{
67
16
    let relevants = chain!(
68
18
        map.overlapping(range0).map(|(k, _v)| k.clone()),
69
16
        map.gaps(range0),
70
    )
71
16
    .collect_vec();
72

            
73
24
    for relevant in relevants {
74
        // `relevant` is a range stored in the rangemap, or a gap, which overlaps with `range0`,
75
        // but which might be bigger or smaller (or both) than `range0`.
76
        //
77
        // Insofar as it's smaller, then if there are other ranges in the rangemap with overlap,
78
        // they'll be handled separately, in other loop iterations.
79
        //
80
        // But insofar as it's bigger, we need to trim it down, because we're only supposed
81
        // to modify `range0`.
82
        //
83
        // We distinguish `k`, the range we are updating in this iteration,
84
        // from `range0`, the overall range from our caller.
85
24
        let k = {
86
24
            let k = relevant
87
24
                .intersect(range0)
88
24
                .expect("intersection of overlapping ranges was empty");
89

            
90
            // RangeBoundsExt::intersect works at the level of the RangeBounds trait,
91
            // returning `Option<(Bound, Bound)>`,  This helper closure converts one of the
92
            // returned `Bound`s back to a plain value.  The returned intersection will
93
            // always be an inclusive range because the input ranges were inclusive
94
            // (ie, closed, in set theory terms) and intersection preserves set closedness.
95
48
            let fix = |b: Bound<&K>| match b {
96
48
                Bound::Included(y) => y.clone(),
97
                _other => unreachable!("intersection of closed ranges wasn't closed"),
98
48
            };
99

            
100
24
            fix(k.0)..=fix(k.1)
101
        };
102

            
103
24
        let v0 = map.get(k.start());
104
24
        let mut v = v0.cloned();
105
        // avoid losing an update if Err is returned, so park any `Err` in `r`
106
24
        let r = update(&mut v, &k);
107
24
        if v.as_ref() != v0 {
108
20
            if let Some(v) = v {
109
20
                map.insert(k, v);
110
20
            } else {
111
                map.remove(k);
112
            }
113
4
        }
114
24
        r?;
115
    }
116
14
    Ok(())
117
16
}
118

            
119
#[cfg(test)]
120
mod test {
121
    // @@ begin test lint list maintained by maint/add_warning @@
122
    #![allow(clippy::bool_assert_comparison)]
123
    #![allow(clippy::clone_on_copy)]
124
    #![allow(clippy::dbg_macro)]
125
    #![allow(clippy::mixed_attributes_style)]
126
    #![allow(clippy::print_stderr)]
127
    #![allow(clippy::print_stdout)]
128
    #![allow(clippy::single_char_pattern)]
129
    #![allow(clippy::unwrap_used)]
130
    #![allow(clippy::unchecked_time_subtraction)]
131
    #![allow(clippy::useless_vec)]
132
    #![allow(clippy::needless_pass_by_value)]
133
    #![allow(clippy::string_slice)] // See arti#2571
134
    //! <!-- @@ end test lint list maintained by maint/add_warning @@ -->
135
    use super::*;
136
    use educe::Educe;
137
    use std::fmt::Debug;
138
    use void::Void;
139

            
140
    type Range = RangeInclusive<u8>;
141
    const ALL_K: Range = 0..=255;
142
    type Id = u32;
143

            
144
    /// Magic value
145
    ///
146
    /// Comparisons use only the value in `v`, but we track its value identity,
147
    /// which lets us see (for example) whether an "equal" update did anything.
148
    #[derive(Debug, Clone, Educe)]
149
    #[educe(PartialEq)]
150
    struct Value {
151
        v: char,
152
        #[educe(PartialEq(ignore))]
153
        id: Id,
154
    }
155

            
156
    /// Test wrapper for `RangeInclusiveMap`
157
    ///
158
    /// Maintains a separate copy of the expected current V for each K, in `reference`.
159
    /// Cross-checks it.
160
    #[derive(Debug, Clone, Educe)]
161
    #[educe(Default, PartialEq)]
162
    struct TestState {
163
        map: RangeInclusiveMap<u8, Value>,
164
        #[educe(Default(expression = "[None; _]"))] // Only short arrays are Default :-/
165
        reference: [Option<char>; 256],
166
        #[educe(PartialEq(ignore))]
167
        ids: IdGenerator,
168
    }
169

            
170
    /// avoids constant repetition of `let ; += 1;` pattern
171
    #[derive(Debug, Clone, Default, PartialEq, Eq)]
172
    struct IdGenerator(Id);
173

            
174
    impl IdGenerator {
175
        fn next(&mut self) -> Id {
176
            let r = self.0;
177
            self.0 += 1;
178
            r
179
        }
180
    }
181

            
182
    impl TestState {
183
        fn from_iter(elems: impl IntoIterator<Item = (Range, char)>) -> Self {
184
            let mut self_ = TestState::default();
185
            let id = self_.ids.next();
186
            for (range, v) in elems {
187
                self_.map.insert(range.clone(), Value { v, id });
188
                for k in range.clone() {
189
                    self_.reference[k as usize] = Some(v);
190
                }
191
            }
192
            self_
193
        }
194

            
195
        /// Calls `rangemap_mutate_range`, but also updates `reference`, makes some checks, etc.
196
        fn mutate_range<E: Debug>(
197
            &mut self,
198
            range0: Range,
199
            mut real_update: impl FnMut(&Range, &mut Option<Value>) -> Result<(), E>,
200
        ) -> Result<(), E> {
201
            let mut updated = [false; 256];
202
            println!("updating range0={range0:?}");
203

            
204
            let r = rangemap_mutate_range(
205
                &mut self.map,
206
                &range0,
207
                |value: &mut Option<Value>, range| {
208
                    println!("updating range0={range:?} range={range:?}");
209
                    assert!(range0.contains(range.start()), "uncontained start");
210
                    assert!(range0.contains(range.end()), "uncontained end");
211
                    let r = real_update(range, value);
212
                    println!("updating range0={range0:?} range={range:?}, to {value:?}, r={r:?}");
213
                    for k in range.clone() {
214
                        updated[k as usize] = true;
215
                        self.reference[k as usize] = value.as_ref().map(|value| value.v);
216
                    }
217
                    r
218
                },
219
            );
220
            self.check();
221

            
222
            if r.is_ok() {
223
                for k in ALL_K {
224
                    assert_eq!(
225
                        updated[k as usize],
226
                        range0.contains(&k),
227
                        "updated inconsistency k={k:?}",
228
                    );
229
                }
230
            }
231

            
232
            r
233
        }
234

            
235
        fn set_range(&mut self, range0: Range, val: char, expected_old_values: &str) {
236
            let id = self.ids.next();
237
            self.mutate_range(range0.clone(), |k, vmut| {
238
                assert!(
239
                    expected_old_values.contains(vmut.as_ref().map(|v| v.v).unwrap_or('_')),
240
                    "{range0:?} {k:?} {val:?} {vmut:?} {expected_old_values:?}"
241
                );
242
                *vmut = Some(Value { v: val, id });
243
                Ok::<_, Void>(())
244
            });
245
        }
246

            
247
        fn check(&self) {
248
            for k in ALL_K {
249
                assert_eq!(
250
                    self.map.get(&k).map(|v| v.v),
251
                    self.reference[k as usize],
252
                    "map now implies wrong v at k={k:?}",
253
                );
254
            }
255
        }
256
    }
257

            
258
    #[test]
259
    fn mutations() {
260
        let s0 = TestState::from_iter([
261
            //
262
            (0..=9, 'a'),
263
            (20..=29, 'x'),
264
        ]);
265

            
266
        {
267
            // mutate precisely an existing range
268
            let mut s = s0.clone();
269
            s.set_range(0..=9, 'b', "a");
270
        }
271
        {
272
            // mutate precisely a gap
273
            let mut s = s0.clone();
274
            s.mutate_range(10..=19, |_k, v| {
275
                assert_eq!(*v, None);
276
                Ok::<_, Void>(())
277
            });
278
            assert_eq!(s, s0);
279
            s.set_range(10..=19, 'n', "_");
280
        }
281
        {
282
            // mutate strictly a subset of an existing range
283
            let mut s = s0.clone();
284
            s.set_range(1..=8, 'b', "a");
285
            // now mutate parts of several ranges, with no gap in between, including a singleton
286
            s.set_range(7..=9, 'c', "ab");
287
        }
288
        {
289
            // mutate parts of several ranges, with a gap in between
290
            let mut s = s0.clone();
291
            s.set_range(5..=25, 'm', "_ax");
292
        }
293
        {
294
            // mutate parts of several ranges, throwing and error halfway through
295
            let mut s = s0.clone();
296
            s.mutate_range(5..=25, |k, v| {
297
                *v = Some(Value { v: 'm', id: 1000 });
298
                (*k.start() < 15).then_some(()).ok_or(())
299
            })
300
            .expect_err("terminated early");
301
        }
302
        {
303
            let mut s = s0.clone();
304
            let range = 0..=9;
305
            // overwrite the whole range with the same value
306
            // if the underlying insert call were made,
307
            // the value in the range would be replaced
308
            s.set_range(range.clone(), 'a', "a");
309
            // check that the thing at 1, the start of the "mutated" range,
310
            // is in fact *not* the value we inserted but the existing one.
311
            // this checks that our own PartialEq skip is workign
312
            let ent = s.map.get(&1).unwrap();
313
            assert_eq!(ent.id, 0);
314
            // check that our assumption about rangemap is true
315
            let id = s.ids.next();
316
            s.map.insert(range.clone(), Value { v: 'a', id });
317
            let ent = s.map.get(&1).unwrap();
318
            assert_eq!(ent.id, id);
319
        }
320
    }
321
}