use core::ffi::c_void;
use std::sync::{Arc, Mutex};
use crate::abi::{InventoryOpResult, InventoryResult};
use crate::engine_enum::Values;
use crate::engine_ids as ids;
use crate::locks::Relock;
use super::ops::BatchError;
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum Done {
Committed,
RolledBack,
Unavailable,
TimedOut,
Refused(BatchError),
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum OpError {
NotRun,
InvalidState,
OutOfMemory,
Other(u32),
}
impl OpError {
pub(super) const IDS: [(Self, &'static str); 2] = [
(Self::InvalidState, ids::INVENTORY_ERROR_INVALID_STATE),
(Self::OutOfMemory, ids::INVENTORY_ERROR_OUT_OF_MEMORY),
];
}
pub(super) type OpErrors = Values<OpError, 2>;
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
pub struct OpResult {
pub error: Option<OpError>,
pub remaining: u32,
}
impl OpResult {
#[must_use]
pub const fn ok(self) -> bool {
self.error.is_none()
}
fn from_raw(raw: InventoryOpResult, errors: &OpErrors) -> Self {
let error = match raw.error {
0 => None,
InventoryOpResult::NOT_RUN => Some(OpError::NotRun),
code => Some(
errors
.variant(u64::from(code))
.unwrap_or(OpError::Other(code)),
),
};
Self {
error,
remaining: raw.remaining,
}
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Outcome {
pub done: Done,
pub ops: Vec<OpResult>,
}
impl Outcome {
#[must_use]
pub const fn is_committed(&self) -> bool {
matches!(self.done, Done::Committed)
}
pub(super) const fn not_run(done: Done) -> Self {
Self {
done,
ops: Vec::new(),
}
}
pub(super) unsafe fn from_raw(result: &InventoryResult, errors: &OpErrors) -> Self {
let done = match result.outcome {
InventoryResult::COMMITTED => Done::Committed,
InventoryResult::ROLLED_BACK => Done::RolledBack,
InventoryResult::TIMED_OUT => Done::TimedOut,
_ => Done::Unavailable,
};
let ops = if result.ops.is_null() || result.op_count == 0 {
&[][..]
} else {
unsafe { core::slice::from_raw_parts(result.ops, result.op_count as usize) }
};
Self {
done,
ops: ops
.iter()
.map(|&raw| OpResult::from_raw(raw, errors))
.collect(),
}
}
}
#[derive(Clone, Debug, Default)]
pub struct Ticket(Arc<Mutex<Option<Outcome>>>);
impl Ticket {
#[must_use]
pub fn outcome(&self) -> Option<Outcome> {
self.0.relock().clone()
}
#[must_use]
pub fn is_done(&self) -> bool {
self.0.relock().is_some()
}
}
pub(super) type Callback = Box<dyn FnOnce(&Outcome) + Send>;
pub(super) struct Pending {
pub(super) ticket: Ticket,
pub(super) done: Callback,
pub(super) errors: OpErrors,
}
impl Pending {
pub(super) fn complete(self, outcome: &Outcome) {
*self.ticket.0.relock() = Some(outcome.clone());
(self.done)(outcome);
}
}
pub(super) unsafe extern "system" fn deliver(result: *const InventoryResult, user: *mut c_void) {
if user.is_null() {
return;
}
let pending = unsafe { Box::from_raw(user.cast::<Pending>()) };
let outcome = unsafe { crate::abi::read_headed(result, size_of::<InventoryResult>()) }
.map_or_else(
|| Outcome::not_run(Done::Unavailable),
|r| unsafe { Outcome::from_raw(&r, &pending.errors) },
);
crate::host::guard(
"panic in an inventory callback",
|| (),
|| {
pending.complete(&outcome);
},
);
}