diff options
39 files changed, 435 insertions, 276 deletions
diff --git a/core/io/resource.cpp b/core/io/resource.cpp index efa622d976..91a1386965 100644 --- a/core/io/resource.cpp +++ b/core/io/resource.cpp @@ -33,6 +33,7 @@ #include "core/core_string_names.h" #include "core/io/file_access.h" #include "core/io/resource_loader.h" +#include "core/math/math_funcs.h" #include "core/object/script_language.h" #include "core/os/os.h" #include "scene/main/node.h" //only so casting works @@ -94,12 +95,43 @@ String Resource::get_path() const { return path_cache; } -void Resource::set_subindex(int p_sub_index) { - subindex = p_sub_index; +String Resource::generate_scene_unique_id() { + // Generate a unique enough hash, but still user-readable. + // If it's not unique it does not matter because the saver will try again. + OS::Date date = OS::get_singleton()->get_date(); + OS::Time time = OS::get_singleton()->get_time(); + uint32_t hash = hash_djb2_one_32(OS::get_singleton()->get_ticks_usec()); + hash = hash_djb2_one_32(date.year, hash); + hash = hash_djb2_one_32(date.month, hash); + hash = hash_djb2_one_32(date.day, hash); + hash = hash_djb2_one_32(time.hour, hash); + hash = hash_djb2_one_32(time.minute, hash); + hash = hash_djb2_one_32(time.second, hash); + hash = hash_djb2_one_32(Math::rand(), hash); + + static constexpr uint32_t characters = 5; + static constexpr uint32_t char_count = ('z' - 'a'); + static constexpr uint32_t base = char_count + ('9' - '0'); + String id; + for (uint32_t i = 0; i < characters; i++) { + uint32_t c = hash % base; + if (c < char_count) { + id += String::chr('a' + c); + } else { + id += String::chr('0' + (c - char_count)); + } + hash /= base; + } + + return id; +} + +void Resource::set_scene_unique_id(const String &p_id) { + scene_unique_id = p_id; } -int Resource::get_subindex() const { - return subindex; +String Resource::get_scene_unique_id() const { + return scene_unique_id; } void Resource::set_name(const String &p_name) { @@ -350,8 +382,8 @@ bool Resource::is_translation_remapped() const { #ifdef TOOLS_ENABLED //helps keep IDs same number when loading/saving scenes. -1 clears ID and it Returns -1 when no id stored -void Resource::set_id_for_path(const String &p_path, int p_id) { - if (p_id == -1) { +void Resource::set_id_for_path(const String &p_path, const String &p_id) { + if (p_id == "") { ResourceCache::path_cache_lock.write_lock(); ResourceCache::resource_path_cache[p_path].erase(get_path()); ResourceCache::path_cache_lock.write_unlock(); @@ -362,15 +394,15 @@ void Resource::set_id_for_path(const String &p_path, int p_id) { } } -int Resource::get_id_for_path(const String &p_path) const { +String Resource::get_id_for_path(const String &p_path) const { ResourceCache::path_cache_lock.read_lock(); if (ResourceCache::resource_path_cache[p_path].has(get_path())) { - int result = ResourceCache::resource_path_cache[p_path][get_path()]; + String result = ResourceCache::resource_path_cache[p_path][get_path()]; ResourceCache::path_cache_lock.read_unlock(); return result; } else { ResourceCache::path_cache_lock.read_unlock(); - return -1; + return ""; } } #endif @@ -414,7 +446,7 @@ Resource::~Resource() { HashMap<String, Resource *> ResourceCache::resources; #ifdef TOOLS_ENABLED -HashMap<String, HashMap<String, int>> ResourceCache::resource_path_cache; +HashMap<String, HashMap<String, String>> ResourceCache::resource_path_cache; #endif RWLock ResourceCache::lock; diff --git a/core/io/resource.h b/core/io/resource.h index 028fed1c6e..0f88738f76 100644 --- a/core/io/resource.h +++ b/core/io/resource.h @@ -59,7 +59,7 @@ private: String name; String path_cache; - int subindex = 0; + String scene_unique_id; virtual bool _use_builtin_script() const { return true; } @@ -105,8 +105,9 @@ public: virtual void set_path(const String &p_path, bool p_take_over = false); String get_path() const; - void set_subindex(int p_sub_index); - int get_subindex() const; + static String generate_scene_unique_id(); + void set_scene_unique_id(const String &p_id); + String get_scene_unique_id() const; virtual Ref<Resource> duplicate(bool p_subresources = false) const; Ref<Resource> duplicate_for_local_scene(Node *p_for_scene, Map<Ref<Resource>, Ref<Resource>> &remap_cache); @@ -140,8 +141,8 @@ public: #ifdef TOOLS_ENABLED //helps keep IDs same number when loading/saving scenes. -1 clears ID and it Returns -1 when no id stored - void set_id_for_path(const String &p_path, int p_id); - int get_id_for_path(const String &p_path) const; + void set_id_for_path(const String &p_path, const String &p_id); + String get_id_for_path(const String &p_path) const; #endif Resource(); @@ -156,7 +157,7 @@ class ResourceCache { static RWLock lock; static HashMap<String, Resource *> resources; #ifdef TOOLS_ENABLED - static HashMap<String, HashMap<String, int>> resource_path_cache; // each tscn has a set of resource paths and IDs + static HashMap<String, HashMap<String, String>> resource_path_cache; // Each tscn has a set of resource paths and IDs. static RWLock path_cache_lock; #endif // TOOLS_ENABLED friend void unregister_core_types(); diff --git a/core/io/resource_format_binary.cpp b/core/io/resource_format_binary.cpp index 0e9815245f..e889586afc 100644 --- a/core/io/resource_format_binary.cpp +++ b/core/io/resource_format_binary.cpp @@ -84,9 +84,10 @@ enum { OBJECT_EXTERNAL_RESOURCE = 1, OBJECT_INTERNAL_RESOURCE = 2, OBJECT_EXTERNAL_RESOURCE_INDEX = 3, - //version 2: added 64 bits support for float and int - //version 3: changed nodepath encoding - FORMAT_VERSION = 3, + // Version 2: added 64 bits support for float and int. + // Version 3: changed nodepath encoding. + // Version 4: new string ID for ext/subresources, breaks forward compat. + FORMAT_VERSION = 4, FORMAT_VERSION_CAN_RENAME_DEPS = 1, FORMAT_VERSION_NO_NODEPATH_PROPERTY = 3, }; @@ -311,7 +312,14 @@ Error ResourceLoaderBinary::parse_variant(Variant &r_v) { } break; case OBJECT_INTERNAL_RESOURCE: { uint32_t index = f->get_32(); - String path = res_path + "::" + itos(index); + String path; + + if (using_named_scene_ids) { // New format. + ERR_FAIL_INDEX_V((int)index, internal_resources.size(), ERR_PARSE_ERROR); + path = internal_resources[index].path; + } else { + path += res_path + "::" + itos(index); + } //always use internal cache for loading internal resources if (!internal_index_cache.has(path)) { @@ -320,7 +328,6 @@ Error ResourceLoaderBinary::parse_variant(Variant &r_v) { } else { r_v = internal_index_cache[path]; } - } break; case OBJECT_EXTERNAL_RESOURCE: { //old file format, still around for compatibility @@ -378,7 +385,6 @@ Error ResourceLoaderBinary::parse_variant(Variant &r_v) { ERR_FAIL_V(ERR_FILE_CORRUPT); } break; } - } break; case VARIANT_CALLABLE: { r_v = Callable(); @@ -659,15 +665,17 @@ Error ResourceLoaderBinary::load() { //maybe it is loaded already String path; - int subindex = 0; + String id; if (!main) { path = internal_resources[i].path; if (path.begins_with("local://")) { path = path.replace_first("local://", ""); - subindex = path.to_int(); + id = path; path = res_path + "::" + path; + + internal_resources.write[i].path = path; // Update path. } if (cache_mode == ResourceFormatLoader::CACHE_MODE_REUSE) { @@ -722,7 +730,7 @@ Error ResourceLoaderBinary::load() { if (path != String() && cache_mode != ResourceFormatLoader::CACHE_MODE_IGNORE) { r->set_path(path, cache_mode == ResourceFormatLoader::CACHE_MODE_REPLACE); //if got here because the resource with same path has different type, replace it } - r->set_subindex(subindex); + r->set_scene_unique_id(id); } if (!main) { @@ -879,7 +887,11 @@ void ResourceLoaderBinary::open(FileAccess *p_f) { print_bl("type: " + type); importmd_ofs = f->get_64(); - for (int i = 0; i < 14; i++) { + uint32_t flags = f->get_32(); + if (flags & ResourceFormatSaverBinaryInstance::FORMAT_FLAG_NAMED_SCENE_IDS) { + using_named_scene_ids = true; + } + for (int i = 0; i < 13; i++) { f->get_32(); //skip a few reserved fields } @@ -1269,11 +1281,7 @@ void ResourceFormatSaverBinaryInstance::_pad_buffer(FileAccess *f, int p_bytes) } } -void ResourceFormatSaverBinaryInstance::_write_variant(const Variant &p_property, const PropertyInfo &p_hint) { - write_variant(f, p_property, resource_set, external_resources, string_map, p_hint); -} - -void ResourceFormatSaverBinaryInstance::write_variant(FileAccess *f, const Variant &p_property, Set<RES> &resource_set, Map<RES, int> &external_resources, Map<StringName, int> &string_map, const PropertyInfo &p_hint) { +void ResourceFormatSaverBinaryInstance::write_variant(FileAccess *f, const Variant &p_property, Map<RES, int> &resource_map, Map<RES, int> &external_resources, Map<StringName, int> &string_map, const PropertyInfo &p_hint) { switch (p_property.get_type()) { case Variant::NIL: { f->store_32(VARIANT_NIL); @@ -1492,13 +1500,13 @@ void ResourceFormatSaverBinaryInstance::write_variant(FileAccess *f, const Varia f->store_32(OBJECT_EXTERNAL_RESOURCE_INDEX); f->store_32(external_resources[res]); } else { - if (!resource_set.has(res)) { + if (!resource_map.has(res)) { f->store_32(OBJECT_EMPTY); ERR_FAIL_MSG("Resource was not pre cached for the resource section, most likely due to circular reference."); } f->store_32(OBJECT_INTERNAL_RESOURCE); - f->store_32(res->get_subindex()); + f->store_32(resource_map[res]); //internal resource } @@ -1526,8 +1534,8 @@ void ResourceFormatSaverBinaryInstance::write_variant(FileAccess *f, const Varia continue; */ - write_variant(f, E->get(), resource_set, external_resources, string_map); - write_variant(f, d[E->get()], resource_set, external_resources, string_map); + write_variant(f, E->get(), resource_map, external_resources, string_map); + write_variant(f, d[E->get()], resource_map, external_resources, string_map); } } break; @@ -1536,7 +1544,7 @@ void ResourceFormatSaverBinaryInstance::write_variant(FileAccess *f, const Varia Array a = p_property; f->store_32(uint32_t(a.size())); for (int i = 0; i < a.size(); i++) { - write_variant(f, a[i], resource_set, external_resources, string_map); + write_variant(f, a[i], resource_map, external_resources, string_map); } } break; @@ -1816,7 +1824,8 @@ Error ResourceFormatSaverBinaryInstance::save(const String &p_path, const RES &p save_unicode_string(f, p_resource->get_class()); f->store_64(0); //offset to import metadata - for (int i = 0; i < 14; i++) { + f->store_32(FORMAT_FLAG_NAMED_SCENE_IDS); + for (int i = 0; i < 13; i++) { f->store_32(0); // reserved } @@ -1886,37 +1895,43 @@ Error ResourceFormatSaverBinaryInstance::save(const String &p_path, const RES &p // save internal resource table f->store_32(saved_resources.size()); //amount of internal resources Vector<uint64_t> ofs_pos; - Set<int> used_indices; + Set<String> used_unique_ids; for (List<RES>::Element *E = saved_resources.front(); E; E = E->next()) { RES r = E->get(); if (r->get_path() == "" || r->get_path().find("::") != -1) { - if (r->get_subindex() != 0) { - if (used_indices.has(r->get_subindex())) { - r->set_subindex(0); //repeated + if (r->get_scene_unique_id() != "") { + if (used_unique_ids.has(r->get_scene_unique_id())) { + r->set_scene_unique_id(""); } else { - used_indices.insert(r->get_subindex()); + used_unique_ids.insert(r->get_scene_unique_id()); } } } } + Map<RES, int> resource_map; + int res_index = 0; for (List<RES>::Element *E = saved_resources.front(); E; E = E->next()) { RES r = E->get(); if (r->get_path() == "" || r->get_path().find("::") != -1) { - if (r->get_subindex() == 0) { - int new_subindex = 1; - if (used_indices.size()) { - new_subindex = used_indices.back()->get() + 1; + if (r->get_scene_unique_id() == "") { + String new_id; + + while (true) { + new_id = r->get_class() + "_" + Resource::generate_scene_unique_id(); + if (!used_unique_ids.has(new_id)) { + break; + } } - r->set_subindex(new_subindex); - used_indices.insert(new_subindex); + r->set_scene_unique_id(new_id); + used_unique_ids.insert(new_id); } - save_unicode_string(f, "local://" + itos(r->get_subindex())); + save_unicode_string(f, "local://" + r->get_scene_unique_id()); if (takeover_paths) { - r->set_path(p_path + "::" + itos(r->get_subindex()), true); + r->set_path(p_path + "::" + r->get_scene_unique_id(), true); } #ifdef TOOLS_ENABLED r->set_edited(false); @@ -1926,6 +1941,7 @@ Error ResourceFormatSaverBinaryInstance::save(const String &p_path, const RES &p } ofs_pos.push_back(f->get_position()); f->store_64(0); //offset in 64 bits + resource_map[r] = res_index++; } Vector<uint64_t> ofs_table; @@ -1941,7 +1957,7 @@ Error ResourceFormatSaverBinaryInstance::save(const String &p_path, const RES &p for (List<Property>::Element *F = rd.properties.front(); F; F = F->next()) { Property &p = F->get(); f->store_32(p.name_idx); - _write_variant(p.value, F->get().pi); + write_variant(f, p.value, resource_map, external_resources, string_map, F->get().pi); } } diff --git a/core/io/resource_format_binary.h b/core/io/resource_format_binary.h index abc7403935..e3dcf44492 100644 --- a/core/io/resource_format_binary.h +++ b/core/io/resource_format_binary.h @@ -60,6 +60,7 @@ class ResourceLoaderBinary { RES cache; }; + bool using_named_scene_ids = false; bool use_sub_threads = false; float *progress = nullptr; Vector<ExtResource> external_resources; @@ -150,14 +151,16 @@ class ResourceFormatSaverBinaryInstance { }; static void _pad_buffer(FileAccess *f, int p_bytes); - void _write_variant(const Variant &p_property, const PropertyInfo &p_hint = PropertyInfo()); void _find_resources(const Variant &p_variant, bool p_main = false); static void save_unicode_string(FileAccess *f, const String &p_string, bool p_bit_on_len = false); int get_string_index(const String &p_string); public: + enum { + FORMAT_FLAG_NAMED_SCENE_IDS = 1 + }; Error save(const String &p_path, const RES &p_resource, uint32_t p_flags = 0); - static void write_variant(FileAccess *f, const Variant &p_property, Set<RES> &resource_set, Map<RES, int> &external_resources, Map<StringName, int> &string_map, const PropertyInfo &p_hint = PropertyInfo()); + static void write_variant(FileAccess *f, const Variant &p_property, Map<RES, int> &resource_map, Map<RES, int> &external_resources, Map<StringName, int> &string_map, const PropertyInfo &p_hint = PropertyInfo()); }; class ResourceFormatSaverBinary : public ResourceFormatSaver { diff --git a/core/object/object.cpp b/core/object/object.cpp index 0644012318..66a4d17b7b 100644 --- a/core/object/object.cpp +++ b/core/object/object.cpp @@ -922,11 +922,11 @@ Variant Object::get_script() const { return script; } -bool Object::has_meta(const String &p_name) const { +bool Object::has_meta(const StringName &p_name) const { return metadata.has(p_name); } -void Object::set_meta(const String &p_name, const Variant &p_value) { +void Object::set_meta(const StringName &p_name, const Variant &p_value) { if (p_value.get_type() == Variant::NIL) { metadata.erase(p_name); return; @@ -935,12 +935,12 @@ void Object::set_meta(const String &p_name, const Variant &p_value) { metadata[p_name] = p_value; } -Variant Object::get_meta(const String &p_name) const { +Variant Object::get_meta(const StringName &p_name) const { ERR_FAIL_COND_V_MSG(!metadata.has(p_name), Variant(), "The object does not have any 'meta' values with the key '" + p_name + "'."); return metadata[p_name]; } -void Object::remove_meta(const String &p_name) { +void Object::remove_meta(const StringName &p_name) { metadata.erase(p_name); } @@ -964,8 +964,8 @@ Array Object::_get_method_list_bind() const { return ret; } -Vector<String> Object::_get_meta_list_bind() const { - Vector<String> _metaret; +Vector<StringName> Object::_get_meta_list_bind() const { + Vector<StringName> _metaret; List<Variant> keys; metadata.get_key_list(&keys); @@ -976,7 +976,7 @@ Vector<String> Object::_get_meta_list_bind() const { return _metaret; } -void Object::get_meta_list(List<String> *p_list) const { +void Object::get_meta_list(List<StringName> *p_list) const { List<Variant> keys; metadata.get_key_list(&keys); for (List<Variant>::Element *E = keys.front(); E; E = E->next()) { diff --git a/core/object/object.h b/core/object/object.h index 296e07983d..b7d0916a54 100644 --- a/core/object/object.h +++ b/core/object/object.h @@ -615,7 +615,7 @@ protected: return &_class_name; } - Vector<String> _get_meta_list_bind() const; + Vector<StringName> _get_meta_list_bind() const; Array _get_property_list_bind() const; Array _get_method_list_bind() const; @@ -743,11 +743,11 @@ public: /* SCRIPT */ - bool has_meta(const String &p_name) const; - void set_meta(const String &p_name, const Variant &p_value); - void remove_meta(const String &p_name); - Variant get_meta(const String &p_name) const; - void get_meta_list(List<String> *p_list) const; + bool has_meta(const StringName &p_name) const; + void set_meta(const StringName &p_name, const Variant &p_value); + void remove_meta(const StringName &p_name); + Variant get_meta(const StringName &p_name) const; + void get_meta_list(List<StringName> *p_list) const; #ifdef TOOLS_ENABLED void set_edited(bool p_edited); diff --git a/doc/classes/AStar.xml b/doc/classes/AStar.xml index cc7f7072b9..327785a3b6 100644 --- a/doc/classes/AStar.xml +++ b/doc/classes/AStar.xml @@ -33,7 +33,7 @@ [/csharp] [/codeblocks] [method _estimate_cost] should return a lower bound of the distance, i.e. [code]_estimate_cost(u, v) <= _compute_cost(u, v)[/code]. This serves as a hint to the algorithm because the custom [code]_compute_cost[/code] might be computation-heavy. If this is not the case, make [method _estimate_cost] return the same value as [method _compute_cost] to provide the algorithm with the most accurate information. - If the default [method _estimate_cost] and [method _compute_cost] methods are used, or if the supplied [method _estimate_cost] method returns a lower bound of the cost, then the paths returned by A* will be the lowest cost paths. Here, the cost of a path equals to the sum of the [method _compute_cost] results of all segments in the path multiplied by the [code]weight_scale[/code]s of the end points of the respective segments. If the default methods are used and the [code]weight_scale[/code]s of all points are set to [code]1.0[/code], then this equals to the sum of Euclidean distances of all segments in the path. + If the default [method _estimate_cost] and [method _compute_cost] methods are used, or if the supplied [method _estimate_cost] method returns a lower bound of the cost, then the paths returned by A* will be the lowest-cost paths. Here, the cost of a path equals the sum of the [method _compute_cost] results of all segments in the path multiplied by the [code]weight_scale[/code]s of the endpoints of the respective segments. If the default methods are used and the [code]weight_scale[/code]s of all points are set to [code]1.0[/code], then this equals the sum of Euclidean distances of all segments in the path. </description> <tutorials> </tutorials> diff --git a/doc/classes/Animation.xml b/doc/classes/Animation.xml index 894e784397..4d192f8e97 100644 --- a/doc/classes/Animation.xml +++ b/doc/classes/Animation.xml @@ -616,7 +616,7 @@ <argument index="1" name="path" type="NodePath"> </argument> <description> - Sets the path of a track. Paths must be valid scene-tree paths to a node, and must be specified starting from the parent node of the node that will reproduce the animation. Tracks that control properties or bones must append their name after the path, separated by [code]":"[/code]. + Sets the path of a track. Paths must be valid scene-tree paths to a node and must be specified starting from the parent node of the node that will reproduce the animation. Tracks that control properties or bones must append their name after the path, separated by [code]":"[/code]. For example, [code]"character/skeleton:ankle"[/code] or [code]"character/mesh:transform/local"[/code]. </description> </method> diff --git a/doc/classes/Area2D.xml b/doc/classes/Area2D.xml index f52c810ad2..cf2e7044af 100644 --- a/doc/classes/Area2D.xml +++ b/doc/classes/Area2D.xml @@ -34,7 +34,7 @@ </argument> <description> If [code]true[/code], the given area overlaps the Area2D. - [b]Note:[/b] The result of this test is not immediate after moving objects. For performance, list of overlaps is updated once per frame and before the physics step. Consider using signals instead. + [b]Note:[/b] The result of this test is not immediate after moving objects. For performance, the list of overlaps is updated once per frame and before the physics step. Consider using signals instead. </description> </method> <method name="overlaps_body" qualifiers="const"> @@ -45,7 +45,7 @@ <description> If [code]true[/code], the given physics body overlaps the Area2D. [b]Note:[/b] The result of this test is not immediate after moving objects. For performance, list of overlaps is updated once per frame and before the physics step. Consider using signals instead. - The [code]body[/code] argument can either be a [PhysicsBody2D] or a [TileMap] instance (while TileMaps are not physics body themselves, they register their tiles with collision shapes as a virtual physics body). + The [code]body[/code] argument can either be a [PhysicsBody2D] or a [TileMap] instance (while TileMaps are not physics bodies themselves, they register their tiles with collision shapes as a virtual physics body). </description> </method> </methods> diff --git a/doc/classes/Array.xml b/doc/classes/Array.xml index 543ec096c7..1ed7250adc 100644 --- a/doc/classes/Array.xml +++ b/doc/classes/Array.xml @@ -1,10 +1,10 @@ <?xml version="1.0" encoding="UTF-8" ?> <class name="Array" version="4.0"> <brief_description> - Generic array datatype. + A generic array datatype. </brief_description> <description> - Generic array which can contain several elements of any type, accessible by a numerical index starting at 0. Negative indices can be used to count from the back, like in Python (-1 is the last element, -2 the second to last, etc.). + A generic array that can contain several elements of any type, accessible by a numerical index starting at 0. Negative indices can be used to count from the back, like in Python (-1 is the last element, -2 is the second to last, etc.). [b]Example:[/b] [codeblocks] [gdscript] @@ -39,7 +39,7 @@ [/csharp] [/codeblocks] [b]Note:[/b] Concatenating with the [code]+=[/code] operator will create a new array, which has a cost. If you want to append another array to an existing array, [method append_array] is more efficient. - [b]Note:[/b] Arrays are always passed by reference. To get a copy of an array which can be modified independently of the original array, use [method duplicate]. + [b]Note:[/b] Arrays are always passed by reference. To get a copy of an array that can be modified independently of the original array, use [method duplicate]. [b]Note:[/b] When declaring an array with [code]const[/code], the array itself can still be mutated by defining the values at individual indices or pushing/removing elements. Using [code]const[/code] will only prevent assigning the constant with another value after it was initialized. </description> <tutorials> diff --git a/doc/classes/ArrayMesh.xml b/doc/classes/ArrayMesh.xml index 7fbf53c7d1..e3d2eb74ff 100644 --- a/doc/classes/ArrayMesh.xml +++ b/doc/classes/ArrayMesh.xml @@ -75,7 +75,7 @@ </argument> <description> Creates a new surface. - Surfaces are created to be rendered using a [code]primitive[/code], which may be any of the types defined in [enum Mesh.PrimitiveType]. (As a note, when using indices, it is recommended to only use points, lines or triangles.) [method Mesh.get_surface_count] will become the [code]surf_idx[/code] for this new surface. + Surfaces are created to be rendered using a [code]primitive[/code], which may be any of the types defined in [enum Mesh.PrimitiveType]. (As a note, when using indices, it is recommended to only use points, lines, or triangles.) [method Mesh.get_surface_count] will become the [code]surf_idx[/code] for this new surface. The [code]arrays[/code] argument is an array of arrays. See [enum Mesh.ArrayType] for the values used in this array. For example, [code]arrays[0][/code] is the array of vertices. That first vertex sub-array is always required; the others are optional. Adding an index array puts this function into "index mode" where the vertex and other arrays become the sources of data and the index array defines the vertex order. All sub-arrays must have the same length as the vertex array or be empty, except for [constant Mesh.ARRAY_INDEX] if it is used. </description> </method> diff --git a/doc/classes/Camera2D.xml b/doc/classes/Camera2D.xml index e8f4cb49b6..ac90b6950b 100644 --- a/doc/classes/Camera2D.xml +++ b/doc/classes/Camera2D.xml @@ -5,7 +5,7 @@ </brief_description> <description> Camera node for 2D scenes. It forces the screen (current layer) to scroll following this node. This makes it easier (and faster) to program scrollable scenes than manually changing the position of [CanvasItem]-based nodes. - This node is intended to be a simple helper to get things going quickly and it may happen that more functionality is desired to change how the camera works. To make your own custom camera node, inherit from [Node2D] and change the transform of the canvas by setting [member Viewport.canvas_transform] in [Viewport] (you can obtain the current [Viewport] by using [method Node.get_viewport]). + This node is intended to be a simple helper to get things going quickly, but more functionality may be desired to change how the camera works. To make your own custom camera node, inherit it from [Node2D] and change the transform of the canvas by setting [member Viewport.canvas_transform] in [Viewport] (you can obtain the current [Viewport] by using [method Node.get_viewport]). Note that the [Camera2D] node's [code]position[/code] doesn't represent the actual position of the screen, which may differ due to applied smoothing or limits. You can use [method get_camera_screen_center] to get the real position. </description> <tutorials> diff --git a/doc/classes/Camera3D.xml b/doc/classes/Camera3D.xml index 0f0bfdc654..f74baa00a0 100644 --- a/doc/classes/Camera3D.xml +++ b/doc/classes/Camera3D.xml @@ -46,7 +46,7 @@ <return type="Array"> </return> <description> - Returns the camera's frustum planes in world-space units as an array of [Plane]s in the following order: near, far, left, top, right, bottom. Not to be confused with [member frustum_offset]. + Returns the camera's frustum planes in world space units as an array of [Plane]s in the following order: near, far, left, top, right, bottom. Not to be confused with [member frustum_offset]. </description> </method> <method name="is_position_behind" qualifiers="const"> @@ -92,7 +92,7 @@ <argument index="1" name="z_depth" type="float"> </argument> <description> - Returns the 3D point in worldspace that maps to the given 2D coordinate in the [Viewport] rectangle on a plane that is the given [code]z_depth[/code] distance into the scene away from the camera. + Returns the 3D point in world space that maps to the given 2D coordinate in the [Viewport] rectangle on a plane that is the given [code]z_depth[/code] distance into the scene away from the camera. </description> </method> <method name="project_ray_normal" qualifiers="const"> @@ -101,7 +101,7 @@ <argument index="0" name="screen_point" type="Vector2"> </argument> <description> - Returns a normal vector in worldspace, that is the result of projecting a point on the [Viewport] rectangle by the camera projection. This is useful for casting rays in the form of (origin, normal) for object intersection or picking. + Returns a normal vector in world space, that is the result of projecting a point on the [Viewport] rectangle by the camera projection. This is useful for casting rays in the form of (origin, normal) for object intersection or picking. </description> </method> <method name="project_ray_origin" qualifiers="const"> @@ -110,7 +110,7 @@ <argument index="0" name="screen_point" type="Vector2"> </argument> <description> - Returns a 3D position in worldspace, that is the result of projecting a point on the [Viewport] rectangle by the camera projection. This is useful for casting rays in the form of (origin, normal) for object intersection or picking. + Returns a 3D position in world space, that is the result of projecting a point on the [Viewport] rectangle by the camera projection. This is useful for casting rays in the form of (origin, normal) for object intersection or picking. </description> </method> <method name="set_cull_mask_bit"> @@ -136,7 +136,7 @@ <argument index="3" name="z_far" type="float"> </argument> <description> - Sets the camera projection to frustum mode (see [constant PROJECTION_FRUSTUM]), by specifying a [code]size[/code], an [code]offset[/code], and the [code]z_near[/code] and [code]z_far[/code] clip planes in world-space units. + Sets the camera projection to frustum mode (see [constant PROJECTION_FRUSTUM]), by specifying a [code]size[/code], an [code]offset[/code], and the [code]z_near[/code] and [code]z_far[/code] clip planes in world space units. </description> </method> <method name="set_orthogonal"> @@ -149,7 +149,7 @@ <argument index="2" name="z_far" type="float"> </argument> <description> - Sets the camera projection to orthogonal mode (see [constant PROJECTION_ORTHOGONAL]), by specifying a [code]size[/code], and the [code]z_near[/code] and [code]z_far[/code] clip planes in world-space units. (As a hint, 2D games often use this projection, with values specified in pixels.) + Sets the camera projection to orthogonal mode (see [constant PROJECTION_ORTHOGONAL]), by specifying a [code]size[/code], and the [code]z_near[/code] and [code]z_far[/code] clip planes in world space units. (As a hint, 2D games often use this projection, with values specified in pixels.) </description> </method> <method name="set_perspective"> @@ -162,7 +162,7 @@ <argument index="2" name="z_far" type="float"> </argument> <description> - Sets the camera projection to perspective mode (see [constant PROJECTION_PERSPECTIVE]), by specifying a [code]fov[/code] (field of view) angle in degrees, and the [code]z_near[/code] and [code]z_far[/code] clip planes in world-space units. + Sets the camera projection to perspective mode (see [constant PROJECTION_PERSPECTIVE]), by specifying a [code]fov[/code] (field of view) angle in degrees, and the [code]z_near[/code] and [code]z_far[/code] clip planes in world space units. </description> </method> <method name="unproject_position" qualifiers="const"> @@ -171,7 +171,7 @@ <argument index="0" name="world_point" type="Vector3"> </argument> <description> - Returns the 2D coordinate in the [Viewport] rectangle that maps to the given 3D point in worldspace. + Returns the 2D coordinate in the [Viewport] rectangle that maps to the given 3D point in world space. [b]Note:[/b] When using this to position GUI elements over a 3D viewport, use [method is_position_behind] to prevent them from appearing if the 3D point is behind the camera: [codeblock] # This code block is part of a script that inherits from Node3D. diff --git a/doc/classes/Control.xml b/doc/classes/Control.xml index 23cbdd8cab..79b3788449 100644 --- a/doc/classes/Control.xml +++ b/doc/classes/Control.xml @@ -9,7 +9,7 @@ [b]User Interface nodes and input[/b] Godot sends input events to the scene's root node first, by calling [method Node._input]. [method Node._input] forwards the event down the node tree to the nodes under the mouse cursor, or on keyboard focus. To do so, it calls [code]MainLoop._input_event[/code]. [b]FIXME:[/b] No longer valid after DisplayServer split and Input refactoring. - Call [method accept_event] so no other node receives the event. Once you accepted an input, it becomes handled so [method Node._unhandled_input] will not process it. + Call [method accept_event] so no other node receives the event. Once you accept an input, it becomes handled so [method Node._unhandled_input] will not process it. Only one [Control] node can be in keyboard focus. Only the node in focus will receive keyboard events. To get the focus, call [method grab_focus]. [Control] nodes lose focus when another node grabs it, or if you hide the node in focus. Sets [member mouse_filter] to [constant MOUSE_FILTER_IGNORE] to tell a [Control] node to ignore mouse or touch events. You'll need it if you place an icon on top of a button. [Theme] resources change the Control's appearance. If you change the [Theme] on a [Control] node, it affects all of its children. To override some of the theme's parameters, call one of the [code]add_theme_*_override[/code] methods, like [method add_theme_font_override]. You can override the theme with the inspector. @@ -147,7 +147,7 @@ * control has [member mouse_filter] set to [constant MOUSE_FILTER_IGNORE]; * control is obstructed by another [Control] on top of it, which doesn't have [member mouse_filter] set to [constant MOUSE_FILTER_IGNORE]; * control's parent has [member mouse_filter] set to [constant MOUSE_FILTER_STOP] or has accepted the event; - * it happens outside parent's rectangle and the parent has either [member rect_clip_content] enabled. + * it happens outside the parent's rectangle and the parent has either [member rect_clip_content] enabled. </description> </method> <method name="_has_point" qualifiers="virtual const"> @@ -168,10 +168,10 @@ </argument> <description> Virtual method to be implemented by the user. Returns a [Control] node that should be used as a tooltip instead of the default one. The [code]for_text[/code] includes the contents of the [member hint_tooltip] property. - The returned node must be of type [Control] or Control-derived. It can have child nodes of any type. It is freed when the tooltip disappears, so make sure you always provide a new instance (if you want to use a pre-existing node from your scene tree, you can duplicate it and pass the duplicated instance).When [code]null[/code] or a non-Control node is returned, the default tooltip will be used instead. + The returned node must be of type [Control] or Control-derived. It can have child nodes of any type. It is freed when the tooltip disappears, so make sure you always provide a new instance (if you want to use a pre-existing node from your scene tree, you can duplicate it and pass the duplicated instance). When [code]null[/code] or a non-Control node is returned, the default tooltip will be used instead. The returned node will be added as child to a [PopupPanel], so you should only provide the contents of that panel. That [PopupPanel] can be themed using [method Theme.set_stylebox] for the type [code]"TooltipPanel"[/code] (see [member hint_tooltip] for an example). [b]Note:[/b] The tooltip is shrunk to minimal size. If you want to ensure it's fully visible, you might want to set its [member rect_min_size] to some non-zero value. - [b]Note:[/b] The node (and any relevant children) should be [member CanvasItem.visible] when returned, otherwise the viewport that instantiates it will not be able to calculate its minimum size reliably. + [b]Note:[/b] The node (and any relevant children) should be [member CanvasItem.visible] when returned, otherwise, the viewport that instantiates it will not be able to calculate its minimum size reliably. Example of usage with a custom-constructed node: [codeblocks] [gdscript] @@ -860,7 +860,7 @@ <argument index="1" name="keep_offsets" type="bool" default="false"> </argument> <description> - Sets the anchors to a [code]preset[/code] from [enum Control.LayoutPreset] enum. This is code equivalent of using the Layout menu in 2D editor. + Sets the anchors to a [code]preset[/code] from [enum Control.LayoutPreset] enum. This is the code equivalent to using the Layout menu in the 2D editor. If [code]keep_offsets[/code] is [code]true[/code], control's position will also be updated. </description> </method> @@ -1029,7 +1029,7 @@ <argument index="2" name="margin" type="int" default="0"> </argument> <description> - Sets the offsets to a [code]preset[/code] from [enum Control.LayoutPreset] enum. This is code equivalent of using the Layout menu in 2D editor. + Sets the offsets to a [code]preset[/code] from [enum Control.LayoutPreset] enum. This is the code equivalent to using the Layout menu in the 2D editor. Use parameter [code]resize_mode[/code] with constants from [enum Control.LayoutPresetMode] to better determine the resulting size of the [Control]. Constant size will be ignored if used with presets that change size, e.g. [code]PRESET_LEFT_WIDE[/code]. Use parameter [code]margin[/code] to determine the gap between the [Control] and the edges. </description> diff --git a/doc/classes/Directory.xml b/doc/classes/Directory.xml index dae8d83f0c..ff92f6c5ef 100644 --- a/doc/classes/Directory.xml +++ b/doc/classes/Directory.xml @@ -159,7 +159,7 @@ <argument index="1" name="show_hidden" type="bool" default="false"> </argument> <description> - Initializes the stream used to list all files and directories using the [method get_next] function, closing the current opened stream if needed. Once the stream has been processed, it should typically be closed with [method list_dir_end]. + Initializes the stream used to list all files and directories using the [method get_next] function, closing the currently opened stream if needed. Once the stream has been processed, it should typically be closed with [method list_dir_end]. If [code]show_navigational[/code] is [code]true[/code], [code].[/code] and [code]..[/code] are included too. If [code]show_hidden[/code] is [code]true[/code], hidden files are included too. </description> diff --git a/doc/classes/EditorPlugin.xml b/doc/classes/EditorPlugin.xml index 753227513b..efd1d30565 100644 --- a/doc/classes/EditorPlugin.xml +++ b/doc/classes/EditorPlugin.xml @@ -404,7 +404,7 @@ </argument> <description> Adds a custom type, which will appear in the list of nodes or resources. An icon can be optionally passed. - When given node or resource is selected, the base type will be instantiated (e.g. "Node3D", "Control", "Resource"), then the script will be loaded and set to this object. + When a given node or resource is selected, the base type will be instantiated (e.g. "Node3D", "Control", "Resource"), then the script will be loaded and set to this object. You can use the virtual method [method _handles] to check if your custom object is being edited by checking the script or using the [code]is[/code] keyword. During run-time, this will be a simple object with a script so this function does not need to be called then. </description> diff --git a/doc/classes/Engine.xml b/doc/classes/Engine.xml index c0a8407ece..3fa7894fdc 100644 --- a/doc/classes/Engine.xml +++ b/doc/classes/Engine.xml @@ -170,7 +170,7 @@ The number of fixed iterations per second. This controls how often physics simulation and [method Node._physics_process] methods are run. This value should generally always be set to [code]60[/code] or above, as Godot doesn't interpolate the physics step. As a result, values lower than [code]60[/code] will look stuttery. This value can be increased to make input more reactive or work around tunneling issues, but keep in mind doing so will increase CPU usage. </member> <member name="physics_jitter_fix" type="float" setter="set_physics_jitter_fix" getter="get_physics_jitter_fix" default="0.5"> - Controls how much physics ticks are synchronized with real time. For 0 or less, the ticks are synchronized. Such values are recommended for network games, where clock synchronization matters. Higher values cause higher deviation of in-game clock and real clock, but allows smoothing out framerate jitters. The default value of 0.5 should be fine for most; values above 2 could cause the game to react to dropped frames with a noticeable delay and are not recommended. + Controls how much physics ticks are synchronized with real time. For 0 or less, the ticks are synchronized. Such values are recommended for network games, where clock synchronization matters. Higher values cause higher deviation of the in-game clock and real clock but smooth out framerate jitters. The default value of 0.5 should be fine for most; values above 2 could cause the game to react to dropped frames with a noticeable delay and are not recommended. [b]Note:[/b] For best results, when using a custom physics interpolation solution, the physics jitter fix should be disabled by setting [member physics_jitter_fix] to [code]0[/code]. </member> <member name="print_error_messages" type="bool" setter="set_print_error_messages" getter="is_printing_error_messages" default="true"> diff --git a/doc/classes/Image.xml b/doc/classes/Image.xml index 167b90ea73..fd80841a63 100644 --- a/doc/classes/Image.xml +++ b/doc/classes/Image.xml @@ -241,7 +241,7 @@ <argument index="0" name="renormalize" type="bool" default="false"> </argument> <description> - Generates mipmaps for the image. Mipmaps are precalculated and lower resolution copies of the image. Mipmaps are automatically used if the image needs to be scaled down when rendered. This improves image quality and the performance of the rendering. Returns an error if the image is compressed, in a custom format or if the image's width/height is 0. + Generates mipmaps for the image. Mipmaps are precalculated lower-resolution copies of the image that are automatically used if the image needs to be scaled down when rendered. They help improve image quality and performance when rendering. This method returns an error if the image is compressed, in a custom format, or if the image's width/height is [code]0[/code]. </description> </method> <method name="get_data" qualifiers="const"> @@ -710,7 +710,7 @@ </constant> <constant name="INTERPOLATE_TRILINEAR" value="3" enum="Interpolation"> Performs bilinear separately on the two most-suited mipmap levels, then linearly interpolates between them. - It's slower than [constant INTERPOLATE_BILINEAR], but produces higher-quality results with much less aliasing artifacts. + It's slower than [constant INTERPOLATE_BILINEAR], but produces higher-quality results with far fewer aliasing artifacts. If the image does not have mipmaps, they will be generated and used internally, but no mipmaps will be generated on the resulting image. [b]Note:[/b] If you intend to scale multiple copies of the original image, it's better to call [method generate_mipmaps]] on it in advance, to avoid wasting processing power in generating them again and again. On the other hand, if the image already has mipmaps, they will be used, and a new set will be generated for the resulting image. diff --git a/doc/classes/Input.xml b/doc/classes/Input.xml index b970070659..c340895130 100644 --- a/doc/classes/Input.xml +++ b/doc/classes/Input.xml @@ -208,7 +208,7 @@ <argument index="4" name="deadzone" type="float" default="-1.0"> </argument> <description> - Get vector input by specifying four actions, two for the X axis and two for the Y axis, negative and positive. + Gets an input vector by specifying four actions for the positive and negative X and Y axes. This method is useful when getting vector input, such as from a joystick, directional pad, arrows, or WASD. The vector has its length limited to 1 and has a circular deadzone, which is useful for using vector input as movement. By default, the deadzone is automatically calculated from the average of the action deadzones. However, you can override the deadzone to be whatever you want (on the range of 0 to 1). </description> diff --git a/doc/classes/Label.xml b/doc/classes/Label.xml index ca22567ad5..9e96f11ccc 100644 --- a/doc/classes/Label.xml +++ b/doc/classes/Label.xml @@ -4,7 +4,7 @@ Displays plain text in a line or wrapped inside a rectangle. For formatted text, use [RichTextLabel]. </brief_description> <description> - Label displays plain text on the screen. It gives you control over the horizontal and vertical alignment, and can wrap the text inside the node's bounding rectangle. It doesn't support bold, italics or other formatting. For that, use [RichTextLabel] instead. + Label displays plain text on the screen. It gives you control over the horizontal and vertical alignment and can wrap the text inside the node's bounding rectangle. It doesn't support bold, italics, or other formatting. For that, use [RichTextLabel] instead. [b]Note:[/b] Contrarily to most other [Control]s, Label's [member Control.mouse_filter] defaults to [constant Control.MOUSE_FILTER_IGNORE] (i.e. it doesn't react to mouse input events). This implies that a label won't display any configured [member Control.hint_tooltip], unless you change its mouse filter. </description> <tutorials> diff --git a/doc/classes/OS.xml b/doc/classes/OS.xml index a266b0da4f..679a51935d 100644 --- a/doc/classes/OS.xml +++ b/doc/classes/OS.xml @@ -239,7 +239,7 @@ <return type="PackedStringArray"> </return> <description> - With this function you can get the list of dangerous permissions that have been granted to the Android application. + With this function, you can get the list of dangerous permissions that have been granted to the Android application. [b]Note:[/b] This method is implemented on Android. </description> </method> @@ -261,7 +261,7 @@ [code]language[/code] - 2 or 3-letter [url=https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes]language code[/url], in lower case. [code]Script[/code] - optional, 4-letter [url=https://en.wikipedia.org/wiki/ISO_15924]script code[/url], in title case. [code]COUNTRY[/code] - optional, 2 or 3-letter [url=https://en.wikipedia.org/wiki/ISO_3166-1]country code[/url], in upper case. - [code]VARIANT[/code] - optional, language variant, region and sort order. Variant can have any number of underscored key words. + [code]VARIANT[/code] - optional, language variant, region and sort order. Variant can have any number of underscored keywords. [code]extra[/code] - optional, semicolon separated list of additional key words. Currency, calendar, sort order and numbering system information. </description> </method> @@ -332,7 +332,7 @@ </return> <description> Returns a string that is unique to the device. - [b]Note:[/b] This string may change without notice if the user reinstalls/upgrades their operating system or changes their hardware. This means it should generally not be used to encrypt persistent data as the data saved prior to an unexpected ID change would become inaccessible. The returned string may also be falsified using external programs, so do not rely on the string returned by [method get_unique_id] for security purposes. + [b]Note:[/b] This string may change without notice if the user reinstalls/upgrades their operating system or changes their hardware. This means it should generally not be used to encrypt persistent data as the data saved before an unexpected ID change would become inaccessible. The returned string may also be falsified using external programs, so do not rely on the string returned by [method get_unique_id] for security purposes. [b]Note:[/b] Returns an empty string on HTML5 and UWP, as this method isn't implemented on those platforms yet. </description> </method> @@ -364,7 +364,7 @@ <argument index="0" name="tag_name" type="String"> </argument> <description> - Returns [code]true[/code] if the feature for the given feature tag is supported in the currently running instance, depending on platform, build etc. Can be used to check whether you're currently running a debug build, on a certain platform or arch, etc. Refer to the [url=https://docs.godotengine.org/en/latest/getting_started/workflow/export/feature_tags.html]Feature Tags[/url] documentation for more details. + Returns [code]true[/code] if the feature for the given feature tag is supported in the currently running instance, depending on the platform, build, etc. Can be used to check whether you're currently running a debug build, on a certain platform or arch, etc. Refer to the [url=https://docs.godotengine.org/en/latest/getting_started/workflow/export/feature_tags.html]Feature Tags[/url] documentation for more details. [b]Note:[/b] Tag names are case-sensitive. </description> </method> @@ -466,7 +466,7 @@ <return type="bool"> </return> <description> - With this function you can request dangerous permissions since normal permissions are automatically granted at install time in Android application. + With this function, you can request dangerous permissions since normal permissions are automatically granted at install time in Android applications. [b]Note:[/b] This method is implemented on Android. </description> </method> diff --git a/doc/classes/Object.xml b/doc/classes/Object.xml index cdf76a3a59..fe7529bceb 100644 --- a/doc/classes/Object.xml +++ b/doc/classes/Object.xml @@ -400,7 +400,7 @@ <method name="get_meta" qualifiers="const"> <return type="Variant"> </return> - <argument index="0" name="name" type="String"> + <argument index="0" name="name" type="StringName"> </argument> <description> Returns the object's metadata entry for the given [code]name[/code]. @@ -454,7 +454,7 @@ <method name="has_meta" qualifiers="const"> <return type="bool"> </return> - <argument index="0" name="name" type="String"> + <argument index="0" name="name" type="StringName"> </argument> <description> Returns [code]true[/code] if a metadata entry is found with the given [code]name[/code]. @@ -543,7 +543,7 @@ <method name="remove_meta"> <return type="void"> </return> - <argument index="0" name="name" type="String"> + <argument index="0" name="name" type="StringName"> </argument> <description> Removes a given entry from the object's metadata. See also [method set_meta]. @@ -619,7 +619,7 @@ <method name="set_meta"> <return type="void"> </return> - <argument index="0" name="name" type="String"> + <argument index="0" name="name" type="StringName"> </argument> <argument index="1" name="value" type="Variant"> </argument> diff --git a/editor/plugins/asset_library_editor_plugin.cpp b/editor/plugins/asset_library_editor_plugin.cpp index bbc8107cf6..556f35d4b0 100644 --- a/editor/plugins/asset_library_editor_plugin.cpp +++ b/editor/plugins/asset_library_editor_plugin.cpp @@ -603,8 +603,7 @@ void EditorAssetLibrary::_notification(int p_what) { void EditorAssetLibrary::_update_repository_options() { Dictionary default_urls; - default_urls["godotengine.org"] = "https://godotengine.org/asset-library/api"; - default_urls["localhost"] = "http://127.0.0.1/asset-library/api"; + default_urls["godotengine.org (Official)"] = "https://godotengine.org/asset-library/api"; Dictionary available_urls = _EDITOR_DEF("asset_library/available_urls", default_urls, true); repository->clear(); Array keys = available_urls.keys(); diff --git a/editor/plugins/collision_shape_2d_editor_plugin.cpp b/editor/plugins/collision_shape_2d_editor_plugin.cpp index 45adbe29de..2c2adc2672 100644 --- a/editor/plugins/collision_shape_2d_editor_plugin.cpp +++ b/editor/plugins/collision_shape_2d_editor_plugin.cpp @@ -31,6 +31,7 @@ #include "collision_shape_2d_editor_plugin.h" #include "canvas_item_editor_plugin.h" +#include "core/os/keyboard.h" #include "scene/resources/capsule_shape_2d.h" #include "scene/resources/circle_shape_2d.h" #include "scene/resources/concave_polygon_shape_2d.h" @@ -97,7 +98,7 @@ Variant CollisionShape2DEditor::get_handle_value(int idx) const { case RECTANGLE_SHAPE: { Ref<RectangleShape2D> rect = node->get_shape(); - if (idx < 3) { + if (idx < 8) { return rect->get_size().abs(); } @@ -176,16 +177,26 @@ void CollisionShape2DEditor::set_handle(int idx, Point2 &p_point) { } break; case RECTANGLE_SHAPE: { - if (idx < 3) { + if (idx < 8) { Ref<RectangleShape2D> rect = node->get_shape(); + Vector2 size = (Point2)original; - Vector2 size = rect->get_size(); - if (idx == 2) { - size = p_point * 2; + if (RECT_HANDLES[idx].x != 0) { + size.x = p_point.x * RECT_HANDLES[idx].x * 2; + } + if (RECT_HANDLES[idx].y != 0) { + size.y = p_point.y * RECT_HANDLES[idx].y * 2; + } + + if (Input::get_singleton()->is_key_pressed(KEY_ALT)) { + rect->set_size(size.abs()); + node->set_global_position(original_transform.get_origin()); } else { - size[idx] = p_point[idx] * 2; + rect->set_size(((Point2)original + (size - (Point2)original) * 0.5).abs()); + Point2 pos = original_transform.affine_inverse().xform(original_transform.get_origin()); + pos += (size - (Point2)original) * 0.5 * RECT_HANDLES[idx] * 0.5; + node->set_global_position(original_transform.xform(pos)); } - rect->set_size(size.abs()); canvas_item_editor->update_viewport(); } @@ -280,8 +291,10 @@ void CollisionShape2DEditor::commit_handle(int idx, Variant &p_org) { Ref<RectangleShape2D> rect = node->get_shape(); undo_redo->add_do_method(rect.ptr(), "set_size", rect->get_size()); + undo_redo->add_do_method(node, "set_global_transform", node->get_global_transform()); undo_redo->add_do_method(canvas_item_editor, "update_viewport"); undo_redo->add_undo_method(rect.ptr(), "set_size", p_org); + undo_redo->add_undo_method(node, "set_global_transform", original_transform); undo_redo->add_undo_method(canvas_item_editor, "update_viewport"); } break; @@ -342,6 +355,8 @@ bool CollisionShape2DEditor::forward_canvas_gui_input(const Ref<InputEvent> &p_e } original = get_handle_value(edit_handle); + original_transform = node->get_global_transform(); + last_point = original; pressed = true; return true; @@ -369,13 +384,26 @@ bool CollisionShape2DEditor::forward_canvas_gui_input(const Ref<InputEvent> &p_e } Vector2 cpoint = canvas_item_editor->snap_point(canvas_item_editor->get_canvas_transform().affine_inverse().xform(mm->get_position())); - cpoint = node->get_global_transform().affine_inverse().xform(cpoint); + cpoint = original_transform.affine_inverse().xform(cpoint); + last_point = cpoint; set_handle(edit_handle, cpoint); return true; } + Ref<InputEventKey> k = p_event; + + if (k.is_valid()) { + if (edit_handle == -1 || !pressed || k->is_echo()) { + return false; + } + + if (shape_type == RECTANGLE_SHAPE && k->get_keycode() == KEY_ALT) { + set_handle(edit_handle, last_point); // Update handle when Alt key is toggled. + } + } + return false; } @@ -492,15 +520,12 @@ void CollisionShape2DEditor::forward_canvas_draw_over_viewport(Control *p_overla case RECTANGLE_SHAPE: { Ref<RectangleShape2D> shape = node->get_shape(); - handles.resize(3); + handles.resize(8); Vector2 ext = shape->get_size() / 2; - handles.write[0] = Point2(ext.x, 0); - handles.write[1] = Point2(0, ext.y); - handles.write[2] = Point2(ext.x, ext.y); - - p_overlay->draw_texture(h, gt.xform(handles[0]) - size); - p_overlay->draw_texture(h, gt.xform(handles[1]) - size); - p_overlay->draw_texture(h, gt.xform(handles[2]) - size); + for (int i = 0; i < handles.size(); i++) { + handles.write[i] = RECT_HANDLES[i] * ext; + p_overlay->draw_texture(h, gt.xform(handles[i]) - size); + } } break; diff --git a/editor/plugins/collision_shape_2d_editor_plugin.h b/editor/plugins/collision_shape_2d_editor_plugin.h index 054db1a61b..7db6bd22aa 100644 --- a/editor/plugins/collision_shape_2d_editor_plugin.h +++ b/editor/plugins/collision_shape_2d_editor_plugin.h @@ -52,6 +52,17 @@ class CollisionShape2DEditor : public Control { SEGMENT_SHAPE }; + const Point2 RECT_HANDLES[8] = { + Point2(1, 0), + Point2(1, 1), + Point2(0, 1), + Point2(-1, 1), + Point2(-1, 0), + Point2(-1, -1), + Point2(0, -1), + Point2(1, -1), + }; + EditorNode *editor; UndoRedo *undo_redo; CanvasItemEditor *canvas_item_editor; @@ -63,6 +74,8 @@ class CollisionShape2DEditor : public Control { int edit_handle; bool pressed; Variant original; + Transform2D original_transform; + Point2 last_point; Variant get_handle_value(int idx) const; void set_handle(int idx, Point2 &p_point); diff --git a/editor/plugins/texture_editor_plugin.cpp b/editor/plugins/texture_editor_plugin.cpp index 4029d6785c..44db06bcfd 100644 --- a/editor/plugins/texture_editor_plugin.cpp +++ b/editor/plugins/texture_editor_plugin.cpp @@ -36,9 +36,30 @@ TextureRect *TexturePreview::get_texture_display() { return texture_display; } +void TexturePreview::_notification(int p_what) { + switch (p_what) { + case NOTIFICATION_ENTER_TREE: + case NOTIFICATION_THEME_CHANGED: { + if (!is_inside_tree()) { + // TODO: This is a workaround because `NOTIFICATION_THEME_CHANGED` + // is getting called for some reason when the `TexturePreview` is + // getting destroyed, which causes `get_theme_font()` to return `nullptr`. + // See https://github.com/godotengine/godot/issues/50743. + break; + } + + if (metadata_label) { + Ref<Font> metadata_label_font = get_theme_font(SNAME("expression"), SNAME("EditorFonts")); + metadata_label->add_theme_font_override("font", metadata_label_font); + } + + checkerboard->set_texture(get_theme_icon(SNAME("Checkerboard"), SNAME("EditorIcons"))); + } break; + } +} + TexturePreview::TexturePreview(Ref<Texture2D> p_texture, bool p_show_metadata) { - TextureRect *checkerboard = memnew(TextureRect); - checkerboard->set_texture(get_theme_icon("Checkerboard", "EditorIcons")); + checkerboard = memnew(TextureRect); checkerboard->set_stretch_mode(TextureRect::STRETCH_TILE); checkerboard->set_texture_repeat(CanvasItem::TEXTURE_REPEAT_ENABLED); checkerboard->set_custom_minimum_size(Size2(0.0, 256.0) * EDSCALE); @@ -52,7 +73,7 @@ TexturePreview::TexturePreview(Ref<Texture2D> p_texture, bool p_show_metadata) { add_child(texture_display); if (p_show_metadata) { - Label *metadata_label = memnew(Label); + metadata_label = memnew(Label); String format; if (Object::cast_to<ImageTexture>(*p_texture)) { @@ -65,16 +86,14 @@ TexturePreview::TexturePreview(Ref<Texture2D> p_texture, bool p_show_metadata) { metadata_label->set_text(itos(p_texture->get_width()) + "x" + itos(p_texture->get_height()) + " " + format); - metadata_label->add_theme_font_size_override("font_size", 16 * EDSCALE); - metadata_label->add_theme_color_override("font_outline_color", Color::named("black")); - metadata_label->add_theme_constant_override("outline_size", 2 * EDSCALE); - Ref<Font> metadata_label_font = get_theme_font("expression", "EditorFonts"); - metadata_label->add_theme_font_override("font", metadata_label_font); - // It's okay that these colors are static since the grid color is static too. metadata_label->add_theme_color_override("font_color", Color::named("white")); metadata_label->add_theme_color_override("font_color_shadow", Color::named("black")); + metadata_label->add_theme_font_size_override("font_size", 16 * EDSCALE); + metadata_label->add_theme_color_override("font_outline_color", Color::named("black")); + metadata_label->add_theme_constant_override("outline_size", 2 * EDSCALE); + metadata_label->add_theme_constant_override("shadow_as_outline", 1); metadata_label->set_h_size_flags(Control::SIZE_SHRINK_END); metadata_label->set_v_size_flags(Control::SIZE_SHRINK_END); diff --git a/editor/plugins/texture_editor_plugin.h b/editor/plugins/texture_editor_plugin.h index 165090be93..36a5513ea6 100644 --- a/editor/plugins/texture_editor_plugin.h +++ b/editor/plugins/texture_editor_plugin.h @@ -39,7 +39,13 @@ class TexturePreview : public MarginContainer { GDCLASS(TexturePreview, MarginContainer); private: - TextureRect *texture_display; + TextureRect *texture_display = nullptr; + + TextureRect *checkerboard = nullptr; + Label *metadata_label = nullptr; + +protected: + void _notification(int p_what); public: TextureRect *get_texture_display(); diff --git a/editor/project_manager.cpp b/editor/project_manager.cpp index 46a1253ca0..447fe6424f 100644 --- a/editor/project_manager.cpp +++ b/editor/project_manager.cpp @@ -494,13 +494,13 @@ private: if (!f) { set_message(TTR("Couldn't create project.godot in project path."), MESSAGE_ERROR); } else { - f->store_line("[gd_resource type=\"Environment\" load_steps=2 format=2]"); + f->store_line("[gd_resource type=\"Environment\" load_steps=2 format=3]"); f->store_line(""); - f->store_line("[sub_resource type=\"Sky\" id=1]"); + f->store_line("[sub_resource type=\"Sky\" id=\"1\"]"); f->store_line(""); f->store_line("[resource]"); f->store_line("background_mode = 2"); - f->store_line("sky = SubResource( 1 )"); + f->store_line("sky = SubResource( \"1\" )"); memdelete(f); } } diff --git a/modules/gltf/gltf_document.cpp b/modules/gltf/gltf_document.cpp index 09bae884b9..30d023606f 100644 --- a/modules/gltf/gltf_document.cpp +++ b/modules/gltf/gltf_document.cpp @@ -2796,7 +2796,7 @@ Error GLTFDocument::_parse_meshes(Ref<GLTFState> state) { mat = mat3d; } - import_mesh->add_surface(primitive, array, morphs, Dictionary(), mat, mat->get_name()); + import_mesh->add_surface(primitive, array, morphs, Dictionary(), mat, mat.is_valid() ? mat->get_name() : String()); } Vector<float> blend_weights; diff --git a/scene/gui/menu_button.cpp b/scene/gui/menu_button.cpp index 1e9baa77fc..73034dee5a 100644 --- a/scene/gui/menu_button.cpp +++ b/scene/gui/menu_button.cpp @@ -55,6 +55,11 @@ void MenuButton::_unhandled_key_input(Ref<InputEvent> p_event) { } } +void MenuButton::_popup_visibility_changed(bool p_visible) { + set_pressed(p_visible); + set_process_internal(p_visible); +} + void MenuButton::pressed() { Size2 size = get_size(); @@ -94,10 +99,26 @@ bool MenuButton::is_switch_on_hover() { } void MenuButton::_notification(int p_what) { - if (p_what == NOTIFICATION_VISIBILITY_CHANGED) { - if (!is_visible_in_tree()) { - popup->hide(); - } + switch (p_what) { + case NOTIFICATION_VISIBILITY_CHANGED: { + if (!is_visible_in_tree()) { + popup->hide(); + } + } break; + case NOTIFICATION_INTERNAL_PROCESS: { + if (switch_on_hover) { + Window *window = Object::cast_to<Window>(get_viewport()); + if (window) { + Vector2i mouse_pos = DisplayServer::get_singleton()->mouse_get_position() - window->get_position(); + MenuButton *menu_btn_other = Object::cast_to<MenuButton>(window->gui_find_control(mouse_pos)); + if (menu_btn_other && menu_btn_other != this && menu_btn_other->is_switch_on_hover() && !menu_btn_other->is_disabled() && + (get_parent()->is_ancestor_of(menu_btn_other) || menu_btn_other->get_parent()->is_ancestor_of(popup))) { + popup->hide(); + menu_btn_other->pressed(); + } + } + } + } break; } } @@ -130,8 +151,8 @@ MenuButton::MenuButton() { popup = memnew(PopupMenu); popup->hide(); add_child(popup); - popup->connect("about_to_popup", callable_mp((BaseButton *)this, &BaseButton::set_pressed), varray(true)); // For when switching from another MenuButton. - popup->connect("popup_hide", callable_mp((BaseButton *)this, &BaseButton::set_pressed), varray(false)); + popup->connect("about_to_popup", callable_mp(this, &MenuButton::_popup_visibility_changed), varray(true)); + popup->connect("popup_hide", callable_mp(this, &MenuButton::_popup_visibility_changed), varray(false)); } MenuButton::~MenuButton() { diff --git a/scene/gui/menu_button.h b/scene/gui/menu_button.h index fd9ae6021e..301769b008 100644 --- a/scene/gui/menu_button.h +++ b/scene/gui/menu_button.h @@ -47,6 +47,8 @@ class MenuButton : public Button { void _gui_input(Ref<InputEvent> p_event) override; + void _popup_visibility_changed(bool p_visible); + protected: void _notification(int p_what); static void _bind_methods(); diff --git a/scene/gui/popup_menu.h b/scene/gui/popup_menu.h index 74718395d3..aedc5d0155 100644 --- a/scene/gui/popup_menu.h +++ b/scene/gui/popup_menu.h @@ -140,7 +140,6 @@ class PopupMenu : public Popup { void _close_pressed(); protected: - friend class MenuButton; void _notification(int p_what); static void _bind_methods(); diff --git a/scene/main/scene_tree.cpp b/scene/main/scene_tree.cpp index 2fe5d7aa78..644ce92018 100644 --- a/scene/main/scene_tree.cpp +++ b/scene/main/scene_tree.cpp @@ -82,6 +82,14 @@ bool SceneTreeTimer::is_process_always() { return process_always; } +void SceneTreeTimer::set_ignore_time_scale(bool p_ignore) { + ignore_time_scale = p_ignore; +} + +bool SceneTreeTimer::is_ignore_time_scale() { + return ignore_time_scale; +} + void SceneTreeTimer::release_connections() { List<Connection> connections; get_all_signal_connections(&connections); @@ -466,8 +474,13 @@ bool SceneTree::process(float p_time) { E = N; continue; } + float time_left = E->get()->get_time_left(); - time_left -= p_time; + if (E->get()->is_ignore_time_scale()) { + time_left -= Engine::get_singleton()->get_process_step(); + } else { + time_left -= p_time; + } E->get()->set_time_left(time_left); if (time_left < 0) { diff --git a/scene/main/scene_tree.h b/scene/main/scene_tree.h index 0be0e185a5..c3d59663d6 100644 --- a/scene/main/scene_tree.h +++ b/scene/main/scene_tree.h @@ -54,6 +54,7 @@ class SceneTreeTimer : public RefCounted { float time_left = 0.0; bool process_always = true; + bool ignore_time_scale = false; protected: static void _bind_methods(); @@ -65,6 +66,9 @@ public: void set_process_always(bool p_process_always); bool is_process_always(); + void set_ignore_time_scale(bool p_ignore); + bool is_ignore_time_scale(); + void release_connections(); SceneTreeTimer(); diff --git a/scene/main/viewport.cpp b/scene/main/viewport.cpp index 5a0a27520b..34a871b993 100644 --- a/scene/main/viewport.cpp +++ b/scene/main/viewport.cpp @@ -502,17 +502,6 @@ void Viewport::_notification(int p_what) { RS::get_singleton()->viewport_set_active(viewport, false); RenderingServer::get_singleton()->viewport_set_parent_viewport(viewport, RID()); - - } break; - case NOTIFICATION_INTERNAL_PROCESS: { - if (gui.tooltip_timer >= 0) { - gui.tooltip_timer -= get_process_delta_time(); - if (gui.tooltip_timer < 0) { - _gui_show_tooltip(); - set_process_internal(false); - } - } - } break; case NOTIFICATION_INTERNAL_PHYSICS_PROCESS: { if (get_tree()->is_debugging_collisions_hint() && contact_2d_debug.is_valid()) { @@ -1489,8 +1478,10 @@ void Viewport::_gui_sort_roots() { void Viewport::_gui_cancel_tooltip() { gui.tooltip_control = nullptr; - gui.tooltip_timer = -1; - set_process_internal(false); + if (gui.tooltip_timer.is_valid()) { + gui.tooltip_timer->release_connections(); + gui.tooltip_timer = Ref<SceneTreeTimer>(); + } if (gui.tooltip_popup) { gui.tooltip_popup->queue_delete(); gui.tooltip_popup = nullptr; @@ -1710,7 +1701,7 @@ void Viewport::_gui_call_notification(Control *p_control, int p_what) { //_unblock(); } -Control *Viewport::_gui_find_control(const Point2 &p_global) { +Control *Viewport::gui_find_control(const Point2 &p_global) { //aca va subwindows _gui_sort_roots(); @@ -1850,7 +1841,7 @@ void Viewport::_gui_input_event(Ref<InputEvent> p_event) { parent_xform=data.parent_canvas_item->get_global_transform(); */ - gui.mouse_focus = _gui_find_control(pos); + gui.mouse_focus = gui_find_control(pos); gui.last_mouse_focus = gui.mouse_focus; if (!gui.mouse_focus) { @@ -1991,7 +1982,7 @@ void Viewport::_gui_input_event(Ref<InputEvent> p_event) { if (gui.mouse_focus) { over = gui.mouse_focus; } else { - over = _gui_find_control(mpos); + over = gui_find_control(mpos); } if (gui.mouse_focus_mask == 0 && over != gui.mouse_over) { @@ -2074,7 +2065,7 @@ void Viewport::_gui_input_event(Ref<InputEvent> p_event) { if (gui.mouse_focus) { over = gui.mouse_focus; } else { - over = _gui_find_control(mpos); + over = gui_find_control(mpos); } if (over != gui.mouse_over) { @@ -2139,10 +2130,15 @@ void Viewport::_gui_input_event(Ref<InputEvent> p_event) { } if (can_tooltip && !is_tooltip_shown) { + if (gui.tooltip_timer.is_valid()) { + gui.tooltip_timer->release_connections(); + gui.tooltip_timer = Ref<SceneTreeTimer>(); + } gui.tooltip_control = over; gui.tooltip_pos = over->get_screen_transform().xform(pos); - gui.tooltip_timer = gui.tooltip_delay; - set_process_internal(true); + gui.tooltip_timer = get_tree()->create_timer(gui.tooltip_delay); + gui.tooltip_timer->set_ignore_time_scale(true); + gui.tooltip_timer->connect("timeout", callable_mp(this, &Viewport::_gui_show_tooltip)); } } @@ -2262,7 +2258,7 @@ void Viewport::_gui_input_event(Ref<InputEvent> p_event) { Transform2D ai = (viewport_under->get_final_transform().affine_inverse() * viewport_under->_get_input_pre_xform()); viewport_pos = ai.xform(viewport_pos); //find control under at pos - gui.drag_mouse_over = viewport_under->_gui_find_control(viewport_pos); + gui.drag_mouse_over = viewport_under->gui_find_control(viewport_pos); if (gui.drag_mouse_over) { Transform2D localizer = gui.drag_mouse_over->get_global_transform_with_canvas().affine_inverse(); gui.drag_mouse_over_pos = localizer.xform(viewport_pos); @@ -2290,7 +2286,7 @@ void Viewport::_gui_input_event(Ref<InputEvent> p_event) { if (touch_event.is_valid()) { Size2 pos = touch_event->get_position(); if (touch_event->is_pressed()) { - Control *over = _gui_find_control(pos); + Control *over = gui_find_control(pos); if (over) { if (over->can_process()) { touch_event = touch_event->xformed_by(Transform2D()); //make a copy @@ -2325,7 +2321,7 @@ void Viewport::_gui_input_event(Ref<InputEvent> p_event) { Size2 pos = gesture_event->get_position(); - Control *over = _gui_find_control(pos); + Control *over = gui_find_control(pos); if (over) { if (over->can_process()) { gesture_event = gesture_event->xformed_by(Transform2D()); //make a copy @@ -2346,7 +2342,7 @@ void Viewport::_gui_input_event(Ref<InputEvent> p_event) { if (drag_event.is_valid()) { Control *over = gui.mouse_focus; if (!over) { - over = _gui_find_control(drag_event->get_position()); + over = gui_find_control(drag_event->get_position()); } if (over) { if (over->can_process()) { diff --git a/scene/main/viewport.h b/scene/main/viewport.h index 32b6ba84b5..b5c49a8a97 100644 --- a/scene/main/viewport.h +++ b/scene/main/viewport.h @@ -50,6 +50,7 @@ class Label; class Timer; class Viewport; class CollisionObject3D; +class SceneTreeTimer; class ViewportTexture : public Texture2D { GDCLASS(ViewportTexture, Texture2D); @@ -373,7 +374,7 @@ private: bool drag_attempted = false; Variant drag_data; ObjectID drag_preview_id; - float tooltip_timer = -1.0; + Ref<SceneTreeTimer> tooltip_timer; float tooltip_delay = 0.0; Transform2D focus_inv_xform; bool roots_order_dirty = false; @@ -404,7 +405,6 @@ private: void _gui_call_notification(Control *p_control, int p_what); void _gui_sort_roots(); - Control *_gui_find_control(const Point2 &p_global); Control *_gui_find_control_at_pos(CanvasItem *p_node, const Point2 &p_global, const Transform2D &p_xform, Transform2D &r_inv_xform); void _gui_input_event(Ref<InputEvent> p_event); @@ -629,6 +629,8 @@ public: bool gui_is_dragging() const; + Control *gui_find_control(const Point2 &p_global); + void set_sdf_oversize(SDFOversize p_sdf_oversize); SDFOversize get_sdf_oversize() const; diff --git a/scene/resources/resource_format_text.cpp b/scene/resources/resource_format_text.cpp index ee61e64ed3..5cf107e8ea 100644 --- a/scene/resources/resource_format_text.cpp +++ b/scene/resources/resource_format_text.cpp @@ -35,8 +35,9 @@ #include "core/io/resource_format_binary.h" #include "core/version.h" -//version 2: changed names for basis, aabb, Vectors, etc. -#define FORMAT_VERSION 2 +// Version 2: changed names for Basis, AABB, Vectors, etc. +// Version 3: new string ID for ext/subresources, breaks forward compat. +#define FORMAT_VERSION 3 #include "core/io/dir_access.h" #include "core/version.h" @@ -56,22 +57,23 @@ Ref<Resource> ResourceLoaderText::get_resource() { Error ResourceLoaderText::_parse_sub_resource_dummy(DummyReadData *p_data, VariantParser::Stream *p_stream, Ref<Resource> &r_res, int &line, String &r_err_str) { VariantParser::Token token; VariantParser::get_token(p_stream, token, line, r_err_str); - if (token.type != VariantParser::TK_NUMBER) { - r_err_str = "Expected number (sub-resource index)"; + if (token.type != VariantParser::TK_NUMBER && token.type != VariantParser::TK_STRING) { + r_err_str = "Expected number (old style) or string (sub-resource index)"; return ERR_PARSE_ERROR; } - int index = token.value; + String unique_id = token.value; - if (!p_data->resource_map.has(index)) { + if (!p_data->resource_map.has(unique_id)) { Ref<DummyResource> dr; dr.instantiate(); - dr->set_subindex(index); - p_data->resource_map[index] = dr; - p_data->resource_set.insert(dr); + dr->set_scene_unique_id(unique_id); + p_data->resource_map[unique_id] = dr; + uint32_t im_size = p_data->resource_index_map.size(); + p_data->resource_index_map.insert(dr, im_size); } - r_res = p_data->resource_map[index]; + r_res = p_data->resource_map[unique_id]; VariantParser::get_token(p_stream, token, line, r_err_str); if (token.type != VariantParser::TK_PARENTHESIS_CLOSE) { @@ -85,12 +87,12 @@ Error ResourceLoaderText::_parse_sub_resource_dummy(DummyReadData *p_data, Varia Error ResourceLoaderText::_parse_ext_resource_dummy(DummyReadData *p_data, VariantParser::Stream *p_stream, Ref<Resource> &r_res, int &line, String &r_err_str) { VariantParser::Token token; VariantParser::get_token(p_stream, token, line, r_err_str); - if (token.type != VariantParser::TK_NUMBER) { - r_err_str = "Expected number (sub-resource index)"; + if (token.type != VariantParser::TK_NUMBER && token.type != VariantParser::TK_STRING) { + r_err_str = "Expected number (old style sub-resource index) or String (ext-resource ID)"; return ERR_PARSE_ERROR; } - int id = token.value; + String id = token.value; ERR_FAIL_COND_V(!p_data->rev_external_resources.has(id), ERR_PARSE_ERROR); @@ -108,14 +110,14 @@ Error ResourceLoaderText::_parse_ext_resource_dummy(DummyReadData *p_data, Varia Error ResourceLoaderText::_parse_sub_resource(VariantParser::Stream *p_stream, Ref<Resource> &r_res, int &line, String &r_err_str) { VariantParser::Token token; VariantParser::get_token(p_stream, token, line, r_err_str); - if (token.type != VariantParser::TK_NUMBER) { - r_err_str = "Expected number (sub-resource index)"; + if (token.type != VariantParser::TK_NUMBER && token.type != VariantParser::TK_STRING) { + r_err_str = "Expected number (old style sub-resource index) or string"; return ERR_PARSE_ERROR; } - int index = token.value; - ERR_FAIL_COND_V(!int_resources.has(index), ERR_INVALID_PARAMETER); - r_res = int_resources[index]; + String id = token.value; + ERR_FAIL_COND_V(!int_resources.has(id), ERR_INVALID_PARAMETER); + r_res = int_resources[id]; VariantParser::get_token(p_stream, token, line, r_err_str); if (token.type != VariantParser::TK_PARENTHESIS_CLOSE) { @@ -129,16 +131,16 @@ Error ResourceLoaderText::_parse_sub_resource(VariantParser::Stream *p_stream, R Error ResourceLoaderText::_parse_ext_resource(VariantParser::Stream *p_stream, Ref<Resource> &r_res, int &line, String &r_err_str) { VariantParser::Token token; VariantParser::get_token(p_stream, token, line, r_err_str); - if (token.type != VariantParser::TK_NUMBER) { - r_err_str = "Expected number (sub-resource index)"; + if (token.type != VariantParser::TK_NUMBER && token.type != VariantParser::TK_STRING) { + r_err_str = "Expected number (old style sub-resource index) or String (ext-resource ID)"; return ERR_PARSE_ERROR; } - int id = token.value; + String id = token.value; if (!ignore_resource_parsing) { if (!ext_resources.has(id)) { - r_err_str = "Can't load cached ext-resource #" + itos(id); + r_err_str = "Can't load cached ext-resource id: " + id; return ERR_PARSE_ERROR; } @@ -409,7 +411,7 @@ Error ResourceLoaderText::load() { String path = next_tag.fields["path"]; String type = next_tag.fields["type"]; - int index = next_tag.fields["id"]; + String id = next_tag.fields["id"]; if (path.find("://") == -1 && path.is_rel_path()) { // path is relative to file being loaded, so convert to a resource path @@ -453,14 +455,14 @@ Error ResourceLoaderText::load() { } else { #ifdef TOOLS_ENABLED //remember ID for saving - res->set_id_for_path(local_path, index); + res->set_id_for_path(local_path, id); #endif } er.cache = res; } - ext_resources[index] = er; + ext_resources[id] = er; error = VariantParser::parse_tag(&stream, lines, error_text, next_tag, &rp); @@ -489,15 +491,15 @@ Error ResourceLoaderText::load() { if (!next_tag.fields.has("id")) { error = ERR_FILE_CORRUPT; - error_text = "Missing 'index' in external resource tag"; + error_text = "Missing 'id' in external resource tag"; _printerr(); return error; } String type = next_tag.fields["type"]; - int id = next_tag.fields["id"]; + String id = next_tag.fields["id"]; - String path = local_path + "::" + itos(id); + String path = local_path + "::" + id; //bool exists=ResourceCache::has(path); @@ -575,7 +577,7 @@ Error ResourceLoaderText::load() { int_resources[id] = res; //always assign int resources if (do_assign && cache_mode != ResourceFormatLoader::CACHE_MODE_IGNORE) { res->set_path(path, cache_mode == ResourceFormatLoader::CACHE_MODE_REPLACE); - res->set_subindex(id); + res->set_scene_unique_id(id); } if (progress && resources_total > 0) { @@ -736,7 +738,7 @@ void ResourceLoaderText::get_dependencies(FileAccess *p_f, List<String> *p_depen if (!next_tag.fields.has("id")) { error = ERR_FILE_CORRUPT; - error_text = "Missing 'index' in external resource tag"; + error_text = "Missing 'id' in external resource tag"; _printerr(); return; } @@ -814,7 +816,7 @@ Error ResourceLoaderText::rename_dependencies(FileAccess *p_f, const String &p_p } String path = next_tag.fields["path"]; - int index = next_tag.fields["id"]; + String id = next_tag.fields["id"]; String type = next_tag.fields["type"]; bool relative = false; @@ -833,7 +835,7 @@ Error ResourceLoaderText::rename_dependencies(FileAccess *p_f, const String &p_p path = base_path.path_to_file(path); } - fw->store_line("[ext_resource path=\"" + path + "\" type=\"" + type + "\" id=" + itos(index) + "]"); + fw->store_line("[ext_resource path=\"" + path + "\" type=\"" + type + "\" id=\"" + id + "\"]"); tag_end = f->get_position(); } @@ -1015,7 +1017,7 @@ Error ResourceLoaderText::save_as_binary(FileAccess *p_f, const String &p_path) String path = next_tag.fields["path"]; String type = next_tag.fields["type"]; - int index = next_tag.fields["id"]; + String id = next_tag.fields["id"]; bs_save_unicode_string(wf.f, type); bs_save_unicode_string(wf.f, path); @@ -1025,7 +1027,7 @@ Error ResourceLoaderText::save_as_binary(FileAccess *p_f, const String &p_path) dr.instantiate(); dr->set_path("res://dummy" + itos(lindex)); //anything is good to detect it for saving as external dummy_read.external_resources[dr] = lindex; - dummy_read.rev_external_resources[index] = dr; + dummy_read.rev_external_resources[id] = dr; error = VariantParser::parse_tag(&stream, lines, error_text, next_tag, &rp); @@ -1069,7 +1071,7 @@ Error ResourceLoaderText::save_as_binary(FileAccess *p_f, const String &p_path) if (!next_tag.fields.has("id")) { error = ERR_FILE_CORRUPT; - error_text = "Missing 'index' in external resource tag"; + error_text = "Missing 'id' in external resource tag"; _printerr(); return error; } @@ -1114,7 +1116,7 @@ Error ResourceLoaderText::save_as_binary(FileAccess *p_f, const String &p_path) if (assign != String()) { Map<StringName, int> empty_string_map; //unused bs_save_unicode_string(wf2, assign, true); - ResourceFormatSaverBinaryInstance::write_variant(wf2, value, dummy_read.resource_set, dummy_read.external_resources, empty_string_map); + ResourceFormatSaverBinaryInstance::write_variant(wf2, value, dummy_read.resource_index_map, dummy_read.external_resources, empty_string_map); prop_count++; } else if (next_tag.name != String()) { @@ -1175,7 +1177,7 @@ Error ResourceLoaderText::save_as_binary(FileAccess *p_f, const String &p_path) Map<StringName, int> empty_string_map; //unused bs_save_unicode_string(wf2, name, true); - ResourceFormatSaverBinaryInstance::write_variant(wf2, value, dummy_read.resource_set, dummy_read.external_resources, empty_string_map); + ResourceFormatSaverBinaryInstance::write_variant(wf2, value, dummy_read.resource_index_map, dummy_read.external_resources, empty_string_map); prop_count++; } @@ -1394,10 +1396,10 @@ String ResourceFormatSaverTextInstance::_write_resources(void *ud, const RES &p_ String ResourceFormatSaverTextInstance::_write_resource(const RES &res) { if (external_resources.has(res)) { - return "ExtResource( " + itos(external_resources[res]) + " )"; + return "ExtResource( \"" + external_resources[res] + "\" )"; } else { if (internal_resources.has(res)) { - return "SubResource( " + itos(internal_resources[res]) + " )"; + return "SubResource( \"" + internal_resources[res] + "\" )"; } else if (res->get_path().length() && res->get_path().find("::") == -1) { if (res->get_path() == local_path) { //circular reference attempt return "null"; @@ -1426,8 +1428,11 @@ void ResourceFormatSaverTextInstance::_find_resources(const Variant &p_variant, ERR_PRINT("Circular reference to resource being saved found: '" + local_path + "' will be null next time it's loaded."); return; } - int index = external_resources.size(); - external_resources[res] = index; + + // Use a numeric ID as a base, because they are sorted in natural order before saving. + // This increases the chances of thread loading to fetch them first. + String id = itos(external_resources.size() + 1) + "_" + Resource::generate_scene_unique_id(); + external_resources[res] = id; return; } @@ -1513,11 +1518,11 @@ Error ResourceFormatSaverTextInstance::save(const String &p_path, const RES &p_r takeover_paths = false; } - // save resources + // Save resources. _find_resources(p_resource, true); if (packed_scene.is_valid()) { - //add instances to external resources if saving a packed scene + // Add instances to external resources if saving a packed scene. for (int i = 0; i < packed_scene->get_state()->get_node_count(); i++) { if (packed_scene->get_state()->is_node_instance_placeholder(i)) { continue; @@ -1525,8 +1530,8 @@ Error ResourceFormatSaverTextInstance::save(const String &p_path, const RES &p_r Ref<PackedScene> instance = packed_scene->get_state()->get_node_instance(i); if (instance.is_valid() && !external_resources.has(instance)) { - int index = external_resources.size(); - external_resources[instance] = index; + int index = external_resources.size() + 1; + external_resources[instance] = itos(index) + "_" + Resource::generate_scene_unique_id(); // Keep the order for improved thread loading performance. } } } @@ -1537,12 +1542,6 @@ Error ResourceFormatSaverTextInstance::save(const String &p_path, const RES &p_r title += "type=\"" + p_resource->get_class() + "\" "; } int load_steps = saved_resources.size() + external_resources.size(); - /* - if (packed_scene.is_valid()) { - load_steps+=packed_scene->get_node_count(); - } - //no, better to not use load steps from nodes, no point to that - */ if (load_steps > 1) { title += "load_steps=" + itos(load_steps) + " "; @@ -1550,51 +1549,61 @@ Error ResourceFormatSaverTextInstance::save(const String &p_path, const RES &p_r title += "format=" + itos(FORMAT_VERSION) + ""; f->store_string(title); - f->store_line("]\n"); //one empty line + f->store_line("]\n"); // One empty line. } #ifdef TOOLS_ENABLED - //keep order from cached ids - Set<int> cached_ids_found; - for (Map<RES, int>::Element *E = external_resources.front(); E; E = E->next()) { - int cached_id = E->key()->get_id_for_path(local_path); - if (cached_id < 0 || cached_ids_found.has(cached_id)) { - E->get() = -1; //reset + // Keep order from cached ids. + Set<String> cached_ids_found; + for (Map<RES, String>::Element *E = external_resources.front(); E; E = E->next()) { + String cached_id = E->key()->get_id_for_path(local_path); + if (cached_id == "" || cached_ids_found.has(cached_id)) { + int sep_pos = E->get().find("_"); + if (sep_pos != -1) { + E->get() = E->get().substr(0, sep_pos + 1); // Keep the order found, for improved thread loading performance. + } else { + E->get() = ""; + } + } else { E->get() = cached_id; cached_ids_found.insert(cached_id); } } - //create IDs for non cached resources - for (Map<RES, int>::Element *E = external_resources.front(); E; E = E->next()) { - if (cached_ids_found.has(E->get())) { //already cached, go on + // Create IDs for non cached resources. + for (Map<RES, String>::Element *E = external_resources.front(); E; E = E->next()) { + if (cached_ids_found.has(E->get())) { // Already cached, go on. continue; } - int attempt = 1; //start from one, more readable format - while (cached_ids_found.has(attempt)) { - attempt++; + String attempt; + while (true) { + attempt = E->get() + Resource::generate_scene_unique_id(); + if (!cached_ids_found.has(attempt)) { + break; + } } cached_ids_found.insert(attempt); E->get() = attempt; - //update also in resource + // Update also in resource. Ref<Resource> res = E->key(); res->set_id_for_path(local_path, attempt); } #else - //make sure to start from one, as it makes format more readable - for (Map<RES, int>::Element *E = external_resources.front(); E; E = E->next()) { - E->get() = E->get() + 1; + // Make sure to start from one, as it makes format more readable. + int counter = 1; + for (Map<RES, String>::Element *E = external_resources.front(); E; E = E->next()) { + E->get() = itos(counter++); } #endif Vector<ResourceSort> sorted_er; - for (Map<RES, int>::Element *E = external_resources.front(); E; E = E->next()) { + for (Map<RES, String>::Element *E = external_resources.front(); E; E = E->next()) { ResourceSort rs; rs.resource = E->key(); - rs.index = E->get(); + rs.id = E->get(); sorted_er.push_back(rs); } @@ -1603,23 +1612,23 @@ Error ResourceFormatSaverTextInstance::save(const String &p_path, const RES &p_r for (int i = 0; i < sorted_er.size(); i++) { String p = sorted_er[i].resource->get_path(); - f->store_string("[ext_resource path=\"" + p + "\" type=\"" + sorted_er[i].resource->get_save_class() + "\" id=" + itos(sorted_er[i].index) + "]\n"); //bundled + f->store_string("[ext_resource path=\"" + p + "\" type=\"" + sorted_er[i].resource->get_save_class() + "\" id=\"" + sorted_er[i].id + "\"]\n"); // Bundled. } if (external_resources.size()) { - f->store_line(String()); //separate + f->store_line(String()); // Separate. } - Set<int> used_indices; + Set<String> used_unique_ids; for (List<RES>::Element *E = saved_resources.front(); E; E = E->next()) { RES res = E->get(); if (E->next() && (res->get_path() == "" || res->get_path().find("::") != -1)) { - if (res->get_subindex() != 0) { - if (used_indices.has(res->get_subindex())) { - res->set_subindex(0); //repeated + if (res->get_scene_unique_id() != "") { + if (used_unique_ids.has(res->get_scene_unique_id())) { + res->set_scene_unique_id(""); // Repeated. } else { - used_indices.insert(res->get_subindex()); + used_unique_ids.insert(res->get_scene_unique_id()); } } } @@ -1631,31 +1640,35 @@ Error ResourceFormatSaverTextInstance::save(const String &p_path, const RES &p_r bool main = (E->next() == nullptr); if (main && packed_scene.is_valid()) { - break; //save as a scene + break; // Save as a scene. } if (main) { f->store_line("[resource]"); } else { String line = "[sub_resource "; - if (res->get_subindex() == 0) { - int new_subindex = 1; - if (used_indices.size()) { - new_subindex = used_indices.back()->get() + 1; + if (res->get_scene_unique_id() == "") { + String new_id; + while (true) { + new_id = res->get_class() + "_" + Resource::generate_scene_unique_id(); + + if (!used_unique_ids.has(new_id)) { + break; + } } - res->set_subindex(new_subindex); - used_indices.insert(new_subindex); + res->set_scene_unique_id(new_id); + used_unique_ids.insert(new_id); } - int idx = res->get_subindex(); - line += "type=\"" + res->get_class() + "\" id=" + itos(idx); - f->store_line(line + "]"); + String id = res->get_scene_unique_id(); + line += "type=\"" + res->get_class() + "\" id=\"" + id; + f->store_line(line + "\"]"); if (takeover_paths) { - res->set_path(p_path + "::" + itos(idx), true); + res->set_path(p_path + "::" + id, true); } - internal_resources[res] = idx; + internal_resources[res] = id; #ifdef TOOLS_ENABLED res->set_edited(false); #endif @@ -1663,7 +1676,6 @@ Error ResourceFormatSaverTextInstance::save(const String &p_path, const RES &p_r List<PropertyInfo> property_list; res->get_property_list(&property_list); - //property_list.sort(); for (List<PropertyInfo>::Element *PE = property_list.front(); PE; PE = PE->next()) { if (skip_editor && PE->get().name.begins_with("__editor")) { continue; @@ -1704,7 +1716,7 @@ Error ResourceFormatSaverTextInstance::save(const String &p_path, const RES &p_r } if (packed_scene.is_valid()) { - //if this is a scene, save nodes and connections! + // If this is a scene, save nodes and connections! Ref<SceneState> state = packed_scene->get_state(); for (int i = 0; i < state->get_node_count(); i++) { StringName type = state->get_node_type(i); @@ -1812,7 +1824,6 @@ Error ResourceFormatSaverTextInstance::save(const String &p_path, const RES &p_r } f->close(); - //memdelete(f); return OK; } diff --git a/scene/resources/resource_format_text.h b/scene/resources/resource_format_text.h index f5d9cca859..5173619a17 100644 --- a/scene/resources/resource_format_text.h +++ b/scene/resources/resource_format_text.h @@ -58,10 +58,8 @@ class ResourceLoaderText { bool ignore_resource_parsing = false; - //Map<String,String> remaps; - - Map<int, ExtResource> ext_resources; - Map<int, RES> int_resources; + Map<String, ExtResource> ext_resources; + Map<String, RES> int_resources; int resources_total = 0; int resource_current = 0; @@ -77,7 +75,6 @@ class ResourceLoaderText { mutable int lines = 0; Map<String, String> remaps; - //void _printerr(); static Error _parse_sub_resources(void *p_self, VariantParser::Stream *p_stream, Ref<Resource> &r_res, int &line, String &r_err_str) { return reinterpret_cast<ResourceLoaderText *>(p_self)->_parse_sub_resource(p_stream, r_res, line, r_err_str); } static Error _parse_ext_resources(void *p_self, VariantParser::Stream *p_stream, Ref<Resource> &r_res, int &line, String &r_err_str) { return reinterpret_cast<ResourceLoaderText *>(p_self)->_parse_ext_resource(p_stream, r_res, line, r_err_str); } @@ -92,9 +89,9 @@ class ResourceLoaderText { struct DummyReadData { Map<RES, int> external_resources; - Map<int, RES> rev_external_resources; - Set<RES> resource_set; - Map<int, RES> resource_map; + Map<String, RES> rev_external_resources; + Map<RES, int> resource_index_map; + Map<String, RES> resource_map; }; static Error _parse_sub_resource_dummys(void *p_self, VariantParser::Stream *p_stream, Ref<Resource> &r_res, int &line, String &r_err_str) { return _parse_sub_resource_dummy((DummyReadData *)(p_self), p_stream, r_res, line, r_err_str); } @@ -168,14 +165,14 @@ class ResourceFormatSaverTextInstance { Set<RES> resource_set; List<RES> saved_resources; - Map<RES, int> external_resources; - Map<RES, int> internal_resources; + Map<RES, String> external_resources; + Map<RES, String> internal_resources; struct ResourceSort { RES resource; - int index = 0; + String id; bool operator<(const ResourceSort &p_right) const { - return index < p_right.index; + return id.naturalnocasecmp_to(p_right.id) < 0; } }; diff --git a/tests/test_object.h b/tests/test_object.h index 36f9ef2a51..a18adf31b6 100644 --- a/tests/test_object.h +++ b/tests/test_object.h @@ -139,7 +139,7 @@ TEST_CASE("[Object] Metadata") { Color(object.get_meta(meta_path)).is_equal_approx(Color(0, 1, 0)), "The returned object metadata after setting should match the expected value."); - List<String> meta_list; + List<StringName> meta_list; object.get_meta_list(&meta_list); CHECK_MESSAGE( meta_list.size() == 1, @@ -154,7 +154,7 @@ TEST_CASE("[Object] Metadata") { "The returned object metadata after removing should match the expected value."); ERR_PRINT_ON; - List<String> meta_list2; + List<StringName> meta_list2; object.get_meta_list(&meta_list2); CHECK_MESSAGE( meta_list2.size() == 0, |