Skip to content

Commit 0c89812

Browse files
committed
Merge branch 'master' into qol/cached-function-pointers
# Conflicts: # godot-macros/src/method_registration/mod.rs # godot-macros/src/method_registration/register_method.rs # godot-macros/src/method_registration/virtual_method_callback.rs
2 parents 259c211 + 2b6fce2 commit 0c89812

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

78 files changed

+1005
-923
lines changed

.github/workflows/full-ci.yml

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -79,9 +79,15 @@ jobs:
7979
# Note: could use `-- --no-deps` to not lint dependencies, however it doesn't really speed up and also skips deps in workspace.
8080
- name: "Check clippy"
8181
run: |
82-
cargo clippy --all-targets $GDEXT_FEATURES ${{ matrix.rust-extra-args }} -- \
83-
-D clippy::suspicious -D clippy::style -D clippy::complexity -D clippy::perf \
84-
-D clippy::dbg_macro -D clippy::todo -D clippy::unimplemented -D warnings
82+
cargo clippy --all-targets $GDEXT_FEATURES -- \
83+
-D clippy::suspicious \
84+
-D clippy::style \
85+
-D clippy::complexity \
86+
-D clippy::perf \
87+
-D clippy::dbg_macro \
88+
-D clippy::todo \
89+
-D clippy::unimplemented \
90+
-D warnings
8591
8692
unit-test:
8793
name: unit-test (${{ matrix.name }}${{ matrix.rust-special }})

.github/workflows/minimal-ci.yml

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -83,9 +83,15 @@ jobs:
8383

8484
- name: "Check clippy"
8585
run: |
86-
cargo clippy --all-targets $GDEXT_FEATURES ${{ matrix.rust-extra-args }} -- \
87-
-D clippy::suspicious -D clippy::style -D clippy::complexity -D clippy::perf \
88-
-D clippy::dbg_macro -D clippy::todo -D clippy::unimplemented -D warnings
86+
cargo clippy --all-targets $GDEXT_FEATURES -- \
87+
-D clippy::suspicious \
88+
-D clippy::style \
89+
-D clippy::complexity \
90+
-D clippy::perf \
91+
-D clippy::dbg_macro \
92+
-D clippy::todo \
93+
-D clippy::unimplemented \
94+
-D warnings
8995
9096
9197
unit-test:

check.sh

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -158,8 +158,8 @@ function cmd_dok() {
158158
# Argument parsing
159159
################################################################################
160160

161-
# By default, disable `codegen-full` to reduce compile times and prevent flip-flopping
162-
# between `itest` compilations and `check.sh` runs.
161+
# By default, disable `codegen-full` to reduce compile times and prevent flip-flopping between
162+
# `itest` compilations and `check.sh` runs. Note that this means some runs are different from CI.
163163
extraCargoArgs=("--no-default-features")
164164
cmds=()
165165
nextArgIsFilter=false

godot-codegen/src/central_generator.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -145,7 +145,7 @@ pub(crate) fn generate_sys_utilities_file(
145145
}
146146

147147
let fn_name_str = &function.name;
148-
let field = util::make_utility_function_ptr_name(&function);
148+
let field = util::make_utility_function_ptr_name(function);
149149
let hash = function.hash;
150150

151151
table.method_decls.push(quote! {
@@ -664,7 +664,7 @@ fn make_builtin_method_table(api: &ExtensionApi, builtin_types: &BuiltinTypeMap)
664664
// TODO reuse builtin_types without api
665665
for builtin in api.builtin_classes.iter() {
666666
let Some(builtin_type) = builtin_types.map.get(&builtin.name) else {
667-
continue // for Nil
667+
continue; // for Nil
668668
};
669669

670670
populate_builtin_methods(&mut table, builtin, &builtin_type.type_names);

godot-codegen/src/class_generator.rs

Lines changed: 25 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -523,6 +523,28 @@ fn make_class(class: &Class, class_name: &TyName, ctx: &mut Context) -> Generate
523523
(TokenStream::new(), TokenStream::new())
524524
};
525525

526+
// The base_ty of `Object` is `()`, and we dont want every engine class to deref to `()`.
527+
let deref_impl = if class_name.rust_ty != "Object" {
528+
quote! {
529+
impl std::ops::Deref for #class_name {
530+
type Target = #base_ty;
531+
532+
fn deref(&self) -> &Self::Target {
533+
// SAFETY: same assumptions as `impl Deref for Gd<T>`, see there for comments
534+
unsafe { std::mem::transmute::<&Self, &Self::Target>(self) }
535+
}
536+
}
537+
impl std::ops::DerefMut for #class_name {
538+
fn deref_mut(&mut self) -> &mut Self::Target {
539+
// SAFETY: see above
540+
unsafe { std::mem::transmute::<&mut Self, &mut Self::Target>(self) }
541+
}
542+
}
543+
}
544+
} else {
545+
TokenStream::new()
546+
};
547+
526548
let all_bases = ctx.inheritance_tree().collect_all_bases(class_name);
527549
let (notification_enum, notification_enum_name) =
528550
make_notification_enum(class_name, &all_bases, ctx);
@@ -612,20 +634,7 @@ fn make_class(class: &Class, class_name: &TyName, ctx: &mut Context) -> Generate
612634

613635
#exportable_impl
614636

615-
impl std::ops::Deref for #class_name {
616-
type Target = #base_ty;
617-
618-
fn deref(&self) -> &Self::Target {
619-
// SAFETY: same assumptions as `impl Deref for Gd<T>`, see there for comments
620-
unsafe { std::mem::transmute::<&Self, &Self::Target>(self) }
621-
}
622-
}
623-
impl std::ops::DerefMut for #class_name {
624-
fn deref_mut(&mut self) -> &mut Self::Target {
625-
// SAFETY: see above
626-
unsafe { std::mem::transmute::<&mut Self, &mut Self::Target>(self) }
627-
}
628-
}
637+
#deref_impl
629638

