diff --git a/src/util/mod.rs b/src/util/mod.rs index 94df1a4..06b69f8 100644 --- a/src/util/mod.rs +++ b/src/util/mod.rs @@ -282,16 +282,28 @@ impl Freeze for T { } } +/// A type containing listeners of this event, +/// which can be invoked by an invoker. +/// +/// The listeners are sorted by phases ([`i8`] by default) +/// that can be called by order. +/// +/// This type was inspired by the event system in Fabric API. pub struct Event where T: ?Sized + 'static, Phase: Ord, { - listeners_and_cache: - parking_lot::RwLock<(Vec<(Phase, *const T)>, Option>, Vec<&'static T>)>, + /// Whether listeners has been modified before requesting the invoker. + dirty: std::sync::atomic::AtomicBool, + invoker_factory: fn(&'static [&'static T]) -> Box, - dirty: std::sync::atomic::AtomicBool, + /// 0: raw listeners with phases + /// 1: cached invoker + /// 2: cached listener references + listeners_and_cache: + parking_lot::RwLock<(Vec<(Phase, *const T)>, Option>, Vec<&'static T>)>, } impl Event @@ -299,6 +311,13 @@ where T: ?Sized, Phase: Ord, { + /// Create a new event with provided event factory. + /// + /// To avoid lifetime problems in the factory, listeners + /// provied are all in static references so that they're + /// able to be copied and moved. + /// So you should add a `move` keyword before the closure + /// to return in the factory. pub const fn new(invoker_factory: fn(&'static [&'static T]) -> Box) -> Self { Self { listeners_and_cache: parking_lot::RwLock::new((Vec::new(), None, Vec::new())), @@ -307,6 +326,11 @@ where } } + /// Get the invoker of this event. + /// + /// Once the invoker is created, it will be cached until + /// the next modification of listeners, and will be re-created + /// by the factory. pub fn invoker(&self) -> &T { if self.dirty.load(std::sync::atomic::Ordering::Acquire) { let mut write_guard = self.listeners_and_cache.write(); @@ -328,6 +352,7 @@ where unsafe { &*(self.listeners_and_cache.read().1.as_ref().unwrap().deref() as *const T) } } + /// Register a listener to this event for the specified phase. pub fn register_with_phase(&mut self, listener: Box, phase: Phase) { self.listeners_and_cache .get_mut() @@ -345,6 +370,7 @@ where T: ?Sized, Phase: Ord + Default, { + /// Register a listener to this event for the default phase. pub fn register(&mut self, listener: Box) { self.register_with_phase(listener, Default::default()) } @@ -392,15 +418,17 @@ mod event_tests { return false; } } - true }) }); + assert!(event.invoker()( + "minecraft by mojang is a propritary software." + )); + event.register(Box::new(|string| { !string.to_lowercase().contains("propritary software") })); - event.register(Box::new(|string| !string.to_lowercase().contains("mojang"))); event.register(Box::new(|string| { !string.to_lowercase().contains("minecraft") @@ -416,4 +444,40 @@ mod event_tests { assert!(!event.invoker()("i love krlite.")); } + + #[test] + fn phases() { + let mut event: Event = Event::new(|listeners| { + Box::new(move |string| { + for listener in listeners { + listener(string); + } + }) + }); + + event.register(Box::new(|string| string.push_str("genshin impact "))); + event.register_with_phase(Box::new(|string| string.push_str("you're right, ")), -3); + event.register_with_phase(Box::new(|string| string.push_str("but ")), -2); + event.register_with_phase(Box::new(|string| string.push_str("is a...")), 10); + + { + let mut string = String::new(); + event.invoker()(&mut string); + assert_eq!(string, "you're right, but genshin impact is a..."); + } + + event.register_with_phase( + Box::new(|string| string.push_str("genshin impact, bootstrap! ")), + -100, + ); + + { + let mut string = String::new(); + event.invoker()(&mut string); + assert_eq!( + string, + "genshin impact, bootstrap! you're right, but genshin impact is a..." + ); + } + } }