use std::future::{Future, IntoFuture};
use std::pin::Pin;
use std::time::Duration;
use super::{
ControllerError, ControllerSpec,
engine::{Controller, ControllerHandle},
};
use crate::{
TaskId, TaskWaiter,
core::{OwnershipTimed, Unwatched, Waiting, Watched},
};
#[must_use = "await the default submission, call and await `execute`, or call `try_intake`"]
pub struct Submit<'a, Watch = Unwatched, Admission = Waiting> {
request: SubmitRequest<'a>,
watch: Watch,
_admission: Admission,
}
enum SubmitRequest<'a> {
Direct {
controller: Option<&'a Controller>,
spec: ControllerSpec,
},
Prepared {
controller: ControllerHandle,
id: TaskId,
spec: ControllerSpec,
},
}
impl<'a> Submit<'a> {
#[inline]
pub(crate) fn direct(controller: Option<&'a Controller>, spec: ControllerSpec) -> Self {
Self {
request: SubmitRequest::Direct { controller, spec },
watch: Unwatched,
_admission: Waiting,
}
}
}
impl Submit<'static> {
#[inline]
fn prepared(controller: ControllerHandle, id: TaskId, spec: ControllerSpec) -> Self {
Self {
request: SubmitRequest::Prepared {
controller,
id,
spec,
},
watch: Unwatched,
_admission: Waiting,
}
}
}
impl SubmitRequest<'_> {
#[inline(always)]
fn into_parts(self) -> Result<(ControllerHandle, TaskId, ControllerSpec), ControllerError> {
match self {
Self::Direct {
controller: Some(controller),
spec,
} => Ok((controller.handle(), TaskId::next(), spec)),
Self::Direct {
controller: None, ..
} => Err(ControllerError::NotConfigured),
Self::Prepared {
controller,
id,
spec,
} => Ok((controller, id, spec)),
}
}
#[inline(always)]
fn try_submit(self) -> Result<TaskId, ControllerError> {
match self {
Self::Direct {
controller: Some(controller),
spec,
} => try_submit_direct(controller, spec),
Self::Direct {
controller: None, ..
} => Err(ControllerError::NotConfigured),
Self::Prepared {
controller,
id,
spec,
} => controller.try_submit_prepared(id, spec),
}
}
#[inline(always)]
fn try_submit_and_watch(
self,
) -> Result<(TaskId, tokio::sync::oneshot::Receiver<crate::TaskOutcome>), ControllerError> {
match self {
Self::Direct {
controller: Some(controller),
spec,
} => try_submit_direct_and_watch(controller, spec),
Self::Direct {
controller: None, ..
} => Err(ControllerError::NotConfigured),
Self::Prepared {
controller,
id,
spec,
} => controller.try_submit_prepared_and_watch(id, spec),
}
}
}
fn try_submit_direct(
controller: &Controller,
spec: ControllerSpec,
) -> Result<TaskId, ControllerError> {
controller
.handle()
.try_submit_prepared(TaskId::next(), spec)
}
fn try_submit_direct_and_watch(
controller: &Controller,
spec: ControllerSpec,
) -> Result<(TaskId, tokio::sync::oneshot::Receiver<crate::TaskOutcome>), ControllerError> {
controller
.handle()
.try_submit_prepared_and_watch(TaskId::next(), spec)
}
impl<'a, Admission> Submit<'a, Unwatched, Admission> {
#[must_use = "configure or execute the watched submission"]
#[inline]
pub fn watch(self) -> Submit<'a, Watched, Admission> {
Submit {
request: self.request,
watch: Watched,
_admission: self._admission,
}
}
}
impl<'a, Watch> Submit<'a, Watch, Waiting> {
#[inline]
pub fn ownership_timeout(self, wait_for: Duration) -> Submit<'a, Watch, OwnershipTimed> {
Submit {
request: self.request,
watch: self.watch,
_admission: OwnershipTimed(wait_for),
}
}
}
impl Submit<'_, Unwatched, Waiting> {
#[inline]
pub async fn execute(self) -> Result<TaskId, ControllerError> {
let (controller, id, spec) = self.request.into_parts()?;
controller.submit_prepared(id, spec).await
}
#[inline(always)]
pub fn try_intake(self) -> Result<TaskId, ControllerError> {
self.request.try_submit()
}
}
impl<'a> IntoFuture for Submit<'a, Unwatched, Waiting> {
type Output = Result<TaskId, ControllerError>;
type IntoFuture = Pin<Box<dyn Future<Output = Self::Output> + Send + 'a>>;
#[inline]
fn into_future(self) -> Self::IntoFuture {
Box::pin(self.execute())
}
}
impl Submit<'_, Unwatched, OwnershipTimed> {
#[inline]
pub async fn execute(self) -> Result<TaskId, ControllerError> {
let wait_for = self._admission.0;
let (controller, id, spec) = self.request.into_parts()?;
controller
.submit_prepared_with_ownership_timeout(id, spec, wait_for)
.await
}
}
impl Submit<'_, Watched, Waiting> {
#[inline]
pub async fn execute(self) -> Result<TaskWaiter, ControllerError> {
let (controller, id, spec) = self.request.into_parts()?;
let (id, receiver) = controller.submit_prepared_and_watch(id, spec).await?;
Ok(TaskWaiter::new(id, receiver))
}
#[inline(always)]
pub fn try_intake(self) -> Result<TaskWaiter, ControllerError> {
let (id, receiver) = self.request.try_submit_and_watch()?;
Ok(TaskWaiter::new(id, receiver))
}
}
impl Submit<'_, Watched, OwnershipTimed> {
#[inline]
pub async fn execute(self) -> Result<TaskWaiter, ControllerError> {
let wait_for = self._admission.0;
let (controller, id, spec) = self.request.into_parts()?;
let (id, receiver) = controller
.submit_prepared_and_watch_with_ownership_timeout(id, spec, wait_for)
.await?;
Ok(TaskWaiter::new(id, receiver))
}
}
impl<Watch, Admission> std::fmt::Debug for Submit<'_, Watch, Admission> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let mut debug = f.debug_struct("Submit");
match &self.request {
SubmitRequest::Direct { controller, spec } => debug
.field("controller_configured", &controller.is_some())
.field("spec", spec),
SubmitRequest::Prepared { id, spec, .. } => debug.field("id", id).field("spec", spec),
};
debug.finish_non_exhaustive()
}
}
#[must_use = "call submit, then await, execute, or try the resulting operation"]
pub struct PreparedSubmission {
controller: ControllerHandle,
id: TaskId,
spec: ControllerSpec,
}
impl PreparedSubmission {
pub(crate) fn new(controller: ControllerHandle, spec: ControllerSpec) -> Self {
Self {
controller,
id: TaskId::next(),
spec,
}
}
#[must_use]
pub fn id(&self) -> TaskId {
self.id
}
#[must_use = "use the prepared controller specification"]
pub fn spec(&self) -> &ControllerSpec {
&self.spec
}
#[must_use = "await, execute, or try the prepared submission operation"]
#[inline]
pub fn submit(self) -> Submit<'static> {
let Self {
controller,
id,
spec,
} = self;
Submit::prepared(controller, id, spec)
}
}
impl std::fmt::Debug for PreparedSubmission {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("PreparedSubmission")
.field("id", &self.id)
.field("spec", &self.spec)
.finish_non_exhaustive()
}
}