swactor/crates/std/src/ctx_ext.rs
Developer 812826bb0a feat: extract swactor-std crate with RuntimeExtension hook pattern
Debloat the core swactor crate by moving higher-level features to a new
swactor-std crate, leaving core with only the essential actor primitives
(spawn, send, stop, timers, lifecycle hooks).

Phase 1: Move Supervisor + Router to swactor-std
- Supervisor (ChildSpec, RestartPolicy, SupervisorStrategy) and Router
  (RoutingStrategy) extracted to crates/std/
- ~465 lines removed from src/actor.rs

Phase 2: Remove restart fields from Actor<A>
- Actor<A> slimmed to { inner: A } — no restart_factory, max_restarts
- Removed spawn_restartable from Ctx and Runtime
- Simplified panic handler: always poison, never inline restart
- Supervision-based restart via Supervisor in swactor-std

Phase 3: RuntimeExtension trait + registry extraction
- New src/extension.rs: RuntimeExtension trait (on_actor_death,
  cleanup_dead, as_any) — single hybrid hook for lifecycle events
- ContextInner slimmed from 11 methods to 5 (removed 7 registry
  methods, added extension())
- Ctx slimmed: removed all registry methods, added extension() accessor
- Runtime: removed 3 registry fields + 9 methods, added
  with_extension() builder + extension() accessor
- TickContext: replaced 3 registry fields with extension hook
- tick_once phase 7: uses ext.on_actor_death() + ext.cleanup_dead()
- Moved NameRegistry, MonitorRegistry, GroupRegistry from delivery.rs
  to swactor-std
- StdExtension wraps the 3 registries, implements RuntimeExtension
- Extension traits: CtxMonitoring, CtxNaming, CtxGroups on Ctx;
  RuntimeNaming, RuntimeGroups on Runtime
- Down, StopReason, MonitorRef, handle_down remain in core
- AddrMap/AddrSet/AddrBuildHasher made public, re-exported
- MonitorRef::from_raw(u64) added for cross-crate construction

All 140 tests pass. Benchmarks updated.

Authored by Claude, lovingly guided by Zachery Aaron Shores-Chmielewski
2026-02-13 06:59:29 +00:00

113 lines
3.7 KiB
Rust

use swactor::actor::{ActorAddress, ActorInterface, Ctx, Message, MonitorRef};
use swactor::Error;
use crate::StdExtension;
fn get_ext<'a>(ctx: &'a Ctx) -> &'a StdExtension {
ctx.extension()
.expect("StdExtension not installed — use Runtime::with_extension()")
.as_any()
.downcast_ref::<StdExtension>()
.expect("Extension is not StdExtension")
}
/// Monitoring extension for [`Ctx`].
///
/// Provides `monitor` / `demonitor` via the [`StdExtension`] monitor registry.
pub trait CtxMonitoring {
/// Subscribe to death notifications from `target`. Returns a [`MonitorRef`]
/// that can be used to cancel the subscription.
fn monitor(&self, target: ActorAddress) -> MonitorRef;
/// Cancel a monitor subscription.
fn demonitor(&self, mref: MonitorRef);
}
impl CtxMonitoring for Ctx<'_> {
fn monitor(&self, target: ActorAddress) -> MonitorRef {
get_ext(self).monitor_registry.register(self.self_addr(), target)
}
fn demonitor(&self, mref: MonitorRef) {
get_ext(self).monitor_registry.deregister(mref);
}
}
/// Naming extension for [`Ctx`].
///
/// Provides `where_is`, `register_name`, and `spawn_named` via the [`StdExtension`]
/// name registry.
pub trait CtxNaming {
/// Look up an actor address by its registered name.
fn where_is(&self, name: &str) -> Option<ActorAddress>;
/// Register a name for the given address.
fn register_name(&self, name: impl Into<String>, addr: ActorAddress) -> Result<(), Error>;
/// Spawn an actor with a registered name, returning its address.
fn spawn_named<A: ActorInterface>(&self, name: impl Into<String>, actor: A) -> Result<ActorAddress, Error>;
}
impl CtxNaming for Ctx<'_> {
fn where_is(&self, name: &str) -> Option<ActorAddress> {
get_ext(self).name_registry.lookup(name)
}
fn register_name(&self, name: impl Into<String>, addr: ActorAddress) -> Result<(), Error> {
get_ext(self).name_registry.register(name.into(), addr)
}
fn spawn_named<A: ActorInterface>(&self, name: impl Into<String>, actor: A) -> Result<ActorAddress, Error> {
let name = name.into();
let addr = self.spawn(actor)?;
if let Err(e) = get_ext(self).name_registry.register(name, addr) {
let _ = self.stop_actor(addr);
return Err(e);
}
Ok(addr)
}
}
/// Group extension for [`Ctx`].
///
/// Provides `join_group`, `leave_group`, `publish`, and `group_members` via
/// the [`StdExtension`] group registry.
pub trait CtxGroups {
/// Add this actor to a named group.
fn join_group(&self, group: impl Into<String>);
/// Remove this actor from a named group.
fn leave_group(&self, group: &str);
/// Broadcast a message to all members of a named group.
/// Returns the number of messages successfully enqueued.
fn publish<M: Message>(&self, group: &str, msg: M) -> usize;
/// Return all members of a named group.
fn group_members(&self, group: &str) -> Vec<ActorAddress>;
}
impl CtxGroups for Ctx<'_> {
fn join_group(&self, group: impl Into<String>) {
get_ext(self).group_registry.join(group.into(), self.self_addr());
}
fn leave_group(&self, group: &str) {
get_ext(self).group_registry.leave(group, &self.self_addr());
}
fn publish<M: Message>(&self, group: &str, msg: M) -> usize {
let members = get_ext(self).group_registry.members(group);
let mut count = 0;
for member in &members {
if self.send(*member, msg.clone()).is_ok() {
count += 1;
}
}
count
}
fn group_members(&self, group: &str) -> Vec<ActorAddress> {
get_ext(self).group_registry.members(group)
}
}