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 "] 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 "] +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 = ::std::marker::PhantomData; + static _DUMMY: ::std::marker::PhantomData = ::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 = ::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)>(); + $( + 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 = ::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>>, } +#[doc(hidden)] +pub fn type_hint_trick(_event: &mut T, event_handlers: A, handlers: B, convert: fn(::std::sync::Arc) -> E) -> impl ::std::iter::Iterator Fn(&'a mut T) + 'static>)> where + A: ::std::iter::Iterator)>, + B: ::std::iter::Iterator)>, + 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), (i32, ::std::sync::Arc)>| ({ + 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() -> usize { static EVENT_ID_COUNT: AtomicUsize = ATOMIC_USIZE_INIT; - let id = EVENT_ID_MAP.lock().expect("failed to allocate event id").entry::>().or_insert_with(|| EventId { id: EVENT_ID_COUNT.fetch_add(1, AtomicOrdering::SeqCst), _t: PhantomData }).id; - let handlers: Arc> = Default::default(); - Arc::make_mut(&mut super::HANDLERS.write().expect("???")).push(handlers); - id + EVENT_ID_MAP.lock().expect("failed to allocate event id").entry::>().or_insert_with(|| { + let handlers: Arc> = 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); +}