/**************************************************************************/ /* packed_scene.cpp */ /**************************************************************************/ /* This file is part of: */ /* GODOT ENGINE */ /* https://godotengine.org */ /**************************************************************************/ /* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ /* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ /* "Software"), to deal in the Software without restriction, including */ /* without limitation the rights to use, copy, modify, merge, publish, */ /* distribute, sublicense, and/or sell copies of the Software, and to */ /* permit persons to whom the Software is furnished to do so, subject to */ /* the following conditions: */ /* */ /* The above copyright notice and this permission notice shall be */ /* included in all copies or substantial portions of the Software. */ /* */ /* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ /* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ /* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ /* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ /* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ /* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /**************************************************************************/ #include "packed_scene.h" #include "core/config/engine.h" #include "core/io/missing_resource.h" #include "core/io/resource_loader.h" #include "core/templates/local_vector.h" #include "scene/2d/node_2d.h" #include "scene/gui/control.h" #include "scene/main/instance_placeholder.h" #include "scene/main/missing_node.h" #include "scene/property_utils.h" #ifndef _3D_DISABLED #include "scene/3d/node_3d.h" #endif // _3D_DISABLED #define PACKED_SCENE_VERSION 3 #ifdef TOOLS_ENABLED SceneState::InstantiationWarningNotify SceneState::instantiation_warn_notify = nullptr; #endif bool SceneState::can_instantiate() const { return nodes.size() > 0; } static Array _sanitize_node_pinned_properties(Node *p_node) { Array pinned = p_node->get_meta("_edit_pinned_properties_", Array()); if (pinned.is_empty()) { return Array(); } HashSet storable_properties; p_node->get_storable_properties(storable_properties); int i = 0; do { if (storable_properties.has(pinned[i])) { i++; } else { pinned.remove_at(i); } } while (i < pinned.size()); if (pinned.is_empty()) { p_node->remove_meta("_edit_pinned_properties_"); } return pinned; } Ref SceneState::get_remap_resource(const Ref &p_resource, HashMap, Ref> &remap_cache, const Ref &p_fallback, Node *p_for_scene) { ERR_FAIL_COND_V(p_resource.is_null(), Ref()); Ref remap_resource; // Find the shared copy of the source resource. HashMap, Ref>::Iterator R = remap_cache.find(p_resource); if (R) { remap_resource = R->value; } else if (p_fallback.is_valid() && p_fallback->is_local_to_scene() && p_fallback->get_class() == p_resource->get_class()) { // Simply copy the data from the source resource to update the fallback resource that was previously set. p_fallback->reset_state(); // May want to reset state. List pi; p_resource->get_property_list(&pi); for (const PropertyInfo &E : pi) { if (!(E.usage & PROPERTY_USAGE_STORAGE)) { continue; } if (E.name == "resource_path") { continue; // Do not change path. } Variant value = p_resource->get(E.name); // The local-to-scene subresource instance is preserved, thus maintaining the previous sharing relationship. // This is mainly used when the sub-scene root is reset in the main scene. Ref sub_res_of_from = value; if (sub_res_of_from.is_valid() && sub_res_of_from->is_local_to_scene()) { value = get_remap_resource(sub_res_of_from, remap_cache, p_fallback->get(E.name), p_fallback->get_local_scene()); } p_fallback->set(E.name, value); } p_fallback->set_scene_unique_id(p_resource->get_scene_unique_id()); // Get the id from the main scene, in case the id changes again when saving the scene. remap_cache[p_resource] = p_fallback; remap_resource = p_fallback; } else { // A copy of the source resource is required to overwrite the previous one. Ref local_dupe = p_resource->duplicate_for_local_scene(p_for_scene, remap_cache); remap_cache[p_resource] = local_dupe; remap_resource = local_dupe; } return remap_resource; } Node *SceneState::instantiate(GenEditState p_edit_state) const { // Nodes where instantiation failed (because something is missing.) List stray_instances; #define NODE_FROM_ID(p_name, p_id) \ Node *p_name; \ if (p_id & FLAG_ID_IS_PATH) { \ NodePath np = node_paths[p_id & FLAG_MASK]; \ p_name = ret_nodes[0]->get_node_or_null(np); \ } else { \ ERR_FAIL_INDEX_V(p_id & FLAG_MASK, nc, nullptr); \ p_name = ret_nodes[p_id & FLAG_MASK]; \ } int nc = nodes.size(); ERR_FAIL_COND_V_MSG(nc == 0, nullptr, vformat("Failed to instantiate scene state of \"%s\", node count is 0. Make sure the PackedScene resource is valid.", path)); const StringName *snames = nullptr; int sname_count = names.size(); if (sname_count) { snames = &names[0]; } const Variant *props = nullptr; int prop_count = variants.size(); if (prop_count) { props = &variants[0]; } //Vector properties; const NodeData *nd = &nodes[0]; Node **ret_nodes = (Node **)alloca(sizeof(Node *) * nc); bool gen_node_path_cache = p_edit_state != GEN_EDIT_STATE_DISABLED && node_path_cache.is_empty(); HashMap, Ref> resources_local_to_scene; LocalVector deferred_node_paths; for (int i = 0; i < nc; i++) { const NodeData &n = nd[i]; Node *parent = nullptr; String old_parent_path; if (i > 0) { ERR_FAIL_COND_V_MSG(n.parent == -1, nullptr, vformat("Invalid scene: node %s does not specify its parent node.", snames[n.name])); NODE_FROM_ID(nparent, n.parent); #ifdef DEBUG_ENABLED if (!nparent && (n.parent & FLAG_ID_IS_PATH)) { WARN_PRINT(String("Parent path '" + String(node_paths[n.parent & FLAG_MASK]) + "' for node '" + String(snames[n.name]) + "' has vanished when instantiating: '" + get_path() + "'.").ascii().get_data()); old_parent_path = String(node_paths[n.parent & FLAG_MASK]).trim_prefix("./").replace_char('/', '@'); nparent = ret_nodes[0]; } #endif parent = nparent; } else { // i == 0 is root node. ERR_FAIL_COND_V_MSG(n.parent != -1, nullptr, vformat("Invalid scene: root node %s cannot specify a parent node.", snames[n.name])); ERR_FAIL_COND_V_MSG(n.type == TYPE_INSTANTIATED && base_scene_idx < 0, nullptr, vformat("Invalid scene: root node %s in an instance, but there's no base scene.", snames[n.name])); } Node *node = nullptr; MissingNode *missing_node = nullptr; bool is_inherited_scene = false; if (i == 0 && base_scene_idx >= 0) { // Scene inheritance on root node. Ref sdata = props[base_scene_idx]; ERR_FAIL_COND_V(sdata.is_null(), nullptr); node = sdata->instantiate(p_edit_state == GEN_EDIT_STATE_DISABLED ? PackedScene::GEN_EDIT_STATE_DISABLED : PackedScene::GEN_EDIT_STATE_INSTANCE); //only main gets main edit state ERR_FAIL_NULL_V(node, nullptr); if (p_edit_state != GEN_EDIT_STATE_DISABLED) { node->set_scene_inherited_state(sdata->get_state()); } is_inherited_scene = true; } else if (n.instance >= 0) { // Instance a scene into this node. if (n.instance & FLAG_INSTANCE_IS_PLACEHOLDER) { const String scene_path = props[n.instance & FLAG_MASK]; if (disable_placeholders) { Ref sdata = ResourceLoader::load(scene_path, "PackedScene"); if (sdata.is_valid()) { node = sdata->instantiate(p_edit_state == GEN_EDIT_STATE_DISABLED ? PackedScene::GEN_EDIT_STATE_DISABLED : PackedScene::GEN_EDIT_STATE_INSTANCE); ERR_FAIL_NULL_V(node, nullptr); } else if (ResourceLoader::is_creating_missing_resources_if_class_unavailable_enabled()) { missing_node = memnew(MissingNode); missing_node->set_original_scene(scene_path); missing_node->set_recording_properties(true); node = missing_node; } else { ERR_FAIL_V_MSG(nullptr, "Placeholder scene is missing."); } } else { InstancePlaceholder *ip = memnew(InstancePlaceholder); ip->set_instance_path(scene_path); node = ip; } node->set_scene_instance_load_placeholder(true); } else { Ref res = props[n.instance & FLAG_MASK]; Ref sdata = res; if (sdata.is_valid()) { node = sdata->instantiate(p_edit_state == GEN_EDIT_STATE_DISABLED ? PackedScene::GEN_EDIT_STATE_DISABLED : PackedScene::GEN_EDIT_STATE_INSTANCE); ERR_FAIL_NULL_V_MSG(node, nullptr, vformat("Failed to load scene dependency: \"%s\". Make sure the required scene is valid.", sdata->get_path())); } else if (ResourceLoader::is_creating_missing_resources_if_class_unavailable_enabled()) { missing_node = memnew(MissingNode); #ifdef TOOLS_ENABLED if (res.is_valid()) { missing_node->set_original_scene(res->get_meta("__load_path__", "")); } #endif missing_node->set_recording_properties(true); node = missing_node; } else { ERR_FAIL_V_MSG(nullptr, "Scene instance is missing."); } } } else if (n.type == TYPE_INSTANTIATED) { // Get the node from somewhere, it likely already exists from another instance. if (parent) { node = parent->_get_child_by_name(snames[n.name]); #ifdef DEBUG_ENABLED if (!node) { WARN_PRINT(String("Node '" + String(ret_nodes[0]->get_path_to(parent)) + "/" + String(snames[n.name]) + "' was modified from inside an instance, but it has vanished.").ascii().get_data()); } #endif } } else { // Node belongs to this scene and must be created. Object *obj = ClassDB::instantiate(snames[n.type]); node = Object::cast_to(obj); if (!node) { if (obj) { memdelete(obj); obj = nullptr; } if (ResourceLoader::is_creating_missing_resources_if_class_unavailable_enabled()) { missing_node = memnew(MissingNode); missing_node->set_original_class(snames[n.type]); missing_node->set_recording_properties(true); node = missing_node; obj = missing_node; } else { WARN_PRINT(vformat("Node %s of type %s cannot be created. A placeholder will be created instead.", snames[n.name], snames[n.type]).ascii().get_data()); if (n.parent >= 0 && n.parent < nc && ret_nodes[n.parent]) { if (Object::cast_to(ret_nodes[n.parent])) { obj = memnew(Control); } else if (Object::cast_to(ret_nodes[n.parent])) { obj = memnew(Node2D); #ifndef _3D_DISABLED } else if (Object::cast_to(ret_nodes[n.parent])) { obj = memnew(Node3D); #endif // _3D_DISABLED } } if (!obj) { obj = memnew(Node); } node = Object::cast_to(obj); } } } if (node) { // may not have found the node (part of instantiated scene and removed) // if found all is good, otherwise ignore //properties int nprop_count = n.properties.size(); if (nprop_count) { const NodeData::Property *nprops = &n.properties[0]; Dictionary missing_resource_properties; HashMap, Ref> resources_local_to_sub_scene; // Record the mappings in the sub-scene. for (int j = 0; j < nprop_count; j++) { bool valid; ERR_FAIL_INDEX_V(nprops[j].value, prop_count, nullptr); if (nprops[j].name & FLAG_PATH_PROPERTY_IS_NODE) { if (!Engine::get_singleton()->is_editor_hint() && node->get_scene_instance_load_placeholder()) { // We cannot know if the referenced nodes exist yet, so instead of deferring, we write the NodePaths directly. uint32_t name_idx = nprops[j].name & (FLAG_PATH_PROPERTY_IS_NODE - 1); ERR_FAIL_UNSIGNED_INDEX_V(name_idx, (uint32_t)sname_count, nullptr); node->set(snames[name_idx], props[nprops[j].value], &valid); continue; } uint32_t name_idx = nprops[j].name & (FLAG_PATH_PROPERTY_IS_NODE - 1); ERR_FAIL_UNSIGNED_INDEX_V(name_idx, (uint32_t)sname_count, nullptr); DeferredNodePathProperties dnp; dnp.value = props[nprops[j].value]; dnp.base = node->get_instance_id(); dnp.property = snames[name_idx]; deferred_node_paths.push_back(dnp); continue; } ERR_FAIL_INDEX_V(nprops[j].name, sname_count, nullptr); if (snames[nprops[j].name] == CoreStringName(script)) { //work around to avoid old script variables from disappearing, should be the proper fix to: //https://github.com/godotengine/godot/issues/2958 //store old state List> old_state; if (node->get_script_instance()) { node->get_script_instance()->get_property_state(old_state); } #ifdef TOOLS_ENABLED const Ref