4次发布
新 0.4.2 | 2024年8月21日 |
---|---|
0.4.1 | 2024年8月1日 |
0.4.0 | 2024年7月12日 |
0.3.0 | 2024年7月11日 |
0.1.1 |
|
#848 in 游戏开发
229 每月下载量
33KB
597 行
gdext_coroutines
"在Godot 4.2+中运行Rust协程和异步代码(通过GDExtension),灵感来源于Unity的协程设计。"
注意
此crate使用了5个nightly(不稳定)功能
#![feature(coroutines)]
#![feature(coroutine_trait)]
#![feature(stmt_expr_attributes)]
#![feature(unboxed_closures)]
#![cfg_attr(feature = "async", feature(async_fn_traits))]
它还要求GdExtension的experimental_threads
功能
设置
将依赖项添加到您的Cargo.toml文件中
[dependencies]
gdext_coroutines = "0.4"
这会做什么?
允许您以异步方式执行代码,此crate的协程与Unity的协程非常相似。
它还允许您执行异步代码(futures),实现使用crate smol
,并需要功能 async
。
#![feature(coroutines)]
use gdext_coroutines::prelude::*;
use godot::prelude::*;
fn run_some_routines(node: Gd<Label>) {
node.start_coroutine(
#[coroutine] || {
godot_print!("Starting coroutine");
godot_print!("Waiting for 5 seconds...");
yield seconds(5.0);
godot_print!("5 seconds have passed!");
godot_print!("Waiting for 30 frames");
yield frames(30);
godot_print!("30 frames have passed!");
godot_print!("Waiting until pigs start flying...");
let pig: Gd<Node2D> = create_pig();
yield wait_until(move || pig.is_flying());
godot_print!("Wow! Pigs are now able to fly! Somehow...");
godot_print!("Waiting while pigs are still flying...");
let pig: Gd<Node2D> = grab_pig();
yield wait_while(move || pig.is_flying());
godot_print!("Finally, no more flying pigs, oof.");
});
node.start_async_task(
async {
godot_print!("Executing async code!");
smol::Timer::after(Duration::from_secs(10)).await;
godot_print!("Async function finished after 10 real time seconds!");
});
}
有关更多示例,请查看仓库中的integration_tests
文件夹。
它是如何做到的?
协程是一个派生自Node
的结构体
#[derive(GodotClass)]
#[class(no_init, base = Node)]
pub struct SpireCoroutine { /* .. */ }
当你调用start_coroutine()
、start_async_task()
或spawn()
时,会创建一个SpireCoroutine
节点,然后将其添加为调用者的子节点。
然后,在每一帧
- Rust协程(
start_coroutine
):轮询当前yield以推进其内部函数。 - Rust未来(
start_async_task
):检查未来是否已执行完毕。
#[godot_api]
impl INode for SpireCoroutine {
fn process(&mut self, delta: f64) {
if !self.paused && self.poll_mode == PollMode::Process {
self.run(delta);
}
}
fn physics_process(&mut self, delta: f64) {
if !self.paused && self.poll_mode == PollMode::Physics {
self.run(delta);
}
}
}
然后它会在完成后自动销毁自身
fn run(&mut self, delta_time: f64) {
if let Some(result) = self.poll(delta_time) {
self.finish_with(result);
}
}
pub fn finish_with(&mut self, result: Variant) {
/* .. */
self.base_mut().emit_signal(SIGNAL_FINISHED.into(), &[result]);
self.de_spawn();
}
由于协程是其创建者的子节点,其行为与其父节点相关
- 如果父节点退出场景树,协程将暂停运行(因为它需要
_process/_physics_process
来运行)。 - 如果父节点被排队释放,协程也会被排队释放,并且其
finished
信号永远不会触发。
还有1
您可以使用信号 finished
从 GdScript 中等待协程。
var coroutine: SpireCoroutine = ..
var result = await coroutine.finished
result
包含了您协程/未来的返回值。
另外还有 2
您可以创建自己的自定义类型 yield,只需实现特质 KeepWaiting
pub trait KeepWaiting {
/// The coroutine calls this to check if it should keep waiting
fn keep_waiting(&mut self) -> bool;
}
然后您可以像这样使用这个特质
let my_custom_yield: dyn KeepWaiting = ...;
yield Yield::Dyn(Box::new(my_custom_yield));
依赖项
~5–15MB
~232K SLoC