Skip to content

Commit 9cd0690

Browse files
committed
proof of concept hot reload testing
1 parent 5910c0e commit 9cd0690

6 files changed

Lines changed: 103 additions & 17 deletions

File tree

test/project/example.gdextension

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
entry_symbol = "example_library_init"
44
compatibility_minimum = "4.1"
5+
reloadable = true
56

67
[libraries]
78

test/project/reload.gd

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
@tool
2+
extends Example
3+
4+
func _notification( what : int ):
5+
if what == NOTIFICATION_EXTENSION_RELOADED:
6+
print("gdscript: NOTIFICATION_EXTENSION_RELOADED")
7+
8+
func _ready() -> void:
9+
custom_signal.connect(func( msg, value) -> void:
10+
print("GSExtension Reloaded : %s, %d" % [msg, value])
11+
get_tree().quit(0))
12+
await get_tree().create_timer(5).timeout
13+
print("Timed Out.")
14+
get_tree().quit(1)

test/project/reload.gd.uid

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
uid://c8v75k3p4pnjj

test/project/reload.tscn

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
[gd_scene load_steps=2 format=3 uid="uid://b4wrd36npp8ov"]
2+
3+
[ext_resource type="Script" uid="uid://cfsxgbu2p7sl3" path="res://reload.gd" id="1_icw2l"]
4+
5+
[node name="Reload" type="Example"]
6+
script = ExtResource("1_icw2l")

test/src/example.cpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,9 @@ void Example::_notification(int p_what) {
114114
rpc_config("test_rpc", opts);
115115
}
116116
//UtilityFunctions::print("Notification: ", String::num(p_what));
117+
if (p_what == NOTIFICATION_EXTENSION_RELOADED) {
118+
emit_custom_signal("NOTIFICATION_EXTENSION_RELOADED ", 0);
119+
}
117120
}
118121

