Skip to content

Commit

Permalink
feat: bevy_async_task 0.3
Browse files Browse the repository at this point in the history
  • Loading branch information
simbleau committed Dec 1, 2024
1 parent 5f62a7c commit edefd93
Show file tree
Hide file tree
Showing 16 changed files with 400 additions and 221 deletions.
8 changes: 8 additions & 0 deletions .clippy.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# LINEBENDER LINT SET - .clippy.toml - v1
# See https://linebender.org/wiki/canonical-lints/
# The default Clippy value is capped at 8 bytes, which was chosen to improve performance on 32-bit.
# Given that we are building for the future and even low-end mobile phones have 64-bit CPUs,
# it makes sense to optimize for 64-bit and accept the performance hits on 32-bit.
# 16 bytes is the number of bytes that fits into two 64-bit CPU registers.
trivial-copy-size-limit = 16
# END LINEBENDER LINT SET
22 changes: 22 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,28 @@ Subheadings to categorize changes are `added, changed, deprecated, removed, fixe

## Unreleased

## 0.3.0

### Added

- Many types now implement `Debug`.

### Changed

- Updated to bevy 0.15.
- `AsyncTaskStatus` was replaced with `std::task::Poll`. If you wish to check if a task runner or pool is idle, you can still do so with `<AsyncTaskRunner>.is_idle()`/`<AsyncTaskPool>.is_idle()`.
- Replaced `TimeoutError` with a non-exhaustive `TaskError` for future proofing.

### Removed

- `blocking_recv()` functions were removed. You should now use `bevy::tasks::block_on(fut)`.
- `AsyncTask.with_timeout()` until further rework is done to return this functionality. Please use `AsyncTask::new_with_duration(Dur, F)` instead.

### Fixed

- Timeouts now work correctly on wasm32.
- `AsyncReceiver` now uses an `AtomicWaker` to ensure the sender is never dropped before receiving.

## 0.2.0

### Changed
Expand Down
79 changes: 69 additions & 10 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,30 +9,89 @@ license = "Apache-2.0 OR MIT"
repository = "https://github.com/loopystudios/bevy_async_task"
authors = ["Spencer C. Imbleau"]
keywords = ["gamedev", "async"]
version = "0.2.0"
categories = ["game-development", "asynchronous"]
version = "0.3.0"
edition = "2021"

[lints]
# LINEBENDER LINT SET - Cargo.toml - v2
# See https://linebender.org/wiki/canonical-lints/
rust.keyword_idents_2024 = "forbid"
rust.non_ascii_idents = "forbid"
rust.non_local_definitions = "forbid"
rust.unsafe_op_in_unsafe_fn = "forbid"
rust.elided_lifetimes_in_paths = "warn"
rust.let_underscore_drop = "warn"
rust.missing_debug_implementations = "warn"
rust.missing_docs = "warn"
rust.single_use_lifetimes = "warn"
rust.trivial_numeric_casts = "warn"
rust.unexpected_cfgs = "warn"
rust.unit_bindings = "warn"
rust.unnameable_types = "warn"
rust.unreachable_pub = "warn"
rust.unused_import_braces = "warn"
rust.unused_lifetimes = "warn"
rust.unused_macro_rules = "warn"
rust.unused_qualifications = "warn"
rust.variant_size_differences = "warn"
clippy.allow_attributes = "warn"
clippy.allow_attributes_without_reason = "warn"
clippy.cast_possible_truncation = "warn"
clippy.collection_is_never_read = "warn"
clippy.dbg_macro = "warn"
clippy.debug_assert_with_mut_call = "warn"
clippy.doc_markdown = "warn"
clippy.exhaustive_enums = "warn"
clippy.fn_to_numeric_cast_any = "forbid"
clippy.infinite_loop = "warn"
clippy.large_include_file = "warn"
clippy.large_stack_arrays = "warn"
clippy.match_same_arms = "warn"
clippy.mismatching_type_param_order = "warn"
clippy.missing_assert_message = "warn"
clippy.missing_errors_doc = "warn"
clippy.missing_fields_in_debug = "warn"
clippy.missing_panics_doc = "warn"
clippy.partial_pub_fields = "warn"
clippy.return_self_not_must_use = "warn"
clippy.same_functions_in_if_condition = "warn"
clippy.semicolon_if_nothing_returned = "warn"
clippy.shadow_unrelated = "warn"
clippy.should_panic_without_expect = "warn"
clippy.todo = "warn"
clippy.trivially_copy_pass_by_ref = "warn"
clippy.unseparated_literal_suffix = "warn"
clippy.use_self = "warn"
clippy.wildcard_imports = "warn"
clippy.cargo_common_metadata = "warn"
clippy.negative_feature_names = "warn"
clippy.redundant_feature_names = "warn"
clippy.wildcard_dependencies = "warn"
# END LINEBENDER LINT SET

[lib]

[dependencies]
tokio = { version = "1.38.0", default-features = false, features = ["sync"] }
bevy = { version = "0.14.0", default-features = false, features = [
tokio = { version = "1.41.1", default-features = false, features = ["sync"] }
bevy = { version = "0.15.0", default-features = false, features = [
"multi_threaded",
] }
async-std = "1.12.0"
async-std = "1.13.0"
thiserror = "2.0.3"
futures = "0.3.31"

[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
async-compat = "0.2.4"

[dev-dependencies]
futures = "0.3.30"
futures-timer = "3.0.3"

[target.'cfg(not(target_arch = "wasm32"))'.dev-dependencies]
tokio = { version = "1.38.0", features = ["full"] }
tokio = { version = "1.41.1", features = ["full"] }

[target.'cfg(target_arch = "wasm32")'.dev-dependencies]
wasm-bindgen-futures = "0.4.42"
wasm-bindgen-test = "0.3.42"
js-sys = "0.3.69"
wasm-bindgen = "0.2.92"
wasm-bindgen-futures = "0.4.47"
wasm-bindgen-test = "0.3.47"
js-sys = "0.3.74"
wasm-bindgen = "0.2.97"
37 changes: 13 additions & 24 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ A minimum crate for ergonomic abstractions to async programming in Bevy. There i

</div>

Bevy Async Task provides Bevy system parameters to run asyncronous tasks in the background with timeout support and future output in the same system. It also provides syntactic sugar to reduce boiler plate when blocking on futures within synchronous contexts.
Bevy Async Task provides Bevy system parameters to run asynchronous tasks in the background on web and native with timeouts and output capture.

## Bevy version support

Expand Down Expand Up @@ -46,18 +46,19 @@ async fn long_task() -> u32 {
5
}

fn my_system(mut task_executor: AsyncTaskRunner<u32>) {
match task_executor.poll() {
AsyncTaskStatus::Idle => {
task_executor.start(long_task());
info!("Started new task!");
}
AsyncTaskStatus::Pending => {
// <Insert loading screen>
}
AsnycTaskStatus::Finished(v) => {
fn my_system(mut task_runner: AsyncTaskRunner<u32>) {
if task_runner.is_idle() {
task_executor.start(long_task());
info!("Started!");
}

match task_runner.poll() {
Poll::Ready(v) => {
info!("Received {v}");
}
Poll::Pending => {
// Waiting...
}
}
}
```
Expand All @@ -84,19 +85,7 @@ fn my_system(mut task_pool: AsyncTaskPool<u64>) {
}
```

Also, you may use timeouts or block on an `AsyncTask<T>`:

```rust
// Blocking:
let task = AsyncTask::new(async { 5 });
assert_eq!(5, task.blocking_recv());

