nautilus_common/actor/
registry.rs

1// -------------------------------------------------------------------------------------------------
2//  Copyright (C) 2015-2025 Posei Systems Pty Ltd. All rights reserved.
3//  https://poseitrader.io
4//
5//  Licensed under the GNU Lesser General Public License Version 3.0 (the "License");
6//  You may not use this file except in compliance with the License.
7//  You may obtain a copy of the License at https://www.gnu.org/licenses/lgpl-3.0.en.html
8//
9//  Unless required by applicable law or agreed to in writing, software
10//  distributed under the License is distributed on an "AS IS" BASIS,
11//  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12//  See the License for the specific language governing permissions and
13//  limitations under the License.
14// -------------------------------------------------------------------------------------------------
15
16use std::{
17    cell::{RefCell, UnsafeCell},
18    fmt::Debug,
19    rc::Rc,
20};
21
22use ahash::{HashMap, HashMapExt};
23use ustr::Ustr;
24
25use super::Actor;
26
27thread_local! {
28    static ACTOR_REGISTRY: ActorRegistry = ActorRegistry::new();
29}
30
31/// Registry for storing actors.
32pub struct ActorRegistry {
33    actors: RefCell<HashMap<Ustr, Rc<UnsafeCell<dyn Actor>>>>,
34}
35
36impl Debug for ActorRegistry {
37    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
38        let actors_ref = self.actors.borrow();
39        let keys: Vec<&Ustr> = actors_ref.keys().collect();
40        f.debug_struct(stringify!(ActorRegistry))
41            .field("actors", &keys)
42            .finish()
43    }
44}
45
46impl Default for ActorRegistry {
47    fn default() -> Self {
48        Self::new()
49    }
50}
51
52impl ActorRegistry {
53    pub fn new() -> Self {
54        Self {
55            actors: RefCell::new(HashMap::new()),
56        }
57    }
58
59    pub fn insert(&self, id: Ustr, actor: Rc<UnsafeCell<dyn Actor>>) {
60        let mut actors = self.actors.borrow_mut();
61        if actors.contains_key(&id) {
62            log::warn!("Replacing existing actor with id: {id}");
63        }
64        actors.insert(id, actor);
65    }
66
67    pub fn get(&self, id: &Ustr) -> Option<Rc<UnsafeCell<dyn Actor>>> {
68        self.actors.borrow().get(id).cloned()
69    }
70
71    /// Returns the number of registered actors.
72    pub fn len(&self) -> usize {
73        self.actors.borrow().len()
74    }
75
76    /// Checks if the registry is empty.
77    pub fn is_empty(&self) -> bool {
78        self.actors.borrow().is_empty()
79    }
80
81    /// Removes an actor from the registry.
82    pub fn remove(&self, id: &Ustr) -> Option<Rc<UnsafeCell<dyn Actor>>> {
83        self.actors.borrow_mut().remove(id)
84    }
85
86    /// Checks if an actor with the `id` exists.
87    pub fn contains(&self, id: &Ustr) -> bool {
88        self.actors.borrow().contains_key(id)
89    }
90}
91
92pub fn get_actor_registry() -> &'static ActorRegistry {
93    ACTOR_REGISTRY.with(|registry| unsafe {
94        // SAFETY: We return a static reference that lives for the lifetime of the thread.
95        // Since this is thread_local storage, each thread has its own instance.
96        // The transmute extends the lifetime to 'static which is safe because
97        // thread_local ensures the registry lives for the thread's entire lifetime.
98        std::mem::transmute::<&ActorRegistry, &'static ActorRegistry>(registry)
99    })
100}
101
102/// Registers an actor.
103pub fn register_actor<T>(actor: T) -> Rc<UnsafeCell<T>>
104where
105    T: Actor + 'static,
106{
107    let actor_id = actor.id();
108    let actor_ref = Rc::new(UnsafeCell::new(actor));
109
110    // Register as Actor (message handling only)
111    let actor_trait_ref: Rc<UnsafeCell<dyn Actor>> = actor_ref.clone();
112    get_actor_registry().insert(actor_id, actor_trait_ref);
113
114    actor_ref
115}
116
117pub fn get_actor(id: &Ustr) -> Option<Rc<UnsafeCell<dyn Actor>>> {
118    get_actor_registry().get(id)
119}
120
121/// Returns a mutable reference to the registered actor of type `T` for the `id`.
122///
123/// # Safety
124///
125/// This function bypasses Rust's borrow checker and type safety.
126/// Caller must ensure:
127/// - Actor with `id` exists in registry.
128/// - No other mutable references to the same actor exist.
129/// - Type `T` matches the actual actor type.
130///
131/// # Panics
132///
133/// Panics if no actor with the specified `id` is found in the registry.
134#[allow(clippy::mut_from_ref)]
135pub fn get_actor_unchecked<T: Actor>(id: &Ustr) -> &mut T {
136    let actor = get_actor(id).unwrap_or_else(|| panic!("Actor for {id} not found"));
137    // SAFETY: Caller must ensure no aliasing and correct type
138    unsafe { &mut *(actor.get() as *mut _ as *mut T) }
139}
140
141/// Safely attempts to get a mutable reference to the registered actor.
142///
143/// Returns `None` if the actor is not found, avoiding panics.
144#[allow(clippy::mut_from_ref)]
145pub fn try_get_actor_unchecked<T: Actor>(id: &Ustr) -> Option<&mut T> {
146    let actor = get_actor(id)?;
147    // SAFETY: Registry guarantees valid actor pointers
148    Some(unsafe { &mut *(actor.get() as *mut _ as *mut T) })
149}
150
151/// Checks if an actor with the `id` exists in the registry.
152pub fn actor_exists(id: &Ustr) -> bool {
153    get_actor_registry().contains(id)
154}
155
156/// Returns the number of registered actors.
157pub fn actor_count() -> usize {
158    get_actor_registry().len()
159}
160
161#[cfg(test)]
162/// Clears the actor registry (for test isolation).
163pub fn clear_actor_registry() {
164    // SAFETY: Clearing registry actors; tests should run single-threaded for actor registry
165    get_actor_registry().actors.borrow_mut().clear();
166}