Skip to content

Commit 64c4a3b

Browse files
bors[bot]toasteater
andauthored
Merge #681
681: Unwrangling the mess that is `godot_wrap_method`, adding generic method support as a side effect r=halzy a=toasteater Due to repeated past additions to `godot_wrap_method_inner`, the implementation has become a total mess of unsafe macro code that is very hard to follow. Meanwhile, the manual interface for method registration has largely been neglected, lagging behind the rest of the crate, exposing `sys` types and unsafe FFI signatures. Recently, I've found myself in need to register a few methods manually and having to deal with this entire situation, so I've taken the chance to make some improvements to this part of the codebase, with all the hindsight that we now have. This has resulted in satisfactory results: - All the unsafe FFI code previously in `godot_wrap_method_inner` is extracted to the library proper, and can no longer be touched by obscure import/inference problems that come from delayed type checking. - Argument errors are now represented with a proper error type with a `Display` implementation. The library can report multiple argument errors at once. "Missing/excessive argument" errors are also more informative. - It's possible to manually register one method for many classes that it may apply to, or many specializations of a generic method to one class by different names. - Safe, straightforward support for stateful methods. This is not in itself a breaking change, but it's expected that the older unsafe interface will be removed at the next possibility. - - - As a curiosity, the syntax for manually registering generic methods currently looks like this, and I find it already fairly usable even without further macro goodness: ```rust #[derive(Copy, Clone, Default)] struct Mixin<T> { _marker: PhantomData<T>, } #[derive(FromVarargs)] struct Args<T> { a: T, b: T, } impl<T, C> StaticArgsMethod<C> for Mixin<T> where T: Add<Output = T> + Send + Sync + ToVariant + FromVariant + 'static, C: NativeClass, { type Args = Args<T>; fn call(&self, _this: RefInstance<'_, C, Shared>, Args { a, b }: Args<T>) -> Variant { (a + b).to_variant() } } fn register_methods(builder: &ClassBuilder<SomeClass>) { builder .build_method("add_i", StaticArgs::<Mixin::<i32>>::default()) .done_stateless(); builder .build_method("add_f", StaticArgs::<Mixin::<f32>>::default()) .done_stateless(); builder .build_method("add_v", StaticArgs::<Mixin::<Vector2>>::default()) .done_stateless(); } ``` ## Unresolved questions ~~As a side effect of moving the error handling to the library side, error sites all point to the library now. This should be fixable with something on the macro side that provides borrow-able site information, like how `profiling::Signature` is handled? This would mean moving the FFI call out of `godot_error` into some interface as well.~~ Resolved. ## Future development If desired, it should be fairly easy to add a higher-level, derivable `Mixin` interface based on this. It should also be possible to add support for generic methods directly in `#[export]`. Co-authored-by: toasteater <48371905+toasteater@users.noreply.github.com>
2 parents 6dc3a29 + 7db7d62 commit 64c4a3b

File tree

15 files changed

+1256
-282
lines changed

15 files changed

+1256
-282
lines changed

gdnative-core/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ mod init;
4141
#[cfg(feature = "nativescript")]
4242
pub mod nativescript;
4343

44+
pub mod log;
4445
mod new_ref;
4546
pub mod object;
4647
pub mod ref_kind;

