1
- use crate :: { DataState , ErrorBounds } ;
1
+ use tracing:: { error, warn} ;
2
+
3
+ use crate :: { data_state:: CanMakeProgress , Awaiting , DataState , ErrorBounds } ;
4
+ use std:: fmt:: Debug ;
2
5
use std:: ops:: Range ;
3
- use std:: time:: Instant ;
6
+ use std:: time:: { Duration , Instant } ;
4
7
5
8
/// Automatically retries with a delay on failure until attempts are exhausted
6
9
#[ derive( Debug ) ]
7
10
pub struct DataStateRetry < T , E : ErrorBounds = anyhow:: Error > {
8
- /// The wrapped [`DataState`]
9
- pub inner : DataState < T , E > ,
10
11
/// Number of attempts that the retries get reset to
11
12
pub max_attempts : u8 ,
12
13
/// The range of milliseconds to select a random value from to set the delay
13
14
/// to retry
14
- pub retry_delay_ms : Range < u16 > ,
15
+ pub retry_delay_millis : Range < u16 > ,
15
16
attempts_left : u8 ,
16
- last_attempt : Option < Instant > ,
17
+ inner : DataState < T , E > , // Not public to ensure resets happen as they should
18
+ next_allowed_attempt : Instant ,
17
19
}
18
20
19
21
impl < T , E : ErrorBounds > DataStateRetry < T , E > {
20
22
/// Creates a new instance of [DataStateRetry]
21
- pub fn new ( max_attempts : u8 , retry_delay_ms : Range < u16 > ) -> Self {
23
+ pub fn new ( max_attempts : u8 , retry_delay_millis : Range < u16 > ) -> Self {
22
24
Self {
23
25
max_attempts,
24
- retry_delay_ms ,
26
+ retry_delay_millis ,
25
27
..Default :: default ( )
26
28
}
27
29
}
@@ -31,9 +33,170 @@ impl<T, E: ErrorBounds> DataStateRetry<T, E> {
31
33
self . attempts_left
32
34
}
33
35
34
- /// If an attempt was made the instant that it happened at
35
- pub fn last_attempt ( & self ) -> Option < Instant > {
36
- self . last_attempt
36
+ /// The instant that needs to be waited for before another attempt is
37
+ /// allowed
38
+ pub fn next_allowed_attempt ( & self ) -> Instant {
39
+ self . next_allowed_attempt
40
+ }
41
+
42
+ /// Provides access to the inner [`DataState`]
43
+ pub fn inner ( & self ) -> & DataState < T , E > {
44
+ & self . inner
45
+ }
46
+
47
+ /// Consumes self and returns the unwrapped inner
48
+ pub fn into_inner ( self ) -> DataState < T , E > {
49
+ self . inner
50
+ }
51
+
52
+ /// Provides access to the stored data if available (returns Some if
53
+ /// self.inner is `Data::Present(_)`)
54
+ pub fn present ( & self ) -> Option < & T > {
55
+ if let DataState :: Present ( data) = self . inner . as_ref ( ) {
56
+ Some ( data)
57
+ } else {
58
+ None
59
+ }
60
+ }
61
+
62
+ /// Provides mutable access to the stored data if available (returns Some if
63
+ /// self.inner is `Data::Present(_)`)
64
+ pub fn present_mut ( & mut self ) -> Option < & mut T > {
65
+ if let DataState :: Present ( data) = self . inner . as_mut ( ) {
66
+ Some ( data)
67
+ } else {
68
+ None
69
+ }
70
+ }
71
+
72
+ #[ cfg( feature = "egui" ) ]
73
+ /// Attempts to load the data and displays appropriate UI if applicable.
74
+ ///
75
+ /// Note see [`DataState::egui_get`] for more info.
76
+ #[ must_use]
77
+ pub fn egui_get < F > (
78
+ & mut self ,
79
+ ui : & mut egui:: Ui ,
80
+ retry_msg : Option < & str > ,
81
+ fetch_fn : F ,
82
+ ) -> CanMakeProgress
83
+ where
84
+ F : FnOnce ( ) -> Awaiting < T , E > ,
85
+ {
86
+ match self . inner . as_ref ( ) {
87
+ DataState :: None | DataState :: AwaitingResponse ( _) => {
88
+ self . ui_spinner_with_attempt_count ( ui) ;
89
+ self . get ( fetch_fn)
90
+ }
91
+ DataState :: Present ( _data) => {
92
+ // Does nothing as data is already present
93
+ CanMakeProgress :: UnableToMakeProgress
94
+ }
95
+ DataState :: Failed ( e) => {
96
+ ui. colored_label (
97
+ ui. visuals ( ) . error_fg_color ,
98
+ format ! ( "{} attempts exhausted. {e}" , self . max_attempts) ,
99
+ ) ;
100
+ if ui. button ( retry_msg. unwrap_or ( "Restart Requests" ) ) . clicked ( ) {
101
+ self . reset_attempts ( ) ;
102
+ self . inner = DataState :: default ( ) ;
103
+ }
104
+ CanMakeProgress :: AbleToMakeProgress
105
+ }
106
+ }
107
+ }
108
+
109
+ /// Attempts to load the data and returns if it is able to make progress.
110
+ ///
111
+ /// See [`DataState::get`] for more info.
112
+ #[ must_use]
113
+ pub fn get < F > ( & mut self , fetch_fn : F ) -> CanMakeProgress
114
+ where
115
+ F : FnOnce ( ) -> Awaiting < T , E > ,
116
+ {
117
+ match self . inner . as_mut ( ) {
118
+ DataState :: None => {
119
+ // Going to make an attempt, set when the next attempt is allowed
120
+ use rand:: Rng ;
121
+ let wait_time_in_millis = rand:: thread_rng ( )
122
+ . gen_range ( self . retry_delay_millis . clone ( ) )
123
+ . into ( ) ;
124
+ self . next_allowed_attempt = Instant :: now ( )
125
+ . checked_add ( Duration :: from_millis ( wait_time_in_millis) )
126
+ . expect ( "failed to get random delay, value was out of range" ) ;
127
+
128
+ self . inner . get ( fetch_fn)
129
+ }
130
+ DataState :: AwaitingResponse ( rx) => {
131
+ if let Some ( new_state) = DataState :: await_data ( rx) {
132
+ // TODO 4: Add some tests to ensure await_data work as this function assumes
133
+ self . inner = match new_state. as_ref ( ) {
134
+ DataState :: None => {
135
+ error ! ( "Unexpected new state received of DataState::None" ) ;
136
+ unreachable ! ( "Only expect Failed or Present variants to be returned but got None" )
137
+ }
138
+ DataState :: AwaitingResponse ( _) => {
139
+ error ! ( "Unexpected new state received of AwaitingResponse" ) ;
140
+ unreachable ! ( "Only expect Failed or Present variants to be returned bug got AwaitingResponse" )
141
+ }
142
+ DataState :: Present ( _) => {
143
+ // Data was successfully received
144
+ self . reset_attempts ( ) ;
145
+ new_state
146
+ }
147
+ DataState :: Failed ( _) => new_state,
148
+ } ;
149
+ }
150
+ CanMakeProgress :: AbleToMakeProgress
151
+ }
152
+ DataState :: Present ( _) => self . inner . get ( fetch_fn) ,
153
+ DataState :: Failed ( err_msg) => {
154
+ if self . attempts_left == 0 {
155
+ self . inner . get ( fetch_fn)
156
+ } else {
157
+ let wait_duration_left = self
158
+ . next_allowed_attempt
159
+ . saturating_duration_since ( Instant :: now ( ) ) ;
160
+ if wait_duration_left. is_zero ( ) {
161
+ warn ! ( ?err_msg, ?self . attempts_left, "retrying request" ) ;
162
+ self . attempts_left -= 1 ;
163
+ self . inner = DataState :: None ;
164
+ }
165
+ CanMakeProgress :: AbleToMakeProgress
166
+ }
167
+ }
168
+ }
169
+ }
170
+
171
+ /// Resets the attempts taken
172
+ pub fn reset_attempts ( & mut self ) {
173
+ self . attempts_left = self . max_attempts ;
174
+ self . next_allowed_attempt = Instant :: now ( ) ;
175
+ }
176
+
177
+ /// Clear stored data
178
+ pub fn clear ( & mut self ) {
179
+ self . inner = DataState :: default ( ) ;
180
+ }
181
+
182
+ /// Returns `true` if the internal data state is [`DataState::Present`].
183
+ #[ must_use]
184
+ pub fn is_present ( & self ) -> bool {
185
+ self . inner . is_present ( )
186
+ }
187
+
188
+ /// Returns `true` if the internal data state is [`DataState::None`].
189
+ #[ must_use]
190
+ pub fn is_none ( & self ) -> bool {
191
+ self . inner . is_none ( )
192
+ }
193
+
194
+ fn ui_spinner_with_attempt_count ( & self , ui : & mut egui:: Ui ) {
195
+ ui. horizontal ( |ui| {
196
+ ui. spinner ( ) ;
197
+ ui. separator ( ) ;
198
+ ui. label ( format ! ( "{} attempts left" , self . attempts_left) )
199
+ } ) ;
37
200
}
38
201
}
39
202
@@ -42,9 +205,9 @@ impl<T, E: ErrorBounds> Default for DataStateRetry<T, E> {
42
205
Self {
43
206
inner : Default :: default ( ) ,
44
207
max_attempts : 3 ,
45
- retry_delay_ms : 1000 ..5000 ,
208
+ retry_delay_millis : 1000 ..5000 ,
46
209
attempts_left : 3 ,
47
- last_attempt : Default :: default ( ) ,
210
+ next_allowed_attempt : Instant :: now ( ) ,
48
211
}
49
212
}
50
213
}
0 commit comments