| 1 | /**************************************************************************/ |
| 2 | /* gdscript.cpp */ |
| 3 | /**************************************************************************/ |
| 4 | /* This file is part of: */ |
| 5 | /* GODOT ENGINE */ |
| 6 | /* https://godotengine.org */ |
| 7 | /**************************************************************************/ |
| 8 | /* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ |
| 9 | /* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ |
| 10 | /* */ |
| 11 | /* Permission is hereby granted, free of charge, to any person obtaining */ |
| 12 | /* a copy of this software and associated documentation files (the */ |
| 13 | /* "Software"), to deal in the Software without restriction, including */ |
| 14 | /* without limitation the rights to use, copy, modify, merge, publish, */ |
| 15 | /* distribute, sublicense, and/or sell copies of the Software, and to */ |
| 16 | /* permit persons to whom the Software is furnished to do so, subject to */ |
| 17 | /* the following conditions: */ |
| 18 | /* */ |
| 19 | /* The above copyright notice and this permission notice shall be */ |
| 20 | /* included in all copies or substantial portions of the Software. */ |
| 21 | /* */ |
| 22 | /* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ |
| 23 | /* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ |
| 24 | /* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ |
| 25 | /* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ |
| 26 | /* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ |
| 27 | /* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ |
| 28 | /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ |
| 29 | /**************************************************************************/ |
| 30 | |
| 31 | #include "gdscript.h" |
| 32 | |
| 33 | #include "gdscript_analyzer.h" |
| 34 | #include "gdscript_cache.h" |
| 35 | #include "gdscript_compiler.h" |
| 36 | #include "gdscript_parser.h" |
| 37 | #include "gdscript_rpc_callable.h" |
| 38 | #include "gdscript_warning.h" |
| 39 | |
| 40 | #ifdef TOOLS_ENABLED |
| 41 | #include "editor/gdscript_docgen.h" |
| 42 | #endif |
| 43 | |
| 44 | #ifdef TESTS_ENABLED |
| 45 | #include "tests/gdscript_test_runner.h" |
| 46 | #endif |
| 47 | |
| 48 | #include "core/config/engine.h" |
| 49 | #include "core/config/project_settings.h" |
| 50 | #include "core/core_constants.h" |
| 51 | #include "core/core_string_names.h" |
| 52 | #include "core/io/file_access.h" |
| 53 | #include "core/io/file_access_encrypted.h" |
| 54 | #include "core/os/os.h" |
| 55 | |
| 56 | #ifdef TOOLS_ENABLED |
| 57 | #include "editor/editor_paths.h" |
| 58 | #endif |
| 59 | |
| 60 | #include <stdint.h> |
| 61 | |
| 62 | /////////////////////////// |
| 63 | |
| 64 | GDScriptNativeClass::GDScriptNativeClass(const StringName &p_name) { |
| 65 | name = p_name; |
| 66 | } |
| 67 | |
| 68 | bool GDScriptNativeClass::_get(const StringName &p_name, Variant &r_ret) const { |
| 69 | bool ok; |
| 70 | int64_t v = ClassDB::get_integer_constant(name, p_name, &ok); |
| 71 | |
| 72 | if (ok) { |
| 73 | r_ret = v; |
| 74 | return true; |
| 75 | } else { |
| 76 | return false; |
| 77 | } |
| 78 | } |
| 79 | |
| 80 | void GDScriptNativeClass::_bind_methods() { |
| 81 | ClassDB::bind_method(D_METHOD("new" ), &GDScriptNativeClass::_new); |
| 82 | } |
| 83 | |
| 84 | Variant GDScriptNativeClass::_new() { |
| 85 | Object *o = instantiate(); |
| 86 | ERR_FAIL_COND_V_MSG(!o, Variant(), "Class type: '" + String(name) + "' is not instantiable." ); |
| 87 | |
| 88 | RefCounted *rc = Object::cast_to<RefCounted>(o); |
| 89 | if (rc) { |
| 90 | return Ref<RefCounted>(rc); |
| 91 | } else { |
| 92 | return o; |
| 93 | } |
| 94 | } |
| 95 | |
| 96 | Object *GDScriptNativeClass::instantiate() { |
| 97 | return ClassDB::instantiate(name); |
| 98 | } |
| 99 | |
| 100 | Variant GDScriptNativeClass::callp(const StringName &p_method, const Variant **p_args, int p_argcount, Callable::CallError &r_error) { |
| 101 | if (p_method == SNAME("new" )) { |
| 102 | // Constructor. |
| 103 | return Object::callp(p_method, p_args, p_argcount, r_error); |
| 104 | } |
| 105 | MethodBind *method = ClassDB::get_method(name, p_method); |
| 106 | if (method && method->is_static()) { |
| 107 | // Native static method. |
| 108 | return method->call(nullptr, p_args, p_argcount, r_error); |
| 109 | } |
| 110 | |
| 111 | r_error.error = Callable::CallError::CALL_ERROR_INVALID_METHOD; |
| 112 | return Variant(); |
| 113 | } |
| 114 | |
| 115 | GDScriptFunction *GDScript::_super_constructor(GDScript *p_script) { |
| 116 | if (p_script->initializer) { |
| 117 | return p_script->initializer; |
| 118 | } else { |
| 119 | GDScript *base_src = p_script->_base; |
| 120 | if (base_src != nullptr) { |
| 121 | return _super_constructor(base_src); |
| 122 | } else { |
| 123 | return nullptr; |
| 124 | } |
| 125 | } |
| 126 | } |
| 127 | |
| 128 | void GDScript::_super_implicit_constructor(GDScript *p_script, GDScriptInstance *p_instance, Callable::CallError &r_error) { |
| 129 | GDScript *base_src = p_script->_base; |
| 130 | if (base_src != nullptr) { |
| 131 | _super_implicit_constructor(base_src, p_instance, r_error); |
| 132 | if (r_error.error != Callable::CallError::CALL_OK) { |
| 133 | return; |
| 134 | } |
| 135 | } |
| 136 | ERR_FAIL_NULL(p_script->implicit_initializer); |
| 137 | p_script->implicit_initializer->call(p_instance, nullptr, 0, r_error); |
| 138 | } |
| 139 | |
| 140 | GDScriptInstance *GDScript::_create_instance(const Variant **p_args, int p_argcount, Object *p_owner, bool p_is_ref_counted, Callable::CallError &r_error) { |
| 141 | /* STEP 1, CREATE */ |
| 142 | |
| 143 | GDScriptInstance *instance = memnew(GDScriptInstance); |
| 144 | instance->base_ref_counted = p_is_ref_counted; |
| 145 | instance->members.resize(member_indices.size()); |
| 146 | instance->script = Ref<GDScript>(this); |
| 147 | instance->owner = p_owner; |
| 148 | instance->owner_id = p_owner->get_instance_id(); |
| 149 | #ifdef DEBUG_ENABLED |
| 150 | //needed for hot reloading |
| 151 | for (const KeyValue<StringName, MemberInfo> &E : member_indices) { |
| 152 | instance->member_indices_cache[E.key] = E.value.index; |
| 153 | } |
| 154 | #endif |
| 155 | instance->owner->set_script_instance(instance); |
| 156 | |
| 157 | /* STEP 2, INITIALIZE AND CONSTRUCT */ |
| 158 | { |
| 159 | MutexLock lock(GDScriptLanguage::singleton->mutex); |
| 160 | instances.insert(instance->owner); |
| 161 | } |
| 162 | |
| 163 | _super_implicit_constructor(this, instance, r_error); |
| 164 | if (r_error.error != Callable::CallError::CALL_OK) { |
| 165 | instance->script = Ref<GDScript>(); |
| 166 | instance->owner->set_script_instance(nullptr); |
| 167 | { |
| 168 | MutexLock lock(GDScriptLanguage::singleton->mutex); |
| 169 | instances.erase(p_owner); |
| 170 | } |
| 171 | ERR_FAIL_V_MSG(nullptr, "Error constructing a GDScriptInstance." ); |
| 172 | } |
| 173 | |
| 174 | if (p_argcount < 0) { |
| 175 | return instance; |
| 176 | } |
| 177 | |
| 178 | initializer = _super_constructor(this); |
| 179 | if (initializer != nullptr) { |
| 180 | initializer->call(instance, p_args, p_argcount, r_error); |
| 181 | if (r_error.error != Callable::CallError::CALL_OK) { |
| 182 | instance->script = Ref<GDScript>(); |
| 183 | instance->owner->set_script_instance(nullptr); |
| 184 | { |
| 185 | MutexLock lock(GDScriptLanguage::singleton->mutex); |
| 186 | instances.erase(p_owner); |
| 187 | } |
| 188 | ERR_FAIL_V_MSG(nullptr, "Error constructing a GDScriptInstance." ); |
| 189 | } |
| 190 | } |
| 191 | //@TODO make thread safe |
| 192 | return instance; |
| 193 | } |
| 194 | |
| 195 | Variant GDScript::_new(const Variant **p_args, int p_argcount, Callable::CallError &r_error) { |
| 196 | /* STEP 1, CREATE */ |
| 197 | |
| 198 | if (!valid) { |
| 199 | r_error.error = Callable::CallError::CALL_ERROR_INVALID_METHOD; |
| 200 | return Variant(); |
| 201 | } |
| 202 | |
| 203 | r_error.error = Callable::CallError::CALL_OK; |
| 204 | Ref<RefCounted> ref; |
| 205 | Object *owner = nullptr; |
| 206 | |
| 207 | GDScript *_baseptr = this; |
| 208 | while (_baseptr->_base) { |
| 209 | _baseptr = _baseptr->_base; |
| 210 | } |
| 211 | |
| 212 | ERR_FAIL_COND_V(_baseptr->native.is_null(), Variant()); |
| 213 | if (_baseptr->native.ptr()) { |
| 214 | owner = _baseptr->native->instantiate(); |
| 215 | } else { |
| 216 | owner = memnew(RefCounted); //by default, no base means use reference |
| 217 | } |
| 218 | ERR_FAIL_COND_V_MSG(!owner, Variant(), "Can't inherit from a virtual class." ); |
| 219 | |
| 220 | RefCounted *r = Object::cast_to<RefCounted>(owner); |
| 221 | if (r) { |
| 222 | ref = Ref<RefCounted>(r); |
| 223 | } |
| 224 | |
| 225 | GDScriptInstance *instance = _create_instance(p_args, p_argcount, owner, r != nullptr, r_error); |
| 226 | if (!instance) { |
| 227 | if (ref.is_null()) { |
| 228 | memdelete(owner); //no owner, sorry |
| 229 | } |
| 230 | return Variant(); |
| 231 | } |
| 232 | |
| 233 | if (ref.is_valid()) { |
| 234 | return ref; |
| 235 | } else { |
| 236 | return owner; |
| 237 | } |
| 238 | } |
| 239 | |
| 240 | bool GDScript::can_instantiate() const { |
| 241 | #ifdef TOOLS_ENABLED |
| 242 | return valid && (tool || ScriptServer::is_scripting_enabled()); |
| 243 | #else |
| 244 | return valid; |
| 245 | #endif |
| 246 | } |
| 247 | |
| 248 | Ref<Script> GDScript::get_base_script() const { |
| 249 | if (_base) { |
| 250 | return Ref<GDScript>(_base); |
| 251 | } else { |
| 252 | return Ref<Script>(); |
| 253 | } |
| 254 | } |
| 255 | |
| 256 | StringName GDScript::get_global_name() const { |
| 257 | return global_name; |
| 258 | } |
| 259 | |
| 260 | StringName GDScript::get_instance_base_type() const { |
| 261 | if (native.is_valid()) { |
| 262 | return native->get_name(); |
| 263 | } |
| 264 | if (base.is_valid() && base->is_valid()) { |
| 265 | return base->get_instance_base_type(); |
| 266 | } |
| 267 | return StringName(); |
| 268 | } |
| 269 | |
| 270 | struct _GDScriptMemberSort { |
| 271 | int index = 0; |
| 272 | StringName name; |
| 273 | _FORCE_INLINE_ bool operator<(const _GDScriptMemberSort &p_member) const { return index < p_member.index; } |
| 274 | }; |
| 275 | |
| 276 | #ifdef TOOLS_ENABLED |
| 277 | |
| 278 | void GDScript::_placeholder_erased(PlaceHolderScriptInstance *p_placeholder) { |
| 279 | placeholders.erase(p_placeholder); |
| 280 | } |
| 281 | #endif |
| 282 | |
| 283 | void GDScript::_get_script_method_list(List<MethodInfo> *r_list, bool p_include_base) const { |
| 284 | const GDScript *current = this; |
| 285 | while (current) { |
| 286 | for (const KeyValue<StringName, GDScriptFunction *> &E : current->member_functions) { |
| 287 | r_list->push_back(E.value->get_method_info()); |
| 288 | } |
| 289 | |
| 290 | if (!p_include_base) { |
| 291 | return; |
| 292 | } |
| 293 | |
| 294 | current = current->_base; |
| 295 | } |
| 296 | } |
| 297 | |
| 298 | void GDScript::get_script_method_list(List<MethodInfo> *r_list) const { |
| 299 | _get_script_method_list(r_list, true); |
| 300 | } |
| 301 | |
| 302 | void GDScript::_get_script_property_list(List<PropertyInfo> *r_list, bool p_include_base) const { |
| 303 | const GDScript *sptr = this; |
| 304 | List<PropertyInfo> props; |
| 305 | |
| 306 | while (sptr) { |
| 307 | Vector<_GDScriptMemberSort> msort; |
| 308 | for (const KeyValue<StringName, MemberInfo> &E : sptr->member_indices) { |
| 309 | _GDScriptMemberSort ms; |
| 310 | ms.index = E.value.index; |
| 311 | ms.name = E.key; |
| 312 | msort.push_back(ms); |
| 313 | } |
| 314 | |
| 315 | msort.sort(); |
| 316 | msort.reverse(); |
| 317 | for (int i = 0; i < msort.size(); i++) { |
| 318 | props.push_front(sptr->member_indices[msort[i].name].property_info); |
| 319 | } |
| 320 | |
| 321 | #ifdef TOOLS_ENABLED |
| 322 | r_list->push_back(sptr->get_class_category()); |
| 323 | #endif // TOOLS_ENABLED |
| 324 | |
| 325 | for (const PropertyInfo &E : props) { |
| 326 | r_list->push_back(E); |
| 327 | } |
| 328 | |
| 329 | if (!p_include_base) { |
| 330 | break; |
| 331 | } |
| 332 | |
| 333 | props.clear(); |
| 334 | sptr = sptr->_base; |
| 335 | } |
| 336 | } |
| 337 | |
| 338 | void GDScript::get_script_property_list(List<PropertyInfo> *r_list) const { |
| 339 | _get_script_property_list(r_list, true); |
| 340 | } |
| 341 | |
| 342 | bool GDScript::has_method(const StringName &p_method) const { |
| 343 | return member_functions.has(p_method); |
| 344 | } |
| 345 | |
| 346 | MethodInfo GDScript::get_method_info(const StringName &p_method) const { |
| 347 | HashMap<StringName, GDScriptFunction *>::ConstIterator E = member_functions.find(p_method); |
| 348 | if (!E) { |
| 349 | return MethodInfo(); |
| 350 | } |
| 351 | |
| 352 | return E->value->get_method_info(); |
| 353 | } |
| 354 | |
| 355 | bool GDScript::get_property_default_value(const StringName &p_property, Variant &r_value) const { |
| 356 | #ifdef TOOLS_ENABLED |
| 357 | |
| 358 | HashMap<StringName, Variant>::ConstIterator E = member_default_values_cache.find(p_property); |
| 359 | if (E) { |
| 360 | r_value = E->value; |
| 361 | return true; |
| 362 | } |
| 363 | |
| 364 | if (base_cache.is_valid()) { |
| 365 | return base_cache->get_property_default_value(p_property, r_value); |
| 366 | } |
| 367 | #endif |
| 368 | return false; |
| 369 | } |
| 370 | |
| 371 | ScriptInstance *GDScript::instance_create(Object *p_this) { |
| 372 | GDScript *top = this; |
| 373 | while (top->_base) { |
| 374 | top = top->_base; |
| 375 | } |
| 376 | |
| 377 | if (top->native.is_valid()) { |
| 378 | if (!ClassDB::is_parent_class(p_this->get_class_name(), top->native->get_name())) { |
| 379 | if (EngineDebugger::is_active()) { |
| 380 | GDScriptLanguage::get_singleton()->debug_break_parse(_get_debug_path(), 1, "Script inherits from native type '" + String(top->native->get_name()) + "', so it can't be assigned to an object of type: '" + p_this->get_class() + "'" ); |
| 381 | } |
| 382 | ERR_FAIL_V_MSG(nullptr, "Script inherits from native type '" + String(top->native->get_name()) + "', so it can't be assigned to an object of type '" + p_this->get_class() + "'" + "." ); |
| 383 | } |
| 384 | } |
| 385 | |
| 386 | Callable::CallError unchecked_error; |
| 387 | return _create_instance(nullptr, 0, p_this, Object::cast_to<RefCounted>(p_this) != nullptr, unchecked_error); |
| 388 | } |
| 389 | |
| 390 | PlaceHolderScriptInstance *GDScript::placeholder_instance_create(Object *p_this) { |
| 391 | #ifdef TOOLS_ENABLED |
| 392 | PlaceHolderScriptInstance *si = memnew(PlaceHolderScriptInstance(GDScriptLanguage::get_singleton(), Ref<Script>(this), p_this)); |
| 393 | placeholders.insert(si); |
| 394 | _update_exports(nullptr, false, si); |
| 395 | return si; |
| 396 | #else |
| 397 | return nullptr; |
| 398 | #endif |
| 399 | } |
| 400 | |
| 401 | bool GDScript::instance_has(const Object *p_this) const { |
| 402 | MutexLock lock(GDScriptLanguage::singleton->mutex); |
| 403 | |
| 404 | return instances.has((Object *)p_this); |
| 405 | } |
| 406 | |
| 407 | bool GDScript::has_source_code() const { |
| 408 | return !source.is_empty(); |
| 409 | } |
| 410 | |
| 411 | String GDScript::get_source_code() const { |
| 412 | return source; |
| 413 | } |
| 414 | |
| 415 | void GDScript::set_source_code(const String &p_code) { |
| 416 | if (source == p_code) { |
| 417 | return; |
| 418 | } |
| 419 | source = p_code; |
| 420 | #ifdef TOOLS_ENABLED |
| 421 | source_changed_cache = true; |
| 422 | #endif |
| 423 | } |
| 424 | |
| 425 | #ifdef TOOLS_ENABLED |
| 426 | void GDScript::_update_exports_values(HashMap<StringName, Variant> &values, List<PropertyInfo> &propnames) { |
| 427 | for (const KeyValue<StringName, Variant> &E : member_default_values_cache) { |
| 428 | values[E.key] = E.value; |
| 429 | } |
| 430 | |
| 431 | for (const PropertyInfo &E : members_cache) { |
| 432 | propnames.push_back(E); |
| 433 | } |
| 434 | |
| 435 | if (base_cache.is_valid()) { |
| 436 | base_cache->_update_exports_values(values, propnames); |
| 437 | } |
| 438 | } |
| 439 | |
| 440 | void GDScript::_add_doc(const DocData::ClassDoc &p_inner_class) { |
| 441 | if (_owner) { // Only the top-level class stores doc info |
| 442 | _owner->_add_doc(p_inner_class); |
| 443 | } else { // Remove old docs, add new |
| 444 | for (int i = 0; i < docs.size(); i++) { |
| 445 | if (docs[i].name == p_inner_class.name) { |
| 446 | docs.remove_at(i); |
| 447 | break; |
| 448 | } |
| 449 | } |
| 450 | docs.append(p_inner_class); |
| 451 | } |
| 452 | } |
| 453 | |
| 454 | void GDScript::_clear_doc() { |
| 455 | docs.clear(); |
| 456 | doc = DocData::ClassDoc(); |
| 457 | } |
| 458 | |
| 459 | String GDScript::get_class_icon_path() const { |
| 460 | return simplified_icon_path; |
| 461 | } |
| 462 | #endif |
| 463 | |
| 464 | bool GDScript::_update_exports(bool *r_err, bool p_recursive_call, PlaceHolderScriptInstance *p_instance_to_update) { |
| 465 | #ifdef TOOLS_ENABLED |
| 466 | |
| 467 | static Vector<GDScript *> base_caches; |
| 468 | if (!p_recursive_call) { |
| 469 | base_caches.clear(); |
| 470 | } |
| 471 | base_caches.append(this); |
| 472 | |
| 473 | bool changed = false; |
| 474 | |
| 475 | if (source_changed_cache) { |
| 476 | source_changed_cache = false; |
| 477 | changed = true; |
| 478 | |
| 479 | String basedir = path; |
| 480 | |
| 481 | if (basedir.is_empty()) { |
| 482 | basedir = get_path(); |
| 483 | } |
| 484 | |
| 485 | if (!basedir.is_empty()) { |
| 486 | basedir = basedir.get_base_dir(); |
| 487 | } |
| 488 | |
| 489 | GDScriptParser parser; |
| 490 | GDScriptAnalyzer analyzer(&parser); |
| 491 | Error err = parser.parse(source, path, false); |
| 492 | |
| 493 | if (err == OK && analyzer.analyze() == OK) { |
| 494 | const GDScriptParser::ClassNode *c = parser.get_tree(); |
| 495 | |
| 496 | if (base_cache.is_valid()) { |
| 497 | base_cache->inheriters_cache.erase(get_instance_id()); |
| 498 | base_cache = Ref<GDScript>(); |
| 499 | } |
| 500 | |
| 501 | GDScriptParser::DataType base_type = parser.get_tree()->base_type; |
| 502 | if (base_type.kind == GDScriptParser::DataType::CLASS) { |
| 503 | Ref<GDScript> bf = GDScriptCache::get_full_script(base_type.script_path, err, path); |
| 504 | if (err == OK) { |
| 505 | bf = Ref<GDScript>(bf->find_class(base_type.class_type->fqcn)); |
| 506 | if (bf.is_valid()) { |
| 507 | base_cache = bf; |
| 508 | bf->inheriters_cache.insert(get_instance_id()); |
| 509 | } |
| 510 | } |
| 511 | } |
| 512 | |
| 513 | members_cache.clear(); |
| 514 | member_default_values_cache.clear(); |
| 515 | _signals.clear(); |
| 516 | |
| 517 | members_cache.push_back(get_class_category()); |
| 518 | |
| 519 | for (int i = 0; i < c->members.size(); i++) { |
| 520 | const GDScriptParser::ClassNode::Member &member = c->members[i]; |
| 521 | |
| 522 | switch (member.type) { |
| 523 | case GDScriptParser::ClassNode::Member::VARIABLE: { |
| 524 | if (!member.variable->exported) { |
| 525 | continue; |
| 526 | } |
| 527 | |
| 528 | members_cache.push_back(member.variable->export_info); |
| 529 | Variant default_value = analyzer.make_variable_default_value(member.variable); |
| 530 | member_default_values_cache[member.variable->identifier->name] = default_value; |
| 531 | } break; |
| 532 | case GDScriptParser::ClassNode::Member::SIGNAL: { |
| 533 | _signals[member.signal->identifier->name] = member.signal->method_info; |
| 534 | } break; |
| 535 | case GDScriptParser::ClassNode::Member::GROUP: { |
| 536 | members_cache.push_back(member.annotation->export_info); |
| 537 | } break; |
| 538 | default: |
| 539 | break; // Nothing. |
| 540 | } |
| 541 | } |
| 542 | } else { |
| 543 | placeholder_fallback_enabled = true; |
| 544 | return false; |
| 545 | } |
| 546 | } else if (placeholder_fallback_enabled) { |
| 547 | return false; |
| 548 | } |
| 549 | |
| 550 | placeholder_fallback_enabled = false; |
| 551 | |
| 552 | if (base_cache.is_valid() && base_cache->is_valid()) { |
| 553 | for (int i = 0; i < base_caches.size(); i++) { |
| 554 | if (base_caches[i] == base_cache.ptr()) { |
| 555 | if (r_err) { |
| 556 | *r_err = true; |
| 557 | } |
| 558 | valid = false; // to show error in the editor |
| 559 | base_cache->valid = false; |
| 560 | base_cache->inheriters_cache.clear(); // to prevent future stackoverflows |
| 561 | base_cache.unref(); |
| 562 | base.unref(); |
| 563 | _base = nullptr; |
| 564 | ERR_FAIL_V_MSG(false, "Cyclic inheritance in script class." ); |
| 565 | } |
| 566 | } |
| 567 | if (base_cache->_update_exports(r_err, true)) { |
| 568 | if (r_err && *r_err) { |
| 569 | return false; |
| 570 | } |
| 571 | changed = true; |
| 572 | } |
| 573 | } |
| 574 | |
| 575 | if ((changed || p_instance_to_update) && placeholders.size()) { //hm :( |
| 576 | |
| 577 | // update placeholders if any |
| 578 | HashMap<StringName, Variant> values; |
| 579 | List<PropertyInfo> propnames; |
| 580 | _update_exports_values(values, propnames); |
| 581 | |
| 582 | if (changed) { |
| 583 | for (PlaceHolderScriptInstance *E : placeholders) { |
| 584 | E->update(propnames, values); |
| 585 | } |
| 586 | } else { |
| 587 | p_instance_to_update->update(propnames, values); |
| 588 | } |
| 589 | } |
| 590 | |
| 591 | return changed; |
| 592 | |
| 593 | #else |
| 594 | return false; |
| 595 | #endif |
| 596 | } |
| 597 | |
| 598 | void GDScript::update_exports() { |
| 599 | #ifdef TOOLS_ENABLED |
| 600 | |
| 601 | bool cyclic_error = false; |
| 602 | _update_exports(&cyclic_error); |
| 603 | if (cyclic_error) { |
| 604 | return; |
| 605 | } |
| 606 | |
| 607 | HashSet<ObjectID> copy = inheriters_cache; //might get modified |
| 608 | |
| 609 | for (const ObjectID &E : copy) { |
| 610 | Object *id = ObjectDB::get_instance(E); |
| 611 | GDScript *s = Object::cast_to<GDScript>(id); |
| 612 | if (!s) { |
| 613 | continue; |
| 614 | } |
| 615 | s->update_exports(); |
| 616 | } |
| 617 | |
| 618 | #endif |
| 619 | } |
| 620 | |
| 621 | String GDScript::_get_debug_path() const { |
| 622 | if (is_built_in() && !get_name().is_empty()) { |
| 623 | return vformat("%s(%s)" , get_name(), get_script_path()); |
| 624 | } else { |
| 625 | return get_script_path(); |
| 626 | } |
| 627 | } |
| 628 | |
| 629 | Error GDScript::_static_init() { |
| 630 | if (static_initializer) { |
| 631 | Callable::CallError call_err; |
| 632 | static_initializer->call(nullptr, nullptr, 0, call_err); |
| 633 | if (call_err.error != Callable::CallError::CALL_OK) { |
| 634 | return ERR_CANT_CREATE; |
| 635 | } |
| 636 | } |
| 637 | Error err = OK; |
| 638 | for (KeyValue<StringName, Ref<GDScript>> &inner : subclasses) { |
| 639 | err = inner.value->_static_init(); |
| 640 | if (err) { |
| 641 | break; |
| 642 | } |
| 643 | } |
| 644 | return err; |
| 645 | } |
| 646 | |
| 647 | #ifdef TOOLS_ENABLED |
| 648 | |
| 649 | void GDScript::_save_old_static_data() { |
| 650 | old_static_variables_indices = static_variables_indices; |
| 651 | old_static_variables = static_variables; |
| 652 | for (KeyValue<StringName, Ref<GDScript>> &inner : subclasses) { |
| 653 | inner.value->_save_old_static_data(); |
| 654 | } |
| 655 | } |
| 656 | |
| 657 | void GDScript::_restore_old_static_data() { |
| 658 | for (KeyValue<StringName, MemberInfo> &E : old_static_variables_indices) { |
| 659 | if (static_variables_indices.has(E.key)) { |
| 660 | static_variables.write[static_variables_indices[E.key].index] = old_static_variables[E.value.index]; |
| 661 | } |
| 662 | } |
| 663 | old_static_variables_indices.clear(); |
| 664 | old_static_variables.clear(); |
| 665 | for (KeyValue<StringName, Ref<GDScript>> &inner : subclasses) { |
| 666 | inner.value->_restore_old_static_data(); |
| 667 | } |
| 668 | } |
| 669 | |
| 670 | #endif |
| 671 | |
| 672 | Error GDScript::reload(bool p_keep_state) { |
| 673 | if (reloading) { |
| 674 | return OK; |
| 675 | } |
| 676 | reloading = true; |
| 677 | |
| 678 | bool has_instances; |
| 679 | { |
| 680 | MutexLock lock(GDScriptLanguage::singleton->mutex); |
| 681 | |
| 682 | has_instances = instances.size(); |
| 683 | } |
| 684 | |
| 685 | ERR_FAIL_COND_V(!p_keep_state && has_instances, ERR_ALREADY_IN_USE); |
| 686 | |
| 687 | String basedir = path; |
| 688 | |
| 689 | if (basedir.is_empty()) { |
| 690 | basedir = get_path(); |
| 691 | } |
| 692 | |
| 693 | if (!basedir.is_empty()) { |
| 694 | basedir = basedir.get_base_dir(); |
| 695 | } |
| 696 | |
| 697 | // Loading a template, don't parse. |
| 698 | #ifdef TOOLS_ENABLED |
| 699 | if (EditorPaths::get_singleton() && basedir.begins_with(EditorPaths::get_singleton()->get_project_script_templates_dir())) { |
| 700 | reloading = false; |
| 701 | return OK; |
| 702 | } |
| 703 | #endif |
| 704 | |
| 705 | { |
| 706 | String source_path = path; |
| 707 | if (source_path.is_empty()) { |
| 708 | source_path = get_path(); |
| 709 | } |
| 710 | Ref<GDScript> cached_script = GDScriptCache::get_cached_script(source_path); |
| 711 | if (!source_path.is_empty() && cached_script.is_null()) { |
| 712 | MutexLock lock(GDScriptCache::singleton->mutex); |
| 713 | GDScriptCache::singleton->shallow_gdscript_cache[source_path] = Ref<GDScript>(this); |
| 714 | } |
| 715 | } |
| 716 | |
| 717 | bool can_run = ScriptServer::is_scripting_enabled() || is_tool(); |
| 718 | |
| 719 | #ifdef TOOLS_ENABLED |
| 720 | if (p_keep_state && can_run && is_valid()) { |
| 721 | _save_old_static_data(); |
| 722 | } |
| 723 | #endif |
| 724 | |
| 725 | valid = false; |
| 726 | GDScriptParser parser; |
| 727 | Error err = parser.parse(source, path, false); |
| 728 | if (err) { |
| 729 | if (EngineDebugger::is_active()) { |
| 730 | GDScriptLanguage::get_singleton()->debug_break_parse(_get_debug_path(), parser.get_errors().front()->get().line, "Parser Error: " + parser.get_errors().front()->get().message); |
| 731 | } |
| 732 | // TODO: Show all error messages. |
| 733 | _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); |
| 734 | reloading = false; |
| 735 | return ERR_PARSE_ERROR; |
| 736 | } |
| 737 | |
| 738 | GDScriptAnalyzer analyzer(&parser); |
| 739 | err = analyzer.analyze(); |
| 740 | |
| 741 | if (err) { |
| 742 | if (EngineDebugger::is_active()) { |
| 743 | GDScriptLanguage::get_singleton()->debug_break_parse(_get_debug_path(), parser.get_errors().front()->get().line, "Parser Error: " + parser.get_errors().front()->get().message); |
| 744 | } |
| 745 | |
| 746 | const List<GDScriptParser::ParserError>::Element *e = parser.get_errors().front(); |
| 747 | while (e != nullptr) { |
| 748 | _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); |
| 749 | e = e->next(); |
| 750 | } |
| 751 | reloading = false; |
| 752 | return ERR_PARSE_ERROR; |
| 753 | } |
| 754 | |
| 755 | can_run = ScriptServer::is_scripting_enabled() || parser.is_tool(); |
| 756 | |
| 757 | GDScriptCompiler compiler; |
| 758 | err = compiler.compile(&parser, this, p_keep_state); |
| 759 | |
| 760 | if (err) { |
| 761 | _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); |
| 762 | if (can_run) { |
| 763 | if (EngineDebugger::is_active()) { |
| 764 | GDScriptLanguage::get_singleton()->debug_break_parse(_get_debug_path(), compiler.get_error_line(), "Parser Error: " + compiler.get_error()); |
| 765 | } |
| 766 | reloading = false; |
| 767 | return ERR_COMPILATION_FAILED; |
| 768 | } else { |
| 769 | reloading = false; |
| 770 | return err; |
| 771 | } |
| 772 | } |
| 773 | |
| 774 | #ifdef TOOLS_ENABLED |
| 775 | // Done after compilation because it needs the GDScript object's inner class GDScript objects, |
| 776 | // which are made by calling make_scripts() within compiler.compile() above. |
| 777 | GDScriptDocGen::generate_docs(this, parser.get_tree()); |
| 778 | #endif |
| 779 | |
| 780 | #ifdef DEBUG_ENABLED |
| 781 | for (const GDScriptWarning &warning : parser.get_warnings()) { |
| 782 | if (EngineDebugger::is_active()) { |
| 783 | Vector<ScriptLanguage::StackInfo> si; |
| 784 | EngineDebugger::get_script_debugger()->send_error("" , get_script_path(), warning.start_line, warning.get_name(), warning.get_message(), false, ERR_HANDLER_WARNING, si); |
| 785 | } |
| 786 | } |
| 787 | #endif |
| 788 | |
| 789 | if (can_run) { |
| 790 | err = _static_init(); |
| 791 | if (err) { |
| 792 | return err; |
| 793 | } |
| 794 | } |
| 795 | |
| 796 | #ifdef TOOLS_ENABLED |
| 797 | if (can_run && p_keep_state) { |
| 798 | _restore_old_static_data(); |
| 799 | } |
| 800 | #endif |
| 801 | |
| 802 | reloading = false; |
| 803 | return OK; |
| 804 | } |
| 805 | |
| 806 | ScriptLanguage *GDScript::get_language() const { |
| 807 | return GDScriptLanguage::get_singleton(); |
| 808 | } |
| 809 | |
| 810 | void GDScript::get_constants(HashMap<StringName, Variant> *p_constants) { |
| 811 | if (p_constants) { |
| 812 | for (const KeyValue<StringName, Variant> &E : constants) { |
| 813 | (*p_constants)[E.key] = E.value; |
| 814 | } |
| 815 | } |
| 816 | } |
| 817 | |
| 818 | void GDScript::get_members(HashSet<StringName> *p_members) { |
| 819 | if (p_members) { |
| 820 | for (const StringName &E : members) { |
| 821 | p_members->insert(E); |
| 822 | } |
| 823 | } |
| 824 | } |
| 825 | |
| 826 | const Variant GDScript::get_rpc_config() const { |
| 827 | return rpc_config; |
| 828 | } |
| 829 | |
| 830 | void GDScript::unload_static() const { |
| 831 | GDScriptCache::remove_script(fully_qualified_name); |
| 832 | } |
| 833 | |
| 834 | Variant GDScript::callp(const StringName &p_method, const Variant **p_args, int p_argcount, Callable::CallError &r_error) { |
| 835 | GDScript *top = this; |
| 836 | while (top) { |
| 837 | HashMap<StringName, GDScriptFunction *>::Iterator E = top->member_functions.find(p_method); |
| 838 | if (E) { |
| 839 | ERR_FAIL_COND_V_MSG(!E->value->is_static(), Variant(), "Can't call non-static function '" + String(p_method) + "' in script." ); |
| 840 | |
| 841 | return E->value->call(nullptr, p_args, p_argcount, r_error); |
| 842 | } |
| 843 | top = top->_base; |
| 844 | } |
| 845 | |
| 846 | //none found, regular |
| 847 | |
| 848 | return Script::callp(p_method, p_args, p_argcount, r_error); |
| 849 | } |
| 850 | |
| 851 | bool GDScript::_get(const StringName &p_name, Variant &r_ret) const { |
| 852 | if (p_name == GDScriptLanguage::get_singleton()->strings._script_source) { |
| 853 | r_ret = get_source_code(); |
| 854 | return true; |
| 855 | } |
| 856 | |
| 857 | const GDScript *top = this; |
| 858 | while (top) { |
| 859 | { |
| 860 | HashMap<StringName, Variant>::ConstIterator E = top->constants.find(p_name); |
| 861 | if (E) { |
| 862 | r_ret = E->value; |
| 863 | return true; |
| 864 | } |
| 865 | } |
| 866 | |
| 867 | { |
| 868 | HashMap<StringName, MemberInfo>::ConstIterator E = top->static_variables_indices.find(p_name); |
| 869 | if (E) { |
| 870 | if (E->value.getter) { |
| 871 | Callable::CallError ce; |
| 872 | r_ret = const_cast<GDScript *>(this)->callp(E->value.getter, nullptr, 0, ce); |
| 873 | return true; |
| 874 | } |
| 875 | r_ret = top->static_variables[E->value.index]; |
| 876 | return true; |
| 877 | } |
| 878 | } |
| 879 | |
| 880 | { |
| 881 | HashMap<StringName, GDScriptFunction *>::ConstIterator E = top->member_functions.find(p_name); |
| 882 | if (E && E->value->is_static()) { |
| 883 | if (top->rpc_config.has(p_name)) { |
| 884 | r_ret = Callable(memnew(GDScriptRPCCallable(const_cast<GDScript *>(top), E->key))); |
| 885 | } else { |
| 886 | r_ret = Callable(const_cast<GDScript *>(top), E->key); |
| 887 | } |
| 888 | return true; |
| 889 | } |
| 890 | } |
| 891 | |
| 892 | { |
| 893 | HashMap<StringName, Ref<GDScript>>::ConstIterator E = top->subclasses.find(p_name); |
| 894 | if (E) { |
| 895 | r_ret = E->value; |
| 896 | return true; |
| 897 | } |
| 898 | } |
| 899 | |
| 900 | top = top->_base; |
| 901 | } |
| 902 | |
| 903 | return false; |
| 904 | } |
| 905 | |
| 906 | bool GDScript::_set(const StringName &p_name, const Variant &p_value) { |
| 907 | if (p_name == GDScriptLanguage::get_singleton()->strings._script_source) { |
| 908 | set_source_code(p_value); |
| 909 | reload(); |
| 910 | return true; |
| 911 | } |
| 912 | |
| 913 | GDScript *top = this; |
| 914 | while (top) { |
| 915 | HashMap<StringName, MemberInfo>::ConstIterator E = top->static_variables_indices.find(p_name); |
| 916 | if (E) { |
| 917 | const MemberInfo *member = &E->value; |
| 918 | Variant value = p_value; |
| 919 | if (member->data_type.has_type && !member->data_type.is_type(value)) { |
| 920 | const Variant *args = &p_value; |
| 921 | Callable::CallError err; |
| 922 | Variant::construct(member->data_type.builtin_type, value, &args, 1, err); |
| 923 | if (err.error != Callable::CallError::CALL_OK || !member->data_type.is_type(value)) { |
| 924 | return false; |
| 925 | } |
| 926 | } |
| 927 | if (member->setter) { |
| 928 | const Variant *args = &value; |
| 929 | Callable::CallError err; |
| 930 | callp(member->setter, &args, 1, err); |
| 931 | return err.error == Callable::CallError::CALL_OK; |
| 932 | } else { |
| 933 | top->static_variables.write[member->index] = value; |
| 934 | return true; |
| 935 | } |
| 936 | } |
| 937 | |
| 938 | top = top->_base; |
| 939 | } |
| 940 | |
| 941 | return false; |
| 942 | } |
| 943 | |
| 944 | void GDScript::_get_property_list(List<PropertyInfo> *p_properties) const { |
| 945 | p_properties->push_back(PropertyInfo(Variant::STRING, "script/source" , PROPERTY_HINT_NONE, "" , PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL)); |
| 946 | |
| 947 | List<const GDScript *> classes; |
| 948 | const GDScript *top = this; |
| 949 | while (top) { |
| 950 | classes.push_back(top); |
| 951 | top = top->_base; |
| 952 | } |
| 953 | |
| 954 | for (const List<const GDScript *>::Element *E = classes.back(); E; E = E->prev()) { |
| 955 | Vector<_GDScriptMemberSort> msort; |
| 956 | for (const KeyValue<StringName, MemberInfo> &F : E->get()->static_variables_indices) { |
| 957 | _GDScriptMemberSort ms; |
| 958 | ms.index = F.value.index; |
| 959 | ms.name = F.key; |
| 960 | msort.push_back(ms); |
| 961 | } |
| 962 | msort.sort(); |
| 963 | |
| 964 | for (int i = 0; i < msort.size(); i++) { |
| 965 | p_properties->push_back(E->get()->static_variables_indices[msort[i].name].property_info); |
| 966 | } |
| 967 | } |
| 968 | } |
| 969 | |
| 970 | void GDScript::_bind_methods() { |
| 971 | ClassDB::bind_vararg_method(METHOD_FLAGS_DEFAULT, "new" , &GDScript::_new, MethodInfo("new" )); |
| 972 | } |
| 973 | |
| 974 | void GDScript::set_path(const String &p_path, bool p_take_over) { |
| 975 | if (is_root_script()) { |
| 976 | Script::set_path(p_path, p_take_over); |
| 977 | } |
| 978 | |
| 979 | String old_path = path; |
| 980 | path = p_path; |
| 981 | GDScriptCache::move_script(old_path, p_path); |
| 982 | |
| 983 | for (KeyValue<StringName, Ref<GDScript>> &kv : subclasses) { |
| 984 | kv.value->set_path(p_path, p_take_over); |
| 985 | } |
| 986 | } |
| 987 | |
| 988 | String GDScript::get_script_path() const { |
| 989 | return path; |
| 990 | } |
| 991 | |
| 992 | Error GDScript::load_source_code(const String &p_path) { |
| 993 | if (p_path.is_empty() || p_path.begins_with("gdscript://" ) || ResourceLoader::get_resource_type(p_path.get_slice("::" , 0)) == "PackedScene" ) { |
| 994 | return OK; |
| 995 | } |
| 996 | |
| 997 | Vector<uint8_t> sourcef; |
| 998 | Error err; |
| 999 | Ref<FileAccess> f = FileAccess::open(p_path, FileAccess::READ, &err); |
| 1000 | if (err) { |
| 1001 | const char *err_name; |
| 1002 | if (err < 0 || err >= ERR_MAX) { |
| 1003 | err_name = "(invalid error code)" ; |
| 1004 | } else { |
| 1005 | err_name = error_names[err]; |
| 1006 | } |
| 1007 | ERR_FAIL_COND_V_MSG(err, err, "Attempt to open script '" + p_path + "' resulted in error '" + err_name + "'." ); |
| 1008 | } |
| 1009 | |
| 1010 | uint64_t len = f->get_length(); |
| 1011 | sourcef.resize(len + 1); |
| 1012 | uint8_t *w = sourcef.ptrw(); |
| 1013 | uint64_t r = f->get_buffer(w, len); |
| 1014 | ERR_FAIL_COND_V(r != len, ERR_CANT_OPEN); |
| 1015 | w[len] = 0; |
| 1016 | |
| 1017 | String s; |
| 1018 | if (s.parse_utf8((const char *)w) != OK) { |
| 1019 | ERR_FAIL_V_MSG(ERR_INVALID_DATA, "Script '" + p_path + "' contains invalid unicode (UTF-8), so it was not loaded. Please ensure that scripts are saved in valid UTF-8 unicode." ); |
| 1020 | } |
| 1021 | |
| 1022 | source = s; |
| 1023 | path = p_path; |
| 1024 | #ifdef TOOLS_ENABLED |
| 1025 | source_changed_cache = true; |
| 1026 | set_edited(false); |
| 1027 | set_last_modified_time(FileAccess::get_modified_time(path)); |
| 1028 | #endif // TOOLS_ENABLED |
| 1029 | return OK; |
| 1030 | } |
| 1031 | |
| 1032 | const HashMap<StringName, GDScriptFunction *> &GDScript::debug_get_member_functions() const { |
| 1033 | return member_functions; |
| 1034 | } |
| 1035 | |
| 1036 | StringName GDScript::debug_get_member_by_index(int p_idx) const { |
| 1037 | for (const KeyValue<StringName, MemberInfo> &E : member_indices) { |
| 1038 | if (E.value.index == p_idx) { |
| 1039 | return E.key; |
| 1040 | } |
| 1041 | } |
| 1042 | |
| 1043 | return "<error>" ; |
| 1044 | } |
| 1045 | |
| 1046 | StringName GDScript::debug_get_static_var_by_index(int p_idx) const { |
| 1047 | for (const KeyValue<StringName, MemberInfo> &E : static_variables_indices) { |
| 1048 | if (E.value.index == p_idx) { |
| 1049 | return E.key; |
| 1050 | } |
| 1051 | } |
| 1052 | |
| 1053 | return "<error>" ; |
| 1054 | } |
| 1055 | |
| 1056 | Ref<GDScript> GDScript::get_base() const { |
| 1057 | return base; |
| 1058 | } |
| 1059 | |
| 1060 | bool GDScript::inherits_script(const Ref<Script> &p_script) const { |
| 1061 | Ref<GDScript> gd = p_script; |
| 1062 | if (gd.is_null()) { |
| 1063 | return false; |
| 1064 | } |
| 1065 | |
| 1066 | const GDScript *s = this; |
| 1067 | |
| 1068 | while (s) { |
| 1069 | if (s == p_script.ptr()) { |
| 1070 | return true; |
| 1071 | } |
| 1072 | s = s->_base; |
| 1073 | } |
| 1074 | |
| 1075 | return false; |
| 1076 | } |
| 1077 | |
| 1078 | GDScript *GDScript::find_class(const String &p_qualified_name) { |
| 1079 | String first = p_qualified_name.get_slice("::" , 0); |
| 1080 | |
| 1081 | Vector<String> class_names; |
| 1082 | GDScript *result = nullptr; |
| 1083 | // Empty initial name means start here. |
| 1084 | if (first.is_empty() || first == global_name) { |
| 1085 | class_names = p_qualified_name.split("::" ); |
| 1086 | result = this; |
| 1087 | } else if (p_qualified_name.begins_with(get_root_script()->path)) { |
| 1088 | // Script path could have a class path separator("::") in it. |
| 1089 | class_names = p_qualified_name.trim_prefix(get_root_script()->path).split("::" ); |
| 1090 | result = get_root_script(); |
| 1091 | } else if (HashMap<StringName, Ref<GDScript>>::Iterator E = subclasses.find(first)) { |
| 1092 | class_names = p_qualified_name.split("::" ); |
| 1093 | result = E->value.ptr(); |
| 1094 | } else if (_owner != nullptr) { |
| 1095 | // Check parent scope. |
| 1096 | return _owner->find_class(p_qualified_name); |
| 1097 | } |
| 1098 | |
| 1099 | // Starts at index 1 because index 0 was handled above. |
| 1100 | for (int i = 1; result != nullptr && i < class_names.size(); i++) { |
| 1101 | String current_name = class_names[i]; |
| 1102 | if (HashMap<StringName, Ref<GDScript>>::Iterator E = result->subclasses.find(current_name)) { |
| 1103 | result = E->value.ptr(); |
| 1104 | } else { |
| 1105 | // Couldn't find inner class. |
| 1106 | return nullptr; |
| 1107 | } |
| 1108 | } |
| 1109 | |
| 1110 | return result; |
| 1111 | } |
| 1112 | |
| 1113 | bool GDScript::has_class(const GDScript *p_script) { |
| 1114 | String fqn = p_script->fully_qualified_name; |
| 1115 | if (fully_qualified_name.is_empty() && fqn.get_slice("::" , 0).is_empty()) { |
| 1116 | return p_script == this; |
| 1117 | } else if (fqn.begins_with(fully_qualified_name)) { |
| 1118 | return p_script == find_class(fqn.trim_prefix(fully_qualified_name)); |
| 1119 | } |
| 1120 | return false; |
| 1121 | } |
| 1122 | |
| 1123 | GDScript *GDScript::get_root_script() { |
| 1124 | GDScript *result = this; |
| 1125 | while (result->_owner) { |
| 1126 | result = result->_owner; |
| 1127 | } |
| 1128 | return result; |
| 1129 | } |
| 1130 | |
| 1131 | RBSet<GDScript *> GDScript::get_dependencies() { |
| 1132 | RBSet<GDScript *> dependencies; |
| 1133 | |
| 1134 | _get_dependencies(dependencies, this); |
| 1135 | dependencies.erase(this); |
| 1136 | |
| 1137 | return dependencies; |
| 1138 | } |
| 1139 | |
| 1140 | RBSet<GDScript *> GDScript::get_inverted_dependencies() { |
| 1141 | RBSet<GDScript *> inverted_dependencies; |
| 1142 | |
| 1143 | List<GDScript *> scripts; |
| 1144 | { |
| 1145 | MutexLock lock(GDScriptLanguage::singleton->mutex); |
| 1146 | |
| 1147 | SelfList<GDScript> *elem = GDScriptLanguage::singleton->script_list.first(); |
| 1148 | while (elem) { |
| 1149 | scripts.push_back(elem->self()); |
| 1150 | elem = elem->next(); |
| 1151 | } |
| 1152 | } |
| 1153 | |
| 1154 | for (GDScript *scr : scripts) { |
| 1155 | if (scr == nullptr || scr == this || scr->destructing) { |
| 1156 | continue; |
| 1157 | } |
| 1158 | |
| 1159 | RBSet<GDScript *> scr_dependencies = scr->get_dependencies(); |
| 1160 | if (scr_dependencies.has(this)) { |
| 1161 | inverted_dependencies.insert(scr); |
| 1162 | } |
| 1163 | } |
| 1164 | |
| 1165 | return inverted_dependencies; |
| 1166 | } |
| 1167 | |
| 1168 | RBSet<GDScript *> GDScript::get_must_clear_dependencies() { |
| 1169 | RBSet<GDScript *> dependencies = get_dependencies(); |
| 1170 | RBSet<GDScript *> must_clear_dependencies; |
| 1171 | HashMap<GDScript *, RBSet<GDScript *>> inverted_dependencies; |
| 1172 | |
| 1173 | for (GDScript *E : dependencies) { |
| 1174 | inverted_dependencies.insert(E, E->get_inverted_dependencies()); |
| 1175 | } |
| 1176 | |
| 1177 | RBSet<GDScript *> cant_clear; |
| 1178 | for (KeyValue<GDScript *, RBSet<GDScript *>> &E : inverted_dependencies) { |
| 1179 | for (GDScript *F : E.value) { |
| 1180 | if (!dependencies.has(F)) { |
| 1181 | cant_clear.insert(E.key); |
| 1182 | for (GDScript *G : E.key->get_dependencies()) { |
| 1183 | cant_clear.insert(G); |
| 1184 | } |
| 1185 | break; |
| 1186 | } |
| 1187 | } |
| 1188 | } |
| 1189 | |
| 1190 | for (KeyValue<GDScript *, RBSet<GDScript *>> &E : inverted_dependencies) { |
| 1191 | if (cant_clear.has(E.key) || ScriptServer::is_global_class(E.key->get_fully_qualified_name())) { |
| 1192 | continue; |
| 1193 | } |
| 1194 | must_clear_dependencies.insert(E.key); |
| 1195 | } |
| 1196 | |
| 1197 | cant_clear.clear(); |
| 1198 | dependencies.clear(); |
| 1199 | inverted_dependencies.clear(); |
| 1200 | return must_clear_dependencies; |
| 1201 | } |
| 1202 | |
| 1203 | bool GDScript::has_script_signal(const StringName &p_signal) const { |
| 1204 | if (_signals.has(p_signal)) { |
| 1205 | return true; |
| 1206 | } |
| 1207 | if (base.is_valid()) { |
| 1208 | return base->has_script_signal(p_signal); |
| 1209 | } |
| 1210 | #ifdef TOOLS_ENABLED |
| 1211 | else if (base_cache.is_valid()) { |
| 1212 | return base_cache->has_script_signal(p_signal); |
| 1213 | } |
| 1214 | #endif |
| 1215 | return false; |
| 1216 | } |
| 1217 | |
| 1218 | void GDScript::_get_script_signal_list(List<MethodInfo> *r_list, bool p_include_base) const { |
| 1219 | for (const KeyValue<StringName, MethodInfo> &E : _signals) { |
| 1220 | r_list->push_back(E.value); |
| 1221 | } |
| 1222 | |
| 1223 | if (!p_include_base) { |
| 1224 | return; |
| 1225 | } |
| 1226 | |
| 1227 | if (base.is_valid()) { |
| 1228 | base->get_script_signal_list(r_list); |
| 1229 | } |
| 1230 | #ifdef TOOLS_ENABLED |
| 1231 | else if (base_cache.is_valid()) { |
| 1232 | base_cache->get_script_signal_list(r_list); |
| 1233 | } |
| 1234 | #endif |
| 1235 | } |
| 1236 | |
| 1237 | void GDScript::get_script_signal_list(List<MethodInfo> *r_signals) const { |
| 1238 | _get_script_signal_list(r_signals, true); |
| 1239 | } |
| 1240 | |
| 1241 | GDScript *GDScript::_get_gdscript_from_variant(const Variant &p_variant) { |
| 1242 | Object *obj = p_variant; |
| 1243 | if (obj == nullptr || obj->get_instance_id().is_null()) { |
| 1244 | return nullptr; |
| 1245 | } |
| 1246 | return Object::cast_to<GDScript>(obj); |
| 1247 | } |
| 1248 | |
| 1249 | void GDScript::_get_dependencies(RBSet<GDScript *> &p_dependencies, const GDScript *p_except) { |
| 1250 | if (p_dependencies.has(this)) { |
| 1251 | return; |
| 1252 | } |
| 1253 | p_dependencies.insert(this); |
| 1254 | |
| 1255 | for (const KeyValue<StringName, GDScriptFunction *> &E : member_functions) { |
| 1256 | if (E.value == nullptr) { |
| 1257 | continue; |
| 1258 | } |
| 1259 | for (const Variant &V : E.value->constants) { |
| 1260 | GDScript *scr = _get_gdscript_from_variant(V); |
| 1261 | if (scr != nullptr && scr != p_except) { |
| 1262 | scr->_get_dependencies(p_dependencies, p_except); |
| 1263 | } |
| 1264 | } |
| 1265 | } |
| 1266 | |
| 1267 | if (implicit_initializer) { |
| 1268 | for (const Variant &V : implicit_initializer->constants) { |
| 1269 | GDScript *scr = _get_gdscript_from_variant(V); |
| 1270 | if (scr != nullptr && scr != p_except) { |
| 1271 | scr->_get_dependencies(p_dependencies, p_except); |
| 1272 | } |
| 1273 | } |
| 1274 | } |
| 1275 | |
| 1276 | if (implicit_ready) { |
| 1277 | for (const Variant &V : implicit_ready->constants) { |
| 1278 | GDScript *scr = _get_gdscript_from_variant(V); |
| 1279 | if (scr != nullptr && scr != p_except) { |
| 1280 | scr->_get_dependencies(p_dependencies, p_except); |
| 1281 | } |
| 1282 | } |
| 1283 | } |
| 1284 | |
| 1285 | for (KeyValue<StringName, Ref<GDScript>> &E : subclasses) { |
| 1286 | if (E.value != p_except) { |
| 1287 | E.value->_get_dependencies(p_dependencies, p_except); |
| 1288 | } |
| 1289 | } |
| 1290 | |
| 1291 | for (const KeyValue<StringName, Variant> &E : constants) { |
| 1292 | GDScript *scr = _get_gdscript_from_variant(E.value); |
| 1293 | if (scr != nullptr && scr != p_except) { |
| 1294 | scr->_get_dependencies(p_dependencies, p_except); |
| 1295 | } |
| 1296 | } |
| 1297 | } |
| 1298 | |
| 1299 | GDScript::GDScript() : |
| 1300 | script_list(this) { |
| 1301 | { |
| 1302 | MutexLock lock(GDScriptLanguage::get_singleton()->mutex); |
| 1303 | |
| 1304 | GDScriptLanguage::get_singleton()->script_list.add(&script_list); |
| 1305 | } |
| 1306 | |
| 1307 | path = vformat("gdscript://%d.gd" , get_instance_id()); |
| 1308 | } |
| 1309 | |
| 1310 | void GDScript::_save_orphaned_subclasses(ClearData *p_clear_data) { |
| 1311 | struct ClassRefWithName { |
| 1312 | ObjectID id; |
| 1313 | String fully_qualified_name; |
| 1314 | }; |
| 1315 | Vector<ClassRefWithName> weak_subclasses; |
| 1316 | // collect subclasses ObjectID and name |
| 1317 | for (KeyValue<StringName, Ref<GDScript>> &E : subclasses) { |
| 1318 | E.value->_owner = nullptr; //bye, you are no longer owned cause I died |
| 1319 | ClassRefWithName subclass; |
| 1320 | subclass.id = E.value->get_instance_id(); |
| 1321 | subclass.fully_qualified_name = E.value->fully_qualified_name; |
| 1322 | weak_subclasses.push_back(subclass); |
| 1323 | } |
| 1324 | |
| 1325 | // clear subclasses to allow unused subclasses to be deleted |
| 1326 | for (KeyValue<StringName, Ref<GDScript>> &E : subclasses) { |
| 1327 | p_clear_data->scripts.insert(E.value); |
| 1328 | } |
| 1329 | subclasses.clear(); |
| 1330 | // subclasses are also held by constants, clear those as well |
| 1331 | for (KeyValue<StringName, Variant> &E : constants) { |
| 1332 | GDScript *gdscr = _get_gdscript_from_variant(E.value); |
| 1333 | if (gdscr != nullptr) { |
| 1334 | p_clear_data->scripts.insert(gdscr); |
| 1335 | } |
| 1336 | } |
| 1337 | constants.clear(); |
| 1338 | |
| 1339 | // keep orphan subclass only for subclasses that are still in use |
| 1340 | for (int i = 0; i < weak_subclasses.size(); i++) { |
| 1341 | ClassRefWithName subclass = weak_subclasses[i]; |
| 1342 | Object *obj = ObjectDB::get_instance(subclass.id); |
| 1343 | if (!obj) { |
| 1344 | continue; |
| 1345 | } |
| 1346 | // subclass is not released |
| 1347 | GDScriptLanguage::get_singleton()->add_orphan_subclass(subclass.fully_qualified_name, subclass.id); |
| 1348 | } |
| 1349 | } |
| 1350 | |
| 1351 | #ifdef DEBUG_ENABLED |
| 1352 | String GDScript::debug_get_script_name(const Ref<Script> &p_script) { |
| 1353 | if (p_script.is_valid()) { |
| 1354 | Ref<GDScript> gdscript = p_script; |
| 1355 | if (gdscript.is_valid()) { |
| 1356 | if (gdscript->get_local_name() != StringName()) { |
| 1357 | return gdscript->get_local_name(); |
| 1358 | } |
| 1359 | return gdscript->get_fully_qualified_name().get_file(); |
| 1360 | } |
| 1361 | |
| 1362 | if (p_script->get_global_name() != StringName()) { |
| 1363 | return p_script->get_global_name(); |
| 1364 | } else if (!p_script->get_path().is_empty()) { |
| 1365 | return p_script->get_path().get_file(); |
| 1366 | } else if (!p_script->get_name().is_empty()) { |
| 1367 | return p_script->get_name(); // Resource name. |
| 1368 | } |
| 1369 | } |
| 1370 | |
| 1371 | return "<unknown script>" ; |
| 1372 | } |
| 1373 | #endif |
| 1374 | |
| 1375 | void GDScript::clear(ClearData *p_clear_data) { |
| 1376 | if (clearing) { |
| 1377 | return; |
| 1378 | } |
| 1379 | clearing = true; |
| 1380 | |
| 1381 | ClearData data; |
| 1382 | ClearData *clear_data = p_clear_data; |
| 1383 | bool is_root = false; |
| 1384 | |
| 1385 | // If `clear_data` is `nullptr`, it means that it's the root. |
| 1386 | // The root is in charge to clear functions and scripts of itself and its dependencies |
| 1387 | if (clear_data == nullptr) { |
| 1388 | clear_data = &data; |
| 1389 | is_root = true; |
| 1390 | } |
| 1391 | |
| 1392 | RBSet<GDScript *> must_clear_dependencies = get_must_clear_dependencies(); |
| 1393 | for (GDScript *E : must_clear_dependencies) { |
| 1394 | clear_data->scripts.insert(E); |
| 1395 | E->clear(clear_data); |
| 1396 | } |
| 1397 | |
| 1398 | for (const KeyValue<StringName, GDScriptFunction *> &E : member_functions) { |
| 1399 | clear_data->functions.insert(E.value); |
| 1400 | } |
| 1401 | member_functions.clear(); |
| 1402 | |
| 1403 | for (KeyValue<StringName, MemberInfo> &E : member_indices) { |
| 1404 | clear_data->scripts.insert(E.value.data_type.script_type_ref); |
| 1405 | E.value.data_type.script_type_ref = Ref<Script>(); |
| 1406 | } |
| 1407 | |
| 1408 | for (KeyValue<StringName, MemberInfo> &E : static_variables_indices) { |
| 1409 | clear_data->scripts.insert(E.value.data_type.script_type_ref); |
| 1410 | E.value.data_type.script_type_ref = Ref<Script>(); |
| 1411 | } |
| 1412 | static_variables.clear(); |
| 1413 | static_variables_indices.clear(); |
| 1414 | |
| 1415 | if (implicit_initializer) { |
| 1416 | clear_data->functions.insert(implicit_initializer); |
| 1417 | implicit_initializer = nullptr; |
| 1418 | } |
| 1419 | |
| 1420 | if (implicit_ready) { |
| 1421 | clear_data->functions.insert(implicit_ready); |
| 1422 | implicit_ready = nullptr; |
| 1423 | } |
| 1424 | |
| 1425 | if (static_initializer) { |
| 1426 | clear_data->functions.insert(static_initializer); |
| 1427 | static_initializer = nullptr; |
| 1428 | } |
| 1429 | |
| 1430 | _save_orphaned_subclasses(clear_data); |
| 1431 | |
| 1432 | #ifdef TOOLS_ENABLED |
| 1433 | // Clearing inner class doc, script doc only cleared when the script source deleted. |
| 1434 | if (_owner) { |
| 1435 | _clear_doc(); |
| 1436 | } |
| 1437 | #endif |
| 1438 | |
| 1439 | // If it's not the root, skip clearing the data |
| 1440 | if (is_root) { |
| 1441 | // All dependencies have been accounted for |
| 1442 | for (GDScriptFunction *E : clear_data->functions) { |
| 1443 | memdelete(E); |
| 1444 | } |
| 1445 | for (Ref<Script> &E : clear_data->scripts) { |
| 1446 | Ref<GDScript> gdscr = E; |
| 1447 | if (gdscr.is_valid()) { |
| 1448 | GDScriptCache::remove_script(gdscr->get_path()); |
| 1449 | } |
| 1450 | } |
| 1451 | clear_data->clear(); |
| 1452 | } |
| 1453 | } |
| 1454 | |
| 1455 | GDScript::~GDScript() { |
| 1456 | if (destructing) { |
| 1457 | return; |
| 1458 | } |
| 1459 | destructing = true; |
| 1460 | |
| 1461 | clear(); |
| 1462 | |
| 1463 | { |
| 1464 | MutexLock lock(GDScriptLanguage::get_singleton()->mutex); |
| 1465 | |
| 1466 | while (SelfList<GDScriptFunctionState> *E = pending_func_states.first()) { |
| 1467 | // Order matters since clearing the stack may already cause |
| 1468 | // the GDScriptFunctionState to be destroyed and thus removed from the list. |
| 1469 | pending_func_states.remove(E); |
| 1470 | GDScriptFunctionState *state = E->self(); |
| 1471 | ObjectID state_id = state->get_instance_id(); |
| 1472 | state->_clear_connections(); |
| 1473 | if (ObjectDB::get_instance(state_id)) { |
| 1474 | state->_clear_stack(); |
| 1475 | } |
| 1476 | } |
| 1477 | } |
| 1478 | |
| 1479 | { |
| 1480 | MutexLock lock(GDScriptLanguage::get_singleton()->mutex); |
| 1481 | |
| 1482 | script_list.remove_from_list(); |
| 1483 | } |
| 1484 | } |
| 1485 | |
| 1486 | ////////////////////////////// |
| 1487 | // INSTANCE // |
| 1488 | ////////////////////////////// |
| 1489 | |
| 1490 | bool GDScriptInstance::set(const StringName &p_name, const Variant &p_value) { |
| 1491 | { |
| 1492 | HashMap<StringName, GDScript::MemberInfo>::Iterator E = script->member_indices.find(p_name); |
| 1493 | if (E) { |
| 1494 | const GDScript::MemberInfo *member = &E->value; |
| 1495 | Variant value = p_value; |
| 1496 | if (member->data_type.has_type && !member->data_type.is_type(value)) { |
| 1497 | const Variant *args = &p_value; |
| 1498 | Callable::CallError err; |
| 1499 | Variant::construct(member->data_type.builtin_type, value, &args, 1, err); |
| 1500 | if (err.error != Callable::CallError::CALL_OK || !member->data_type.is_type(value)) { |
| 1501 | return false; |
| 1502 | } |
| 1503 | } |
| 1504 | if (member->setter) { |
| 1505 | const Variant *args = &value; |
| 1506 | Callable::CallError err; |
| 1507 | callp(member->setter, &args, 1, err); |
| 1508 | return err.error == Callable::CallError::CALL_OK; |
| 1509 | } else { |
| 1510 | members.write[member->index] = value; |
| 1511 | return true; |
| 1512 | } |
| 1513 | } |
| 1514 | } |
| 1515 | |
| 1516 | GDScript *sptr = script.ptr(); |
| 1517 | while (sptr) { |
| 1518 | { |
| 1519 | HashMap<StringName, GDScript::MemberInfo>::ConstIterator E = sptr->static_variables_indices.find(p_name); |
| 1520 | if (E) { |
| 1521 | const GDScript::MemberInfo *member = &E->value; |
| 1522 | Variant value = p_value; |
| 1523 | if (member->data_type.has_type && !member->data_type.is_type(value)) { |
| 1524 | const Variant *args = &p_value; |
| 1525 | Callable::CallError err; |
| 1526 | Variant::construct(member->data_type.builtin_type, value, &args, 1, err); |
| 1527 | if (err.error != Callable::CallError::CALL_OK || !member->data_type.is_type(value)) { |
| 1528 | return false; |
| 1529 | } |
| 1530 | } |
| 1531 | if (member->setter) { |
| 1532 | const Variant *args = &value; |
| 1533 | Callable::CallError err; |
| 1534 | callp(member->setter, &args, 1, err); |
| 1535 | return err.error == Callable::CallError::CALL_OK; |
| 1536 | } else { |
| 1537 | sptr->static_variables.write[member->index] = value; |
| 1538 | return true; |
| 1539 | } |
| 1540 | } |
| 1541 | } |
| 1542 | |
| 1543 | { |
| 1544 | HashMap<StringName, GDScriptFunction *>::Iterator E = sptr->member_functions.find(GDScriptLanguage::get_singleton()->strings._set); |
| 1545 | if (E) { |
| 1546 | Variant name = p_name; |
| 1547 | const Variant *args[2] = { &name, &p_value }; |
| 1548 | |
| 1549 | Callable::CallError err; |
| 1550 | Variant ret = E->value->call(this, (const Variant **)args, 2, err); |
| 1551 | if (err.error == Callable::CallError::CALL_OK && ret.get_type() == Variant::BOOL && ret.operator bool()) { |
| 1552 | return true; |
| 1553 | } |
| 1554 | } |
| 1555 | } |
| 1556 | |
| 1557 | sptr = sptr->_base; |
| 1558 | } |
| 1559 | |
| 1560 | return false; |
| 1561 | } |
| 1562 | |
| 1563 | bool GDScriptInstance::get(const StringName &p_name, Variant &r_ret) const { |
| 1564 | { |
| 1565 | HashMap<StringName, GDScript::MemberInfo>::ConstIterator E = script->member_indices.find(p_name); |
| 1566 | if (E) { |
| 1567 | if (E->value.getter) { |
| 1568 | Callable::CallError err; |
| 1569 | r_ret = const_cast<GDScriptInstance *>(this)->callp(E->value.getter, nullptr, 0, err); |
| 1570 | if (err.error == Callable::CallError::CALL_OK) { |
| 1571 | return true; |
| 1572 | } |
| 1573 | } |
| 1574 | r_ret = members[E->value.index]; |
| 1575 | return true; |
| 1576 | } |
| 1577 | } |
| 1578 | |
| 1579 | const GDScript *sptr = script.ptr(); |
| 1580 | while (sptr) { |
| 1581 | { |
| 1582 | HashMap<StringName, Variant>::ConstIterator E = sptr->constants.find(p_name); |
| 1583 | if (E) { |
| 1584 | r_ret = E->value; |
| 1585 | return true; |
| 1586 | } |
| 1587 | } |
| 1588 | |
| 1589 | { |
| 1590 | HashMap<StringName, GDScript::MemberInfo>::ConstIterator E = sptr->static_variables_indices.find(p_name); |
| 1591 | if (E) { |
| 1592 | if (E->value.getter) { |
| 1593 | Callable::CallError ce; |
| 1594 | r_ret = const_cast<GDScript *>(sptr)->callp(E->value.getter, nullptr, 0, ce); |
| 1595 | return true; |
| 1596 | } |
| 1597 | r_ret = sptr->static_variables[E->value.index]; |
| 1598 | return true; |
| 1599 | } |
| 1600 | } |
| 1601 | |
| 1602 | { |
| 1603 | HashMap<StringName, MethodInfo>::ConstIterator E = sptr->_signals.find(p_name); |
| 1604 | if (E) { |
| 1605 | r_ret = Signal(this->owner, E->key); |
| 1606 | return true; |
| 1607 | } |
| 1608 | } |
| 1609 | |
| 1610 | { |
| 1611 | HashMap<StringName, GDScriptFunction *>::ConstIterator E = sptr->member_functions.find(p_name); |
| 1612 | if (E) { |
| 1613 | if (sptr->rpc_config.has(p_name)) { |
| 1614 | r_ret = Callable(memnew(GDScriptRPCCallable(this->owner, E->key))); |
| 1615 | } else { |
| 1616 | r_ret = Callable(this->owner, E->key); |
| 1617 | } |
| 1618 | return true; |
| 1619 | } |
| 1620 | } |
| 1621 | |
| 1622 | { |
| 1623 | HashMap<StringName, Ref<GDScript>>::ConstIterator E = sptr->subclasses.find(p_name); |
| 1624 | if (E) { |
| 1625 | r_ret = E->value; |
| 1626 | return true; |
| 1627 | } |
| 1628 | } |
| 1629 | |
| 1630 | { |
| 1631 | HashMap<StringName, GDScriptFunction *>::ConstIterator E = sptr->member_functions.find(GDScriptLanguage::get_singleton()->strings._get); |
| 1632 | if (E) { |
| 1633 | Variant name = p_name; |
| 1634 | const Variant *args[1] = { &name }; |
| 1635 | |
| 1636 | Callable::CallError err; |
| 1637 | Variant ret = const_cast<GDScriptFunction *>(E->value)->call(const_cast<GDScriptInstance *>(this), (const Variant **)args, 1, err); |
| 1638 | if (err.error == Callable::CallError::CALL_OK && ret.get_type() != Variant::NIL) { |
| 1639 | r_ret = ret; |
| 1640 | return true; |
| 1641 | } |
| 1642 | } |
| 1643 | } |
| 1644 | sptr = sptr->_base; |
| 1645 | } |
| 1646 | |
| 1647 | return false; |
| 1648 | } |
| 1649 | |
| 1650 | Variant::Type GDScriptInstance::get_property_type(const StringName &p_name, bool *r_is_valid) const { |
| 1651 | const GDScript *sptr = script.ptr(); |
| 1652 | while (sptr) { |
| 1653 | if (sptr->member_indices.has(p_name)) { |
| 1654 | if (r_is_valid) { |
| 1655 | *r_is_valid = true; |
| 1656 | } |
| 1657 | return sptr->member_indices[p_name].property_info.type; |
| 1658 | } |
| 1659 | sptr = sptr->_base; |
| 1660 | } |
| 1661 | |
| 1662 | if (r_is_valid) { |
| 1663 | *r_is_valid = false; |
| 1664 | } |
| 1665 | return Variant::NIL; |
| 1666 | } |
| 1667 | |
| 1668 | void GDScriptInstance::validate_property(PropertyInfo &p_property) const { |
| 1669 | Variant property = (Dictionary)p_property; |
| 1670 | const Variant *args[1] = { &property }; |
| 1671 | |
| 1672 | const GDScript *sptr = script.ptr(); |
| 1673 | while (sptr) { |
| 1674 | HashMap<StringName, GDScriptFunction *>::ConstIterator E = sptr->member_functions.find(GDScriptLanguage::get_singleton()->strings._validate_property); |
| 1675 | if (E) { |
| 1676 | Callable::CallError err; |
| 1677 | Variant ret = E->value->call(const_cast<GDScriptInstance *>(this), args, 1, err); |
| 1678 | if (err.error == Callable::CallError::CALL_OK) { |
| 1679 | p_property = PropertyInfo::from_dict(property); |
| 1680 | return; |
| 1681 | } |
| 1682 | } |
| 1683 | sptr = sptr->_base; |
| 1684 | } |
| 1685 | } |
| 1686 | |
| 1687 | void GDScriptInstance::get_property_list(List<PropertyInfo> *p_properties) const { |
| 1688 | // exported members, not done yet! |
| 1689 | |
| 1690 | const GDScript *sptr = script.ptr(); |
| 1691 | List<PropertyInfo> props; |
| 1692 | |
| 1693 | while (sptr) { |
| 1694 | HashMap<StringName, GDScriptFunction *>::ConstIterator E = sptr->member_functions.find(GDScriptLanguage::get_singleton()->strings._get_property_list); |
| 1695 | if (E) { |
| 1696 | Callable::CallError err; |
| 1697 | Variant ret = const_cast<GDScriptFunction *>(E->value)->call(const_cast<GDScriptInstance *>(this), nullptr, 0, err); |
| 1698 | if (err.error == Callable::CallError::CALL_OK) { |
| 1699 | ERR_FAIL_COND_MSG(ret.get_type() != Variant::ARRAY, "Wrong type for _get_property_list, must be an array of dictionaries." ); |
| 1700 | |
| 1701 | Array arr = ret; |
| 1702 | for (int i = 0; i < arr.size(); i++) { |
| 1703 | Dictionary d = arr[i]; |
| 1704 | ERR_CONTINUE(!d.has("name" )); |
| 1705 | ERR_CONTINUE(!d.has("type" )); |
| 1706 | |
| 1707 | PropertyInfo pinfo; |
| 1708 | pinfo.name = d["name" ]; |
| 1709 | pinfo.type = Variant::Type(d["type" ].operator int()); |
| 1710 | if (d.has("hint" )) { |
| 1711 | pinfo.hint = PropertyHint(d["hint" ].operator int()); |
| 1712 | } |
| 1713 | if (d.has("hint_string" )) { |
| 1714 | pinfo.hint_string = d["hint_string" ]; |
| 1715 | } |
| 1716 | if (d.has("usage" )) { |
| 1717 | pinfo.usage = d["usage" ]; |
| 1718 | } |
| 1719 | if (d.has("class_name" )) { |
| 1720 | pinfo.class_name = d["class_name" ]; |
| 1721 | } |
| 1722 | |
| 1723 | ERR_CONTINUE(pinfo.name.is_empty() && (pinfo.usage & PROPERTY_USAGE_STORAGE)); |
| 1724 | ERR_CONTINUE(pinfo.type < 0 || pinfo.type >= Variant::VARIANT_MAX); |
| 1725 | |
| 1726 | props.push_back(pinfo); |
| 1727 | } |
| 1728 | } |
| 1729 | } |
| 1730 | |
| 1731 | //instance a fake script for editing the values |
| 1732 | |
| 1733 | Vector<_GDScriptMemberSort> msort; |
| 1734 | for (const KeyValue<StringName, GDScript::MemberInfo> &F : sptr->member_indices) { |
| 1735 | _GDScriptMemberSort ms; |
| 1736 | ms.index = F.value.index; |
| 1737 | ms.name = F.key; |
| 1738 | msort.push_back(ms); |
| 1739 | } |
| 1740 | |
| 1741 | msort.sort(); |
| 1742 | msort.reverse(); |
| 1743 | for (int i = 0; i < msort.size(); i++) { |
| 1744 | props.push_front(sptr->member_indices[msort[i].name].property_info); |
| 1745 | } |
| 1746 | |
| 1747 | #ifdef TOOLS_ENABLED |
| 1748 | p_properties->push_back(sptr->get_class_category()); |
| 1749 | #endif // TOOLS_ENABLED |
| 1750 | |
| 1751 | for (PropertyInfo &prop : props) { |
| 1752 | validate_property(prop); |
| 1753 | p_properties->push_back(prop); |
| 1754 | } |
| 1755 | |
| 1756 | props.clear(); |
| 1757 | |
| 1758 | sptr = sptr->_base; |
| 1759 | } |
| 1760 | } |
| 1761 | |
| 1762 | bool GDScriptInstance::property_can_revert(const StringName &p_name) const { |
| 1763 | Variant name = p_name; |
| 1764 | const Variant *args[1] = { &name }; |
| 1765 | |
| 1766 | const GDScript *sptr = script.ptr(); |
| 1767 | while (sptr) { |
| 1768 | HashMap<StringName, GDScriptFunction *>::ConstIterator E = sptr->member_functions.find(GDScriptLanguage::get_singleton()->strings._property_can_revert); |
| 1769 | if (E) { |
| 1770 | Callable::CallError err; |
| 1771 | Variant ret = E->value->call(const_cast<GDScriptInstance *>(this), args, 1, err); |
| 1772 | if (err.error == Callable::CallError::CALL_OK && ret.get_type() == Variant::BOOL && ret.operator bool()) { |
| 1773 | return true; |
| 1774 | } |
| 1775 | } |
| 1776 | sptr = sptr->_base; |
| 1777 | } |
| 1778 | |
| 1779 | return false; |
| 1780 | } |
| 1781 | |
| 1782 | bool GDScriptInstance::property_get_revert(const StringName &p_name, Variant &r_ret) const { |
| 1783 | Variant name = p_name; |
| 1784 | const Variant *args[1] = { &name }; |
| 1785 | |
| 1786 | const GDScript *sptr = script.ptr(); |
| 1787 | while (sptr) { |
| 1788 | HashMap<StringName, GDScriptFunction *>::ConstIterator E = sptr->member_functions.find(GDScriptLanguage::get_singleton()->strings._property_get_revert); |
| 1789 | if (E) { |
| 1790 | Callable::CallError err; |
| 1791 | Variant ret = E->value->call(const_cast<GDScriptInstance *>(this), args, 1, err); |
| 1792 | if (err.error == Callable::CallError::CALL_OK && ret.get_type() != Variant::NIL) { |
| 1793 | r_ret = ret; |
| 1794 | return true; |
| 1795 | } |
| 1796 | } |
| 1797 | sptr = sptr->_base; |
| 1798 | } |
| 1799 | |
| 1800 | return false; |
| 1801 | } |
| 1802 | |
| 1803 | void GDScriptInstance::get_method_list(List<MethodInfo> *p_list) const { |
| 1804 | const GDScript *sptr = script.ptr(); |
| 1805 | while (sptr) { |
| 1806 | for (const KeyValue<StringName, GDScriptFunction *> &E : sptr->member_functions) { |
| 1807 | p_list->push_back(E.value->get_method_info()); |
| 1808 | } |
| 1809 | sptr = sptr->_base; |
| 1810 | } |
| 1811 | } |
| 1812 | |
| 1813 | bool GDScriptInstance::has_method(const StringName &p_method) const { |
| 1814 | const GDScript *sptr = script.ptr(); |
| 1815 | while (sptr) { |
| 1816 | HashMap<StringName, GDScriptFunction *>::ConstIterator E = sptr->member_functions.find(p_method); |
| 1817 | if (E) { |
| 1818 | return true; |
| 1819 | } |
| 1820 | sptr = sptr->_base; |
| 1821 | } |
| 1822 | |
| 1823 | return false; |
| 1824 | } |
| 1825 | |
| 1826 | Variant GDScriptInstance::callp(const StringName &p_method, const Variant **p_args, int p_argcount, Callable::CallError &r_error) { |
| 1827 | GDScript *sptr = script.ptr(); |
| 1828 | if (unlikely(p_method == SNAME("_ready" ))) { |
| 1829 | // Call implicit ready first, including for the super classes. |
| 1830 | while (sptr) { |
| 1831 | if (sptr->implicit_ready) { |
| 1832 | sptr->implicit_ready->call(this, nullptr, 0, r_error); |
| 1833 | } |
| 1834 | sptr = sptr->_base; |
| 1835 | } |
| 1836 | |
| 1837 | // Reset this back for the regular call. |
| 1838 | sptr = script.ptr(); |
| 1839 | } |
| 1840 | while (sptr) { |
| 1841 | HashMap<StringName, GDScriptFunction *>::Iterator E = sptr->member_functions.find(p_method); |
| 1842 | if (E) { |
| 1843 | return E->value->call(this, p_args, p_argcount, r_error); |
| 1844 | } |
| 1845 | sptr = sptr->_base; |
| 1846 | } |
| 1847 | r_error.error = Callable::CallError::CALL_ERROR_INVALID_METHOD; |
| 1848 | return Variant(); |
| 1849 | } |
| 1850 | |
| 1851 | void GDScriptInstance::notification(int p_notification, bool p_reversed) { |
| 1852 | //notification is not virtual, it gets called at ALL levels just like in C. |
| 1853 | Variant value = p_notification; |
| 1854 | const Variant *args[1] = { &value }; |
| 1855 | |
| 1856 | List<GDScript *> pl; |
| 1857 | GDScript *sptr = script.ptr(); |
| 1858 | while (sptr) { |
| 1859 | if (p_reversed) { |
| 1860 | pl.push_back(sptr); |
| 1861 | } else { |
| 1862 | pl.push_front(sptr); |
| 1863 | } |
| 1864 | sptr = sptr->_base; |
| 1865 | } |
| 1866 | for (GDScript *sc : pl) { |
| 1867 | HashMap<StringName, GDScriptFunction *>::Iterator E = sc->member_functions.find(GDScriptLanguage::get_singleton()->strings._notification); |
| 1868 | if (E) { |
| 1869 | Callable::CallError err; |
| 1870 | E->value->call(this, args, 1, err); |
| 1871 | if (err.error != Callable::CallError::CALL_OK) { |
| 1872 | //print error about notification call |
| 1873 | } |
| 1874 | } |
| 1875 | } |
| 1876 | } |
| 1877 | |
| 1878 | String GDScriptInstance::to_string(bool *r_valid) { |
| 1879 | if (has_method(CoreStringNames::get_singleton()->_to_string)) { |
| 1880 | Callable::CallError ce; |
| 1881 | Variant ret = callp(CoreStringNames::get_singleton()->_to_string, nullptr, 0, ce); |
| 1882 | if (ce.error == Callable::CallError::CALL_OK) { |
| 1883 | if (ret.get_type() != Variant::STRING) { |
| 1884 | if (r_valid) { |
| 1885 | *r_valid = false; |
| 1886 | } |
| 1887 | ERR_FAIL_V_MSG(String(), "Wrong type for " + CoreStringNames::get_singleton()->_to_string + ", must be a String." ); |
| 1888 | } |
| 1889 | if (r_valid) { |
| 1890 | *r_valid = true; |
| 1891 | } |
| 1892 | return ret.operator String(); |
| 1893 | } |
| 1894 | } |
| 1895 | if (r_valid) { |
| 1896 | *r_valid = false; |
| 1897 | } |
| 1898 | return String(); |
| 1899 | } |
| 1900 | |
| 1901 | Ref<Script> GDScriptInstance::get_script() const { |
| 1902 | return script; |
| 1903 | } |
| 1904 | |
| 1905 | ScriptLanguage *GDScriptInstance::get_language() { |
| 1906 | return GDScriptLanguage::get_singleton(); |
| 1907 | } |
| 1908 | |
| 1909 | const Variant GDScriptInstance::get_rpc_config() const { |
| 1910 | return script->get_rpc_config(); |
| 1911 | } |
| 1912 | |
| 1913 | void GDScriptInstance::reload_members() { |
| 1914 | #ifdef DEBUG_ENABLED |
| 1915 | |
| 1916 | Vector<Variant> new_members; |
| 1917 | new_members.resize(script->member_indices.size()); |
| 1918 | |
| 1919 | //pass the values to the new indices |
| 1920 | for (KeyValue<StringName, GDScript::MemberInfo> &E : script->member_indices) { |
| 1921 | if (member_indices_cache.has(E.key)) { |
| 1922 | Variant value = members[member_indices_cache[E.key]]; |
| 1923 | new_members.write[E.value.index] = value; |
| 1924 | } |
| 1925 | } |
| 1926 | |
| 1927 | members.resize(new_members.size()); //resize |
| 1928 | |
| 1929 | //apply |
| 1930 | members = new_members; |
| 1931 | |
| 1932 | //pass the values to the new indices |
| 1933 | member_indices_cache.clear(); |
| 1934 | for (const KeyValue<StringName, GDScript::MemberInfo> &E : script->member_indices) { |
| 1935 | member_indices_cache[E.key] = E.value.index; |
| 1936 | } |
| 1937 | |
| 1938 | #endif |
| 1939 | } |
| 1940 | |
| 1941 | GDScriptInstance::GDScriptInstance() { |
| 1942 | owner = nullptr; |
| 1943 | base_ref_counted = false; |
| 1944 | } |
| 1945 | |
| 1946 | GDScriptInstance::~GDScriptInstance() { |
| 1947 | MutexLock lock(GDScriptLanguage::get_singleton()->mutex); |
| 1948 | |
| 1949 | while (SelfList<GDScriptFunctionState> *E = pending_func_states.first()) { |
| 1950 | // Order matters since clearing the stack may already cause |
| 1951 | // the GDSCriptFunctionState to be destroyed and thus removed from the list. |
| 1952 | pending_func_states.remove(E); |
| 1953 | GDScriptFunctionState *state = E->self(); |
| 1954 | ObjectID state_id = state->get_instance_id(); |
| 1955 | state->_clear_connections(); |
| 1956 | if (ObjectDB::get_instance(state_id)) { |
| 1957 | state->_clear_stack(); |
| 1958 | } |
| 1959 | } |
| 1960 | |
| 1961 | if (script.is_valid() && owner) { |
| 1962 | script->instances.erase(owner); |
| 1963 | } |
| 1964 | } |
| 1965 | |
| 1966 | /************* SCRIPT LANGUAGE **************/ |
| 1967 | |
| 1968 | GDScriptLanguage *GDScriptLanguage::singleton = nullptr; |
| 1969 | |
| 1970 | String GDScriptLanguage::get_name() const { |
| 1971 | return "GDScript" ; |
| 1972 | } |
| 1973 | |
| 1974 | /* LANGUAGE FUNCTIONS */ |
| 1975 | |
| 1976 | void GDScriptLanguage::_add_global(const StringName &p_name, const Variant &p_value) { |
| 1977 | if (globals.has(p_name)) { |
| 1978 | //overwrite existing |
| 1979 | global_array.write[globals[p_name]] = p_value; |
| 1980 | return; |
| 1981 | } |
| 1982 | globals[p_name] = global_array.size(); |
| 1983 | global_array.push_back(p_value); |
| 1984 | _global_array = global_array.ptrw(); |
| 1985 | } |
| 1986 | |
| 1987 | void GDScriptLanguage::add_global_constant(const StringName &p_variable, const Variant &p_value) { |
| 1988 | _add_global(p_variable, p_value); |
| 1989 | } |
| 1990 | |
| 1991 | void GDScriptLanguage::add_named_global_constant(const StringName &p_name, const Variant &p_value) { |
| 1992 | named_globals[p_name] = p_value; |
| 1993 | } |
| 1994 | |
| 1995 | Variant GDScriptLanguage::get_any_global_constant(const StringName &p_name) { |
| 1996 | if (named_globals.has(p_name)) { |
| 1997 | return named_globals[p_name]; |
| 1998 | } |
| 1999 | if (globals.has(p_name)) { |
| 2000 | return _global_array[globals[p_name]]; |
| 2001 | } |
| 2002 | ERR_FAIL_V_MSG(Variant(), vformat("Could not find any global constant with name: %s." , p_name)); |
| 2003 | } |
| 2004 | |
| 2005 | void GDScriptLanguage::remove_named_global_constant(const StringName &p_name) { |
| 2006 | ERR_FAIL_COND(!named_globals.has(p_name)); |
| 2007 | named_globals.erase(p_name); |
| 2008 | } |
| 2009 | |
| 2010 | void GDScriptLanguage::init() { |
| 2011 | //populate global constants |
| 2012 | int gcc = CoreConstants::get_global_constant_count(); |
| 2013 | for (int i = 0; i < gcc; i++) { |
| 2014 | _add_global(StaticCString::create(CoreConstants::get_global_constant_name(i)), CoreConstants::get_global_constant_value(i)); |
| 2015 | } |
| 2016 | |
| 2017 | _add_global(StaticCString::create("PI" ), Math_PI); |
| 2018 | _add_global(StaticCString::create("TAU" ), Math_TAU); |
| 2019 | _add_global(StaticCString::create("INF" ), INFINITY); |
| 2020 | _add_global(StaticCString::create("NAN" ), NAN); |
| 2021 | |
| 2022 | //populate native classes |
| 2023 | |
| 2024 | List<StringName> class_list; |
| 2025 | ClassDB::get_class_list(&class_list); |
| 2026 | for (const StringName &n : class_list) { |
| 2027 | if (globals.has(n)) { |
| 2028 | continue; |
| 2029 | } |
| 2030 | Ref<GDScriptNativeClass> nc = memnew(GDScriptNativeClass(n)); |
| 2031 | _add_global(n, nc); |
| 2032 | } |
| 2033 | |
| 2034 | //populate singletons |
| 2035 | |
| 2036 | List<Engine::Singleton> singletons; |
| 2037 | Engine::get_singleton()->get_singletons(&singletons); |
| 2038 | for (const Engine::Singleton &E : singletons) { |
| 2039 | _add_global(E.name, E.ptr); |
| 2040 | } |
| 2041 | |
| 2042 | #ifdef TESTS_ENABLED |
| 2043 | GDScriptTests::GDScriptTestRunner::handle_cmdline(); |
| 2044 | #endif |
| 2045 | } |
| 2046 | |
| 2047 | String GDScriptLanguage::get_type() const { |
| 2048 | return "GDScript" ; |
| 2049 | } |
| 2050 | |
| 2051 | String GDScriptLanguage::get_extension() const { |
| 2052 | return "gd" ; |
| 2053 | } |
| 2054 | |
| 2055 | void GDScriptLanguage::finish() { |
| 2056 | _call_stack.free(); |
| 2057 | |
| 2058 | // Clear the cache before parsing the script_list |
| 2059 | GDScriptCache::clear(); |
| 2060 | |
| 2061 | // Clear dependencies between scripts, to ensure cyclic references are broken |
| 2062 | // (to avoid leaks at exit). |
| 2063 | SelfList<GDScript> *s = script_list.first(); |
| 2064 | while (s) { |
| 2065 | // This ensures the current script is not released before we can check |
| 2066 | // what's the next one in the list (we can't get the next upfront because we |
| 2067 | // don't know if the reference breaking will cause it -or any other after |
| 2068 | // it, for that matter- to be released so the next one is not the same as |
| 2069 | // before). |
| 2070 | Ref<GDScript> scr = s->self(); |
| 2071 | if (scr.is_valid()) { |
| 2072 | for (KeyValue<StringName, GDScriptFunction *> &E : scr->member_functions) { |
| 2073 | GDScriptFunction *func = E.value; |
| 2074 | for (int i = 0; i < func->argument_types.size(); i++) { |
| 2075 | func->argument_types.write[i].script_type_ref = Ref<Script>(); |
| 2076 | } |
| 2077 | func->return_type.script_type_ref = Ref<Script>(); |
| 2078 | } |
| 2079 | for (KeyValue<StringName, GDScript::MemberInfo> &E : scr->member_indices) { |
| 2080 | E.value.data_type.script_type_ref = Ref<Script>(); |
| 2081 | } |
| 2082 | |
| 2083 | // Clear backup for scripts that could slip out of the cyclic reference |
| 2084 | // check |
| 2085 | scr->clear(); |
| 2086 | } |
| 2087 | s = s->next(); |
| 2088 | } |
| 2089 | script_list.clear(); |
| 2090 | function_list.clear(); |
| 2091 | } |
| 2092 | |
| 2093 | void GDScriptLanguage::profiling_start() { |
| 2094 | #ifdef DEBUG_ENABLED |
| 2095 | MutexLock lock(this->mutex); |
| 2096 | |
| 2097 | SelfList<GDScriptFunction> *elem = function_list.first(); |
| 2098 | while (elem) { |
| 2099 | elem->self()->profile.call_count.set(0); |
| 2100 | elem->self()->profile.self_time.set(0); |
| 2101 | elem->self()->profile.total_time.set(0); |
| 2102 | elem->self()->profile.frame_call_count.set(0); |
| 2103 | elem->self()->profile.frame_self_time.set(0); |
| 2104 | elem->self()->profile.frame_total_time.set(0); |
| 2105 | elem->self()->profile.last_frame_call_count = 0; |
| 2106 | elem->self()->profile.last_frame_self_time = 0; |
| 2107 | elem->self()->profile.last_frame_total_time = 0; |
| 2108 | elem = elem->next(); |
| 2109 | } |
| 2110 | |
| 2111 | profiling = true; |
| 2112 | #endif |
| 2113 | } |
| 2114 | |
| 2115 | void GDScriptLanguage::profiling_stop() { |
| 2116 | #ifdef DEBUG_ENABLED |
| 2117 | MutexLock lock(this->mutex); |
| 2118 | |
| 2119 | profiling = false; |
| 2120 | #endif |
| 2121 | } |
| 2122 | |
| 2123 | int GDScriptLanguage::profiling_get_accumulated_data(ProfilingInfo *p_info_arr, int p_info_max) { |
| 2124 | int current = 0; |
| 2125 | #ifdef DEBUG_ENABLED |
| 2126 | |
| 2127 | MutexLock lock(this->mutex); |
| 2128 | |
| 2129 | SelfList<GDScriptFunction> *elem = function_list.first(); |
| 2130 | while (elem) { |
| 2131 | if (current >= p_info_max) { |
| 2132 | break; |
| 2133 | } |
| 2134 | p_info_arr[current].call_count = elem->self()->profile.call_count.get(); |
| 2135 | p_info_arr[current].self_time = elem->self()->profile.self_time.get(); |
| 2136 | p_info_arr[current].total_time = elem->self()->profile.total_time.get(); |
| 2137 | p_info_arr[current].signature = elem->self()->profile.signature; |
| 2138 | elem = elem->next(); |
| 2139 | current++; |
| 2140 | } |
| 2141 | #endif |
| 2142 | |
| 2143 | return current; |
| 2144 | } |
| 2145 | |
| 2146 | int GDScriptLanguage::profiling_get_frame_data(ProfilingInfo *p_info_arr, int p_info_max) { |
| 2147 | int current = 0; |
| 2148 | |
| 2149 | #ifdef DEBUG_ENABLED |
| 2150 | MutexLock lock(this->mutex); |
| 2151 | |
| 2152 | SelfList<GDScriptFunction> *elem = function_list.first(); |
| 2153 | while (elem) { |
| 2154 | if (current >= p_info_max) { |
| 2155 | break; |
| 2156 | } |
| 2157 | if (elem->self()->profile.last_frame_call_count > 0) { |
| 2158 | p_info_arr[current].call_count = elem->self()->profile.last_frame_call_count; |
| 2159 | p_info_arr[current].self_time = elem->self()->profile.last_frame_self_time; |
| 2160 | p_info_arr[current].total_time = elem->self()->profile.last_frame_total_time; |
| 2161 | p_info_arr[current].signature = elem->self()->profile.signature; |
| 2162 | current++; |
| 2163 | } |
| 2164 | elem = elem->next(); |
| 2165 | } |
| 2166 | #endif |
| 2167 | |
| 2168 | return current; |
| 2169 | } |
| 2170 | |
| 2171 | struct GDScriptDepSort { |
| 2172 | //must support sorting so inheritance works properly (parent must be reloaded first) |
| 2173 | bool operator()(const Ref<GDScript> &A, const Ref<GDScript> &B) const { |
| 2174 | if (A == B) { |
| 2175 | return false; //shouldn't happen but.. |
| 2176 | } |
| 2177 | const GDScript *I = B->get_base().ptr(); |
| 2178 | while (I) { |
| 2179 | if (I == A.ptr()) { |
| 2180 | // A is a base of B |
| 2181 | return true; |
| 2182 | } |
| 2183 | |
| 2184 | I = I->get_base().ptr(); |
| 2185 | } |
| 2186 | |
| 2187 | return false; //not a base |
| 2188 | } |
| 2189 | }; |
| 2190 | |
| 2191 | void GDScriptLanguage::reload_all_scripts() { |
| 2192 | #ifdef DEBUG_ENABLED |
| 2193 | print_verbose("GDScript: Reloading all scripts" ); |
| 2194 | List<Ref<GDScript>> scripts; |
| 2195 | { |
| 2196 | MutexLock lock(this->mutex); |
| 2197 | |
| 2198 | SelfList<GDScript> *elem = script_list.first(); |
| 2199 | while (elem) { |
| 2200 | // Scripts will reload all subclasses, so only reload root scripts. |
| 2201 | if (elem->self()->is_root_script() && elem->self()->get_path().is_resource_file()) { |
| 2202 | print_verbose("GDScript: Found: " + elem->self()->get_path()); |
| 2203 | scripts.push_back(Ref<GDScript>(elem->self())); //cast to gdscript to avoid being erased by accident |
| 2204 | } |
| 2205 | elem = elem->next(); |
| 2206 | } |
| 2207 | } |
| 2208 | |
| 2209 | //as scripts are going to be reloaded, must proceed without locking here |
| 2210 | |
| 2211 | scripts.sort_custom<GDScriptDepSort>(); //update in inheritance dependency order |
| 2212 | |
| 2213 | for (Ref<GDScript> &scr : scripts) { |
| 2214 | print_verbose("GDScript: Reloading: " + scr->get_path()); |
| 2215 | scr->load_source_code(scr->get_path()); |
| 2216 | scr->reload(true); |
| 2217 | } |
| 2218 | #endif |
| 2219 | } |
| 2220 | |
| 2221 | void GDScriptLanguage::reload_tool_script(const Ref<Script> &p_script, bool p_soft_reload) { |
| 2222 | #ifdef DEBUG_ENABLED |
| 2223 | |
| 2224 | List<Ref<GDScript>> scripts; |
| 2225 | { |
| 2226 | MutexLock lock(this->mutex); |
| 2227 | |
| 2228 | SelfList<GDScript> *elem = script_list.first(); |
| 2229 | while (elem) { |
| 2230 | // Scripts will reload all subclasses, so only reload root scripts. |
| 2231 | if (elem->self()->is_root_script() && elem->self()->get_path().is_resource_file()) { |
| 2232 | scripts.push_back(Ref<GDScript>(elem->self())); //cast to gdscript to avoid being erased by accident |
| 2233 | } |
| 2234 | elem = elem->next(); |
| 2235 | } |
| 2236 | } |
| 2237 | |
| 2238 | //when someone asks you why dynamically typed languages are easier to write.... |
| 2239 | |
| 2240 | HashMap<Ref<GDScript>, HashMap<ObjectID, List<Pair<StringName, Variant>>>> to_reload; |
| 2241 | |
| 2242 | //as scripts are going to be reloaded, must proceed without locking here |
| 2243 | |
| 2244 | scripts.sort_custom<GDScriptDepSort>(); //update in inheritance dependency order |
| 2245 | |
| 2246 | for (Ref<GDScript> &scr : scripts) { |
| 2247 | bool reload = scr == p_script || to_reload.has(scr->get_base()); |
| 2248 | |
| 2249 | if (!reload) { |
| 2250 | continue; |
| 2251 | } |
| 2252 | |
| 2253 | to_reload.insert(scr, HashMap<ObjectID, List<Pair<StringName, Variant>>>()); |
| 2254 | |
| 2255 | if (!p_soft_reload) { |
| 2256 | //save state and remove script from instances |
| 2257 | HashMap<ObjectID, List<Pair<StringName, Variant>>> &map = to_reload[scr]; |
| 2258 | |
| 2259 | while (scr->instances.front()) { |
| 2260 | Object *obj = scr->instances.front()->get(); |
| 2261 | //save instance info |
| 2262 | List<Pair<StringName, Variant>> state; |
| 2263 | if (obj->get_script_instance()) { |
| 2264 | obj->get_script_instance()->get_property_state(state); |
| 2265 | map[obj->get_instance_id()] = state; |
| 2266 | obj->set_script(Variant()); |
| 2267 | } |
| 2268 | } |
| 2269 | |
| 2270 | //same thing for placeholders |
| 2271 | #ifdef TOOLS_ENABLED |
| 2272 | |
| 2273 | while (scr->placeholders.size()) { |
| 2274 | Object *obj = (*scr->placeholders.begin())->get_owner(); |
| 2275 | |
| 2276 | //save instance info |
| 2277 | if (obj->get_script_instance()) { |
| 2278 | map.insert(obj->get_instance_id(), List<Pair<StringName, Variant>>()); |
| 2279 | List<Pair<StringName, Variant>> &state = map[obj->get_instance_id()]; |
| 2280 | obj->get_script_instance()->get_property_state(state); |
| 2281 | obj->set_script(Variant()); |
| 2282 | } else { |
| 2283 | // no instance found. Let's remove it so we don't loop forever |
| 2284 | scr->placeholders.erase(*scr->placeholders.begin()); |
| 2285 | } |
| 2286 | } |
| 2287 | |
| 2288 | #endif |
| 2289 | |
| 2290 | for (const KeyValue<ObjectID, List<Pair<StringName, Variant>>> &F : scr->pending_reload_state) { |
| 2291 | map[F.key] = F.value; //pending to reload, use this one instead |
| 2292 | } |
| 2293 | } |
| 2294 | } |
| 2295 | |
| 2296 | for (KeyValue<Ref<GDScript>, HashMap<ObjectID, List<Pair<StringName, Variant>>>> &E : to_reload) { |
| 2297 | Ref<GDScript> scr = E.key; |
| 2298 | scr->reload(p_soft_reload); |
| 2299 | |
| 2300 | //restore state if saved |
| 2301 | for (KeyValue<ObjectID, List<Pair<StringName, Variant>>> &F : E.value) { |
| 2302 | List<Pair<StringName, Variant>> &saved_state = F.value; |
| 2303 | |
| 2304 | Object *obj = ObjectDB::get_instance(F.key); |
| 2305 | if (!obj) { |
| 2306 | continue; |
| 2307 | } |
| 2308 | |
| 2309 | if (!p_soft_reload) { |
| 2310 | //clear it just in case (may be a pending reload state) |
| 2311 | obj->set_script(Variant()); |
| 2312 | } |
| 2313 | obj->set_script(scr); |
| 2314 | |
| 2315 | ScriptInstance *script_inst = obj->get_script_instance(); |
| 2316 | |
| 2317 | if (!script_inst) { |
| 2318 | //failed, save reload state for next time if not saved |
| 2319 | if (!scr->pending_reload_state.has(obj->get_instance_id())) { |
| 2320 | scr->pending_reload_state[obj->get_instance_id()] = saved_state; |
| 2321 | } |
| 2322 | continue; |
| 2323 | } |
| 2324 | |
| 2325 | if (script_inst->is_placeholder() && scr->is_placeholder_fallback_enabled()) { |
| 2326 | PlaceHolderScriptInstance *placeholder = static_cast<PlaceHolderScriptInstance *>(script_inst); |
| 2327 | for (List<Pair<StringName, Variant>>::Element *G = saved_state.front(); G; G = G->next()) { |
| 2328 | placeholder->property_set_fallback(G->get().first, G->get().second); |
| 2329 | } |
| 2330 | } else { |
| 2331 | for (List<Pair<StringName, Variant>>::Element *G = saved_state.front(); G; G = G->next()) { |
| 2332 | script_inst->set(G->get().first, G->get().second); |
| 2333 | } |
| 2334 | } |
| 2335 | |
| 2336 | scr->pending_reload_state.erase(obj->get_instance_id()); //as it reloaded, remove pending state |
| 2337 | } |
| 2338 | |
| 2339 | //if instance states were saved, set them! |
| 2340 | } |
| 2341 | |
| 2342 | #endif |
| 2343 | } |
| 2344 | |
| 2345 | void GDScriptLanguage::frame() { |
| 2346 | calls = 0; |
| 2347 | |
| 2348 | #ifdef DEBUG_ENABLED |
| 2349 | if (profiling) { |
| 2350 | MutexLock lock(this->mutex); |
| 2351 | |
| 2352 | SelfList<GDScriptFunction> *elem = function_list.first(); |
| 2353 | while (elem) { |
| 2354 | elem->self()->profile.last_frame_call_count = elem->self()->profile.frame_call_count.get(); |
| 2355 | elem->self()->profile.last_frame_self_time = elem->self()->profile.frame_self_time.get(); |
| 2356 | elem->self()->profile.last_frame_total_time = elem->self()->profile.frame_total_time.get(); |
| 2357 | elem->self()->profile.frame_call_count.set(0); |
| 2358 | elem->self()->profile.frame_self_time.set(0); |
| 2359 | elem->self()->profile.frame_total_time.set(0); |
| 2360 | elem = elem->next(); |
| 2361 | } |
| 2362 | } |
| 2363 | |
| 2364 | #endif |
| 2365 | } |
| 2366 | |
| 2367 | /* EDITOR FUNCTIONS */ |
| 2368 | void GDScriptLanguage::get_reserved_words(List<String> *p_words) const { |
| 2369 | // TODO: Add annotations here? |
| 2370 | static const char *_reserved_words[] = { |
| 2371 | // operators |
| 2372 | "and" , |
| 2373 | "in" , |
| 2374 | "not" , |
| 2375 | "or" , |
| 2376 | // types and values |
| 2377 | "false" , |
| 2378 | "float" , |
| 2379 | "int" , |
| 2380 | "bool" , |
| 2381 | "null" , |
| 2382 | "PI" , |
| 2383 | "TAU" , |
| 2384 | "INF" , |
| 2385 | "NAN" , |
| 2386 | "self" , |
| 2387 | "true" , |
| 2388 | "void" , |
| 2389 | // functions |
| 2390 | "as" , |
| 2391 | "assert" , |
| 2392 | "await" , |
| 2393 | "breakpoint" , |
| 2394 | "class" , |
| 2395 | "class_name" , |
| 2396 | "extends" , |
| 2397 | "is" , |
| 2398 | "func" , |
| 2399 | "preload" , |
| 2400 | "signal" , |
| 2401 | "super" , |
| 2402 | // var |
| 2403 | "const" , |
| 2404 | "enum" , |
| 2405 | "static" , |
| 2406 | "var" , |
| 2407 | // control flow |
| 2408 | "break" , |
| 2409 | "continue" , |
| 2410 | "if" , |
| 2411 | "elif" , |
| 2412 | "else" , |
| 2413 | "for" , |
| 2414 | "pass" , |
| 2415 | "return" , |
| 2416 | "match" , |
| 2417 | "while" , |
| 2418 | // These keywords are not implemented currently, but reserved for (potential) future use. |
| 2419 | // We highlight them as keywords to make errors easier to understand. |
| 2420 | "trait" , |
| 2421 | "namespace" , |
| 2422 | "yield" , |
| 2423 | nullptr |
| 2424 | }; |
| 2425 | |
| 2426 | const char **w = _reserved_words; |
| 2427 | |
| 2428 | while (*w) { |
| 2429 | p_words->push_back(*w); |
| 2430 | w++; |
| 2431 | } |
| 2432 | |
| 2433 | List<StringName> functions; |
| 2434 | GDScriptUtilityFunctions::get_function_list(&functions); |
| 2435 | |
| 2436 | for (const StringName &E : functions) { |
| 2437 | p_words->push_back(String(E)); |
| 2438 | } |
| 2439 | } |
| 2440 | |
| 2441 | bool GDScriptLanguage::is_control_flow_keyword(String p_keyword) const { |
| 2442 | return p_keyword == "break" || |
| 2443 | p_keyword == "continue" || |
| 2444 | p_keyword == "elif" || |
| 2445 | p_keyword == "else" || |
| 2446 | p_keyword == "if" || |
| 2447 | p_keyword == "for" || |
| 2448 | p_keyword == "match" || |
| 2449 | p_keyword == "pass" || |
| 2450 | p_keyword == "return" || |
| 2451 | p_keyword == "while" ; |
| 2452 | } |
| 2453 | |
| 2454 | bool GDScriptLanguage::handles_global_class_type(const String &p_type) const { |
| 2455 | return p_type == "GDScript" ; |
| 2456 | } |
| 2457 | |
| 2458 | String GDScriptLanguage::get_global_class_name(const String &p_path, String *r_base_type, String *r_icon_path) const { |
| 2459 | Error err; |
| 2460 | Ref<FileAccess> f = FileAccess::open(p_path, FileAccess::READ, &err); |
| 2461 | if (err) { |
| 2462 | return String(); |
| 2463 | } |
| 2464 | |
| 2465 | String source = f->get_as_utf8_string(); |
| 2466 | |
| 2467 | GDScriptParser parser; |
| 2468 | err = parser.parse(source, p_path, false); |
| 2469 | |
| 2470 | const GDScriptParser::ClassNode *c = parser.get_tree(); |
| 2471 | if (!c) { |
| 2472 | return String(); // No class parsed. |
| 2473 | } |
| 2474 | |
| 2475 | /* **WARNING** |
| 2476 | * |
| 2477 | * This function is written with the goal to be *extremely* error tolerant, as such |
| 2478 | * it should meet the following requirements: |
| 2479 | * |
| 2480 | * - It must not rely on the analyzer (in fact, the analyzer must not be used here), |
| 2481 | * because at the time global classes are parsed, the dependencies may not be present |
| 2482 | * yet, hence the function will fail (which is unintended). |
| 2483 | * - It must not fail even if the parsing fails, because even if the file is broken, |
| 2484 | * it should attempt its best to retrieve the inheritance metadata. |
| 2485 | * |
| 2486 | * Before changing this function, please ask the current maintainer of EditorFileSystem. |
| 2487 | */ |
| 2488 | |
| 2489 | if (r_base_type) { |
| 2490 | const GDScriptParser::ClassNode *subclass = c; |
| 2491 | String path = p_path; |
| 2492 | GDScriptParser subparser; |
| 2493 | while (subclass) { |
| 2494 | if (subclass->extends_used) { |
| 2495 | if (!subclass->extends_path.is_empty()) { |
| 2496 | if (subclass->extends.size() == 0) { |
| 2497 | get_global_class_name(subclass->extends_path, r_base_type); |
| 2498 | subclass = nullptr; |
| 2499 | break; |
| 2500 | } else { |
| 2501 | Vector<GDScriptParser::IdentifierNode *> extend_classes = subclass->extends; |
| 2502 | |
| 2503 | Ref<FileAccess> subfile = FileAccess::open(subclass->extends_path, FileAccess::READ); |
| 2504 | if (subfile.is_null()) { |
| 2505 | break; |
| 2506 | } |
| 2507 | String subsource = subfile->get_as_utf8_string(); |
| 2508 | |
| 2509 | if (subsource.is_empty()) { |
| 2510 | break; |
| 2511 | } |
| 2512 | String subpath = subclass->extends_path; |
| 2513 | if (subpath.is_relative_path()) { |
| 2514 | subpath = path.get_base_dir().path_join(subpath).simplify_path(); |
| 2515 | } |
| 2516 | |
| 2517 | if (OK != subparser.parse(subsource, subpath, false)) { |
| 2518 | break; |
| 2519 | } |
| 2520 | path = subpath; |
| 2521 | subclass = subparser.get_tree(); |
| 2522 | |
| 2523 | while (extend_classes.size() > 0) { |
| 2524 | bool found = false; |
| 2525 | for (int i = 0; i < subclass->members.size(); i++) { |
| 2526 | if (subclass->members[i].type != GDScriptParser::ClassNode::Member::CLASS) { |
| 2527 | continue; |
| 2528 | } |
| 2529 | |
| 2530 | const GDScriptParser::ClassNode *inner_class = subclass->members[i].m_class; |
| 2531 | if (inner_class->identifier->name == extend_classes[0]->name) { |
| 2532 | extend_classes.remove_at(0); |
| 2533 | found = true; |
| 2534 | subclass = inner_class; |
| 2535 | break; |
| 2536 | } |
| 2537 | } |
| 2538 | if (!found) { |
| 2539 | subclass = nullptr; |
| 2540 | break; |
| 2541 | } |
| 2542 | } |
| 2543 | } |
| 2544 | } else if (subclass->extends.size() == 1) { |
| 2545 | *r_base_type = subclass->extends[0]->name; |
| 2546 | subclass = nullptr; |
| 2547 | } else { |
| 2548 | break; |
| 2549 | } |
| 2550 | } else { |
| 2551 | *r_base_type = "RefCounted" ; |
| 2552 | subclass = nullptr; |
| 2553 | } |
| 2554 | } |
| 2555 | } |
| 2556 | if (r_icon_path) { |
| 2557 | *r_icon_path = c->simplified_icon_path; |
| 2558 | } |
| 2559 | return c->identifier != nullptr ? String(c->identifier->name) : String(); |
| 2560 | } |
| 2561 | |
| 2562 | thread_local GDScriptLanguage::CallStack GDScriptLanguage::_call_stack; |
| 2563 | |
| 2564 | GDScriptLanguage::GDScriptLanguage() { |
| 2565 | calls = 0; |
| 2566 | ERR_FAIL_COND(singleton); |
| 2567 | singleton = this; |
| 2568 | strings._init = StaticCString::create("_init" ); |
| 2569 | strings._static_init = StaticCString::create("_static_init" ); |
| 2570 | strings._notification = StaticCString::create("_notification" ); |
| 2571 | strings._set = StaticCString::create("_set" ); |
| 2572 | strings._get = StaticCString::create("_get" ); |
| 2573 | strings._get_property_list = StaticCString::create("_get_property_list" ); |
| 2574 | strings._validate_property = StaticCString::create("_validate_property" ); |
| 2575 | strings._property_can_revert = StaticCString::create("_property_can_revert" ); |
| 2576 | strings._property_get_revert = StaticCString::create("_property_get_revert" ); |
| 2577 | strings._script_source = StaticCString::create("script/source" ); |
| 2578 | _debug_parse_err_line = -1; |
| 2579 | _debug_parse_err_file = "" ; |
| 2580 | |
| 2581 | profiling = false; |
| 2582 | script_frame_time = 0; |
| 2583 | |
| 2584 | int dmcs = GLOBAL_DEF(PropertyInfo(Variant::INT, "debug/settings/gdscript/max_call_stack" , PROPERTY_HINT_RANGE, "512," + itos(GDScriptFunction::MAX_CALL_DEPTH - 1) + ",1" ), 1024); |
| 2585 | |
| 2586 | if (EngineDebugger::is_active()) { |
| 2587 | //debugging enabled! |
| 2588 | |
| 2589 | _debug_max_call_stack = dmcs; |
| 2590 | } else { |
| 2591 | _debug_max_call_stack = 0; |
| 2592 | } |
| 2593 | |
| 2594 | #ifdef DEBUG_ENABLED |
| 2595 | GLOBAL_DEF("debug/gdscript/warnings/enable" , true); |
| 2596 | GLOBAL_DEF("debug/gdscript/warnings/exclude_addons" , true); |
| 2597 | for (int i = 0; i < (int)GDScriptWarning::WARNING_MAX; i++) { |
| 2598 | GDScriptWarning::Code code = (GDScriptWarning::Code)i; |
| 2599 | Variant default_enabled = GDScriptWarning::get_default_value(code); |
| 2600 | String path = GDScriptWarning::get_settings_path_from_code(code); |
| 2601 | GLOBAL_DEF(GDScriptWarning::get_property_info(code), default_enabled); |
| 2602 | } |
| 2603 | #endif // DEBUG_ENABLED |
| 2604 | } |
| 2605 | |
| 2606 | GDScriptLanguage::~GDScriptLanguage() { |
| 2607 | singleton = nullptr; |
| 2608 | } |
| 2609 | |
| 2610 | void GDScriptLanguage::add_orphan_subclass(const String &p_qualified_name, const ObjectID &p_subclass) { |
| 2611 | orphan_subclasses[p_qualified_name] = p_subclass; |
| 2612 | } |
| 2613 | |
| 2614 | Ref<GDScript> GDScriptLanguage::get_orphan_subclass(const String &p_qualified_name) { |
| 2615 | HashMap<String, ObjectID>::Iterator orphan_subclass_element = orphan_subclasses.find(p_qualified_name); |
| 2616 | if (!orphan_subclass_element) { |
| 2617 | return Ref<GDScript>(); |
| 2618 | } |
| 2619 | ObjectID orphan_subclass = orphan_subclass_element->value; |
| 2620 | Object *obj = ObjectDB::get_instance(orphan_subclass); |
| 2621 | orphan_subclasses.remove(orphan_subclass_element); |
| 2622 | if (!obj) { |
| 2623 | return Ref<GDScript>(); |
| 2624 | } |
| 2625 | return Ref<GDScript>(Object::cast_to<GDScript>(obj)); |
| 2626 | } |
| 2627 | |
| 2628 | Ref<GDScript> GDScriptLanguage::get_script_by_fully_qualified_name(const String &p_name) { |
| 2629 | { |
| 2630 | MutexLock lock(mutex); |
| 2631 | |
| 2632 | SelfList<GDScript> *elem = script_list.first(); |
| 2633 | while (elem) { |
| 2634 | GDScript *scr = elem->self(); |
| 2635 | if (scr->fully_qualified_name == p_name) { |
| 2636 | return scr; |
| 2637 | } |
| 2638 | elem = elem->next(); |
| 2639 | } |
| 2640 | } |
| 2641 | |
| 2642 | Ref<GDScript> scr; |
| 2643 | scr.instantiate(); |
| 2644 | scr->fully_qualified_name = p_name; |
| 2645 | return scr; |
| 2646 | } |
| 2647 | |
| 2648 | /*************** RESOURCE ***************/ |
| 2649 | |
| 2650 | 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) { |
| 2651 | Error err; |
| 2652 | Ref<GDScript> scr = GDScriptCache::get_full_script(p_path, err, "" , p_cache_mode == CACHE_MODE_IGNORE); |
| 2653 | |
| 2654 | if (err && scr.is_valid()) { |
| 2655 | // If !scr.is_valid(), the error was likely from scr->load_source_code(), which already generates an error. |
| 2656 | ERR_PRINT_ED(vformat(R"(Failed to load script "%s" with error "%s".)" , p_path, error_names[err])); |
| 2657 | } |
| 2658 | |
| 2659 | if (r_error) { |
| 2660 | // Don't fail loading because of parsing error. |
| 2661 | *r_error = scr.is_valid() ? OK : err; |
| 2662 | } |
| 2663 | |
| 2664 | return scr; |
| 2665 | } |
| 2666 | |
| 2667 | void ResourceFormatLoaderGDScript::get_recognized_extensions(List<String> *p_extensions) const { |
| 2668 | p_extensions->push_back("gd" ); |
| 2669 | } |
| 2670 | |
| 2671 | bool ResourceFormatLoaderGDScript::handles_type(const String &p_type) const { |
| 2672 | return (p_type == "Script" || p_type == "GDScript" ); |
| 2673 | } |
| 2674 | |
| 2675 | String ResourceFormatLoaderGDScript::get_resource_type(const String &p_path) const { |
| 2676 | String el = p_path.get_extension().to_lower(); |
| 2677 | if (el == "gd" ) { |
| 2678 | return "GDScript" ; |
| 2679 | } |
| 2680 | return "" ; |
| 2681 | } |
| 2682 | |
| 2683 | void ResourceFormatLoaderGDScript::get_dependencies(const String &p_path, List<String> *p_dependencies, bool p_add_types) { |
| 2684 | Ref<FileAccess> file = FileAccess::open(p_path, FileAccess::READ); |
| 2685 | ERR_FAIL_COND_MSG(file.is_null(), "Cannot open file '" + p_path + "'." ); |
| 2686 | |
| 2687 | String source = file->get_as_utf8_string(); |
| 2688 | if (source.is_empty()) { |
| 2689 | return; |
| 2690 | } |
| 2691 | |
| 2692 | GDScriptParser parser; |
| 2693 | if (OK != parser.parse(source, p_path, false)) { |
| 2694 | return; |
| 2695 | } |
| 2696 | |
| 2697 | for (const String &E : parser.get_dependencies()) { |
| 2698 | p_dependencies->push_back(E); |
| 2699 | } |
| 2700 | } |
| 2701 | |
| 2702 | Error ResourceFormatSaverGDScript::save(const Ref<Resource> &p_resource, const String &p_path, uint32_t p_flags) { |
| 2703 | Ref<GDScript> sqscr = p_resource; |
| 2704 | ERR_FAIL_COND_V(sqscr.is_null(), ERR_INVALID_PARAMETER); |
| 2705 | |
| 2706 | String source = sqscr->get_source_code(); |
| 2707 | |
| 2708 | { |
| 2709 | Error err; |
| 2710 | Ref<FileAccess> file = FileAccess::open(p_path, FileAccess::WRITE, &err); |
| 2711 | |
| 2712 | ERR_FAIL_COND_V_MSG(err, err, "Cannot save GDScript file '" + p_path + "'." ); |
| 2713 | |
| 2714 | file->store_string(source); |
| 2715 | if (file->get_error() != OK && file->get_error() != ERR_FILE_EOF) { |
| 2716 | return ERR_CANT_CREATE; |
| 2717 | } |
| 2718 | } |
| 2719 | |
| 2720 | if (ScriptServer::is_reload_scripts_on_save_enabled()) { |
| 2721 | GDScriptLanguage::get_singleton()->reload_tool_script(p_resource, true); |
| 2722 | } |
| 2723 | |
| 2724 | return OK; |
| 2725 | } |
| 2726 | |
| 2727 | void ResourceFormatSaverGDScript::get_recognized_extensions(const Ref<Resource> &p_resource, List<String> *p_extensions) const { |
| 2728 | if (Object::cast_to<GDScript>(*p_resource)) { |
| 2729 | p_extensions->push_back("gd" ); |
| 2730 | } |
| 2731 | } |
| 2732 | |
| 2733 | bool ResourceFormatSaverGDScript::recognize(const Ref<Resource> &p_resource) const { |
| 2734 | return Object::cast_to<GDScript>(*p_resource) != nullptr; |
| 2735 | } |
| 2736 | |