use super::{
BoxedGenerator, BoxedPrintableGenerator, Generator, PrintableGenerator, label_from_name,
};
use crate::pretty::PrettyPrinter;
use crate::test_case::TestCase;
use std::cell::RefCell;
use std::sync::{Arc, OnceLock};
const DEFERRED_LABEL: u64 = label_from_name("hegel.deferred");
struct Shared<B> {
generator: OnceLock<B>,
label: OnceLock<u64>,
}
impl<B> Shared<B> {
fn new() -> Self {
Shared {
generator: OnceLock::new(),
label: OnceLock::new(),
}
}
}
thread_local! {
static CALCULATING_LABELS: RefCell<Vec<usize>> = const { RefCell::new(Vec::new()) };
}
struct CalculatingLabel(usize);
impl Drop for CalculatingLabel {
fn drop(&mut self) {
CALCULATING_LABELS.with(|calculating| {
let mut calculating = calculating.borrow_mut();
let position = calculating.iter().rposition(|&key| key == self.0).unwrap();
calculating.remove(position);
});
}
}
struct DeferredGenerator<B> {
inner: Arc<Shared<B>>,
}
impl<B> DeferredGenerator<B> {
fn get(&self) -> &B {
self.inner
.generator
.get()
.unwrap_or_else(|| panic!("DeferredGenerator has not been set"))
}
}
impl<T, B: Generator<T> + Send + Sync> Generator<T> for DeferredGenerator<B> {
fn label(&self) -> u64 {
if let Some(&label) = self.inner.label.get() {
return label;
}
let generator = self.get();
let key = Arc::as_ptr(&self.inner) as usize;
let in_progress = CALCULATING_LABELS.with(|calculating| {
let mut calculating = calculating.borrow_mut();
let in_progress = calculating.contains(&key);
if !in_progress {
calculating.push(key);
}
in_progress
});
if in_progress {
return DEFERRED_LABEL;
}
let _guard = CalculatingLabel(key);
let label = generator.label();
*self.inner.label.get_or_init(|| label)
}
fn do_draw(&self, tc: &TestCase) -> T {
self.get().do_draw(tc)
}
}
impl<T, B: PrintableGenerator<T> + Send + Sync> PrintableGenerator<T> for DeferredGenerator<B> {
fn do_draw_and_print(&self, tc: &TestCase, printer: &mut PrettyPrinter) -> T {
self.get().do_draw_and_print(tc, printer)
}
}
pub struct DeferredGeneratorDefinition<T, B = BoxedPrintableGenerator<'static, T>> {
inner: Arc<Shared<B>>,
_phantom: std::marker::PhantomData<fn(T)>,
}
impl<T: Send + Sync + 'static> DeferredGeneratorDefinition<T, BoxedPrintableGenerator<'static, T>> {
pub fn generator(&self) -> BoxedPrintableGenerator<'static, T> {
DeferredGenerator {
inner: Arc::clone(&self.inner),
}
.boxed_printable()
}
pub fn set(self, generator: impl PrintableGenerator<T> + Send + Sync + 'static) {
let _ = self.inner.generator.set(generator.boxed_printable());
}
}
impl<T: Send + Sync + 'static> DeferredGeneratorDefinition<T, BoxedGenerator<'static, T>> {
pub fn generator(&self) -> BoxedGenerator<'static, T> {
DeferredGenerator {
inner: Arc::clone(&self.inner),
}
.boxed()
}
pub fn set(self, generator: impl Generator<T> + Send + Sync + 'static) {
let _ = self.inner.generator.set(generator.boxed());
}
}
pub fn deferred<T>() -> DeferredGeneratorDefinition<T> {
DeferredGeneratorDefinition {
inner: Arc::new(Shared::new()),
_phantom: std::marker::PhantomData,
}
}
pub fn deferred_silent<T>() -> DeferredGeneratorDefinition<T, BoxedGenerator<'static, T>> {
DeferredGeneratorDefinition {
inner: Arc::new(Shared::new()),
_phantom: std::marker::PhantomData,
}
}