diff --git a/.github/composite/godot-install/action.yml b/.github/composite/godot-install/action.yml index ca4378e7d..d985688d6 100644 --- a/.github/composite/godot-install/action.yml +++ b/.github/composite/godot-install/action.yml @@ -37,13 +37,19 @@ runs: # shell: bash - name: "Download Godot artifact" + env: + ARTIFACT_NAME: ${{ inputs.artifact-name }} # if: steps.cache-godot.outputs.cache-hit != 'true' # If a specific Godot revision should be used, rather than latest, use this: # curl https://nightly.link/Bromeon/godot4-nightly/actions/runs/4910907653/${{ inputs.artifact-name }}.zip \ run: | - curl https://nightly.link/Bromeon/godot4-nightly/workflows/compile-godot/master/${{ inputs.artifact-name }}.zip \ - -Lo artifact.zip \ - --retry 3 + if [[ $ARTIFACT_NAME == *"stable"* ]]; then + url="https://nightly.link/Bromeon/godot4-nightly/workflows/compile-godot-stable/master/$ARTIFACT_NAME.zip" + else + url="https://nightly.link/Bromeon/godot4-nightly/workflows/compile-godot-nightly/master/$ARTIFACT_NAME.zip" + fi + + curl "$url" -Lo artifact.zip --retry 3 unzip artifact.zip -d $RUNNER_DIR/godot_bin shell: bash diff --git a/.github/workflows/full-ci.yml b/.github/workflows/full-ci.yml index 6e212c685..9a00ddb76 100644 --- a/.github/workflows/full-ci.yml +++ b/.github/workflows/full-ci.yml @@ -123,26 +123,36 @@ jobs: strategy: fail-fast: false # cancel all jobs as soon as one fails? matrix: + # Naming: {os}[-{runtimeVersion}]-{apiVersion} + # runtimeVersion = version of Godot binary; apiVersion = version of GDExtension API against which gdext is compiled. + # Order this way because macOS typically has the longest duration, followed by Windows, so it benefits total workflow execution time. # Additionally, the 'linux (msrv *)' special case will then be listed next to the other 'linux' jobs. # Note: Windows uses '--target x86_64-pc-windows-msvc' by default as Cargo argument. include: # macOS + - name: macos-stableRt-4.0.3 + os: macos-12 + artifact-name: macos-stable + godot-binary: godot.macos.editor.dev.x86_64 + godot-prebuilt-patch: '4.0.3' + - name: macos-4.0.3 os: macos-12 - artifact-name: macos + artifact-name: macos-nightly godot-binary: godot.macos.editor.dev.x86_64 godot-prebuilt-patch: '4.0.3' - name: macos-double os: macos-12 + artifact-name: macos-double-nightly godot-binary: godot.macos.editor.dev.double.x86_64 rust-extra-args: --features godot/double-precision - name: macos-nightly os: macos-12 - artifact-name: macos + artifact-name: macos-nightly godot-binary: godot.macos.editor.dev.x86_64 rust-extra-args: --features godot/custom-godot with-llvm: true @@ -150,20 +160,27 @@ jobs: # Windows + - name: windows-stableRt-4.0.3 + os: windows-latest + artifact-name: windows-stable + godot-binary: godot.windows.editor.dev.x86_64.exe + godot-prebuilt-patch: '4.0.3' + - name: windows-4.0.3 os: windows-latest - artifact-name: windows + artifact-name: windows-nightly godot-binary: godot.windows.editor.dev.x86_64.exe godot-prebuilt-patch: '4.0.3' - name: windows-double os: windows-latest + artifact-name: windows-double-nightly godot-binary: godot.windows.editor.dev.double.x86_64.exe rust-extra-args: --features godot/double-precision - name: windows-nightly os: windows-latest - artifact-name: windows + artifact-name: windows-nightly godot-binary: godot.windows.editor.dev.x86_64.exe rust-extra-args: --features godot/custom-godot godot-prebuilt-patch: '4.1' @@ -172,44 +189,50 @@ jobs: # Don't use latest Ubuntu (22.04) as it breaks lots of ecosystem compatibility. # If ever moving to ubuntu-latest, need to manually install libtinfo5 for LLVM. + - name: linux-stableRt-4.0.3 + os: ubuntu-20.04 + artifact-name: linux-stable + godot-binary: godot.linuxbsd.editor.dev.x86_64 + - name: linux-4.0.3 os: ubuntu-20.04 - artifact-name: linux + artifact-name: linux-nightly godot-binary: godot.linuxbsd.editor.dev.x86_64 godot-check-header: false # disabled for now - name: linux-4.0.2 os: ubuntu-20.04 - artifact-name: linux + artifact-name: linux-nightly godot-binary: godot.linuxbsd.editor.dev.x86_64 godot-prebuilt-patch: '4.0.2' - name: linux-4.0.1 os: ubuntu-20.04 - artifact-name: linux + artifact-name: linux-nightly godot-binary: godot.linuxbsd.editor.dev.x86_64 godot-prebuilt-patch: '4.0.1' - name: linux-4.0 os: ubuntu-20.04 - artifact-name: linux + artifact-name: linux-nightly godot-binary: godot.linuxbsd.editor.dev.x86_64 godot-prebuilt-patch: '4.0' - name: linux-double os: ubuntu-20.04 + artifact-name: linux-double-nightly godot-binary: godot.linuxbsd.editor.dev.double.x86_64 rust-extra-args: --features godot/double-precision - name: linux-features os: ubuntu-20.04 - artifact-name: linux + artifact-name: linux-nightly godot-binary: godot.linuxbsd.editor.dev.x86_64 rust-extra-args: --features godot/threads,godot/serde - name: linux-nightly os: ubuntu-20.04 - artifact-name: linux + artifact-name: linux-nightly godot-binary: godot.linuxbsd.editor.dev.x86_64 rust-extra-args: --features godot/custom-godot godot-prebuilt-patch: '4.1' @@ -220,35 +243,27 @@ jobs: # The gcc version can possibly be removed later, as it is slower and needs a larger artifact than the clang one. # --disallow-focus: fail if #[itest(focus)] is encountered, to prevent running only a few tests for full CI - - name: linux-memcheck-gcc-4.0.3 - os: ubuntu-20.04 - artifact-name: linux-memcheck-gcc - godot-binary: godot.linuxbsd.editor.dev.x86_64.san - godot-args: -- --disallow-focus - rust-toolchain: nightly - rust-env-rustflags: -Zrandomize-layout - - name: linux-memcheck-clang-4.0.3 + - name: linux-memcheck-stableRt-4.0.3 os: ubuntu-20.04 - artifact-name: linux-memcheck-clang + artifact-name: linux-memcheck-clang-stable godot-binary: godot.linuxbsd.editor.dev.x86_64.llvm.san godot-args: -- --disallow-focus rust-toolchain: nightly rust-env-rustflags: -Zrandomize-layout - - name: linux-memcheck-gcc-nightly - os: ubuntu-20.04 - artifact-name: linux-memcheck-gcc - godot-binary: godot.linuxbsd.editor.dev.x86_64.san - godot-args: -- --disallow-focus - rust-toolchain: nightly - rust-env-rustflags: -Zrandomize-layout - rust-extra-args: --features godot/custom-godot - godot-prebuilt-patch: '4.1' + # FIXME(#298): memory leaks detected when running 4.0.3 API with 4.1+ binary +# - name: linux-memcheck-4.0.3 +# os: ubuntu-20.04 +# artifact-name: linux-memcheck-clang-nightly +# godot-binary: godot.linuxbsd.editor.dev.x86_64.llvm.san +# godot-args: -- --disallow-focus +# rust-toolchain: nightly +# rust-env-rustflags: -Zrandomize-layout - - name: linux-memcheck-clang-nightly + - name: linux-memcheck-nightly os: ubuntu-20.04 - artifact-name: linux-memcheck-clang + artifact-name: linux-memcheck-clang-nightly godot-binary: godot.linuxbsd.editor.dev.x86_64.llvm.san godot-args: -- --disallow-focus rust-toolchain: nightly @@ -263,7 +278,7 @@ jobs: - name: "Run Godot integration test" uses: ./.github/composite/godot-itest with: - artifact-name: godot-${{ matrix.artifact-name || matrix.name }} + artifact-name: godot-${{ matrix.artifact-name }} godot-binary: ${{ matrix.godot-binary }} godot-args: ${{ matrix.godot-args }} godot-prebuilt-patch: ${{ matrix.godot-prebuilt-patch }} diff --git a/.github/workflows/minimal-ci.yml b/.github/workflows/minimal-ci.yml index 93f612ccd..6c148b334 100644 --- a/.github/workflows/minimal-ci.yml +++ b/.github/workflows/minimal-ci.yml @@ -86,7 +86,7 @@ jobs: - name: "Run Godot integration test" uses: ./.github/composite/godot-itest with: - artifact-name: godot-linux + artifact-name: godot-linux-nightly godot-binary: godot.linuxbsd.editor.dev.x86_64 diff --git a/godot-bindings/src/godot_exe.rs b/godot-bindings/src/godot_exe.rs index 6b2814c6d..7a4a409c6 100644 --- a/godot-bindings/src/godot_exe.rs +++ b/godot-bindings/src/godot_exe.rs @@ -6,10 +6,10 @@ //! Commands related to Godot executable -use crate::custom::godot_version::GodotVersion; use crate::godot_version::parse_godot_version; use crate::header_gen::generate_rust_binding; use crate::watch::StopWatch; +use crate::GodotVersion; use regex::Regex; use std::fs; @@ -99,7 +99,7 @@ fn update_version_file(version: &str) { } */ -fn read_godot_version(godot_bin: &Path) -> GodotVersion { +pub(crate) fn read_godot_version(godot_bin: &Path) -> GodotVersion { let output = Command::new(godot_bin) .arg("--version") .output() @@ -259,7 +259,7 @@ fn polyfill_legacy_header(c: &mut String) { ); } -fn locate_godot_binary() -> PathBuf { +pub(crate) fn locate_godot_binary() -> PathBuf { if let Ok(string) = std::env::var("GODOT4_BIN") { println!("Found GODOT4_BIN with path to executable: '{string}'"); println!("cargo:rerun-if-env-changed=GODOT4_BIN"); diff --git a/godot-bindings/src/godot_version.rs b/godot-bindings/src/godot_version.rs index f687f23b3..7abfd858b 100644 --- a/godot-bindings/src/godot_version.rs +++ b/godot-bindings/src/godot_version.rs @@ -6,28 +6,11 @@ //#![allow(unused_variables, dead_code)] +use crate::GodotVersion; use regex::{Captures, Regex}; use std::error::Error; use std::str::FromStr; -#[derive(Debug, Eq, PartialEq)] -pub struct GodotVersion { - /// the original string (trimmed, stripped of text around) - pub full_string: String, - - pub major: u8, - pub minor: u8, - - /// 0 if none - pub patch: u8, - - /// alpha|beta|dev|stable - pub status: String, - - /// Git revision 'custom_build.{rev}' or '{official}.rev', if available - pub custom_rev: Option, -} - pub fn parse_godot_version(version_str: &str) -> Result> { // Format of the string emitted by `godot --version`: // https://github.com/godot-rust/gdext/issues/118#issuecomment-1465748123 diff --git a/godot-bindings/src/lib.rs b/godot-bindings/src/lib.rs index 8cbf4a334..ed8493ea4 100644 --- a/godot-bindings/src/lib.rs +++ b/godot-bindings/src/lib.rs @@ -17,6 +17,26 @@ compile_error!( "At least one of `custom-godot` or `prebuilt-godot` must be specified (none given)." ); +// This is outside of `godot_version` to allow us to use it even when we don't have the `custom-godot` +// feature enabled. +#[derive(Debug, Eq, PartialEq)] +pub struct GodotVersion { + /// the original string (trimmed, stripped of text around) + pub full_string: String, + + pub major: u8, + pub minor: u8, + + /// 0 if none + pub patch: u8, + + /// alpha|beta|dev|stable + pub status: String, + + /// Git revision 'custom_build.{rev}' or '{official}.rev', if available + pub custom_rev: Option, +} + // ---------------------------------------------------------------------------------------------------------------------------------------------- // Regenerate all files @@ -41,6 +61,10 @@ mod custom { pub fn write_gdextension_headers_from_c(h_path: &Path, rs_path: &Path, watch: &mut StopWatch) { godot_exe::write_gdextension_headers(h_path, rs_path, true, watch); } + + pub(crate) fn get_godot_version() -> GodotVersion { + godot_exe::read_godot_version(&godot_exe::locate_godot_binary()) + } } #[cfg(feature = "custom-godot")] @@ -70,6 +94,23 @@ mod prebuilt { .unwrap_or_else(|e| panic!("failed to write gdextension_interface.rs: {e}")); watch.record("write_header_rs"); } + + pub(crate) fn get_godot_version() -> GodotVersion { + let version: Vec<&str> = godot4_prebuilt::GODOT_VERSION + .split('.') + .collect::>(); + GodotVersion { + full_string: godot4_prebuilt::GODOT_VERSION.into(), + major: version[0].parse().unwrap(), + minor: version[1].parse().unwrap(), + patch: version + .get(2) + .and_then(|patch| patch.parse().ok()) + .unwrap_or(0), + status: "stable".into(), + custom_rev: None, + } + } } #[cfg(not(feature = "custom-godot"))] @@ -85,3 +126,21 @@ pub fn clear_dir(dir: &Path, watch: &mut StopWatch) { } std::fs::create_dir_all(dir).unwrap_or_else(|e| panic!("failed to create dir: {e}")); } + +pub fn emit_godot_version_cfg() { + let GodotVersion { + major, + minor, + patch, + .. + } = get_godot_version(); + + println!(r#"cargo:rustc-cfg=gdextension_api="{major}.{minor}""#); + + // Godot drops the patch version if it is 0. + if patch != 0 { + println!(r#"cargo:rustc-cfg=gdextension_exact_api="{major}.{minor}.{patch}""#); + } else { + println!(r#"cargo:rustc-cfg=gdextension_exact_api="{major}.{minor}""#); + } +} diff --git a/godot-codegen/src/central_generator.rs b/godot-codegen/src/central_generator.rs index 7c1373577..11054ab06 100644 --- a/godot-codegen/src/central_generator.rs +++ b/godot-codegen/src/central_generator.rs @@ -272,6 +272,15 @@ fn make_build_config(header: &Header) -> TokenStream { String::from_utf8_lossy(c_str.to_bytes()).to_string() } } + + /// Version of the Godot engine which loaded gdext via GDExtension binding, as + /// `(major, minor, patch)` triple. + pub fn godot_runtime_version_triple() -> (u8, u8, u8) { + let version = unsafe { + crate::runtime_metadata().godot_version + }; + (version.major as u8, version.minor as u8, version.patch as u8) + } } } } diff --git a/godot-core/Cargo.toml b/godot-core/Cargo.toml index a606a6687..8c96dd95b 100644 --- a/godot-core/Cargo.toml +++ b/godot-core/Cargo.toml @@ -29,4 +29,5 @@ godot = { path = "../godot" } serde_json = "1.0" [build-dependencies] +godot-bindings = { path = "../godot-bindings" } godot-codegen = { path = "../godot-codegen" } diff --git a/godot-core/build.rs b/godot-core/build.rs index a6add2833..16c92c736 100644 --- a/godot-core/build.rs +++ b/godot-core/build.rs @@ -17,4 +17,6 @@ fn main() { godot_codegen::generate_core_files(gen_path); println!("cargo:rerun-if-changed=build.rs"); + + godot_bindings::emit_godot_version_cfg(); } diff --git a/godot-core/src/builtin/array.rs b/godot-core/src/builtin/array.rs index 55a102b06..64a81639b 100644 --- a/godot-core/src/builtin/array.rs +++ b/godot-core/src/builtin/array.rs @@ -717,7 +717,7 @@ impl FromVariant for Array { } let array = unsafe { - Self::from_sys_init(|self_ptr| { + sys::from_sys_init_or_init_default::(|self_ptr| { let array_from_variant = sys::builtin_fn!(array_from_variant); array_from_variant(self_ptr, variant.var_sys()); }) diff --git a/godot-core/src/builtin/basis.rs b/godot-core/src/builtin/basis.rs index 635c6fa39..f7f453a68 100644 --- a/godot-core/src/builtin/basis.rs +++ b/godot-core/src/builtin/basis.rs @@ -143,10 +143,20 @@ impl Basis { /// orthonormalized. The `target` and `up` vectors cannot be zero, and /// cannot be parallel to each other. /// + #[cfg(gdextension_api = "4.0")] /// _Godot equivalent: `Basis.looking_at()`_ pub fn new_looking_at(target: Vector3, up: Vector3) -> Self { super::inner::InnerBasis::looking_at(target, up) } + #[cfg(not(gdextension_api = "4.0"))] + /// If `use_model_front` is true, the +Z axis (asset front) is treated as forward (implies +X is left) + /// and points toward the target position. By default, the -Z axis (camera forward) is treated as forward + /// (implies +X is right). + /// + /// _Godot equivalent: `Basis.looking_at()`_ + pub fn new_looking_at(target: Vector3, up: Vector3, use_model_front: bool) -> Self { + super::inner::InnerBasis::looking_at(target, up, use_model_front) + } /// Creates a `[Vector3; 3]` with the columns of the `Basis`. pub fn to_cols(self) -> [Vector3; 3] { diff --git a/godot-core/src/builtin/callable.rs b/godot-core/src/builtin/callable.rs index ed9e9e6ee..a7652172b 100644 --- a/godot-core/src/builtin/callable.rs +++ b/godot-core/src/builtin/callable.rs @@ -45,7 +45,7 @@ impl Callable { // upcast not needed let method = method_name.into(); unsafe { - Self::from_sys_init(|self_ptr| { + sys::from_sys_init_or_init_default::(|self_ptr| { let ctor = sys::builtin_fn!(callable_from_object_method); let args = [object.sys_const(), method.sys_const()]; ctor(self_ptr, args.as_ptr()); diff --git a/godot-core/src/builtin/string/godot_string.rs b/godot-core/src/builtin/string/godot_string.rs index 85f8f50e6..fbe152551 100644 --- a/godot-core/src/builtin/string/godot_string.rs +++ b/godot-core/src/builtin/string/godot_string.rs @@ -223,7 +223,7 @@ impl FromStr for GodotString { impl From<&StringName> for GodotString { fn from(string: &StringName) -> Self { unsafe { - Self::from_sys_init(|self_ptr| { + sys::from_sys_init_or_init_default::(|self_ptr| { let ctor = sys::builtin_fn!(string_from_string_name); let args = [string.sys_const()]; ctor(self_ptr, args.as_ptr()); @@ -244,7 +244,7 @@ impl From for GodotString { impl From<&NodePath> for GodotString { fn from(path: &NodePath) -> Self { unsafe { - Self::from_sys_init(|self_ptr| { + sys::from_sys_init_or_init_default::(|self_ptr| { let ctor = sys::builtin_fn!(string_from_node_path); let args = [path.sys_const()]; ctor(self_ptr, args.as_ptr()); diff --git a/godot-core/src/builtin/string/node_path.rs b/godot-core/src/builtin/string/node_path.rs index ff4c7ac88..a88e5e623 100644 --- a/godot-core/src/builtin/string/node_path.rs +++ b/godot-core/src/builtin/string/node_path.rs @@ -101,7 +101,7 @@ impl_rust_string_conv!(NodePath); impl From<&GodotString> for NodePath { fn from(string: &GodotString) -> Self { unsafe { - Self::from_sys_init(|self_ptr| { + sys::from_sys_init_or_init_default::(|self_ptr| { let ctor = sys::builtin_fn!(node_path_from_string); let args = [string.sys_const()]; ctor(self_ptr, args.as_ptr()); diff --git a/godot-core/src/builtin/string/string_name.rs b/godot-core/src/builtin/string/string_name.rs index b35b467a8..8add64453 100644 --- a/godot-core/src/builtin/string/string_name.rs +++ b/godot-core/src/builtin/string/string_name.rs @@ -129,7 +129,7 @@ impl_rust_string_conv!(StringName); impl From<&GodotString> for StringName { fn from(string: &GodotString) -> Self { unsafe { - Self::from_sys_init(|self_ptr| { + sys::from_sys_init_or_init_default::(|self_ptr| { let ctor = sys::builtin_fn!(string_name_from_string); let args = [string.sys_const()]; ctor(self_ptr, args.as_ptr()); diff --git a/godot-core/src/builtin/transform3d.rs b/godot-core/src/builtin/transform3d.rs index ce48f0702..1ed816437 100644 --- a/godot-core/src/builtin/transform3d.rs +++ b/godot-core/src/builtin/transform3d.rs @@ -145,6 +145,7 @@ impl Transform3D { /// See [`Basis::new_looking_at()`] for more information. /// /// _Godot equivalent: Transform3D.looking_at()_ + #[cfg(gdextension_api = "4.0")] #[must_use] pub fn looking_at(self, target: Vector3, up: Vector3) -> Self { Self { @@ -152,6 +153,14 @@ impl Transform3D { origin: self.origin, } } + #[cfg(not(gdextension_api = "4.0"))] + #[must_use] + pub fn looking_at(self, target: Vector3, up: Vector3, use_model_front: bool) -> Self { + Self { + basis: Basis::new_looking_at(target - self.origin, up, use_model_front), + origin: self.origin, + } + } /// Returns the transform with the basis orthogonal (90 degrees), and /// normalized axis vectors (scale of 1 or -1). diff --git a/godot-core/src/builtin/variant/impls.rs b/godot-core/src/builtin/variant/impls.rs index f108d585f..cbad9c7e2 100644 --- a/godot-core/src/builtin/variant/impls.rs +++ b/godot-core/src/builtin/variant/impls.rs @@ -26,7 +26,12 @@ macro_rules! impl_variant_metadata { } }; } - +// Certain types need to be passed as initialized pointers in their from_variant implementations in 4.0. Because +// 4.0 uses `*ptr = value` to return the type, and some types in c++ override `operator=` in c++ in a way +// that requires the pointer the be initialized. But some other types will cause a memory leak in 4.1 if +// initialized. +// +// Thus we can use `init` to indicate when it must be initialized in 4.0. macro_rules! impl_variant_traits { ($T:ty, $from_fn:ident, $to_fn:ident, $variant_type:ident) => { impl_variant_traits!(@@ $T, $from_fn, $to_fn, $variant_type;); @@ -61,13 +66,16 @@ macro_rules! impl_variant_traits { return Err(VariantConversionError::BadType) } + // For 4.0: // In contrast to T -> Variant, the conversion Variant -> T assumes // that the destination is initialized (at least for some T). For example: // void String::operator=(const String &p_str) { _cowdata._ref(p_str._cowdata); } // does a copy-on-write and explodes if this->_cowdata is not initialized. // We can thus NOT use Self::from_sys_init(). + // + // This was changed in 4.1. let result = unsafe { - Self::from_sys_init(|self_ptr| { + sys::from_sys_init_or_init_default(|self_ptr| { let converter = sys::builtin_fn!($to_fn); converter(self_ptr, variant.var_sys()); }) diff --git a/godot-core/src/builtin/variant/mod.rs b/godot-core/src/builtin/variant/mod.rs index ae57fc25d..0a8e94a40 100644 --- a/godot-core/src/builtin/variant/mod.rs +++ b/godot-core/src/builtin/variant/mod.rs @@ -109,7 +109,7 @@ impl Variant { let mut error = sys::default_call_error(); let result = unsafe { - Variant::from_var_sys_init(|variant_ptr| { + Variant::from_var_sys_init_or_init_default(|variant_ptr| { interface_fn!(variant_call)( self.var_sys(), method.string_sys(), @@ -133,7 +133,7 @@ impl Variant { let mut is_valid = false as u8; let result = unsafe { - Variant::from_var_sys_init(|variant_ptr| { + Self::from_var_sys_init_or_init_default(|variant_ptr| { interface_fn!(variant_evaluate)( op_sys, self.var_sys(), @@ -194,6 +194,36 @@ impl Variant { fn var_sys = sys; } + #[doc(hidden)] + pub unsafe fn from_var_sys_init_default( + init_fn: impl FnOnce(sys::GDExtensionVariantPtr), + ) -> Self { + #[allow(unused_mut)] + let mut variant = Variant::nil(); + init_fn(variant.var_sys()); + variant + } + + /// # Safety + /// + /// See [`GodotFfi::from_sys_init`] and [`GodotFfi::from_sys_init_default`]. + #[cfg(gdextension_api = "4.0")] + pub unsafe fn from_var_sys_init_or_init_default( + init_fn: impl FnOnce(sys::GDExtensionVariantPtr), + ) -> Self { + Self::from_var_sys_init_default(init_fn) + } + + /// # Safety + /// + /// See [`GodotFfi::from_sys_init`] and [`GodotFfi::from_sys_init_default`]. + #[cfg(not(gdextension_api = "4.0"))] + pub unsafe fn from_var_sys_init_or_init_default( + init_fn: impl FnOnce(sys::GDExtensionUninitializedVariantPtr), + ) -> Self { + Self::from_var_sys_init(init_fn) + } + #[doc(hidden)] pub fn var_sys_const(&self) -> sys::GDExtensionConstVariantPtr { sys::to_const_ptr(self.var_sys()) diff --git a/godot-core/src/lib.rs b/godot-core/src/lib.rs index 47e79d174..b1f0b4783 100644 --- a/godot-core/src/lib.rs +++ b/godot-core/src/lib.rs @@ -148,3 +148,39 @@ pub mod private { std::io::stdout().flush().expect("flush stdout"); } } + +macro_rules! generate_gdextension_api_version { + ( + $( + ($name:ident, $gdextension_api:ident) => { + $($version:literal, )* + } + ),* $(,)? + ) => { + $( + $( + #[cfg($gdextension_api = $version)] + #[allow(dead_code)] + const $name: &str = $version; + )* + )* + }; +} + +// If multiple gdextension_api_version's are found then this will generate several structs with the same +// name, causing a compile error. +// +// This includes all versions we're developing for, including unreleased future versions. +generate_gdextension_api_version!( + (GDEXTENSION_EXACT_API, gdextension_exact_api) => { + "4.0", + "4.0.1", + "4.0.2", + "4.0.3", + "4.1", + }, + (GDEXTENSION_API, gdextension_api) => { + "4.0", + "4.1", + }, +); diff --git a/godot-ffi/build.rs b/godot-ffi/build.rs index 4c6283111..c929796ea 100644 --- a/godot-ffi/build.rs +++ b/godot-ffi/build.rs @@ -27,4 +27,6 @@ fn main() { watch.write_stats_to(&gen_path.join("ffi-stats.txt")); println!("cargo:rerun-if-changed=build.rs"); + + godot_bindings::emit_godot_version_cfg(); } diff --git a/godot-ffi/src/godot_ffi.rs b/godot-ffi/src/godot_ffi.rs index 974f61a21..4bfb2f7c3 100644 --- a/godot-ffi/src/godot_ffi.rs +++ b/godot-ffi/src/godot_ffi.rs @@ -96,6 +96,32 @@ pub unsafe trait GodotFfi { unsafe fn move_return_ptr(self, dst: sys::GDExtensionTypePtr, call_type: PtrcallType); } +// In Godot 4.0.x, a lot of that are "constructed into" require a default-initialized value. +// In Godot 4.1+, placement new is used, requiring no prior value. +// This method abstracts over that. Outside of GodotFfi because it should not be overridden. + +/// # Safety +/// +/// See [`GodotFfi::from_sys_init`] and [`GodotFfi::from_sys_init_default`]. +#[cfg(gdextension_api = "4.0")] +pub unsafe fn from_sys_init_or_init_default( + init_fn: impl FnOnce(sys::GDExtensionTypePtr), +) -> T { + T::from_sys_init_default(init_fn) +} + +/// # Safety +/// +/// See [`GodotFfi::from_sys_init`] and [`GodotFfi::from_sys_init_default`]. +#[cfg(not(gdextension_api = "4.0"))] +pub unsafe fn from_sys_init_or_init_default( + init_fn: impl FnOnce(sys::GDExtensionUninitializedTypePtr), +) -> T { + T::from_sys_init(init_fn) +} + +// ---------------------------------------------------------------------------------------------------------------------------------------------- + /// Marks a type as having a nullable counterpart in Godot. /// /// This trait primarily exists to implement GodotFfi for `Option>`, which is not possible @@ -114,13 +140,6 @@ unsafe impl GodotFfi for Option where T: GodotNullablePtr, { - fn sys(&self) -> sys::GDExtensionTypePtr { - match self { - Some(value) => value.sys(), - None => ptr::null_mut() as sys::GDExtensionTypePtr, - } - } - unsafe fn from_sys(ptr: sys::GDExtensionTypePtr) -> Self { ptr_then(ptr, |ptr| T::from_sys(ptr)) } @@ -132,6 +151,13 @@ where Self::from_sys(raw.assume_init()) } + fn sys(&self) -> sys::GDExtensionTypePtr { + match self { + Some(value) => value.sys(), + None => ptr::null_mut() as sys::GDExtensionTypePtr, + } + } + unsafe fn from_arg_ptr(ptr: sys::GDExtensionTypePtr, call_type: PtrcallType) -> Self { ptr_then(ptr, |ptr| T::from_arg_ptr(ptr, call_type)) } @@ -143,6 +169,8 @@ where } } +// ---------------------------------------------------------------------------------------------------------------------------------------------- + /// An indication of what type of pointer call is being made. #[derive(Default, Copy, Clone, Eq, PartialEq, Debug)] pub enum PtrcallType { diff --git a/godot-ffi/src/lib.rs b/godot-ffi/src/lib.rs index 50beaee36..961e09acc 100644 --- a/godot-ffi/src/lib.rs +++ b/godot-ffi/src/lib.rs @@ -32,7 +32,9 @@ use std::ffi::CStr; #[doc(hidden)] pub use paste; -pub use crate::godot_ffi::{GodotFfi, GodotFuncMarshal, GodotNullablePtr, PtrcallType}; +pub use crate::godot_ffi::{ + from_sys_init_or_init_default, GodotFfi, GodotFuncMarshal, GodotNullablePtr, PtrcallType, +}; pub use gen::central::*; pub use gen::gdextension_interface::*; pub use gen::interface::*;