Skip to content

Commit b27c8ab

Browse files
Modernize Cursor API (#596)
* Remove default Cursor::get_foo implementations * Encourage Cursor::{get_key, get_val}; Update CursorList * Respond to comments
1 parent 5c020b8 commit b27c8ab

File tree

13 files changed

+122
-57
lines changed

13 files changed

+122
-57
lines changed

differential-dataflow/src/operators/join.rs

+5-6
Original file line numberDiff line numberDiff line change
@@ -661,11 +661,11 @@ where
661661

662662
let mut thinker = JoinThinker::new();
663663

664-
while batch.key_valid(batch_storage) && trace.key_valid(trace_storage) && effort < *fuel {
664+
while let (Some(batch_key), Some(trace_key), true) = (batch.get_key(batch_storage), trace.get_key(trace_storage), effort < *fuel) {
665665

666-
match trace.key(trace_storage).cmp(&batch.key(batch_storage)) {
667-
Ordering::Less => trace.seek_key(trace_storage, batch.key(batch_storage)),
668-
Ordering::Greater => batch.seek_key(batch_storage, trace.key(trace_storage)),
666+
match trace_key.cmp(&batch_key) {
667+
Ordering::Less => trace.seek_key(trace_storage, batch_key),
668+
Ordering::Greater => batch.seek_key(batch_storage, trace_key),
669669
Ordering::Equal => {
670670

671671
use crate::IntoOwned;
@@ -679,8 +679,7 @@ where
679679

680680
// populate `temp` with the results in the best way we know how.
681681
thinker.think(|v1,v2,t,r1,r2| {
682-
let key = batch.key(batch_storage);
683-
logic(key, v1, v2, &t, r1, r2, &mut session);
682+
logic(batch_key, v1, v2, &t, r1, r2, &mut session);
684683
});
685684

686685
// TODO: Effort isn't perfectly tracked as we might still have some data in the

differential-dataflow/src/operators/mod.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -45,9 +45,9 @@ impl<'a, C: Cursor> EditList<'a, C> {
4545
L: Fn(C::TimeGat<'_>)->C::Time,
4646
{
4747
self.clear();
48-
while cursor.val_valid(storage) {
48+
while let Some(val) = cursor.get_val(storage) {
4949
cursor.map_times(storage, |time1, diff1| self.push(logic(time1), diff1.into_owned()));
50-
self.seal(cursor.val(storage));
50+
self.seal(val);
5151
cursor.step_val(storage);
5252
}
5353
}

differential-dataflow/src/trace/cursor/cursor_list.rs

+56-37
Original file line numberDiff line numberDiff line change
@@ -26,58 +26,68 @@ impl<C: Cursor> CursorList<C> {
2626
result
2727
}
2828

29-
// Initialize min_key with the indices of cursors with the minimum key.
30-
//
31-
// This method scans the current keys of each cursor, and tracks the indices
32-
// of cursors whose key equals the minimum valid key seen so far. As it goes,
33-
// if it observes an improved key it clears the current list, updates the
34-
// minimum key, and continues.
35-
//
36-
// Once finished, it invokes `minimize_vals()` to ensure the value cursor is
37-
// in a consistent state as well.
29+
/// Initialize min_key with the indices of cursors with the minimum key.
30+
///
31+
/// This method scans the current keys of each cursor, and tracks the indices
32+
/// of cursors whose key equals the minimum valid key seen so far. As it goes,
33+
/// if it observes an improved key it clears the current list, updates the
34+
/// minimum key, and continues.
35+
///
36+
/// Once finished, it invokes `minimize_vals()` to ensure the value cursor is
37+
/// in a consistent state as well.
3838
fn minimize_keys(&mut self, storage: &[C::Storage]) {
3939

4040
self.min_key.clear();
4141

42-
// Determine the index of the cursor with minimum key.
43-
let mut min_key_opt = None;
44-
for (index, cursor) in self.cursors.iter().enumerate() {
45-
let key = cursor.get_key(&storage[index]);
46-
if key.is_some() {
47-
if min_key_opt.is_none() || key.lt(&min_key_opt) {
48-
min_key_opt = key;
49-
self.min_key.clear();
50-
}
51-
if key.eq(&min_key_opt) {
52-
self.min_key.push(index);
42+
// We'll visit each non-`None` key, maintaining the indexes of the least keys in `self.min_key`.
43+
let mut iter = self.cursors.iter().enumerate().flat_map(|(idx, cur)| cur.get_key(&storage[idx]).map(|key| (idx, key)));
44+
if let Some((idx, key)) = iter.next() {
45+
let mut min_key = key;
46+
self.min_key.push(idx);
47+
for (idx, key) in iter {
48+
match key.cmp(&min_key) {
49+
std::cmp::Ordering::Less => {
50+
self.min_key.clear();
51+
self.min_key.push(idx);
52+
min_key = key;
53+
}
54+
std::cmp::Ordering::Equal => {
55+
self.min_key.push(idx);
56+
}
57+
std::cmp::Ordering::Greater => { }
5358
}
5459
}
5560
}
5661

5762
self.minimize_vals(storage);
5863
}
5964

60-
// Initialize min_val with the indices of minimum key cursors with the minimum value.
61-
//
62-
// This method scans the current values of cursor with minimum keys, and tracks the
63-
// indices of cursors whose value equals the minimum valid value seen so far. As it
64-
// goes, if it observes an improved value it clears the current list, updates the minimum
65-
// value, and continues.
65+
/// Initialize min_val with the indices of minimum key cursors with the minimum value.
66+
///
67+
/// This method scans the current values of cursor with minimum keys, and tracks the
68+
/// indices of cursors whose value equals the minimum valid value seen so far. As it
69+
/// goes, if it observes an improved value it clears the current list, updates the minimum
70+
/// value, and continues.
6671
fn minimize_vals(&mut self, storage: &[C::Storage]) {
6772

6873
self.min_val.clear();
6974

70-
// Determine the index of the cursor with minimum value.
71-
let mut min_val = None;
72-
for &index in self.min_key.iter() {
73-
let val = self.cursors[index].get_val(&storage[index]);
74-
if val.is_some() {
75-
if min_val.is_none() || val.lt(&min_val) {
76-
min_val = val;
77-
self.min_val.clear();
78-
}
79-
if val.eq(&min_val) {
80-
self.min_val.push(index);
75+
// We'll visit each non-`None` value, maintaining the indexes of the least values in `self.min_val`.
76+
let mut iter = self.min_key.iter().cloned().flat_map(|idx| self.cursors[idx].get_val(&storage[idx]).map(|val| (idx, val)));
77+
if let Some((idx, val)) = iter.next() {
78+
let mut min_val = val;
79+
self.min_val.push(idx);
80+
for (idx, val) in iter {
81+
match val.cmp(&min_val) {
82+
std::cmp::Ordering::Less => {
83+
self.min_val.clear();
84+
self.min_val.push(idx);
85+
min_val = val;
86+
}
87+
std::cmp::Ordering::Equal => {
88+
self.min_val.push(idx);
89+
}
90+
std::cmp::Ordering::Greater => { }
8191
}
8292
}
8393
}
@@ -114,6 +124,15 @@ impl<C: Cursor> Cursor for CursorList<C> {
114124
debug_assert!(self.cursors[self.min_val[0]].val_valid(&storage[self.min_val[0]]));
115125
self.cursors[self.min_val[0]].val(&storage[self.min_val[0]])
116126
}
127+
#[inline]
128+
fn get_key<'a>(&self, storage: &'a Vec<C::Storage>) -> Option<Self::Key<'a>> {
129+
self.min_key.get(0).map(|idx| self.cursors[*idx].key(&storage[*idx]))
130+
}
131+
#[inline]
132+
fn get_val<'a>(&self, storage: &'a Vec<C::Storage>) -> Option<Self::Val<'a>> {
133+
self.min_val.get(0).map(|idx| self.cursors[*idx].val(&storage[*idx]))
134+
}
135+
117136
#[inline]
118137
fn map_times<L: FnMut(Self::TimeGat<'_>, Self::DiffGat<'_>)>(&mut self, storage: &Vec<C::Storage>, mut logic: L) {
119138
for &index in self.min_val.iter() {

differential-dataflow/src/trace/cursor/mod.rs

+5-9
Original file line numberDiff line numberDiff line change
@@ -50,13 +50,9 @@ pub trait Cursor {
5050
fn val<'a>(&self, storage: &'a Self::Storage) -> Self::Val<'a>;
5151

5252
/// Returns a reference to the current key, if valid.
53-
fn get_key<'a>(&self, storage: &'a Self::Storage) -> Option<Self::Key<'a>> {
54-
if self.key_valid(storage) { Some(self.key(storage)) } else { None }
55-
}
53+
fn get_key<'a>(&self, storage: &'a Self::Storage) -> Option<Self::Key<'a>>;
5654
/// Returns a reference to the current value, if valid.
57-
fn get_val<'a>(&self, storage: &'a Self::Storage) -> Option<Self::Val<'a>> {
58-
if self.val_valid(storage) { Some(self.val(storage)) } else { None }
59-
}
55+
fn get_val<'a>(&self, storage: &'a Self::Storage) -> Option<Self::Val<'a>>;
6056

6157
/// Applies `logic` to each pair of time and difference. Intended for mutation of the
6258
/// closure's scope.
@@ -85,14 +81,14 @@ pub trait Cursor {
8581
{
8682
let mut out = Vec::new();
8783
self.rewind_keys(storage);
88-
while self.key_valid(storage) {
84+
while let Some(key) = self.get_key(storage) {
8985
self.rewind_vals(storage);
90-
while self.val_valid(storage) {
86+
while let Some(val) = self.get_val(storage) {
9187
let mut kv_out = Vec::new();
9288
self.map_times(storage, |ts, r| {
9389
kv_out.push((ts.into_owned(), r.into_owned()));
9490
});
95-
out.push(((self.key(storage).into_owned(), self.val(storage).into_owned()), kv_out));
91+
out.push(((key.into_owned(), val.into_owned()), kv_out));
9692
self.step_val(storage);
9793
}
9894
self.step_key(storage);

differential-dataflow/src/trace/implementations/mod.rs

+12
Original file line numberDiff line numberDiff line change
@@ -483,6 +483,15 @@ pub mod containers {
483483

484484
/// Reference to the element at this position.
485485
fn index(&self, index: usize) -> Self::ReadItem<'_>;
486+
487+
/// Reference to the element at this position, if it exists.
488+
fn get(&self, index: usize) -> Option<Self::ReadItem<'_>> {
489+
if index < self.len() {
490+
Some(self.index(index))
491+
}
492+
else { None }
493+
}
494+
486495
/// Number of contained elements
487496
fn len(&self) -> usize;
488497
/// Returns the last item if the container is non-empty.
@@ -559,6 +568,9 @@ pub mod containers {
559568
fn index(&self, index: usize) -> Self::ReadItem<'_> {
560569
&self[index]
561570
}
571+
fn get(&self, index: usize) -> Option<Self::ReadItem<'_>> {
572+
<[T]>::get(&self, index)
573+
}
562574
fn len(&self) -> usize {
563575
self[..].len()
564576
}

differential-dataflow/src/trace/implementations/ord_neu.rs

+6
Original file line numberDiff line numberDiff line change
@@ -493,6 +493,9 @@ mod val_batch {
493493

494494
type Storage = OrdValBatch<L>;
495495

496+
fn get_key<'a>(&self, storage: &'a Self::Storage) -> Option<Self::Key<'a>> { storage.storage.keys.get(self.key_cursor) }
497+
fn get_val<'a>(&self, storage: &'a Self::Storage) -> Option<Self::Val<'a>> { if self.val_valid(storage) { Some(self.val(storage)) } else { None } }
498+
496499
fn key<'a>(&self, storage: &'a OrdValBatch<L>) -> Self::Key<'a> { storage.storage.keys.index(self.key_cursor) }
497500
fn val<'a>(&self, storage: &'a OrdValBatch<L>) -> Self::Val<'a> { storage.storage.vals.index(self.val_cursor) }
498501
fn map_times<L2: FnMut(Self::TimeGat<'_>, Self::DiffGat<'_>)>(&mut self, storage: &OrdValBatch<L>, mut logic: L2) {
@@ -997,6 +1000,9 @@ mod key_batch {
9971000

9981001
type Storage = OrdKeyBatch<L>;
9991002

1003+
fn get_key<'a>(&self, storage: &'a Self::Storage) -> Option<Self::Key<'a>> { storage.storage.keys.get(self.key_cursor) }
1004+
fn get_val<'a>(&self, storage: &'a Self::Storage) -> Option<&'a ()> { if self.val_valid(storage) { Some(&()) } else { None } }
1005+
10001006
fn key<'a>(&self, storage: &'a Self::Storage) -> Self::Key<'a> { storage.storage.keys.index(self.key_cursor) }
10011007
fn val<'a>(&self, _storage: &'a Self::Storage) -> &'a () { &() }
10021008
fn map_times<L2: FnMut(Self::TimeGat<'_>, Self::DiffGat<'_>)>(&mut self, storage: &Self::Storage, mut logic: L2) {

differential-dataflow/src/trace/implementations/rhh.rs

+3-3
Original file line numberDiff line numberDiff line change
@@ -666,9 +666,9 @@ mod val_batch {
666666

667667
type Storage = RhhValBatch<L>;
668668

669-
fn key<'a>(&self, storage: &'a RhhValBatch<L>) -> Self::Key<'a> {
670-
storage.storage.keys.index(self.key_cursor)
671-
}
669+
fn get_key<'a>(&self, storage: &'a RhhValBatch<L>) -> Option<Self::Key<'a>> { storage.storage.keys.get(self.key_cursor) }
670+
fn get_val<'a>(&self, storage: &'a RhhValBatch<L>) -> Option<Self::Val<'a>> { if self.val_valid(storage) { storage.storage.vals.get(self.val_cursor) } else { None } }
671+
fn key<'a>(&self, storage: &'a RhhValBatch<L>) -> Self::Key<'a> { storage.storage.keys.index(self.key_cursor) }
672672
fn val<'a>(&self, storage: &'a RhhValBatch<L>) -> Self::Val<'a> { storage.storage.vals.index(self.val_cursor) }
673673
fn map_times<L2: FnMut(Self::TimeGat<'_>, Self::DiffGat<'_>)>(&mut self, storage: &RhhValBatch<L>, mut logic: L2) {
674674
let (lower, upper) = storage.storage.updates_for_value(self.val_cursor);

differential-dataflow/src/trace/mod.rs

+3
Original file line numberDiff line numberDiff line change
@@ -403,6 +403,9 @@ pub mod rc_blanket_impls {
403403
#[inline] fn key<'a>(&self, storage: &'a Self::Storage) -> Self::Key<'a> { self.cursor.key(storage) }
404404
#[inline] fn val<'a>(&self, storage: &'a Self::Storage) -> Self::Val<'a> { self.cursor.val(storage) }
405405

406+
#[inline] fn get_key<'a>(&self, storage: &'a Self::Storage) -> Option<Self::Key<'a>> { self.cursor.get_key(storage) }
407+
#[inline] fn get_val<'a>(&self, storage: &'a Self::Storage) -> Option<Self::Val<'a>> { self.cursor.get_val(storage) }
408+
406409
#[inline]
407410
fn map_times<L: FnMut(Self::TimeGat<'_>, Self::DiffGat<'_>)>(&mut self, storage: &Self::Storage, logic: L) {
408411
self.cursor.map_times(storage, logic)

differential-dataflow/src/trace/wrappers/enter.rs

+6
Original file line numberDiff line numberDiff line change
@@ -184,6 +184,9 @@ where
184184
#[inline] fn key<'a>(&self, storage: &'a Self::Storage) -> Self::Key<'a> { self.cursor.key(storage) }
185185
#[inline] fn val<'a>(&self, storage: &'a Self::Storage) -> Self::Val<'a> { self.cursor.val(storage) }
186186

187+
#[inline] fn get_key<'a>(&self, storage: &'a Self::Storage) -> Option<Self::Key<'a>> { self.cursor.get_key(storage) }
188+
#[inline] fn get_val<'a>(&self, storage: &'a Self::Storage) -> Option<Self::Val<'a>> { self.cursor.get_val(storage) }
189+
187190
#[inline]
188191
fn map_times<L: FnMut(&TInner, Self::DiffGat<'_>)>(&mut self, storage: &Self::Storage, mut logic: L) {
189192
use crate::IntoOwned;
@@ -238,6 +241,9 @@ where
238241
#[inline] fn key<'a>(&self, storage: &'a Self::Storage) -> Self::Key<'a> { self.cursor.key(&storage.batch) }
239242
#[inline] fn val<'a>(&self, storage: &'a Self::Storage) -> Self::Val<'a> { self.cursor.val(&storage.batch) }
240243

244+
#[inline] fn get_key<'a>(&self, storage: &'a Self::Storage) -> Option<Self::Key<'a>> { self.cursor.get_key(&storage.batch) }
245+
#[inline] fn get_val<'a>(&self, storage: &'a Self::Storage) -> Option<Self::Val<'a>> { self.cursor.get_val(&storage.batch) }
246+
241247
#[inline]
242248
fn map_times<L: FnMut(&TInner, Self::DiffGat<'_>)>(&mut self, storage: &Self::Storage, mut logic: L) {
243249
use crate::IntoOwned;

differential-dataflow/src/trace/wrappers/enter_at.rs

+6
Original file line numberDiff line numberDiff line change
@@ -211,6 +211,9 @@ where
211211
#[inline] fn key<'a>(&self, storage: &'a Self::Storage) -> Self::Key<'a> { self.cursor.key(storage) }
212212
#[inline] fn val<'a>(&self, storage: &'a Self::Storage) -> Self::Val<'a> { self.cursor.val(storage) }
213213

214+
#[inline] fn get_key<'a>(&self, storage: &'a Self::Storage) -> Option<Self::Key<'a>> { self.cursor.get_key(storage) }
215+
#[inline] fn get_val<'a>(&self, storage: &'a Self::Storage) -> Option<Self::Val<'a>> { self.cursor.get_val(storage) }
216+
214217
#[inline]
215218
fn map_times<L: FnMut(&TInner, Self::DiffGat<'_>)>(&mut self, storage: &Self::Storage, mut logic: L) {
216219
let key = self.key(storage);
@@ -270,6 +273,9 @@ where
270273
#[inline] fn key<'a>(&self, storage: &'a Self::Storage) -> Self::Key<'a> { self.cursor.key(&storage.batch) }
271274
#[inline] fn val<'a>(&self, storage: &'a Self::Storage) -> Self::Val<'a> { self.cursor.val(&storage.batch) }
272275

276+
#[inline] fn get_key<'a>(&self, storage: &'a Self::Storage) -> Option<Self::Key<'a>> { self.cursor.get_key(&storage.batch) }
277+
#[inline] fn get_val<'a>(&self, storage: &'a Self::Storage) -> Option<Self::Val<'a>> { self.cursor.get_val(&storage.batch) }
278+
273279
#[inline]
274280
fn map_times<L: FnMut(&TInner, Self::DiffGat<'_>)>(&mut self, storage: &Self::Storage, mut logic: L) {
275281
let key = self.key(storage);

differential-dataflow/src/trace/wrappers/filter.rs

+6
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,9 @@ where
148148
#[inline] fn key<'a>(&self, storage: &'a Self::Storage) -> Self::Key<'a> { self.cursor.key(storage) }
149149
#[inline] fn val<'a>(&self, storage: &'a Self::Storage) -> Self::Val<'a> { self.cursor.val(storage) }
150150

151+
#[inline] fn get_key<'a>(&self, storage: &'a Self::Storage) -> Option<Self::Key<'a>> { self.cursor.get_key(storage) }
152+
#[inline] fn get_val<'a>(&self, storage: &'a Self::Storage) -> Option<Self::Val<'a>> { self.cursor.get_val(storage) }
153+
151154
#[inline]
152155
fn map_times<L: FnMut(Self::TimeGat<'_>, Self::DiffGat<'_>)>(&mut self, storage: &Self::Storage, logic: L) {
153156
let key = self.key(storage);
@@ -203,6 +206,9 @@ where
203206
#[inline] fn key<'a>(&self, storage: &'a Self::Storage) -> Self::Key<'a> { self.cursor.key(&storage.batch) }
204207
#[inline] fn val<'a>(&self, storage: &'a Self::Storage) -> Self::Val<'a> { self.cursor.val(&storage.batch) }
205208

209+
#[inline] fn get_val<'a>(&self, storage: &'a Self::Storage) -> Option<Self::Val<'a>> { self.cursor.get_val(&storage.batch) }
210+
#[inline] fn get_key<'a>(&self, storage: &'a Self::Storage) -> Option<Self::Key<'a>> { self.cursor.get_key(&storage.batch) }
211+
206212
#[inline]
207213
fn map_times<L: FnMut(Self::TimeGat<'_>, Self::DiffGat<'_>)>(&mut self, storage: &Self::Storage, logic: L) {
208214
let key = self.key(storage);

differential-dataflow/src/trace/wrappers/freeze.rs

+6
Original file line numberDiff line numberDiff line change
@@ -199,6 +199,9 @@ where
199199
#[inline] fn key<'a>(&self, storage: &'a Self::Storage) -> Self::Key<'a> { self.cursor.key(storage) }
200200
#[inline] fn val<'a>(&self, storage: &'a Self::Storage) -> Self::Val<'a> { self.cursor.val(storage) }
201201

202+
#[inline] fn get_key<'a>(&self, storage: &'a Self::Storage) -> Option<Self::Key<'a>> { self.cursor.get_key(storage) }
203+
#[inline] fn get_val<'a>(&self, storage: &'a Self::Storage) -> Option<Self::Val<'a>> { self.cursor.get_val(storage) }
204+
202205
#[inline] fn map_times<L: FnMut(Self::TimeGat<'_>, Self::DiffGat<'_>)>(&mut self, storage: &Self::Storage, mut logic: L) {
203206
let func = &self.func;
204207
self.cursor.map_times(storage, |time, diff| {
@@ -251,6 +254,9 @@ where
251254
#[inline] fn key<'a>(&self, storage: &'a Self::Storage) -> Self::Key<'a> { self.cursor.key(&storage.batch) }
252255
#[inline] fn val<'a>(&self, storage: &'a Self::Storage) -> Self::Val<'a> { self.cursor.val(&storage.batch) }
253256

257+
#[inline] fn get_key<'a>(&self, storage: &'a Self::Storage) -> Option<Self::Key<'a>> { self.cursor.get_key(&storage.batch) }
258+
#[inline] fn get_val<'a>(&self, storage: &'a Self::Storage) -> Option<Self::Val<'a>> { self.cursor.get_val(&storage.batch) }
259+
254260
#[inline] fn map_times<L: FnMut(Self::TimeGat<'_>, Self::DiffGat<'_>)>(&mut self, storage: &Self::Storage, mut logic: L) {
255261
let func = &self.func;
256262
self.cursor.map_times(&storage.batch, |time, diff| {

differential-dataflow/src/trace/wrappers/frontier.rs

+6
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,9 @@ impl<C: Cursor> Cursor for CursorFrontier<C, C::Time> {
143143
#[inline] fn key<'a>(&self, storage: &'a Self::Storage) -> Self::Key<'a> { self.cursor.key(storage) }
144144
#[inline] fn val<'a>(&self, storage: &'a Self::Storage) -> Self::Val<'a> { self.cursor.val(storage) }
145145

146+
#[inline] fn get_key<'a>(&self, storage: &'a Self::Storage) -> Option<Self::Key<'a>> { self.cursor.get_key(storage) }
147+
#[inline] fn get_val<'a>(&self, storage: &'a Self::Storage) -> Option<Self::Val<'a>> { self.cursor.get_val(storage) }
148+
146149
#[inline]
147150
fn map_times<L: FnMut(Self::TimeGat<'_>, Self::DiffGat<'_>)>(&mut self, storage: &Self::Storage, mut logic: L) {
148151
let since = self.since.borrow();
@@ -206,6 +209,9 @@ where
206209
#[inline] fn key<'a>(&self, storage: &'a Self::Storage) -> Self::Key<'a> { self.cursor.key(&storage.batch) }
207210
#[inline] fn val<'a>(&self, storage: &'a Self::Storage) -> Self::Val<'a> { self.cursor.val(&storage.batch) }
208211

212+
#[inline] fn get_key<'a>(&self, storage: &'a Self::Storage) -> Option<Self::Key<'a>> { self.cursor.get_key(&storage.batch) }
213+
#[inline] fn get_val<'a>(&self, storage: &'a Self::Storage) -> Option<Self::Val<'a>> { self.cursor.get_val(&storage.batch) }
214+
209215
#[inline]
210216
fn map_times<L: FnMut(Self::TimeGat<'_>, Self::DiffGat<'_>)>(&mut self, storage: &Self::Storage, mut logic: L) {
211217
let since = self.since.borrow();

0 commit comments

Comments
 (0)