Skip to content

Commit 82f928e

Browse files
committed
Resolve concerns for the original PR
- `set_executor` and `register_runtime` are changed to allow multiple calls, at the expense of slightly worse error messages when they aren't called as necessary. - Renamed the top-level re-export to `tasks` instead of `asn`. - Added documentation for previously undocumented public items. - Removed `async` from default features
1 parent ffe0248 commit 82f928e

File tree

14 files changed

+40
-91
lines changed

14 files changed

+40
-91
lines changed

README.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,10 @@ crate-type = ["cdylib"]
5656

5757
The bindings are currently generated from the API description of Godot 3.2.3-stable by default. To use the bindings with another version or a custom build, see [Using custom builds of Godot](https://godot-rust.github.io/book/advanced-guides/custom-bindings.html) in the user guide.
5858

59+
### Async / "`yield`" support
60+
61+
Async support is a work-in-progress, with a low-level API available in the `gdnative-async` crate. This crate is re-exported as `gdnative::tasks`, if the `async` feature is enabled on `gdnative`.
62+
5963
## Example
6064

6165
The most general use-case of the bindings will be to interact with Godot using the generated wrapper

gdnative-async/Cargo.toml

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,11 @@ edition = "2018"
1616
gdnative-derive = { path = "../gdnative-derive", version = "=0.9.3" }
1717
gdnative-core = { path = "../gdnative-core", version = "=0.9.3" }
1818
gdnative-bindings = { path = "../gdnative-bindings", version = "=0.9.3" }
19-
futures-task = "0.3.13"
19+
futures-task = "0.3.17"
2020
atomic-waker = "1.0.0"
21-
once_cell = "1.7.2"
22-
thiserror = "1.0"
23-
parking_lot = "0.11.0"
24-
crossbeam-channel = "0.5.0"
21+
once_cell = "1.8.0"
22+
parking_lot = "0.11.2"
23+
crossbeam-channel = "0.5.1"
24+
crossbeam-utils = "0.8.5"
2525

2626
[build-dependencies]

gdnative-async/src/executor.rs

Lines changed: 7 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,45 +1,21 @@
1+
use std::cell::Cell;
2+
13
use futures_task::LocalSpawn;
2-
use once_cell::unsync::OnceCell as UnsyncCell;
3-
use thiserror::Error;
44

55
thread_local!(
6-
static LOCAL_SPAWN: UnsyncCell<&'static dyn LocalSpawn> = UnsyncCell::new();
6+
static LOCAL_SPAWN: Cell<Option<&'static dyn LocalSpawn>> = Cell::new(None);
77
);
88

9-
/// Error returned by `set_*_executor` if an executor of the kind has already been set.
10-
#[derive(Error, Debug)]
11-
#[error("an executor is already set")]
12-
pub struct SetExecutorError {
13-
_private: (),
14-
}
15-
16-
impl SetExecutorError {
17-
fn new() -> Self {
18-
SetExecutorError { _private: () }
19-
}
20-
}
21-
229
pub(crate) fn local_spawn() -> Option<&'static dyn LocalSpawn> {
23-
LOCAL_SPAWN.with(|cell| cell.get().copied())
10+
LOCAL_SPAWN.with(|cell| cell.get())
2411
}
2512

