Skip to content

Commit a6932c7

Browse files
authored
Merge pull request #1197 from godot-rust/bugfix/hot-reload-noinit
Make hot-reload work with `#[class(no_init)]`
2 parents 443621c + 1662122 commit a6932c7

File tree

7 files changed

+90
-10
lines changed

7 files changed

+90
-10
lines changed

.github/workflows/full-ci.yml

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -246,7 +246,7 @@ jobs:
246246
os: macos-13
247247
artifact-name: macos-x86-nightly
248248
godot-binary: godot.macos.editor.dev.x86_64
249-
with-hot-reload: true
249+
hot-reload: stable
250250

251251
- name: macos-double-x86
252252
os: macos-13
@@ -264,7 +264,7 @@ jobs:
264264
os: macos-latest
265265
artifact-name: macos-arm-nightly
266266
godot-binary: godot.macos.editor.dev.arm64
267-
with-hot-reload: true
267+
hot-reload: stable
268268

269269
# api-custom on macOS arm64 not working, due to clang linker issues.
270270
# - name: macos-double-arm
@@ -285,7 +285,7 @@ jobs:
285285
os: windows-latest
286286
artifact-name: windows-nightly
287287
godot-binary: godot.windows.editor.dev.x86_64.exe
288-
with-hot-reload: true
288+
hot-reload: stable
289289

290290
- name: windows-double
291291
os: windows-latest
@@ -317,7 +317,7 @@ jobs:
317317
artifact-name: linux-nightly
318318
godot-binary: godot.linuxbsd.editor.dev.x86_64
319319
rust-extra-args: --features itest/codegen-full
320-
with-hot-reload: true
320+
hot-reload: api-custom
321321

322322
# Combines now a lot of features, but should be OK. lazy-function-tables doesn't work with experimental-threads.
323323
- name: linux-double-lazy
@@ -352,12 +352,14 @@ jobs:
352352
rust-cache-key: release
353353

354354
# Linux compat (4.1 disabled, already covered by memcheck)
355+
# No hot-reload before 4.4, as the Godot project is 4.4+.
355356

356357
- name: linux-4.4
357358
os: ubuntu-22.04
358359
artifact-name: linux-4.4
359360
godot-binary: godot.linuxbsd.editor.dev.x86_64
360361
godot-prebuilt-patch: '4.4'
362+
hot-reload: stable
361363

362364
- name: linux-4.3
363365
os: ubuntu-22.04
@@ -418,10 +420,11 @@ jobs:
418420
godot-indirect-json: ${{ matrix.godot-indirect-json }}
419421

420422
- name: "Build and test hot-reload"
421-
if: ${{ matrix.with-hot-reload }}
423+
if: ${{ matrix.hot-reload }}
422424
working-directory: itest/hot-reload/godot
423425
# Repeat a few times, our hot reload integration test can sometimes be a bit flaky.
424-
run: $RETRY ./run-test.sh
426+
# Don't pass in rust-extra-args as environment; they're intended for itest.
427+
run: $RETRY ./run-test.sh ${{ matrix.hot-reload }}
425428
shell: bash
426429

427430

.github/workflows/minimal-ci.yml

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -159,7 +159,7 @@ jobs:
159159
artifact-name: linux-nightly
160160
godot-binary: godot.linuxbsd.editor.dev.x86_64
161161
rust-extra-args: --features itest/codegen-full
162-
with-hot-reload: true
162+
hot-reload: stable
163163

164164
- name: linux-custom-api-json
165165
os: ubuntu-22.04
@@ -173,6 +173,7 @@ jobs:
173173
artifact-name: linux-nightly
174174
godot-binary: godot.linuxbsd.editor.dev.x86_64
175175
rust-extra-args: --features itest/experimental-threads,itest/codegen-full-experimental,godot/api-custom,godot/serde,itest/register-docs
176+
hot-reload: api-custom
176177

177178
- name: linux-release
178179
os: ubuntu-22.04
@@ -240,9 +241,9 @@ jobs:
240241
godot-indirect-json: ${{ matrix.godot-indirect-json }}
241242

242243
- name: "Build and test hot-reload"
243-
if: ${{ matrix.with-hot-reload }}
244+
if: ${{ matrix.hot-reload }}
244245
working-directory: itest/hot-reload/godot
245-
run: ./run-test.sh
246+
run: ./run-test.sh ${{ matrix.hot-reload }}
246247
shell: bash
247248

248249

godot-core/src/registry/callbacks.rs

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,24 @@ pub unsafe extern "C" fn create<T: cap::GodotDefault>(
4343
create_custom(T::__godot_user_init).unwrap_or(std::ptr::null_mut())
4444
}
4545

46+
/// Workaround for <https://github.com/godot-rust/gdext/issues/874> before Godot 4.5.
47+
///
48+
/// Godot expects a creator function, but doesn't require an actual object to be instantiated.
49+
#[cfg(all(since_api = "4.4", before_api = "4.5"))]
50+
pub unsafe extern "C" fn create_null<T>(
51+
_class_userdata: *mut std::ffi::c_void,
52+
_notify_postinitialize: sys::GDExtensionBool,
53+
) -> sys::GDExtensionObjectPtr {
54+
std::ptr::null_mut()
55+
}
56+
57+
#[cfg(before_api = "4.4")]
58+
pub unsafe extern "C" fn create_null<T>(
59+
_class_userdata: *mut std::ffi::c_void,
60+
) -> sys::GDExtensionObjectPtr {
61+
std::ptr::null_mut()
62+
}
63+
4664
/// Godot FFI function for recreating a GDExtension instance, e.g. after a hot reload.
4765
///
4866
/// If the `init()` constructor panics, null is returned.
@@ -55,6 +73,17 @@ pub unsafe extern "C" fn recreate<T: cap::GodotDefault>(
5573
.unwrap_or(std::ptr::null_mut())
5674
}
5775

