1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
//! Async population evaluator.
//!
//! Available only with the `async` feature. Used by the `run_async`
//! method on algorithms that support async problems.
use futures::stream::{FuturesOrdered, StreamExt};
use crate::core::async_problem::AsyncProblem;
use crate::core::candidate::Candidate;
/// Evaluate every decision concurrently against `problem`, preserving
/// input order in the returned vector. Concurrency is bounded by
/// `concurrency` (≥ 1) — too high a value wastes memory and may
/// overload downstream services; too low forfeits parallelism.
///
/// Returns a future that the caller drives via their preferred
/// runtime (typically tokio).
pub async fn evaluate_batch_async<P>(
problem: &P,
decisions: Vec<P::Decision>,
concurrency: usize,
) -> Vec<Candidate<P::Decision>>
where
P: AsyncProblem,
{
assert!(
concurrency >= 1,
"evaluate_batch_async concurrency must be >= 1"
);
let mut out: Vec<Candidate<P::Decision>> = Vec::with_capacity(decisions.len());
// Process in concurrency-bounded chunks to keep peak memory low
// and avoid blasting downstream services. Each chunk uses
// FuturesOrdered to preserve per-chunk order, and chunks are
// emitted in their natural order.
let mut iter = decisions.into_iter();
loop {
let mut futs = FuturesOrdered::new();
for _ in 0..concurrency {
match iter.next() {
Some(d) => {
futs.push_back(async move {
let e = problem.evaluate_async(&d).await;
Candidate::new(d, e)
});
}
None => break,
}
}
if futs.is_empty() {
break;
}
while let Some(c) = futs.next().await {
out.push(c);
}
}
out
}