gdnative-core/src/log.rs

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
//! Functions for using the engine's logging system in the editor.
2+
use std::ffi::CStr;
3+
use std::fmt::{self, Display};
4+
5+
use crate::core_types::GodotString;
6+
use crate::private;
7+
8+
/// Value representing a call site for errors and warnings. Can be constructed
9+
/// using the `godot_site!` macro, or manually.
10+
#[derive(Copy, Clone, Debug)]
11+
pub struct Site<'a> {
12+
file: &'a CStr,
13+
func: &'a CStr,
14+
line: u32,
15+
}
16+
17+
impl<'a> Site<'a> {
18+
/// Construct a new `Site` value using values provided manually.
19+
#[inline]
20+
pub const fn new(file: &'a CStr, func: &'a CStr, line: u32) -> Self {
21+
Site { file, func, line }
22+
}
23+
}
24+
25+
impl<'a> Default for Site<'a> {
26+
#[inline]
27+
fn default() -> Self {
28+
let unset = unsafe { CStr::from_bytes_with_nul_unchecked(b"<unset>\0") };
29+
Site::new(unset, unset, 0)
30+
}
31+
}
32+
33+
impl<'a> Display for Site<'a> {
34+
#[inline]
35+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
36+
write!(
37+
f,
38+
"file {}, {}, line {}",
39+
self.file.to_string_lossy(),
40+
self.func.to_string_lossy(),
41+
self.line
42+
)
43+
}
44+
}
45+
46+
/// Print a message to the Godot console.
47+
///
48+
/// # Panics
49+
///
50+
/// If the API isn't initialized.
51+
#[inline]
52+
pub fn print<S: Display>(msg: S) {
53+
unsafe {
54+
let msg = GodotString::from_str(&msg.to_string());
55+
(private::get_api().godot_print)(&msg.to_sys() as *const _);
56+
}
57+
}
58+
59+
/// Print a warning to the Godot console.
60+
///
61+
/// # Panics
62+
///
63+
/// If the API isn't initialized, or if the message contains any NUL-bytes.
64+
#[inline]
65+
pub fn warn<S: Display>(site: Site<'_>, msg: S) {
66+
let msg = msg.to_string();
67+
let msg = ::std::ffi::CString::new(msg).unwrap();
68+
69+
unsafe {
70+
(private::get_api().godot_print_warning)(
71+
msg.as_ptr(),
72+
site.func.as_ptr(),
73+
site.file.as_ptr(),
74+
site.line as libc::c_int,
75+
);
76+
}
77+
}
78+
79+
/// Print an error to the Godot console.
80+
///
81+
/// # Panics
82+
///
83+
/// If the API isn't initialized, or if the message contains any NUL-bytes.
84+
#[inline]
85+
pub fn error<S: Display>(site: Site<'_>, msg: S) {
86+
let msg = msg.to_string();
87+
let msg = ::std::ffi::CString::new(msg).unwrap();
88+
89+
unsafe {
90+
(private::get_api().godot_print_error)(
91+
msg.as_ptr(),
92+
site.func.as_ptr(),
93+
site.file.as_ptr(),
94+
site.line as libc::c_int,
95+
);
96+
}
97+
}

gdnative-core/src/macros.rs

Lines changed: 50 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -102,13 +102,7 @@ macro_rules! godot_gdnative_terminate {
102102
#[macro_export]
103103
macro_rules! godot_print {
104104
($($args:tt)*) => ({
105-
let msg = format!($($args)*);
106-
107-
#[allow(unused_unsafe)]
108-
unsafe {
109-
let msg = $crate::core_types::GodotString::from_str(msg);
110-
($crate::private::get_api().godot_print)(&msg.to_sys() as *const _);
111-
}
105+
$crate::log::print(::std::format_args!($($args)*));
112106
});
113107
}
114108

@@ -139,6 +133,51 @@ macro_rules! godot_dbg {
139133
};
140134
}
141135

