commit 505fd7fc89c69a4522fb7e6a871264f56dd4a817 Author: SoniEx2 Date: Wed Oct 24 12:12:24 2018 -0300 First commit, experimental run of v0.3.0 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..96ef6c0 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +/target +Cargo.lock diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..3a5ea37 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "eventbus" +version = "0.3.0" +authors = ["SoniEx2 "] +description = "Safe, fast and concurrent event system, inspired by the MinecraftForge event bus." +keywords = ["event", "safe", "fast", "concurrent", "bus"] +license = "AGPL-3.0+" +repository = "https://bitbucket.org/TeamSoni/eventbus.rs" + +[dependencies] +lazy_static = "1.1.0" +anymap = "0.12.1" diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..cd1a0a1 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,238 @@ +/* + * Event Bus + * Copyright (C) 2018 Soni L. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +/// Registers an event hook. +/// +/// Usage: `register_hook!(bus, priority, type, handler)` +#[macro_export] +macro_rules! register_hook { + ($b:expr, $p:expr, $t:ty, $h:expr) => { + { + 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<$t> = ::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); + $crate::register::<$t, _>($b, $p, $h, id) + } + } +} + +/// Posts an event. +/// +/// Usage: `post!(bus, type, event)` +#[macro_export] +macro_rules! post_event { + ($b:expr, $t:ty, $e:expr) => { + { + 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<$t> = ::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); + let event: &mut $t = $e; + let handlers = $crate::get_post_targets::<$t>($b, event, id); + for (_pri, fun) in handlers.iter() { + fun(event); + if <$t>::cancellable() && event.cancelled() { + break; + } + } + <$t>::cancellable() && event.cancelled() + } + } +} + +/// Basic trait for defining an event. +pub trait Event: 'static { + /// Returns whether this event is cancellable. + /// + /// When this is true, `cancelled` and `cancel` should also be implemented. + fn cancellable() -> bool { + false + } + + /// Returns whether this event has been cancelled. + fn cancelled(&self) -> bool { + false + } + + /// Sets whether this event is cancelled. + /// + /// # Panics + /// + /// Panics if this event is not cancellable. + fn set_cancelled(&mut self, cancel: bool) { + panic!("not cancellable"); + } +} + +/// An event bus. +pub struct EventBus { + id: usize, + dropper: Mutex>>, +} + +#[macro_use] +extern crate lazy_static; +extern crate anymap; + +//use std::marker::PhantomData; +use std::sync::atomic::AtomicUsize; +use std::sync::atomic::ATOMIC_USIZE_INIT; +use std::sync::atomic::Ordering as AtomicOrdering; +use std::sync::Mutex; +use std::collections::LinkedList; +use std::any::Any; +use std::sync::Arc; +use std::sync::RwLock; +use std::mem; + +/* only exists as a module to keep us from accidentally misusing these */ +mod id_map { + use std::marker::PhantomData; + use std::sync::atomic::AtomicUsize; + use std::sync::atomic::ATOMIC_USIZE_INIT; + use std::sync::atomic::Ordering as AtomicOrdering; + use std::sync::Mutex; + use std::sync::Arc; + use super::Event; + use super::Handlers; + lazy_static! { + static ref EVENT_ID_MAP: Mutex<::anymap::Map<::anymap::any::Any + Send + Sync>> = Mutex::new(::anymap::Map::new()); + } + struct EventId { + id: usize, + _t: PhantomData + } + + 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 + } +} + +/// Low-level event handling function, returns the event ID for use with get_post_targets. +/// +/// This function is (extremely) slow! +pub fn get_event_id() -> usize { + id_map::get_event_id::() +} + +type RwArcVec = RwLock>>; + +struct Handlers(RwArcVec)>>); + +impl Default for Handlers { + fn default() -> Self { + Handlers(Default::default()) + } +} + +lazy_static! { + static ref HANDLERS: RwArcVec> = Default::default(); +} + +/// Low-level event handling function, returns the handlers that should be called to post the +/// event. +/// +/// This is useful if you want to simulate inheritance. +/// +/// # Panics +/// +/// Panics if `id` doesn't match the given `T` (but not if the given `id` doesn't exist). +pub fn get_post_targets(bus: &EventBus, event: &mut T, id: usize) -> Arc)>> { + let target: Option>> = HANDLERS.read().expect("failed to lock for reading").get(id).map(|arc| Arc::clone(arc)) + .map(|any| Arc::downcast::>(any).expect("Wrong id")) + .and_then(|handlers| handlers.0.read().expect("failed to lock for reading").get(bus.id).map(|hooks| Arc::clone(&*hooks.read().expect("failed to lock for reading")))); + target.unwrap_or_else(|| Arc::new(Vec::new())) +} + +/// Low-level event handling function, registers an event. +/// +/// This function is kinda slow. It doesn't block currently executing hooks (and, in fact, may be +/// safely called from a running hook), but does block threads from starting hook execution. +/// +/// # Panics +/// +/// Panics if `id` doesn't match the given `T`, or if `id` doesn't exist. +pub fn register Fn(&'a mut T) + Send + Sync + 'static>(bus: &EventBus, priority: i32, handler: F, id: usize) { + HANDLERS.read().expect("failed to lock for reading").get(id).map(|arc| Arc::clone(arc)) + .map(|any| Arc::downcast::>(any).expect("Wrong id")) + .map(|handlers| { + let mut lock = handlers.0.read().expect("failed to lock for reading"); + if lock.len() <= id { + mem::drop(lock); + let mut wlock = handlers.0.write().expect("failed to lock for writing"); + if wlock.len() <= id { // another thread may have touched this + let vec = Arc::get_mut(&mut wlock).expect("failed to mutate busses"); + let count = id - vec.len() + 1; + vec.reserve_exact(count); + for _ in 0..count { + vec.push(Default::default()); + } + } + // there's no way to downgrade a write lock, but as long as we never shrink the + // vecs it should be fine. + mem::drop(wlock); + lock = handlers.0.read().expect("failed to lock for reading"); + } + let option = lock.get(bus.id); + // last RwLock + let hooks = option.expect("failed to make vecs"); + let hook: (i32, Arc) = (priority, Arc::new(handler)); + let mut wlock = hooks.write().expect("failed to lock for writing"); + let vec = Arc::make_mut(&mut wlock); + // would be nice if Result had a .coalesce() + let pos = match vec.binary_search_by(|probe| probe.0.cmp(&priority)) { Ok(p) => p, Err(p) => p }; + vec.insert(pos, hook); + }).expect("No such id"); +} + +static BUS_ID: AtomicUsize = ATOMIC_USIZE_INIT; + +lazy_static! { + // this is the closest to high-performance we can get with std + // would be nicer if this didn't lock the whole list + static ref FREED_BUS_IDS: Mutex> = Mutex::new(LinkedList::new()); +} + +impl EventBus { + /// Makes a new EventBus. + pub fn new() -> EventBus { + EventBus { + id: FREED_BUS_IDS.lock().unwrap().pop_front().unwrap_or_else(|| BUS_ID.fetch_add(1, AtomicOrdering::SeqCst)), + dropper: Default::default(), + } + } +} + +impl Drop for EventBus { + fn drop(&mut self) { + // TODO + } +} diff --git a/tests/basic_usage.rs b/tests/basic_usage.rs new file mode 100644 index 0000000..a5ce0e2 --- /dev/null +++ b/tests/basic_usage.rs @@ -0,0 +1,38 @@ +#[macro_use] +extern crate eventbus; + +use eventbus::{Event, EventBus}; + +struct MyEvent { + i: i32 +} + +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_basic_usage() { + 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, MyEvent, &mut event); + assert_eq!(event.i, 4); + register_hook!(&event_bus, 1, MyEvent, no_handler); + post_event!(&event_bus, MyEvent, &mut event); + assert_eq!(event.i, 5); + //event_bus.unregister(handler_id); + post_event!(&event_bus, MyEvent, &mut event); + //assert_eq!(event.i, 5); +}