// Timeout:
let task = AsyncTask::<()>::pending().with_timeout(Duration::from_millis(10));
assert!(task.blocking_recv().is_err());
```

Need to steer manually? Break the task into parts.
Need to steer manually? Break the task into parts. Also see our [`cross_system` example](./examples/cross_system.rs).

```rust
let task = AsyncTask::new(async move {
Expand Down
22 changes: 0 additions & 22 deletions examples/blocking.rs

This file was deleted.

47 changes: 47 additions & 0 deletions examples/cross_system.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
//! Cross system example - This example shows how to start a task from one system and poll it from
//! another through a resource.
use async_std::task::sleep;
use bevy::{app::PanicHandlerPlugin, log::LogPlugin, prelude::*, tasks::AsyncComputeTaskPool};
use bevy_async_task::{AsyncReceiver, AsyncTask};
use std::time::Duration;

#[derive(Resource, DerefMut, Deref, Default)]
struct MyTask(Option<AsyncReceiver<u32>>);

/// An async task that takes time to compute!
async fn long_task() -> u32 {
sleep(Duration::from_millis(1000)).await;
5
}

fn system1_start(mut my_task: ResMut<'_, MyTask>) {
let (fut, receiver) = AsyncTask::new(long_task()).into_parts();
my_task.replace(receiver);
AsyncComputeTaskPool::get().spawn_local(fut).detach();
info!("Started!");
}

fn system2_poll(mut my_task: ResMut<'_, MyTask>) {
let Some(receiver) = my_task.0.as_mut() else {
return;
};
match receiver.try_recv() {
Some(v) => {
info!("Received {v}");
}
None => {
// Waiting...
}
}
}

/// Entry point
pub fn main() {
App::new()
.init_resource::<MyTask>()
.add_plugins((MinimalPlugins, LogPlugin::default(), PanicHandlerPlugin))
.add_systems(Startup, system1_start)
.add_systems(Update, system2_poll)
.run();
}
11 changes: 7 additions & 4 deletions examples/pool.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
//! Task pool example - this demonstrates running several async tasks concurrently.
use async_std::task::sleep;
use bevy::{app::PanicHandlerPlugin, log::LogPlugin, prelude::*};
use bevy_async_task::{AsyncTaskPool, AsyncTaskStatus};
use std::time::Duration;
use bevy_async_task::AsyncTaskPool;
use std::{task::Poll, time::Duration};

fn system1(mut task_pool: AsyncTaskPool<u64>) {
fn system1(mut task_pool: AsyncTaskPool<'_, u64>) {
if task_pool.is_idle() {
info!("Queueing 5 tasks...");
for i in 1..=5 {
Expand All @@ -15,12 +17,13 @@ fn system1(mut task_pool: AsyncTaskPool<u64>) {
}

for status in task_pool.iter_poll() {
if let AsyncTaskStatus::Finished(t) = status {
if let Poll::Ready(t) = status {
info!("Received {t}");
}
}
}

/// Entry point
pub fn main() {
App::new()
.add_plugins((MinimalPlugins, LogPlugin::default(), PanicHandlerPlugin))
Expand Down
1 change: 1 addition & 0 deletions examples/run_wasm/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
[package]
name = "run_wasm"
edition = "2021"
publish = false

[dependencies]
Expand Down
32 changes: 18 additions & 14 deletions examples/simple.rs
Original file line number Diff line number Diff line change
@@ -1,32 +1,36 @@
//! Simple example - this demonstrates running one async task continuously.
use async_std::task::sleep;
use bevy::{app::PanicHandlerPlugin, log::LogPlugin, prelude::*};
use bevy_async_task::{AsyncTaskRunner, AsyncTaskStatus};
use std::time::Duration;
use bevy_async_task::AsyncTaskRunner;
use std::{task::Poll, time::Duration};

/// An async task that takes time to compute!
async fn long_task() -> u32 {
sleep(Duration::from_millis(1000)).await;
5
}

fn my_system(mut task_executor: AsyncTaskRunner<u32>) {
match task_executor.poll() {
AsyncTaskStatus::Idle => {
// Start an async task!
task_executor.start(long_task());
// Closures also work:
// task_executor.start(async { 5 });
info!("Started!");
fn my_system(mut task_runner: AsyncTaskRunner<'_, u32>) {
if task_runner.is_idle() {
// Start an async task!
task_runner.start(long_task());
// Closures also work:
// task_executor.start(async { 5 });
info!("Started!");
}

match task_runner.poll() {
Poll::Ready(v) => {
info!("Received {v}");
}
AsyncTaskStatus::Pending => {
Poll::Pending => {
// Waiting...
}
AsyncTaskStatus::Finished(v) => {
info!("Received {v}");
}
}
}

/// Entry point
pub fn main() {
App::new()
.add_plugins((MinimalPlugins, LogPlugin::default(), PanicHandlerPlugin))
Expand Down
Loading

0 comments on commit edefd93

Please sign in to comment.