8个版本 (破坏性更新)
0.7.0 | 2024年7月5日 |
---|---|
0.6.0 | 2024年6月10日 |
0.5.0 | 2024年5月3日 |
0.4.0 | 2024年5月3日 |
0.1.0 | 2024年1月26日 |
#353 in 游戏开发
每月32次下载
7MB
460 行
bevy_mod_async
bevy_mod_async
是我为Bevy中的异步任务创建的一个更便捷的API。它建立在bevy_tasks
的executor之上,因此不需要引入另一个异步运行时(尽管bevy_mod_async
确实使用了tokio
的一些类型来提供其异步接口——这在未来可以被替换)。
使用方法
添加 AsyncTasksPlugin
确保所有异步任务都将得到适当更新,并添加了 TaskContext
需要用于启动任务所需资源
use bevy_mod_async::prelude::*;
...
app.add_plugins(AsyncTasksPlugin);
之后,bevy_mod_async
有两个主要的API:commands.spawn_task()
(接受一个类型为 TaskContext
的单个参数的异步闭包)
commands.spawn_task(|cx| async move {
...
});
和 TaskContext::with_world
,它用于专门、异步地访问世界
cx.with_world(|world| {
// do anything with `world` here as in an exclusive system
let e = world.spawn(()).id();
world.despawn(e);
world.resource_mut::<Counter>().0 += 1;
}).await;
在此API之上提供了一些便利方法
let e = cx.spawn(()).await;
cx.despawn(e).await;
let a = cx.load_asset::<Mesh>("model.glb#Mesh0").await.unwrap();
许多这些API返回 WithWorld
,一个 Future
,当等待它时,返回在 World
上执行命令的结果。这意味着在等待这个future之后,世界上的任何修改都将生效。由于WithWorld
futures(默认情况下)每帧只推进一次,这也意味着每次 .await
通常会延迟一帧的执行。如果这不可取,任务也可以分离
cx.spawn(()).detach();
这将仍然将任务推送到Bevy的executor,但它不会挂起执行(这显然也意味着世界没有被修改)。
动机
普通的bevy_tasks
有什么问题?嗯,Bevy启动异步任务的主要API使用AsyncTaskPool
let task = AsyncComputeTaskPool::get().spawn(my_task);
一旦您启动了一个新的任务,执行器就会负责轮询结果的完成。但您如何访问结果呢?您必须自己进行轮询,通常使用 Bevy 系统。
struct MyTask(Task<..>);
fn handle_task_completion(mut tasks: Query<&mut MyTask>, mut commands: Commands) {
for mut task in &mut tasks {
if task.is_finished() {
let result = block_on(poll_once(&mut task.0));
// handle result
}
}
}
如果您正在启动大量类似的异步任务,这还不错。对于一次性异步任务,例如响应资源加载,这就很多样板代码。相反,您可以在 async
块中处理异步任务的完成。
AsyncComputeTaskPool::get().spawn(async move {
let result = my_task.await;
println!("{result}");
}).detach();
当然,这种方法仅适用于您能够在不访问 ECS 的情况下舒适地处理结果,因为任务本身无法访问 World
。如果您想要避免创建特定于任务的系统,另一种方法是用通道将任务发送到任务处理系统,并返回它们的结果。这是 bevy_mod_tasks
使用的方案。有一个专用的系统来运行队列中的任务,一个资源来累积它们,以及通道在专用系统与您的任务之间传递任务和结果。
这显然有一些限制:所有对 World
的访问都必须等到下一次这个专用系统运行时才能获取结果,并且这些访问都不能并行化。另一方面,如果您只是做些像加载资源这样简单的事情,等待它加载完成,然后再将其加载到场景中,bevy_mod_async
提供了一个简单的 API,以易于阅读、线性的方式编写这个逻辑。在这种情况下,性能影响可以忽略不计:任务只运行一次,并且是在加载时间,而不是在热游戏循环中。
示例
hello_world
演示了 TaskContext
的一些异步 API,并试图简单地解释其内部工作原理。展示了如何订阅异步事件流以及基本的 UI 响应。
async_asset
演示了异步加载资源。启动加载界面,然后在场景准备好加载时销毁它。
timers
演示了 sleep
API,以及在没有界面运行的 Bevy 中启动异步任务。
Bevy 版本
bevy | bevy_mod_async |
---|---|
0.12 | 0.1-0.2 |
0.13 | 0.3-0.6 |
0.14 | 0.7 |
贡献
欢迎 Pull Request。我必须承认我不是异步 Rust 编程的世界级专家,也没有对 Bevy 的 ECS 内部有深刻的理解。如果有人有性能、易用性或其他改进的想法,我愿意接受贡献。
依赖项
~12–48MB
~775K SLoC