Skip to content

Timer features #14

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 13 commits into from
May 17, 2025
23 changes: 20 additions & 3 deletions .github/workflows/rust.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,10 @@ jobs:
os: [ubuntu-latest, windows-latest, macos-latest]
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
- name: Checkout
uses: actions/checkout@v4
- name: Rust Toolchain Setup
uses: dtolnay/[email protected]

- name: Install
if: ${{ matrix.os == 'ubuntu-latest' }}
Expand All @@ -26,18 +29,32 @@ jobs:

- name: Test
run: |
rustup component add clippy
cargo clippy
cargo test

cargo test --features tick_event
cargo test --features timer_registration
cargo test --features tick_event,timer_registration

- name: Bench
run: |
cargo bench
cargo bench --features tick_event
cargo bench --features timer_registration
cargo bench --features tick_event,timer_registration

- name: Build
run: |
cargo build
cargo build --release
cargo build --features tick_event
cargo build --features timer_registration
cargo build --features tick_event,timer_registration

- name: Generate Coverage Report
if: ${{ matrix.os == 'ubuntu-latest' }}
run: |
cargo tarpaulin --engine llvm --out xml --output-dir target
cargo tarpaulin --engine llvm --out xml --output-dir target --all-features

- name: Upload coverage reports to Codecov
if: ${{ matrix.os == 'ubuntu-latest' }}
Expand Down
19 changes: 18 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "ticked_async_executor"
version = "0.2.1"
version = "0.3.0"
authors = ["coder137"]
edition = "2021"
description = "Local executor that runs woken async tasks when it is ticked"
Expand All @@ -9,10 +9,27 @@ repository = "https://github.com/coder137/ticked-async-executor"
categories = ["asynchronous", "concurrency", "game-development", "simulation"]
readme = "README.md"

[features]
# Provides a tick event in the form of a `tokio::sync::watch::Receiver<f64>` as per the `delta` provided
# to the `TickedAsyncExecutorTicker::tick` API
# Also provides a timer implementation: `TickedTimerFromTickEvent`
tick_event = []

# Timers can be registered with the `TickedAsyncExecutorTicker` via the `TickedAsyncExecutorSpawner`
# The timers count down with every call to `TickedAsyncExecutorTicker::tick` API as per the delta provided
# Once the timer has elapsed, the corresponding `tokio::sync::oneshot::*` channel is notified
# Also provides a timer implementation: `TickedTimerFromTimerRegistration`
timer_registration = []

[dependencies]
async-task = "4.7"
pin-project = "1"
tokio = { version = "1", default-features = false, features = ["sync"] }

[dev-dependencies]
tokio = { version = "1", features = ["full"] }
criterion = "0.5"

[[bench]]
name = "benchmark"
harness = false
8 changes: 5 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

Async Local Executor which executes woken tasks only when it is ticked

MSRV: 1.87

# Usage

## Default Local Executor
Expand All @@ -11,7 +13,7 @@ use ticked_async_executor::*;

const DELTA: f64 = 1000.0 / 60.0;

let executor = TickedAsyncExecutor::default();
let mut executor = TickedAsyncExecutor::default();

executor.spawn_local("MyIdentifier", async move {}).detach();

Expand All @@ -28,7 +30,7 @@ use ticked_async_executor::*;

const DELTA: f64 = 1000.0 / 60.0;

let (spawner, ticker) = SplitTickedAsyncExecutor::default();
let (spawner, mut ticker) = SplitTickedAsyncExecutor::default();

spawner.spawn_local("MyIdentifier", async move {}).detach();

Expand All @@ -45,7 +47,7 @@ use ticked_async_executor::*;

const DELTA: f64 = 1000.0 / 60.0;

let executor = TickedAsyncExecutor::default();
let mut executor = TickedAsyncExecutor::default();

executor.spawn_local("MyIdentifier1", async move {}).detach();
executor.spawn_local("MyIdentifier2", async move {}).detach();
Expand Down
148 changes: 148 additions & 0 deletions benches/benchmark.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
use criterion::{criterion_group, criterion_main, Criterion};

use ticked_async_executor::TickedAsyncExecutor;

fn ticked_async_executor_benchmark(c: &mut Criterion) {
spawn_tasks_benchmark(c);

#[cfg(feature = "tick_event")]
timer_from_tick_event_benchmark(c);

#[cfg(feature = "timer_registration")]
timer_from_timer_registration_benchmark(c);
}

