aboutsummaryrefslogtreecommitdiff
path: root/helix-event/src/hook.rs
diff options
context:
space:
mode:
authorPascal Kuthe2023-11-30 23:03:26 +0000
committerBlaž Hrastnik2024-01-23 02:20:19 +0000
commit13ed4f6c4748019787d24c2b686d417b71604242 (patch)
tree8263b77ee05a22dfc85410345069efb3668b1877 /helix-event/src/hook.rs
parent7d7ace551cd58f0b6d65af7a6dfa8f896d94724a (diff)
Add hook/event system
Diffstat (limited to 'helix-event/src/hook.rs')
-rw-r--r--helix-event/src/hook.rs91
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 {}