630639
#[macro_export]
631640
#[allow(non_snake_case)]
@@ -707,7 +716,7 @@ fn make_notification_enum(
707716
all_bases: &Vec<TyName>,
708717
ctx: &mut Context,
709718
) -> (Option<TokenStream>, Ident) {
710-
let Some(all_constants) = ctx.notification_constants(class_name) else {
719+
let Some(all_constants) = ctx.notification_constants(class_name) else {
711720
// Class has no notification constants: reuse (direct/indirect) base enum
712721
return (None, ctx.notification_enum_name(class_name).name);
713722
};
@@ -1210,7 +1219,7 @@ pub(crate) fn make_utility_function_definition(
12101219
}
12111220

12121221
let function_name_str = &function.name;
1213-
let fn_ptr = util::make_utility_function_ptr_name(&function);
1222+
let fn_ptr = util::make_utility_function_ptr_name(function);
12141223

12151224
let return_value = function
12161225
.return_type

godot-codegen/src/codegen_special_cases.rs

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,22 +28,24 @@ pub(crate) fn is_class_excluded(_class: &str) -> bool {
2828

2929
#[cfg(not(feature = "codegen-full"))]
3030
fn is_type_excluded(ty: &str, ctx: &mut Context) -> bool {
31+
use crate::{util, RustTy};
32+
3133
fn is_rust_type_excluded(ty: &RustTy) -> bool {
3234
match ty {
3335
RustTy::BuiltinIdent(_) => false,
3436
RustTy::BuiltinArray(_) => false,
35-
RustTy::RawPointer { inner, .. } => is_rust_type_excluded(&inner),
37+
RustTy::RawPointer { inner, .. } => is_rust_type_excluded(inner),
3638
RustTy::EngineArray { elem_class, .. } => is_class_excluded(elem_class.as_str()),
3739
RustTy::EngineEnum {
3840
surrounding_class, ..
3941
} => match surrounding_class.as_ref() {
4042
None => false,
4143
Some(class) => is_class_excluded(class.as_str()),
4244
},
43-
RustTy::EngineClass { class, .. } => is_class_excluded(&class),
45+
RustTy::EngineClass { class, .. } => is_class_excluded(class),
4446
}
4547
}
46-
is_rust_type_excluded(&to_rust_type(ty, None, ctx))
48+
is_rust_type_excluded(&util::to_rust_type(ty, None, ctx))
4749
}
4850

4951
pub(crate) fn is_method_excluded(

godot-codegen/src/special_cases.rs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,10 +38,12 @@ pub(crate) fn is_deleted(class_name: &TyName, godot_method_name: &str) -> bool {
3838

3939
#[rustfmt::skip]
4040
pub(crate) fn is_class_deleted(class_name: &TyName) -> bool {
41-
// Exclude experimental classes for now; possibly feature-gate them in the future.
42-
if is_class_experimental(class_name) {
41+
// TODO feature-gate experimental classes.
42+
/*
43+
if !cfg!(feature = "experimental-godot-api") && is_class_experimental(class_name) {
4344
return true;
4445
}
46+
*/
4547

4648
match class_name.godot_ty.as_str() {
4749
// Hardcoded cases that are not accessible.
@@ -73,6 +75,7 @@ pub(crate) fn is_class_deleted(class_name: &TyName) -> bool {
7375
}
7476

7577
#[rustfmt::skip]
78+
#[allow(dead_code)] // remove once used.
7679
fn is_class_experimental(class_name: &TyName) -> bool {
7780
// These classes are currently hardcoded, but the information is available in Godot's doc/classes directory.
7881
// The XML file contains a property <class name="NavigationMesh" ... is_experimental="true">.

godot-core/src/builtin/variant/variant_traits.rs

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,4 +60,28 @@ pub enum VariantConversionError {
6060

6161
/// Variant value is missing a value for the target type
6262
MissingValue,
63+
64+
/// Variant value is null but expected to be non-null
65+
VariantIsNil,
6366
}
67+
68+
impl std::fmt::Display for VariantConversionError {
69+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
70+
match self {
71+
VariantConversionError::BadType => {
72+
f.write_str("Variant type does not match expected type")
73+
}
74+
VariantConversionError::BadValue => {
75+
f.write_str("Variant value cannot be represented in target type")
76+
}
77+
VariantConversionError::MissingValue => {
78+
f.write_str("Variant value is missing a value for the target type")
79+
}
80+
VariantConversionError::VariantIsNil => {
81+
f.write_str("Variant value is null but expected to be non-null")
82+
}
83+
}
84+
}
85+
}
86+
87+
impl std::error::Error for VariantConversionError {}

godot-core/src/obj/gd.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -684,7 +684,8 @@ impl<T: GodotClass> Drop for Gd<T> {
684684
// No-op for manually managed objects
685685

686686
out!("Gd::drop <{}>", std::any::type_name::<T>());
687-
let is_last = T::Mem::maybe_dec_ref(self); // may drop
687+
// SAFETY: This `Gd` wont be dropped again after this.
688+
let is_last = unsafe { T::Mem::maybe_dec_ref(self) }; // may drop
688689
if is_last {
689690
unsafe {
690691
interface_fn!(object_destroy)(self.obj_sys());
@@ -788,7 +789,7 @@ impl<T: GodotClass> FromVariant for Gd<T> {
788789
// TODO(#234) remove this cast when Godot stops allowing illegal conversions
789790
// (See https://github.com/godot-rust/gdext/issues/158)
790791
.and_then(|obj| obj.owned_cast().ok())
791-
.ok_or(VariantConversionError::BadType)
792+
.ok_or(VariantConversionError::VariantIsNil)
792793
}
793794
}
794795

godot-core/src/obj/traits.rs

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -320,9 +320,21 @@ pub mod mem {
320320
#[doc(hidden)]
321321
fn maybe_inc_ref<T: GodotClass>(obj: &Gd<T>);
322322

323-
/// If ref-counted, then decrement count
323+
/// If ref-counted, then decrement count. Returns `true` if the count hit 0 and the object can be
324+
/// safely freed.
325+
///
326+
/// This behavior can be overriden by a script, making it possible for the function to return `false`
327+
/// even when the reference count hits 0. This is meant to be used to have a separate reference count
328+
/// from Godot's internal reference count, or otherwise stop the object from being freed when the
329+
/// reference count hits 0.
330+
///
331+
/// # Safety
332+
///
333+
/// If this method is used on a [`Gd`] that inherits from [`RefCounted`](crate::engine::RefCounted)
334+
/// then the reference count must either be incremented before it hits 0, or some [`Gd`] referencing
335+
/// this object must be forgotten.
324336
#[doc(hidden)]
325-
fn maybe_dec_ref<T: GodotClass>(obj: &Gd<T>) -> bool;
337+
unsafe fn maybe_dec_ref<T: GodotClass>(obj: &Gd<T>) -> bool;
326338

327339
/// Check if ref-counted, return `None` if information is not available (dynamic and obj dead)
328340
#[doc(hidden)]
@@ -362,7 +374,7 @@ pub mod mem {
362374
});
363375
}
364376

365-
fn maybe_dec_ref<T: GodotClass>(obj: &Gd<T>) -> bool {
377+
unsafe fn maybe_dec_ref<T: GodotClass>(obj: &Gd<T>) -> bool {
366378
out!(" Stat::dec <{}>", std::any::type_name::<T>());
367379
obj.as_ref_counted(|refc| {
368380
let is_last = refc.unreference();
@@ -407,7 +419,7 @@ pub mod mem {
407419
}
408420
}
409421

410-
fn maybe_dec_ref<T: GodotClass>(obj: &Gd<T>) -> bool {
422+
unsafe fn maybe_dec_ref<T: GodotClass>(obj: &Gd<T>) -> bool {
411423
out!(" Dyn::dec <{}>", std::any::type_name::<T>());
412424
if obj
413425
.instance_id_or_none()
@@ -435,7 +447,7 @@ pub mod mem {
435447
impl Memory for ManualMemory {
436448
fn maybe_init_ref<T: GodotClass>(_obj: &Gd<T>) {}
437449
fn maybe_inc_ref<T: GodotClass>(_obj: &Gd<T>) {}
438-
fn maybe_dec_ref<T: GodotClass>(_obj: &Gd<T>) -> bool {
450+
unsafe fn maybe_dec_ref<T: GodotClass>(_obj: &Gd<T>) -> bool {
439451
false
440452
}
441453
fn is_ref_counted<T: GodotClass>(_obj: &Gd<T>) -> Option<bool> {

0 commit comments

Comments
 (0)