use std::any::TypeId;
use std::collections::HashSet;
use std::ops::{Deref, DerefMut};
use std::sync::Arc;
use acton_ern::Ern;
use dashmap::DashMap;
use futures::future::join_all;
use tracing::{instrument, trace};
use crate::actor::{ActorConfig, Idle, ManagedActor};
use crate::common::{ActorHandle, ActorRuntime, BrokerRef};
use crate::message::{BrokerRequest, BrokerRequestEnvelope, SubscribeBroker};
use crate::traits::ActorHandleInterface;
#[cfg(feature = "ipc")]
use crate::common::ipc::{IpcPushNotification, IpcTypeRegistry, SubscriptionManager};
#[cfg(feature = "ipc")]
use parking_lot::RwLock;
#[derive(Default, Debug, Clone)]
pub struct Broker {
subscribers: Subscribers,
actor_handle: ActorHandle,
#[cfg(feature = "ipc")]
ipc_subscription_manager: Arc<RwLock<Option<Arc<SubscriptionManager>>>>,
#[cfg(feature = "ipc")]
ipc_type_registry: Arc<IpcTypeRegistry>,
}
type Subscribers = Arc<DashMap<TypeId, HashSet<(Ern, ActorHandle)>>>;
impl Deref for Broker {
type Target = ActorHandle;
#[inline]
fn deref(&self) -> &Self::Target {
&self.actor_handle
}
}
impl DerefMut for Broker {
#[inline]
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.actor_handle
}
}
impl Broker {
#[instrument]
pub(crate) async fn initialize(runtime: ActorRuntime) -> BrokerRef {
let actor_config = ActorConfig::new(Ern::with_root("broker_main").unwrap(), None, None)
.expect("Couldn't create initial broker config");
assert!(
!runtime.0.cancellation_token.is_cancelled(),
"ActonInner cancellation_token must be present and active before creating ManagedActor in Broker::initialize"
);
let mut broker_actor: ManagedActor<Idle, Self> =
ManagedActor::new(Some(&runtime), Some(&actor_config));
#[cfg(feature = "ipc")]
{
broker_actor.model.ipc_subscription_manager =
runtime.0.ipc_subscription_manager.clone();
broker_actor.model.ipc_type_registry = runtime.0.ipc_type_registry.clone();
}
broker_actor
.mutate_on::<BrokerRequest>(|actor, event| {
trace!(message_type = ?event.message.message_type_id, "Broker received BrokerRequest");
let subscribers = actor.model.subscribers.clone(); let message_to_broadcast = event.message.clone();
#[cfg(feature = "ipc")]
let ipc_sub_mgr = actor.model.ipc_subscription_manager.clone();
#[cfg(feature = "ipc")]
let ipc_type_reg = actor.model.ipc_type_registry.clone();
Box::pin(async move {
Self::broadcast(subscribers, message_to_broadcast.clone()).await;
#[cfg(feature = "ipc")]
Self::forward_to_ipc(&ipc_sub_mgr, &ipc_type_reg, &message_to_broadcast);
})
})
.act_on::<SubscribeBroker>(|actor, event| {
let subscription_msg = event.message.clone();
let type_id = subscription_msg.message_type_id;
let subscriber_handle = subscription_msg.subscriber_context.clone();
let subscriber_id = subscription_msg.subscriber_id;
trace!(subscriber = %subscriber_id, message_type = ?type_id, "Broker received SubscribeBroker");
let subscribers_map = actor.model.subscribers.clone(); Box::pin(async move {
let subscriber_id_for_insert = subscriber_id.clone(); subscribers_map
.entry(type_id)
.or_default() .insert((subscriber_id_for_insert, subscriber_handle)); trace!(subscriber = %subscriber_id, message_type = ?type_id, "Subscription added"); })
});
trace!("Starting the Broker actor...");
let mut broker_handle = broker_actor.start().await;
broker_handle.broker = Box::from(Some(broker_handle.clone()));
trace!("Broker started with handle ID: {}", broker_handle.id());
broker_handle
}
pub async fn broadcast(
subscribers: Subscribers, request: BrokerRequest,
) {
let message_type_id = request.message_type_id; trace!(message_type = ?message_type_id, "Broadcasting message");
if let Some(subscribers_set) = subscribers.get(&message_type_id) {
let num_subscribers = subscribers_set.len();
trace!(count = num_subscribers, message_type = ?message_type_id, "Found subscribers");
let shared_envelope: BrokerRequestEnvelope = request.into();
let mut futures = Vec::with_capacity(num_subscribers);
for (_, subscriber_handle) in subscribers_set.value() {
let handle = subscriber_handle.clone();
let envelope_to_send = shared_envelope.clone();
futures.push(async move {
trace!(subscriber = %handle.id(), message_type = ?message_type_id, "Sending broadcast");
handle.send(envelope_to_send).await;
});
}
join_all(futures).await;
trace!(count = num_subscribers, message_type = ?message_type_id, "Broadcast sends completed");
} else {
trace!(message_type = ?message_type_id, "No subscribers found for message type");
}
}
#[cfg(feature = "ipc")]
fn forward_to_ipc(
ipc_sub_mgr: &Arc<RwLock<Option<Arc<SubscriptionManager>>>>,
ipc_type_reg: &Arc<IpcTypeRegistry>,
request: &BrokerRequest,
) {
let sub_mgr = {
let guard = ipc_sub_mgr.read();
guard.clone()
};
let Some(sub_mgr) = sub_mgr else {
trace!("No IPC subscription manager active, skipping IPC forward");
return;
};
let Some(type_name) = ipc_type_reg.get_type_name_by_id(&request.message_type_id) else {
trace!(type_id = ?request.message_type_id, "Type not registered for IPC, skipping forward");
return;
};
let payload_json = match ipc_type_reg
.serialize_by_type_id(&request.message_type_id, request.message.as_ref())
{
Ok(json) => json,
Err(e) => {
trace!(type_name, error = %e, "Failed to serialize payload for IPC forward");
return;
}
};
let notification = IpcPushNotification::new(type_name.clone(), None, payload_json);
sub_mgr.forward_to_subscribers(¬ification);
trace!(type_name, "Forwarded broadcast to IPC subscribers");
}
}