use std::any::{Any, TypeId};
use std::future::Future;
use std::pin::Pin;
use std::sync::{Arc, OnceLock};
use std::time::{Duration, Instant};
use dashmap::DashMap;
use crate::actor::handle::ActorHandle;
use crate::actor::Actor;
use crate::error::{SendError, SpawnError};
use crate::types::{ActorId, StopReason};
type StopFn =
dyn Fn(StopReason) -> Pin<Box<dyn Future<Output = Result<(), SendError>> + Send>> + Send + Sync;
static SYSTEMS: OnceLock<DashMap<String, Arc<ActorSystem>>> = OnceLock::new();
fn systems() -> &'static DashMap<String, Arc<ActorSystem>> {
SYSTEMS.get_or_init(DashMap::new)
}
#[derive(Debug, Clone)]
pub struct ShutdownPolicy {
pub timeout: Duration,
pub per_actor_timeout: Duration,
}
impl Default for ShutdownPolicy {
fn default() -> Self {
Self {
timeout: Duration::from_secs(30),
per_actor_timeout: Duration::from_secs(5),
}
}
}
#[derive(Debug, Clone, Default)]
pub struct SystemConfig {
pub shutdown_policy: ShutdownPolicy,
}
struct AnyActorHandle {
type_id: TypeId,
handle: Box<dyn Any + Send + Sync>,
stopper: Box<StopFn>,
#[allow(dead_code)]
created_at: Instant,
}
impl AnyActorHandle {
fn new<A: Actor>(handle: &ActorHandle<A>) -> Self {
let cloned = handle.clone();
let stopper_handle = handle.clone();
Self {
type_id: TypeId::of::<ActorHandle<A>>(),
handle: Box::new(cloned),
stopper: Box::new(move |reason| {
let h = stopper_handle.clone();
Box::pin(async move { h.stop(reason).await })
}),
created_at: Instant::now(),
}
}
fn downcast<A: Actor>(&self) -> Option<ActorHandle<A>> {
if self.type_id == TypeId::of::<ActorHandle<A>>() {
self.handle.downcast_ref::<ActorHandle<A>>().cloned()
} else {
None
}
}
async fn stop(&self, reason: StopReason) -> Result<(), SendError> {
(self.stopper)(reason).await
}
}
pub(crate) struct RegistryGuard {
system: Arc<ActorSystem>,
id: ActorId,
name: Option<String>,
}
impl RegistryGuard {
pub(crate) fn new(system: Arc<ActorSystem>, id: ActorId, name: Option<String>) -> Self {
Self { system, id, name }
}
}
impl Drop for RegistryGuard {
fn drop(&mut self) {
self.system.unregister_by_id(&self.id);
if let Some(name) = &self.name {
self.system.by_name.remove(name);
}
}
}
pub struct ActorSystem {
name: String,
by_name: DashMap<String, AnyActorHandle>,
by_id: DashMap<ActorId, AnyActorHandle>,
shutdown_policy: ShutdownPolicy,
}
impl std::fmt::Debug for ActorSystem {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("ActorSystem")
.field("name", &self.name)
.finish_non_exhaustive()
}
}
impl ActorSystem {
#[allow(clippy::should_implement_trait)]
pub fn default() -> Arc<ActorSystem> {
let reg = systems();
reg.entry("default".to_string())
.or_insert_with(|| {
Arc::new(ActorSystem {
name: "default".to_string(),
by_name: DashMap::new(),
by_id: DashMap::new(),
shutdown_policy: ShutdownPolicy::default(),
})
})
.value()
.clone()
}
pub fn create(name: impl Into<String>) -> Result<Arc<ActorSystem>, SpawnError> {
let name = name.into();
let reg = systems();
match reg.entry(name.clone()) {
dashmap::mapref::entry::Entry::Occupied(_) => Err(SpawnError::SystemNameTaken(name)),
dashmap::mapref::entry::Entry::Vacant(v) => {
let system = Arc::new(ActorSystem {
name,
by_name: DashMap::new(),
by_id: DashMap::new(),
shutdown_policy: ShutdownPolicy::default(),
});
v.insert(system.clone());
Ok(system)
}
}
}
pub fn create_with(
name: impl Into<String>,
config: SystemConfig,
) -> Result<Arc<ActorSystem>, SpawnError> {
let name = name.into();
let reg = systems();
match reg.entry(name.clone()) {
dashmap::mapref::entry::Entry::Occupied(_) => Err(SpawnError::SystemNameTaken(name)),
dashmap::mapref::entry::Entry::Vacant(v) => {
let system = Arc::new(ActorSystem {
name,
by_name: DashMap::new(),
by_id: DashMap::new(),
shutdown_policy: config.shutdown_policy,
});
v.insert(system.clone());
Ok(system)
}
}
}
pub fn get_named(name: &str) -> Option<Arc<ActorSystem>> {
systems().get(name).map(|entry| entry.value().clone())
}
pub fn all() -> Vec<String> {
systems().iter().map(|e| e.key().clone()).collect()
}
pub fn name(&self) -> &str {
&self.name
}
pub fn get<A: Actor>(&self, name: &str) -> Option<ActorHandle<A>> {
self.by_name.get(name).and_then(|e| e.downcast::<A>())
}
#[allow(dead_code)]
pub(crate) fn get_by_id<A: Actor>(&self, id: &ActorId) -> Option<ActorHandle<A>> {
self.by_id.get(id).and_then(|e| e.downcast::<A>())
}
pub async fn stop(&self, name: &str) -> Result<(), crate::error::SendError> {
let entry = self
.by_name
.get(name)
.ok_or(crate::error::SendError::Closed)?;
entry.stop(StopReason::Graceful).await
}
pub async fn kill(&self, name: &str) -> Result<(), crate::error::SendError> {
let entry = self
.by_name
.get(name)
.ok_or(crate::error::SendError::Closed)?;
entry.stop(StopReason::Kill).await
}
pub fn registered(&self) -> Vec<String> {
self.by_name.iter().map(|e| e.key().clone()).collect()
}
pub(crate) fn register_actor<A: Actor>(
&self,
id: &ActorId,
name: Option<&str>,
handle: &ActorHandle<A>,
) -> Result<(), SpawnError> {
if let Some(n) = name {
let entry = self.by_name.entry(n.to_string());
match entry {
dashmap::mapref::entry::Entry::Occupied(_) => {
return Err(SpawnError::NameTaken {
name: n.to_string(),
system: self.name.clone(),
});
}
dashmap::mapref::entry::Entry::Vacant(v) => {
v.insert(AnyActorHandle::new(handle));
}
}
}
self.by_id.insert(id.clone(), AnyActorHandle::new(handle));
Ok(())
}
pub(crate) fn unregister_by_id(&self, id: &ActorId) {
self.by_id.remove(id);
}
pub async fn shutdown(&self) {
self.shutdown_with(self.shutdown_policy.clone()).await;
}
pub async fn shutdown_with(&self, policy: ShutdownPolicy) {
let deadline = Instant::now() + policy.timeout;
self.by_name.clear();
let entries: Vec<(ActorId, AnyActorHandle)> = self
.by_id
.iter()
.map(|e| e.key().clone())
.collect::<Vec<_>>()
.into_iter()
.filter_map(|id| self.by_id.remove(&id))
.collect();
for (_id, entry) in &entries {
if Instant::now() >= deadline {
let _ = entry.stop(StopReason::Kill).await;
continue;
}
let result =
tokio::time::timeout(policy.per_actor_timeout, entry.stop(StopReason::Graceful))
.await;
if result.is_err() {
let _ = entry.stop(StopReason::Kill).await;
}
}
}
}