Skip to content

Commit

Permalink
Merge #999
Browse files Browse the repository at this point in the history
999: Implement automatic `NativeClass` registration via inventory. Implement mix-ins. r=chitoyuu a=chitoyuu

## Implement automatic NativeClass registration via inventory

This adds the optional `inventory` feature, which allows `NativeClass` types to be automatically registered on supported platforms (everything that OSS Godot currently supports except WASM).

Run-time diagnostic functions are added to help debug missing registration problems that are highly likely to arise when porting `inventory`-enabled projects to WASM.

An internal `cfg_ex` attribute macro is added to help manage cfg conditions.

Close #350. Note that the standalone registration attribute syntax proposed by the original issue isn't implemented, for the limited usefulness -- there are much fewer cases where manual `NativeClass` impls are necessary thanks to all the improvements since the original issue.

## Implement mix-in `#[methods]` blocks

Adds the `#[methods(mixin = "Name")]` syntax for declaring mix-in blocks. Mix-in blocks have a many-to-many relationship with `NativeClass` types. Both `impl Type` and `impl Trait for Type` blocks are accepted.

The argument name is changed from `as` in the original proposal to `mixin`, because we might still want to keep universal `#[methods]` blocks in the future for ease of use with WASM. `#[methods(mixin)]` makes a lot more sense for a future auto-registered mixin block than `#[methods(as /* as what? */)]`.

All mix-in blocks have to be manually registered for gdnative v0.11.x. Some difficulty was encountered when trying to make auto-mixins compatible with existing code. It might still be possible with some tricks like autoref-specialization, but that might be too much effort given that we likely want to re-design much of the hierarchy for 0.12.

Close #984.

## Allow `#[register_with]` for `#[monomorphize]`

Enables `#[monomorphize]` to take the same standalone `#[register_with]` attribute as `#[derive(NativeClass)]`. This is chosen for short term consistency, but will probably change in a later version w/ #848, which might not still get implemented for a fair bit of time.


Co-authored-by: Chitose Yuuzaki <[email protected]>
  • Loading branch information
bors[bot] and chitoyuu authored Jan 4, 2023
2 parents f920000 + adad1a6 commit 16e5847
Show file tree
Hide file tree
Showing 53 changed files with 1,154 additions and 119 deletions.
18 changes: 18 additions & 0 deletions .github/workflows/full-ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -277,20 +277,38 @@ jobs:
godot: "3.5.1-stable"
postfix: ' (ptrcall)'
build_args: '--features ptrcall'
- rust: stable
godot: "3.5.1-stable"
postfix: ' (inventory)'
build_args: '--features inventory'
- rust: stable
godot: "3.5.1-stable"
postfix: ' (inventory)'
# Limiting no-manual-register tests to stable as to not slow down CI too far -- if inventory is
# working across all 3 Rust versions, this is likely to be as well.
build_args: '--features inventory,no-manual-register'
- rust: nightly
godot: "3.5.1-stable"
postfix: ' (nightly)'
- rust: nightly
godot: "3.5.1-stable"
postfix: ' (nightly, ptrcall)'
build_args: '--features ptrcall'
- rust: nightly
godot: "3.5.1-stable"
postfix: ' (nightly, inventory)'
build_args: '--features inventory'
- rust: '1.63'
godot: "3.5.1-stable"
postfix: ' (msrv 1.63)'
- rust: '1.63'
godot: "3.5.1-stable"
postfix: ' (msrv 1.63, ptrcall)'
build_args: '--features ptrcall'
- rust: '1.63'
godot: "3.5.1-stable"
postfix: ' (msrv 1.63, inventory)'
build_args: '--features inventory'

# Test with oldest supported engine version
- rust: stable
Expand Down
6 changes: 5 additions & 1 deletion check.sh
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,8 @@ function findGodot() {
fi
}

features="gdnative/async,gdnative/serde"
features="gdnative/async,gdnative/serde,gdnative/inventory"
itest_toggled_features="no-manual-register"
cmds=()

for arg in "${args[@]}"; do
Expand All @@ -81,6 +82,9 @@ for arg in "${args[@]}"; do
cmds+=("cargo build --manifest-path test/Cargo.toml --features $features")
cmds+=("cp target/debug/*gdnative_test* test/project/lib/")
cmds+=("$godotBin --path test/project")
cmds+=("cargo build --manifest-path test/Cargo.toml --features $features,$itest_toggled_features")
cmds+=("cp target/debug/*gdnative_test* test/project/lib/")
cmds+=("$godotBin --path test/project")
;;
doc)
cmds+=("cargo doc --lib -p gdnative --no-deps --features $features")
Expand Down
1 change: 1 addition & 0 deletions gdnative-core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ libc = "0.2"
once_cell = "1"
parking_lot = "0.12"
serde = { version = "1", features = ["derive"], optional = true }
inventory = { version = "0.3", optional = true }

