summaryrefslogtreecommitdiff
path: root/modules/gdscript/gdscript.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'modules/gdscript/gdscript.cpp')
-rw-r--r--modules/gdscript/gdscript.cpp817
1 files changed, 527 insertions, 290 deletions
diff --git a/modules/gdscript/gdscript.cpp b/modules/gdscript/gdscript.cpp
index b4da9c1224..fe79f37454 100644
--- a/modules/gdscript/gdscript.cpp
+++ b/modules/gdscript/gdscript.cpp
@@ -1,32 +1,32 @@
-/*************************************************************************/
-/* gdscript.cpp */
-/*************************************************************************/
-/* This file is part of: */
-/* GODOT ENGINE */
-/* https://godotengine.org */
-/*************************************************************************/
-/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */
-/* */
-/* Permission is hereby granted, free of charge, to any person obtaining */
-/* a copy of this software and associated documentation files (the */
-/* "Software"), to deal in the Software without restriction, including */
-/* without limitation the rights to use, copy, modify, merge, publish, */
-/* distribute, sublicense, and/or sell copies of the Software, and to */
-/* permit persons to whom the Software is furnished to do so, subject to */
-/* the following conditions: */
-/* */
-/* The above copyright notice and this permission notice shall be */
-/* included in all copies or substantial portions of the Software. */
-/* */
-/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
-/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
-/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
-/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
-/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
-/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
-/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
-/*************************************************************************/
+/**************************************************************************/
+/* gdscript.cpp */
+/**************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/**************************************************************************/
+/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/**************************************************************************/
#include "gdscript.h"
@@ -98,7 +98,7 @@ Variant GDScriptNativeClass::callp(const StringName &p_method, const Variant **p
return Object::callp(p_method, p_args, p_argcount, r_error);
}
MethodBind *method = ClassDB::get_method(name, p_method);
- if (method) {
+ if (method && method->is_static()) {
// Native static method.
return method->call(nullptr, p_args, p_argcount, r_error);
}
@@ -248,6 +248,10 @@ Ref<Script> GDScript::get_base_script() const {
}
}
+StringName GDScript::get_global_name() const {
+ return name;
+}
+
StringName GDScript::get_instance_base_type() const {
if (native.is_valid()) {
return native->get_name();
@@ -478,7 +482,7 @@ void GDScript::_clear_doc() {
void GDScript::_update_doc() {
_clear_doc();
- doc.script_path = "\"" + get_path().get_slice("://", 1) + "\"";
+ doc.script_path = vformat(R"("%s")", get_script_path().get_slice("://", 1));
if (!name.is_empty()) {
doc.name = name;
} else {
@@ -674,36 +678,14 @@ bool GDScript::_update_exports(bool *r_err, bool p_recursive_call, PlaceHolderSc
base_cache = Ref<GDScript>();
}
- if (c->extends_used) {
- String ext_path = "";
- if (String(c->extends_path) != "" && String(c->extends_path) != get_path()) {
- ext_path = c->extends_path;
- if (ext_path.is_relative_path()) {
- String base_path = get_path();
- if (base_path.is_empty() || base_path.is_relative_path()) {
- ERR_PRINT(("Could not resolve relative path for parent class: " + ext_path).utf8().get_data());
- } else {
- ext_path = base_path.get_base_dir().path_join(ext_path);
- }
- }
- } else if (c->extends.size() != 0) {
- const StringName &base_class = c->extends[0];
-
- if (ScriptServer::is_global_class(base_class)) {
- ext_path = ScriptServer::get_global_class_path(base_class);
- }
- }
-
- if (!ext_path.is_empty()) {
- if (ext_path != get_path()) {
- Ref<GDScript> bf = ResourceLoader::load(ext_path);
-
- if (bf.is_valid()) {
- base_cache = bf;
- bf->inheriters_cache.insert(get_instance_id());
- }
- } else {
- ERR_PRINT(("Path extending itself in " + ext_path).utf8().get_data());
+ GDScriptParser::DataType base_type = parser.get_tree()->base_type;
+ if (base_type.kind == GDScriptParser::DataType::CLASS) {
+ Ref<GDScript> bf = GDScriptCache::get_full_script(base_type.script_path, err, path);
+ if (err == OK) {
+ bf = Ref<GDScript>(bf->find_class(base_type.class_type->fqcn));
+ if (bf.is_valid()) {
+ base_cache = bf;
+ bf->inheriters_cache.insert(get_instance_id());
}
}
}
@@ -724,10 +706,7 @@ bool GDScript::_update_exports(bool *r_err, bool p_recursive_call, PlaceHolderSc
}
members_cache.push_back(member.variable->export_info);
- Variant default_value;
- if (member.variable->initializer && member.variable->initializer->is_constant) {
- default_value = member.variable->initializer->reduced_value;
- }
+ Variant default_value = analyzer.make_variable_default_value(member.variable);
member_default_values_cache[member.variable->identifier->name] = default_value;
} break;
case GDScriptParser::ClassNode::Member::SIGNAL: {
@@ -825,22 +804,20 @@ void GDScript::update_exports() {
#endif
}
-void GDScript::_set_subclass_path(Ref<GDScript> &p_sc, const String &p_path) {
- p_sc->path = p_path;
- for (KeyValue<StringName, Ref<GDScript>> &E : p_sc->subclasses) {
- _set_subclass_path(E.value, p_path);
- }
-}
-
String GDScript::_get_debug_path() const {
if (is_built_in() && !get_name().is_empty()) {
- return get_name() + " (" + get_path() + ")";
+ return vformat("%s(%s)", get_name(), get_script_path());
} else {
- return get_path();
+ return get_script_path();
}
}
Error GDScript::reload(bool p_keep_state) {
+ if (reloading) {
+ return OK;
+ }
+ reloading = true;
+
bool has_instances;
{
MutexLock lock(GDScriptLanguage::singleton->mutex);
@@ -860,9 +837,10 @@ Error GDScript::reload(bool p_keep_state) {
basedir = basedir.get_base_dir();
}
-// Loading a template, don't parse.
+ // Loading a template, don't parse.
#ifdef TOOLS_ENABLED
if (EditorPaths::get_singleton() && basedir.begins_with(EditorPaths::get_singleton()->get_project_script_templates_dir())) {
+ reloading = false;
return OK;
}
#endif
@@ -872,11 +850,10 @@ Error GDScript::reload(bool p_keep_state) {
if (source_path.is_empty()) {
source_path = get_path();
}
- if (!source_path.is_empty()) {
- MutexLock lock(GDScriptCache::singleton->lock);
- if (!GDScriptCache::singleton->shallow_gdscript_cache.has(source_path)) {
- GDScriptCache::singleton->shallow_gdscript_cache[source_path] = this;
- }
+ Ref<GDScript> cached_script = GDScriptCache::get_cached_script(source_path);
+ if (!source_path.is_empty() && cached_script.is_null()) {
+ MutexLock lock(GDScriptCache::singleton->mutex);
+ GDScriptCache::singleton->shallow_gdscript_cache[source_path] = Ref<GDScript>(this);
}
}
@@ -889,6 +866,7 @@ Error GDScript::reload(bool p_keep_state) {
}
// TODO: Show all error messages.
_err_print_error("GDScript::reload", path.is_empty() ? "built-in" : (const char *)path.utf8().get_data(), parser.get_errors().front()->get().line, ("Parse Error: " + parser.get_errors().front()->get().message).utf8().get_data(), false, ERR_HANDLER_SCRIPT);
+ reloading = false;
return ERR_PARSE_ERROR;
}
@@ -905,6 +883,7 @@ Error GDScript::reload(bool p_keep_state) {
_err_print_error("GDScript::reload", path.is_empty() ? "built-in" : (const char *)path.utf8().get_data(), e->get().line, ("Parse Error: " + e->get().message).utf8().get_data(), false, ERR_HANDLER_SCRIPT);
e = e->next();
}
+ reloading = false;
return ERR_PARSE_ERROR;
}
@@ -913,18 +892,16 @@ Error GDScript::reload(bool p_keep_state) {
GDScriptCompiler compiler;
err = compiler.compile(&parser, this, p_keep_state);
-#ifdef TOOLS_ENABLED
- _update_doc();
-#endif
-
if (err) {
if (can_run) {
if (EngineDebugger::is_active()) {
GDScriptLanguage::get_singleton()->debug_break_parse(_get_debug_path(), compiler.get_error_line(), "Parser Error: " + compiler.get_error());
}
_err_print_error("GDScript::reload", path.is_empty() ? "built-in" : (const char *)path.utf8().get_data(), compiler.get_error_line(), ("Compile Error: " + compiler.get_error()).utf8().get_data(), false, ERR_HANDLER_SCRIPT);
+ reloading = false;
return ERR_COMPILATION_FAILED;
} else {
+ reloading = false;
return err;
}
}
@@ -932,19 +909,12 @@ Error GDScript::reload(bool p_keep_state) {
for (const GDScriptWarning &warning : parser.get_warnings()) {
if (EngineDebugger::is_active()) {
Vector<ScriptLanguage::StackInfo> si;
- EngineDebugger::get_script_debugger()->send_error("", get_path(), warning.start_line, warning.get_name(), warning.get_message(), false, ERR_HANDLER_WARNING, si);
+ EngineDebugger::get_script_debugger()->send_error("", get_script_path(), warning.start_line, warning.get_name(), warning.get_message(), false, ERR_HANDLER_WARNING, si);
}
}
#endif
- valid = true;
-
- for (KeyValue<StringName, Ref<GDScript>> &E : subclasses) {
- _set_subclass_path(E.value, path);
- }
-
- _init_rpc_methods_properties();
-
+ reloading = false;
return OK;
}
@@ -1037,25 +1007,29 @@ void GDScript::_get_property_list(List<PropertyInfo> *p_properties) const {
void GDScript::_bind_methods() {
ClassDB::bind_vararg_method(METHOD_FLAGS_DEFAULT, "new", &GDScript::_new, MethodInfo("new"));
-
- ClassDB::bind_method(D_METHOD("get_as_byte_code"), &GDScript::get_as_byte_code);
-}
-
-Vector<uint8_t> GDScript::get_as_byte_code() const {
- return Vector<uint8_t>();
-};
-
-// TODO: Fully remove this. There's not this kind of "bytecode" anymore.
-Error GDScript::load_byte_code(const String &p_path) {
- return ERR_COMPILATION_FAILED;
}
void GDScript::set_path(const String &p_path, bool p_take_over) {
- Script::set_path(p_path, p_take_over);
+ String old_path = path;
+ if (is_root_script()) {
+ Script::set_path(p_path, p_take_over);
+ }
this->path = p_path;
+ GDScriptCache::move_script(old_path, p_path);
+ for (KeyValue<StringName, Ref<GDScript>> &kv : subclasses) {
+ kv.value->set_path(p_path, p_take_over);
+ }
+}
+
+String GDScript::get_script_path() const {
+ return path;
}
Error GDScript::load_source_code(const String &p_path) {
+ if (p_path.is_empty() || p_path.begins_with("gdscript://") || ResourceLoader::get_resource_type(p_path.get_slice("::", 0)) == "PackedScene") {
+ return OK;
+ }
+
Vector<uint8_t> sourcef;
Error err;
Ref<FileAccess> f = FileAccess::open(p_path, FileAccess::READ, &err);
@@ -1127,6 +1101,131 @@ bool GDScript::inherits_script(const Ref<Script> &p_script) const {
return false;
}
+GDScript *GDScript::find_class(const String &p_qualified_name) {
+ String first = p_qualified_name.get_slice("::", 0);
+
+ Vector<String> class_names;
+ GDScript *result = nullptr;
+ // Empty initial name means start here.
+ if (first.is_empty() || first == name) {
+ class_names = p_qualified_name.split("::");
+ result = this;
+ } else if (p_qualified_name.begins_with(get_root_script()->path)) {
+ // Script path could have a class path separator("::") in it.
+ class_names = p_qualified_name.trim_prefix(get_root_script()->path).split("::");
+ result = get_root_script();
+ } else if (HashMap<StringName, Ref<GDScript>>::Iterator E = subclasses.find(first)) {
+ class_names = p_qualified_name.split("::");
+ result = E->value.ptr();
+ } else if (_owner != nullptr) {
+ // Check parent scope.
+ return _owner->find_class(p_qualified_name);
+ }
+
+ // Starts at index 1 because index 0 was handled above.
+ for (int i = 1; result != nullptr && i < class_names.size(); i++) {
+ String current_name = class_names[i];
+ if (HashMap<StringName, Ref<GDScript>>::Iterator E = result->subclasses.find(current_name)) {
+ result = E->value.ptr();
+ } else {
+ // Couldn't find inner class.
+ return nullptr;
+ }
+ }
+
+ return result;
+}
+
+bool GDScript::has_class(const GDScript *p_script) {
+ String fqn = p_script->fully_qualified_name;
+ if (fully_qualified_name.is_empty() && fqn.get_slice("::", 0).is_empty()) {
+ return p_script == this;
+ } else if (fqn.begins_with(fully_qualified_name)) {
+ return p_script == find_class(fqn.trim_prefix(fully_qualified_name));
+ }
+ return false;
+}
+
+GDScript *GDScript::get_root_script() {
+ GDScript *result = this;
+ while (result->_owner) {
+ result = result->_owner;
+ }
+ return result;
+}
+
+RBSet<GDScript *> GDScript::get_dependencies() {
+ RBSet<GDScript *> dependencies;
+
+ _get_dependencies(dependencies, this);
+ dependencies.erase(this);
+
+ return dependencies;
+}
+
+RBSet<GDScript *> GDScript::get_inverted_dependencies() {
+ RBSet<GDScript *> inverted_dependencies;
+
+ List<GDScript *> scripts;
+ {
+ MutexLock lock(GDScriptLanguage::singleton->mutex);
+
+ SelfList<GDScript> *elem = GDScriptLanguage::singleton->script_list.first();
+ while (elem) {
+ scripts.push_back(elem->self());
+ elem = elem->next();
+ }
+ }
+
+ for (GDScript *scr : scripts) {
+ if (scr == nullptr || scr == this || scr->destructing) {
+ continue;
+ }
+
+ RBSet<GDScript *> scr_dependencies = scr->get_dependencies();
+ if (scr_dependencies.has(this)) {
+ inverted_dependencies.insert(scr);
+ }
+ }
+
+ return inverted_dependencies;
+}
+
+RBSet<GDScript *> GDScript::get_must_clear_dependencies() {
+ RBSet<GDScript *> dependencies = get_dependencies();
+ RBSet<GDScript *> must_clear_dependencies;
+ HashMap<GDScript *, RBSet<GDScript *>> inverted_dependencies;
+
+ for (GDScript *E : dependencies) {
+ inverted_dependencies.insert(E, E->get_inverted_dependencies());
+ }
+
+ RBSet<GDScript *> cant_clear;
+ for (KeyValue<GDScript *, RBSet<GDScript *>> &E : inverted_dependencies) {
+ for (GDScript *F : E.value) {
+ if (!dependencies.has(F)) {
+ cant_clear.insert(E.key);
+ for (GDScript *G : E.key->get_dependencies()) {
+ cant_clear.insert(G);
+ }
+ break;
+ }
+ }
+ }
+
+ for (KeyValue<GDScript *, RBSet<GDScript *>> &E : inverted_dependencies) {
+ if (cant_clear.has(E.key) || ScriptServer::is_global_class(E.key->get_fully_qualified_name())) {
+ continue;
+ }
+ must_clear_dependencies.insert(E.key);
+ }
+
+ cant_clear.clear();
+ dependencies.clear();
+ inverted_dependencies.clear();
+ return must_clear_dependencies;
+}
+
bool GDScript::has_script_signal(const StringName &p_signal) const {
if (_signals.has(p_signal)) {
return true;
@@ -1188,18 +1287,76 @@ String GDScript::_get_gdscript_reference_class_name(const GDScript *p_gdscript)
return class_name;
}
+GDScript *GDScript::_get_gdscript_from_variant(const Variant &p_variant) {
+ Object *obj = p_variant;
+ if (obj == nullptr || obj->get_instance_id().is_null()) {
+ return nullptr;
+ }
+ return Object::cast_to<GDScript>(obj);
+}
+
+void GDScript::_get_dependencies(RBSet<GDScript *> &p_dependencies, const GDScript *p_except) {
+ if (p_dependencies.has(this)) {
+ return;
+ }
+ p_dependencies.insert(this);
+
+ for (const KeyValue<StringName, GDScriptFunction *> &E : member_functions) {
+ if (E.value == nullptr) {
+ continue;
+ }
+ for (const Variant &V : E.value->constants) {
+ GDScript *scr = _get_gdscript_from_variant(V);
+ if (scr != nullptr && scr != p_except) {
+ scr->_get_dependencies(p_dependencies, p_except);
+ }
+ }
+ }
+
+ if (implicit_initializer) {
+ for (const Variant &V : implicit_initializer->constants) {
+ GDScript *scr = _get_gdscript_from_variant(V);
+ if (scr != nullptr && scr != p_except) {
+ scr->_get_dependencies(p_dependencies, p_except);
+ }
+ }
+ }
+
+ if (implicit_ready) {
+ for (const Variant &V : implicit_ready->constants) {
+ GDScript *scr = _get_gdscript_from_variant(V);
+ if (scr != nullptr && scr != p_except) {
+ scr->_get_dependencies(p_dependencies, p_except);
+ }
+ }
+ }
+
+ for (KeyValue<StringName, Ref<GDScript>> &E : subclasses) {
+ if (E.value != p_except) {
+ E.value->_get_dependencies(p_dependencies, p_except);
+ }
+ }
+
+ for (const KeyValue<StringName, Variant> &E : constants) {
+ GDScript *scr = _get_gdscript_from_variant(E.value);
+ if (scr != nullptr && scr != p_except) {
+ scr->_get_dependencies(p_dependencies, p_except);
+ }
+ }
+}
+
GDScript::GDScript() :
script_list(this) {
-#ifdef DEBUG_ENABLED
{
MutexLock lock(GDScriptLanguage::get_singleton()->mutex);
GDScriptLanguage::get_singleton()->script_list.add(&script_list);
}
-#endif
+
+ path = vformat("gdscript://%d.gd", get_instance_id());
}
-void GDScript::_save_orphaned_subclasses() {
+void GDScript::_save_orphaned_subclasses(GDScript::ClearData *p_clear_data) {
struct ClassRefWithName {
ObjectID id;
String fully_qualified_name;
@@ -1215,8 +1372,17 @@ void GDScript::_save_orphaned_subclasses() {
}
// clear subclasses to allow unused subclasses to be deleted
+ for (KeyValue<StringName, Ref<GDScript>> &E : subclasses) {
+ p_clear_data->scripts.insert(E.value);
+ }
subclasses.clear();
// subclasses are also held by constants, clear those as well
+ for (KeyValue<StringName, Variant> &E : constants) {
+ GDScript *gdscr = _get_gdscript_from_variant(E.value);
+ if (gdscr != nullptr) {
+ p_clear_data->scripts.insert(gdscr);
+ }
+ }
constants.clear();
// keep orphan subclass only for subclasses that are still in use
@@ -1238,58 +1404,59 @@ void GDScript::_init_rpc_methods_properties() {
rpc_config = base->rpc_config.duplicate();
}
- GDScript *cscript = this;
- HashMap<StringName, Ref<GDScript>>::Iterator sub_E = subclasses.begin();
- while (cscript) {
- // RPC Methods
- for (KeyValue<StringName, GDScriptFunction *> &E : cscript->member_functions) {
- Variant config = E.value->get_rpc_config();
- if (config.get_type() != Variant::NIL) {
- rpc_config[E.value->get_name()] = config;
- }
- }
-
- if (cscript != this) {
- ++sub_E;
- }
-
- if (sub_E) {
- cscript = sub_E->value.ptr();
- } else {
- cscript = nullptr;
+ // RPC Methods
+ for (KeyValue<StringName, GDScriptFunction *> &E : member_functions) {
+ Variant config = E.value->get_rpc_config();
+ if (config.get_type() != Variant::NIL) {
+ rpc_config[E.value->get_name()] = config;
}
}
}
-GDScript::~GDScript() {
- {
- MutexLock lock(GDScriptLanguage::get_singleton()->mutex);
+void GDScript::clear(GDScript::ClearData *p_clear_data) {
+ if (clearing) {
+ return;
+ }
+ clearing = true;
- 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);
- E->self()->_clear_stack();
- }
+ GDScript::ClearData data;
+ GDScript::ClearData *clear_data = p_clear_data;
+ bool is_root = false;
+
+ // If `clear_data` is `nullptr`, it means that it's the root.
+ // The root is in charge to clear functions and scripts of itself and its dependencies
+ if (clear_data == nullptr) {
+ clear_data = &data;
+ is_root = true;
+ }
+
+ RBSet<GDScript *> must_clear_dependencies = get_must_clear_dependencies();
+ for (GDScript *E : must_clear_dependencies) {
+ clear_data->scripts.insert(E);
+ E->clear(clear_data);
}
for (const KeyValue<StringName, GDScriptFunction *> &E : member_functions) {
- memdelete(E.value);
+ clear_data->functions.insert(E.value);
}
+ member_functions.clear();
- if (implicit_initializer) {
- memdelete(implicit_initializer);
+ for (KeyValue<StringName, GDScript::MemberInfo> &E : member_indices) {
+ clear_data->scripts.insert(E.value.data_type.script_type_ref);
+ E.value.data_type.script_type_ref = Ref<Script>();
}
- if (implicit_ready) {
- memdelete(implicit_ready);
+ if (implicit_initializer) {
+ clear_data->functions.insert(implicit_initializer);
+ implicit_initializer = nullptr;
}
- if (GDScriptCache::singleton) { // Cache may have been already destroyed at engine shutdown.
- GDScriptCache::remove_script(get_path());
+ if (implicit_ready) {
+ clear_data->functions.insert(implicit_ready);
+ implicit_ready = nullptr;
}
- _save_orphaned_subclasses();
+ _save_orphaned_subclasses(clear_data);
#ifdef TOOLS_ENABLED
// Clearing inner class doc, script doc only cleared when the script source deleted.
@@ -1298,13 +1465,55 @@ GDScript::~GDScript() {
}
#endif
-#ifdef DEBUG_ENABLED
+ // If it's not the root, skip clearing the data
+ if (is_root) {
+ // All dependencies have been accounted for
+ for (GDScriptFunction *E : clear_data->functions) {
+ memdelete(E);
+ }
+ for (Ref<Script> &E : clear_data->scripts) {
+ Ref<GDScript> gdscr = E;
+ if (gdscr.is_valid()) {
+ GDScriptCache::remove_script(gdscr->get_path());
+ }
+ }
+ clear_data->clear();
+ }
+}
+
+GDScript::~GDScript() {
+ if (destructing) {
+ return;
+ }
+ destructing = true;
+
+ 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();
+ }
+ }
+ }
+
{
MutexLock lock(GDScriptLanguage::get_singleton()->mutex);
GDScriptLanguage::get_singleton()->script_list.remove(&script_list);
}
-#endif
+
+ if (GDScriptCache::singleton) { // Cache may have been already destroyed at engine shutdown.
+ GDScriptCache::remove_script(get_path());
+ }
}
//////////////////////////////
@@ -1317,41 +1526,24 @@ bool GDScriptInstance::set(const StringName &p_name, const Variant &p_value) {
HashMap<StringName, GDScript::MemberInfo>::Iterator E = script->member_indices.find(p_name);
if (E) {
const GDScript::MemberInfo *member = &E->value;
- if (member->setter) {
- const Variant *val = &p_value;
+ Variant value = p_value;
+ if (member->data_type.has_type && !member->data_type.is_type(value)) {
+ const Variant *args = &p_value;
Callable::CallError err;
- callp(member->setter, &val, 1, err);
- if (err.error == Callable::CallError::CALL_OK) {
- return true; //function exists, call was successful
- } else {
+ Variant::construct(member->data_type.builtin_type, value, &args, 1, err);
+ if (err.error != Callable::CallError::CALL_OK || !member->data_type.is_type(value)) {
return false;
}
+ }
+ if (member->setter) {
+ const Variant *args = &value;
+ Callable::CallError err;
+ callp(member->setter, &args, 1, err);
+ return err.error == Callable::CallError::CALL_OK;
} else {
- if (member->data_type.has_type) {
- if (member->data_type.builtin_type == Variant::ARRAY && member->data_type.has_container_element_type()) {
- // Typed array.
- if (p_value.get_type() == Variant::ARRAY) {
- return VariantInternal::get_array(&members.write[member->index])->typed_assign(p_value);
- } else {
- return false;
- }
- } else if (!member->data_type.is_type(p_value)) {
- // Try conversion
- Callable::CallError ce;
- const Variant *value = &p_value;
- Variant converted;
- Variant::construct(member->data_type.builtin_type, converted, &value, 1, ce);
- if (ce.error == Callable::CallError::CALL_OK) {
- members.write[member->index] = converted;
- return true;
- } else {
- return false;
- }
- }
- }
- members.write[member->index] = p_value;
+ members.write[member->index] = value;
+ return true;
}
- return true;
}
}
@@ -1733,7 +1925,12 @@ GDScriptInstance::~GDScriptInstance() {
// 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);
- E->self()->_clear_stack();
+ GDScriptFunctionState *state = E->self();
+ ObjectID state_id = state->get_instance_id();
+ state->_clear_connections();
+ if (ObjectDB::get_instance(state_id)) {
+ state->_clear_stack();
+ }
}
if (script.is_valid() && owner) {
@@ -1770,6 +1967,16 @@ void GDScriptLanguage::add_named_global_constant(const StringName &p_name, const
named_globals[p_name] = p_value;
}
+Variant GDScriptLanguage::get_any_global_constant(const StringName &p_name) {
+ if (named_globals.has(p_name)) {
+ return named_globals[p_name];
+ }
+ if (globals.has(p_name)) {
+ return _global_array[globals[p_name]];
+ }
+ ERR_FAIL_V_MSG(Variant(), vformat("Could not find any global constant with name: %s.", p_name));
+}
+
void GDScriptLanguage::remove_named_global_constant(const StringName &p_name) {
ERR_FAIL_COND(!named_globals.has(p_name));
named_globals.erase(p_name);
@@ -1826,6 +2033,42 @@ Error GDScriptLanguage::execute_file(const String &p_path) {
}
void GDScriptLanguage::finish() {
+ if (_call_stack) {
+ memdelete_arr(_call_stack);
+ _call_stack = nullptr;
+ }
+
+ // Clear the cache before parsing the script_list
+ GDScriptCache::clear();
+
+ // Clear dependencies between scripts, to ensure cyclic references are broken
+ // (to avoid leaks at exit).
+ SelfList<GDScript> *s = script_list.first();
+ while (s) {
+ // This ensures the current script is not released before we can check
+ // what's the next one in the list (we can't get the next upfront because we
+ // don't know if the reference breaking will cause it -or any other after
+ // it, for that matter- to be released so the next one is not the same as
+ // before).
+ Ref<GDScript> scr = s->self();
+ if (scr.is_valid()) {
+ for (KeyValue<StringName, GDScriptFunction *> &E : scr->member_functions) {
+ GDScriptFunction *func = E.value;
+ for (int i = 0; i < func->argument_types.size(); i++) {
+ func->argument_types.write[i].script_type_ref = Ref<Script>();
+ }
+ func->return_type.script_type_ref = Ref<Script>();
+ }
+ for (KeyValue<StringName, GDScript::MemberInfo> &E : scr->member_indices) {
+ E.value.data_type.script_type_ref = Ref<Script>();
+ }
+
+ // Clear backup for scripts that could slip out of the cyclic reference
+ // check
+ scr->clear();
+ }
+ s = s->next();
+ }
}
void GDScriptLanguage::profiling_start() {
@@ -1935,7 +2178,8 @@ void GDScriptLanguage::reload_all_scripts() {
SelfList<GDScript> *elem = script_list.first();
while (elem) {
- if (elem->self()->get_path().is_resource_file()) {
+ // Scripts will reload all subclasses, so only reload root scripts.
+ if (elem->self()->is_root_script() && elem->self()->get_path().is_resource_file()) {
print_verbose("GDScript: Found: " + elem->self()->get_path());
scripts.push_back(Ref<GDScript>(elem->self())); //cast to gdscript to avoid being erased by accident
}
@@ -1964,7 +2208,8 @@ void GDScriptLanguage::reload_tool_script(const Ref<Script> &p_script, bool p_so
SelfList<GDScript> *elem = script_list.first();
while (elem) {
- if (elem->self()->get_path().is_resource_file()) {
+ // Scripts will reload all subclasses, so only reload root scripts.
+ if (elem->self()->is_root_script() && elem->self()->get_path().is_resource_file()) {
scripts.push_back(Ref<GDScript>(elem->self())); //cast to gdscript to avoid being erased by accident
}
elem = elem->next();
@@ -2192,7 +2437,6 @@ bool GDScriptLanguage::handles_global_class_type(const String &p_type) const {
}
String GDScriptLanguage::get_global_class_name(const String &p_path, String *r_base_type, String *r_icon_path) const {
- Vector<uint8_t> sourcef;
Error err;
Ref<FileAccess> f = FileAccess::open(p_path, FileAccess::READ, &err);
if (err) {
@@ -2204,87 +2448,100 @@ String GDScriptLanguage::get_global_class_name(const String &p_path, String *r_b
GDScriptParser parser;
err = parser.parse(source, p_path, false);
- // TODO: Simplify this code by using the analyzer to get full inheritance.
- if (err == OK) {
- const GDScriptParser::ClassNode *c = parser.get_tree();
- if (r_icon_path) {
- if (c->icon_path.is_empty() || c->icon_path.is_absolute_path()) {
- *r_icon_path = c->icon_path;
- } else if (c->icon_path.is_relative_path()) {
- *r_icon_path = p_path.get_base_dir().path_join(c->icon_path).simplify_path();
- }
- }
- if (r_base_type) {
- const GDScriptParser::ClassNode *subclass = c;
- String path = p_path;
- GDScriptParser subparser;
- while (subclass) {
- if (subclass->extends_used) {
- if (!subclass->extends_path.is_empty()) {
- if (subclass->extends.size() == 0) {
- get_global_class_name(subclass->extends_path, r_base_type);
- subclass = nullptr;
- break;
- } else {
- Vector<StringName> extend_classes = subclass->extends;
+ const GDScriptParser::ClassNode *c = parser.get_tree();
+ if (!c) {
+ return String(); // No class parsed.
+ }
+
+ /* **WARNING**
+ *
+ * This function is written with the goal to be *extremely* error tolerant, as such
+ * it should meet the following requirements:
+ *
+ * - It must not rely on the analyzer (in fact, the analyzer must not be used here),
+ * because at the time global classes are parsed, the dependencies may not be present
+ * yet, hence the function will fail (which is unintended).
+ * - It must not fail even if the parsing fails, because even if the file is broken,
+ * it should attempt its best to retrieve the inheritance metadata.
+ *
+ * Before changing this function, please ask the current maintainer of EditorFileSystem.
+ */
+
+ if (r_icon_path) {
+ if (c->icon_path.is_empty() || c->icon_path.is_absolute_path()) {
+ *r_icon_path = c->icon_path.simplify_path();
+ } else if (c->icon_path.is_relative_path()) {
+ *r_icon_path = p_path.get_base_dir().path_join(c->icon_path).simplify_path();
+ }
+ }
+ if (r_base_type) {
+ const GDScriptParser::ClassNode *subclass = c;
+ String path = p_path;
+ GDScriptParser subparser;
+ while (subclass) {
+ if (subclass->extends_used) {
+ if (!subclass->extends_path.is_empty()) {
+ if (subclass->extends.size() == 0) {
+ get_global_class_name(subclass->extends_path, r_base_type);
+ subclass = nullptr;
+ break;
+ } else {
+ Vector<StringName> extend_classes = subclass->extends;
- Ref<FileAccess> subfile = FileAccess::open(subclass->extends_path, FileAccess::READ);
- if (subfile.is_null()) {
- break;
- }
- String subsource = subfile->get_as_utf8_string();
+ Ref<FileAccess> subfile = FileAccess::open(subclass->extends_path, FileAccess::READ);
+ if (subfile.is_null()) {
+ break;
+ }
+ String subsource = subfile->get_as_utf8_string();
- if (subsource.is_empty()) {
- break;
- }
- String subpath = subclass->extends_path;
- if (subpath.is_relative_path()) {
- subpath = path.get_base_dir().path_join(subpath).simplify_path();
- }
+ if (subsource.is_empty()) {
+ break;
+ }
+ String subpath = subclass->extends_path;
+ if (subpath.is_relative_path()) {
+ subpath = path.get_base_dir().path_join(subpath).simplify_path();
+ }
- if (OK != subparser.parse(subsource, subpath, false)) {
- break;
- }
- path = subpath;
- subclass = subparser.get_tree();
-
- while (extend_classes.size() > 0) {
- bool found = false;
- for (int i = 0; i < subclass->members.size(); i++) {
- if (subclass->members[i].type != GDScriptParser::ClassNode::Member::CLASS) {
- continue;
- }
-
- const GDScriptParser::ClassNode *inner_class = subclass->members[i].m_class;
- if (inner_class->identifier->name == extend_classes[0]) {
- extend_classes.remove_at(0);
- found = true;
- subclass = inner_class;
- break;
- }
+ if (OK != subparser.parse(subsource, subpath, false)) {
+ break;
+ }
+ path = subpath;
+ subclass = subparser.get_tree();
+
+ while (extend_classes.size() > 0) {
+ bool found = false;
+ for (int i = 0; i < subclass->members.size(); i++) {
+ if (subclass->members[i].type != GDScriptParser::ClassNode::Member::CLASS) {
+ continue;
}
- if (!found) {
- subclass = nullptr;
+
+ const GDScriptParser::ClassNode *inner_class = subclass->members[i].m_class;
+ if (inner_class->identifier->name == extend_classes[0]) {
+ extend_classes.remove_at(0);
+ found = true;
+ subclass = inner_class;
break;
}
}
+ if (!found) {
+ subclass = nullptr;
+ break;
+ }
}
- } else if (subclass->extends.size() == 1) {
- *r_base_type = subclass->extends[0];
- subclass = nullptr;
- } else {
- break;
}
- } else {
- *r_base_type = "RefCounted";
+ } else if (subclass->extends.size() == 1) {
+ *r_base_type = subclass->extends[0];
subclass = nullptr;
+ } else {
+ break;
}
+ } else {
+ *r_base_type = "RefCounted";
+ subclass = nullptr;
}
}
- return c->identifier != nullptr ? String(c->identifier->name) : String();
}
-
- return String();
+ return c->identifier != nullptr ? String(c->identifier->name) : String();
}
GDScriptLanguage::GDScriptLanguage() {
@@ -2306,8 +2563,7 @@ GDScriptLanguage::GDScriptLanguage() {
script_frame_time = 0;
_debug_call_stack_pos = 0;
- int dmcs = GLOBAL_DEF("debug/settings/gdscript/max_call_stack", 1024);
- ProjectSettings::get_singleton()->set_custom_property_info("debug/settings/gdscript/max_call_stack", PropertyInfo(Variant::INT, "debug/settings/gdscript/max_call_stack", PROPERTY_HINT_RANGE, "1024,4096,1,or_greater")); //minimum is 1024
+ int dmcs = GLOBAL_DEF(PropertyInfo(Variant::INT, "debug/settings/gdscript/max_call_stack", PROPERTY_HINT_RANGE, "1024,4096,1,or_greater"), 1024);
if (EngineDebugger::is_active()) {
//debugging enabled!
@@ -2322,50 +2578,17 @@ GDScriptLanguage::GDScriptLanguage() {
#ifdef DEBUG_ENABLED
GLOBAL_DEF("debug/gdscript/warnings/enable", true);
- GLOBAL_DEF("debug/gdscript/warnings/treat_warnings_as_errors", false);
GLOBAL_DEF("debug/gdscript/warnings/exclude_addons", true);
for (int i = 0; i < (int)GDScriptWarning::WARNING_MAX; i++) {
GDScriptWarning::Code code = (GDScriptWarning::Code)i;
Variant default_enabled = GDScriptWarning::get_default_value(code);
String path = GDScriptWarning::get_settings_path_from_code(code);
- GLOBAL_DEF(path, default_enabled);
-
- PropertyInfo property_info = GDScriptWarning::get_property_info(code);
- ProjectSettings::get_singleton()->set_custom_property_info(path, property_info);
+ GLOBAL_DEF(GDScriptWarning::get_property_info(code), default_enabled);
}
#endif // DEBUG_ENABLED
}
GDScriptLanguage::~GDScriptLanguage() {
- if (_call_stack) {
- memdelete_arr(_call_stack);
- }
-
- // Clear dependencies between scripts, to ensure cyclic references are broken (to avoid leaks at exit).
- SelfList<GDScript> *s = script_list.first();
- while (s) {
- GDScript *scr = s->self();
- // This ensures the current script is not released before we can check what's the next one
- // in the list (we can't get the next upfront because we don't know if the reference breaking
- // will cause it -or any other after it, for that matter- to be released so the next one
- // is not the same as before).
- scr->reference();
-
- for (KeyValue<StringName, GDScriptFunction *> &E : scr->member_functions) {
- GDScriptFunction *func = E.value;
- for (int i = 0; i < func->argument_types.size(); i++) {
- func->argument_types.write[i].script_type_ref = Ref<Script>();
- }
- func->return_type.script_type_ref = Ref<Script>();
- }
- for (KeyValue<StringName, GDScript::MemberInfo> &E : scr->member_indices) {
- E.value.data_type.script_type_ref = Ref<Script>();
- }
-
- s = s->next();
- scr->unreference();
- }
-
singleton = nullptr;
}
@@ -2387,6 +2610,26 @@ Ref<GDScript> GDScriptLanguage::get_orphan_subclass(const String &p_qualified_na
return Ref<GDScript>(Object::cast_to<GDScript>(obj));
}
+Ref<GDScript> GDScriptLanguage::get_script_by_fully_qualified_name(const String &p_name) {
+ {
+ MutexLock lock(mutex);
+
+ SelfList<GDScript> *elem = script_list.first();
+ while (elem) {
+ GDScript *scr = elem->self();
+ if (scr->fully_qualified_name == p_name) {
+ return scr;
+ }
+ elem = elem->next();
+ }
+ }
+
+ Ref<GDScript> scr;
+ scr.instantiate();
+ scr->fully_qualified_name = p_name;
+ return scr;
+}
+
/*************** RESOURCE ***************/
Ref<Resource> ResourceFormatLoaderGDScript::load(const String &p_path, const String &p_original_path, Error *r_error, bool p_use_sub_threads, float *r_progress, CacheMode p_cache_mode) {
@@ -2397,8 +2640,6 @@ Ref<Resource> ResourceFormatLoaderGDScript::load(const String &p_path, const Str
Error err;
Ref<GDScript> scr = GDScriptCache::get_full_script(p_path, err, "", p_cache_mode == CACHE_MODE_IGNORE);
- // TODO: Reintroduce binary and encrypted scripts.
-
if (scr.is_null()) {
// Don't fail loading because of parsing error.
scr.instantiate();
@@ -2413,9 +2654,6 @@ Ref<Resource> ResourceFormatLoaderGDScript::load(const String &p_path, const Str
void ResourceFormatLoaderGDScript::get_recognized_extensions(List<String> *p_extensions) const {
p_extensions->push_back("gd");
- // TODO: Reintroduce binary and encrypted scripts.
- // p_extensions->push_back("gdc");
- // p_extensions->push_back("gde");
}
bool ResourceFormatLoaderGDScript::handles_type(const String &p_type) const {
@@ -2424,8 +2662,7 @@ bool ResourceFormatLoaderGDScript::handles_type(const String &p_type) const {
String ResourceFormatLoaderGDScript::get_resource_type(const String &p_path) const {
String el = p_path.get_extension().to_lower();
- // TODO: Reintroduce binary and encrypted scripts.
- if (el == "gd" /*|| el == "gdc" || el == "gde"*/) {
+ if (el == "gd") {
return "GDScript";
}
return "";