use pin_project::pin_project;
use std::{
cell::RefCell,
future::Future,
pin::Pin,
task::{Context, Poll},
};
use super::{ContextData, EventContext};
pub trait WithEventContext: Future {
fn with_event_context(self, context_data: ContextData) -> EventContextFuture<Self>
where
Self: Sized,
{
EventContextFuture {
future: self,
context_data: Some(context_data),
}
}
}
impl<F: Future> WithEventContext for F {}
#[pin_project]
pub struct EventContextFuture<F> {
#[pin]
future: F,
context_data: Option<ContextData>,
}
impl<F: Future> Future for EventContextFuture<F> {
type Output = F::Output;
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
let this = self.project();
let data = this
.context_data
.take()
.expect("EventContextFuture must not be polled after a panic");
let guard = SeedGuard::enter(data);
let res = this.future.poll(cx);
*this.context_data = Some(guard.finish());
res
}
}
struct PendingSeed {
data: Option<ContextData>,
ctx: Option<EventContext>,
}
thread_local! {
static PENDING_SEEDS: RefCell<Vec<PendingSeed>> = const { RefCell::new(Vec::new()) };
}
pub(super) fn materialize_pending_seeds() {
PENDING_SEEDS.with(|p| {
let mut pending = p.borrow_mut();
for seed in pending.iter_mut() {
if seed.ctx.is_none() {
let data = seed.data.take().expect("unmaterialized seed retains data");
seed.ctx = Some(EventContext::push_entry(data));
}
}
})
}
#[must_use]
struct SeedGuard(());
impl SeedGuard {
fn enter(data: ContextData) -> Self {
PENDING_SEEDS.with(|p| {
p.borrow_mut().push(PendingSeed {
data: Some(data),
ctx: None,
})
});
SeedGuard(())
}
fn finish(self) -> ContextData {
let seed = PENDING_SEEDS
.with(|p| p.borrow_mut().pop())
.expect("SeedGuard::finish: pending-seed stack is empty");
std::mem::forget(self);
match seed {
PendingSeed { ctx: Some(ctx), .. } => ctx.data(),
PendingSeed { data, .. } => data.expect("unmaterialized seed retains data"),
}
}
}
impl Drop for SeedGuard {
fn drop(&mut self) {
let _ = PENDING_SEEDS.with(|p| p.borrow_mut().pop());
}
}
#[cfg(test)]
pub(super) fn pending_seed_depth() -> usize {
PENDING_SEEDS.with(|p| p.borrow().len())
}