diff options
author | Pascal Kuthe | 2023-11-30 23:03:26 +0000 |
---|---|---|
committer | Blaž Hrastnik | 2024-01-23 02:20:19 +0000 |
commit | 13ed4f6c4748019787d24c2b686d417b71604242 (patch) | |
tree | 8263b77ee05a22dfc85410345069efb3668b1877 /helix-event/src/registry.rs | |
parent | 7d7ace551cd58f0b6d65af7a6dfa8f896d94724a (diff) |
Add hook/event system
Diffstat (limited to 'helix-event/src/registry.rs')
-rw-r--r-- | helix-event/src/registry.rs | 131 |
1 files changed, 131 insertions, 0 deletions
diff --git a/helix-event/src/registry.rs b/helix-event/src/registry.rs new file mode 100644 index 00000000..d43c48ac --- /dev/null +++ b/helix-event/src/registry.rs @@ -0,0 +1,131 @@ +//! A global registry where events are registered and can be +//! subscribed to by registering hooks. The registry identifies event +//! types using their type name so multiple event with the same type name +//! may not be registered (will cause a panic to ensure soundness) + +use std::any::TypeId; + +use anyhow::{bail, Result}; +use hashbrown::hash_map::Entry; +use hashbrown::HashMap; +use parking_lot::RwLock; + +use crate::hook::ErasedHook; +use crate::runtime_local; + +pub struct Registry { + events: HashMap<&'static str, TypeId, ahash::RandomState>, + handlers: HashMap<&'static str, Vec<ErasedHook>, ahash::RandomState>, +} + +impl Registry { + pub fn register_event<E: Event + 'static>(&mut self) { + let ty = TypeId::of::<E>(); + assert_eq!(ty, TypeId::of::<E::Static>()); + match self.events.entry(E::ID) { + Entry::Occupied(entry) => { + if entry.get() == &ty { + // don't warn during tests to avoid log spam + #[cfg(not(feature = "integration_test"))] + panic!("Event {} was registered multiple times", E::ID); + } else { + panic!("Multiple events with ID {} were registered", E::ID); + } + } + Entry::Vacant(ent) => { + ent.insert(ty); + self.handlers.insert(E::ID, Vec::new()); + } + } + } + + /// # Safety + /// + /// `hook` must be totally generic over all lifetime parameters of `E`. For + /// example if `E` was a known type `Foo<'a, 'b> then the correct trait bound + /// would be `F: for<'a, 'b, 'c> Fn(&'a mut Foo<'b, 'c>)` but there is no way to + /// express that kind of constraint for a generic type with the rust type system + /// right now. + pub unsafe fn register_hook<E: Event>( + &mut self, + hook: impl Fn(&mut E) -> Result<()> + 'static + Send + Sync, + ) { + // ensure event type ids match so we can rely on them always matching + let id = E::ID; + let Some(&event_id) = self.events.get(id) else { + panic!("Tried to register handler for unknown event {id}"); + }; + assert!( + TypeId::of::<E::Static>() == event_id, + "Tried to register invalid hook for event {id}" + ); + let hook = ErasedHook::new(hook); + self.handlers.get_mut(id).unwrap().push(hook); + } + + pub fn register_dynamic_hook( + &mut self, + hook: impl Fn() -> Result<()> + 'static + Send + Sync, + id: &str, + ) -> Result<()> { + // ensure event type ids match so we can rely on them always matching + if self.events.get(id).is_none() { + bail!("Tried to register handler for unknown event {id}"); + }; + let hook = ErasedHook::new_dynamic(hook); + self.handlers.get_mut(id).unwrap().push(hook); + Ok(()) + } + + pub fn dispatch<E: Event>(&self, mut event: E) { + let Some(hooks) = self.handlers.get(E::ID) else { + log::error!("Dispatched unknown event {}", E::ID); + return; + }; + let event_id = self.events[E::ID]; + + assert_eq!( + TypeId::of::<E::Static>(), + event_id, + "Tried to dispatch invalid event {}", + E::ID + ); + + for hook in hooks { + // safety: event type is the same + if let Err(err) = unsafe { hook.call(&mut event) } { + log::error!("{} hook failed: {err:#?}", E::ID); + crate::status::report_blocking(err); + } + } + } +} + +runtime_local! { + static REGISTRY: RwLock<Registry> = RwLock::new(Registry { + // hardcoded random number is good enough here we don't care about DOS resistance + // and avoids the additional complexity of `Option<Registry>` + events: HashMap::with_hasher(ahash::RandomState::with_seeds(423, 9978, 38322, 3280080)), + handlers: HashMap::with_hasher(ahash::RandomState::with_seeds(423, 99078, 382322, 3282938)), + }); +} + +pub(crate) fn with<T>(f: impl FnOnce(&Registry) -> T) -> T { + f(®ISTRY.read()) +} + +pub(crate) fn with_mut<T>(f: impl FnOnce(&mut Registry) -> T) -> T { + f(&mut REGISTRY.write()) +} + +/// # Safety +/// The number of specified lifetimes and the static type *must* be correct. +/// This is ensured automatically by the [`events`](crate::events) +/// macro. +pub unsafe trait Event: Sized { + /// Globally unique (case sensitive) string that identifies this type. + /// A good candidate is the events type name + const ID: &'static str; + const LIFETIMES: usize; + type Static: Event + 'static; +} |