From 515d2ad07c4842d3c9bccb0f2aac32b786bd6066 Mon Sep 17 00:00:00 2001 From: Niket Naidu Date: Tue, 15 Apr 2025 00:42:52 -0700 Subject: [PATCH 1/9] Added cargo.toml --- Cargo.toml | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 6919c0b..6ca77b3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,13 @@ [package] name = "ticked_async_executor" -version = "0.1.0" +version = "0.2.0" +authors = ["coder137"] edition = "2021" +description = "Local executor that runs woken async tasks when it is ticked" +license = "Apache-2.0" +repository = "https://github.com/coder137/ticked-async-executor" +categories = ["asynchronous"] +readme = "README.md" [dependencies] async-task = "4.7" From 0029f4ba991cbd015246f4921f32c97cf5d66315 Mon Sep 17 00:00:00 2001 From: Niket Naidu Date: Tue, 15 Apr 2025 00:43:03 -0700 Subject: [PATCH 2/9] Updated README --- README.md | 36 +++++++++++++++++++++++++++++++----- 1 file changed, 31 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 919abcb..7dba0b6 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,10 @@ # Ticked Async Executor -Rust based Async Executor which executes woken tasks only when it is ticked +Async Local Executor which executes woken tasks only when it is ticked -# Example +# Usage + +## Default Local Executor ```rust let executor = TickedAsyncExecutor::default(); @@ -10,9 +12,33 @@ let executor = TickedAsyncExecutor::default(); executor.spawn_local("MyIdentifier", async move {}).detach(); // Make sure to tick your executor to run the tasks -executor.tick(DELTA, LIMIT); +executor.tick(DELTA, None); +``` + +## Split Local Executor + +```rust +let task_state_cb: fn(TaskState) = |_state| {}; +let (spawner, ticker) = new_split_ticked_async_executor(task_state_cb); + +spawner.spawn_local("MyIdentifier", async move {}).detach(); + +// Tick your ticker to run the tasks +ticker.tick(DELTA, None); +``` + +## Limit the number of woken tasks run per tick + +```rust +let executor = TickedAsyncExecutor::default(); + +executor.spawn_local("MyIdentifier", async move {}).detach(); + +// At max 10 tasks are run +executor.tick(DELTA, Some(10)); ``` -# Limitation +# Caveats -- Does not work with the tokio runtime and async constructs that use the tokio runtime internally +- Uses the `smol` ecosystem +- Ensure that tasks are spawned on the same thread as the one that initializes the executor From 01ed2cf76b11bd185684119ba653c9e9303666a3 Mon Sep 17 00:00:00 2001 From: Niket Naidu Date: Tue, 15 Apr 2025 00:44:33 -0700 Subject: [PATCH 3/9] Updated cargo.toml --- Cargo.toml | 4 ---- 1 file changed, 4 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 6ca77b3..dadbe3a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,10 +12,6 @@ readme = "README.md" [dependencies] async-task = "4.7" pin-project = "1" - -# For timer only -# TODO, Add this under a feature gate -# TODO, Only tokio::sync::watch channel is used (find individual dependency) tokio = { version = "1.0", default-features = false, features = ["sync"] } [dev-dependencies] From cd4b267cc6b565d83ed1a571d08778f6befa7dba Mon Sep 17 00:00:00 2001 From: Niket Naidu Date: Tue, 15 Apr 2025 01:00:07 -0700 Subject: [PATCH 4/9] Added doc tests --- Cargo.toml | 2 +- README.md | 14 +++++++++++++- src/lib.rs | 2 ++ 3 files changed, 16 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index dadbe3a..94f6fd2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,7 +12,7 @@ readme = "README.md" [dependencies] async-task = "4.7" pin-project = "1" -tokio = { version = "1.0", default-features = false, features = ["sync"] } +tokio = { version = "1", default-features = false, features = ["sync"] } [dev-dependencies] tokio = { version = "1", features = ["full"] } diff --git a/README.md b/README.md index 7dba0b6..db4ca3d 100644 --- a/README.md +++ b/README.md @@ -7,6 +7,10 @@ Async Local Executor which executes woken tasks only when it is ticked ## Default Local Executor ```rust +use ticked_async_executor::*; + +const DELTA: f64 = 1000.0 / 60.0; + let executor = TickedAsyncExecutor::default(); executor.spawn_local("MyIdentifier", async move {}).detach(); @@ -18,7 +22,11 @@ executor.tick(DELTA, None); ## Split Local Executor ```rust +use ticked_async_executor::*; + +const DELTA: f64 = 1000.0 / 60.0; let task_state_cb: fn(TaskState) = |_state| {}; + let (spawner, ticker) = new_split_ticked_async_executor(task_state_cb); spawner.spawn_local("MyIdentifier", async move {}).detach(); @@ -30,11 +38,15 @@ ticker.tick(DELTA, None); ## Limit the number of woken tasks run per tick ```rust +use ticked_async_executor::*; + +const DELTA: f64 = 1000.0 / 60.0; + let executor = TickedAsyncExecutor::default(); executor.spawn_local("MyIdentifier", async move {}).detach(); -// At max 10 tasks are run +// Runs upto 10 woken tasks per tick executor.tick(DELTA, Some(10)); ``` diff --git a/src/lib.rs b/src/lib.rs index 5295ab3..2aeddef 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,3 +1,5 @@ +#![doc = include_str!("../README.md")] + mod droppable_future; use droppable_future::*; From 59fdde4403a619c066c39d7965da95d049b54d81 Mon Sep 17 00:00:00 2001 From: Niket Naidu Date: Tue, 15 Apr 2025 01:03:37 -0700 Subject: [PATCH 5/9] Updated README unit tests --- README.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/README.md b/README.md index db4ca3d..19d20cc 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,9 @@ let executor = TickedAsyncExecutor::default(); executor.spawn_local("MyIdentifier", async move {}).detach(); // Make sure to tick your executor to run the tasks +assert_eq!(executor.num_tasks(), 1); executor.tick(DELTA, None); +assert_eq!(executor.num_tasks(), 0); ``` ## Split Local Executor @@ -32,7 +34,9 @@ let (spawner, ticker) = new_split_ticked_async_executor(task_state_cb); spawner.spawn_local("MyIdentifier", async move {}).detach(); // Tick your ticker to run the tasks +assert_eq!(spawner.num_tasks(), 1); ticker.tick(DELTA, None); +assert_eq!(spawner.num_tasks(), 0); ``` ## Limit the number of woken tasks run per tick @@ -47,7 +51,9 @@ let executor = TickedAsyncExecutor::default(); executor.spawn_local("MyIdentifier", async move {}).detach(); // Runs upto 10 woken tasks per tick +assert_eq!(executor.num_tasks(), 1); executor.tick(DELTA, Some(10)); +assert_eq!(executor.num_tasks(), 0); ``` # Caveats From a78ee85821255114372636940778d02b6e112121 Mon Sep 17 00:00:00 2001 From: Niket Naidu Date: Tue, 15 Apr 2025 01:05:12 -0700 Subject: [PATCH 6/9] Updated unit tests --- README.md | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 19d20cc..2879321 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,7 @@ let executor = TickedAsyncExecutor::default(); executor.spawn_local("MyIdentifier", async move {}).detach(); -// Make sure to tick your executor to run the tasks +// Tick your executor to run the tasks assert_eq!(executor.num_tasks(), 1); executor.tick(DELTA, None); assert_eq!(executor.num_tasks(), 0); @@ -48,11 +48,14 @@ const DELTA: f64 = 1000.0 / 60.0; let executor = TickedAsyncExecutor::default(); -executor.spawn_local("MyIdentifier", async move {}).detach(); +executor.spawn_local("MyIdentifier1", async move {}).detach(); +executor.spawn_local("MyIdentifier2", async move {}).detach(); -// Runs upto 10 woken tasks per tick +// Runs upto 1 woken tasks per tick +assert_eq!(executor.num_tasks(), 2); +executor.tick(DELTA, Some(1)); assert_eq!(executor.num_tasks(), 1); -executor.tick(DELTA, Some(10)); +executor.tick(DELTA, Some(1)); assert_eq!(executor.num_tasks(), 0); ``` From ac0ea73365bcff8891c776add8e67ef156418fd0 Mon Sep 17 00:00:00 2001 From: Niket Naidu Date: Tue, 15 Apr 2025 01:24:09 -0700 Subject: [PATCH 7/9] Added SplitTickedAsyncExecutor instead of a function --- README.md | 3 +- src/split_ticked_async_executor.rs | 59 +++++++++++++++++------------- src/ticked_async_executor.rs | 4 +- 3 files changed, 37 insertions(+), 29 deletions(-) diff --git a/README.md b/README.md index 2879321..9b9538c 100644 --- a/README.md +++ b/README.md @@ -27,9 +27,8 @@ assert_eq!(executor.num_tasks(), 0); use ticked_async_executor::*; const DELTA: f64 = 1000.0 / 60.0; -let task_state_cb: fn(TaskState) = |_state| {}; -let (spawner, ticker) = new_split_ticked_async_executor(task_state_cb); +let (spawner, ticker) = SplitTickedAsyncExecutor::default(); spawner.spawn_local("MyIdentifier", async move {}).detach(); diff --git a/src/split_ticked_async_executor.rs b/src/split_ticked_async_executor.rs index 002b3c7..e0fda13 100644 --- a/src/split_ticked_async_executor.rs +++ b/src/split_ticked_async_executor.rs @@ -19,31 +19,40 @@ pub enum TaskState { pub type Task = async_task::Task; type Payload = (TaskIdentifier, async_task::Runnable); -pub fn new_split_ticked_async_executor( - observer: O, -) -> (TickedAsyncExecutorSpawner, TickedAsyncExecutorTicker) -where - O: Fn(TaskState) + Clone + Send + Sync + 'static, -{ - let (tx_channel, rx_channel) = mpsc::channel(); - let num_woken_tasks = Arc::new(AtomicUsize::new(0)); - let num_spawned_tasks = Arc::new(AtomicUsize::new(0)); - let (tx_tick_event, rx_tick_event) = tokio::sync::watch::channel(1.0); - let spawner = TickedAsyncExecutorSpawner { - tx_channel, - num_woken_tasks: num_woken_tasks.clone(), - num_spawned_tasks: num_spawned_tasks.clone(), - observer: observer.clone(), - rx_tick_event, - }; - let ticker = TickedAsyncExecutorTicker { - rx_channel, - num_woken_tasks, - num_spawned_tasks, - observer, - tx_tick_event, - }; - (spawner, ticker) +pub struct SplitTickedAsyncExecutor; + +impl SplitTickedAsyncExecutor { + pub fn default() -> ( + TickedAsyncExecutorSpawner, + TickedAsyncExecutorTicker, + ) { + Self::new(|_state| {}) + } + + pub fn new(observer: O) -> (TickedAsyncExecutorSpawner, TickedAsyncExecutorTicker) + where + O: Fn(TaskState) + Clone + Send + Sync + 'static, + { + let (tx_channel, rx_channel) = mpsc::channel(); + let num_woken_tasks = Arc::new(AtomicUsize::new(0)); + let num_spawned_tasks = Arc::new(AtomicUsize::new(0)); + let (tx_tick_event, rx_tick_event) = tokio::sync::watch::channel(1.0); + let spawner = TickedAsyncExecutorSpawner { + tx_channel, + num_woken_tasks: num_woken_tasks.clone(), + num_spawned_tasks: num_spawned_tasks.clone(), + observer: observer.clone(), + rx_tick_event, + }; + let ticker = TickedAsyncExecutorTicker { + rx_channel, + num_woken_tasks, + num_spawned_tasks, + observer, + tx_tick_event, + }; + (spawner, ticker) + } } pub struct TickedAsyncExecutorSpawner { diff --git a/src/ticked_async_executor.rs b/src/ticked_async_executor.rs index 525ab10..a014246 100644 --- a/src/ticked_async_executor.rs +++ b/src/ticked_async_executor.rs @@ -1,7 +1,7 @@ use std::future::Future; use crate::{ - new_split_ticked_async_executor, Task, TaskIdentifier, TaskState, TickedAsyncExecutorSpawner, + SplitTickedAsyncExecutor, Task, TaskIdentifier, TaskState, TickedAsyncExecutorSpawner, TickedAsyncExecutorTicker, TickedTimer, }; @@ -21,7 +21,7 @@ where O: Fn(TaskState) + Clone + Send + Sync + 'static, { pub fn new(observer: O) -> Self { - let (spawner, ticker) = new_split_ticked_async_executor(observer); + let (spawner, ticker) = SplitTickedAsyncExecutor::new(observer); Self { spawner, ticker } } From 3581e73454a851268b8b8e885d4377efed2fb682 Mon Sep 17 00:00:00 2001 From: Niket Naidu Date: Tue, 15 Apr 2025 01:26:18 -0700 Subject: [PATCH 8/9] Updated README --- README.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/README.md b/README.md index 9b9538c..b433167 100644 --- a/README.md +++ b/README.md @@ -62,3 +62,10 @@ assert_eq!(executor.num_tasks(), 0); - Uses the `smol` ecosystem - Ensure that tasks are spawned on the same thread as the one that initializes the executor + +# Roadmap + +- [x] TickedAsyncExecutor +- [x] SplitTickedAsyncExecutor + - Similar to the channel API, but spawner and ticker cannot be moved to different threads +- [ ] Tracing From 601ca8c155a9ca8a55d1312d051fcd6f9915e3b8 Mon Sep 17 00:00:00 2001 From: Niket Naidu Date: Tue, 15 Apr 2025 22:36:10 -0700 Subject: [PATCH 9/9] Added cargo categories --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 94f6fd2..923b932 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,7 +6,7 @@ edition = "2021" description = "Local executor that runs woken async tasks when it is ticked" license = "Apache-2.0" repository = "https://github.com/coder137/ticked-async-executor" -categories = ["asynchronous"] +categories = ["asynchronous", "concurrency", "game-development", "simulation"] readme = "README.md" [dependencies]