136+
/// Creates a `gdnative::log::Site` value from the current position in code,
137+
/// optionally with a function path for identification.
138+
///
139+
/// # Examples
140+
///
141+
/// ```ignore
142+
/// // WARN: <unset>: foo At: path/to/file.rs:123
143+
/// gdnative::log::warn(godot_site!(), "foo");
144+
/// // WARN: Foo::my_func: bar At: path/to/file.rs:123
145+
/// gdnative::log::error(godot_site!(Foo::my_func), "bar");
146+
/// ```
147+
#[macro_export]
148+
macro_rules! godot_site {
149+
() => {{
150+
// SAFETY: I guess we can assume that all sane file systems don't allow
151+
// NUL-bytes in paths?
152+
#[allow(unused_unsafe)]
153+
let site: $crate::log::Site<'static> = unsafe {
154+
let file = ::std::ffi::CStr::from_bytes_with_nul_unchecked(
155+
::std::concat!(::std::file!(), "\0").as_bytes(),
156+
);
157+
let func = ::std::ffi::CStr::from_bytes_with_nul_unchecked(b"<unset>\0");
158+
$crate::log::Site::new(file, func, ::std::line!())
159+
};
160+
161+
site
162+
}};
163+
($($path:tt)+) => {{
164+
// SAFETY: I guess we can assume that all sane file systems don't allow
165+
// NUL-bytes in paths?
166+
#[allow(unused_unsafe)]
167+
let site: $crate::log::Site<'static> = unsafe {
168+
let file = ::std::ffi::CStr::from_bytes_with_nul_unchecked(
169+
::std::concat!(::std::file!(), "\0").as_bytes(),
170+
);
171+
let func = ::std::ffi::CStr::from_bytes_with_nul_unchecked(
172+
::std::concat!(::std::stringify!($($path)+), "\0").as_bytes(),
173+
);
174+
$crate::log::Site::new(file, func, ::std::line!())
175+
};
176+
177+
site
178+
}};
179+
}
180+
142181
/// Print a warning using the engine's logging system (visible in the editor).
143182
///
144183
/// # Guarantees
@@ -150,22 +189,8 @@ macro_rules! godot_dbg {
150189
#[macro_export]
151190
macro_rules! godot_warn {
152191
($($args:tt)*) => ({
153-
let msg = format!($($args)*);
154-
let line = line!();
155-
let file = file!();
156-
#[allow(unused_unsafe)]
157-
unsafe {
158-
let msg = ::std::ffi::CString::new(msg).unwrap();
159-
let file = ::std::ffi::CString::new(file).unwrap();
160-
let func = b"<native>\0";
161-
($crate::private::get_api().godot_print_warning)(
162-
msg.as_ptr() as *const _,
163-
func.as_ptr() as *const _,
164-
file.as_ptr() as *const _,
165-
line as _,
166-
);
167-
}
168-
})
192+
$crate::log::warn($crate::godot_site!(), ::std::format_args!($($args)*));
193+
});
169194
}
170195

171196
/// Print an error using the engine's logging system (visible in the editor).
@@ -179,22 +204,8 @@ macro_rules! godot_warn {
179204
#[macro_export]
180205
macro_rules! godot_error {
181206
($($args:tt)*) => ({
182-
let msg = format!($($args)*);
183-
let line = line!();
184-
let file = file!();
185-
#[allow(unused_unsafe)]
186-
unsafe {
187-
let msg = ::std::ffi::CString::new(msg).unwrap();
188-
let file = ::std::ffi::CString::new(file).unwrap();
189-
let func = b"<native>\0";
190-
($crate::private::get_api().godot_print_error)(
191-
msg.as_ptr() as *const _,
192-
func.as_ptr() as *const _,
193-
file.as_ptr() as *const _,
194-
line as _,
195-
);
196-
}
197-
})
207+
$crate::log::error($crate::godot_site!(), ::std::format_args!($($args)*));
208+
});
198209
}
199210

200211
macro_rules! impl_basic_trait_as_sys {

gdnative-core/src/nativescript/init.rs

Lines changed: 34 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,9 @@
3131
//! For full examples, see [`examples`](https://github.com/godot-rust/godot-rust/tree/master/examples)
3232
//! in the godot-rust repository.
3333
34+
// Temporary for unsafe method registration
35+
#![allow(deprecated)]
36+
3437
use crate::*;
3538

3639
use std::ffi::CString;
@@ -45,8 +48,12 @@ use crate::private::get_api;
4548

4649
use super::emplace;
4750

51+
pub mod method;
4852
pub mod property;
4953

54+
pub use self::method::{
55+
Method, MethodBuilder, RpcMode, ScriptMethod, ScriptMethodAttributes, ScriptMethodFn, Varargs,
56+
};
5057
pub use self::property::{Export, ExportInfo, PropertyBuilder, Usage as PropertyUsage};
5158

5259
/// A handle that can register new classes to the engine during initialization.
@@ -212,37 +219,6 @@ impl InitHandle {
212219
}
213220
}
214221

