1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219
//! # Puppeteer
//!
//! Puppeteer (pptr) is an actor-based framework designed to simplify the
//! development of composable and maintainable asynchronous systems in Rust. With
//! its type-driven API design, Puppeteer provides a safe and convenient way to
//! create and manage actors that communicate through message passing.
//!
//! Puppeteer is built with composability, encapsulation, and single responsibility
//! in mind, rather than focusing on building large-scale distributed systems. This
//! is reflected in its approach of allowing only one instance of an actor per
//! type, promoting a modular and maintainable architecture. Each puppet
//! encapsulates a specific responsibility and communicates with other puppets
//! through message passing, leading to a more composable system.
//!
//! While this design decision might seem like a limitation, it excels in building
//! regular systems that prioritize these principles, providing a high degree of
//! scalability for typical applications. Puppeteer's approach to building systems
//! as a collection of independent, reusable components that communicate through
//! message passing is driven by Alan Kay's vision of object-oriented programming.
//!
//! Rather than being another copy of Erlang OTP or Akka, Puppeteer has different
//! goals and aims to provide a fresh perspective at the intersection of the actor
//! model and object-oriented programming through message passing. Whether you're
//! building a responsive user interface or a modular system that values
//! composability and maintainability, Puppeteer makes it easy to write efficient
//! and maintainable asynchronous code in Rust.
//!
//! ## Key Features
//!
//! - **Type-Driven Development**: Puppeteer leverages Rust's strong type system to ensure
//! compile-time safety and runtime reliability. It provides a seamless and expressive API for
//! defining actors and their message-handling behaviors.
//!
//! - **Ergonomic API**: Puppeteer offers a clean and intuitive API for creating actors and handling
//! messages. It integrates seamlessly with popular Rust libraries and frameworks, enabling
//! developers to build highly concurrent applications with ease.
//!
//! - **Effortless Asynchronous Programming**: Puppeteer simplifies asynchronous programming in Rust
//! by utilizing the Tokio runtime and working well with Rust's `async`/`await`. It allows you to
//! write asynchronous code that is easy to read and understand.
//!
//! - **Performance-Driven**: Puppeteer is designed with performance in mind. It efficiently handles
//! messages concurrently and in parallel, offering different execution modes to suit your specific
//! needs.
//!
//! - **Flexible Supervision**: Puppeteer provides a versatile oversight system that allows you to
//! supervise and organize actors hierarchically. It offers predefined supervision strategies and
//! supports custom strategies to handle errors and maintain system stability.
//!
//! - **Robust Error Handling**: Puppeteer includes built-in features for monitoring actors and
//! handling errors. It provides mechanisms to report and manage critical and non critical errors,
//! ensuring the stability and reliability of your system.
//!
//! - **Lifecycle Management**: Puppeteer offers built-in preimplemented methods for managing the
//! lifecycle of actors, including initialization, startup, shutdown, and state reset.
//!
//! - **Resource Management**: Puppeteer provides a way to manage resources that can be shared among
//! actors. In Rust, some libraries provide structures that are not easy and idiomatic to send
//! between actor, making it challenging to pass them through messages or include them as part of
//! an actor's state due to Rust's ownership rules. Examples include UI libraries where context and
//! ui state cannot be easily shared. Puppeteer's resource management system offers a solution to
//! handle such cases by allowing actors to safely access and modify shared resources. This enables
//! scenarios like implementing The Elm Architecture (TEA) or Redux-like state management, where
//! actor or actors can update a common shared state. While sharing state among actors is
//! generally not recommended, Puppeteer's resource management provides a practical approach when
//! it becomes necessary, ensuring safety and efficiency in the actor-based system.
//!
//! ## Getting Started
//!
//! ```rust
//! use pptr::prelude::*;
//!
//! #[derive(Debug, Clone, Default)]
//! struct PingActor;
//!
//! impl Puppet for PingActor {
//! // This actor uses the 'OneForAll' supervision strategy.
//! // If any child actor fails, all child actors will be restarted.
//! type Supervision = OneForAll;
//!
//! // The 'reset' method is called when the actor needs to reset its state.
//! // In this example, we simply return a default instance of 'PingActor'.
//! async fn reset(&self, _ctx: &Context<Self>) -> Result<Self, CriticalError> {
//! Ok(Self::default())
//! }
//! }
//!
//! // We define a 'Ping' message that contains a counter value.
//! #[derive(Debug)]
//! struct Ping(u32);
//!
//! // The 'Handler' trait defines how the actor should handle incoming messages.
//! // It is a generic trait, which allows defining message handling for specific message types,
//! // rather than using a single large enum for all possible messages.
//! // This provides better type safety and easier maintainability.
//! // By implementing the 'Handler' trait for a particular message type and actor,
//! // you can define the specific behavior for handling that message within the actor.
//! // Additionally, the 'Handler' trait can be implemented multiple times for the same message type,
//! // allowing different actors to handle the same message type in their own unique way.
//! // This flexibility enables better separation of concerns and modular design in the actor system.
//! impl Handler<Ping> for PingActor {
//!
//! // The 'Response' associated type specifies the type of the response returned by the handler.
//! // In this case, the response type is '()', which means the handler doesn't return any meaningful value.
//! // It is common to use '()' as the response type when the handler only performs side effects and doesn't need to return a specific value.
//! type Response = ();
//!
//! // The 'Executor' associated type specifies the execution strategy for handling messages.
//! // It determines how the actor processes incoming messages concurrently.
//! // The 'SequentialExecutor' processes messages sequentially, one at a time, in the order they are received.
//! // This ensures that the handler for each message is executed to completion before processing the next message.
//! // The 'SequentialExecutor' is suitable when the order of message processing is important and the handler doesn't perform any blocking operations.
//! type Executor = SequentialExecutor;
//!
//! // The 'handle_message' method is called when the actor receives a 'Ping' message.
//! // It prints the received counter value and sends a 'Pong' message to 'PongActor'
//! // with an incremented counter value, until the counter reaches 10.
//! async fn handle_message(&mut self, msg: Ping, ctx: &Context<Self>) -> Result<Self::Response, PuppetError> {
//! // The 'ctx' parameter is a reference to the 'Context' struct, which encapsulates
//! // the actor's execution context and provides access to the same methods as the 'pptr' instance.
//! // It allows the actor to send messages, spawn new actors, and perform other actions.
//! // If an actor is spawned using 'ctx', it automatically assigns the spawning actor as its supervisor.
//! // The 'ctx' parameter enables safe and consistent interaction with the actor system,
//! // abstracting away the underlying complexity.
//!
//! println!("Ping received: {}", msg.0);
//! if msg.0 < 10 {
//! // By using 'ctx.send', the actor can send messages to other actors directly from the message handler,
//! // ensuring proper error propagation and potential supervision.
//! ctx.send::<PongActor, _>(Pong(msg.0 + 1))?;
//! } else {
//! println!("Ping-Pong finished!");
//! }
//!
//! Ok(())
//! }
//! }
//!
//! #[derive(Debug, Clone, Default)]
//! struct PongActor;
//!
//! // By default, similar to 'PingActor', the 'reset' method returns a default instance of 'PongActor'.
//! impl Puppet for PongActor {
//! type Supervision = OneForAll;
//! }
//!
//! // We define a 'Pong' message that contains a counter value.
//! #[derive(Debug)]
//! struct Pong(u32);
//!
//! impl Handler<Pong> for PongActor {
//! type Response = ();
//! type Executor = SequentialExecutor;
//!
//! // The 'handle_message' method for 'PongActor' is similar to 'PingActor'.
//! // It prints the received counter value and sends a 'Ping' message back to 'PingActor'
//! // with an incremented counter value, until the counter reaches 10.
//! async fn handle_message(&mut self, msg: Pong, ctx: &Context<Self>) -> Result<Self::Response, PuppetError> {
//! println!("Pong received: {}", msg.0);
//!
//! if msg.0 < 10 {
//! ctx.send::<PingActor, _>(Ping(msg.0 + 1))?;
//! } else {
//! println!("Ping-Pong finished!");
//! }
//! Ok(())
//! }
//! }
//!
//! #[tokio::main]
//! async fn main() -> Result<(), PuppetError> {
//!
//! // Create a new instance of the Puppeteer.
//! let pptr = Puppeteer::new();
//!
//! // Spawn a 'PingActor' and specify 'PingActor' as its own supervisor.
//! // This means that 'PingActor' will manage itself.
//! pptr.spawn::<PingActor, PingActor>(PingActor::default()).await?;
//!
//! // Spawn a 'PongActor' using the shorter 'spawn_self' method.
//! // This is equivalent to specifying 'PongActor' as its own supervisor.
//! pptr.spawn_self(PongActor::default()).await?;
//!
//! // Send an initial 'Ping' message to 'PingActor' with a counter value of 0.
//! // This starts the ping-pong game between 'PingActor' and 'PongActor'.
//! pptr.send::<PingActor, _>(Ping(0))?;
//!
//! tokio::time::sleep(std::time::Duration::from_secs(1)).await;
//!
//! Ok(())
//! }
//! ```
pub mod address;
pub mod errors;
pub mod executor;
pub mod message;
pub mod pid;
pub mod puppet;
pub mod puppeteer;
pub mod supervision;
pub mod prelude {
pub use crate::address::Address;
pub use crate::errors::CriticalError;
pub use crate::errors::NonCriticalError;
pub use crate::errors::PuppetError;
pub use crate::executor::ConcurrentExecutor;
pub use crate::executor::DedicatedConcurrentExecutor;
pub use crate::executor::SequentialExecutor;
pub use crate::message::Message;
pub use crate::pid::Pid;
pub use crate::puppet::Context;
pub use crate::puppet::Handler;
pub use crate::puppet::Puppet;
pub use crate::puppet::Puppetable;
pub use crate::puppeteer::Puppeteer;
pub use crate::supervision::strategy::*;
}