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/hook.rs | |
parent | 7d7ace551cd58f0b6d65af7a6dfa8f896d94724a (diff) |
Add hook/event system
Diffstat (limited to 'helix-event/src/hook.rs')
-rw-r--r-- | helix-event/src/hook.rs | 91 |
1 files changed, 91 insertions, 0 deletions
diff --git a/helix-event/src/hook.rs b/helix-event/src/hook.rs new file mode 100644 index 00000000..7fb68148 --- /dev/null +++ b/helix-event/src/hook.rs @@ -0,0 +1,91 @@ +//! rust dynamic dispatch is extremely limited so we have to build our +//! own vtable implementation. Otherwise implementing the event system would not be possible. +//! A nice bonus of this approach is that we can optimize the vtable a bit more. Normally +//! a dyn Trait fat pointer contains two pointers: A pointer to the data itself and a +//! pointer to a global (static) vtable entry which itself contains multiple other pointers +//! (the various functions of the trait, drop, size and align). That makes dynamic +//! dispatch pretty slow (double pointer indirections). However, we only have a single function +//! in the hook trait and don't need a drop implementation (event system is global anyway +//! and never dropped) so we can just store the entire vtable inline. + +use anyhow::Result; +use std::ptr::{self, NonNull}; + +use crate::Event; + +/// Opaque handle type that represents an erased type parameter. +/// +/// If extern types were stable, this could be implemented as `extern { pub type Opaque; }` but +/// until then we can use this. +/// +/// Care should be taken that we don't use a concrete instance of this. It should only be used +/// through a reference, so we can maintain something else's lifetime. +struct Opaque(()); + +pub(crate) struct ErasedHook { + data: NonNull<Opaque>, + call: unsafe fn(NonNull<Opaque>, NonNull<Opaque>, NonNull<Opaque>), +} + +impl ErasedHook { + pub(crate) fn new_dynamic<H: Fn() -> Result<()> + 'static + Send + Sync>( + hook: H, + ) -> ErasedHook { + unsafe fn call<F: Fn() -> Result<()> + 'static + Send + Sync>( + hook: NonNull<Opaque>, + _event: NonNull<Opaque>, + result: NonNull<Opaque>, + ) { + let hook: NonNull<F> = hook.cast(); + let result: NonNull<Result<()>> = result.cast(); + let hook: &F = hook.as_ref(); + let res = hook(); + ptr::write(result.as_ptr(), res) + } + + unsafe { + ErasedHook { + data: NonNull::new_unchecked(Box::into_raw(Box::new(hook)) as *mut Opaque), + call: call::<H>, + } + } + } + + pub(crate) fn new<E: Event, F: Fn(&mut E) -> Result<()>>(hook: F) -> ErasedHook { + unsafe fn call<E: Event, F: Fn(&mut E) -> Result<()>>( + hook: NonNull<Opaque>, + event: NonNull<Opaque>, + result: NonNull<Opaque>, + ) { + let hook: NonNull<F> = hook.cast(); + let mut event: NonNull<E> = event.cast(); + let result: NonNull<Result<()>> = result.cast(); + let hook: &F = hook.as_ref(); + let res = hook(event.as_mut()); + ptr::write(result.as_ptr(), res) + } + + unsafe { + ErasedHook { + data: NonNull::new_unchecked(Box::into_raw(Box::new(hook)) as *mut Opaque), + call: call::<E, F>, + } + } + } + + pub(crate) unsafe fn call<E: Event>(&self, event: &mut E) -> Result<()> { + let mut res = Ok(()); + + unsafe { + (self.call)( + self.data, + NonNull::from(event).cast(), + NonNull::from(&mut res).cast(), + ); + } + res + } +} + +unsafe impl Sync for ErasedHook {} +unsafe impl Send for ErasedHook {} |