@@ -47,3 +47,343 @@ where
47
47
self . project ( ) . inner . poll_next ( cx)
48
48
}
49
49
}
50
+
51
+ pub mod skip {
52
+ use eyeball:: SharedObservable ;
53
+ use futures_core:: Stream ;
54
+
55
+ use super :: super :: controller:: TimelineFocusKind ;
56
+
57
+ const MAXIMUM_NUMBER_OF_INITIAL_ITEMS : usize = 20 ;
58
+
59
+ #[ derive( Clone ) ]
60
+ pub struct SkipCount {
61
+ count : SharedObservable < usize > ,
62
+ }
63
+
64
+ impl SkipCount {
65
+ pub fn new ( ) -> Self {
66
+ Self { count : SharedObservable :: new ( 0 ) }
67
+ }
68
+
69
+ /// Compute the `count` value for [the `Skip` higher-order
70
+ /// stream][`Skip`].
71
+ ///
72
+ /// This is useful when new items are inserted, removed and so on.
73
+ ///
74
+ /// [`Skip`]: eyeball_im_util::vector::Skip
75
+ pub fn compute_next (
76
+ & self ,
77
+ previous_number_of_items : usize ,
78
+ next_number_of_items : usize ,
79
+ ) -> usize {
80
+ let current_count = self . count . get ( ) ;
81
+
82
+ // Initial states: no items is present.
83
+ if previous_number_of_items == 0 {
84
+ // Adjust the count to provide a maximum number of initial items. We want to
85
+ // skip the first items until we get a certain number of items to display.
86
+ //
87
+ // | `next_number_of_items` | `MAX…` | output | will display |
88
+ // |------------------------|--------|--------|--------------|
89
+ // | 60 | 20 | 40 | 20 items |
90
+ // | 10 | 20 | 0 | 10 items |
91
+ // | 0 | 20 | 0 | 0 item |
92
+ //
93
+ next_number_of_items. saturating_sub ( MAXIMUM_NUMBER_OF_INITIAL_ITEMS )
94
+ }
95
+ // Not the initial state: there are items.
96
+ else {
97
+ // There is less items than before. Shift to the left `count` by the difference
98
+ // between `previous_number_of_items` and `next_number_of_items` to keep the
99
+ // same number of items in the stream as much as possible.
100
+ //
101
+ // This is not a backwards pagination, it cannot “go below 0”, however this is
102
+ // necessary to handle the case where the timeline is cleared and
103
+ // the number of items becomes 0 for example.
104
+ if next_number_of_items < previous_number_of_items {
105
+ current_count. saturating_sub ( previous_number_of_items - next_number_of_items)
106
+ }
107
+ // Return `current_count` with no modification, we don't want to adjust the
108
+ // count, we want to see all initial items and new items.
109
+ else {
110
+ current_count
111
+ }
112
+ }
113
+ }
114
+
115
+ /// Compute the `count` value for [the `Skip` higher-order
116
+ /// stream][`Skip`] when a backwards pagination is happening.
117
+ ///
118
+ /// It returns the new value for `count` in addition to
119
+ /// `Some(number_of_items)` to fulfill the page up to `page_size`,
120
+ /// `None` otherwise. For example, assuming a `page_size` of 15,
121
+ /// if the `count` moves from 10 to 0, then 10 new items will
122
+ /// appear in the stream, but 5 are missing because they aren't
123
+ /// present in the stream: the stream has reached its beginning:
124
+ /// `Some(5)` will be returned. This is useful
125
+ /// for the pagination mechanism to fill the timeline with more items,
126
+ /// either from a storage, or from the network.
127
+ ///
128
+ /// [`Skip`]: eyeball_im_util::vector::Skip
129
+ pub fn compute_next_when_paginating_backwards (
130
+ & self ,
131
+ page_size : usize ,
132
+ ) -> ( usize , Option < usize > ) {
133
+ let current_count = self . count . get ( ) ;
134
+
135
+ // We skip the values from the start of the timeline; paginating backwards means
136
+ // we have to reduce the count until reaching 0.
137
+ //
138
+ // | `current_count` | `page_size` | output |
139
+ // |-----------------|-------------|----------------|
140
+ // | 50 | 20 | (30, None) |
141
+ // | 30 | 20 | (10, None) |
142
+ // | 10 | 20 | (0, Some(10)) |
143
+ // | 0 | 20 | (0, Some(20)) |
144
+ // ^ ^^^^^^^^
145
+ // | |
146
+ // | it needs 20 items to fulfill the
147
+ // | page size
148
+ // count becomes 0
149
+ //
150
+ if current_count >= page_size {
151
+ ( current_count - page_size, None )
152
+ } else {
153
+ ( 0 , Some ( page_size - current_count) )
154
+ }
155
+ }
156
+
157
+ /// Compute the `count` value for [the `Skip` higher-order
158
+ /// stream][`Skip`] when a backwards pagination is happening.
159
+ ///
160
+ /// The `page_size` is present to mimic the
161
+ /// [`compute_count_when_paginating_backwards`] function but it is
162
+ /// actually useless for the current implementation.
163
+ ///
164
+ /// [`Skip`]: eyeball_im_util::vector::Skip
165
+ pub fn compute_next_when_paginating_forwards ( & self , _page_size : usize ) -> usize {
166
+ let current_count = self . count . get ( ) ;
167
+
168
+ // Nothing to do, the count remains unchanged as we skip the first values, not
169
+ // the last values; paginating forwards will add items at the end, not at the
170
+ // start of the timeline.
171
+ current_count
172
+ }
173
+
174
+ /// Subscribe to update of the count value.
175
+ pub fn subscribe ( & self ) -> impl Stream < Item = usize > {
176
+ self . count . subscribe ( )
177
+ }
178
+
179
+ /// Update the skip count if and only if the timeline has a live focus
180
+ /// ([`TimelineFocusKind::Live`]).
181
+ pub fn update ( & self , count : usize , focus_kind : & TimelineFocusKind ) {
182
+ if matches ! ( focus_kind, TimelineFocusKind :: Live ) {
183
+ self . count . set_if_not_eq ( count) ;
184
+ }
185
+ }
186
+ }
187
+
188
+ #[ cfg( test) ]
189
+ mod tests {
190
+ use super :: SkipCount ;
191
+
192
+ #[ test]
193
+ fn test_compute_count_from_underflowing_initial_states ( ) {
194
+ let skip_count = SkipCount :: new ( ) ;
195
+
196
+ // Initial state with too few new items. None is skipped.
197
+ let previous_number_of_items = 0 ;
198
+ let next_number_of_items = previous_number_of_items + 10 ;
199
+ let count = skip_count. compute_next ( previous_number_of_items, next_number_of_items) ;
200
+ assert_eq ! ( count, 0 ) ;
201
+ skip_count. count . set ( count) ;
202
+
203
+ // Add 5 new items. The count stays at 0 because we don't want to skip the
204
+ // previous items.
205
+ let previous_number_of_items = next_number_of_items;
206
+ let next_number_of_items = previous_number_of_items + 5 ;
207
+ let count = skip_count. compute_next ( previous_number_of_items, next_number_of_items) ;
208
+ assert_eq ! ( count, 0 ) ;
209
+ skip_count. count . set ( count) ;
210
+
211
+ // Add 20 new items. The count stays at 0 because we don't want to
212
+ // skip the previous items.
213
+ let previous_number_of_items = next_number_of_items;
214
+ let next_number_of_items = previous_number_of_items + 20 ;
215
+ let count = skip_count. compute_next ( previous_number_of_items, next_number_of_items) ;
216
+ assert_eq ! ( count, 0 ) ;
217
+ skip_count. count . set ( count) ;
218
+
219
+ // Remove a certain number of items. The count stays at 0 because it was
220
+ // previously 0, no items are skipped, nothing to adjust.
221
+ let previous_number_of_items = next_number_of_items;
222
+ let next_number_of_items = previous_number_of_items - 4 ;
223
+ let count = skip_count. compute_next ( previous_number_of_items, next_number_of_items) ;
224
+ assert_eq ! ( count, 0 ) ;
225
+ skip_count. count . set ( count) ;
226
+
227
+ // Remove all items. The count goes to 0 (regardless it was 0 before).
228
+ let previous_number_of_items = next_number_of_items;
229
+ let next_number_of_items = 0 ;
230
+ let count = skip_count. compute_next ( previous_number_of_items, next_number_of_items) ;
231
+ assert_eq ! ( count, 0 ) ;
232
+ }
233
+
234
+ #[ test]
235
+ fn test_compute_count_from_overflowing_initial_states ( ) {
236
+ let skip_count = SkipCount :: new ( ) ;
237
+
238
+ // Initial state with too much new items. Some are skipped.
239
+ let previous_number_of_items = 0 ;
240
+ let next_number_of_items = previous_number_of_items + 30 ;
241
+ let count = skip_count. compute_next ( previous_number_of_items, next_number_of_items) ;
242
+ assert_eq ! ( count, 10 ) ;
243
+ skip_count. count . set ( count) ;
244
+
245
+ // Add 5 new items. The count stays at 10 because we don't want to skip the
246
+ // previous items.
247
+ let previous_number_of_items = next_number_of_items;
248
+ let next_number_of_items = previous_number_of_items + 5 ;
249
+ let count = skip_count. compute_next ( previous_number_of_items, next_number_of_items) ;
250
+ assert_eq ! ( count, 10 ) ;
251
+ skip_count. count . set ( count) ;
252
+
253
+ // Add 20 new items. The count stays at 10 because we don't want to
254
+ // skip the previous items.
255
+ let previous_number_of_items = next_number_of_items;
256
+ let next_number_of_items = previous_number_of_items + 20 ;
257
+ let count = skip_count. compute_next ( previous_number_of_items, next_number_of_items) ;
258
+ assert_eq ! ( count, 10 ) ;
259
+ skip_count. count . set ( count) ;
260
+
261
+ // Remove a certain number of items. The count is reduced by 5 so that the same
262
+ // number of items are presented.
263
+ let previous_number_of_items = next_number_of_items;
264
+ let next_number_of_items = previous_number_of_items - 4 ;
265
+ let count = skip_count. compute_next ( previous_number_of_items, next_number_of_items) ;
266
+ assert_eq ! ( count, 6 ) ;
267
+ skip_count. count . set ( count) ;
268
+
269
+ // Remove all items. The count goes to 0 (regardless it was 6 before).
270
+ let previous_number_of_items = next_number_of_items;
271
+ let next_number_of_items = 0 ;
272
+ let count = skip_count. compute_next ( previous_number_of_items, next_number_of_items) ;
273
+ assert_eq ! ( count, 0 ) ;
274
+ }
275
+
276
+ #[ test]
277
+ fn test_compute_count_when_paginating_backwards_from_underflowing_initial_states ( ) {
278
+ let skip_count = SkipCount :: new ( ) ;
279
+
280
+ // Initial state with too few new items. None is skipped.
281
+ let previous_number_of_items = 0 ;
282
+ let next_number_of_items = previous_number_of_items + 10 ;
283
+ let count = skip_count. compute_next ( previous_number_of_items, next_number_of_items) ;
284
+ assert_eq ! ( count, 0 ) ;
285
+ skip_count. count . set ( count) ;
286
+
287
+ // Add 30 new items. The count stays at 0 because we don't want to skip the
288
+ // previous items.
289
+ let previous_number_of_items = next_number_of_items;
290
+ let next_number_of_items = previous_number_of_items + 30 ;
291
+ let count = skip_count. compute_next ( previous_number_of_items, next_number_of_items) ;
292
+ assert_eq ! ( count, 0 ) ;
293
+ skip_count. count . set ( count) ;
294
+
295
+ let page_size = 20 ;
296
+
297
+ // Paginate backwards.
298
+ let ( count, needs) = skip_count. compute_next_when_paginating_backwards ( page_size) ;
299
+ assert_eq ! ( count, 0 ) ;
300
+ assert_eq ! ( needs, Some ( 20 ) ) ;
301
+ }
302
+
303
+ #[ test]
304
+ fn test_compute_count_when_paginating_backwards_from_overflowing_initial_states ( ) {
305
+ let skip_count = SkipCount :: new ( ) ;
306
+
307
+ // Initial state with too much new items. Some are skipped.
308
+ let previous_number_of_items = 0 ;
309
+ let next_number_of_items = previous_number_of_items + 50 ;
310
+ let count = skip_count. compute_next ( previous_number_of_items, next_number_of_items) ;
311
+ assert_eq ! ( count, 30 ) ;
312
+ skip_count. count . set ( count) ;
313
+
314
+ // Add 30 new items. The count stays at 30 because we don't want to
315
+ // skip the previous items.
316
+ let previous_number_of_items = next_number_of_items;
317
+ let next_number_of_items = previous_number_of_items + 30 ;
318
+ let count = skip_count. compute_next ( previous_number_of_items, next_number_of_items) ;
319
+ assert_eq ! ( count, 30 ) ;
320
+ skip_count. count . set ( count) ;
321
+
322
+ let page_size = 20 ;
323
+
324
+ // Paginate backwards. The count shifts by `page_size`, and the page is full.
325
+ let ( count, needs) = skip_count. compute_next_when_paginating_backwards ( page_size) ;
326
+ assert_eq ! ( count, 10 ) ;
327
+ assert_eq ! ( needs, None ) ;
328
+ skip_count. count . set ( count) ;
329
+
330
+ // Paginate backwards. The count shifts by `page_size` but reaches 0 before the
331
+ // page becomes full. It needs 10 more items to fulfill the page.
332
+ let ( count, needs) = skip_count. compute_next_when_paginating_backwards ( page_size) ;
333
+ assert_eq ! ( count, 0 ) ;
334
+ assert_eq ! ( needs, Some ( 10 ) ) ;
335
+ }
336
+
337
+ #[ test]
338
+ fn test_compute_count_when_paginating_forwards_from_underflowing_initial_states ( ) {
339
+ let skip_count = SkipCount :: new ( ) ;
340
+
341
+ // Initial state with too few new items. None is skipped.
342
+ let previous_number_of_items = 0 ;
343
+ let next_number_of_items = previous_number_of_items + 10 ;
344
+ let count = skip_count. compute_next ( previous_number_of_items, next_number_of_items) ;
345
+ assert_eq ! ( count, 0 ) ;
346
+ skip_count. count . set ( count) ;
347
+
348
+ // Add 30 new items. The count stays at 0 because we don't want to skip the
349
+ // previous items.
350
+ let previous_number_of_items = next_number_of_items;
351
+ let next_number_of_items = previous_number_of_items + 30 ;
352
+ let count = skip_count. compute_next ( previous_number_of_items, next_number_of_items) ;
353
+ assert_eq ! ( count, 0 ) ;
354
+ skip_count. count . set ( count) ;
355
+
356
+ let page_size = 20 ;
357
+
358
+ // Paginate forwards. The count remains unchanged.
359
+ let count = skip_count. compute_next_when_paginating_forwards ( page_size) ;
360
+ assert_eq ! ( count, 0 ) ;
361
+ }
362
+
363
+ #[ test]
364
+ fn test_compute_count_when_paginating_forwards_from_overflowing_initial_states ( ) {
365
+ let skip_count = SkipCount :: new ( ) ;
366
+
367
+ // Initial state with too much new items. Some are skipped.
368
+ let previous_number_of_items = 0 ;
369
+ let next_number_of_items = previous_number_of_items + 50 ;
370
+ let count = skip_count. compute_next ( previous_number_of_items, next_number_of_items) ;
371
+ assert_eq ! ( count, 30 ) ;
372
+ skip_count. count . set ( count) ;
373
+
374
+ // Add 30 new items. The count stays at 30 because we don't want to
375
+ // skip the previous items.
376
+ let previous_number_of_items = next_number_of_items;
377
+ let next_number_of_items = previous_number_of_items + 30 ;
378
+ let count = skip_count. compute_next ( previous_number_of_items, next_number_of_items) ;
379
+ assert_eq ! ( count, 30 ) ;
380
+ skip_count. count . set ( count) ;
381
+
382
+ let page_size = 20 ;
383
+
384
+ // Paginate forwards. The count remains unchanged.
385
+ let count = skip_count. compute_next_when_paginating_forwards ( page_size) ;
386
+ assert_eq ! ( count, 30 ) ;
387
+ }
388
+ }
389
+ }
0 commit comments