fn spawn_tasks_benchmark(c: &mut Criterion) {
c.bench_function("Spawn 1 task", |b| {
b.iter_with_large_drop(|| {
let mut executor = TickedAsyncExecutor::default();
executor.spawn_local("empty", async move {}).detach();
executor.tick(0.1, None);
assert_eq!(executor.num_tasks(), 0);
});
});

c.bench_function("Spawn 2 tasks", |b| {
b.iter_with_large_drop(|| {
let mut executor = TickedAsyncExecutor::default();
executor.spawn_local("empty1", async move {}).detach();
executor.spawn_local("empty2", async move {}).detach();

executor.tick(0.1, None);
assert_eq!(executor.num_tasks(), 0);
});
});

c.bench_function("Spawn 100 tasks", |b| {
b.iter_with_large_drop(|| {
let mut executor = TickedAsyncExecutor::default();
for _ in 0..100 {
executor.spawn_local("_", async move {}).detach();
}

executor.tick(0.1, None);
assert_eq!(executor.num_tasks(), 0);
});
});

c.bench_function("Spawn 1000 tasks", |b| {
b.iter_with_large_drop(|| {
let mut executor = TickedAsyncExecutor::default();
for _ in 0..1000 {
executor.spawn_local("_", async move {}).detach();
}

executor.tick(0.1, None);
assert_eq!(executor.num_tasks(), 0);
});
});

c.bench_function("Spawn 10000 tasks", |b| {
b.iter_with_large_drop(|| {
let mut executor = TickedAsyncExecutor::default();
for _ in 0..10000 {
executor.spawn_local("_", async move {}).detach();
}

executor.tick(0.1, None);
assert_eq!(executor.num_tasks(), 0);
});
});
}

#[cfg(feature = "tick_event")]
fn timer_from_tick_event_benchmark(c: &mut Criterion) {
c.bench_function("Spawn 1 timer from tick event", |b| {
b.iter_with_large_drop(|| {
let mut executor = TickedAsyncExecutor::default();

let timer = executor.create_timer_from_tick_event();
executor
.spawn_local("empty", async move {
timer.sleep_for(1.0).await;
})
.detach();

executor.wait_till_completed(0.1);
assert_eq!(executor.num_tasks(), 0);
});
});

c.bench_function("Spawn 1000 timers from tick event", |b| {
b.iter_with_large_drop(|| {
let mut executor = TickedAsyncExecutor::default();

for _ in 0..1000 {
let timer = executor.create_timer_from_tick_event();
executor
.spawn_local("empty", async move {
timer.sleep_for(1.0).await;
})
.detach();
}

executor.wait_till_completed(0.1);
assert_eq!(executor.num_tasks(), 0);
});
});
}

#[cfg(feature = "timer_registration")]
fn timer_from_timer_registration_benchmark(c: &mut Criterion) {
c.bench_function("Spawn 1 timer from timer registration", |b| {
b.iter_with_large_drop(|| {
let mut executor = TickedAsyncExecutor::default();

let timer = executor.create_timer_from_timer_registration();
executor
.spawn_local("empty", async move {
timer.sleep_for(1.0).await;
})
.detach();

executor.wait_till_completed(0.1);
assert_eq!(executor.num_tasks(), 0);
});
});

c.bench_function("Spawn 1000 timers from timer registration", |b| {
b.iter_with_large_drop(|| {
let mut executor = TickedAsyncExecutor::default();

for _ in 0..1000 {
let timer = executor.create_timer_from_timer_registration();
executor
.spawn_local("empty", async move {
timer.sleep_for(1.0).await;
})
.detach();
}

executor.wait_till_completed(0.1);
assert_eq!(executor.num_tasks(), 0);
});
});
}

criterion_group!(benches, ticked_async_executor_benchmark);
criterion_main!(benches);
11 changes: 9 additions & 2 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,12 @@ pub use split_ticked_async_executor::*;
mod ticked_async_executor;
pub use ticked_async_executor::*;

mod ticked_timer;
pub use ticked_timer::*;
#[cfg(feature = "tick_event")]
mod ticked_timer_from_tick_event;
#[cfg(feature = "tick_event")]
pub use ticked_timer_from_tick_event::*;

#[cfg(feature = "timer_registration")]
mod ticked_timer_from_timer_registration;
#[cfg(feature = "timer_registration")]
pub use ticked_timer_from_timer_registration::*;
Loading