215-
pub type ScriptMethodFn = unsafe extern "C" fn(
216-
*mut sys::godot_object,
217-
*mut libc::c_void,
218-
*mut libc::c_void,
219-
libc::c_int,
220-
*mut *mut sys::godot_variant,
221-
) -> sys::godot_variant;
222-
223-
pub enum RpcMode {
224-
Disabled,
225-
Remote,
226-
RemoteSync,
227-
Master,
228-
Puppet,
229-
MasterSync,
230-
PuppetSync,
231-
}
232-
233-
pub struct ScriptMethodAttributes {
234-
pub rpc_mode: RpcMode,
235-
}
236-
237-
pub struct ScriptMethod<'l> {
238-
pub name: &'l str,
239-
pub method_ptr: Option<ScriptMethodFn>,
240-
pub attributes: ScriptMethodAttributes,
241-
242-
pub method_data: *mut libc::c_void,
243-
pub free_func: Option<unsafe extern "C" fn(*mut libc::c_void) -> ()>,
244-
}
245-
246222
#[derive(Debug)]
247223
pub struct ClassBuilder<C> {
248224
init_handle: *mut libc::c_void,
@@ -252,6 +228,7 @@ pub struct ClassBuilder<C> {
252228

253229
impl<C: NativeClass> ClassBuilder<C> {
254230
#[inline]
231+
#[deprecated(note = "Unsafe registration is deprecated. Use `build_method` instead.")]
255232
pub fn add_method_advanced(&self, method: ScriptMethod) {
256233
let method_name = CString::new(method.name).unwrap();
257234

@@ -285,6 +262,7 @@ impl<C: NativeClass> ClassBuilder<C> {
285262
}
286263

287264
#[inline]
265+
#[deprecated(note = "Unsafe registration is deprecated. Use `build_method` instead.")]
288266
pub fn add_method_with_rpc_mode(&self, name: &str, method: ScriptMethodFn, rpc_mode: RpcMode) {
289267
self.add_method_advanced(ScriptMethod {
290268
name,
@@ -296,10 +274,35 @@ impl<C: NativeClass> ClassBuilder<C> {
296274
}
297275

298276
#[inline]
277+
#[deprecated(note = "Unsafe registration is deprecated. Use `build_method` instead.")]
299278
pub fn add_method(&self, name: &str, method: ScriptMethodFn) {
300279
self.add_method_with_rpc_mode(name, method, RpcMode::Disabled);
301280
}
302281

282+
/// Returns a `MethodBuilder` which can be used to add a method to the class being
283+
/// registered.
284+
///
285+
/// # Examples
286+
///
287+
/// Basic usage:
288+
///
289+
/// ```ignore
290+
/// // `Bar` is a stateful method implementing `Method<C>`
291+
///
292+
/// builder
293+
/// .build_method("foo", Bar { baz: 42 })
294+
/// .with_rpc_mode(RpcMode::RemoteSync)
295+
/// .done();
296+
/// ```
297+
#[inline]
298+
pub fn build_method<'a, F: Method<C>>(
299+
&'a self,
300+
name: &'a str,
301+
method: F,
302+
) -> MethodBuilder<'a, C, F> {
303+
MethodBuilder::new(self, name, method)
304+
}
305+
303306
/// Returns a `PropertyBuilder` which can be used to add a property to the class being
304307
/// registered.
305308
///

0 commit comments

Comments
 (0)