use std::{collections::HashMap, fmt, sync::Arc};
use log::error;
use tokio::sync::{Mutex, RwLock};
use uuid::Uuid;
use crate as pipeless;
#[derive(Clone,Copy,PartialEq)]
pub enum HookType {
PreProcess,
Process,
PostProcess,
}
impl fmt::Display for HookType {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", match self {
HookType::PreProcess => "pre_process",
HookType::Process => "process",
HookType::PostProcess => "post_process",
})
}
}
pub trait HookTrait: Send + Sync {
fn exec_hook(
&self,
frame: pipeless::data::Frame,
stage_context: &pipeless::stages::stage::Context
) -> Option<pipeless::data::Frame>;
}
#[derive(Clone)]
pub struct StatelessHook {
h_type: HookType,
h_body: Arc<dyn HookTrait>,
}
impl StatelessHook {
fn get_hook_type(&self) -> HookType {
self.h_type
}
fn get_hook_body(&self) -> Arc<dyn HookTrait> {
self.h_body.clone()
}
}
#[derive(Clone)]
pub struct StatefulHook {
h_type: HookType,
h_body: Arc<Mutex<dyn HookTrait>>,
last_frame_map: Arc<RwLock<HashMap<Uuid, u64>>>,
}
impl StatefulHook {
fn get_hook_type(&self) -> HookType {
self.h_type
}
fn get_hook_body(&self) -> Arc<Mutex<dyn HookTrait>> {
self.h_body.clone()
}
async fn last_processed_frame_number(&self, pipeline_id: &Uuid) -> u64 {
let read_guard = self.last_frame_map.read().await;
match read_guard.get(pipeline_id) {
Some(n) => *n,
None => 0,
}
}
async fn increment_last_processed(&self, pipeline_id: &Uuid) {
let mut write_guard = self.last_frame_map.write().await;
let last = match write_guard.get(pipeline_id) {
Some(n) => *n,
None => 0,
};
write_guard.insert(*pipeline_id, last + 1);
}
}
#[derive(Clone)] pub enum Hook {
StatelessHook(StatelessHook),
StatefulHook(StatefulHook),
}
unsafe impl std::marker::Sync for Hook {}
unsafe impl std::marker::Send for Hook {}
impl Hook {
pub fn new_stateless(hook_type: HookType, hook_body: Arc<dyn HookTrait>) -> Self {
let hook = StatelessHook {
h_type: hook_type,
h_body: hook_body,
};
Self::StatelessHook(hook)
}
pub fn new_stateful(hook_type: HookType, hook_body: Arc<Mutex<dyn HookTrait>>) -> Self {
let hook = StatefulHook {
h_type: hook_type,
h_body: hook_body,
last_frame_map: Arc::new(RwLock::new(HashMap::new())),
};
Self::StatefulHook(hook)
}
pub async fn exec_hook(
&self,
frame: pipeless::data::Frame,
stage_context: Arc<pipeless::stages::stage::Context>,
) -> std::option::Option<pipeless::data::Frame> {
match self {
Hook::StatelessHook(hook) => {
let (send, recv) = tokio::sync::oneshot::channel();
rayon::spawn({
let stage_context = stage_context.clone();
let hook = hook.clone();
move || {
let f = hook.get_hook_body().exec_hook(frame, &stage_context);
let _ = send.send(f);
}
});
let worker_res = recv.await;
match worker_res {
Ok(f) => {
return f;
},
Err(err) => {
error!("Error pulling results from rayon worker: {}", err);
return None;
}
}
},
Hook::StatefulHook(hook) => {
let frame_pipeline_id = frame.get_pipeline_id().clone();
{
let prev_frame_number = frame.get_frame_number() - 1;
let mut last_processed = hook.last_processed_frame_number(&frame_pipeline_id).await;
while last_processed != prev_frame_number {
last_processed = hook.last_processed_frame_number(&frame_pipeline_id).await;
tokio::task::yield_now().await;
}
}
let worker_res = tokio::task::spawn_blocking({
let stage_context = stage_context.clone();
let hook = hook.clone();
move || async move{
let h_body = hook.get_hook_body();
let locked_hook = h_body.lock().await;
let res = locked_hook.exec_hook(frame, &stage_context);
hook.increment_last_processed(&frame_pipeline_id).await;
res
}
}).await;
match worker_res {
Ok(f) => f.await,
Err(err) => {
error!("Error getting result from the tokio worker: {}", err);
None
}
}
},
}
}
pub fn get_hook_type(&self) -> HookType {
match self {
Hook::StatelessHook(hook) => {
hook.get_hook_type()
},
Hook::StatefulHook(hook) => {
hook.get_hook_type()
},
}
}
}