#![forbid(unsafe_code)]
#![warn(missing_docs)]
use std::cell::RefCell;
use std::panic::{catch_unwind, AssertUnwindSafe};
use std::rc::Rc;
pub const WILDCARD: &str = "*";
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct Token(String);
impl Token {
#[must_use]
pub fn as_str(&self) -> &str {
&self.0
}
}
impl std::fmt::Display for Token {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(&self.0)
}
}
type Callback<D> = Rc<dyn Fn(&str, &D)>;
struct Entry<D> {
token: Token,
callback: Callback<D>,
once: bool,
}
struct Topic<D> {
entries: Vec<Entry<D>>,
}
impl<D> Topic<D> {
fn new() -> Self {
Topic {
entries: Vec::new(),
}
}
}
struct Inner<D> {
topics: Vec<(String, Topic<D>)>,
last_uid: i64,
deferred: Vec<DeferredJob<D>>,
}
struct DeferredJob<D> {
message: String,
data: D,
immediate_exceptions: bool,
}
fn delivery_levels(message: &str) -> impl Iterator<Item = &str> {
let trailing_wildcard = (message != WILDCARD).then_some(WILDCARD);
std::iter::once(message)
.chain(message.rmatch_indices('.').map(move |(i, _)| &message[..i]))
.chain(trailing_wildcard)
}
pub struct PubSub<D> {
inner: RefCell<Inner<D>>,
immediate_exceptions: std::cell::Cell<bool>,
}
impl<D> Default for PubSub<D> {
fn default() -> Self {
Self::new()
}
}
impl<D> std::fmt::Debug for PubSub<D> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let inner = self.inner.borrow();
f.debug_struct("PubSub")
.field(
"topics",
&inner
.topics
.iter()
.map(|(name, t)| (name.as_str(), t.entries.len()))
.collect::<Vec<_>>(),
)
.field("last_uid", &inner.last_uid)
.field("pending", &inner.deferred.len())
.field("immediate_exceptions", &self.immediate_exceptions.get())
.finish()
}
}
impl<D> PubSub<D> {
#[must_use]
pub fn new() -> Self {
PubSub {
inner: RefCell::new(Inner {
topics: Vec::new(),
last_uid: -1,
deferred: Vec::new(),
}),
immediate_exceptions: std::cell::Cell::new(false),
}
}
pub fn set_immediate_exceptions(&self, value: bool) {
self.immediate_exceptions.set(value);
}
#[must_use]
pub fn immediate_exceptions(&self) -> bool {
self.immediate_exceptions.get()
}
pub fn subscribe<F>(&self, topic: impl Into<String>, func: F) -> Token
where
F: Fn(&str, &D) + 'static,
{
self.subscribe_entry(topic.into(), Rc::new(func), false)
}
fn subscribe_entry(&self, topic: String, callback: Callback<D>, once: bool) -> Token {
let mut inner = self.inner.borrow_mut();
inner.last_uid += 1;
let token = Token(format!("uid_{}", inner.last_uid));
let entry = Entry {
token: token.clone(),
callback,
once,
};
match inner.topics.iter_mut().find(|(name, _)| name == &topic) {
Some((_, t)) => t.entries.push(entry),
None => {
let mut t = Topic::new();
t.entries.push(entry);
inner.topics.push((topic, t));
}
}
token
}
pub fn subscribe_all<F>(&self, func: F) -> Token
where
F: Fn(&str, &D) + 'static,
{
self.subscribe(WILDCARD, func)
}
pub fn subscribe_once<F>(&self, topic: impl Into<String>, func: F) -> Token
where
F: Fn(&str, &D) + 'static,
{
self.subscribe_entry(topic.into(), Rc::new(func), true)
}
#[must_use]
pub fn publish(&self, topic: impl Into<String>, data: D) -> bool {
let message = topic.into();
let immediate = self.immediate_exceptions.get();
let has = self.message_has_subscribers(&message);
if !has {
return false;
}
self.inner.borrow_mut().deferred.push(DeferredJob {
message,
data,
immediate_exceptions: immediate,
});
true
}
#[must_use]
pub fn publish_sync(&self, topic: impl Into<String>, data: D) -> bool {
let message = topic.into();
let immediate = self.immediate_exceptions.get();
let has = self.message_has_subscribers(&message);
if !has {
return false;
}
self.deliver(&message, &data, immediate);
true
}
pub fn process_deferred(&self) {
let batch = std::mem::take(&mut self.inner.borrow_mut().deferred);
let mut held_panic: Option<Box<dyn std::any::Any + Send>> = None;
for job in batch {
if job.immediate_exceptions {
self.deliver(&job.message, &job.data, true);
} else if let Err(panic) = catch_unwind(AssertUnwindSafe(|| {
self.deliver(&job.message, &job.data, false);
})) {
if held_panic.is_none() {
held_panic = Some(panic);
}
}
}
if let Some(panic) = held_panic {
std::panic::resume_unwind(panic);
}
}
#[must_use]
pub fn pending(&self) -> usize {
self.inner.borrow().deferred.len()
}
fn deliver(&self, message: &str, data: &D, immediate_exceptions: bool) {
let mut held_panic: Option<Box<dyn std::any::Any + Send>> = None;
for level in delivery_levels(message) {
let snapshot: Vec<Callback<D>> = {
let mut inner = self.inner.borrow_mut();
match inner.topics.iter_mut().find(|(name, _)| name == level) {
Some((_, t)) => {
let snapshot = t.entries.iter().map(|e| e.callback.clone()).collect();
t.entries.retain(|e| !e.once);
snapshot
}
None => Vec::new(),
}
};
for callback in snapshot {
if immediate_exceptions {
callback(message, data);
} else if let Err(panic) =
catch_unwind(AssertUnwindSafe(|| callback(message, data)))
{
if held_panic.is_none() {
held_panic = Some(panic);
}
}
}
}
if let Some(panic) = held_panic {
std::panic::resume_unwind(panic);
}
}
fn message_has_subscribers(&self, message: &str) -> bool {
let inner = self.inner.borrow();
delivery_levels(message).any(|level| {
inner
.topics
.iter()
.any(|(name, t)| name == level && !t.entries.is_empty())
})
}
pub fn unsubscribe(&self, token: &Token) -> Option<Token> {
let mut inner = self.inner.borrow_mut();
for (_, t) in inner.topics.iter_mut() {
if let Some(idx) = t.entries.iter().position(|e| &e.token == token) {
t.entries.remove(idx);
return Some(token.clone());
}
}
None
}
pub fn unsubscribe_topic(&self, topic: &str) -> bool {
let is_topic = {
let inner = self.inner.borrow();
inner.topics.iter().any(|(name, _)| name.starts_with(topic))
};
if is_topic {
self.clear_subscriptions(topic);
}
is_topic
}
pub fn clear_all_subscriptions(&self) {
self.inner.borrow_mut().topics.clear();
}
pub fn clear_subscriptions(&self, topic: &str) {
self.inner
.borrow_mut()
.topics
.retain(|(name, _)| !name.starts_with(topic));
}
#[must_use]
pub fn count_subscriptions(&self, topic: &str) -> usize {
let inner = self.inner.borrow();
for (name, t) in &inner.topics {
if name.starts_with(topic) {
return t.entries.len();
}
}
0
}
#[must_use]
pub fn get_subscriptions(&self, topic: &str) -> Vec<String> {
let inner = self.inner.borrow();
inner
.topics
.iter()
.filter(|(name, _)| name.starts_with(topic))
.map(|(name, _)| name.clone())
.collect()
}
}