Fix cyclic references in GDScript 2.0
This commit is contained in:
parent
e8f9cd8ac5
commit
5704055d30
20 changed files with 626 additions and 113 deletions
|
@ -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) {
|
||||
|
|
|
@ -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();
|
||||
};
|
||||
|
|
|
@ -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()) {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
};
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -430,6 +430,7 @@ public:
|
|||
};
|
||||
|
||||
private:
|
||||
friend class GDScript;
|
||||
friend class GDScriptCompiler;
|
||||
friend class GDScriptByteCodeGenerator;
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -1,7 +1,4 @@
|
|||
const A := 42
|
||||
|
||||
func test():
|
||||
pass
|
||||
|
||||
func something():
|
||||
return "OK"
|
|
@ -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
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
const A = preload("preload_cyclic_reference_a.notest.gd")
|
||||
|
||||
func test():
|
||||
A.test_cyclic_reference()
|
|
@ -1 +1,2 @@
|
|||
GDTEST_OK
|
||||
godot
|
|
@ -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()
|
|
@ -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)
|
|
@ -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()
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue