use core::{
future::Future,
marker::PhantomData,
mem,
pin::Pin,
task::{Context, Poll},
};
use emit_core::{ctxt::Ctxt, empty::Empty, props::Props, str::Str, value::Value};
pub struct Frame<C: Ctxt> {
scope: mem::ManuallyDrop<C::Frame>,
ctxt: mem::ManuallyDrop<C>,
}
impl<C: Ctxt> Frame<C> {
#[track_caller]
#[must_use = "call `enter`, `call`, `in_fn` or `in_future` to make the pushed properties active"]
pub fn current(ctxt: C) -> Self {
Self::push(ctxt, Empty)
}
#[track_caller]
#[must_use = "call `enter`, `call`, `in_fn`, or `in_future` to make the pushed properties active"]
pub fn push(ctxt: C, props: impl Props) -> Self {
let scope = ctxt.open_push(props);
Self::from_parts(ctxt, scope)
}
#[track_caller]
#[must_use = "call `enter`, `call`, `in_fn`, or `in_future` to make the properties active"]
pub fn root(ctxt: C, props: impl Props) -> Self {
let scope = ctxt.open_root(props);
Self::from_parts(ctxt, scope)
}
#[track_caller]
#[must_use = "call `enter`, `call`, `in_fn`, or `in_future` to make the properties active"]
pub fn disabled(ctxt: C, props: impl Props) -> Self {
let scope = ctxt.open_disabled(props);
Self::from_parts(ctxt, scope)
}
#[track_caller]
#[must_use = "call `enter`, `call`, `in_fn`, or `in_future` to make unset property active"]
pub fn filtered(ctxt: C, filter: impl Fn(Str, Value) -> bool) -> Self {
let scope = ctxt.with_current(|current| ctxt.open_root(current.filter(filter)));
Self::from_parts(ctxt, scope)
}
#[track_caller]
pub fn with<R>(&mut self, with: impl FnOnce(&C::Current) -> R) -> R {
self.enter().with(with)
}
#[track_caller]
pub fn enter(&mut self) -> EnterGuard<'_, C> {
self.ctxt.enter(&mut self.scope);
EnterGuard {
scope: self,
_marker: PhantomData,
}
}
#[track_caller]
pub fn call<R>(mut self, scope: impl FnOnce() -> R) -> R {
let __guard = self.enter();
scope()
}
#[track_caller]
pub fn in_fn<R>(self, scope: impl FnOnce() -> R) -> impl FnOnce() -> R {
|| self.call(scope)
}
#[track_caller]
#[must_use = "futures do nothing unless polled"]
pub fn in_future<F>(self, future: F) -> FrameFuture<C, F> {
FrameFuture {
frame: self,
future,
}
}
pub fn inner(&self) -> &C::Frame {
&self.scope
}
pub fn inner_mut(&mut self) -> &mut C::Frame {
&mut self.scope
}
pub const fn from_parts(ctxt: C, scope: C::Frame) -> Self {
let scope = mem::ManuallyDrop::new(scope);
let ctxt = mem::ManuallyDrop::new(ctxt);
Frame { ctxt, scope }
}
pub fn into_parts(mut self) -> (C, C::Frame) {
let ctxt = unsafe { mem::ManuallyDrop::take(&mut self.ctxt) };
let scope = unsafe { mem::ManuallyDrop::take(&mut self.scope) };
mem::forget(self);
(ctxt, scope)
}
}
impl<C: Ctxt> Drop for Frame<C> {
fn drop(&mut self) {
let ctxt = unsafe { mem::ManuallyDrop::take(&mut self.ctxt) };
let scope = unsafe { mem::ManuallyDrop::take(&mut self.scope) };
ctxt.close(scope)
}
}
pub struct EnterGuard<'a, C: Ctxt> {
scope: &'a mut Frame<C>,
_marker: PhantomData<*mut fn()>,
}
impl<'a, C: Ctxt> EnterGuard<'a, C> {
#[track_caller]
pub fn with<R>(&mut self, with: impl FnOnce(&C::Current) -> R) -> R {
self.scope.ctxt.with_current(with)
}
}
impl<'a, C: Ctxt> Drop for EnterGuard<'a, C> {
fn drop(&mut self) {
self.scope.ctxt.exit(&mut self.scope.scope);
}
}
pub struct FrameFuture<C: Ctxt, F> {
frame: Frame<C>,
future: F,
}
impl<C: Ctxt, F: Future> Future for FrameFuture<C, F> {
type Output = F::Output;
#[track_caller]
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
let unpinned = unsafe { Pin::get_unchecked_mut(self) };
let __guard = unpinned.frame.enter();
unsafe { Pin::new_unchecked(&mut unpinned.future) }.poll(cx)
}
}
#[cfg(test)]
mod tests {
#[cfg(feature = "std")]
use super::*;
#[cfg(feature = "std")]
#[test]
fn frame_manual() {
let ctxt = crate::platform::DefaultCtxt::new();
let frame = Frame::push(&ctxt, ("a", 1));
let (ctxt, mut inner) = frame.into_parts();
ctxt.enter(&mut inner);
ctxt.with_current(|props| {
assert_eq!(1, props.pull::<i32, _>("a").unwrap());
});
ctxt.exit(&mut inner);
let frame = Frame::from_parts(ctxt, inner);
drop(frame);
}
#[cfg(feature = "std")]
#[test]
fn frame_exec() {
let ctxt = crate::platform::DefaultCtxt::new();
let mut frame = Frame::push(&ctxt, ("a", 1));
frame.with(|props| {
assert_eq!(1, props.pull::<i32, _>("a").unwrap());
});
drop(frame);
}
#[cfg(feature = "std")]
#[test]
fn frame_filtered() {
let ctxt = crate::platform::DefaultCtxt::new();
let mut outer = Frame::push(&ctxt, [("a", 1), ("b", 2)]);
outer.with(|props| {
let mut inner = Frame::filtered(&ctxt, |k, _| k == "b");
assert_eq!(1, props.pull::<i32, _>("a").unwrap());
assert_eq!(2, props.pull::<i32, _>("b").unwrap());
inner.with(|props| {
assert!(props.get("a").is_none());
assert_eq!(2, props.pull::<i32, _>("b").unwrap());
})
});
}
#[cfg(feature = "std")]
#[test]
fn frame_in_fn() {
let ctxt = crate::platform::DefaultCtxt::new();
let frame = Frame::push(&ctxt, ("a", 1));
let f = frame.in_fn(|| {
Frame::current(&ctxt).with(|props| {
assert_eq!(1, props.pull::<i32, _>("a").unwrap());
});
});
f();
}
#[cfg(all(
feature = "std",
not(all(
target_arch = "wasm32",
target_vendor = "unknown",
target_os = "unknown"
)),
not(miri)
))]
#[tokio::test]
async fn frame_in_future() {
let ctxt = crate::platform::DefaultCtxt::new();
let frame = Frame::push(&ctxt, ("a", 1));
frame
.in_future(async {
Frame::current(&ctxt).with(|props| {
assert_eq!(1, props.pull::<i32, _>("a").unwrap());
})
})
.await;
}
}