First commit, experimental run of v0.3.0
This commit is contained in:
commit
505fd7fc89
|
@ -0,0 +1,2 @@
|
||||||
|
/target
|
||||||
|
Cargo.lock
|
|
@ -0,0 +1,12 @@
|
||||||
|
[package]
|
||||||
|
name = "eventbus"
|
||||||
|
version = "0.3.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"]
|
||||||
|
license = "AGPL-3.0+"
|
||||||
|
repository = "https://bitbucket.org/TeamSoni/eventbus.rs"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
lazy_static = "1.1.0"
|
||||||
|
anymap = "0.12.1"
|
|
@ -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 <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/// 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<LinkedList<Box<dyn Fn(usize) + Send + Sync>>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[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<T: Event> {
|
||||||
|
id: usize,
|
||||||
|
_t: PhantomData<dyn Fn(&mut T) + Send + Sync>
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_event_id<T: Event>() -> 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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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<T: Event>() -> usize {
|
||||||
|
id_map::get_event_id::<T>()
|
||||||
|
}
|
||||||
|
|
||||||
|
type RwArcVec<T> = RwLock<Arc<Vec<T>>>;
|
||||||
|
|
||||||
|
struct Handlers<T: Event>(RwArcVec<RwArcVec<(i32, Arc<dyn Fn(&mut T) + Send + Sync>)>>);
|
||||||
|
|
||||||
|
impl<T: Event> Default for Handlers<T> {
|
||||||
|
fn default() -> Self {
|
||||||
|
Handlers(Default::default())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
lazy_static! {
|
||||||
|
static ref HANDLERS: RwArcVec<Arc<dyn Any + Send + Sync>> = 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<T: Event>(bus: &EventBus, event: &mut T, id: usize) -> Arc<Vec<(i32, Arc<dyn Fn(&mut T) + Send + Sync>)>> {
|
||||||
|
let target: Option<Arc<Vec<_>>> = HANDLERS.read().expect("failed to lock for reading").get(id).map(|arc| Arc::clone(arc))
|
||||||
|
.map(|any| Arc::downcast::<Handlers<T>>(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<T: Event, F: for<'a> 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::<Handlers<T>>(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<dyn Fn(&mut T) + Send + Sync + 'static>) = (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<LinkedList<usize>> = 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
|
||||||
|
}
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
Loading…
Reference in New Issue