From 3e871ccc8df54063d86e0da094b178657201faea Mon Sep 17 00:00:00 2001
From: SoniEx2 <endermoneymod@gmail.com>
Date: Mon, 3 Dec 2018 11:03:54 -0200
Subject: [PATCH] Add multiple ty to post_event!.

---
 Cargo.toml                                    |   6 +-
 compiletest/.gitignore                        |   2 +
 compiletest/Cargo.toml                        |   9 ++
 compiletest/README.txt                        |   3 +
 .../compile-fail/hygiene/cancellable.rs       |  17 +++
 .../compile-fail/hygiene/event_id.rs          |   3 +-
 compiletest/src/lib.rs                        |  23 ++++
 src/lib.rs                                    | 121 ++++++++++++++----
 tests/basic_usage.rs                          |   6 +-
 tests/compile-fail/README                     |   1 -
 tests/dyn_events.rs                           |   6 +-
 tests/useful_testing.rs                       |  45 +++++++
 12 files changed, 209 insertions(+), 33 deletions(-)
 create mode 100644 compiletest/.gitignore
 create mode 100644 compiletest/Cargo.toml
 create mode 100644 compiletest/README.txt
 create mode 100644 compiletest/compile-fail/hygiene/cancellable.rs
 rename tests/compile-fail/hygiene.rs => compiletest/compile-fail/hygiene/event_id.rs (76%)
 create mode 100644 compiletest/src/lib.rs
 delete mode 100644 tests/compile-fail/README
 create mode 100644 tests/useful_testing.rs

diff --git a/Cargo.toml b/Cargo.toml
index e0a0ff7..0a3a329 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -1,6 +1,6 @@
 [package]
 name = "eventbus"
-version = "0.4.0"
+version = "0.5.0"
 authors = ["SoniEx2 <endermoneymod@gmail.com>"]
 description = "Safe, fast and concurrent event system, inspired by the MinecraftForge event bus."
 keywords = ["event", "safe", "fast", "concurrent", "bus"]
@@ -10,10 +10,14 @@ repository = "https://cybre.tech/SoniEx2/rust.eventbus"
 [dependencies]
 lazy_static = "1.1.0"
 anymap = "0.12.1"
+itertools = "0.7.11"
 
 [dev-dependencies]
 criterion = "0.2.5"
 
+[workspace]
+members = ["compiletest"]
+
 [[bench]]
 name = "post_single_benchmark"
 harness = false
