├── .github ├── FUNDING.yml └── workflows │ ├── publish.yml │ └── toolchain.yml ├── .gitignore ├── Cargo.toml ├── LICENSE-APACHE.txt ├── LICENSE-MIT.txt ├── README.md ├── examples ├── using_async_std │ └── main.rs ├── using_global_runtime │ └── main.rs ├── using_provided_runtime │ └── main.rs ├── using_timers │ └── main.rs └── using_tokio │ └── main.rs └── src ├── actor.rs ├── addr.rs ├── lib.rs ├── macros.rs ├── runtimes.rs ├── runtimes ├── async_std.rs ├── panic.rs └── tokio.rs ├── timer.rs └── utils.rs /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: [Diggsey] 4 | -------------------------------------------------------------------------------- /.github/workflows/publish.yml: -------------------------------------------------------------------------------- 1 | on: 2 | release: 3 | types: [published] 4 | 5 | name: Publish 6 | 7 | jobs: 8 | release: 9 | name: Release 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: actions/checkout@v2 13 | - uses: actions-rs/toolchain@v1 14 | with: 15 | profile: minimal 16 | toolchain: stable 17 | override: true 18 | - uses: actions-rs/cargo@v1 19 | with: 20 | command: login 21 | args: -- ${{secrets.CARGO_TOKEN}} 22 | - uses: actions-rs/cargo@v1 23 | with: 24 | command: publish 25 | -------------------------------------------------------------------------------- /.github/workflows/toolchain.yml: -------------------------------------------------------------------------------- 1 | on: [push, pull_request] 2 | 3 | name: CI 4 | 5 | jobs: 6 | check: 7 | name: Check 8 | runs-on: ubuntu-latest 9 | steps: 10 | - uses: actions/checkout@v2 11 | - uses: actions-rs/toolchain@v1 12 | with: 13 | profile: minimal 14 | toolchain: stable 15 | override: true 16 | - uses: actions-rs/cargo@v1 17 | with: 18 | command: check 19 | args: --all-features 20 | 21 | fmt: 22 | name: Rustfmt 23 | runs-on: ubuntu-latest 24 | steps: 25 | - uses: actions/checkout@v2 26 | - uses: actions-rs/toolchain@v1 27 | with: 28 | profile: minimal 29 | toolchain: stable 30 | override: true 31 | - run: rustup component add rustfmt 32 | - uses: actions-rs/cargo@v1 33 | with: 34 | command: fmt 35 | args: -- --check 36 | 37 | clippy: 38 | name: Clippy 39 | runs-on: ubuntu-latest 40 | steps: 41 | - uses: actions/checkout@v2 42 | - uses: actions-rs/toolchain@v1 43 | with: 44 | profile: minimal 45 | toolchain: stable 46 | override: true 47 | - run: rustup component add clippy 48 | - uses: actions-rs/cargo@v1 49 | with: 50 | command: clippy 51 | args: --all-features -- -D warnings 52 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | Cargo.lock 3 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "act-zero" 3 | version = "0.4.0" 4 | authors = ["Diggory Blake "] 5 | edition = "2018" 6 | description = "Ergonomic actor system" 7 | repository = "https://github.com/Diggsey/act-zero" 8 | readme = "README.md" 9 | license = "MIT OR Apache-2.0" 10 | 11 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 12 | 13 | [features] 14 | default-tokio = ["tokio"] 15 | default-async-std = ["async-std"] 16 | default-disabled = [] 17 | nightly = [] 18 | tracing = ["tynm"] 19 | 20 | [dependencies] 21 | futures = "0.3.6" 22 | async-trait = "0.1.41" 23 | log = "0.4.11" 24 | tokio = { version = "1.0.1", features = ["time", "net"], optional = true } 25 | async-std = { version = "1.8.0", optional = true } 26 | tynm = { version = "0.1.4", optional = true } 27 | 28 | [dev-dependencies] 29 | tokio = { version = "1.0.1", features = ["rt", "macros", "time"] } 30 | async-std = { version = "1.8.0", features = ["attributes"] } 31 | 32 | [[example]] 33 | name = "using_tokio" 34 | required-features = ["tokio"] 35 | 36 | [[example]] 37 | name = "using_async_std" 38 | required-features = ["async-std"] 39 | 40 | [[example]] 41 | name = "using_global_runtime" 42 | 43 | [[example]] 44 | name = "using_provided_runtime" 45 | 46 | [[example]] 47 | name = "using_timers" 48 | required-features = ["tokio"] 49 | -------------------------------------------------------------------------------- /LICENSE-APACHE.txt: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /LICENSE-MIT.txt: -------------------------------------------------------------------------------- 1 | Permission is hereby granted, free of charge, to any 2 | person obtaining a copy of this software and associated 3 | documentation files (the "Software"), to deal in the 4 | Software without restriction, including without 5 | limitation the rights to use, copy, modify, merge, 6 | publish, distribute, sublicense, and/or sell copies of 7 | the Software, and to permit persons to whom the Software 8 | is furnished to do so, subject to the following 9 | conditions: 10 | 11 | The above copyright notice and this permission notice 12 | shall be included in all copies or substantial portions 13 | of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF 16 | ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 17 | TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 18 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 19 | SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 20 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 21 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 22 | IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 23 | DEALINGS IN THE SOFTWARE. 24 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # act-zero 2 | 3 | An actor system for Rust, designed with several goals in mind: 4 | 5 | - No boilerplate. 6 | - Ergonomic. 7 | - Supports standard trait-based static and dynamic polymorphism. 8 | - Embraces async/await. 9 | - Executor agnostic. 10 | 11 | Very little code is required to get started: 12 | 13 | ```rust 14 | use std::error::Error; 15 | 16 | use futures::executor::LocalPool; 17 | use act_zero::*; 18 | 19 | struct SimpleGreeter { 20 | number_of_greets: i32, 21 | } 22 | 23 | impl Actor for SimpleGreeter {} 24 | 25 | impl SimpleGreeter { 26 | async fn greet(&mut self, name: String) -> ActorResult { 27 | self.number_of_greets += 1; 28 | Produces::ok(format!( 29 | "Hello, {}. You are number {}!", 30 | name, self.number_of_greets 31 | )) 32 | } 33 | } 34 | 35 | fn main() -> Result<(), Box> { 36 | let mut pool = LocalPool::new(); 37 | let spawner = pool.spawner(); 38 | pool.run_until(async move { 39 | let actor_ref = Addr::new( 40 | &spawner, 41 | SimpleGreeter { 42 | number_of_greets: 0, 43 | }, 44 | )?; 45 | 46 | let greeting = call!(actor_ref.greet("John".into())).await?; 47 | println!("{}", greeting); 48 | 49 | let greeting = call!(actor_ref.greet("Emma".into())).await?; 50 | println!("{}", greeting); 51 | Ok(()) 52 | }) 53 | } 54 | ``` 55 | 56 | See the `examples` folder for more varied uses. 57 | -------------------------------------------------------------------------------- /examples/using_async_std/main.rs: -------------------------------------------------------------------------------- 1 | //! This example shows how you can use the async-std runtime with act-zero. 2 | 3 | use act_zero::runtimes::async_std::spawn_actor; 4 | use act_zero::*; 5 | 6 | struct HelloWorldActor; 7 | 8 | impl Actor for HelloWorldActor {} 9 | 10 | impl HelloWorldActor { 11 | async fn say_hello(&mut self) { 12 | println!("Hello, world!"); 13 | } 14 | } 15 | 16 | #[async_std::main] 17 | async fn main() -> Result<(), ActorError> { 18 | let addr = spawn_actor(HelloWorldActor); 19 | call!(addr.say_hello()).await?; 20 | Ok(()) 21 | } 22 | -------------------------------------------------------------------------------- /examples/using_global_runtime/main.rs: -------------------------------------------------------------------------------- 1 | //! This example shows how you can allow a downstream crate to select a 2 | //! single global runtime. This is useful when writing a library crate. 3 | //! 4 | //! The `cfg` attributes are used to make it easier to run this example, 5 | //! but are not necessary in library code. 6 | #![allow(unused)] 7 | 8 | #[cfg(not(feature = "default-disabled"))] 9 | use act_zero::runtimes::default::spawn_actor; 10 | use act_zero::*; 11 | 12 | struct HelloWorldActor; 13 | 14 | impl Actor for HelloWorldActor {} 15 | 16 | impl HelloWorldActor { 17 | async fn say_hello(&mut self) { 18 | println!("Hello, world!"); 19 | } 20 | } 21 | 22 | #[cfg(not(feature = "default-disabled"))] 23 | async fn run_example() -> Result<(), ActorError> { 24 | let addr = spawn_actor(HelloWorldActor); 25 | call!(addr.say_hello()).await?; 26 | Ok(()) 27 | } 28 | 29 | // Everything below this point is only necessary for this example because 30 | // it's a binary crate, and not a library. 31 | #[cfg(all( 32 | any(feature = "default-tokio", feature = "default-async-std"), 33 | not(feature = "default-disabled") 34 | ))] 35 | #[cfg_attr(feature = "default-async-std", async_std::main)] 36 | #[cfg_attr(feature = "default-tokio", tokio::main)] 37 | async fn main() -> Result<(), ActorError> { 38 | run_example().await 39 | } 40 | 41 | #[cfg(not(all( 42 | any(feature = "default-tokio", feature = "default-async-std"), 43 | not(feature = "default-disabled") 44 | )))] 45 | fn main() { 46 | panic!("No default runtime selected") 47 | } 48 | -------------------------------------------------------------------------------- /examples/using_provided_runtime/main.rs: -------------------------------------------------------------------------------- 1 | //! This example shows how you write a library crate which spawns 2 | //! actors using a runtime provided by the caller. 3 | 4 | use act_zero::*; 5 | use futures::executor::LocalPool; 6 | use futures::task::Spawn; 7 | 8 | struct HelloWorldActor; 9 | 10 | impl Actor for HelloWorldActor {} 11 | 12 | impl HelloWorldActor { 13 | async fn say_hello(&mut self) { 14 | println!("Hello, world!"); 15 | } 16 | } 17 | 18 | async fn run_example(spawner: &impl Spawn) -> Result<(), ActorError> { 19 | let addr = Addr::new(spawner, HelloWorldActor)?; 20 | call!(addr.say_hello()).await?; 21 | Ok(()) 22 | } 23 | 24 | fn main() -> Result<(), ActorError> { 25 | let mut pool = LocalPool::new(); 26 | let spawner = pool.spawner(); 27 | 28 | pool.run_until(run_example(&spawner)) 29 | } 30 | -------------------------------------------------------------------------------- /examples/using_timers/main.rs: -------------------------------------------------------------------------------- 1 | //! This example shows how you can use timers. For this example 2 | //! we're using the Tokio runtime, but any runtime implementing 3 | //! `SupportsTimers` can be used. 4 | //! 5 | //! See the runtime examples for different ways of configuring 6 | //! the runtime. 7 | 8 | use std::time::Duration; 9 | 10 | use act_zero::runtimes::tokio::{spawn_actor, Timer}; 11 | use act_zero::timer::Tick; 12 | use act_zero::*; 13 | use async_trait::async_trait; 14 | use tokio::task::spawn_blocking; 15 | 16 | // Create an actor that prompts for input if the user is idle for too long 17 | #[derive(Default)] 18 | struct LonelyActor { 19 | addr: WeakAddr, 20 | timer: Timer, 21 | } 22 | 23 | #[async_trait] 24 | impl Actor for LonelyActor { 25 | async fn started(&mut self, addr: Addr) -> ActorResult<()> { 26 | // Store our own address for later 27 | self.addr = addr.downgrade(); 28 | 29 | // Start the timer 30 | self.comfort().await; 31 | Produces::ok(()) 32 | } 33 | } 34 | 35 | impl LonelyActor { 36 | async fn comfort(&mut self) { 37 | println!(":)"); 38 | 39 | // Schedule our "tick" method to be called in 10 seconds. 40 | // Timer methods have variations for "strong" and "weak" addresses: the former will keep 41 | // the actor alive as long as the timer is still active, whilst the latter will allow the 42 | // actor to stop if there are no other references to it. 43 | // In this case it doesn't really matter, but using the weak variation avoids a conversion. 44 | self.timer 45 | .set_timeout_for_weak(self.addr.clone(), Duration::from_secs(10)); 46 | } 47 | } 48 | 49 | // Actors must implement the `Tick` trait in order to use timers. 50 | #[async_trait] 51 | impl Tick for LonelyActor { 52 | async fn tick(&mut self) -> ActorResult<()> { 53 | // Tick events may be produced spuriously, so you should always call `tick()` on each timer 54 | // owned by the actor to confirm that the timer has elapsed. 55 | // This also allows you to determine *which* timer elapsed if you have multiple. 56 | if self.timer.tick() { 57 | println!("Are you still there?"); 58 | } 59 | Produces::ok(()) 60 | } 61 | } 62 | 63 | #[tokio::main] 64 | async fn main() -> Result<(), ActorError> { 65 | let addr = spawn_actor(LonelyActor::default()); 66 | 67 | // Spawn a blocking task to read stdin. We could do this with Tokio's 68 | // async IO functionality but this is just easier. 69 | spawn_blocking(move || { 70 | let mut input = String::new(); 71 | loop { 72 | std::io::stdin().read_line(&mut input)?; 73 | send!(addr.comfort()); 74 | } 75 | }) 76 | .await? 77 | } 78 | -------------------------------------------------------------------------------- /examples/using_tokio/main.rs: -------------------------------------------------------------------------------- 1 | //! This example shows how you can use the Tokio runtime with act-zero. 2 | 3 | use act_zero::runtimes::tokio::spawn_actor; 4 | use act_zero::*; 5 | 6 | struct HelloWorldActor; 7 | 8 | impl Actor for HelloWorldActor {} 9 | 10 | impl HelloWorldActor { 11 | async fn say_hello(&mut self) { 12 | println!("Hello, world!"); 13 | } 14 | } 15 | 16 | #[tokio::main] 17 | async fn main() -> Result<(), ActorError> { 18 | let addr = spawn_actor(HelloWorldActor); 19 | call!(addr.say_hello()).await?; 20 | Ok(()) 21 | } 22 | -------------------------------------------------------------------------------- /src/actor.rs: -------------------------------------------------------------------------------- 1 | use std::error::Error; 2 | use std::future::Future; 3 | use std::mem; 4 | use std::pin::Pin; 5 | use std::task::{Context, Poll}; 6 | 7 | use async_trait::async_trait; 8 | use futures::channel::oneshot; 9 | use futures::future::FutureExt; 10 | use log::error; 11 | 12 | use crate::Addr; 13 | 14 | /// The type of error returned by an actor method. 15 | pub type ActorError = Box; 16 | /// Short alias for a `Result, ActorError>`. 17 | pub type ActorResult = Result, ActorError>; 18 | 19 | /// A concrete type similar to a `BoxFuture<'static, Result>`, but 20 | /// without requiring an allocation if the value is immediately ready. 21 | /// This type implements the `Future` trait and can be directly `await`ed. 22 | #[derive(Debug)] 23 | #[non_exhaustive] 24 | pub enum Produces { 25 | /// No value was produced. 26 | None, 27 | /// A value is ready. 28 | Value(T), 29 | /// A value may be sent in the future. 30 | Deferred(oneshot::Receiver>), 31 | } 32 | 33 | impl Unpin for Produces {} 34 | 35 | impl Produces { 36 | /// Returns `Ok(Produces::Value(value))` 37 | pub fn ok(value: T) -> ActorResult { 38 | Ok(Produces::Value(value)) 39 | } 40 | } 41 | 42 | impl Future for Produces { 43 | type Output = Result; 44 | 45 | fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { 46 | loop { 47 | break match mem::replace(&mut *self, Produces::None) { 48 | Produces::None => Poll::Ready(Err(oneshot::Canceled)), 49 | Produces::Value(value) => Poll::Ready(Ok(value)), 50 | Produces::Deferred(mut recv) => match recv.poll_unpin(cx) { 51 | Poll::Ready(Ok(producer)) => { 52 | *self = producer; 53 | continue; 54 | } 55 | Poll::Ready(Err(e)) => Poll::Ready(Err(e)), 56 | Poll::Pending => { 57 | *self = Produces::Deferred(recv); 58 | Poll::Pending 59 | } 60 | }, 61 | }; 62 | } 63 | } 64 | } 65 | 66 | /// Trait implemented by all actors. 67 | /// This trait is defined using the `#[async_trait]` attribute: 68 | /// ```ignore 69 | /// #[async_trait] 70 | /// pub trait Actor: Send + 'static { 71 | /// /// Called automatically when an actor is started. Actors can use this 72 | /// /// to store their own address for future use. 73 | /// async fn started(&mut self, _addr: Addr) -> ActorResult<()> 74 | /// where 75 | /// Self: Sized, 76 | /// { 77 | /// Ok(()) 78 | /// } 79 | /// 80 | /// /// Called when any actor method returns an error. If this method 81 | /// /// returns `true`, the actor will stop. 82 | /// /// The default implementation logs the error using the `log` crate 83 | /// /// and then stops the actor. 84 | /// async fn error(&mut self, error: ActorError) -> bool { 85 | /// error!("{}", error); 86 | /// true 87 | /// } 88 | /// } 89 | /// ``` 90 | /// 91 | /// In order to use a trait object with the actor system, such as with `Addr`, 92 | /// the trait must extend this `Actor` trait. 93 | #[async_trait] 94 | pub trait Actor: Send + 'static { 95 | /// Called automatically when an actor is started. Actors can use this 96 | /// to store their own address for future use. 97 | async fn started(&mut self, _addr: Addr) -> ActorResult<()> 98 | where 99 | Self: Sized, 100 | { 101 | Produces::ok(()) 102 | } 103 | 104 | /// Called when any actor method returns an error. If this method 105 | /// returns `true`, the actor will stop. 106 | /// The default implementation logs the error using the `log` crate 107 | /// and then stops the actor. 108 | async fn error(&mut self, error: ActorError) -> bool { 109 | error!("{}", error); 110 | true 111 | } 112 | } 113 | 114 | /// Actor methods may return any type implementing this trait. 115 | pub trait IntoActorResult { 116 | /// The type to be sent back to the caller. 117 | type Output; 118 | /// Perform the conversion to an ActorResult. 119 | fn into_actor_result(self) -> ActorResult; 120 | } 121 | 122 | impl IntoActorResult for ActorResult { 123 | type Output = T; 124 | fn into_actor_result(self) -> ActorResult { 125 | self 126 | } 127 | } 128 | 129 | impl IntoActorResult for () { 130 | type Output = (); 131 | fn into_actor_result(self) -> ActorResult<()> { 132 | Produces::ok(()) 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /src/addr.rs: -------------------------------------------------------------------------------- 1 | use std::any::Any; 2 | use std::cmp::Ordering; 3 | use std::fmt::{self, Debug}; 4 | use std::future::Future; 5 | use std::hash::{Hash, Hasher}; 6 | use std::sync::{Arc, Weak}; 7 | use std::{mem, ptr}; 8 | 9 | use futures::channel::{mpsc, oneshot}; 10 | use futures::future::{self, BoxFuture, FutureExt}; 11 | use futures::select_biased; 12 | use futures::stream::{FuturesUnordered, StreamExt}; 13 | use futures::task::{Spawn, SpawnError, SpawnExt}; 14 | 15 | use crate::{send, Actor, Produces, Termination}; 16 | 17 | type MutItem = Box FnOnce(&'a mut T) -> BoxFuture<'a, bool> + Send>; 18 | type FutItem = BoxFuture<'static, ()>; 19 | 20 | async fn mutex_task( 21 | value: T, 22 | mut mut_channel: mpsc::UnboundedReceiver>, 23 | mut fut_channel: mpsc::UnboundedReceiver, 24 | ) { 25 | let mut futs = FuturesUnordered::new(); 26 | // Re-bind 'value' so that it is dropped before futs. 27 | // That will ensure .termination() completes only once the value's drop has finished. 28 | let mut value = value; 29 | loop { 30 | // Obtain an item 31 | let current_item = loop { 32 | if select_biased! { 33 | _ = futs.select_next_some() => false, 34 | item = mut_channel.next() => if let Some(item) = item { 35 | break item 36 | } else { 37 | true 38 | }, 39 | item = fut_channel.select_next_some() => { 40 | futs.push(item); 41 | false 42 | }, 43 | complete => true, 44 | } { 45 | return; 46 | } 47 | }; 48 | 49 | // Wait for the current item to run 50 | let mut current_future = current_item(&mut value).fuse(); 51 | loop { 52 | select_biased! { 53 | done = current_future => if done { 54 | return; 55 | } else { 56 | break 57 | }, 58 | _ = futs.select_next_some() => {}, 59 | item = fut_channel.select_next_some() => futs.push(item), 60 | } 61 | } 62 | } 63 | } 64 | 65 | struct AddrInner { 66 | mut_channel: mpsc::UnboundedSender>, 67 | fut_channel: mpsc::UnboundedSender, 68 | } 69 | 70 | impl AddrInner { 71 | fn send_mut(this: &Arc, item: MutItem) { 72 | this.downcast_ref::() 73 | .unwrap() 74 | .mut_channel 75 | .unbounded_send(item) 76 | .ok(); 77 | } 78 | fn send_fut(this: &Arc, item: FutItem) { 79 | this.downcast_ref::() 80 | .unwrap() 81 | .fut_channel 82 | .unbounded_send(item) 83 | .ok(); 84 | } 85 | 86 | // Must only be called if we have previously encountered a witness value of type `F`. 87 | fn send_mut_upcasted &mut U + Copy + Send>( 88 | this: &Arc, 89 | item: MutItem, 90 | ) { 91 | assert_eq!(mem::size_of::(), 0); 92 | 93 | this.downcast_ref::() 94 | .unwrap() 95 | .mut_channel 96 | .unbounded_send(Box::new(move |x| { 97 | let f: F = unsafe { mem::zeroed() }; 98 | item(f(x)) 99 | })) 100 | .ok(); 101 | } 102 | } 103 | 104 | fn send_unreachable(_: &Arc, _: T) { 105 | unreachable!() 106 | } 107 | 108 | /// Trait provides methods for spawning futures onto an actor. Implemented by 109 | /// `Addr` and `WeakAddr` alike. 110 | pub trait AddrLike: Send + Sync + Clone + Debug + 'static + AsAddr { 111 | /// Type of the actor reference by this address. 112 | type Actor: Actor + ?Sized; 113 | 114 | #[doc(hidden)] 115 | fn send_mut(&self, item: MutItem); 116 | 117 | /// Spawn a future onto the actor which does not return a value. 118 | fn send_fut(&self, fut: impl Future + Send + 'static); 119 | 120 | /// Spawn a future onto the actor and provide the means to get back 121 | /// the result. The future will be cancelled if the receiver is 122 | /// dropped before it has completed. 123 | fn call_fut( 124 | &self, 125 | fut: impl Future> + Send + 'static, 126 | ) -> Produces { 127 | let (mut tx, rx) = oneshot::channel(); 128 | self.send_fut(async move { 129 | select_biased! { 130 | _ = tx.cancellation().fuse() => {} 131 | res = fut.fuse() => { 132 | let _ = tx.send(res); 133 | } 134 | }; 135 | }); 136 | Produces::Deferred(rx) 137 | } 138 | 139 | /// Equivalent to `send_fut` but provides access to the actor's address. 140 | fn send_fut_with + Send + 'static>(&self, f: impl FnOnce(Self) -> F) { 141 | self.send_fut(f(self.clone())); 142 | } 143 | 144 | /// Equivalent to `call_fut` but provides access to the actor's address. 145 | fn call_fut_with> + Send + 'static>( 146 | &self, 147 | f: impl FnOnce(Self) -> F, 148 | ) -> Produces { 149 | self.call_fut(f(self.clone())) 150 | } 151 | 152 | /// Returns a future which resolves when the actor terminates. If the 153 | /// actor has already terminated, or if this address is detached, the 154 | /// future will resolve immediately. 155 | fn termination(&self) -> Termination { 156 | Termination(self.call_fut(future::pending())) 157 | } 158 | } 159 | 160 | /// Implemented by addresses and references to addresses 161 | pub trait AsAddr { 162 | /// The inner address type 163 | type Addr: AddrLike; 164 | 165 | /// Obtain a direct reference to the address 166 | fn as_addr(&self) -> &Self::Addr; 167 | } 168 | 169 | impl AsAddr for &T { 170 | type Addr = T::Addr; 171 | fn as_addr(&self) -> &Self::Addr { 172 | (**self).as_addr() 173 | } 174 | } 175 | impl AsAddr for crate::Addr { 176 | type Addr = Self; 177 | fn as_addr(&self) -> &Self::Addr { 178 | self 179 | } 180 | } 181 | impl AsAddr for crate::WeakAddr { 182 | type Addr = Self; 183 | fn as_addr(&self) -> &Self::Addr { 184 | self 185 | } 186 | } 187 | 188 | /// A strong reference to a spawned actor. Actors can be spawned using `Addr::new`. 189 | /// 190 | /// Methods can be called on the actor after it has been spawned using the 191 | /// `send!(...)` and `call!(...)` macros. 192 | /// 193 | /// Can be converted to the address of a trait-object using the `upcast!(...)` 194 | /// macro. 195 | pub struct Addr { 196 | inner: Option>, 197 | send_mut: &'static (dyn Fn(&Arc, MutItem) + Send + Sync), 198 | send_fut: &'static (dyn Fn(&Arc, FutItem) + Send + Sync), 199 | } 200 | 201 | impl Debug for Addr { 202 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 203 | write!( 204 | f, 205 | "{} {{ detached: {} }}", 206 | std::any::type_name::(), 207 | self.inner.is_none() 208 | ) 209 | } 210 | } 211 | 212 | impl Clone for Addr { 213 | fn clone(&self) -> Self { 214 | Self { 215 | inner: self.inner.clone(), 216 | send_mut: self.send_mut, 217 | send_fut: self.send_fut, 218 | } 219 | } 220 | } 221 | 222 | impl Default for Addr { 223 | fn default() -> Self { 224 | Self::detached() 225 | } 226 | } 227 | 228 | impl PartialEq> for Addr { 229 | fn eq(&self, rhs: &Addr) -> bool { 230 | self.ptr() == rhs.ptr() 231 | } 232 | } 233 | 234 | impl PartialEq> for Addr { 235 | fn eq(&self, rhs: &WeakAddr) -> bool { 236 | self.ptr() == rhs.ptr() 237 | } 238 | } 239 | 240 | impl Eq for Addr {} 241 | impl Hash for Addr { 242 | fn hash(&self, state: &mut H) { 243 | self.ptr().hash(state) 244 | } 245 | } 246 | 247 | impl PartialOrd> for Addr { 248 | fn partial_cmp(&self, rhs: &Addr) -> Option { 249 | self.ptr().partial_cmp(&rhs.ptr()) 250 | } 251 | } 252 | 253 | impl PartialOrd> for Addr { 254 | fn partial_cmp(&self, rhs: &WeakAddr) -> Option { 255 | self.ptr().partial_cmp(&rhs.ptr()) 256 | } 257 | } 258 | impl Ord for Addr { 259 | fn cmp(&self, rhs: &Addr) -> Ordering { 260 | self.ptr().cmp(&rhs.ptr()) 261 | } 262 | } 263 | 264 | impl AddrLike for Addr { 265 | type Actor = T; 266 | 267 | #[doc(hidden)] 268 | fn send_mut(&self, item: MutItem) { 269 | if let Some(inner) = &self.inner { 270 | (self.send_mut)(inner, item); 271 | } 272 | } 273 | 274 | fn send_fut(&self, fut: impl Future + Send + 'static) { 275 | if let Some(inner) = &self.inner { 276 | (self.send_fut)(inner, FutureExt::boxed(fut)); 277 | } 278 | } 279 | } 280 | 281 | impl Addr { 282 | /// Spawn an actor using the given spawner. If successful returns the address of the actor. 283 | pub fn new(spawner: &S, value: T) -> Result { 284 | let (mtx, mrx) = mpsc::unbounded(); 285 | let (ftx, frx) = mpsc::unbounded(); 286 | spawner.spawn(mutex_task(value, mrx, frx))?; 287 | let addr = Self { 288 | inner: Some(Arc::new(AddrInner { 289 | mut_channel: mtx, 290 | fut_channel: ftx, 291 | })), 292 | send_mut: &AddrInner::::send_mut, 293 | send_fut: &AddrInner::::send_fut, 294 | }; 295 | 296 | // Tell the actor its own address 297 | send!(addr.started(addr.clone())); 298 | 299 | Ok(addr) 300 | } 301 | #[doc(hidden)] 302 | pub fn upcast &mut U + Copy + Send + 'static>( 303 | self, 304 | _f: F, 305 | ) -> Addr { 306 | Addr { 307 | inner: self.inner, 308 | send_mut: &AddrInner::::send_mut_upcasted::, 309 | send_fut: self.send_fut, 310 | } 311 | } 312 | } 313 | impl Addr { 314 | /// Create an address which does not refer to any actor. 315 | pub fn detached() -> Self { 316 | Self { 317 | inner: None, 318 | send_mut: &send_unreachable, 319 | send_fut: &send_unreachable, 320 | } 321 | } 322 | fn ptr(&self) -> *const () { 323 | if let Some(inner) = &self.inner { 324 | Arc::as_ptr(inner) as *const () 325 | } else { 326 | ptr::null() 327 | } 328 | } 329 | } 330 | impl Addr { 331 | /// Downgrade to a weak reference, which does not try to keep the actor alive. 332 | pub fn downgrade(&self) -> WeakAddr { 333 | WeakAddr { 334 | inner: self.inner.as_ref().map(Arc::downgrade), 335 | send_mut: self.send_mut, 336 | send_fut: self.send_fut, 337 | } 338 | } 339 | /// Attempt to downcast the address of a "trait-object actor" to a concrete type. 340 | /// 341 | /// This function may succeed even when the cast would normally be 342 | /// unsuccessful if the address has become detached. 343 | pub fn downcast(self) -> Result, Addr> { 344 | if let Some(inner) = &self.inner { 345 | if inner.is::>() { 346 | Ok(Addr { 347 | inner: self.inner, 348 | send_mut: &AddrInner::::send_mut, 349 | send_fut: self.send_fut, 350 | }) 351 | } else { 352 | Err(self) 353 | } 354 | } else { 355 | Ok(Addr::detached()) 356 | } 357 | } 358 | } 359 | 360 | /// A weak reference to a spawned actor. 361 | /// 362 | /// Methods can be called on the actor after it has been spawned using the 363 | /// `send!(...)` and `call!(...)` macros. 364 | /// 365 | /// Can be converted to the address of a trait-object using the `upcast!(...)` 366 | /// macro. 367 | pub struct WeakAddr { 368 | inner: Option>, 369 | send_mut: &'static (dyn Fn(&Arc, MutItem) + Send + Sync), 370 | send_fut: &'static (dyn Fn(&Arc, FutItem) + Send + Sync), 371 | } 372 | 373 | impl Clone for WeakAddr { 374 | fn clone(&self) -> Self { 375 | Self { 376 | inner: self.inner.clone(), 377 | send_mut: self.send_mut, 378 | send_fut: self.send_fut, 379 | } 380 | } 381 | } 382 | 383 | impl Debug for WeakAddr { 384 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 385 | write!(f, "{} {{..}}", std::any::type_name::()) 386 | } 387 | } 388 | 389 | impl Default for WeakAddr { 390 | fn default() -> Self { 391 | Self::detached() 392 | } 393 | } 394 | 395 | impl PartialEq> for WeakAddr { 396 | fn eq(&self, rhs: &Addr) -> bool { 397 | self.ptr() == rhs.ptr() 398 | } 399 | } 400 | 401 | impl PartialEq> for WeakAddr { 402 | fn eq(&self, rhs: &WeakAddr) -> bool { 403 | self.ptr() == rhs.ptr() 404 | } 405 | } 406 | 407 | impl Eq for WeakAddr {} 408 | impl Hash for WeakAddr { 409 | fn hash(&self, state: &mut H) { 410 | self.ptr().hash(state) 411 | } 412 | } 413 | 414 | impl PartialOrd> for WeakAddr { 415 | fn partial_cmp(&self, rhs: &Addr) -> Option { 416 | self.ptr().partial_cmp(&rhs.ptr()) 417 | } 418 | } 419 | 420 | impl PartialOrd> for WeakAddr { 421 | fn partial_cmp(&self, rhs: &WeakAddr) -> Option { 422 | self.ptr().partial_cmp(&rhs.ptr()) 423 | } 424 | } 425 | impl Ord for WeakAddr { 426 | fn cmp(&self, rhs: &WeakAddr) -> Ordering { 427 | self.ptr().cmp(&rhs.ptr()) 428 | } 429 | } 430 | 431 | fn upgrade_weak(maybe_weak: &Option>) -> Option> { 432 | maybe_weak.as_ref().and_then(Weak::upgrade) 433 | } 434 | 435 | impl AddrLike for WeakAddr { 436 | type Actor = T; 437 | 438 | #[doc(hidden)] 439 | fn send_mut(&self, item: MutItem) { 440 | if let Some(inner) = upgrade_weak(&self.inner) { 441 | (self.send_mut)(&inner, item); 442 | } 443 | } 444 | 445 | fn send_fut(&self, fut: impl Future + Send + 'static) { 446 | if let Some(inner) = upgrade_weak(&self.inner) { 447 | (self.send_fut)(&inner, FutureExt::boxed(fut)); 448 | } 449 | } 450 | } 451 | 452 | impl WeakAddr { 453 | /// Create an address which does not refer to any actor. 454 | pub fn detached() -> Self { 455 | Self { 456 | inner: None, 457 | send_mut: &send_unreachable, 458 | send_fut: &send_unreachable, 459 | } 460 | } 461 | // TODO: Replace this with an implementation using `Weak::as_ptr` once support for 462 | // unsized values hits stable. 463 | fn ptr(&self) -> *const () { 464 | if let Some(inner) = upgrade_weak(&self.inner) { 465 | Arc::as_ptr(&inner) as *const () 466 | } else { 467 | ptr::null() 468 | } 469 | } 470 | } 471 | impl WeakAddr { 472 | #[doc(hidden)] 473 | pub fn upcast &mut U + Copy + Send + 'static>( 474 | self, 475 | _f: F, 476 | ) -> WeakAddr { 477 | WeakAddr { 478 | inner: self.inner, 479 | send_mut: &AddrInner::::send_mut_upcasted::, 480 | send_fut: self.send_fut, 481 | } 482 | } 483 | } 484 | impl WeakAddr { 485 | /// Upgrade this to a strong reference. If the actor has already stopped the returned 486 | /// address will be detached. 487 | pub fn upgrade(&self) -> Addr { 488 | if let Some(inner) = upgrade_weak(&self.inner) { 489 | Addr { 490 | inner: Some(inner), 491 | send_mut: self.send_mut, 492 | send_fut: self.send_fut, 493 | } 494 | } else { 495 | Addr::detached() 496 | } 497 | } 498 | } 499 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! # act-zero 2 | //! An actor system for Rust, designed with several goals in mind: 3 | //! - No boilerplate. 4 | //! - Ergonomic. 5 | //! - Supports standard trait-based static and dynamic polymorphism. 6 | //! - Embraces async/await. 7 | //! - Executor agnostic. 8 | //! 9 | //! Very little code is required to get started: 10 | //! 11 | //! ``` 12 | //! use std::error::Error; 13 | //! 14 | //! use futures::executor::LocalPool; 15 | //! use act_zero::*; 16 | //! 17 | //! struct SimpleGreeter { 18 | //! number_of_greets: i32, 19 | //! } 20 | //! 21 | //! impl Actor for SimpleGreeter {} 22 | //! 23 | //! impl SimpleGreeter { 24 | //! async fn greet(&mut self, name: String) -> ActorResult { 25 | //! self.number_of_greets += 1; 26 | //! Produces::ok(format!( 27 | //! "Hello, {}. You are number {}!", 28 | //! name, self.number_of_greets 29 | //! )) 30 | //! } 31 | //! } 32 | //! 33 | //! fn main() -> Result<(), Box> { 34 | //! let mut pool = LocalPool::new(); 35 | //! let spawner = pool.spawner(); 36 | //! pool.run_until(async move { 37 | //! let actor_ref = Addr::new( 38 | //! &spawner, 39 | //! SimpleGreeter { 40 | //! number_of_greets: 0, 41 | //! }, 42 | //! )?; 43 | //! 44 | //! let greeting = call!(actor_ref.greet("John".into())).await?; 45 | //! println!("{}", greeting); 46 | //! 47 | //! let greeting = call!(actor_ref.greet("Emma".into())).await?; 48 | //! println!("{}", greeting); 49 | //! Ok(()) 50 | //! }) 51 | //! } 52 | //! ``` 53 | //! 54 | //! For mixing traits and actors, it's recommended to use the `async_trait` crate 55 | //! to allow using async methods in traits. 56 | 57 | #![deny(missing_docs)] 58 | 59 | mod actor; 60 | mod addr; 61 | mod macros; 62 | pub mod runtimes; 63 | pub mod timer; 64 | mod utils; 65 | 66 | pub use actor::*; 67 | pub use addr::*; 68 | pub use macros::*; 69 | pub use utils::*; 70 | 71 | #[doc(hidden)] 72 | pub mod hidden { 73 | pub use futures::channel::oneshot; 74 | pub use futures::future::FutureExt; 75 | 76 | #[cfg(feature = "tracing")] 77 | pub use log::trace; 78 | 79 | #[cfg(not(feature = "tracing"))] 80 | #[doc(hidden)] 81 | #[macro_export] 82 | macro_rules! trace { 83 | // Strip out all uses of `trace!` 84 | ($($args:tt)*) => {}; 85 | } 86 | #[cfg(not(feature = "tracing"))] 87 | pub use trace; 88 | 89 | #[cfg(feature = "tracing")] 90 | pub fn type_name_of_val(_val: &T) -> tynm::TypeName<'static> { 91 | tynm::TypeName::new::<&T>() 92 | } 93 | #[cfg(feature = "tracing")] 94 | pub fn type_name_of_addr(_val: &T) -> tynm::TypeName<'static> { 95 | tynm::TypeName::new::<&T::Actor>() 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /src/macros.rs: -------------------------------------------------------------------------------- 1 | #[doc(hidden)] 2 | #[macro_export] 3 | macro_rules! __impl_send { 4 | ( 5 | @parse $caller:tt receiver=[$($receiver:tt)*] tokens = [. $method:ident ($($args:expr),*)] 6 | ) => { 7 | $crate::__impl_send!(@move_args $caller args=[$($args),*] input=[$($receiver)*, $method]) 8 | }; 9 | ( 10 | @parse $caller:tt receiver=[$($receiver:tt)*] tokens = [$token:tt $($tokens:tt)*] 11 | ) => { 12 | $crate::__impl_send!(@parse $caller receiver=[$($receiver)* $token] tokens = [$($tokens)*]) 13 | }; 14 | ( 15 | @move_args $caller:tt args = [] input = $input:tt 16 | ) => { 17 | $crate::__impl_send!(@$caller args=[] moved=[] input=$input) 18 | }; 19 | ( 20 | @move_args $caller:tt args = [$arg0:expr] input = $input:tt 21 | ) => { 22 | $crate::__impl_send!(@$caller args=[$arg0] moved=[arg0] input=$input) 23 | }; 24 | ( 25 | @move_args $caller:tt args = [$arg0:expr, $arg1:expr] input = $input:tt 26 | ) => { 27 | $crate::__impl_send!(@$caller args=[$arg0, $arg1] moved=[arg0, arg1] input=$input) 28 | }; 29 | ( 30 | @move_args $caller:tt args = [$arg0:expr, $arg1:expr, $arg2:expr] input = $input:tt 31 | ) => { 32 | $crate::__impl_send!(@$caller args=[$arg0, $arg1, $arg2] moved=[arg0, arg1, arg2] input=$input) 33 | }; 34 | ( 35 | @move_args $caller:tt args = [$arg0:expr, $arg1:expr, $arg2:expr, $arg3:expr] input = $input:tt 36 | ) => { 37 | $crate::__impl_send!(@$caller args=[$arg0, $arg1, $arg2, $arg3] moved=[arg0, arg1, arg2, arg3] input=$input) 38 | }; 39 | ( 40 | @move_args $caller:tt args = [$arg0:expr, $arg1:expr, $arg2:expr, $arg3:expr, $arg4:expr] input = $input:tt 41 | ) => { 42 | $crate::__impl_send!(@$caller args=[$arg0, $arg1, $arg2, $arg3, $arg4] moved=[arg0, arg1, arg2, arg3, arg4] input=$input) 43 | }; 44 | ( 45 | @move_args $caller:tt args = [$arg0:expr, $arg1:expr, $arg2:expr, $arg3:expr, $arg4:expr, $arg5:expr] input = $input:tt 46 | ) => { 47 | $crate::__impl_send!(@$caller args=[$arg0, $arg1, $arg2, $arg3, $arg4, $arg5] moved=[arg0, arg1, arg2, arg3, arg4, arg5] input=$input) 48 | }; 49 | ( 50 | @move_args $caller:tt args = [$arg0:expr, $arg1:expr, $arg2:expr, $arg3:expr, $arg4:expr, $arg5:expr, $arg6:expr] input = $input:tt 51 | ) => { 52 | $crate::__impl_send!(@$caller args=[$arg0, $arg1, $arg2, $arg3, $arg4, $arg5, $arg6] moved=[arg0, arg1, arg2, arg3, arg4, arg5, arg6] input=$input) 53 | }; 54 | ( 55 | @move_args $caller:tt args = [$arg0:expr, $arg1:expr, $arg2:expr, $arg3:expr, $arg4:expr, $arg5:expr, $arg6:expr, $arg7:expr] input = $input:tt 56 | ) => { 57 | $crate::__impl_send!(@$caller args=[$arg0, $arg1, $arg2, $arg3, $arg4, $arg5, $arg6, $arg7] moved=[arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7] input=$input) 58 | }; 59 | ( 60 | @move_args $caller:tt args = [$arg0:expr, $arg1:expr, $arg2:expr, $arg3:expr, $arg4:expr, $arg5:expr, $arg6:expr, $arg7:expr, $arg8:expr] input = $input:tt 61 | ) => { 62 | $crate::__impl_send!(@$caller args=[$arg0, $arg1, $arg2, $arg3, $arg4, $arg5, $arg6, $arg7, $arg8] moved=[arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8] input=$input) 63 | }; 64 | ( 65 | @move_args $caller:tt args = [$arg0:expr, $arg1:expr, $arg2:expr, $arg3:expr, $arg4:expr, $arg5:expr, $arg6:expr, $arg7:expr, $arg8:expr, $arg9:expr] input = $input:tt 66 | ) => { 67 | $crate::__impl_send!(@$caller args=[$arg0, $arg1, $arg2, $arg3, $arg4, $arg5, $arg6, $arg7, $arg8, $arg9] moved=[arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9] input=$input) 68 | }; 69 | ( 70 | @send args=[$($args:expr),*] moved=[$($moved:ident),*] input=[$addr:expr, $method:ident] 71 | ) => { 72 | { 73 | $( 74 | let $moved = $args; 75 | )* 76 | let addr = $crate::AsAddr::as_addr(&$addr); 77 | let addr2 = addr.clone(); 78 | $crate::hidden::trace!("send!({}::{}(...))", $crate::hidden::type_name_of_addr(addr).as_display(), stringify!($method)); 79 | $crate::AddrLike::send_mut(addr, Box::new(move |x| { 80 | $crate::hidden::trace!("{}::{}(...)", $crate::hidden::type_name_of_val(x).as_display(), stringify!($method)); 81 | $crate::hidden::FutureExt::boxed(async move { 82 | let _addr = addr2; 83 | if let Err(e) = $crate::IntoActorResult::into_actor_result(x.$method($($moved),*).await) { 84 | $crate::Actor::error(x, e).await 85 | } else { 86 | false 87 | } 88 | }) 89 | })); 90 | } 91 | }; 92 | ( 93 | @call args=[$($args:expr),*] moved=[$($moved:ident),*] input=[$addr:expr, $method:ident] 94 | ) => { 95 | { 96 | $( 97 | let $moved = $args; 98 | )* 99 | let addr = $crate::AsAddr::as_addr(&$addr); 100 | let addr2 = addr.clone(); 101 | $crate::hidden::trace!("call!({}::{}(...))", $crate::hidden::type_name_of_addr(addr).as_display(), stringify!($method)); 102 | let (tx, rx) = $crate::hidden::oneshot::channel(); 103 | $crate::AddrLike::send_mut(addr, Box::new(move |x| { 104 | $crate::hidden::trace!("{}::{}(...)", $crate::hidden::type_name_of_val(x).as_display(), stringify!($method)); 105 | $crate::hidden::FutureExt::boxed(async move { 106 | let _addr = addr2; 107 | match $crate::IntoActorResult::into_actor_result(x.$method($($moved),*).await) { 108 | Ok(x) => { 109 | let _ = tx.send(x); 110 | false 111 | } 112 | Err(e) => $crate::Actor::error(x, e).await, 113 | } 114 | }) 115 | })); 116 | $crate::Produces::Deferred(rx) 117 | } 118 | }; 119 | } 120 | 121 | /// Sends a method call to be executed by the actor. 122 | /// 123 | /// ```ignore 124 | /// send!(addr.method(arg1, arg2)) 125 | /// ``` 126 | /// 127 | /// Constraints: 128 | /// - The method must be an inherent method or trait method callable on the 129 | /// actor type. 130 | /// - The method can take either `&self` or `&mut self` as the receiver, but only `&mut self` will 131 | /// be passed in. 132 | /// - The method must return a future, with an output that implements `IntoActorResult`. 133 | /// - The arguments must be `Send + 'static`. 134 | #[macro_export] 135 | macro_rules! send { 136 | ($($tokens:tt)*) => { 137 | $crate::__impl_send!(@parse send receiver=[] tokens=[$($tokens)*]) 138 | }; 139 | } 140 | 141 | /// Sends a method call to be executed by the actor, and returns a future that can 142 | /// be awaited to get back the result. 143 | /// 144 | /// ```ignore 145 | /// call!(addr.method(arg1, arg2)) 146 | /// ``` 147 | /// 148 | /// The same constraints as for the `send!(...)` macro apply. 149 | #[macro_export] 150 | macro_rules! call { 151 | ($($tokens:tt)*) => { 152 | $crate::__impl_send!(@parse call receiver=[] tokens=[$($tokens)*]) 153 | }; 154 | } 155 | 156 | /// Converts an `Addr` or `WeakAddr` to an `Addr` or `WeakAddr`. 157 | /// 158 | /// ```ignore 159 | /// let trait_addr: Addr = upcast!(addr); 160 | /// ``` 161 | #[macro_export] 162 | macro_rules! upcast { 163 | ($x:expr) => { 164 | ($x).upcast(|x| x as _) 165 | }; 166 | } 167 | -------------------------------------------------------------------------------- /src/runtimes.rs: -------------------------------------------------------------------------------- 1 | //! Contains integrations with specific runtimes 2 | //! 3 | //! Supported features: 4 | //! - `tokio` 5 | //! Enables the tokio runtime. 6 | //! - `async-std` 7 | //! Enables the async-std runtime. 8 | //! - `default-tokio` 9 | //! Enables the tokio runtime and re-exports it under the name `default`. 10 | //! - `default-async-std` 11 | //! Enables the async-std runtime and re-exports it under the name `default`. 12 | //! - `default-disabled` 13 | //! Prevents a default runtime being exported, regardless of other features. 14 | //! 15 | //! Multiple runtimes may be enabled, but only one default runtime may be 16 | //! chosen. It is not necessary to choose a default runtime unless you want 17 | //! to use the `default` module. 18 | //! 19 | //! If no default runtime is selected, and the `default-disabled` option is 20 | //! not enabled, the `panic` runtime will be re-exported as the default. 21 | //! This allows library authors to build against the default runtime whilst 22 | //! remaining runtime agnostic. 23 | 24 | #[cfg(feature = "tokio")] 25 | pub mod tokio; 26 | 27 | #[cfg(feature = "async-std")] 28 | pub mod async_std; 29 | 30 | pub mod panic; 31 | 32 | #[cfg(all(feature = "default-tokio", not(feature = "default-disabled")))] 33 | pub use self::tokio as default; 34 | 35 | #[cfg(all(feature = "default-async-std", not(feature = "default-disabled")))] 36 | pub use self::async_std as default; 37 | 38 | #[cfg(not(any( 39 | feature = "default-tokio", 40 | feature = "default-async-std", 41 | feature = "default-disabled" 42 | )))] 43 | pub use self::panic as default; 44 | -------------------------------------------------------------------------------- /src/runtimes/async_std.rs: -------------------------------------------------------------------------------- 1 | //! `async-std`-specific functionality 2 | 3 | use std::time::Instant; 4 | 5 | use futures::future::{BoxFuture, FutureExt}; 6 | use futures::task::{Spawn, SpawnError}; 7 | 8 | use crate::{timer, Actor, Addr}; 9 | 10 | /// Type representing the async-std runtime. 11 | #[derive(Debug, Copy, Clone, Default)] 12 | pub struct Runtime; 13 | 14 | /// Alias for a timer based on async-std. This type can be default-constructed. 15 | pub type Timer = timer::Timer; 16 | 17 | /// Provides an infallible way to spawn an actor onto the async-std runtime, 18 | /// equivalent to `Addr::new`. 19 | pub fn spawn_actor(actor: T) -> Addr { 20 | Addr::new(&Runtime, actor).unwrap() 21 | } 22 | 23 | impl Spawn for Runtime { 24 | fn spawn_obj(&self, future: futures::future::FutureObj<'static, ()>) -> Result<(), SpawnError> { 25 | async_std::task::spawn(future); 26 | Ok(()) 27 | } 28 | } 29 | 30 | impl timer::SupportsTimers for Runtime { 31 | type Delay = BoxFuture<'static, ()>; 32 | fn delay(&self, deadline: Instant) -> Self::Delay { 33 | let duration = deadline.saturating_duration_since(Instant::now()); 34 | async_std::task::sleep(duration).boxed() 35 | } 36 | } 37 | 38 | #[cfg(test)] 39 | mod tests { 40 | use super::*; 41 | use crate::*; 42 | 43 | struct Echo; 44 | 45 | impl Actor for Echo {} 46 | impl Echo { 47 | async fn echo(&mut self, x: &'static str) -> ActorResult<&'static str> { 48 | Produces::ok(x) 49 | } 50 | } 51 | 52 | #[async_std::test] 53 | async fn smoke_test() { 54 | let addr = spawn_actor(Echo); 55 | 56 | let res = call!(addr.echo("test")).await.unwrap(); 57 | 58 | assert_eq!(res, "test"); 59 | } 60 | 61 | // Tests that .termination() waits for the Actor to be dropped 62 | #[async_std::test] 63 | async fn wait_drop_test() { 64 | use std::time::{Duration, Instant}; 65 | struct WaitDrop { 66 | tx: std::sync::mpsc::SyncSender, 67 | } 68 | impl Actor for WaitDrop {} 69 | impl Drop for WaitDrop { 70 | fn drop(&mut self) { 71 | std::thread::sleep(Duration::from_millis(100)); 72 | self.tx.send(5).unwrap(); 73 | } 74 | } 75 | let (tx, rx) = std::sync::mpsc::sync_channel(1); 76 | let addr = spawn_actor(WaitDrop { tx }); 77 | let ended = addr.termination(); 78 | drop(addr); 79 | ended.await; 80 | let res = rx.try_recv(); 81 | assert_eq!(res, Ok(5)); 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /src/runtimes/panic.rs: -------------------------------------------------------------------------------- 1 | //! Dummy runtime implementation which panics on use. 2 | //! 3 | //! This is used when no default runtime is enabled, and allows 4 | //! libraries to be built against this crate that *use* features 5 | //! requiring a runtime, but remaining runtime-agnostic. 6 | //! 7 | //! Binary crates should enable a specific default runtime, or 8 | //! enable the `default-disabled` feature to ensure this runtime 9 | //! is not used. 10 | 11 | use std::time::Instant; 12 | 13 | use futures::future::Pending; 14 | use futures::task::{Spawn, SpawnError}; 15 | 16 | use crate::{timer, Actor, Addr}; 17 | 18 | /// Type representing the dummy runtime. 19 | #[derive(Debug, Copy, Clone, Default)] 20 | pub struct Runtime; 21 | 22 | /// Alias for a dummy timer. This type can be default-constructed. 23 | /// Will always panic on use. 24 | pub type Timer = timer::Timer; 25 | 26 | /// Spawn an actor onto the dummy runtime. 27 | /// Will always panic. 28 | pub fn spawn_actor(actor: T) -> Addr { 29 | Addr::new(&Runtime, actor).unwrap() 30 | } 31 | 32 | impl Spawn for Runtime { 33 | fn spawn_obj( 34 | &self, 35 | _future: futures::future::FutureObj<'static, ()>, 36 | ) -> Result<(), SpawnError> { 37 | panic!("No default runtime selected") 38 | } 39 | } 40 | 41 | impl timer::SupportsTimers for Runtime { 42 | type Delay = Pending<()>; 43 | fn delay(&self, _deadline: Instant) -> Self::Delay { 44 | panic!("No default runtime selected") 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/runtimes/tokio.rs: -------------------------------------------------------------------------------- 1 | //! Tokio-specific functionality 2 | 3 | use std::time::Instant; 4 | 5 | use futures::task::{Spawn, SpawnError}; 6 | 7 | use crate::{timer, Actor, Addr}; 8 | 9 | /// Type representing the Tokio runtime. 10 | #[derive(Debug, Copy, Clone, Default)] 11 | pub struct Runtime; 12 | 13 | /// Alias for a timer based on Tokio. This type can be default-constructed. 14 | pub type Timer = timer::Timer; 15 | 16 | /// Provides an infallible way to spawn an actor onto the Tokio runtime, 17 | /// equivalent to `Addr::new`. 18 | pub fn spawn_actor(actor: T) -> Addr { 19 | Addr::new(&Runtime, actor).unwrap() 20 | } 21 | 22 | impl Spawn for Runtime { 23 | fn spawn_obj(&self, future: futures::future::FutureObj<'static, ()>) -> Result<(), SpawnError> { 24 | tokio::spawn(future); 25 | Ok(()) 26 | } 27 | } 28 | 29 | impl timer::SupportsTimers for Runtime { 30 | type Delay = tokio::time::Sleep; 31 | fn delay(&self, deadline: Instant) -> Self::Delay { 32 | tokio::time::sleep_until(deadline.into()) 33 | } 34 | } 35 | 36 | #[cfg(test)] 37 | mod tests { 38 | use super::*; 39 | use crate::*; 40 | 41 | #[tokio::test] 42 | async fn smoke_test() { 43 | struct Echo; 44 | 45 | impl Actor for Echo {} 46 | impl Echo { 47 | async fn echo(&mut self, x: &'static str) -> ActorResult<&'static str> { 48 | Produces::ok(x) 49 | } 50 | } 51 | 52 | let addr = spawn_actor(Echo); 53 | 54 | let res = call!(addr.echo("test")).await.unwrap(); 55 | 56 | assert_eq!(res, "test"); 57 | } 58 | 59 | #[tokio::test] 60 | async fn timer_test() { 61 | use std::time::{Duration, Instant}; 62 | 63 | use async_trait::async_trait; 64 | use futures::channel::oneshot; 65 | 66 | #[derive(Default)] 67 | struct DebouncedEcho { 68 | addr: WeakAddr, 69 | timer: Timer, 70 | response: Option<(&'static str, oneshot::Sender<&'static str>)>, 71 | } 72 | 73 | #[async_trait] 74 | impl Actor for DebouncedEcho { 75 | async fn started(&mut self, addr: Addr) -> ActorResult<()> { 76 | self.addr = addr.downgrade(); 77 | Produces::ok(()) 78 | } 79 | } 80 | 81 | #[async_trait] 82 | impl timer::Tick for DebouncedEcho { 83 | async fn tick(&mut self) -> ActorResult<()> { 84 | if self.timer.tick() { 85 | let (msg, tx) = self.response.take().unwrap(); 86 | let _ = tx.send(msg); 87 | } 88 | Produces::ok(()) 89 | } 90 | } 91 | impl DebouncedEcho { 92 | async fn echo( 93 | &mut self, 94 | msg: &'static str, 95 | ) -> ActorResult> { 96 | let (tx, rx) = oneshot::channel(); 97 | self.response = Some((msg, tx)); 98 | self.timer 99 | .set_timeout_for_strong(self.addr.upgrade(), Duration::from_secs(1)); 100 | Produces::ok(rx) 101 | } 102 | } 103 | 104 | let addr = spawn_actor(DebouncedEcho::default()); 105 | 106 | let start_time = Instant::now(); 107 | let res = call!(addr.echo("test")).await.unwrap(); 108 | drop(addr); 109 | 110 | assert_eq!(res.await.unwrap(), "test"); 111 | let end_time = Instant::now(); 112 | 113 | assert!(end_time - start_time >= Duration::from_secs(1)); 114 | } 115 | 116 | #[tokio::test] 117 | async fn weak_timer_test() { 118 | use std::time::{Duration, Instant}; 119 | 120 | use async_trait::async_trait; 121 | use futures::channel::oneshot; 122 | 123 | #[derive(Default)] 124 | struct DebouncedEcho { 125 | addr: WeakAddr, 126 | timer: Timer, 127 | response: Option<(&'static str, oneshot::Sender<&'static str>)>, 128 | } 129 | 130 | #[async_trait] 131 | impl Actor for DebouncedEcho { 132 | async fn started(&mut self, addr: Addr) -> ActorResult<()> { 133 | self.addr = addr.downgrade(); 134 | Produces::ok(()) 135 | } 136 | } 137 | 138 | #[async_trait] 139 | impl timer::Tick for DebouncedEcho { 140 | async fn tick(&mut self) -> ActorResult<()> { 141 | if self.timer.tick() { 142 | let (msg, tx) = self.response.take().unwrap(); 143 | let _ = tx.send(msg); 144 | } 145 | Produces::ok(()) 146 | } 147 | } 148 | impl DebouncedEcho { 149 | async fn echo( 150 | &mut self, 151 | msg: &'static str, 152 | ) -> ActorResult> { 153 | let (tx, rx) = oneshot::channel(); 154 | self.response = Some((msg, tx)); 155 | self.timer 156 | .set_timeout_for_weak(self.addr.clone(), Duration::from_secs(1)); 157 | Produces::ok(rx) 158 | } 159 | } 160 | 161 | let addr = spawn_actor(DebouncedEcho::default()); 162 | 163 | let start_time = Instant::now(); 164 | let res = call!(addr.echo("test")).await.unwrap(); 165 | drop(addr); 166 | 167 | assert!(res.await.is_err()); 168 | let end_time = Instant::now(); 169 | 170 | assert!(end_time - start_time < Duration::from_millis(10)); 171 | } 172 | 173 | // Tests that .termination() waits for the Actor to be dropped. 174 | // Note that this probably won't race anyway, tokio would need 175 | // rt-threaded feature. 176 | #[tokio::test] 177 | async fn wait_drop_test() { 178 | use std::time::{Duration, Instant}; 179 | struct WaitDrop { 180 | tx: std::sync::mpsc::SyncSender, 181 | } 182 | impl Actor for WaitDrop {} 183 | impl Drop for WaitDrop { 184 | fn drop(&mut self) { 185 | std::thread::sleep(Duration::from_millis(100)); 186 | self.tx.send(5).unwrap(); 187 | } 188 | } 189 | 190 | let (tx, rx) = std::sync::mpsc::sync_channel(1); 191 | let addr = spawn_actor(WaitDrop { tx }); 192 | let ended = addr.termination(); 193 | drop(addr); 194 | ended.await; 195 | let res = rx.try_recv(); 196 | assert_eq!(res, Ok(5)); 197 | } 198 | } 199 | -------------------------------------------------------------------------------- /src/timer.rs: -------------------------------------------------------------------------------- 1 | //! Functionality related to timers. 2 | //! 3 | //! Timers requires support from a runtime implementing the `SupportsTimers` trait. 4 | 5 | use std::future::Future; 6 | use std::mem; 7 | use std::time::{Duration, Instant}; 8 | 9 | use async_trait::async_trait; 10 | use futures::future::FutureExt; 11 | use futures::{pin_mut, select_biased}; 12 | 13 | use crate::{send, upcast, Actor, ActorResult, Addr, AddrLike, WeakAddr}; 14 | 15 | /// Timers can be used on runtimes implementing this trait. 16 | pub trait SupportsTimers { 17 | /// The type of future returned by `delay`. 18 | type Delay: Future + Send + 'static; 19 | 20 | /// Create a future which will complete when the deadline 21 | /// is passed. 22 | fn delay(&self, deadline: Instant) -> Self::Delay; 23 | } 24 | 25 | /// Provides an actor with a "tick" method, that will be called whenever 26 | /// a timer elapses. 27 | /// 28 | /// Note: spurious tick events may be received: the expectation is that 29 | /// actors respond to this event by checking if any timers have elapsed. 30 | /// The `Timer` struct has a `tick()` method for this purpose. 31 | /// 32 | /// This trait is defined using the `#[async_trait]` attribute as follows: 33 | /// ```ignore 34 | /// #[async_trait] 35 | /// pub trait Tick: Actor { 36 | /// /// Called whenever a timer might have elapsed. 37 | /// async fn tick(&mut self) -> ActorResult<()>; 38 | /// } 39 | /// ``` 40 | /// 41 | #[async_trait] 42 | pub trait Tick: Actor { 43 | /// Called whenever a timer might have elapsed. 44 | async fn tick(&mut self) -> ActorResult<()>; 45 | } 46 | 47 | /// Timers will be in one of these states. 48 | #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)] 49 | pub enum TimerState { 50 | /// The timer is inactive. This is the default state. 51 | Inactive, 52 | /// The timer is configured to tick once, when the deadline 53 | /// is reached. 54 | Timeout { 55 | /// When this timer will tick 56 | deadline: Instant, 57 | }, 58 | /// The timer is configured to tick when the deadline is 59 | /// reached, and to repeat at a set interval. 60 | Interval { 61 | /// When this timer will next tick 62 | deadline: Instant, 63 | /// Interval between ticks. 64 | interval: Duration, 65 | }, 66 | } 67 | 68 | impl TimerState { 69 | /// Returns the point in time when this timer will next fire, or 70 | /// `None` if the timer is currently inactive. 71 | pub fn deadline(&self) -> Option { 72 | match *self { 73 | TimerState::Inactive => None, 74 | TimerState::Timeout { deadline } => Some(deadline), 75 | TimerState::Interval { deadline, .. } => Some(deadline), 76 | } 77 | } 78 | /// Returns the interval between ticks if the timer is active and set 79 | /// to repeat. 80 | pub fn interval(&self) -> Option { 81 | match *self { 82 | TimerState::Inactive | TimerState::Timeout { .. } => None, 83 | TimerState::Interval { interval, .. } => Some(interval), 84 | } 85 | } 86 | } 87 | 88 | impl Default for TimerState { 89 | fn default() -> Self { 90 | Self::Inactive 91 | } 92 | } 93 | 94 | #[derive(Debug)] 95 | enum InternalTimerState { 96 | Inactive, 97 | Timeout { 98 | deadline: Instant, 99 | }, 100 | IntervalWeak { 101 | addr: WeakAddr, 102 | deadline: Instant, 103 | interval: Duration, 104 | }, 105 | IntervalStrong { 106 | addr: Addr, 107 | deadline: Instant, 108 | interval: Duration, 109 | }, 110 | } 111 | 112 | impl Default for InternalTimerState { 113 | fn default() -> Self { 114 | Self::Inactive 115 | } 116 | } 117 | 118 | impl InternalTimerState { 119 | fn public_state(&self) -> TimerState { 120 | match *self { 121 | InternalTimerState::Inactive => TimerState::Inactive, 122 | InternalTimerState::Timeout { deadline } => TimerState::Timeout { deadline }, 123 | InternalTimerState::IntervalWeak { 124 | deadline, interval, .. 125 | } 126 | | InternalTimerState::IntervalStrong { 127 | deadline, interval, .. 128 | } => TimerState::Interval { deadline, interval }, 129 | } 130 | } 131 | } 132 | 133 | /// A timer suitable for use by actors. 134 | #[derive(Debug, Default)] 135 | pub struct Timer { 136 | runtime: R, 137 | state: InternalTimerState, 138 | } 139 | 140 | impl Timer { 141 | /// Construct a new timer with the provided runtime. 142 | pub fn new(runtime: R) -> Self { 143 | Self { 144 | runtime, 145 | state: InternalTimerState::Inactive, 146 | } 147 | } 148 | /// Get the state of the timer 149 | pub fn state(&self) -> TimerState { 150 | self.state.public_state() 151 | } 152 | /// True if this timer is expected to tick in the future. 153 | pub fn is_active(&self) -> bool { 154 | self.state() != TimerState::Inactive 155 | } 156 | /// Reset the timer to the inactive state. 157 | pub fn clear(&mut self) { 158 | self.state = InternalTimerState::Inactive; 159 | } 160 | /// Check if the timer has elapsed. 161 | pub fn tick(&mut self) -> bool { 162 | match mem::replace(&mut self.state, InternalTimerState::Inactive) { 163 | InternalTimerState::Inactive => false, 164 | InternalTimerState::Timeout { deadline } => { 165 | if deadline <= Instant::now() { 166 | true 167 | } else { 168 | self.state = InternalTimerState::Timeout { deadline }; 169 | false 170 | } 171 | } 172 | InternalTimerState::IntervalWeak { 173 | deadline, 174 | interval, 175 | addr, 176 | } => { 177 | if deadline <= Instant::now() { 178 | self.set_interval_at_weak_internal(addr, deadline + interval, interval); 179 | true 180 | } else { 181 | self.state = InternalTimerState::IntervalWeak { 182 | deadline, 183 | interval, 184 | addr, 185 | }; 186 | false 187 | } 188 | } 189 | InternalTimerState::IntervalStrong { 190 | deadline, 191 | interval, 192 | addr, 193 | } => { 194 | if deadline <= Instant::now() { 195 | self.set_interval_at_strong_internal(addr, deadline + interval, interval); 196 | true 197 | } else { 198 | self.state = InternalTimerState::IntervalStrong { 199 | deadline, 200 | interval, 201 | addr, 202 | }; 203 | false 204 | } 205 | } 206 | } 207 | } 208 | fn set_interval_at_weak_internal( 209 | &mut self, 210 | addr: WeakAddr, 211 | start: Instant, 212 | interval: Duration, 213 | ) { 214 | let addr2 = addr.clone(); 215 | let delay = self.runtime.delay(start); 216 | addr.send_fut(async move { 217 | delay.await; 218 | send!(addr2.tick()); 219 | }); 220 | 221 | self.state = InternalTimerState::IntervalWeak { 222 | deadline: start, 223 | interval, 224 | addr, 225 | }; 226 | } 227 | fn set_interval_at_strong_internal( 228 | &mut self, 229 | addr: Addr, 230 | start: Instant, 231 | interval: Duration, 232 | ) { 233 | let addr2 = addr.clone(); 234 | let delay = self.runtime.delay(start); 235 | addr.send_fut(async move { 236 | delay.await; 237 | send!(addr2.tick()); 238 | }); 239 | 240 | self.state = InternalTimerState::IntervalStrong { 241 | deadline: start, 242 | interval, 243 | addr, 244 | }; 245 | } 246 | fn set_timeout_internal( 247 | &mut self, 248 | addr: impl AddrLike, 249 | deadline: Instant, 250 | ) { 251 | let addr2 = addr.clone(); 252 | let delay = self.runtime.delay(deadline); 253 | addr.send_fut(async move { 254 | delay.await; 255 | send!(addr2.tick()); 256 | }); 257 | 258 | self.state = InternalTimerState::Timeout { deadline }; 259 | } 260 | fn run_with_timeout_internal< 261 | T: Tick + ?Sized, 262 | A: AddrLike, 263 | F: Future + Send + 'static, 264 | >( 265 | &mut self, 266 | addr: A, 267 | deadline: Instant, 268 | f: impl FnOnce(A) -> F + Send + 'static, 269 | ) { 270 | let addr2 = addr.clone(); 271 | let delay = self.runtime.delay(deadline).fuse(); 272 | 273 | addr.send_fut(async move { 274 | pin_mut!(delay); 275 | if select_biased! { 276 | _ = f(addr2.clone()).fuse() => true, 277 | _ = delay => false, 278 | } { 279 | // Future completed first, so wait for delay too 280 | delay.await; 281 | } 282 | send!(addr2.tick()); 283 | }); 284 | 285 | self.state = InternalTimerState::Timeout { deadline }; 286 | } 287 | 288 | /// Configure the timer to tick at a set interval with an initial delay. 289 | /// The timer will not try to keep the actor alive. 290 | pub fn set_interval_at_weak( 291 | &mut self, 292 | addr: WeakAddr, 293 | start: Instant, 294 | interval: Duration, 295 | ) { 296 | self.set_interval_at_weak_internal(upcast!(addr), start, interval); 297 | } 298 | /// Configure the timer to tick at a set interval with an initial delay. 299 | /// The timer will try to keep the actor alive. 300 | pub fn set_interval_at_strong( 301 | &mut self, 302 | addr: Addr, 303 | start: Instant, 304 | interval: Duration, 305 | ) { 306 | self.set_interval_at_strong_internal(upcast!(addr), start, interval); 307 | } 308 | /// Configure the timer to tick at a set interval, with the initial tick sent immediately. 309 | /// The timer will not try to keep the actor alive. 310 | pub fn set_interval_weak(&mut self, addr: WeakAddr, interval: Duration) { 311 | self.set_interval_at_weak_internal(upcast!(addr), Instant::now(), interval); 312 | } 313 | /// Configure the timer to tick at a set interval, with the initial tick sent immediately. 314 | /// The timer will try to keep the actor alive. 315 | pub fn set_interval_strong(&mut self, addr: Addr, interval: Duration) { 316 | self.set_interval_at_strong_internal(upcast!(addr), Instant::now(), interval); 317 | } 318 | /// Configure the timer to tick once at the specified time. 319 | /// The timer will not try to keep the actor alive. 320 | pub fn set_timeout_weak(&mut self, addr: WeakAddr, deadline: Instant) { 321 | self.set_timeout_internal(addr, deadline); 322 | } 323 | /// Configure the timer to tick once at the specified time. 324 | /// The timer will try to keep the actor alive until that time. 325 | pub fn set_timeout_strong(&mut self, addr: Addr, deadline: Instant) { 326 | self.set_timeout_internal(addr, deadline); 327 | } 328 | /// Configure the timer to tick once after a delay. 329 | /// The timer will not try to keep the actor alive. 330 | pub fn set_timeout_for_weak(&mut self, addr: WeakAddr, duration: Duration) { 331 | self.set_timeout_internal(addr, Instant::now() + duration); 332 | } 333 | /// Configure the timer to tick once after a delay. 334 | /// The timer will try to keep the actor alive until that time. 335 | pub fn set_timeout_for_strong(&mut self, addr: Addr, duration: Duration) { 336 | self.set_timeout_internal(addr, Instant::now() + duration); 337 | } 338 | /// Configure the timer to tick once at the specified time, whilst simultaneously 339 | /// running a task to completion. If the timeout completes first, the task will 340 | /// be dropped. 341 | /// The timer will not try to keep the actor alive. 342 | pub fn run_with_timeout_weak + Send + 'static>( 343 | &mut self, 344 | addr: WeakAddr, 345 | deadline: Instant, 346 | f: impl FnOnce(WeakAddr) -> F + Send + 'static, 347 | ) { 348 | self.run_with_timeout_internal(addr, deadline, f); 349 | } 350 | /// Configure the timer to tick once at the specified time, whilst simultaneously 351 | /// running a task to completion. If the timeout completes first, the task will 352 | /// be dropped. 353 | /// The timer will try to keep the actor alive until that time. 354 | pub fn run_with_timeout_strong + Send + 'static>( 355 | &mut self, 356 | addr: Addr, 357 | deadline: Instant, 358 | f: impl FnOnce(Addr) -> F + Send + 'static, 359 | ) { 360 | self.run_with_timeout_internal(addr, deadline, f); 361 | } 362 | /// Configure the timer to tick once at the specified time, whilst simultaneously 363 | /// running a task to completion. If the timeout completes first, the task will 364 | /// be dropped. 365 | /// The timer will not try to keep the actor alive. 366 | pub fn run_with_timeout_for_weak + Send + 'static>( 367 | &mut self, 368 | addr: WeakAddr, 369 | duration: Duration, 370 | f: impl FnOnce(WeakAddr) -> F + Send + 'static, 371 | ) { 372 | self.run_with_timeout_internal(addr, Instant::now() + duration, f); 373 | } 374 | /// Configure the timer to tick once at the specified time, whilst simultaneously 375 | /// running a task to completion. If the timeout completes first, the task will 376 | /// be dropped. 377 | /// The timer will try to keep the actor alive until that time. 378 | pub fn run_with_timeout_for_strong< 379 | T: Tick + ?Sized, 380 | F: Future + Send + 'static, 381 | >( 382 | &mut self, 383 | addr: Addr, 384 | duration: Duration, 385 | f: impl FnOnce(Addr) -> F + Send + 'static, 386 | ) { 387 | self.run_with_timeout_internal(addr, Instant::now() + duration, f); 388 | } 389 | } 390 | -------------------------------------------------------------------------------- /src/utils.rs: -------------------------------------------------------------------------------- 1 | use std::future::Future; 2 | 3 | use futures::future::FutureExt; 4 | 5 | use crate::Produces; 6 | 7 | /// A future which completes upon termination of an actor. 8 | #[derive(Debug)] 9 | pub struct Termination(pub(crate) Produces<()>); 10 | 11 | impl Future for Termination { 12 | type Output = (); 13 | 14 | fn poll( 15 | mut self: std::pin::Pin<&mut Self>, 16 | cx: &mut std::task::Context<'_>, 17 | ) -> std::task::Poll { 18 | self.0.poll_unpin(cx).map(|_| ()) 19 | } 20 | } 21 | --------------------------------------------------------------------------------