use super::listener::{ErasedAroundListener, ErasedListener, ListenerOptions};
use super::types::{
DispatchError, Event, EventOperation, ListenerRegistrationId, ListenerRole, Routing,
};
use crate::context::{Context, ScopeNode};
use crate::gated::PublishStep;
use parking_lot::Mutex;
use std::any::TypeId;
use std::collections::HashMap;
use std::sync::Arc;
#[derive(Debug, thiserror::Error)]
#[error("event contract mismatch")]
pub(crate) struct EventContractMismatch;
#[derive(Clone, Copy, PartialEq, Eq)]
struct EventContract {
args: TypeId,
output: TypeId,
}
impl EventContract {
fn of<E: Event>() -> Self {
Self {
args: TypeId::of::<E::Args>(),
output: TypeId::of::<E::Output>(),
}
}
}
#[derive(Clone)]
#[doc(hidden)]
pub enum HookKind {
Plain {
role: ListenerRole,
callback: ErasedListener,
},
Around(ErasedAroundListener),
}
impl HookKind {
pub(crate) fn role(&self) -> ListenerRole {
match self {
Self::Plain { role, .. } => *role,
Self::Around(_) => ListenerRole::Around,
}
}
}
pub(crate) struct Hook {
pub(crate) id: ListenerRegistrationId,
prepend: bool,
global: bool,
pub(crate) scope: Option<Arc<ScopeNode>>,
once: bool,
options: ListenerOptions,
pub(crate) kind: HookKind,
}
impl Hook {
pub(crate) fn new(
ctx: &Context,
kind: HookKind,
options: ListenerOptions,
id: ListenerRegistrationId,
) -> Self {
Self {
id,
prepend: options.is_prepend(),
global: options.is_global(),
scope: ctx.scope.clone(),
once: options.is_once(),
options,
kind,
}
}
fn snap(&self) -> HookSnap {
HookSnap {
id: self.id.clone(),
scope: self.scope.clone(),
options: self.options,
kind: self.kind.clone(),
}
}
}
pub(crate) struct HookSnap {
pub(crate) id: ListenerRegistrationId,
pub(crate) scope: Option<Arc<ScopeNode>>,
pub(crate) options: ListenerOptions,
pub(crate) kind: HookKind,
}
#[derive(Default)]
struct EventState {
contracts: HashMap<&'static str, EventContract>,
hooks: HashMap<&'static str, Vec<Hook>>,
}
#[derive(Default)]
pub(crate) struct EventStore {
state: Mutex<EventState>,
}
impl EventStore {
pub(crate) fn new() -> Self {
Self::default()
}
fn bind_locked<E: Event>(state: &mut EventState) -> Result<(), ()> {
let contract = EventContract::of::<E>();
match state.contracts.get(E::NAME) {
Some(bound) if *bound != contract => Err(()),
Some(_) => Ok(()),
None => {
state.contracts.insert(E::NAME, contract);
Ok(())
}
}
}
fn add_reserved<E: Event>(&self, hook: Hook) -> Result<(), EventContractMismatch> {
let mut state = self.state.lock();
Self::bind_locked::<E>(&mut state).map_err(|()| EventContractMismatch)?;
let prepend = hook.prepend;
let list = state.hooks.entry(E::NAME).or_default();
if prepend {
list.insert(0, hook);
} else {
list.push(hook);
}
Ok(())
}
pub(crate) fn remove(&self, name: &str, id: &ListenerRegistrationId) -> bool {
let removed = {
let mut state = self.state.lock();
let Some(list) = state.hooks.get_mut(name) else {
return false;
};
let Some(at) = list.iter().position(|hook| hook.id == *id) else {
return false;
};
let removed = list.remove(at);
if list.is_empty() {
state.hooks.remove(name);
}
removed
};
drop(removed);
true
}
pub(crate) fn claim(&self, name: &str, id: &ListenerRegistrationId) -> (bool, bool) {
let claimed = {
let mut state = self.state.lock();
let Some(list) = state.hooks.get_mut(name) else {
return (false, false);
};
let Some(at) = list.iter().position(|hook| hook.id == *id) else {
return (false, false);
};
if !list[at].once {
return (true, false);
}
let claimed = list.remove(at);
if list.is_empty() {
state.hooks.remove(name);
}
Some(claimed)
};
drop(claimed);
(true, true)
}
pub(crate) fn preflight<E: Event>(
&self,
routing: &Routing,
operation: EventOperation,
) -> Result<Vec<HookSnap>, DispatchError> {
let mut state = self.state.lock();
Self::bind_locked::<E>(&mut state)
.map_err(|()| DispatchError::EventContractMismatch { event: E::NAME })?;
let Some(list) = state.hooks.get(E::NAME) else {
return Ok(Vec::new());
};
let eligible = |hook: &Hook| match routing {
Routing::Unscoped => true,
Routing::Scoped(scope) => {
hook.global
|| match (&hook.scope, &scope.layer) {
(None, _) => true,
(Some(_), None) => false,
(Some(registration), Some(dispatch)) => dispatch.reaches(registration),
}
}
};
let compatible = |role| match operation {
EventOperation::Emit | EventOperation::EmitParallel | EventOperation::Query => {
matches!(role, ListenerRole::Observer | ListenerRole::Responder)
}
EventOperation::Waterfall => {
matches!(role, ListenerRole::Mapper | ListenerRole::Around)
}
};
if let Some(role) = list
.iter()
.filter(|hook| eligible(hook))
.map(|hook| hook.kind.role())
.find(|role| !compatible(*role))
{
return Err(DispatchError::IncompatibleRole { operation, role });
}
Ok(list
.iter()
.filter(|hook| eligible(hook))
.map(Hook::snap)
.collect())
}
}
pub(crate) struct HookPublish<E: Event> {
pub(crate) store: Arc<EventStore>,
pub(crate) hook: Option<Hook>,
pub(crate) _event: std::marker::PhantomData<fn(E) -> E>,
pub(crate) contract_mismatch: bool,
}
impl<E: Event> PublishStep for HookPublish<E> {
fn publish(&mut self) -> std::result::Result<(), crate::gated::PublishRefused> {
let hook = self.hook.take().expect("HookPublish runs at most once");
match self.store.add_reserved::<E>(hook) {
Ok(()) => Ok(()),
Err(_) => {
self.contract_mismatch = true;
Err(crate::gated::PublishRefused)
}
}
}
}