76+
/// Workaround for <https://github.com/godot-rust/gdext/issues/874> before Godot 4.5.
77+
///
78+
/// Godot expects a creator function, but doesn't require an actual object to be instantiated.
79+
#[cfg(all(since_api = "4.2", before_api = "4.5"))]
80+
pub unsafe extern "C" fn recreate_null<T>(
81+
_class_userdata: *mut std::ffi::c_void,
82+
_object: sys::GDExtensionObjectPtr,
83+
) -> sys::GDExtensionClassInstancePtr {
84+
std::ptr::null_mut()
85+
}
86+
5887
pub(crate) fn create_custom<T, F>(
5988
make_user_instance: F,
6089
) -> Result<sys::GDExtensionObjectPtr, PanicPayload>

godot-core/src/registry/plugin.rs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,10 @@ pub struct Struct {
147147

148148
/// Godot low-level `create` function, wired up to library-generated `init`.
149149
///
150+
/// For `#[class(no_init)]`, behavior depends on Godot version:
151+
/// - 4.5 and later: `None`
152+
/// - until 4.4: a dummy function that fails, to not break hot reloading.
153+
///
150154
/// This is mutually exclusive with [`ITraitImpl::user_create_fn`].
151155
pub(crate) generated_create_fn: Option<GodotCreateFn>,
152156

@@ -222,6 +226,19 @@ impl Struct {
222226
self
223227
}
224228

229+
// Workaround for https://github.com/godot-rust/gdext/issues/874, before https://github.com/godotengine/godot/pull/99133 is merged in 4.5.
230+
#[cfg(before_api = "4.5")]
231+
pub fn with_generated_no_default<T: GodotClass>(mut self) -> Self {
232+
set(&mut self.generated_create_fn, callbacks::create_null::<T>);
233+
234+
#[cfg(since_api = "4.2")]
235+
set(
236+
&mut self.generated_recreate_fn,
237+
callbacks::recreate_null::<T>,
238+
);
239+
self
240+
}
241+
225242
pub fn with_default_get_virtual_fn<T: GodotClass + UserClass>(mut self) -> Self {
226243
set(
227244
&mut self.default_get_virtual_fn,

godot-macros/src/class/derive_godot_class.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,10 @@ pub fn derive_godot_class(item: venial::Item) -> ParseResult<TokenStream> {
122122
}
123123
InitStrategy::Absent => {
124124
is_instantiable = false;
125+
126+
// Workaround for https://github.com/godot-rust/gdext/issues/874 before Godot 4.5.
127+
#[cfg(before_api = "4.5")]
128+
modifiers.push(quote! { with_generated_no_default::<#class_name> });
125129
}
126130
};
127131
if is_instantiable {

itest/hot-reload/godot/run-test.sh

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,16 @@
66

77
rel="."
88

9+
# If argument is 'api-custom', set 'cargoArgs' to use that feature. If 'stable', set 'cargoArgs' to empty string. Otherwise, error.
10+
if [[ $1 == "api-custom" ]]; then
11+
cargoArgs="--features godot/api-custom"
12+
elif [[ $1 == "stable" ]]; then
13+
cargoArgs=""
14+
else
15+
echo "[Bash] Error: Unknown argument '$1'. Expected 'api-custom' or 'stable'."
16+
exit 1
17+
fi
18+
919
# Restore un-reloaded files on exit (for local testing).
1020
cleanedUp=0 # avoid recursion if cleanup fails
1121
godotPid=0 # kill previous instance if necessary
@@ -36,7 +46,6 @@ cp editor_layout.cfg $rel/.godot/editor/editor_layout.cfg
3646
#cp MainScene.tscn $rel/MainScene.tscn
3747

3848
# Compile original Rust source.
39-
cargoArgs=""
4049
#cargoArgs="--features godot/__debug-log"
4150
cargo build -p hot-reload $cargoArgs
4251

itest/hot-reload/rust/src/lib.rs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,9 @@ struct Reloadable {
2828
#[export]
2929
#[init(val = Planet::Earth)]
3030
favorite_planet: Planet,
31+
32+
#[init(val = NoDefault::obtain())]
33+
_other_object: Gd<NoDefault>,
3134
}
3235

3336
#[godot_api]
@@ -41,10 +44,24 @@ impl Reloadable {
4144
fn from_string(s: GString) -> Gd<Self> {
4245
Gd::from_object(Reloadable {
4346
favorite_planet: Planet::from_godot(s),
47+
_other_object: NoDefault::obtain(),
4448
})
4549
}
4650
}
4751

52+
// no_init reloadability - https://github.com/godot-rust/gdext/issues/874.
53+
#[derive(GodotClass)]
54+
#[class(no_init, base=Node)]
55+
struct NoDefault {}
56+
57+
#[godot_api]
58+
impl NoDefault {
59+
#[func]
60+
fn obtain() -> Gd<Self> {
61+
Gd::from_object(NoDefault {})
62+
}
63+
}
64+
4865
// ----------------------------------------------------------------------------------------------------------------------------------------------
4966

5067
#[derive(GodotConvert, Var, Export)]

0 commit comments

Comments
 (0)