use crate::{
error::Result,
utils::{guard::Guarded, runtime_lock},
Device, Stream,
};
use std::time::Duration;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum EventBackend {
None,
Cpu,
Metal,
Cuda,
}
pub struct Event {
pub(crate) c_event: safemlx_sys::mlx_event,
}
pub struct TimedEvaluation {
completion: Event,
}
impl TimedEvaluation {
pub(crate) fn from_completion(completion: Event) -> Self {
Self { completion }
}
pub fn event(&self) -> &Event {
&self.completion
}
pub fn is_complete(&self) -> Result<bool> {
Ok(self.try_elapsed()?.is_some())
}
pub fn synchronize(&self) -> Result<()> {
self.completion.synchronize()
}
pub fn elapsed(&self) -> Result<Duration> {
let _guard = runtime_lock::enter();
let seconds = f64::try_from_op(|seconds| unsafe {
safemlx_sys::mlx_event_elapsed(seconds, self.completion.c_event)
})?;
duration_from_seconds(seconds)
}
pub fn try_elapsed(&self) -> Result<Option<Duration>> {
let _guard = runtime_lock::enter();
let mut seconds = 0.0;
let mut ready = false;
<() as Guarded>::try_from_op(|_| unsafe {
safemlx_sys::mlx_event_try_elapsed(&mut seconds, &mut ready, self.completion.c_event)
})?;
ready.then(|| duration_from_seconds(seconds)).transpose()
}
}
fn duration_from_seconds(seconds: f64) -> Result<Duration> {
Duration::try_from_secs_f64(seconds).map_err(|_| {
crate::error::Exception::custom(format!(
"backend returned invalid elapsed time: {seconds} seconds"
))
})
}
impl std::fmt::Debug for TimedEvaluation {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("TimedEvaluation")
.field("completion", &self.completion)
.field("elapsed", &self.try_elapsed())
.finish()
}
}
impl Event {
pub fn synchronize(&self) -> Result<()> {
let _guard = runtime_lock::enter();
<() as Guarded>::try_from_op(|_| unsafe {
safemlx_sys::mlx_event_synchronize(self.c_event)
})
}
pub fn is_complete(&self) -> Result<bool> {
let _guard = runtime_lock::enter();
bool::try_from_op(|complete| unsafe {
safemlx_sys::mlx_event_query(complete, self.c_event)
})
}
pub fn wait_on(&self, stream: impl AsRef<Stream>) -> Result<()> {
stream.as_ref().wait_event(self)
}
pub fn device(&self) -> Result<Option<Device>> {
let _guard = runtime_lock::enter();
let has_device = bool::try_from_op(|present| unsafe {
safemlx_sys::mlx_event_has_device(present, self.c_event)
})?;
if has_device {
Device::try_from_op(|device| unsafe {
safemlx_sys::mlx_event_get_device(device, self.c_event)
})
.map(Some)
} else {
Ok(None)
}
}
pub fn backend(&self) -> Result<EventBackend> {
let _guard = runtime_lock::enter();
let raw = u32::try_from_op(|backend| unsafe {
safemlx_sys::mlx_event_get_backend(backend.cast(), self.c_event)
})?;
match raw {
safemlx_sys::mlx_event_backend__MLX_EVENT_BACKEND_NONE => Ok(EventBackend::None),
safemlx_sys::mlx_event_backend__MLX_EVENT_BACKEND_CPU => Ok(EventBackend::Cpu),
safemlx_sys::mlx_event_backend__MLX_EVENT_BACKEND_METAL => Ok(EventBackend::Metal),
safemlx_sys::mlx_event_backend__MLX_EVENT_BACKEND_CUDA => Ok(EventBackend::Cuda),
_ => unreachable!("MLX returned an unknown completion backend"),
}
}
}
impl Drop for Event {
fn drop(&mut self) {
let _guard = runtime_lock::enter();
let status = unsafe { safemlx_sys::mlx_event_free(self.c_event) };
debug_assert_eq!(status, crate::utils::SUCCESS);
}
}
impl std::fmt::Debug for Event {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Event")
.field("backend", &self.backend())
.field("device", &self.device())
.field("complete", &self.is_complete())
.finish()
}
}