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    sync::OnceLock,
21};
22
23use ahash::{HashMap, HashMapExt};
24use ustr::Ustr;
25
26use super::Actor;
27
28pub struct ActorRegistry {
29    actors: RefCell<HashMap<Ustr, Rc<UnsafeCell<dyn Actor>>>>,
30}
31
32impl Debug for ActorRegistry {
33    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
34        let actors_ref = self.actors.borrow();
35        let keys: Vec<&Ustr> = actors_ref.keys().collect();
36        f.debug_struct(stringify!(ActorRegistry))
37            .field("actors", &keys)
38            .finish()
39    }
40}
41
42impl Default for ActorRegistry {
43    fn default() -> Self {
44        Self::new()
45    }
46}
47
48impl ActorRegistry {
49    pub fn new() -> Self {
50        Self {
51            actors: RefCell::new(HashMap::new()),
52        }
53    }
54
55    pub fn insert(&self, id: Ustr, actor: Rc<UnsafeCell<dyn Actor>>) {
56        self.actors.borrow_mut().insert(id, actor);
57    }
58
59    pub fn get(&self, id: &Ustr) -> Option<Rc<UnsafeCell<dyn Actor>>> {
60        self.actors.borrow().get(id).cloned()
61    }
62}
63
64// SAFETY: ActorRegistry uses non-thread-safe internals (Rc, RefCell, UnsafeCell).
65// We mark it Sync + Send to satisfy `OnceLock<T>: Sync` for static initialization,
66// but all registry operations must still occur on a single thread. Moving or accessing
67// from multiple threads is undefined behavior.
68unsafe impl Sync for ActorRegistry {}
69unsafe impl Send for ActorRegistry {}
70
71static ACTOR_REGISTRY: OnceLock<ActorRegistry> = OnceLock::new();
72
73pub fn get_actor_registry() -> &'static ActorRegistry {
74    ACTOR_REGISTRY.get_or_init(ActorRegistry::new)
75}
76
77pub fn register_actor(actor: Rc<UnsafeCell<dyn Actor>>) {
78    // SAFETY: We only immutably borrow the actor to call `id()`,
79    // which takes &self. This does not violate aliasing or mutable borrow rules.
80    let actor_id = unsafe { &*actor.get() }.id();
81    get_actor_registry().insert(actor_id, actor);
82}
83
84pub fn get_actor(id: &Ustr) -> Option<Rc<UnsafeCell<dyn Actor>>> {
85    get_actor_registry().get(id)
86}
87
88/// Returns a mutable reference to the registered actor of type `T` for the given `id`.
89///
90/// # Panics
91///
92/// Panics if no actor with the specified `id` is found in the registry.
93#[allow(clippy::mut_from_ref)]
94pub fn get_actor_unchecked<T: Actor>(id: &Ustr) -> &mut T {
95    let actor = get_actor(id).unwrap_or_else(|| panic!("Actor for {id} not found"));
96    unsafe { &mut *(actor.get() as *mut _ as *mut T) }
97}
98
99// Clears the global actor registry (for test isolation).
100#[cfg(test)]
101pub fn clear_actor_registry() {
102    // SAFETY: Clearing registry actors; tests should run single-threaded for actor registry
103    get_actor_registry().actors.borrow_mut().clear();
104}