26个版本 (11个重大更新)
0.12.1 | 2024年7月4日 |
---|---|
0.11.1 | 2024年5月21日 |
0.8.0 | 2024年3月27日 |
在游戏开发类别中排名第96
每月下载量134次
在 3 crates中使用
200KB
4.5K SLoC
Bevy Defer
一个简单的异步运行时,用于执行异步协程。
动机
异步Rust非常适合建模以等待为中心的任务,如协程。在游戏开发中不使用异步是一种巨大的浪费。
想象一下,我们想要模拟一个快速的剑攻击动画,在异步Rust中这很简单
swing_animation().await;
show_damage_number().await;
damage_vfx().await;
swing_animation().await;
show_damage_number().await;
damage_vfx().await;
在每个await
点,我们等待某个操作完成,而不浪费资源在循环中空转线程或在一个系统中定义复杂的状态机。
如果我们想同时运行伤害数字和伤害特效,并在下一次攻击之前等待两者完成呢?使用async
语义就很简单了!
futures::join! {
show_damage_number(),
damage_vfx()
};
swing_animation().await;
为什么不使用bevy_tasks
呢?
bevy_tasks
没有直接访问世界的能力,这使得在其中编写游戏逻辑变得困难。
bevy_defer
背后的核心思想很简单
// Pseudocode
static WORLD_CELL: Mutex<&mut World>;
fn run_async_executor(world: &mut World) {
let executor = world.get_executor();
WORLD_CELL.set(world);
executor.run();
WORLD_CELL.remove(world);
}
在执行器上生成的Futures可以通过访问函数访问World
,类似于数据库事务的工作方式
WORLD_CELL.with(|world: &mut World| {
world.entity(entity).get::<Transform>().clone()
})
只要不能从世界中借用引用,并且执行器是单线程的,这完全没问题!
启动任务
您可以从World
、App
、Commands
、AsyncWorld
或AsyncExecutor
将任务启动到bevy_defer
上。
以下是一个示例
commands.spawn_task(|| async move {
// Wait for state to be `GameState::Animating`.
AsyncWorld.state_stream::<GameState>().filter(|x| x == &GameState::Animating).next().await;
// Obtain info from a resource.
// Since the `World` stored as a thread local,
// a closure is the preferable syntax to access it.
let richard_entity = AsyncWorld.resource::<NamedEntities>()
.get(|res| *res.get("Richard").unwrap())?;
// Move to an entity's scope, does not verify the entity exists.
let richard = AsyncWorld.entity(richard_entity);
// We can also mutate the world directly.
richard.component::<HP>().set(|hp| hp.set(500))?;
// Move to a component's scope, does not verify the entity or component exists.
let animator = AsyncWorld.component::<Animator>();
// Implementing `AsyncComponentDeref` allows you to add extension methods to `AsyncComponent`.
animator.animate("Wave").await?;
// Spawn another future on the executor.
let audio = AsyncWorld.spawn(sound_routine(richard_entity));
// Dance for 5 seconds with `select`.
futures::select!(
_ = animator.animate("Dance").fuse() => (),
_ = AsyncWorld.sleep(Duration::from_secs(5)) => println!("Dance cancelled"),
);
// animate back to idle
richard.component::<Animator>().animate("Idle").await?;
// Wait for spawned future to complete
audio.await?;
// Tell the bevy App to quit.
AsyncWorld.quit();
Ok(())
});
世界访问器
所有世界访问的入口点是AsyncWorld
,例如,可以通过以下方式访问一个Component
let translation = AsyncWorld
.entity(entity)
.component::<Transform>()
.get(|t| {
t.translation
}
)
这适用于您期望的所有Bevy功能,如Resource
、Query
等。有关更多详细信息,请参阅access
模块和AsyncAccess
特质。
如果您拥有底层类型,可以通过Deref
为这些访问器添加扩展方法。有关更多详细信息,请参阅access::deref
模块。对于添加异步访问器的方法,async_access
派生宏非常有用。
我们不提供AsyncSystemParam
,相反,您应该使用基于单次系统的API在AsyncWorld
上。它们可以涵盖您需要在bevy_defer
中运行系统的所有用例。
异步基础
以下是一些您可能从异步生态系统中发现的有用公用工具。
-
AsyncWorld.spawn()
创建一个未来。 -
AsyncWorld.spawn_scoped()
创建一个未来,并提供一个获取结果的句柄。 -
AsyncWorld.yield_now()
使当前帧暂停执行,类似于协程的工作方式。 -
AsyncWorld.sleep(4.0)
暂停未来4
秒。 -
AsyncWorld.sleep_frames(4)
暂停未来4
帧。
同步与异步的桥接
对于新手用户来说,在同步和异步之间进行通信可能会很令人望而却步。请参阅这篇精彩的tokio文章:[https://tokio.rs/tokio/topics/bridging](https://tokio.rs/tokio/topics/bridging)。
从同步到异步的通信很简单,异步代码可以提供通道给同步代码,并在它们上面调用 await
,暂停任务。一旦同步代码通过通道发送数据,它就会唤醒并继续相应的任务。
从异步到同步的通信需要更多的思考。这通常意味着在异步函数中修改世界,然后系统可以在同步代码中监听这种特定的更改。
async {
entity.component::<IsJumping>().set(|j| *j == true);
}
pub fn jump_system(query: Query<Name, Changed<IsJumping>>) {
for name in &query {
println!("{} is jumping!", name);
}
}
核心原则是异步代码应该帮助同步代码做更少的工作,反之亦然!
信号和AsyncSystems
AsyncSystems
和 Signals
为用户界面提供了按实体反应的功能。查看它们各自的模块以获取更多信息。
实现细节
bevy_defer
使用一个单线程的运行时,它始终在主调度程序内的 bevy 主线程上运行,这对于简单的游戏逻辑、等待密集型或IO密集型任务来说很理想,但不应在 bevy_defer
中运行CPU密集型任务。在 bevy_tasks
中的 AsyncComputeTaskPool
是此类用例的理想选择。我们可以使用 AsyncComputeTaskPool::get().spawn()
在任务池中创建一个未来,并在 bevy_defer
中调用 await
。
使用提示
futures
和/或 futures_lite
包提供了我们使用的优秀工具。
例如,可以使用 futures::join!
来并发运行任务,并使用 futures::select!
来取消任务,例如,如果关卡已完成,则销毁任务。
版本
bevy | bevy_defer |
---|---|
0.12 | 0.1 |
0.13 | 0.2-0.11 |
0.14 | 0.12-latest |
许可证
在以下许可证下:
Apache License,版本 2.0(LICENSE-APACHE 或 [https://apache.ac.cn/licenses/LICENSE-2.0](https://apache.ac.cn/licenses/LICENSE-2.0))MIT 许可证(LICENSE-MIT 或 [https://open-source.org.cn/licenses/MIT](https://open-source.org.cn/licenses/MIT))由您选择。
贡献
欢迎贡献!
除非您明确声明,否则根据Apache-2.0许可证定义的您有意提交以包含在作品中的任何贡献,应双许可如上,不附加任何额外条款或条件。
依赖关系
~38–75MB
~1.5M SLoC