1use crate::{
2 Engine, EvolutionContext, Generation, Handler, Limit,
3 events::{EngineLogger, Event, GenerationSnapshot, HealthMonitor, LoggingHandler},
4};
5use crate::{generation::GenerationView, init_logging};
6use radiate_core::Expr;
7use radiate_core::error::{RadiateResult, Result};
8use radiate_core::{Chromosome, EngineState, Score};
9use std::collections::VecDeque;
10use std::time::Duration;
11
12pub trait RuntimeLimit<E: Engine> {
13 fn proceed(&mut self, context: &E::Ctx) -> RadiateResult<bool>;
14}
15
16pub struct EngineRuntime<E: Engine> {
17 engine: E,
18 limits: Vec<Box<dyn RuntimeLimit<E>>>,
19}
20
21impl<E: Engine> EngineRuntime<E> {
22 pub fn new(engine: E) -> Self {
23 Self {
24 engine,
25 limits: Vec::new(),
26 }
27 }
28
29 #[inline]
30 pub fn run(mut self) -> Result<E::Epoch> {
31 loop {
32 if matches!(self.engine.state(), EngineState::Stopped) {
33 return Ok(self.engine.epoch());
34 }
35
36 self.step()?;
37 }
38 }
39
40 #[inline]
41 fn step(&mut self) -> Result<()> {
42 if matches!(self.engine.state(), EngineState::Stopped) {
43 return Ok(());
44 }
45
46 self.engine.step()?;
47
48 let ctx = self.engine.context();
49
50 for limit in self.limits.iter_mut() {
51 if !limit.proceed(ctx)? {
52 self.engine.stop();
53 return Ok(());
54 }
55 }
56
57 Ok(())
58 }
59
60 fn add_limit<L>(&mut self, limit: L)
61 where
62 L: RuntimeLimit<E> + 'static,
63 {
64 let boxed: Box<dyn RuntimeLimit<E>> = Box::new(limit);
65 self.limits.push(boxed);
66 }
67}
68
69impl<C, T, E> EngineRuntime<E>
72where
73 E: Engine<Epoch = Generation<C, T>, Ctx = EvolutionContext<C, T>>,
74 C: Chromosome + Clone + 'static,
75 T: Clone + Send + Sync + 'static,
76{
77 pub fn chain_if(self, condition: bool, action_fn: impl FnOnce(Self) -> Self) -> Self {
78 if condition { action_fn(self) } else { self }
79 }
80
81 pub fn last(self) -> Result<E::Epoch> {
82 self.run()
83 }
84
85 pub fn every<F>(self, interval: usize, mut action_fn: F) -> Self
86 where
87 F: FnMut(GenerationView<C, T>) + Send + Sync + 'static,
88 {
89 assert!(interval > 0, "every interval must be greater than zero");
90 let guarded_interval = interval.max(1);
91
92 self.engine
93 .context()
94 .event_stream()
95 .subscribe(move |ctx: &GenerationSnapshot<C, T>| {
96 let inner = &ctx.generation;
97 action_fn(GenerationView::from(inner.as_ref()));
98 })
99 .schedule(Expr::every(guarded_interval))
100 .unwrap();
101 self
102 }
103
104 pub fn throttle<F>(self, duration: Duration, mut action_fn: F) -> Self
105 where
106 F: FnMut(GenerationView<C, T>) + Send + Sync + 'static,
107 {
108 self.engine
109 .context()
110 .event_stream()
111 .subscribe(move |ctx: &GenerationSnapshot<C, T>| {
112 let inner = &ctx.generation;
113 action_fn(GenerationView::from(inner.as_ref()));
114 })
115 .schedule(Expr::throttle(duration))
116 .unwrap();
117 self
118 }
119
120 pub fn subscribe<EV: Event>(self, handler: impl Handler<EV>) -> Self {
121 self.engine.context().event_stream().subscribe(handler);
122 self
123 }
124}
125
126impl<C, T, E> EngineRuntime<E>
129where
130 E: Engine<Epoch = Generation<C, T>, Ctx = EvolutionContext<C, T>>,
131 C: Chromosome + Clone + 'static,
132 T: Clone + Send + Sync + 'static,
133{
134 pub fn until_score(mut self, score: impl Into<Score>) -> EngineRuntime<E> {
135 self.add_limit(Limit::Score(score.into()));
136 self
137 }
138
139 pub fn until_generation(mut self, generations: usize) -> EngineRuntime<E> {
140 self.add_limit(Limit::Generation(generations));
141 self
142 }
143
144 pub fn until_seconds(mut self, seconds: f64) -> EngineRuntime<E> {
145 self.add_limit(Limit::Seconds(Duration::from_secs_f64(seconds)));
146 self
147 }
148
149 pub fn until_duration(mut self, duration: impl Into<std::time::Duration>) -> EngineRuntime<E> {
150 self.add_limit(Limit::Seconds(duration.into()));
151 self
152 }
153
154 pub fn until_convergence(mut self, window: usize, epsilon: f32) -> EngineRuntime<E> {
155 self.add_limit(Limit::Convergence(
156 window,
157 epsilon,
158 VecDeque::with_capacity(window),
159 ));
160 self
161 }
162
163 pub fn until_expr(mut self, expr: impl Into<Expr>) -> EngineRuntime<E> {
164 self.add_limit(Limit::Expr(expr.into()));
165 self
166 }
167
168 pub fn until<F>(mut self, limit: F) -> EngineRuntime<E>
169 where
170 C: 'static,
171 F: Fn(GenerationView<C, T>) -> bool + 'static,
172 {
173 self.add_limit(limit);
174 self
175 }
176
177 pub fn limit(self, limit: impl Into<Limit>) -> EngineRuntime<E> {
178 let limit = limit.into();
179 match limit {
180 Limit::Generation(gens) => self.until_generation(gens),
181 Limit::Seconds(secs) => self.until_duration(secs),
182 Limit::Score(score) => self.until_score(score),
183 Limit::Convergence(window, epsilon, _) => self.until_convergence(window, epsilon),
184 Limit::Expr(expr) => self.until_expr(expr),
185 Limit::Combined(lims) => lims
186 .into_iter()
187 .fold(self, |runtime, limit| runtime.limit(limit)),
188 Limit::Fn => self,
189 }
190 }
191
192 pub fn take(self, count: usize) -> EngineRuntime<E> {
193 self.until_generation(count)
194 }
195
196 pub fn take_while<F>(self, predicate: F) -> EngineRuntime<E>
197 where
198 C: 'static,
199 F: Fn(GenerationView<C, T>) -> bool + 'static,
200 {
201 self.until(move |view: GenerationView<C, T>| -> bool { !predicate(view) })
202 }
203}
204
205impl<C, T, E> EngineRuntime<E>
208where
209 E: Engine<Epoch = Generation<C, T>, Ctx = EvolutionContext<C, T>>,
210 C: Chromosome + Clone + 'static,
211 T: Clone + Send + Sync + 'static,
212{
213 pub fn logging(self) -> EngineRuntime<E> {
214 init_logging();
215 let stream = self.engine.context().event_stream();
216
217 stream.attatch(EngineLogger::<T>::new()).unwrap();
218 stream.attatch(HealthMonitor::<T>::default()).unwrap();
219 stream.subscribe(LoggingHandler);
220
221 self
222 }
223}
224
225impl<E> Iterator for EngineRuntime<E>
226where
227 E: Engine + 'static,
228{
229 type Item = E::Epoch;
230
231 fn next(&mut self) -> Option<Self::Item> {
232 if matches!(self.engine.state(), EngineState::Stopped) {
233 return None;
234 }
235
236 self.step().ok()?;
237 Some(self.engine.epoch())
238 }
239}