pub mod types;
use alloc::sync::Arc;
use std::{
collections::HashMap,
fmt::{Debug, Formatter, Result},
};
pub use self::types::{WalletEvent, WalletEventType};
type Handler<T> = Arc<dyn Fn(&T) + Send + Sync + 'static>;
pub struct EventEmitter {
handlers: HashMap<WalletEventType, Vec<Handler<WalletEvent>>>,
}
impl EventEmitter {
pub fn new() -> Self {
Self {
handlers: HashMap::new(),
}
}
pub fn on<F>(&mut self, events: impl IntoIterator<Item = WalletEventType>, handler: F)
where
F: Fn(&WalletEvent) + 'static + Send + Sync,
{
let mut events = events.into_iter().peekable();
let handler = Arc::new(handler);
if events.peek().is_none() {
for event_type in [
WalletEventType::NewOutput,
WalletEventType::SpentOutput,
WalletEventType::TransactionInclusion,
WalletEventType::TransactionProgress,
#[cfg(feature = "ledger_nano")]
WalletEventType::LedgerAddressGeneration,
] {
self.handlers.entry(event_type).or_default().push(handler.clone());
}
}
for event in events {
self.handlers.entry(event).or_default().push(handler.clone());
}
}
pub fn clear(&mut self, events: impl IntoIterator<Item = WalletEventType>) {
let mut events = events.into_iter().peekable();
if events.peek().is_none() {
self.handlers.clear();
}
for event in events {
self.handlers.remove(&event);
}
}
pub fn emit(&self, event: WalletEvent) {
let event_type = match &event {
WalletEvent::NewOutput(_) => WalletEventType::NewOutput,
WalletEvent::SpentOutput(_) => WalletEventType::SpentOutput,
WalletEvent::TransactionInclusion(_) => WalletEventType::TransactionInclusion,
WalletEvent::TransactionProgress(_) => WalletEventType::TransactionProgress,
#[cfg(feature = "ledger_nano")]
WalletEvent::LedgerAddressGeneration(_) => WalletEventType::LedgerAddressGeneration,
};
if let Some(handlers) = self.handlers.get(&event_type) {
for handler in handlers {
handler(&event);
}
}
}
}
impl Default for EventEmitter {
fn default() -> Self {
Self::new()
}
}
impl Debug for EventEmitter {
fn fmt(&self, f: &mut Formatter<'_>) -> Result {
write!(
f,
"event_types_with_handlers: {:?}",
self.handlers.keys().collect::<Vec<&WalletEventType>>()
)
}
}
#[cfg(test)]
mod tests {
use std::{
str::FromStr,
sync::{
atomic::{AtomicUsize, Ordering},
Arc,
},
};
use pretty_assertions::assert_eq;
use super::{
types::{TransactionInclusionEvent, TransactionProgressEvent, WalletEvent, WalletEventType},
EventEmitter,
};
use crate::{types::block::payload::signed_transaction::TransactionId, wallet::types::InclusionState};
#[test]
fn events() {
let mut emitter = EventEmitter::new();
let event_counter = Arc::new(AtomicUsize::new(0));
emitter.on([WalletEventType::TransactionInclusion], |_name| {
});
emitter.on(
[
WalletEventType::TransactionProgress,
WalletEventType::TransactionInclusion,
],
move |_name| {
},
);
let event_counter_clone = Arc::clone(&event_counter);
emitter.on([], move |_name| {
event_counter_clone.fetch_add(1, Ordering::SeqCst);
});
emitter.emit(WalletEvent::TransactionProgress(
TransactionProgressEvent::BuildingTransaction,
));
emitter.emit(WalletEvent::TransactionInclusion(TransactionInclusionEvent {
transaction_id: TransactionId::from_str(
"0x2289d9981fb23cc5f4f6c2742685eeb480f8476089888aa886a18232bad8198900000000",
)
.expect("invalid tx id"),
inclusion_state: InclusionState::Confirmed,
}));
assert_eq!(2, event_counter.load(Ordering::SeqCst));
emitter.clear([WalletEventType::TransactionProgress]);
emitter.emit(WalletEvent::TransactionProgress(
TransactionProgressEvent::BuildingTransaction,
));
assert_eq!(2, event_counter.load(Ordering::SeqCst));
emitter.clear([]);
emitter.emit(WalletEvent::TransactionProgress(
TransactionProgressEvent::BuildingTransaction,
));
emitter.emit(WalletEvent::TransactionInclusion(TransactionInclusionEvent {
transaction_id: TransactionId::from_str(
"0x2289d9981fb23cc5f4f6c2742685eeb480f8476089888aa886a18232bad8198900000000",
)
.expect("invalid tx id"),
inclusion_state: InclusionState::Confirmed,
}));
assert_eq!(2, event_counter.load(Ordering::SeqCst));
let event_counter_clone = Arc::clone(&event_counter);
emitter.on([WalletEventType::TransactionProgress], move |_name| {
event_counter_clone.fetch_add(1, Ordering::SeqCst);
});
for _ in 0..1_000_000 {
emitter.emit(WalletEvent::TransactionProgress(
TransactionProgressEvent::BuildingTransaction,
));
}
assert_eq!(1_000_002, event_counter.load(Ordering::SeqCst));
}
}