diff --git a/compiletest/.gitignore b/compiletest/.gitignore
new file mode 100644
index 0000000..96ef6c0
--- /dev/null
+++ b/compiletest/.gitignore
@@ -0,0 +1,2 @@
+/target
+Cargo.lock
diff --git a/compiletest/Cargo.toml b/compiletest/Cargo.toml
new file mode 100644
index 0000000..4576796
--- /dev/null
+++ b/compiletest/Cargo.toml
@@ -0,0 +1,9 @@
+[package]
+name = "eventbus-compiletest"
+version = "0.0.0"
+authors = ["SoniEx2 <endermoneymod@gmail.com>"]
+publish = false
+
+[dependencies]
+compiletest_rs = { version = "0.3", features = ["stable"] }
+eventbus = { path = ".." }
diff --git a/compiletest/README.txt b/compiletest/README.txt
new file mode 100644
index 0000000..44142f9
--- /dev/null
+++ b/compiletest/README.txt
@@ -0,0 +1,3 @@
+$ cargo test
+
+boilerplate from: https://github.com/dtolnay/ref-cast/tree/ea05783b5754fc01baf671f97edf120a3f4afc7f/compiletest
diff --git a/compiletest/compile-fail/hygiene/cancellable.rs b/compiletest/compile-fail/hygiene/cancellable.rs
new file mode 100644
index 0000000..1bdcd96
--- /dev/null
+++ b/compiletest/compile-fail/hygiene/cancellable.rs
@@ -0,0 +1,17 @@
+#[macro_use]
+extern crate eventbus;
+
+use eventbus::{Event, EventBus};
+
+struct MyEvent {
+    i: i32
+}
+
+impl Event for MyEvent {
+}
+
+fn test_hygiene() {
+    let bus = [EventBus::new()];
+    let mut event = MyEvent { i: 3 };
+    post_event!(&bus[CANCELLABLE.load(::std::sync::atomic::Ordering::Relaxed)], &mut event, MyEvent); //~ ERROR cannot find value `CANCELLABLE` in this scope
+}
diff --git a/tests/compile-fail/hygiene.rs b/compiletest/compile-fail/hygiene/event_id.rs
similarity index 76%
rename from tests/compile-fail/hygiene.rs
rename to compiletest/compile-fail/hygiene/event_id.rs
index 6fea17a..71c7ca4 100644
--- a/tests/compile-fail/hygiene.rs
+++ b/compiletest/compile-fail/hygiene/event_id.rs
@@ -10,9 +10,8 @@ struct MyEvent {
 impl Event for MyEvent {
 }
 
-#[test]
 fn test_hygiene() {
     let bus = [EventBus::new()];
     let mut event = MyEvent { i: 3 };
-    post_event!(&bus[EVENT_ID.load(::std::sync::atomic::Ordering::Relaxed)], MyEvent, &mut event);
+    post_event!(&bus[EVENT_ID.load(::std::sync::atomic::Ordering::Relaxed)], &mut event, MyEvent); //~ ERROR cannot find value `EVENT_ID` in this scope
 }
diff --git a/compiletest/src/lib.rs b/compiletest/src/lib.rs
new file mode 100644
index 0000000..052b1de
--- /dev/null
+++ b/compiletest/src/lib.rs
@@ -0,0 +1,23 @@
+#![cfg(test)]
+
+extern crate compiletest_rs as compiletest;
+
+use std::env;
+
+fn run_mode(mode: &'static str) {
+    let mut config = compiletest::Config::default();
+
+    config.mode = mode.parse().expect("invalid mode");
+    config.target_rustcflags = Some("-L ../target/debug/deps".to_owned());
+    if let Ok(name) = env::var("TESTNAME") {
+        config.filter = Some(name);
+    }
+    config.src_base = mode.into();
+
+    compiletest::run_tests(&config);
+}
+
+#[test]
+fn compile_fail() {
+    run_mode("compile-fail");
+}
diff --git a/src/lib.rs b/src/lib.rs
index 105b7ef..ae8c0a9 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -31,7 +31,7 @@ macro_rules! register_hook {
                 static EVENT_ID: ::std::sync::atomic::AtomicUsize = std::sync::atomic::ATOMIC_USIZE_INIT;
                 static EVENT_ID_INIT: ::std::sync::Once = std::sync::ONCE_INIT;
                 // no generics allowed.
-                static DUMMY: ::std::marker::PhantomData<dyn Fn(&mut $t) + Send + Sync> = ::std::marker::PhantomData;
+                static _DUMMY: ::std::marker::PhantomData<dyn Fn(&mut $t) + Send + Sync> = ::std::marker::PhantomData;
                 EVENT_ID_INIT.call_once(|| {
                     EVENT_ID.store($crate::get_event_id::<$t>(), std::sync::atomic::Ordering::Relaxed);
                 });
@@ -47,9 +47,8 @@ macro_rules! register_hook {
 /// Usage: `post_event!(bus, type, event)`
 #[macro_export]
 macro_rules! post_event {
-    // TODO allow multiple $t:ty at once and re-#[doc(hidden)] the low-level get_post_targets and
-    // get_event_id
-    ($b:expr, $t:ty, $e:expr) => {
+    // prefer (bus, ty, ty, ty, ..., evt) but rust is just bad at parsing
+    ($b:expr, $e:expr, $($t:ty),+) => {
         {
             // hygiene
             let bus: &$crate::EventBus = $b;
@@ -64,26 +63,38 @@ macro_rules! post_event {
                 });
                 let cancellable = CANCELLABLE.load(std::sync::atomic::Ordering::Relaxed);
 
-                // event type setup
-                static EVENT_ID: ::std::sync::atomic::AtomicUsize = ::std::sync::atomic::ATOMIC_USIZE_INIT;
-                static EVENT_ID_INIT: ::std::sync::Once = ::std::sync::ONCE_INIT;
-                // no generics allowed.
-                static DUMMY: ::std::marker::PhantomData<dyn Fn(&mut $t) + Send + Sync> = ::std::marker::PhantomData;
-                EVENT_ID_INIT.call_once(|| {
-                    EVENT_ID.store($crate::get_event_id::<$t>(), std::sync::atomic::Ordering::Relaxed);
-                });
-                let id = EVENT_ID.load(std::sync::atomic::Ordering::Relaxed);
+                #[allow(unused_mut)]
+                let mut event_handlers = ::std::iter::empty::<(i32, Box<dyn Fn(&mut _)>)>();
+                $(
+                let handlers;
+                #[allow(unused_mut)]
+                let mut event_handlers = {
+                    // event type setup
+                    static EVENT_ID: ::std::sync::atomic::AtomicUsize = ::std::sync::atomic::ATOMIC_USIZE_INIT;
+                    static EVENT_ID_INIT: ::std::sync::Once = ::std::sync::ONCE_INIT;
+                    // no generics allowed.
+                    static _DUMMY: ::std::marker::PhantomData<dyn Fn(&mut $t) + Send + Sync> = ::std::marker::PhantomData;
+                    EVENT_ID_INIT.call_once(|| {
+                        EVENT_ID.store($crate::get_event_id::<$t>(), std::sync::atomic::Ordering::Relaxed);
+                    });
+                    let id = EVENT_ID.load(std::sync::atomic::Ordering::Relaxed);
 
-                // handler retrieval and invokation
-                let event: &mut $t = event;
-                let handlers = $crate::get_post_targets::<$t>(bus, event, id);
-                for (_pri, fun) in handlers.iter() {
+                    // handler retrieval and invokation
+                    {
+                        let subevent: &mut $t = event;
+                        handlers = $crate::get_post_targets::<$t>(bus, subevent, id);
+                    }
+                    $crate::type_hint_trick(event, event_handlers, handlers.iter().cloned(), |f| move |evt| f(evt))
+                };
+                )+
+
+                for (_pri, fun) in event_handlers {
                     fun(event);
-                    if cancellable && <$t as $crate::Event>::cancelled(event) {
+                    if cancellable && <_ as $crate::Event>::cancelled(event) {
                         break;
                     }
                 }
-                cancellable && <$t as $crate::Event>::cancelled(event)
+                cancellable && <_ as $crate::Event>::cancelled(event)
             }
         }
     }
@@ -124,9 +135,25 @@ pub struct EventBus {
     dropper: Mutex<LinkedList<Box<dyn Fn(usize) + Send + Sync>>>,
 }
 
+#[doc(hidden)]
+pub fn type_hint_trick<T: Event + Sized, A, B, C: Event + ?Sized, E>(_event: &mut T, event_handlers: A, handlers: B, convert: fn(::std::sync::Arc<dyn Fn(&mut C) + Send + Sync>) -> E) -> impl ::std::iter::Iterator<Item=(i32, Box<dyn for<'a> Fn(&'a mut T) + 'static>)> where
+    A: ::std::iter::Iterator<Item=(i32, Box<dyn Fn(&mut T)>)>,
+    B: ::std::iter::Iterator<Item=(i32, ::std::sync::Arc<dyn Fn(&mut C) + Send + Sync>)>,
+    E: Fn(&mut T) + 'static,
+    {
+    itertools::merge_join_by(event_handlers, handlers, |l, r| l.0.cmp(&r.0))
+        .map(move |eob: itertools::EitherOrBoth<(i32, Box<dyn Fn(&mut T)>), (i32, ::std::sync::Arc<dyn Fn(&mut C) + Send + Sync>)>| ({
+            eob.as_ref().left().map(|v| v.0).or_else(|| eob.as_ref().right().map(|v| v.0)).unwrap_or(0)
+        }, Box::new(move |evt: &mut _| {
+            eob.as_ref().left().map(|x| (x.1)(evt));
+            eob.as_ref().right().map(|x| convert(x.1.clone())(evt));
+        }) as Box<_>))
+}
+
 #[macro_use]
 extern crate lazy_static;
 extern crate anymap;
+extern crate itertools;
 
 //use std::marker::PhantomData;
 use std::sync::atomic::AtomicUsize;
@@ -159,10 +186,11 @@ mod id_map {
 
     pub fn get_event_id<T: Event + ?Sized>() -> usize {
         static EVENT_ID_COUNT: AtomicUsize = ATOMIC_USIZE_INIT;
-        let id = EVENT_ID_MAP.lock().expect("failed to allocate event id").entry::<EventId<T>>().or_insert_with(|| EventId { id: EVENT_ID_COUNT.fetch_add(1, AtomicOrdering::SeqCst), _t: PhantomData }).id;
-        let handlers: Arc<Handlers<T>> = Default::default();
-        Arc::make_mut(&mut super::HANDLERS.write().expect("???")).push(handlers);
-        id
+        EVENT_ID_MAP.lock().expect("failed to allocate event id").entry::<EventId<T>>().or_insert_with(|| {
+            let handlers: Arc<Handlers<T>> = Default::default();
+            Arc::make_mut(&mut super::HANDLERS.write().expect("???")).push(handlers);
+            EventId { id: EVENT_ID_COUNT.fetch_add(1, AtomicOrdering::SeqCst), _t: PhantomData }
+        }).id
     }
 }
 
@@ -266,3 +294,50 @@ impl Drop for EventBus {
         // TODO
     }
 }
+
+// test with good error messages
+#[cfg(test)]
+mod useful_test {
+    use super::{Event, EventBus};
+
+    struct MyEvent {
+        i: i32
+    }
+
+    trait DynEvent : Event {
+    }
+
+    impl DynEvent for MyEvent {
+    }
+
+    impl Event for MyEvent {
+    }
+
+    fn add_handler(e: &mut MyEvent) {
+        /* adds 1 */
+        e.i += 1;
+    }
+
+    fn no_handler(e: &mut MyEvent) {
+        /* does nothing */
+        e.i += 0;
+    }
+
+    #[test]
+    fn test_usage_internal() {
+        let event_bus = EventBus::new();
+        //let handler_id = event_bus.register(add_handler, 0);
+        register_hook!(&event_bus, 0, MyEvent, add_handler);
+        let mut event = MyEvent { i: 3 };
+        assert_eq!(event.i, 3);
+        post_event!(&event_bus, &mut event, MyEvent);
+        post_event!(&event_bus, &mut event, dyn DynEvent);
+        assert_eq!(event.i, 4);
+        register_hook!(&event_bus, 1, MyEvent, no_handler);
+        post_event!(&event_bus, &mut event, MyEvent);
+        assert_eq!(event.i, 5);
+        //event_bus.unregister(handler_id);
+        post_event!(&event_bus, &mut event, MyEvent);
+        //assert_eq!(event.i, 5);
+    }
+}
diff --git a/tests/basic_usage.rs b/tests/basic_usage.rs
index a5ce0e2..c491b5b 100644
--- a/tests/basic_usage.rs
+++ b/tests/basic_usage.rs
@@ -27,12 +27,12 @@ fn test_basic_usage() {
     register_hook!(&event_bus, 0, MyEvent, add_handler);
     let mut event = MyEvent { i: 3 };
     assert_eq!(event.i, 3);
-    post_event!(&event_bus, MyEvent, &mut event);
+    post_event!(&event_bus, &mut event, MyEvent);
     assert_eq!(event.i, 4);
     register_hook!(&event_bus, 1, MyEvent, no_handler);
-    post_event!(&event_bus, MyEvent, &mut event);
+    post_event!(&event_bus, &mut event, MyEvent);
     assert_eq!(event.i, 5);
     //event_bus.unregister(handler_id);
-    post_event!(&event_bus, MyEvent, &mut event);
+    post_event!(&event_bus, &mut event, MyEvent);
     //assert_eq!(event.i, 5);
 }
diff --git a/tests/compile-fail/README b/tests/compile-fail/README
deleted file mode 100644
index 1da9fec..0000000
--- a/tests/compile-fail/README
+++ /dev/null
@@ -1 +0,0 @@
-These are currently not being automatically tested because compiletest_rs doesn't work on stable.
diff --git a/tests/dyn_events.rs b/tests/dyn_events.rs
index 0407f7f..a032ee3 100644
--- a/tests/dyn_events.rs
+++ b/tests/dyn_events.rs
@@ -42,12 +42,12 @@ fn test_dyn_usage() {
     register_hook!(&event_bus, 0, dyn MyEvent, add_handler);
     let mut event = MyEventImpl { i: 3 };
     assert_eq!(event.i, 3);
-    post_event!(&event_bus, dyn MyEvent, &mut event);
+    post_event!(&event_bus, &mut event, dyn MyEvent);
     assert_eq!(event.i, 4);
     register_hook!(&event_bus, 1, dyn MyEvent, no_handler);
-    post_event!(&event_bus, dyn MyEvent, &mut event);
+    post_event!(&event_bus, &mut event, dyn MyEvent);
     assert_eq!(event.i, 5);
     //event_bus.unregister(handler_id);
-    post_event!(&event_bus, dyn MyEvent, &mut event);
+    post_event!(&event_bus, &mut event, dyn MyEvent);
     //assert_eq!(event.i, 5);
 }
diff --git a/tests/useful_testing.rs b/tests/useful_testing.rs
new file mode 100644
index 0000000..3a3690b
--- /dev/null
+++ b/tests/useful_testing.rs
@@ -0,0 +1,45 @@
+#[macro_use]
+extern crate eventbus;
+
+use eventbus::{Event, EventBus};
+
+struct MyEvent {
+    i: i32
+}
+
+trait DynEvent : Event {
+}
+
+impl DynEvent for MyEvent {
+}
+
+impl Event for MyEvent {
+}
+
+fn add_handler(e: &mut MyEvent) {
+    /* adds 1 */
+    e.i += 1;
+}
+
+fn no_handler(e: &mut MyEvent) {
+    /* does nothing */
+    e.i += 0;
+}
+
+#[test]
+fn test_usage_external() {
+    let event_bus = EventBus::new();
+    //let handler_id = event_bus.register(add_handler, 0);
+    register_hook!(&event_bus, 0, MyEvent, add_handler);
+    let mut event = MyEvent { i: 3 };
+    assert_eq!(event.i, 3);
+    post_event!(&event_bus, &mut event, MyEvent);
+    post_event!(&event_bus, &mut event, dyn DynEvent);
+    assert_eq!(event.i, 4);
+    register_hook!(&event_bus, 1, MyEvent, no_handler);
+    post_event!(&event_bus, &mut event, MyEvent);
+    assert_eq!(event.i, 5);
+    //event_bus.unregister(handler_id);
+    post_event!(&event_bus, &mut event, MyEvent);
+    //assert_eq!(event.i, 5);
+}