119122
bool Example::_set(const StringName &p_name, const Variant &p_value) {

test/src/register_types.cpp

Lines changed: 78 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -12,38 +12,99 @@
1212
#include <godot_cpp/godot.hpp>
1313

1414
#include "example.h"
15+
#include "godot_cpp/classes/dir_access.hpp"
16+
#include "godot_cpp/classes/editor_interface.hpp"
17+
#include "godot_cpp/classes/engine.hpp"
18+
#include "godot_cpp/classes/file_access.hpp"
19+
#include "godot_cpp/classes/gd_extension_manager.hpp"
1520
#include "tests.h"
1621

1722
using namespace godot;
1823

19-
void initialize_example_module(ModuleInitializationLevel p_level) {
20-
if (p_level != MODULE_INITIALIZATION_LEVEL_SCENE) {
21-
return;
24+
// Global flag to control the timer thread
25+
std::atomic keep_running(true);
26+
27+
// Function to simulate extension reload
28+
void trigger_extension_reload() {
29+
if (Engine::get_singleton()->is_editor_hint()) {
30+
if (const EditorInterface *ei = EditorInterface::get_singleton(); ei->get_edited_scene_root()) {
31+
UtilityFunctions::print("Editor has a loaded scene: ", ei->get_edited_scene_root()->get_name());
32+
} else {
33+
return;
34+
}
35+
}
36+
37+
keep_running = false;
38+
const auto gdextension_path = String{ "res://example.gdextension" };
39+
UtilityFunctions::print("Simulating GDExtension reload...");
40+
41+
// Reload all extensions
42+
if (GDExtensionManager *ext_manager = GDExtensionManager::get_singleton()) {
43+
ext_manager->call_deferred("reload_extension", gdextension_path);
44+
UtilityFunctions::print("GDExtensionManager::reload_extension(...) called successfully.");
45+
} else {
46+
UtilityFunctions::print("Error: GDExtensionManager singleton not available.");
47+
}
48+
}
49+
50+
// Timer thread function
51+
void timer_thread_function(const float interval_seconds) {
52+
// Single cycle for testing: sleep once, trigger reload, then exit (keep_running=false in trigger).
53+
while (keep_running) {
54+
std::this_thread::sleep_for(std::chrono::seconds(static_cast<int>(interval_seconds)));
55+
trigger_extension_reload();
2256
}
57+
}
2358

24-
GDREGISTER_CLASS(ExampleRef);
25-
GDREGISTER_CLASS(ExampleMin);
26-
GDREGISTER_CLASS(Example);
27-
GDREGISTER_VIRTUAL_CLASS(ExampleVirtual);
28-
GDREGISTER_ABSTRACT_CLASS(ExampleAbstractBase);
29-
GDREGISTER_CLASS(ExampleConcrete);
30-
GDREGISTER_CLASS(ExampleBase);
31-
GDREGISTER_CLASS(ExampleChild);
32-
GDREGISTER_RUNTIME_CLASS(ExampleRuntime);
33-
GDREGISTER_CLASS(ExamplePrzykład);
34-
GDREGISTER_INTERNAL_CLASS(ExampleInternal);
59+
void initialize_example_module(ModuleInitializationLevel p_level) {
60+
if (p_level == MODULE_INITIALIZATION_LEVEL_SCENE) {
61+
UtilityFunctions::print("Initializing Integration Testing Extension");
62+
GDREGISTER_CLASS(ExampleRef);
63+
GDREGISTER_CLASS(ExampleMin);
64+
GDREGISTER_CLASS(Example);
65+
GDREGISTER_VIRTUAL_CLASS(ExampleVirtual);
66+
GDREGISTER_ABSTRACT_CLASS(ExampleAbstractBase);
67+
GDREGISTER_CLASS(ExampleConcrete);
68+
GDREGISTER_CLASS(ExampleBase);
69+
GDREGISTER_CLASS(ExampleChild);
70+
GDREGISTER_RUNTIME_CLASS(ExampleRuntime);
71+
GDREGISTER_CLASS(ExamplePrzykład);
72+
GDREGISTER_INTERNAL_CLASS(ExampleInternal);
73+
} else if (p_level == MODULE_INITIALIZATION_LEVEL_EDITOR) {
74+
const String lock_path = "user://extension_timer_started";
75+
for (const auto &arg : OS::get_singleton()->get_cmdline_args()) {
76+
if (arg.begins_with("reload") && !FileAccess::file_exists(lock_path)) {
77+
constexpr float reload_interval = 3.0f;
78+
UtilityFunctions::print("No lock file found; starting timer thread for hot-reload test.");
79+
std::thread timer_thread(timer_thread_function, reload_interval);
80+
timer_thread.detach(); // Detach the thread to run independently
81+
82+
// Create the lock file to prevent future inits from starting duplicates.
83+
if (const Ref<FileAccess> lock_file = FileAccess::open(lock_path, FileAccess::WRITE); lock_file.is_valid()) {
84+
lock_file->close();
85+
} else {
86+
UtilityFunctions::print("Warning: Failed to create lock file.");
87+
}
88+
} else {
89+
UtilityFunctions::print("Lock file exists; skipping timer thread start (already triggered reload).");
90+
}
91+
}
92+
}
3593
}
3694

3795
void uninitialize_example_module(ModuleInitializationLevel p_level) {
38-
if (p_level != MODULE_INITIALIZATION_LEVEL_SCENE) {
39-
return;
96+
if (p_level == MODULE_INITIALIZATION_LEVEL_SCENE) {
97+
UtilityFunctions::print("Uninitializing Integration Testing Extension");
98+
} else if (p_level == MODULE_INITIALIZATION_LEVEL_EDITOR) {
99+
// Stop the timer thread when deinitializing
100+
keep_running = false;
40101
}
41102
}
42103

43104
extern "C" {
44105
// Initialization.
45106
GDExtensionBool GDE_EXPORT example_library_init(GDExtensionInterfaceGetProcAddress p_get_proc_address, GDExtensionClassLibraryPtr p_library, GDExtensionInitialization *r_initialization) {
46-
godot::GDExtensionBinding::InitObject init_obj(p_get_proc_address, p_library, r_initialization);
107+
GDExtensionBinding::InitObject init_obj(p_get_proc_address, p_library, r_initialization);
47108

48109
init_obj.register_initializer(initialize_example_module);
49110
init_obj.register_terminator(uninitialize_example_module);

0 commit comments

Comments
 (0)