Skip to main content

meerkat_mob/runtime/
spawn_policy.rs

1use crate::MobRuntimeMode;
2use crate::ids::{MeerkatId, ProfileName};
3#[cfg(target_arch = "wasm32")]
4use crate::tokio;
5use async_trait::async_trait;
6use std::sync::Arc;
7use tokio::sync::RwLock;
8
9/// Specification for auto-spawning a member when a [`SpawnPolicy`] resolves a target.
10#[derive(Debug, Clone)]
11pub struct SpawnSpec {
12    pub profile: ProfileName,
13    pub runtime_mode: Option<MobRuntimeMode>,
14}
15
16/// Policy that determines whether an unknown meerkat ID should trigger an
17/// automatic spawn.
18///
19/// Attached to the [`MobActor`] at build-time or set dynamically at runtime.
20/// When a `send_message` (external turn) targets an unknown meerkat and a
21/// spawn policy is present, the actor calls [`resolve`] to decide whether
22/// to auto-provision a member.
23#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
24#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
25pub trait SpawnPolicy: Send + Sync {
26    /// Given an unknown meerkat ID, return a spawn spec if the policy
27    /// should auto-spawn a member for it, or `None` to fall through
28    /// to the normal `MeerkatNotFound` error.
29    async fn resolve(&self, target: &MeerkatId) -> Option<SpawnSpec>;
30}
31
32/// Explicit owner for mutable runtime spawn-policy state.
33#[derive(Default)]
34pub struct SpawnPolicyService {
35    policy: RwLock<Option<Arc<dyn SpawnPolicy>>>,
36}
37
38impl SpawnPolicyService {
39    pub fn new() -> Self {
40        Self::default()
41    }
42
43    pub async fn set(&self, policy: Option<Arc<dyn SpawnPolicy>>) {
44        *self.policy.write().await = policy;
45    }
46
47    pub async fn resolve(&self, target: &MeerkatId) -> Option<SpawnSpec> {
48        let policy = self.policy.read().await.clone();
49        let policy = policy?;
50        policy.resolve(target).await
51    }
52}
53
54#[cfg(test)]
55mod tests {
56    use super::*;
57
58    struct StaticPolicy;
59
60    #[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
61    #[cfg_attr(not(target_arch = "wasm32"), async_trait)]
62    impl SpawnPolicy for StaticPolicy {
63        async fn resolve(&self, target: &MeerkatId) -> Option<SpawnSpec> {
64            Some(SpawnSpec {
65                profile: ProfileName::from(format!("role-{target}")),
66                runtime_mode: Some(MobRuntimeMode::TurnDriven),
67            })
68        }
69    }
70
71    #[tokio::test]
72    async fn spawn_policy_service_swaps_runtime_policy_authority() {
73        let service = SpawnPolicyService::new();
74        let target = MeerkatId::from("worker-1");
75        assert!(service.resolve(&target).await.is_none());
76
77        service.set(Some(Arc::new(StaticPolicy))).await;
78        let resolved = service
79            .resolve(&target)
80            .await
81            .expect("policy should resolve");
82        assert_eq!(resolved.profile, ProfileName::from("role-worker-1"));
83        assert_eq!(resolved.runtime_mode, Some(MobRuntimeMode::TurnDriven));
84    }
85}