GDScript: Cancel suspended functions when reloading a script

This commit is contained in:
HolonProduction 2025-02-07 10:32:05 +01:00
parent 134da37497
commit 676e4c9013
7 changed files with 51 additions and 15 deletions

View file

@ -1625,6 +1625,27 @@ void GDScript::clear(ClearData *p_clear_data) {
}
}
void GDScript::cancel_pending_functions(bool warn) {
MutexLock lock(GDScriptLanguage::get_singleton()->mutex);
while (SelfList<GDScriptFunctionState> *E = pending_func_states.first()) {
// Order matters since clearing the stack may already cause
// the GDScriptFunctionState to be destroyed and thus removed from the list.
pending_func_states.remove(E);
GDScriptFunctionState *state = E->self();
#ifdef DEBUG_ENABLED
if (warn) {
WARN_PRINT("Canceling suspended execution of \"" + state->get_readable_function() + "\" due to a script reload.");
}
#endif
ObjectID state_id = state->get_instance_id();
state->_clear_connections();
if (ObjectDB::get_instance(state_id)) {
state->_clear_stack();
}
}
}
GDScript::~GDScript() {
if (destructing) {
return;
@ -1640,21 +1661,7 @@ GDScript::~GDScript() {
clear();
{
MutexLock lock(GDScriptLanguage::get_singleton()->mutex);
while (SelfList<GDScriptFunctionState> *E = pending_func_states.first()) {
// Order matters since clearing the stack may already cause
// the GDScriptFunctionState to be destroyed and thus removed from the list.
pending_func_states.remove(E);
GDScriptFunctionState *state = E->self();
ObjectID state_id = state->get_instance_id();
state->_clear_connections();
if (ObjectDB::get_instance(state_id)) {
state->_clear_stack();
}
}
}
cancel_pending_functions(false);
{
MutexLock lock(GDScriptLanguage::get_singleton()->mutex);

View file

@ -244,6 +244,9 @@ public:
void clear(GDScript::ClearData *p_clear_data = nullptr);
// Cancels all functions of the script that are are waiting to be resumed after using await.
void cancel_pending_functions(bool warn);
virtual bool is_valid() const override { return valid; }
virtual bool is_abstract() const override { return false; } // GDScript does not support abstract classes.

View file

@ -2660,6 +2660,8 @@ Error GDScriptCompiler::_prepare_compilation(GDScript *p_script, const GDScriptP
p_script->clearing = true;
p_script->cancel_pending_functions(true);
p_script->native = Ref<GDScriptNativeClass>();
p_script->base = Ref<GDScript>();
p_script->_base = nullptr;

View file

@ -616,6 +616,13 @@ public:
bool is_valid(bool p_extended_check = false) const;
Variant resume(const Variant &p_arg = Variant());
#ifdef DEBUG_ENABLED
// Returns a human-readable representation of the function.
String get_readable_function() {
return state.function_name;
}
#endif
void _clear_stack();
void _clear_connections();

View file

@ -0,0 +1,12 @@
# TODO: This test is currently disabled since it triggers some complex memory leaks. Try enabling it again once GH-101830 is fixed.
signal finished
const scr: GDScript = preload("reload_suspended_function_helper.notest.gd")
func test():
@warning_ignore("UNSAFE_METHOD_ACCESS")
scr.test(self)
@warning_ignore("RETURN_VALUE_DISCARDED")
scr.reload(true)
finished.emit()

View file

@ -0,0 +1,2 @@
GDTEST_RUNTIME_ERROR
>> WARNING: Canceling suspended execution of "test" due to a script reload.

View file

@ -0,0 +1,3 @@
static func test(a):
await a.finished
pass