rtactor/
lib.rs

1//! A framework to implement the reactive pattern on real-time systems.
2//!
3//! # Example of reactive actor executed by a dispatcher in its thread
4//!
5//! ```
6//! use rtactor::dispatcher;
7//! use rtactor::{spawn_dispatcher, ActiveMailbox, Addr, Behavior, Message, ProcessContext, send_notification};
8//! use std::time::Duration;
9//!
10//!    // A very simple reactive actor that allows incrementing and querying an integer.
11//!    struct TestReactive {
12//!        pub val: i32,
13//!    }
14//!
15//!    enum Notification {
16//!        Increment(i32),
17//!    }
18//!
19//!    enum Request {
20//!        GetValue,
21//!        ToString(String /*label*/),
22//!    }
23//!
24//!    enum Response {
25//!        GetValue(i32),
26//!        ToString(String),
27//!    }
28//!
29//!    impl Behavior for TestReactive {
30//!        fn process_message<'a>(&mut self, context: &'a mut ProcessContext, msg: &Message) {
31//!            match msg {
32//!                Message::Notification(notif) => {
33//!                    if let Some(notif) = notif.data.downcast_ref::<Notification>() {
34//!                        match notif {
35//!                            Notification::Increment(increment) => self.val += increment,
36//!                        }
37//!                    }
38//!                }
39//!                Message::Request(request) => {
40//!                    if let Some(data) = request.data.downcast_ref::<Request>() {
41//!                        match data {
42//!                            Request::GetValue => {
43//!                                context.send_response(request, Response::GetValue(self.val));
44//!                            }
45//!
46//!                            Request::ToString(label) => context.send_response(
47//!                                &request,
48//!                                Response::ToString(format!("{label}: {}", self.val)),
49//!                            ),
50//!                        }
51//!                    }
52//!                }
53//!                _ => panic!(),
54//!            }
55//!        }
56//!    }
57//!
58//!    let initial_value = 0;
59//!
60//!    // Start a dispatcher inside its own thread.
61//!    // The active object is created with the closure called inside the dispatcher thread.
62//!    // This allows to have reactive object that are not movable between threads.
63//!    let (dispatcher_addr, join_handle, test_reactive_addr) = spawn_dispatcher(10, move |disp| {
64//!        // Create a reactive object on the heap.
65//!        let test_reactive = Box::new(TestReactive { val: initial_value });
66//!        // Move it inside the dispatcher and return the reactive address as the return of `setup_func`
67//!        disp.register_reactive(test_reactive)
68//!    });
69//!
70//!    send_notification(&test_reactive_addr, Notification::Increment(10))
71//!    .unwrap();
72//!
73//!    // Create an active object to interact with the reactive under test.
74//!    let mut prober = ActiveMailbox::new(1);
75//!
76//!    // Request the value.
77//!    let result = prober.request_for::<_, Response>(
78//!        &test_reactive_addr,
79//!        Request::GetValue,
80//!        Duration::from_secs(10),
81//!    );
82//!
83//!    if let Ok(Response::GetValue(val)) = result {
84//!        assert_eq!(val, 10);
85//!    } else {
86//!        panic!();
87//!    }
88//!
89//!    // An other notification.
90//!    send_notification(&test_reactive_addr, Notification::Increment(-3))
91//!    .unwrap();
92//!
93//!    // An other different request.
94//!    let result = prober.request_for::<_, Response>(
95//!        &test_reactive_addr,
96//!        Request::ToString("the value".to_string()),
97//!        Duration::from_secs(10),
98//!    );
99//!    if let Ok(Response::ToString(s)) = result {
100//!        assert_eq!(s, "the value: 7");
101//!    } else {
102//!        panic!();
103//!    }
104//!
105//!    // Request to stop the dispatcher using its own address.
106//!    let result = prober.request_for::<_, dispatcher::Response>(
107//!        &dispatcher_addr,
108//!        dispatcher::Request::StopDispatcher{},
109//!        Duration::from_secs(10),
110//!    );
111//!    if let Ok(dispatcher::Response::StopDispatcher()) = result {
112//!    } else {
113//!        panic!();
114//!    }
115//!
116//!    // Wait that the dispatcher thread finishes.
117//!    join_handle.join().unwrap();
118//! ```
119//!
120//! # Example of simulation of a reactive actor in a single threaded test
121//!
122//! ```
123//! use rtactor::simulation::SimulationDispatcher;
124//! use rtactor::{ActiveMailbox, Behavior, Message, ProcessContext, send_notification};
125//! use std::time::Duration;
126//!
127//! // A very simple reactive actor that allows incrementing and querying an integer.
128//! struct TestReactive {
129//!     pub val: i32,
130//! }
131//!
132//! enum Notification {
133//!     Increment(i32),
134//! }
135//!
136//! enum Request {
137//!     GetValue,
138//!     ToString(String /*label*/),
139//! }
140//!
141//! enum Response {
142//!     GetValue(i32),
143//!     ToString(String),
144//! }
145//!
146//! impl Behavior for TestReactive {
147//!     fn process_message<'a>(&mut self, context: &'a mut ProcessContext, msg: &Message) {
148//!         match msg {
149//!             Message::Notification(notif) => {
150//!                 if let Some(notif) = notif.data.downcast_ref::<Notification>() {
151//!                     match notif {
152//!                         Notification::Increment(increment) => self.val += increment,
153//!                     }
154//!                 }
155//!             }
156//!             Message::Request(request) => {
157//!                 if let Some(data) = request.data.downcast_ref::<Request>() {
158//!                     match data {
159//!                         Request::GetValue => {
160//!                             context.send_response(request, Response::GetValue(self.val))
161//!                         }
162//!
163//!                         Request::ToString(label) => context.send_response(
164//!                             &request,
165//!                             Response::ToString(format!("{label}: {}", self.val)),
166//!                         ),
167//!                     }
168//!                 }
169//!             }
170//!             _ => panic!(),
171//!         }
172//!     }
173//! }
174//!
175//! // Create a simulation dispatcher.
176//! let mut disp = SimulationDispatcher::new(10);
177//!
178//! // Create a reactive object on the heap.
179//! let test_reactive = Box::new(TestReactive { val: 0 });
180//!
181//! // Move it inside the dispatcher. It starts the dispatch of messages for it.
182//! let test_reactive_addr = disp.register_reactive(test_reactive);
183//!
184//! // Send a notification to the reactive.
185//!     send_notification(&test_reactive_addr, Notification::Increment(10))
186//!     .unwrap();
187//!
188//! // Create an active object to interact with the reactive under test.
189//! let mut prober = ActiveMailbox::new(1);
190//!
191//! // Ask the simulation dispatcher to simulate a request by the active actor.
192//! let result = disp.active_request_for::<_, Response>(
193//!     &mut prober,
194//!     &test_reactive_addr,
195//!     Request::GetValue,
196//!     Duration::from_secs(10),
197//! );
198//! if let Ok(Response::GetValue(val)) = result {
199//!     assert_eq!(val, 10);
200//! } else {
201//!     panic!();
202//! }
203//!
204//! // An other notification.
205//!     send_notification(&test_reactive_addr, Notification::Increment(-3))
206//!     .unwrap();
207//!
208//! // An other different request.
209//! let result = disp.active_request_for::<_, Response>(
210//!     &mut prober,
211//!     &test_reactive_addr,
212//!     Request::ToString("the value".to_string()),
213//!     Duration::from_secs(10),
214//! );
215//! if let Ok(Response::ToString(s)) = result {
216//!     assert_eq!(s, "the value: 7");
217//! } else {
218//!     panic!();
219//! }
220//!
221//! // No need to stop the dispatcher, there is no thread, everything is single threaded.
222//! // The reactive actor will be dropped by the drop of the simulation dispatcher.
223//! ```
224//!
225//! # Doc about actors
226//! A good explanation why the actor pattern is good for multitask:
227//! <https://getakka.net/articles/intro/what-problems-does-actor-model-solve.html>
228//!
229//! A well designed rust actor framework (but not suitable for real-time):
230//! <https://docs.rs/axiom/latest/axiom/>
231
232pub extern crate rtactor_macros;
233
234extern crate self as rtactor;
235
236mod active;
237mod actor;
238mod reactive;
239
240#[cfg(feature = "mockall")]
241mod reactive_mocker;
242
243pub mod dispatcher;
244pub mod mpsc_dispatcher;
245pub mod profiled_actor;
246pub mod profiling_aggregator;
247pub mod simulation;
248
249pub use actor::send_notification;
250pub use actor::Addr;
251pub use actor::Error;
252pub use actor::Message;
253pub use actor::Notification;
254pub use actor::Request;
255pub use actor::RequestId;
256pub use actor::Response;
257
258#[deprecated = "use ActiveMailbox instead"]
259#[allow(deprecated)]
260pub use active::ActiveActor;
261
262pub use active::ActiveMailbox;
263pub use active::SyncAccessor;
264
265pub use mpsc_dispatcher::spawn_dispatcher;
266pub use mpsc_dispatcher::MpscDispatcher;
267pub use reactive::Behavior;
268pub use reactive::DummyBehavior;
269pub use reactive::Instant;
270pub use reactive::ProcessContext;
271pub use reactive::Timeout;
272pub use reactive::Timer;
273
274#[cfg(feature = "async-actor")]
275pub mod async_actor;
276
277#[cfg(feature = "async-actor")]
278pub use async_actor::{AsyncAccessor, AsyncMailbox};
279
280#[cfg(feature = "mockall")]
281pub use reactive::MockBehavior;
282
283// This crate used in the interface is reexported to allow user to build with the same version.
284// see https://doc.rust-lang.org/rustdoc/write-documentation/re-exports.html
285#[cfg(feature = "mockall")]
286pub use mockall;