Skip to content

Commit 9c70810

Browse files
committed
tokio runtime recipe
1 parent 6f2faf6 commit 9c70810

File tree

2 files changed

+98
-0
lines changed

2 files changed

+98
-0
lines changed

src/SUMMARY.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
- [Logging](./recipes/logging.md)
2929
- [Loading external resources](./recipes/loading_external_resources.md)
3030
- [Custom nodes Plugin](./recipes/custom-nodes-plugin.md)
31+
- [Tokio Runtime](./recipes/tokio_runtime.md)
3132
- [Exporting](./exporting.md)
3233
- [Android](./exporting/android.md)
3334
- [(TODO) iOS](./exporting/ios.md)

src/recipes/tokio_runtime.md

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
# Tokio Runtime
2+
3+
This recipe is based off of the test written for `gdnative-async`, which uses the `futures` crate in the executor. For cases where you may need a `tokio` runtime, it is possible to execute spawned tokio tasks in much the same way, with some alterations.
4+
5+
## Defining the Executor
6+
The executor itself can be defined the same way.
7+
8+
```rust
9+
thread_local! {
10+
static EXECUTOR: &'static SharedLocalPool = {
11+
Box::leak(Box::new(SharedLocalPool::default()))
12+
};
13+
}
14+
```
15+
16+
However, our `SharedLocalPool` will store a `LocalSet` instead, and the `futures::task::LocalSpawn` implementation for the type will simply spawn a local task from that.
17+
18+
```rust
19+
use tokio::task::LocalSet;
20+
21+
#[derive(Default)]
22+
struct SharedLocalPool {
23+
local_set: LocalSet,
24+
}
25+
26+
impl futures::task::LocalSpawn for SharedLocalPool {
27+
fn spawn_local_obj(
28+
&self,
29+
future: futures::task::LocalFutureObj<'static, ()>,
30+
) -> Result<(), futures::task::SpawnError> {
31+
self.local_set.spawn_local(future);
32+
33+
Ok(())
34+
}
35+
}
36+
```
37+
38+
## The Executor Driver
39+
40+
Finally, we need to create a `NativeClass` which will act as the driver for our executor. This will store the tokio `Runtime`.
41+
42+
```rust
43+
use tokio::runtime::{Builder, Runtime};
44+
45+
#[derive(NativeClass)]
46+
#[inherit(Node)]
47+
struct AsyncExecutorDriver {
48+
runtime: Runtime,
49+
}
50+
51+
impl AsyncExecutorDriver {
52+
fn new(_owner: &Node) -> Self {
53+
AsyncExecutorDriver {
54+
runtime: Builder::new_current_thread()
55+
.enable_io() // optional, depending on your needs
56+
.enable_time() // optional, depending on your needs
57+
.build()
58+
.unwrap(),
59+
}
60+
}
61+
}
62+
```
63+
64+
In the `_process` call of our `AsyncExecutorDriver`, we can block on `run_until` on the `LocalSet`, which will run or wake any of its local tasks.
65+
66+
```rust
67+
#[methods]
68+
impl AsyncExecutorDriver {
69+
#[export]
70+
fn _process(&self, _owner: &Node, _delta: f64) {
71+
EXECUTOR.with(|e| {
72+
self.runtime
73+
.block_on(async {
74+
e.local_set
75+
.run_until(async {
76+
tokio::task::spawn_local(async {}).await
77+
})
78+
.await
79+
})
80+
.unwrap()
81+
})
82+
}
83+
}
84+
```
85+
86+
From there, initializing is just the same as it is in the tests.
87+
88+
```rust
89+
fn init(handle: InitHandle) {
90+
gdnative::tasks::register_runtime(&handle);
91+
gdnative::tasks::set_executor(EXECUTOR.with(|e| *e));
92+
93+
...
94+
handle.add_class::<AsyncExecutorDriver>();
95+
}
96+
```
97+

0 commit comments

Comments
 (0)