use std::{marker, mem, num, pin};
use dope_sys::{
io::{
reactor::{
Full,
submit::{self, Queue as _, Ready as _},
},
socket::option::tuning,
},
table::token::{Codec, Context, Tuning as _},
};
use o3::{alloc, base::num::bounded};
use crate::reactor::{cq, op::controls, sq};
pub(crate) mod raw;
#[derive(Debug)]
pub struct State<E, const N: usize> {
target: Option<E>,
values: [libc::c_int; N],
status: Status,
connect: mem::MaybeUninit<sq::raw::Deferred>,
next: Option<sq::Fixed>,
_pin: marker::PhantomPinned,
}
#[derive(Debug)]
struct Status {
retiring: bool,
stopped: bool,
phase: Phase,
count: num::NonZero<u32>,
error: Option<cq::Outcome>,
}
impl<E, const N: usize> Default for State<E, N> {
fn default() -> Self {
Self {
target: None,
values: [0; N],
status: Status {
retiring: false,
stopped: false,
phase: Phase::Idle,
count: num::NonZero::<u32>::MIN,
error: None,
},
connect: mem::MaybeUninit::uninit(),
next: None,
_pin: marker::PhantomPinned,
}
}
}
#[derive(Debug)]
pub struct Table<E, const N: usize> {
states: pin::Pin<Box<[State<E, N>]>>,
ready: Option<sq::Fixed>,
tail: Option<sq::Fixed>,
}
#[derive(Clone, Copy, Debug)]
enum Phase {
Idle,
Options,
OptionsConnect,
Ready,
Connecting,
Complete,
}
mod progress;
pub(crate) use progress::Progress;
pub struct Occupied<'a, E, const N: usize> {
state: pin::Pin<&'a mut State<E, N>>,
target: E,
}
pub(crate) enum Retirement<'a, E, const N: usize> {
Ready,
Pending,
Cancel(self::Cancelling<'a, E, N>),
}
pub(crate) struct Cancelling<'a, E, const N: usize> {
state: pin::Pin<&'a mut State<E, N>>,
slot: sq::Fixed,
}
impl<E: Copy, const N: usize> Table<E, N> {
pub(crate) fn retirement(&mut self, slot: sq::Fixed) -> Retirement<'_, E, N> {
use tuning::Table as _;
let Some(state) = self.get(slot.index()) else {
return Retirement::Ready;
};
if state.target.is_none() {
return Retirement::Ready;
}
if state.status.retiring {
return Retirement::Pending;
}
Retirement::Cancel(Cancelling { state, slot })
}
}
impl<E, const N: usize> Cancelling<'_, E, N> {
pub(crate) fn try_submit<T: Codec>(self, queue: sq::raw::Queue<'_, T>) -> Result<(), Full> {
let vacancy = queue.try_one()?;
let index =
<T::Context as Context>::Slot::from(bounded::U32::clamp(self.slot.index() as usize));
let target = T::Tuning::new(index);
let control = cancel::<T>(target);
submit::raw::Vacancy::prepare(vacancy, &control).push();
let state = unsafe { self.state.get_unchecked_mut() };
state.status.retiring = true;
state.status.stopped = true;
Ok(())
}
}
impl<E, const N: usize> Table<E, N> {
unsafe fn admitted(&mut self, slot: sq::Fixed) -> pin::Pin<&mut State<E, N>> {
unsafe {
self.states
.as_mut()
.map_unchecked_mut(|states| states.get_unchecked_mut(slot.index() as usize))
}
}
}
pub(in crate::driver) fn cancel<T: Codec>(target: T::Tuning) -> sq::Owned<T> {
controls::cancel(T::tuning(target), true)
.flags(sq::Flags::SKIP_SUCCESS)
.user_data(T::cancel_tune(target))
}
impl<E, const N: usize> tuning::Create for Table<E, N> {
fn try_new(capacity: num::NonZero<u32>) -> Result<Self, alloc::Error> {
use alloc::BoxSliceExt as _;
const {
assert!(mem::align_of::<Self>() == mem::align_of::<pin::Pin<Box<[State<E, N>]>>>());
};
let states = Box::<[State<E, N>]>::try_default(capacity.get() as usize)?;
Ok(Self {
states: Box::into_pin(states),
ready: None,
tail: None,
})
}
}
impl<E: Copy, const N: usize> tuning::Table<E, N> for Table<E, N> {
type State = State<E, N>;
type Occupied<'a>
= Occupied<'a, E, N>
where
Self: 'a;
fn bind(&mut self, index: u32) -> Result<Self::Occupied<'_>, tuning::Missing> {
let state = self.get(index).ok_or(tuning::Missing::Slot)?;
tuning::raw::State::bind(state).ok_or(tuning::Missing::Vacant)
}
fn get(&mut self, index: u32) -> Option<pin::Pin<&mut Self::State>> {
if index as usize >= self.states.len() {
return None;
}
Some(unsafe {
self.states
.as_mut()
.map_unchecked_mut(|states| states.get_unchecked_mut(index as usize))
})
}
fn retry_cancel(&mut self, index: u32) {
let Some(state) = self.get(index) else {
return;
};
unsafe { state.get_unchecked_mut() }.status.retiring = false;
}
}
impl<E, const N: usize> tuning::Quiesce for Table<E, N> {
fn is_quiescent(&self) -> bool {
self.states.iter().all(|state| state.target.is_none())
}
}
impl<E: Copy, const N: usize> tuning::Occupied<E> for Occupied<'_, E, N> {
fn target(&self) -> E {
self.target
}
fn clear(self) {
let state = unsafe { self.state.get_unchecked_mut() };
state.target = None;
state.status.retiring = false;
state.status.stopped = false;
state.status.phase = Phase::Idle;
}
}
const _: () = {
assert!(std::mem::size_of::<Table<u64, 7>>() == 3 * std::mem::size_of::<usize>());
assert!(mem::size_of::<Status>() == 16);
assert!(mem::size_of::<State<num::NonZero<u64>, 7>>() == 120);
};
impl<E, const N: usize> State<E, N> {
unsafe fn begin(
self: pin::Pin<&mut Self>,
target: E,
count: num::NonZero<u32>,
terminal: Option<sq::raw::Deferred>,
) -> &mut [libc::c_int; N] {
let state = unsafe { self.get_unchecked_mut() };
state.target = Some(target);
state.status.count = count;
state.status.error = None;
state.status.phase = match terminal {
Some(entry) => {
state.connect.write(entry);
Phase::OptionsConnect
}
None => Phase::Options,
};
state.status.stopped = false;
state.status.retiring = false;
state.next = None;
&mut state.values
}
}