1use std::any::TypeId;
12use std::collections::HashMap;
13use std::future::Future;
14use std::pin::Pin;
15use std::sync::LazyLock;
16
17use crate::Actor;
18use crate::ActorEnvironment;
19use crate::AnyActorHandle;
20use crate::Data;
21use crate::id::Uid;
22use crate::proc::InstanceCell;
23use crate::proc::Proc;
24
25pub const USER_PORT_OFFSET: u64 = 1024;
27
28#[macro_export]
40macro_rules! register_spawnable {
41 ($actor:ty) => {
42 const _: () = {
43 static NAME: std::sync::LazyLock<&'static str> = std::sync::LazyLock::new(|| {
44 <$actor as $crate::internal_macro_support::typeuri::Named>::typename()
45 });
46
47 $crate::internal_macro_support::inventory::submit! {
48 $crate::actor::remote::SpawnableActor {
49 name: &NAME,
50 gspawn_root_bind: <$actor as $crate::actor::RemoteSpawn>::gspawn_root_bind,
51 gspawn_child: <$actor as $crate::actor::RemoteSpawn>::gspawn_child,
52 get_type_id: <$actor as $crate::actor::RemoteSpawn>::get_type_id,
53 }
54 }
55 };
56 };
57}
58
59#[derive(Debug)]
62pub struct SpawnableActor {
63 pub name: &'static LazyLock<&'static str>,
69
70 pub gspawn_root_bind: fn(
74 &Proc,
75 Uid,
76 Data,
77 ActorEnvironment,
78 ) -> Pin<
79 Box<dyn Future<Output = Result<crate::ActorAddr, anyhow::Error>> + Send>,
80 >,
81
82 pub gspawn_child:
86 fn(
87 &Proc,
88 InstanceCell,
89 Uid,
90 Data,
91 ActorEnvironment,
92 ) -> Pin<Box<dyn Future<Output = Result<AnyActorHandle, anyhow::Error>> + Send>>,
93
94 pub get_type_id: fn() -> TypeId,
97}
98
99inventory::collect!(SpawnableActor);
100
101#[derive(Debug)]
104pub struct Remote {
105 by_name: HashMap<&'static str, &'static SpawnableActor>,
106 by_type_id: HashMap<TypeId, &'static SpawnableActor>,
107}
108
109impl Remote {
110 pub fn collect() -> Self {
112 let mut by_name = HashMap::new();
113 let mut by_type_id = HashMap::new();
114 for entry in inventory::iter::<SpawnableActor> {
115 if by_name.insert(**entry.name, entry).is_some() {
116 panic!("actor name {} registered multiple times", **entry.name);
117 }
118 let type_id = (entry.get_type_id)();
119 if by_type_id.insert(type_id, entry).is_some() {
120 panic!(
121 "type id {:?} ({}) registered multiple times",
122 type_id, **entry.name
123 );
124 }
125 }
126 Self {
127 by_name,
128 by_type_id,
129 }
130 }
131
132 pub fn global() -> &'static Self {
134 static REMOTE: LazyLock<Remote> = LazyLock::new(Remote::collect);
135 &REMOTE
136 }
137
138 pub fn name_of<A: Actor>(&self) -> Option<&'static str> {
140 self.by_type_id
141 .get(&TypeId::of::<A>())
142 .map(|entry| **entry.name)
143 }
144
145 pub async fn gspawn(
149 &self,
150 proc: &Proc,
151 actor_type: &str,
152 actor_uid: Uid,
153 params: Data,
154 environment: ActorEnvironment,
155 ) -> Result<crate::ActorAddr, anyhow::Error> {
156 let entry = self
157 .by_name
158 .get(actor_type)
159 .ok_or_else(|| anyhow::anyhow!("actor type {} not registered", actor_type))?;
160 (entry.gspawn_root_bind)(proc, actor_uid, params, environment).await
161 }
162
163 pub async fn gspawn_child(
166 &self,
167 proc: &Proc,
168 parent: InstanceCell,
169 actor_type: &str,
170 actor_uid: Uid,
171 params: Data,
172 ) -> Result<AnyActorHandle, anyhow::Error> {
173 let environment = parent.actor_environment().clone();
174 self.gspawn_child_in_environment(proc, parent, actor_type, actor_uid, params, environment)
175 .await
176 }
177
178 pub(crate) async fn gspawn_child_in_environment(
181 &self,
182 proc: &Proc,
183 parent: InstanceCell,
184 actor_type: &str,
185 actor_uid: Uid,
186 params: Data,
187 environment: ActorEnvironment,
188 ) -> Result<AnyActorHandle, anyhow::Error> {
189 let entry = self
190 .by_name
191 .get(actor_type)
192 .ok_or_else(|| anyhow::anyhow!("actor type {} not registered", actor_type))?;
193 (entry.gspawn_child)(proc, parent, actor_uid, params, environment).await
194 }
195}
196
197#[cfg(test)]
198mod tests {
199 use std::assert_matches;
200
201 use async_trait::async_trait;
202
203 use super::*;
204 use crate as hyperactor; use crate::Context;
206 use crate::Handler;
207 use crate::RemoteSpawn;
208 use crate::id::Label;
209
210 #[derive(Debug)]
211 #[hyperactor::export(())]
212 struct MyActor;
213
214 #[async_trait]
215 impl Actor for MyActor {}
216
217 #[async_trait]
218 impl RemoteSpawn for MyActor {
219 type Params = bool;
220
221 async fn new(params: bool, _environment: &ActorEnvironment) -> Result<Self, anyhow::Error> {
222 if params {
223 Ok(MyActor)
224 } else {
225 Err(anyhow::anyhow!("some failure"))
226 }
227 }
228 }
229
230 #[async_trait]
231 impl Handler<()> for MyActor {
232 async fn handle(&mut self, _cx: &Context<Self>, _message: ()) -> anyhow::Result<()> {
233 unimplemented!()
234 }
235 }
236
237 register_spawnable!(MyActor);
238
239 #[derive(Debug, Default)]
240 #[hyperactor::export(())]
241 struct GenericActor<T>(std::marker::PhantomData<T>);
242
243 #[async_trait]
244 impl<T: Send + 'static> Actor for GenericActor<T> {}
245
246 #[async_trait]
247 impl<T: Send + Sync + 'static> Handler<()> for GenericActor<T> {
248 async fn handle(&mut self, _cx: &Context<Self>, _message: ()) -> anyhow::Result<()> {
249 unimplemented!()
250 }
251 }
252
253 register_spawnable!(GenericActor<u64>);
254 register_spawnable!(GenericActor<bool>);
255
256 #[tokio::test]
257 async fn test_registry() {
258 let remote = Remote::collect();
259 assert_matches!(
260 remote.name_of::<MyActor>(),
261 Some("hyperactor::actor::remote::tests::MyActor")
262 );
263 assert_matches!(
264 remote.name_of::<GenericActor<u64>>(),
265 Some("hyperactor::actor::remote::tests::GenericActor<u64>")
266 );
267 assert_matches!(
268 remote.name_of::<GenericActor<bool>>(),
269 Some("hyperactor::actor::remote::tests::GenericActor<bool>")
270 );
271 assert_ne!(
272 <GenericActor<u64> as typeuri::Named>::typename(),
273 <GenericActor<bool> as typeuri::Named>::typename()
274 );
275
276 let _ = remote
277 .gspawn(
278 &Proc::isolated(),
279 "hyperactor::actor::remote::tests::MyActor",
280 Uid::instance(Label::new("actor").unwrap()),
281 bincode::serde::encode_to_vec(true, bincode::config::legacy()).unwrap(),
282 ActorEnvironment::default(),
283 )
284 .await
285 .unwrap();
286
287 let err = remote
288 .gspawn(
289 &Proc::isolated(),
290 "hyperactor::actor::remote::tests::MyActor",
291 Uid::instance(Label::new("actor").unwrap()),
292 bincode::serde::encode_to_vec(false, bincode::config::legacy()).unwrap(),
293 ActorEnvironment::default(),
294 )
295 .await
296 .unwrap_err();
297
298 assert_eq!(err.to_string().as_str(), "some failure");
299 }
300
301 #[tokio::test]
302 async fn test_instance_gspawn_child_returns_erased_handle() {
303 let proc = Proc::isolated();
304 let parent = proc.client("parent");
305
306 let child = parent
307 .gspawn(
308 "hyperactor::actor::remote::tests::MyActor",
309 bincode::serde::encode_to_vec(true, bincode::config::legacy()).unwrap(),
310 )
311 .await
312 .unwrap();
313
314 assert!(!child.actor_id().is_root());
315 assert!(child.downcast::<MyActor>().is_some());
316 assert!(child.downcast::<GenericActor<u64>>().is_none());
317
318 child.stop("test").unwrap();
319 child.await;
320 }
321
322 #[tokio::test]
323 async fn test_instance_gspawn_uid_uses_explicit_uid() {
324 let proc = Proc::isolated();
325 let parent = proc.client("parent");
326 let uid = Uid::instance(Label::new("child").unwrap());
327
328 let child = parent
329 .gspawn_uid(
330 "hyperactor::actor::remote::tests::MyActor",
331 uid.clone(),
332 bincode::serde::encode_to_vec(true, bincode::config::legacy()).unwrap(),
333 )
334 .await
335 .unwrap();
336
337 assert_eq!(child.actor_id().uid(), &uid);
338
339 child.stop("test").unwrap();
340 child.await;
341 }
342
343 #[tokio::test]
344 async fn test_instance_gspawn_uid_rejects_duplicate_uid() {
345 let proc = Proc::isolated();
346 let parent = proc.client("parent");
347 let uid = Uid::instance(Label::new("child").unwrap());
348
349 let child = parent
350 .gspawn_uid(
351 "hyperactor::actor::remote::tests::MyActor",
352 uid.clone(),
353 bincode::serde::encode_to_vec(true, bincode::config::legacy()).unwrap(),
354 )
355 .await
356 .unwrap();
357
358 let err = parent
359 .gspawn_uid(
360 "hyperactor::actor::remote::tests::MyActor",
361 uid,
362 bincode::serde::encode_to_vec(true, bincode::config::legacy()).unwrap(),
363 )
364 .await
365 .unwrap_err();
366
367 assert!(err.to_string().contains("has already been spawned"));
368
369 child.stop("test").unwrap();
370 child.await;
371 }
372}