des_sim/execution/strategy/continue_strategy/
limit_abort.rs1use crate::execution::phase::UncheckedActiveExecutor;
9use crate::execution::strategy::{ContinueStrategy, ContinuousStrategyResult};
10use crate::modeling::model::Model;
11use std::fmt::{Display, Formatter};
12
13#[derive(Debug)]
15pub enum LimitAbortStrategyError<RunnerError> {
16 Runner(RunnerError),
18 LimitExceeded {
20 limit_micro_step_count: u64,
21 limit_micro_step_exceeded_count: usize,
22 },
23}
24
25impl<RunnerError: Display> Display for LimitAbortStrategyError<RunnerError> {
26 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
27 match self {
28 LimitAbortStrategyError::Runner(e) => {
29 write!(f, "{}", e)
30 }
31 LimitAbortStrategyError::LimitExceeded {
32 limit_micro_step_count,
33 limit_micro_step_exceeded_count,
34 } => {
35 write!(
36 f,
37 "The maximum limit of {} micro-steps has been exceeded {} times.",
38 limit_micro_step_count, limit_micro_step_exceeded_count
39 )
40 }
41 }
42 }
43}
44
45impl<RunnerError: std::error::Error> std::error::Error for LimitAbortStrategyError<RunnerError> {}
46
47#[derive(Clone)]
55pub struct LimitAbortStrategy {
56 limit_micro_step_count: u64,
57 allow_error_count: usize,
58 error_count: usize,
59}
60
61impl<E, M: Model<E>, RunnerError> ContinueStrategy<E, M, RunnerError> for LimitAbortStrategy {
62 type Err = LimitAbortStrategyError<RunnerError>;
63
64 fn handle_micro_step_continue(
65 &mut self,
66 model: &M,
67 unchecked: UncheckedActiveExecutor<E, M>,
68 ) -> ContinuousStrategyResult<E, M, Self::Err> {
69 let current_micro_step = unchecked.current_micro_step();
70
71 if current_micro_step.value() < self.limit_micro_step_count {
73 return Ok(unchecked.into_active_executor());
74 }
75
76 self.error_count += 1;
77 let mut next_active = unchecked.into_active_executor();
78
79 if self.error_count <= self.allow_error_count {
81 next_active.discard_remain_micro_step(model);
83 Ok(next_active)
84 } else {
85 next_active.discard_remain_micro_step(model);
87 Err((
88 next_active,
89 LimitAbortStrategyError::LimitExceeded {
90 limit_micro_step_count: self.limit_micro_step_count,
91 limit_micro_step_exceeded_count: self.error_count,
92 },
93 ))
94 }
95 }
96}
97
98impl LimitAbortStrategy {
99 pub fn new(limit_micro_step_count: u64, allow_error_count: usize) -> Self {
105 LimitAbortStrategy {
106 limit_micro_step_count,
107 allow_error_count,
108 error_count: 0,
109 }
110 }
111}
112
113#[cfg(test)]
114mod tests {
115 use super::*;
116 use crate::context::{ActiveExecutorContext, EventContext};
117 use crate::event_scheduler::EventScheduler;
118 use crate::modeling::event::Event;
119 use crate::modeling::hook::instance::HookDelegate;
120 use crate::primitive::time::{MicroStep, MicroStepStatus, TickStatus};
121 use crate::source_handler::SourceHandler;
122
123 #[derive(Debug, PartialEq, Eq, Clone, Copy)]
124 enum TestEvent {}
125
126 struct TestModel;
127
128 impl Model<TestEvent> for TestModel {
129 fn handle_event(
130 &mut self,
131 _context: &mut EventContext<TestEvent, Self>,
132 _event: &Event<TestEvent>,
133 ) {
134 }
136 }
137
138 fn create_unchecked_executor(
140 micro_step_val: u64,
141 ) -> UncheckedActiveExecutor<TestEvent, TestModel> {
142 let active_context = ActiveExecutorContext {
143 current_tick_status: TickStatus::initialize(),
144 next_micro_step_status: MicroStepStatus::new(MicroStep::new(micro_step_val + 1)),
145 hook_delegate: HookDelegate::new(),
146 source_handler: SourceHandler::new(),
147 event_scheduler: EventScheduler::new(),
148 };
149 UncheckedActiveExecutor::new(active_context, MicroStep::new(micro_step_val))
150 }
151
152 #[test]
153 fn test_limit_abort_strategy_under_limit() {
154 let model = TestModel;
155 let mut strategy = LimitAbortStrategy::new(10, 1);
157
158 let unchecked = create_unchecked_executor(9);
160 let result: ContinuousStrategyResult<
161 TestEvent,
162 TestModel,
163 LimitAbortStrategyError<String>,
164 > = strategy.handle_micro_step_continue(&model, unchecked);
165
166 assert!(result.is_ok());
168 assert_eq!(strategy.error_count, 0);
169 }
170
171 #[test]
172 fn test_limit_abort_strategy_reach_limit_within_allowance() {
173 let model = TestModel;
174 let mut strategy = LimitAbortStrategy::new(10, 1);
176
177 let unchecked = create_unchecked_executor(10);
179 let result: ContinuousStrategyResult<
180 TestEvent,
181 TestModel,
182 LimitAbortStrategyError<String>,
183 > = strategy.handle_micro_step_continue(&model, unchecked);
184
185 assert!(result.is_ok());
187 assert_eq!(strategy.error_count, 1);
188 }
189
190 #[test]
191 fn test_limit_abort_strategy_exceed_allowance_aborts() {
192 let model = TestModel;
193 let mut strategy = LimitAbortStrategy::new(10, 1);
195
196 let unchecked_1 = create_unchecked_executor(10);
198 let unchecked_1_result: ContinuousStrategyResult<
199 TestEvent,
200 TestModel,
201 LimitAbortStrategyError<String>,
202 > = strategy.handle_micro_step_continue(&model, unchecked_1);
203 assert!(unchecked_1_result.is_ok());
204
205 let unchecked_2 = create_unchecked_executor(10);
207 let unchecked_2_result: ContinuousStrategyResult<
208 TestEvent,
209 TestModel,
210 LimitAbortStrategyError<String>,
211 > = strategy.handle_micro_step_continue(&model, unchecked_2);
212
213 assert!(unchecked_2_result.is_err());
215 assert_eq!(strategy.error_count, 2);
216
217 let (_context, error) = unchecked_2_result.err().unwrap();
219 match error {
220 LimitAbortStrategyError::LimitExceeded {
221 limit_micro_step_count,
222 limit_micro_step_exceeded_count,
223 } => {
224 assert_eq!(limit_micro_step_count, 10);
225 assert_eq!(limit_micro_step_exceeded_count, 2);
226 }
227 _ => panic!("Expected LimitAbortStrategyError::LimitExceeded variant"),
228 }
229 }
230
231 #[test]
232 fn test_error_display() {
233 let error: LimitAbortStrategyError<String> = LimitAbortStrategyError::LimitExceeded {
235 limit_micro_step_count: 5,
236 limit_micro_step_exceeded_count: 3,
237 };
238 let message = format!("{}", error);
239 assert_eq!(
240 message,
241 "The maximum limit of 5 micro-steps has been exceeded 3 times."
242 );
243
244 let runner_error = LimitAbortStrategyError::Runner("internal error".to_string());
245 assert_eq!(format!("{}", runner_error), "internal error");
246 }
247}