2613
/// Sets the global executor for the current thread to a `Box<dyn LocalSpawn>`. This value is leaked.
27-
pub fn set_boxed_executor(sp: Box<dyn LocalSpawn>) -> Result<(), SetExecutorError> {
14+
pub fn set_boxed_executor(sp: Box<dyn LocalSpawn>) {
2815
set_executor(Box::leak(sp))
2916
}
3017

3118
/// Sets the global executor for the current thread to a `&'static dyn LocalSpawn`.
32-
pub fn set_executor(sp: &'static dyn LocalSpawn) -> Result<(), SetExecutorError> {
33-
LOCAL_SPAWN.with(|cell| cell.set(sp).map_err(|_| SetExecutorError::new()))
34-
}
35-
36-
/// Sets the global executor for the current thread with a function that will only be called
37-
/// if an executor isn't set yet.
38-
pub fn ensure_executor_with<F>(f: F)
39-
where
40-
F: FnOnce() -> &'static dyn LocalSpawn,
41-
{
42-
LOCAL_SPAWN.with(|cell| {
43-
cell.get_or_init(f);
44-
});
19+
pub fn set_executor(sp: &'static dyn LocalSpawn) {
20+
LOCAL_SPAWN.with(|cell| cell.set(Some(sp)))
4521
}

gdnative-async/src/future.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,8 @@ pub(crate) fn make<T>() -> (Yield<T>, Resume<T>) {
2020
(future, resume)
2121
}
2222

23-
/// Signal
23+
/// Future that can be `await`ed for a signal or a `resume` call from Godot. See
24+
/// [`Context`](crate::Context) for methods that return this future.
2425
pub struct Yield<T> {
2526
waker: Arc<AtomicWaker>,
2627
arg_recv: Receiver<T>,

gdnative-async/src/lib.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ mod future;
1414
mod method;
1515
mod rt;
1616

17-
pub use executor::{ensure_executor_with, set_boxed_executor, set_executor, SetExecutorError};
17+
pub use executor::{set_boxed_executor, set_executor};
18+
pub use future::Yield;
1819
pub use method::{Async, AsyncMethod, Spawner};
19-
pub use rt::{register_runtime, terminate_runtime, Context, InitError};
20+
pub use rt::{register_runtime, terminate_runtime, Context};

gdnative-async/src/method.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,8 @@ pub trait AsyncMethod<C: NativeClass>: Send + Sync + 'static {
2424
/// Spawns the future for result of this method with `spawner`. This is done so
2525
/// that implementors of this trait do not have to name their future types.
2626
///
27-
/// If the `spawner` object is not used, the method call will fail, output an error,
28-
/// and return a `Nil` variant.
27+
/// If the `spawner` object is not used, the Godot side of the call will fail, output an
28+
/// error, and return a `Nil` variant.
2929
fn spawn_with(&self, spawner: Spawner<'_, C>);
3030

3131
/// Returns an optional site where this method is defined. Used for logging errors in FFI wrappers.
@@ -37,6 +37,7 @@ pub trait AsyncMethod<C: NativeClass>: Send + Sync + 'static {
3737
}
3838
}
3939

40+
/// A helper structure for working around naming future types. See [`Spawner::spawn`].
4041
pub struct Spawner<'a, C: NativeClass> {
4142
sp: &'static dyn LocalSpawn,
4243
ctx: Context,

gdnative-async/src/rt.rs

Lines changed: 7 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,6 @@ use std::marker::PhantomData;
22

33
use gdnative_bindings::Object;
44
use gdnative_core::object::SubClass;
5-
use once_cell::sync::OnceCell;
6-
use thiserror::Error;
75

86
use gdnative_core::core_types::{GodotError, Variant};
97
use gdnative_core::nativescript::export::InitHandle;
@@ -18,20 +16,6 @@ mod func_state;
1816

1917
use func_state::FuncState;
2018

21-
static REGISTRATION: OnceCell<()> = OnceCell::new();
22-
23-
#[derive(Debug, Error)]
24-
#[error("async runtime must only be initialized once")]
25-
pub struct InitError {
26-
_private: (),
27-
}
28-
29-
impl InitError {
30-
fn new() -> Self {
31-
InitError { _private: () }
32-
}
33-
}
34-
3519
/// Context for creating `yield`-like futures in async methods.
3620
pub struct Context {
3721
func_state: Instance<FuncState, Shared>,
@@ -108,22 +92,15 @@ impl Context {
10892
}
10993
}
11094

111-
pub fn register_runtime(handle: &InitHandle) -> Result<(), InitError> {
112-
let mut called = false;
113-
114-
REGISTRATION.get_or_init(|| {
115-
handle.add_class::<bridge::SignalBridge>();
116-
handle.add_class::<func_state::FuncState>();
117-
called = true;
118-
});
119-
120-
if called {
121-
Ok(())
122-
} else {
123-
Err(InitError::new())
124-
}
95+
/// Adds required supporting NativeScript classes to `handle`. This must be called once and
96+
/// only once per initialization.
97+
pub fn register_runtime(handle: &InitHandle) {
98+
handle.add_class::<bridge::SignalBridge>();
99+
handle.add_class::<func_state::FuncState>();
125100
}
126101

102+
/// Releases all observers still in use. This should be called in the
103+
/// `godot_gdnative_terminate` callback.
127104
pub fn terminate_runtime() {
128105
bridge::terminate();
129106
}

gdnative-async/src/rt/bridge.rs

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -43,8 +43,7 @@ struct Entry {
4343
resume: Resume<Vec<Variant>>,
4444

4545
// Just need to keep this alive.
46-
#[allow(dead_code)]
47-
obj: Instance<SignalBridge, Shared>,
46+
_obj: Instance<SignalBridge, Shared>,
4847
}
4948

5049
pub(super) struct SignalBridge {
@@ -69,11 +68,6 @@ impl SignalBridge {
6968
signal: &str,
7069
resume: Resume<Vec<Variant>>,
7170
) -> Result<(), GodotError> {
72-
assert!(
73-
super::REGISTRATION.get().is_some(),
74-
"async API must be registered before any async methods can be called"
75-
);
76-
7771
let mut pool = BRIDGES.get_or_init(Mutex::default).lock();
7872
let (id, bridge) = pool.free.pop().unwrap_or_else(|| {
7973
let id = pool.next_id();
@@ -90,8 +84,8 @@ impl SignalBridge {
9084
)?;
9185

9286
let entry = Entry {
93-
obj: bridge,
9487
resume,
88+
_obj: bridge,
9589
};
9690

9791
assert!(pool.busy.insert(id, entry).is_none());

gdnative-async/src/rt/func_state.rs

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -53,11 +53,6 @@ impl NativeClass for FuncState {
5353

5454
impl FuncState {
5555
pub fn new() -> Instance<Self, Unique> {
56-
assert!(
57-
super::REGISTRATION.get().is_some(),
58-
"async API must be registered before any async methods can be called"
59-
);
60-
6156
Instance::emplace(FuncState {
6257
kind: Kind::Pending,
6358
})

gdnative/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ readme = "../README.md"
1212
edition = "2018"
1313

1414
[features]
15-
default = ["bindings", "async"]
15+
default = ["bindings"]
1616
formatted = ["gdnative-bindings/formatted", "gdnative-bindings/one_class_one_file"]
1717
serde = ["gdnative-core/serde"]
1818

0 commit comments

Comments
 (0)