Fix cyclic references in GDScript 2.0

This commit is contained in:
Adam Scott 2022-10-09 12:41:28 -04:00
parent e8f9cd8ac5
commit 5704055d30
20 changed files with 626 additions and 113 deletions

View file

@ -808,6 +808,11 @@ String GDScript::_get_debug_path() const {
}
Error GDScript::reload(bool p_keep_state) {
if (reloading) {
return OK;
}
reloading = true;
bool has_instances;
{
MutexLock lock(GDScriptLanguage::singleton->mutex);
@ -830,6 +835,7 @@ Error GDScript::reload(bool p_keep_state) {
// 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
@ -839,11 +845,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);
}
}
@ -856,6 +861,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;
}
@ -872,6 +878,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;
}
@ -886,8 +893,10 @@ Error GDScript::reload(bool p_keep_state) {
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;
}
}
@ -900,6 +909,7 @@ Error GDScript::reload(bool p_keep_state) {
}
#endif
reloading = false;
return OK;
}
@ -1006,16 +1016,22 @@ Error GDScript::load_byte_code(const String &p_path) {
}
void GDScript::set_path(const String &p_path, bool 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);
}
}
Error GDScript::load_source_code(const String &p_path) {
if (p_path.is_empty() || 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);
@ -1133,6 +1149,78 @@ GDScript *GDScript::get_root_script() {
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;
@ -1194,6 +1282,69 @@ String GDScript::_get_gdscript_reference_class_name(const GDScript *p_gdscript)
return class_name;
}
GDScript *GDScript::_get_gdscript_from_variant(const Variant &p_variant) {
Variant::Type type = p_variant.get_type();
if (type != Variant::Type::OBJECT)
return nullptr;
Object *obj = p_variant;
if (obj == nullptr) {
return nullptr;
}
return Object::cast_to<GDScript>(obj);
}
void GDScript::_get_dependencies(RBSet<GDScript *> &p_dependencies, const GDScript *p_except) {
if (skip_dependencies || 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
@ -1253,33 +1404,58 @@ void GDScript::_init_rpc_methods_properties() {
}
}
GDScript::~GDScript() {
{
MutexLock lock(GDScriptLanguage::get_singleton()->mutex);
void GDScript::clear() {
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();
}
RBSet<GDScript *> must_clear_dependencies = get_must_clear_dependencies();
HashMap<GDScript *, ObjectID> must_clear_dependencies_objectids;
// Log the objectids before clearing, as a cascade of clear could
// remove instances that are still in the clear loop
for (GDScript *E : must_clear_dependencies) {
must_clear_dependencies_objectids.insert(E, E->get_instance_id());
}
for (GDScript *E : must_clear_dependencies) {
Object *obj = ObjectDB::get_instance(must_clear_dependencies_objectids[E]);
if (obj == nullptr) {
continue;
}
E->skip_dependencies = true;
E->clear();
E->skip_dependencies = false;
GDScriptCache::remove_script(E->get_path());
}
RBSet<StringName> member_function_names;
for (const KeyValue<StringName, GDScriptFunction *> &E : member_functions) {
memdelete(E.value);
member_function_names.insert(E.key);
}
for (const StringName &E : member_function_names) {
if (member_functions.has(E)) {
memdelete(member_functions[E]);
}
}
member_function_names.clear();
member_functions.clear();
for (KeyValue<StringName, GDScript::MemberInfo> &E : member_indices) {
E.value.data_type.script_type_ref = Ref<Script>();
}
if (implicit_initializer) {
memdelete(implicit_initializer);
}
implicit_initializer = nullptr;
if (implicit_ready) {
memdelete(implicit_ready);
}
if (GDScriptCache::singleton) { // Cache may have been already destroyed at engine shutdown.
GDScriptCache::remove_script(get_path());
}
implicit_ready = nullptr;
_save_orphaned_subclasses();
@ -1289,6 +1465,27 @@ GDScript::~GDScript() {
_clear_doc();
}
#endif
clearing = false;
}
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);
E->self()->_clear_stack();
}
}
#ifdef DEBUG_ENABLED
{
@ -1297,6 +1494,10 @@ GDScript::~GDScript() {
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());
}
}
//////////////////////////////
@ -2336,26 +2537,27 @@ GDScriptLanguage::~GDScriptLanguage() {
// 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>();
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>();
}
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();
scr->unreference();
}
singleton = nullptr;
@ -2379,6 +2581,27 @@ 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();
scr = scr->find_class(p_name);
if (scr != nullptr) {
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) {

View file

@ -61,6 +61,8 @@ class GDScript : public Script {
GDCLASS(GDScript, Script);
bool tool = false;
bool valid = false;
bool reloading = false;
bool skip_dependencies = false;
struct MemberInfo {
int index = 0;
@ -124,6 +126,8 @@ class GDScript : public Script {
int subclass_count = 0;
RBSet<Object *> instances;
bool destructing = false;
bool clearing = false;
//exported members
String source;
String path;
@ -163,6 +167,9 @@ class GDScript : public Script {
// This method will map the class name from "RefCounted" to "MyClass.InnerClass".
static String _get_gdscript_reference_class_name(const GDScript *p_gdscript);
GDScript *_get_gdscript_from_variant(const Variant &p_variant);
void _get_dependencies(RBSet<GDScript *> &p_dependencies, const GDScript *p_except);
protected:
bool _get(const StringName &p_name, Variant &r_ret) const;
bool _set(const StringName &p_name, const Variant &p_value);
@ -173,6 +180,8 @@ protected:
static void _bind_methods();
public:
void clear();
virtual bool is_valid() const override { return valid; }
bool inherits_script(const Ref<Script> &p_script) const override;
@ -193,6 +202,10 @@ public:
const Ref<GDScriptNativeClass> &get_native() const { return native; }
const String &get_script_class_name() const { return name; }
RBSet<GDScript *> get_dependencies();
RBSet<GDScript *> get_inverted_dependencies();
RBSet<GDScript *> get_must_clear_dependencies();
virtual bool has_script_signal(const StringName &p_signal) const override;
virtual void get_script_signal_list(List<MethodInfo> *r_signals) const override;
@ -270,6 +283,7 @@ class GDScriptInstance : public ScriptInstance {
friend class GDScriptLambdaCallable;
friend class GDScriptLambdaSelfCallable;
friend class GDScriptCompiler;
friend class GDScriptCache;
friend struct GDScriptUtilityFunctionsDefinitions;
ObjectID owner_id;
@ -518,6 +532,8 @@ public:
void add_orphan_subclass(const String &p_qualified_name, const ObjectID &p_subclass);
Ref<GDScript> get_orphan_subclass(const String &p_qualified_name);
Ref<GDScript> get_script_by_fully_qualified_name(const String &p_name);
GDScriptLanguage();
~GDScriptLanguage();
};

View file

@ -39,6 +39,7 @@
#include "core/templates/hash_map.h"
#include "gdscript.h"
#include "gdscript_utility_functions.h"
#include "scene/resources/packed_scene.h"
static MethodInfo info_from_utility_func(const StringName &p_function) {
ERR_FAIL_COND_V(!Variant::has_utility_function(p_function), MethodInfo());
@ -3103,7 +3104,7 @@ void GDScriptAnalyzer::reduce_identifier(GDScriptParser::IdentifierNode *p_ident
GDScriptParser::DataType result;
result.kind = GDScriptParser::DataType::NATIVE;
result.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT;
if (autoload.path.to_lower().ends_with(GDScriptLanguage::get_singleton()->get_extension())) {
if (ResourceLoader::get_resource_type(autoload.path) == "GDScript") {
Ref<GDScriptParserRef> singl_parser = get_parser_for(autoload.path);
if (singl_parser.is_valid()) {
Error err = singl_parser->raise_status(GDScriptParserRef::INTERFACE_SOLVED);
@ -3111,6 +3112,34 @@ void GDScriptAnalyzer::reduce_identifier(GDScriptParser::IdentifierNode *p_ident
result = type_from_metatype(singl_parser->get_parser()->head->get_datatype());
}
}
} else if (ResourceLoader::get_resource_type(autoload.path) == "PackedScene") {
Error err = OK;
Ref<PackedScene> scene = GDScriptCache::get_packed_scene(autoload.path, err);
if (err == OK && scene->get_state().is_valid()) {
Ref<SceneState> state = scene->get_state();
if (state->get_node_count() > 0) {
const int ROOT_NODE = 0;
for (int i = 0; i < state->get_node_property_count(ROOT_NODE); i++) {
if (state->get_node_property_name(ROOT_NODE, i) != SNAME("script")) {
continue;
}
Ref<GDScript> scr = state->get_node_property_value(ROOT_NODE, i);
if (scr.is_null()) {
continue;
}
Ref<GDScriptParserRef> singl_parser = get_parser_for(scr->get_path());
if (singl_parser.is_valid()) {
err = singl_parser->raise_status(GDScriptParserRef::INTERFACE_SOLVED);
if (err == OK) {
result = type_from_metatype(singl_parser->get_parser()->head->get_datatype());
}
}
break;
}
}
}
}
result.is_constant = true;
p_identifier->set_datatype(result);
@ -3236,9 +3265,28 @@ void GDScriptAnalyzer::reduce_preload(GDScriptParser::PreloadNode *p_preload) {
}
} else {
// TODO: Don't load if validating: use completion cache.
p_preload->resource = ResourceLoader::load(p_preload->resolved_path);
if (p_preload->resource.is_null()) {
push_error(vformat(R"(Could not preload resource file "%s".)", p_preload->resolved_path), p_preload->path);
// Must load GDScript and PackedScenes separately to permit cyclic references
// as ResourceLoader::load() detect and reject those.
if (ResourceLoader::get_resource_type(p_preload->resolved_path) == "GDScript") {
Error err = OK;
Ref<GDScript> res = GDScriptCache::get_shallow_script(p_preload->resolved_path, err, parser->script_path);
p_preload->resource = res;
if (err != OK) {
push_error(vformat(R"(Could not preload resource script "%s".)", p_preload->resolved_path), p_preload->path);
}
} else if (ResourceLoader::get_resource_type(p_preload->resolved_path) == "PackedScene") {
Error err = OK;
Ref<PackedScene> res = GDScriptCache::get_packed_scene(p_preload->resolved_path, err, parser->script_path);
p_preload->resource = res;
if (err != OK) {
push_error(vformat(R"(Could not preload resource scene "%s".)", p_preload->resolved_path), p_preload->path);
}
} else {
p_preload->resource = ResourceLoader::load(p_preload->resolved_path);
if (p_preload->resource.is_null()) {
push_error(vformat(R"(Could not preload resource file "%s".)", p_preload->resolved_path), p_preload->path);
}
}
}
}
@ -3280,6 +3328,17 @@ void GDScriptAnalyzer::reduce_subscript(GDScriptParser::SubscriptNode *p_subscri
// Just try to get it.
bool valid = false;
Variant value = p_subscript->base->reduced_value.get_named(p_subscript->attribute->name, valid);
// If it's a GDScript instance, try to get the full script. Maybe it's not still completely loaded.
Ref<GDScript> gdscr = Ref<GDScript>(p_subscript->base->reduced_value);
if (!valid && gdscr.is_valid()) {
Error err = OK;
GDScriptCache::get_full_script(gdscr->get_path(), err);
if (err == OK) {
value = p_subscript->base->reduced_value.get_named(p_subscript->attribute->name, valid);
}
}
if (!valid) {
push_error(vformat(R"(Cannot get member "%s" from "%s".)", p_subscript->attribute->name, p_subscript->base->reduced_value), p_subscript->index);
result_type.kind = GDScriptParser::DataType::VARIANT;
@ -3662,50 +3721,43 @@ GDScriptParser::DataType GDScriptAnalyzer::type_from_variant(const Variant &p_va
scr = obj->get_script();
}
if (scr.is_valid()) {
if (scr->is_valid()) {
result.script_type = scr;
result.script_path = scr->get_path();
Ref<GDScript> gds = scr;
if (gds.is_valid()) {
result.kind = GDScriptParser::DataType::CLASS;
// This might be an inner class, so we want to get the parser for the root.
// But still get the inner class from that tree.
GDScript *current = gds.ptr();
List<StringName> class_chain;
while (current->_owner) {
// Push to front so it's in reverse.
class_chain.push_front(current->name);
current = current->_owner;
}
Ref<GDScriptParserRef> ref = get_parser_for(current->get_path());
if (ref.is_null()) {
push_error("Could not find script in path.", p_source);
GDScriptParser::DataType error_type;
error_type.kind = GDScriptParser::DataType::VARIANT;
return error_type;
}
ref->raise_status(GDScriptParserRef::INTERFACE_SOLVED);
GDScriptParser::ClassNode *found = ref->get_parser()->head;
// It should be okay to assume this exists, since we have a complete script already.
for (const StringName &E : class_chain) {
found = found->get_member(E).m_class;
}
result.class_type = found;
result.script_path = ref->get_parser()->script_path;
} else {
result.kind = GDScriptParser::DataType::SCRIPT;
result.script_type = scr;
result.script_path = scr->get_path();
Ref<GDScript> gds = scr;
if (gds.is_valid()) {
result.kind = GDScriptParser::DataType::CLASS;
// This might be an inner class, so we want to get the parser for the root.
// But still get the inner class from that tree.
GDScript *current = gds.ptr();
List<StringName> class_chain;
while (current->_owner) {
// Push to front so it's in reverse.
class_chain.push_front(current->name);
current = current->_owner;
}
result.native_type = scr->get_instance_base_type();
Ref<GDScriptParserRef> ref = get_parser_for(current->get_path());
if (ref.is_null()) {
push_error("Could not find script in path.", p_source);
GDScriptParser::DataType error_type;
error_type.kind = GDScriptParser::DataType::VARIANT;
return error_type;
}
ref->raise_status(GDScriptParserRef::INTERFACE_SOLVED);
GDScriptParser::ClassNode *found = ref->get_parser()->head;
// It should be okay to assume this exists, since we have a complete script already.
for (const StringName &E : class_chain) {
found = found->get_member(E).m_class;
}
result.class_type = found;
result.script_path = ref->get_parser()->script_path;
} else {
push_error(vformat(R"(Constant value uses script from "%s" which is loaded but not compiled.)", scr->get_path()), p_source);
result.kind = GDScriptParser::DataType::VARIANT;
result.type_source = GDScriptParser::DataType::UNDETECTED;
result.is_meta_type = false;
result.kind = GDScriptParser::DataType::SCRIPT;
}
result.native_type = scr->get_instance_base_type();
} else {
result.kind = GDScriptParser::DataType::NATIVE;
if (result.native_type == GDScriptNativeClass::get_class_static()) {

View file

@ -36,6 +36,7 @@
#include "gdscript_analyzer.h"
#include "gdscript_compiler.h"
#include "gdscript_parser.h"
#include "scene/resources/packed_scene.h"
bool GDScriptParserRef::is_valid() const {
return parser != nullptr;
@ -96,27 +97,88 @@ Error GDScriptParserRef::raise_status(Status p_new_status) {
return result;
}
GDScriptParserRef::~GDScriptParserRef() {
void GDScriptParserRef::clear() {
if (cleared) {
return;
}
cleared = true;
if (parser != nullptr) {
memdelete(parser);
}
if (analyzer != nullptr) {
memdelete(analyzer);
}
MutexLock lock(GDScriptCache::singleton->lock);
}
GDScriptParserRef::~GDScriptParserRef() {
clear();
MutexLock lock(GDScriptCache::singleton->mutex);
GDScriptCache::singleton->parser_map.erase(path);
}
GDScriptCache *GDScriptCache::singleton = nullptr;
void GDScriptCache::move_script(const String &p_from, const String &p_to) {
if (singleton == nullptr) {
return;
}
MutexLock lock(singleton->mutex);
for (KeyValue<String, HashSet<String>> &E : singleton->packed_scene_dependencies) {
if (E.value.has(p_from)) {
E.value.insert(p_to);
E.value.erase(p_from);
}
}
if (singleton->parser_map.has(p_from) && !p_from.is_empty()) {
singleton->parser_map[p_to] = singleton->parser_map[p_from];
}
singleton->parser_map.erase(p_from);
if (singleton->shallow_gdscript_cache.has(p_from) && !p_from.is_empty()) {
singleton->shallow_gdscript_cache[p_to] = singleton->shallow_gdscript_cache[p_from];
}
singleton->shallow_gdscript_cache.erase(p_from);
if (singleton->full_gdscript_cache.has(p_from) && !p_from.is_empty()) {
singleton->full_gdscript_cache[p_to] = singleton->full_gdscript_cache[p_from];
}
singleton->full_gdscript_cache.erase(p_from);
}
void GDScriptCache::remove_script(const String &p_path) {
MutexLock lock(singleton->lock);
if (singleton == nullptr) {
return;
}
MutexLock lock(singleton->mutex);
for (KeyValue<String, HashSet<String>> &E : singleton->packed_scene_dependencies) {
if (!E.value.has(p_path)) {
continue;
}
E.value.erase(p_path);
}
GDScriptCache::clear_unreferenced_packed_scenes();
if (singleton->parser_map.has(p_path)) {
singleton->parser_map[p_path]->clear();
singleton->parser_map.erase(p_path);
}
singleton->dependencies.erase(p_path);
singleton->shallow_gdscript_cache.erase(p_path);
singleton->full_gdscript_cache.erase(p_path);
}
Ref<GDScriptParserRef> GDScriptCache::get_parser(const String &p_path, GDScriptParserRef::Status p_status, Error &r_error, const String &p_owner) {
MutexLock lock(singleton->lock);
MutexLock lock(singleton->mutex);
Ref<GDScriptParserRef> ref;
if (!p_owner.is_empty()) {
singleton->dependencies[p_owner].insert(p_path);
@ -163,7 +225,7 @@ String GDScriptCache::get_source_code(const String &p_path) {
}
Ref<GDScript> GDScriptCache::get_shallow_script(const String &p_path, Error &r_error, const String &p_owner) {
MutexLock lock(singleton->lock);
MutexLock lock(singleton->mutex);
if (!p_owner.is_empty()) {
singleton->dependencies[p_owner].insert(p_path);
}
@ -185,12 +247,12 @@ Ref<GDScript> GDScriptCache::get_shallow_script(const String &p_path, Error &r_e
script->load_source_code(p_path);
GDScriptCompiler::make_scripts(script.ptr(), parser_ref->get_parser()->get_tree(), true);
singleton->shallow_gdscript_cache[p_path] = script.ptr();
singleton->shallow_gdscript_cache[p_path] = script;
return script;
}
Ref<GDScript> GDScriptCache::get_full_script(const String &p_path, Error &r_error, const String &p_owner, bool p_update_from_disk) {
MutexLock lock(singleton->lock);
MutexLock lock(singleton->mutex);
if (!p_owner.is_empty()) {
singleton->dependencies[p_owner].insert(p_path);
@ -220,19 +282,21 @@ Ref<GDScript> GDScriptCache::get_full_script(const String &p_path, Error &r_erro
return script;
}
singleton->full_gdscript_cache[p_path] = script;
singleton->shallow_gdscript_cache.erase(p_path);
r_error = script->reload(true);
if (r_error) {
singleton->shallow_gdscript_cache[p_path] = script;
singleton->full_gdscript_cache.erase(p_path);
return script;
}
singleton->full_gdscript_cache[p_path] = script.ptr();
singleton->shallow_gdscript_cache.erase(p_path);
return script;
}
Ref<GDScript> GDScriptCache::get_cached_script(const String &p_path) {
MutexLock lock(singleton->lock);
MutexLock lock(singleton->mutex);
if (singleton->full_gdscript_cache.has(p_path)) {
return singleton->full_gdscript_cache[p_path];
@ -246,11 +310,11 @@ Ref<GDScript> GDScriptCache::get_cached_script(const String &p_path) {
}
Error GDScriptCache::finish_compiling(const String &p_owner) {
MutexLock lock(singleton->lock);
MutexLock lock(singleton->mutex);
// Mark this as compiled.
Ref<GDScript> script = get_cached_script(p_owner);
singleton->full_gdscript_cache[p_owner] = script.ptr();
singleton->full_gdscript_cache[p_owner] = script;
singleton->shallow_gdscript_cache.erase(p_owner);
HashSet<String> depends = singleton->dependencies[p_owner];
@ -271,13 +335,73 @@ Error GDScriptCache::finish_compiling(const String &p_owner) {
return err;
}
Ref<PackedScene> GDScriptCache::get_packed_scene(const String &p_path, Error &r_error, const String &p_owner) {
MutexLock lock(singleton->mutex);
if (singleton->packed_scene_cache.has(p_path)) {
singleton->packed_scene_dependencies[p_path].insert(p_owner);
return singleton->packed_scene_cache[p_path];
}
Ref<PackedScene> scene;
scene.instantiate();
r_error = OK;
if (p_path.is_empty()) {
r_error = ERR_FILE_BAD_PATH;
return scene;
}
scene->set_path(p_path);
singleton->packed_scene_cache[p_path] = scene;
singleton->packed_scene_dependencies[p_path].insert(p_owner);
scene->recreate_state();
scene->reload_from_file();
return scene;
}
void GDScriptCache::clear_unreferenced_packed_scenes() {
if (singleton == nullptr) {
return;
}
MutexLock lock(singleton->mutex);
for (KeyValue<String, HashSet<String>> &E : singleton->packed_scene_dependencies) {
if (E.value.size() > 0 || !ResourceLoader::is_imported(E.key)) {
continue;
}
singleton->packed_scene_dependencies.erase(E.key);
singleton->packed_scene_cache.erase(E.key);
}
}
GDScriptCache::GDScriptCache() {
singleton = this;
}
GDScriptCache::~GDScriptCache() {
destructing = true;
RBSet<Ref<GDScriptParserRef>> parser_map_refs;
for (KeyValue<String, GDScriptParserRef *> &E : parser_map) {
parser_map_refs.insert(E.value);
}
for (Ref<GDScriptParserRef> &E : parser_map_refs) {
if (E.is_valid())
E->clear();
}
parser_map_refs.clear();
parser_map.clear();
shallow_gdscript_cache.clear();
full_gdscript_cache.clear();
packed_scene_cache.clear();
packed_scene_dependencies.clear();
singleton = nullptr;
}

View file

@ -36,6 +36,7 @@
#include "core/templates/hash_map.h"
#include "core/templates/hash_set.h"
#include "gdscript.h"
#include "scene/resources/packed_scene.h"
class GDScriptAnalyzer;
class GDScriptParser;
@ -56,6 +57,7 @@ private:
Status status = EMPTY;
Error result = OK;
String path;
bool cleared = false;
friend class GDScriptCache;
@ -64,6 +66,7 @@ public:
Status get_status() const;
GDScriptParser *get_parser() const;
Error raise_status(Status p_new_status);
void clear();
GDScriptParserRef() {}
~GDScriptParserRef();
@ -72,19 +75,25 @@ public:
class GDScriptCache {
// String key is full path.
HashMap<String, GDScriptParserRef *> parser_map;
HashMap<String, GDScript *> shallow_gdscript_cache;
HashMap<String, GDScript *> full_gdscript_cache;
HashMap<String, Ref<GDScript>> shallow_gdscript_cache;
HashMap<String, Ref<GDScript>> full_gdscript_cache;
HashMap<String, HashSet<String>> dependencies;
HashMap<String, Ref<PackedScene>> packed_scene_cache;
HashMap<String, HashSet<String>> packed_scene_dependencies;
friend class GDScript;
friend class GDScriptParserRef;
friend class GDScriptInstance;
static GDScriptCache *singleton;
Mutex lock;
static void remove_script(const String &p_path);
bool destructing = false;
Mutex mutex;
public:
static void move_script(const String &p_from, const String &p_to);
static void remove_script(const String &p_path);
static Ref<GDScriptParserRef> get_parser(const String &p_path, GDScriptParserRef::Status status, Error &r_error, const String &p_owner = String());
static String get_source_code(const String &p_path);
static Ref<GDScript> get_shallow_script(const String &p_path, Error &r_error, const String &p_owner = String());
@ -92,6 +101,16 @@ public:
static Ref<GDScript> get_cached_script(const String &p_path);
static Error finish_compiling(const String &p_owner);
static Ref<PackedScene> get_packed_scene(const String &p_path, Error &r_error, const String &p_owner = "");
static void clear_unreferenced_packed_scenes();
static bool is_destructing() {
if (singleton == nullptr) {
return true;
}
return singleton->destructing;
};
GDScriptCache();
~GDScriptCache();
};

View file

@ -141,6 +141,7 @@ GDScriptDataType GDScriptCompiler::_gdtype_from_datatype(const GDScriptParser::D
result.script_type_ref = script;
}
result.script_type = script.ptr();
result.native_type = p_datatype.native_type;
}
} break;
case GDScriptParser::DataType::ENUM:
@ -354,11 +355,22 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code
if (class_node->identifier && class_node->identifier->name == identifier) {
res = Ref<GDScript>(main_script);
} else {
res = ResourceLoader::load(ScriptServer::get_global_class_path(identifier));
if (res.is_null()) {
_set_error("Can't load global class " + String(identifier) + ", cyclic reference?", p_expression);
r_error = ERR_COMPILATION_FAILED;
return GDScriptCodeGenerator::Address();
String global_class_path = ScriptServer::get_global_class_path(identifier);
if (ResourceLoader::get_resource_type(global_class_path) == "GDScript") {
Error err = OK;
res = GDScriptCache::get_full_script(global_class_path, err);
if (err != OK) {
_set_error("Can't load global class " + String(identifier), p_expression);
r_error = ERR_COMPILATION_FAILED;
return GDScriptCodeGenerator::Address();
}
} else {
res = ResourceLoader::load(global_class_path);
if (res.is_null()) {
_set_error("Can't load global class " + String(identifier) + ", cyclic reference?", p_expression);
r_error = ERR_COMPILATION_FAILED;
return GDScriptCodeGenerator::Address();
}
}
}
@ -2172,6 +2184,7 @@ Error GDScriptCompiler::_populate_class_members(GDScript *p_script, const GDScri
parsing_classes.insert(p_script);
p_script->clearing = true;
#ifdef TOOLS_ENABLED
p_script->doc_functions.clear();
p_script->doc_variables.clear();
@ -2194,10 +2207,24 @@ Error GDScriptCompiler::_populate_class_members(GDScript *p_script, const GDScri
p_script->base = Ref<GDScript>();
p_script->_base = nullptr;
p_script->members.clear();
// This makes possible to clear script constants and member_functions without heap-use-after-free errors.
HashMap<StringName, Variant> constants;
for (const KeyValue<StringName, Variant> &E : p_script->constants) {
constants.insert(E.key, E.value);
}
p_script->constants.clear();
constants.clear();
HashMap<StringName, GDScriptFunction *> member_functions;
for (const KeyValue<StringName, GDScriptFunction *> &E : p_script->member_functions) {
member_functions.insert(E.key, E.value);
}
p_script->member_functions.clear();
for (const KeyValue<StringName, GDScriptFunction *> &E : member_functions) {
memdelete(E.value);
}
member_functions.clear();
if (p_script->implicit_initializer) {
memdelete(p_script->implicit_initializer);
}
@ -2212,6 +2239,8 @@ Error GDScriptCompiler::_populate_class_members(GDScript *p_script, const GDScri
p_script->implicit_initializer = nullptr;
p_script->implicit_ready = nullptr;
p_script->clearing = false;
p_script->tool = parser->is_tool();
if (!p_script->name.is_empty()) {
@ -2454,10 +2483,8 @@ Error GDScriptCompiler::_populate_class_members(GDScript *p_script, const GDScri
}
#ifdef TOOLS_ENABLED
p_script->member_lines[name] = inner_class->start_line;
#endif
p_script->constants.insert(name, subclass); //once parsed, goes to the list of constants
}

View file

@ -149,10 +149,17 @@ GDScriptFunction::GDScriptFunction() {
}
GDScriptFunction::~GDScriptFunction() {
get_script()->member_functions.erase(name);
for (int i = 0; i < lambdas.size(); i++) {
memdelete(lambdas[i]);
}
for (int i = 0; i < argument_types.size(); i++) {
argument_types.write[i].script_type_ref = Ref<Script>();
}
return_type.script_type_ref = Ref<Script>();
#ifdef DEBUG_ENABLED
MutexLock lock(GDScriptLanguage::get_singleton()->mutex);

View file

@ -430,6 +430,7 @@ public:
};
private:
friend class GDScript;
friend class GDScriptCompiler;
friend class GDScriptByteCodeGenerator;

View file

@ -648,7 +648,13 @@ GDScriptParser::ClassNode *GDScriptParser::parse_class() {
if (consume(GDScriptTokenizer::Token::IDENTIFIER, R"(Expected identifier for the class name after "class".)")) {
n_class->identifier = parse_identifier();
if (n_class->outer) {
n_class->fqcn = n_class->outer->fqcn + "::" + n_class->identifier->name;
String fqcn = n_class->outer->fqcn;
if (fqcn.is_empty()) {
fqcn = script_path;
}
n_class->fqcn = fqcn + "::" + n_class->identifier->name;
} else {
n_class->fqcn = n_class->identifier->name;
}
}

View file

@ -251,7 +251,10 @@ bool GDScriptTestRunner::make_tests_for_dir(const String &p_dir) {
return false;
}
} else {
if (next.get_extension().to_lower() == "gd") {
if (next.ends_with(".notest.gd")) {
next = dir->get_next();
continue;
} else if (next.get_extension().to_lower() == "gd") {
#ifndef DEBUG_ENABLED
// On release builds, skip tests marked as debug only.
Error open_err = OK;
@ -597,6 +600,9 @@ GDScriptTest::TestResult GDScriptTest::execute_test_code(bool p_is_generating) {
}
enable_stdout();
GDScriptCache::remove_script(script->get_path());
return result;
}

View file

@ -1,4 +1,4 @@
const Constants = preload("gdscript_to_preload.gd")
const Constants = preload("gdscript_to_preload.notest.gd")
func test():
var a := Constants.A

View file

@ -0,0 +1,4 @@
const A = preload("preload_cyclic_reference_a.notest.gd")
func test():
A.test_cyclic_reference()

View file

@ -0,0 +1,12 @@
const B = preload("preload_cyclic_reference_b.notest.gd")
const WAITING_FOR = "godot"
static func test_cyclic_reference():
B.test_cyclic_reference()
static func test_cyclic_reference_2():
B.test_cyclic_reference_2()
static func test_cyclic_reference_3():
B.test_cyclic_reference_3()

View file

@ -0,0 +1,10 @@
const A = preload("preload_cyclic_reference_a.notest.gd")
static func test_cyclic_reference():
A.test_cyclic_reference_2()
static func test_cyclic_reference_2():
A.test_cyclic_reference_3()
static func test_cyclic_reference_3():
print(A.WAITING_FOR)

View file

@ -1,4 +1,4 @@
const preloaded : GDScript = preload("gdscript_to_preload.gd")
const preloaded : GDScript = preload("gdscript_to_preload.notest.gd")
func test():
var preloaded_instance: preloaded = preloaded.new()

View file

@ -601,9 +601,13 @@ Error ResourceLoaderText::load() {
resource_current++;
int_resources[id] = res; //always assign int resources
if (do_assign && cache_mode != ResourceFormatLoader::CACHE_MODE_IGNORE) {
res->set_path(path, cache_mode == ResourceFormatLoader::CACHE_MODE_REPLACE);
res->set_scene_unique_id(id);
if (do_assign) {
if (cache_mode == ResourceFormatLoader::CACHE_MODE_IGNORE) {
res->set_path(path);
} else {
res->set_path(path, cache_mode == ResourceFormatLoader::CACHE_MODE_REPLACE);
res->set_scene_unique_id(id);
}
}
Dictionary missing_resource_properties;

View file

@ -124,5 +124,7 @@ Shape2D::Shape2D(const RID &p_rid) {
}
Shape2D::~Shape2D() {
PhysicsServer2D::get_singleton()->free(shape);
if (PhysicsServer2D::get_singleton() != nullptr) {
PhysicsServer2D::get_singleton()->free(shape);
}
}

View file

@ -128,5 +128,7 @@ Shape3D::Shape3D(RID p_shape) :
shape(p_shape) {}
Shape3D::~Shape3D() {
PhysicsServer3D::get_singleton()->free(shape);
if (PhysicsServer3D::get_singleton() != nullptr) {
PhysicsServer3D::get_singleton()->free(shape);
}
}