use std::{
pin::Pin,
task::{Context, Poll},
};
use pin_project_lite::pin_project;
use topcoat_core::error::Result;
use super::{Identity, IdentityGuard};
use crate::{View, ViewFirst, ViewSwap};
pin_project! {
pub struct IdentityView<V> {
#[pin]
view: V,
identity: Identity,
}
}
impl<V: View> IdentityView<V> {
pub fn new(identity: Identity, view: V) -> Self {
Self { view, identity }
}
}
impl<V: View> View for IdentityView<V> {
fn poll_first(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Result<ViewFirst>> {
let this = self.project();
let _guard = IdentityGuard::install(*this.identity);
this.view.poll_first(cx)
}
fn poll_swap(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Result<Option<ViewSwap>>> {
let this = self.project();
let _guard = IdentityGuard::install(*this.identity);
this.view.poll_swap(cx)
}
}
#[cfg(test)]
mod tests {
use std::{
panic::{AssertUnwindSafe, catch_unwind},
pin::pin,
task::Waker,
};
use super::*;
use crate::{ViewHandle, identity::SiteKey};
const SITE_A: SiteKey = SiteKey::new(file!(), line!(), column!(), 0);
struct Probe {
polled: bool,
seen: Vec<Identity>,
}
impl Probe {
fn new() -> Self {
Self {
polled: false,
seen: Vec::new(),
}
}
}
impl View for Probe {
fn poll_first(mut self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<Result<ViewFirst>> {
self.seen.push(Identity::current());
if std::mem::replace(&mut self.polled, true) {
Poll::Ready(Ok(ViewFirst {
content: ViewHandle::empty(),
live: true,
}))
} else {
Poll::Pending
}
}
fn poll_swap(
mut self: Pin<&mut Self>,
_cx: &mut Context<'_>,
) -> Poll<Result<Option<ViewSwap>>> {
self.seen.push(Identity::current());
Poll::Ready(Ok(None))
}
}
struct Boom;
impl View for Boom {
fn poll_first(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<Result<ViewFirst>> {
panic!("boom")
}
fn poll_swap(
self: Pin<&mut Self>,
_cx: &mut Context<'_>,
) -> Poll<Result<Option<ViewSwap>>> {
panic!("boom")
}
}
#[test]
fn the_view_installs_its_identity_only_while_polling() {
let identity = Identity::ROOT.child(SITE_A);
let mut view = pin!(IdentityView::new(identity, Probe::new()));
let mut cx = Context::from_waker(Waker::noop());
assert_eq!(Identity::current(), Identity::ROOT);
assert!(view.as_mut().poll_first(&mut cx).is_pending());
assert!(view.as_mut().poll_first(&mut cx).is_ready());
assert!(view.as_mut().poll_swap(&mut cx).is_ready());
assert_eq!(Identity::current(), Identity::ROOT);
assert_eq!(view.view.seen, [identity, identity, identity]);
}
#[test]
fn interleaved_siblings_each_see_their_own_identity() {
let first_identity = Identity::ROOT.keyed_child(SITE_A, 1);
let second_identity = Identity::ROOT.keyed_child(SITE_A, 2);
let mut first = pin!(IdentityView::new(first_identity, Probe::new()));
let mut second = pin!(IdentityView::new(second_identity, Probe::new()));
let mut cx = Context::from_waker(Waker::noop());
assert!(first.as_mut().poll_first(&mut cx).is_pending());
assert!(second.as_mut().poll_first(&mut cx).is_pending());
assert!(first.as_mut().poll_first(&mut cx).is_ready());
assert!(second.as_mut().poll_first(&mut cx).is_ready());
assert_eq!(first.view.seen, [first_identity, first_identity]);
assert_eq!(second.view.seen, [second_identity, second_identity]);
}
#[test]
fn the_view_restores_the_identity_when_a_poll_panics() {
let mut view = pin!(IdentityView::new(Identity::ROOT.child(SITE_A), Boom));
let result = catch_unwind(AssertUnwindSafe(|| {
let mut cx = Context::from_waker(Waker::noop());
let _ = view.as_mut().poll_first(&mut cx);
}));
assert!(result.is_err());
assert_eq!(Identity::current(), Identity::ROOT);
}
}