diff --git a/godot-core/src/meta/class_id.rs b/godot-core/src/meta/class_id.rs index d8df04fab..efaca595e 100644 --- a/godot-core/src/meta/class_id.rs +++ b/godot-core/src/meta/class_id.rs @@ -309,8 +309,17 @@ impl ClassIdCache { } fn clear(&mut self) { - self.entries.clear(); + // There are two types of hot reload: + // - dylib reload (dylib mtime newer): the old dylib is unloaded and the new one is loaded + // - .gdextension reload (.gdextension mtime newer): the existing dylib is re-initialized without unloading + // + // .gdextension reload keeps existing ClassIds alive with each id being an index into entries. + // To account for this, we preserve the backing data for existing entries, but drop the cached Godot + // StringNames and the TypeId lookup so they can be rebuilt. + for entry in &mut self.entries { + entry.godot_str = OnceCell::new(); + } + self.type_to_index.clear(); - self.string_to_index.clear(); } } diff --git a/godot-ffi/src/lib.rs b/godot-ffi/src/lib.rs index a1e2e4156..fdd87509d 100644 --- a/godot-ffi/src/lib.rs +++ b/godot-ffi/src/lib.rs @@ -284,7 +284,15 @@ pub unsafe fn initialize( /// # Safety /// See [`initialize`]. pub unsafe fn deinitialize() { - deinitialize_binding() + deinitialize_binding(); + + // Clear the main thread ID to allow re-initialization during hot reload. + #[cfg(not(wasm_nothreads))] + { + if MAIN_THREAD_ID.is_initialized() { + MAIN_THREAD_ID.clear(); + } + } } fn print_preamble(version: GDExtensionGodotVersion) { diff --git a/itest/hot-reload/godot/run-test.sh b/itest/hot-reload/godot/run-test.sh index 828f8e583..58b171979 100755 --- a/itest/hot-reload/godot/run-test.sh +++ b/itest/hot-reload/godot/run-test.sh @@ -54,6 +54,11 @@ cargo build -p hot-reload $cargoArgs # Wait briefly so artifacts are present on file system. sleep 0.5 +# ---------------------------------------------------------------- +# Test Case 1: Update Rust source and compile to trigger reload. +# ---------------------------------------------------------------- + +echo "[Bash] Scenario 1: Reload after updating Rust source..." $GODOT4_BIN -e --headless --path $rel & godotPid=$! echo "[Bash] Wait for Godot ready (PID $godotPid)..." @@ -70,6 +75,32 @@ echo "[Bash] Wait for Godot exit..." wait $godotPid status=$? echo "[Bash] Godot (PID $godotPid) has completed with status $status." +if [[ $status -ne 0 ]]; then + exit $status +fi + +# ---------------------------------------------------------------- +# Test Case 2: Touch the .gdextension file to trigger reload. +# ---------------------------------------------------------------- +godotPid=0 + +echo "[Bash] Scenario 2: Reload after touching rust.gdextension..." +$GODOT4_BIN -e --headless --path $rel & +godotPid=$! +echo "[Bash] Wait for Godot ready (PID $godotPid)..." +$GODOT4_BIN --headless --no-header --script ReloadOrchestrator.gd -- await + +# update timestamp to trigger reload +touch "$rel/rust.gdextension" +$GODOT4_BIN --headless --no-header --script ReloadOrchestrator.gd -- notify +echo "[Bash] Wait for Godot exit (dirty scenario)..." +wait $godotPid +status=$? +echo "[Bash] Godot (PID $godotPid) has completed with status $status." +if [[ $status -ne 0 ]]; then + exit $status +fi +godotPid=0