[dev-dependencies]
gdnative = { path = "../gdnative" } # for doc-tests
2 changes: 1 addition & 1 deletion gdnative-core/src/core_types/byte_array.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ godot_test!(
let original_read = {
let read = arr.read();
assert_eq!(&[0, 1, 2, 3, 4, 5, 6, 7], read.as_slice());
read.clone()
read
};

let mut cow_arr = arr.new_ref();
Expand Down
2 changes: 1 addition & 1 deletion gdnative-core/src/core_types/color_array.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ godot_test!(
Color::from_rgb(0.0, 1.0, 0.0),
Color::from_rgb(0.0, 0.0, 1.0),
], read.as_slice());
read.clone()
read
};

let mut cow_arr = arr.new_ref();
Expand Down
2 changes: 2 additions & 0 deletions gdnative-core/src/core_types/error.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use crate::sys;

/// Error codes used in various Godot APIs.
#[allow(clippy::unnecessary_cast)] // False positives: casts necessary for cross-platform
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[repr(u32)]
Expand Down Expand Up @@ -65,6 +66,7 @@ impl GodotError {
/// `err` should be a valid value for `GodotError`.
#[inline]
#[doc(hidden)]
#[allow(clippy::unnecessary_cast)] // False positives: casts necessary for cross-platform
pub unsafe fn result_from_sys(err: sys::godot_error) -> Result<(), Self> {
if err == sys::godot_error_GODOT_OK {
return Ok(());
Expand Down
4 changes: 2 additions & 2 deletions gdnative-core/src/core_types/float32_array.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ godot_test!(
for (n, i) in read.as_slice().iter().enumerate() {
assert_relative_eq!(n as f32, i);
}
read.clone()
read
};

let mut cow_arr = arr.new_ref();
Expand All @@ -30,7 +30,7 @@ godot_test!(
}

for i in 0..8 {
assert_relative_eq!(i as f32 * 2., cow_arr.get(i as i32));
assert_relative_eq!(i as f32 * 2., cow_arr.get(i));
}

// the write shouldn't have affected the original array
Expand Down
4 changes: 2 additions & 2 deletions gdnative-core/src/core_types/int32_array.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ godot_test!(
let original_read = {
let read = arr.read();
assert_eq!(&[0, 1, 2, 3, 4, 5, 6, 7], read.as_slice());
read.clone()
read
};

let mut cow_arr = arr.new_ref();
Expand All @@ -28,7 +28,7 @@ godot_test!(
}

for i in 0..8 {
assert_eq!(i * 2, cow_arr.get(i as i32));
assert_eq!(i * 2, cow_arr.get(i));
}

// the write shouldn't have affected the original array
Expand Down
2 changes: 1 addition & 1 deletion gdnative-core/src/core_types/string_array.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ godot_test!(
GodotString::from("bar"),
GodotString::from("baz"),
], read.as_slice());
read.clone()
read
};

let mut cow_arr = arr.new_ref();
Expand Down
5 changes: 5 additions & 0 deletions gdnative-core/src/core_types/variant.rs
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,7 @@ decl_variant_type!(
);

impl VariantType {
#[allow(clippy::unnecessary_cast)] // False positives: casts necessary for cross-platform
#[doc(hidden)]
#[inline]
pub fn from_sys(v: sys::godot_variant_type) -> VariantType {
Expand All @@ -182,6 +183,7 @@ impl VariantType {
}
}

#[allow(clippy::unnecessary_cast)] // False positives: casts necessary for cross-platform
#[repr(u32)]
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
pub enum CallError {
Expand All @@ -198,6 +200,7 @@ pub enum CallError {
}

impl CallError {
#[allow(clippy::unnecessary_cast)] // False positives: casts necessary for cross-platform
#[inline]
fn from_sys(v: sys::godot_variant_call_error_error) -> Result<(), CallError> {
if v == sys::godot_variant_call_error_error_GODOT_CALL_ERROR_CALL_OK {
Expand Down Expand Up @@ -230,6 +233,7 @@ impl std::fmt::Display for CallError {
impl std::error::Error for CallError {}

/// Godot variant operator kind.
#[allow(clippy::unnecessary_cast)] // False positives: casts necessary for cross-platform
#[repr(u32)]
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
pub enum VariantOperator {
Expand Down Expand Up @@ -269,6 +273,7 @@ pub enum VariantOperator {
In = sys::godot_variant_operator_GODOT_VARIANT_OP_IN as u32,
}

#[allow(clippy::unnecessary_cast)] // False positives: casts necessary for cross-platform
impl VariantOperator {
const MAX: u32 = sys::godot_variant_operator_GODOT_VARIANT_OP_MAX as u32;

Expand Down
2 changes: 1 addition & 1 deletion gdnative-core/src/core_types/vector2_array.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ godot_test!(
Vector2::new(3.0, 4.0),
Vector2::new(5.0, 6.0),
], read.as_slice());
read.clone()
read
};

let mut cow_arr = arr.new_ref();
Expand Down
1 change: 1 addition & 0 deletions gdnative-core/src/core_types/vector3.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ pub struct Vector3 {
pub z: f32,
}

#[allow(clippy::unnecessary_cast)] // False positives: casts necessary for cross-platform
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
#[repr(u32)]
pub enum Axis {
Expand Down
2 changes: 1 addition & 1 deletion gdnative-core/src/core_types/vector3_array.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ godot_test!(
Vector3::new(3.0, 4.0, 5.0),
Vector3::new(5.0, 6.0, 7.0),
], read.as_slice());
read.clone()
read
};

let mut cow_arr = arr.new_ref();
Expand Down
7 changes: 7 additions & 0 deletions gdnative-core/src/export/class.rs
Original file line number Diff line number Diff line change
Expand Up @@ -99,13 +99,20 @@ pub trait NativeClass: Sized + 'static {

/// A NativeScript "class" that is statically named. [`NativeClass`] types that implement this
/// trait can be registered using [`InitHandle::add_class`].
///
/// This trait will be renamed to [`Monomorphized`] in a future version since its purpose has
/// grown beyond simply providing a static type name.
pub trait StaticallyNamed: NativeClass {
/// The name of the class.
///
/// This name must be unique for the dynamic library. For generic or library code where this
/// is hard to satisfy, consider using [`InitHandle::add_class_as`] to provide a name
/// at registration time instead.
const CLASS_NAME: &'static str;

/// Function that registers methods specific to this monomorphization.
#[inline]
fn nativeclass_register_monomorphized(_builder: &ClassBuilder<Self>) {}
}

/// Trait used to provide information of Godot-exposed methods of a script class.
Expand Down
49 changes: 49 additions & 0 deletions gdnative-core/src/export/class_builder.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
use std::any::TypeId;
use std::cell::RefCell;
use std::collections::HashSet;
use std::ffi::CString;
use std::marker::PhantomData;
use std::ptr;
Expand All @@ -20,6 +23,7 @@ use crate::private::get_api;
pub struct ClassBuilder<C> {
pub(super) init_handle: *mut libc::c_void,
pub(super) class_name: CString,
mixins: RefCell<HashSet<TypeId, ahash::RandomState>>,
_marker: PhantomData<C>,
}

Expand All @@ -28,6 +32,7 @@ impl<C: NativeClass> ClassBuilder<C> {
Self {
init_handle,
class_name,
mixins: RefCell::default(),
_marker: PhantomData,
}
}
Expand Down Expand Up @@ -241,4 +246,48 @@ impl<C: NativeClass> ClassBuilder<C> {
);
}
}

/// Add a mixin to the class being registered.
///
/// # Examples
///
/// ```
/// use gdnative::prelude::*;
///
/// #[derive(NativeClass)]
/// #[inherit(Node)]
/// #[register_with(my_register)]
/// #[no_constructor]
/// struct MyType {}
///
/// // This creates a opaque type `MyMixin` in the current scope that implements
/// // the `Mixin` trait. Mixin types have no public interface or stable layout.
/// #[methods(mixin = "MyMixin")]
/// impl MyType {
/// #[method]
/// fn my_method(&self) -> i64 { 42 }
/// }
///
/// fn my_register(builder: &ClassBuilder<MyType>) {
/// builder.mixin::<MyMixin>();
/// }
/// ```
#[inline]
pub fn mixin<M: Mixin<C>>(&self) {
if self.mixins.borrow_mut().insert(TypeId::of::<M>()) {
M::register(self);
}
}
}

/// Trait for mixins, manually registered `#[methods]` blocks that may be applied to multiple types.
///
/// This trait is implemented on generated types by the `#[methods]` proc-macro only, and has no public interface.
/// Use [`ClassBuilder::mixin`] to register mixins to [`NativeClass`] types.
pub trait Mixin<C>: crate::private::mixin::Sealed + 'static
where
C: NativeClass,
{
#[doc(hidden)]
fn register(builder: &ClassBuilder<C>);
}
Loading

0 comments on commit 16e5847

Please sign in to comment.