use std::future::Future;
use std::pin::Pin;
use std::sync::Arc;
use std::task::{Context, Poll};
use crate::entropy::{Entropy, SeededEntropy};
pub const SHUFFLE_STREAM_SALT: u64 = 0x5_11F7_1E5E_ED00_u64;
const SKIP_ONE_IN: u64 = 4;
const MAX_CONSECUTIVE_SKIPS: u8 = 3;
pub fn stream(seed: u64, call: u64) -> Arc<dyn Entropy> {
let derived = SeededEntropy::new(seed ^ SHUFFLE_STREAM_SALT).derive_uuid(call.to_le_bytes());
let bytes: [u8; 8] = derived.as_bytes()[..8]
.try_into()
.expect("a uuid has 16 bytes");
SeededEntropy::shared(u64::from_le_bytes(bytes))
}
fn should_skip(rng: &dyn Entropy, skips: &mut u8) -> bool {
if *skips < MAX_CONSECUTIVE_SKIPS && rng.next_u64().is_multiple_of(SKIP_ONE_IN) {
*skips += 1;
true
} else {
*skips = 0;
false
}
}
pub struct Interleave<F: Future> {
ops: Vec<Option<Pin<Box<F>>>>,
outputs: Vec<Option<F::Output>>,
skips: Vec<u8>,
rng: Arc<dyn Entropy>,
}
impl<F: Future> Interleave<F> {
pub fn new(ops: Vec<F>, rng: Arc<dyn Entropy>) -> Self {
let len = ops.len();
Self {
ops: ops.into_iter().map(|op| Some(Box::pin(op))).collect(),
outputs: (0..len).map(|_| None).collect(),
skips: vec![0; len],
rng,
}
}
}
impl<F: Future> Unpin for Interleave<F> {}
impl<F: Future> Future for Interleave<F> {
type Output = Vec<F::Output>;
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
let this = self.get_mut();
let mut order: Vec<usize> = (0..this.ops.len())
.filter(|&index| this.ops[index].is_some())
.collect();
for last in (1..order.len()).rev() {
let pick = usize::try_from(this.rng.next_u64() % (last as u64 + 1))
.expect("pick is at most `last`, which is a usize");
order.swap(last, pick);
}
let mut skipped = false;
for index in order {
if !skipped && should_skip(this.rng.as_ref(), &mut this.skips[index]) {
skipped = true;
continue;
}
let Some(op) = this.ops[index].as_mut() else {
continue;
};
if let Poll::Ready(output) = op.as_mut().poll(cx) {
this.outputs[index] = Some(output);
this.ops[index] = None;
}
}
if this.ops.iter().all(Option::is_none) {
let outputs = this
.outputs
.iter_mut()
.map(|output| output.take().expect("every op finished"))
.collect();
return Poll::Ready(outputs);
}
if skipped {
cx.waker().wake_by_ref();
}
Poll::Pending
}
}
pub struct Shuffled<F> {
op: Pin<Box<F>>,
skips: u8,
rng: Arc<dyn Entropy>,
}
impl<F> Shuffled<F> {
pub fn new(op: F, rng: Arc<dyn Entropy>) -> Self {
Self {
op: Box::pin(op),
skips: 0,
rng,
}
}
}
impl<F: Future> Future for Shuffled<F> {
type Output = F::Output;
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
let this = &mut *self;
if should_skip(this.rng.as_ref(), &mut this.skips) {
cx.waker().wake_by_ref();
return Poll::Pending;
}
this.op.as_mut().poll(cx)
}
}
#[cfg(test)]
mod tests {
use super::{MAX_CONSECUTIVE_SKIPS, should_skip, stream};
#[test]
fn skips_are_capped() {
let rng = stream(0, 0);
let mut skips = 0;
let mut run = 0;
for _ in 0..10_000 {
if should_skip(rng.as_ref(), &mut skips) {
run += 1;
assert!(run <= MAX_CONSECUTIVE_SKIPS, "a skip run stays capped");
} else {
run = 0;
}
}
}
#[test]
fn streams_are_seed_and_call_deterministic() {
assert_eq!(stream(7, 1).next_u64(), stream(7, 1).next_u64());
assert_ne!(stream(7, 1).next_u64(), stream(7, 2).next_u64());
assert_ne!(stream(7, 1).next_u64(), stream(8, 1).next_u64());
}
}