Merge pull request #62185 from reduz/export-node-pointer-path

Add ability to export Node pointers as NodePaths
This commit is contained in:
Rémi Verschelde 2022-06-27 11:14:36 +02:00 committed by GitHub
commit fbc3777467
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 184 additions and 36 deletions

View file

@ -613,6 +613,7 @@ void register_global_constants() {
BIND_CORE_ENUM_CONSTANT(PROPERTY_HINT_ARRAY_TYPE);
BIND_CORE_ENUM_CONSTANT(PROPERTY_HINT_LOCALE_ID);
BIND_CORE_ENUM_CONSTANT(PROPERTY_HINT_LOCALIZABLE_STRING);
BIND_CORE_ENUM_CONSTANT(PROPERTY_HINT_NODE_TYPE);
BIND_CORE_ENUM_CONSTANT(PROPERTY_HINT_MAX);
BIND_CORE_ENUM_CONSTANT(PROPERTY_USAGE_NONE);

View file

@ -91,6 +91,7 @@ enum PropertyHint {
PROPERTY_HINT_INT_IS_POINTER,
PROPERTY_HINT_LOCALE_ID,
PROPERTY_HINT_LOCALIZABLE_STRING,
PROPERTY_HINT_NODE_TYPE, ///< a node object type
PROPERTY_HINT_MAX,
// When updating PropertyHint, also sync the hardcoded list in VisualScriptEditorVariableEdit
};

View file

@ -2583,7 +2583,9 @@
<constant name="PROPERTY_HINT_LOCALIZABLE_STRING" value="44" enum="PropertyHint">
Hints that a dictionary property is string translation map. Dictionary keys are locale codes and, values are translated strings.
</constant>
<constant name="PROPERTY_HINT_MAX" value="45" enum="PropertyHint">
<constant name="PROPERTY_HINT_NODE_TYPE" value="45" enum="PropertyHint">
</constant>
<constant name="PROPERTY_HINT_MAX" value="46" enum="PropertyHint">
</constant>
<constant name="PROPERTY_USAGE_NONE" value="0" enum="PropertyUsageFlags">
</constant>

View file

@ -38,7 +38,7 @@
Gets the edited object.
</description>
</method>
<method name="get_edited_property">
<method name="get_edited_property" qualifiers="const">
<return type="StringName" />
<description>
Gets the edited property. If your editor is for a single property (added via [method EditorInspectorPlugin._parse_property]), then this will return the property.

View file

@ -406,7 +406,7 @@ Object *EditorProperty::get_edited_object() {
return object;
}
StringName EditorProperty::get_edited_property() {
StringName EditorProperty::get_edited_property() const {
return property;
}
@ -437,16 +437,20 @@ Variant EditorPropertyRevert::get_property_revert_value(Object *p_object, const
return PropertyUtils::get_property_default_value(p_object, p_property, r_is_valid);
}
bool EditorPropertyRevert::can_property_revert(Object *p_object, const StringName &p_property) {
bool EditorPropertyRevert::can_property_revert(Object *p_object, const StringName &p_property, const Variant *p_custom_current_value) {
bool is_valid_revert = false;
Variant revert_value = EditorPropertyRevert::get_property_revert_value(p_object, p_property, &is_valid_revert);
if (!is_valid_revert) {
return false;
}
Variant current_value = p_object->get(p_property);
Variant current_value = p_custom_current_value ? *p_custom_current_value : p_object->get(p_property);
return PropertyUtils::is_property_value_different(current_value, revert_value);
}
StringName EditorProperty::_get_revert_property() const {
return property;
}
void EditorProperty::update_revert_and_pin_status() {
if (property == StringName()) {
return; //no property, so nothing to do
@ -458,7 +462,8 @@ void EditorProperty::update_revert_and_pin_status() {
CRASH_COND(!node);
new_pinned = node->is_property_pinned(property);
}
bool new_can_revert = EditorPropertyRevert::can_property_revert(object, property) && !is_read_only();
Variant current = object->get(_get_revert_property());
bool new_can_revert = EditorPropertyRevert::can_property_revert(object, property, &current) && !is_read_only();
if (new_can_revert != can_revert || new_pinned != pinned) {
can_revert = new_can_revert;
@ -717,11 +722,15 @@ void EditorProperty::set_bottom_editor(Control *p_control) {
bottom_editor = p_control;
}
Variant EditorProperty::_get_cache_value(const StringName &p_prop, bool &r_valid) const {
return object->get(p_prop, &r_valid);
}
bool EditorProperty::is_cache_valid() const {
if (object) {
for (const KeyValue<StringName, Variant> &E : cache) {
bool valid;
Variant value = object->get(E.key, &valid);
Variant value = _get_cache_value(E.key, valid);
if (!valid || value != E.value) {
return false;
}
@ -733,7 +742,7 @@ void EditorProperty::update_cache() {
cache.clear();
if (object && property != StringName()) {
bool valid;
Variant value = object->get(property, &valid);
Variant value = _get_cache_value(property, valid);
if (valid) {
cache[property] = value;
}

View file

@ -50,7 +50,7 @@ public:
static bool is_property_value_different(const Variant &p_a, const Variant &p_b);
static Variant get_property_revert_value(Object *p_object, const StringName &p_property, bool *r_is_valid);
static bool can_property_revert(Object *p_object, const StringName &p_property);
static bool can_property_revert(Object *p_object, const StringName &p_property, const Variant *p_custom_current_value = nullptr);
};
class EditorProperty : public Container {
@ -131,6 +131,9 @@ protected:
virtual void shortcut_input(const Ref<InputEvent> &p_event) override;
const Color *_get_property_colors();
virtual Variant _get_cache_value(const StringName &p_prop, bool &r_valid) const;
virtual StringName _get_revert_property() const;
public:
void emit_changed(const StringName &p_property, const Variant &p_value, const StringName &p_field = StringName(), bool p_changing = false);
@ -143,7 +146,7 @@ public:
bool is_read_only() const;
Object *get_edited_object();
StringName get_edited_property();
StringName get_edited_property() const;
virtual void update_property();
void update_revert_and_pin_status();

View file

@ -43,6 +43,7 @@
#include "scene/main/window.h"
#include "scene/resources/font.h"
#include "scene/resources/mesh.h"
#include "scene/resources/packed_scene.h"
///////////////////// Nil /////////////////////////
@ -3017,6 +3018,23 @@ void EditorPropertyNodePath::_set_read_only(bool p_read_only) {
clear->set_disabled(p_read_only);
};
String EditorPropertyNodePath::_get_meta_pointer_property() const {
ERR_FAIL_COND_V(!pointer_mode, String());
return SceneState::get_meta_pointer_property(get_edited_property());
}
Variant EditorPropertyNodePath::_get_cache_value(const StringName &p_prop, bool &r_valid) const {
if (p_prop == get_edited_property()) {
r_valid = true;
return const_cast<EditorPropertyNodePath *>(this)->get_edited_object()->get(_get_meta_pointer_property(), &r_valid);
}
return Variant();
}
StringName EditorPropertyNodePath::_get_revert_property() const {
return _get_meta_pointer_property();
}
void EditorPropertyNodePath::_node_selected(const NodePath &p_path) {
NodePath path = p_path;
Node *base_node = nullptr;
@ -3048,7 +3066,11 @@ void EditorPropertyNodePath::_node_selected(const NodePath &p_path) {
if (base_node) { // for AnimationTrackKeyEdit
path = base_node->get_path().rel_path_to(p_path);
}
emit_changed(get_edited_property(), path);
if (pointer_mode && base_node) {
emit_changed(_get_meta_pointer_property(), path);
} else {
emit_changed(get_edited_property(), path);
}
update_property();
}
@ -3064,7 +3086,11 @@ void EditorPropertyNodePath::_node_assign() {
}
void EditorPropertyNodePath::_node_clear() {
emit_changed(get_edited_property(), NodePath());
if (pointer_mode) {
emit_changed(_get_meta_pointer_property(), NodePath());
} else {
emit_changed(get_edited_property(), NodePath());
}
update_property();
}
@ -3092,7 +3118,12 @@ bool EditorPropertyNodePath::is_drop_valid(const Dictionary &p_drag_data) const
}
void EditorPropertyNodePath::update_property() {
NodePath p = get_edited_object()->get(get_edited_property());
NodePath p;
if (pointer_mode) {
p = get_edited_object()->get(_get_meta_pointer_property());
} else {
p = get_edited_object()->get(get_edited_property());
}
assign->set_tooltip(p);
if (p == NodePath()) {
@ -3131,7 +3162,8 @@ void EditorPropertyNodePath::update_property() {
assign->set_icon(EditorNode::get_singleton()->get_object_icon(target_node, "Node"));
}
void EditorPropertyNodePath::setup(const NodePath &p_base_hint, Vector<StringName> p_valid_types, bool p_use_path_from_scene_root) {
void EditorPropertyNodePath::setup(const NodePath &p_base_hint, Vector<StringName> p_valid_types, bool p_use_path_from_scene_root, bool p_pointer_mode) {
pointer_mode = p_pointer_mode;
base_hint = p_base_hint;
valid_types = p_valid_types;
use_path_from_scene_root = p_use_path_from_scene_root;
@ -3927,23 +3959,31 @@ EditorProperty *EditorInspectorDefaultPlugin::get_editor_for_property(Object *p_
return editor;
} break;
case Variant::OBJECT: {
EditorPropertyResource *editor = memnew(EditorPropertyResource);
editor->setup(p_object, p_path, p_hint == PROPERTY_HINT_RESOURCE_TYPE ? p_hint_text : "Resource");
if (p_hint == PROPERTY_HINT_NODE_TYPE) {
EditorPropertyNodePath *editor = memnew(EditorPropertyNodePath);
Vector<String> types = p_hint_text.split(",", false);
Vector<StringName> sn = Variant(types); //convert via variant
editor->setup(NodePath(), sn, false, true);
return editor;
} else {
EditorPropertyResource *editor = memnew(EditorPropertyResource);
editor->setup(p_object, p_path, p_hint == PROPERTY_HINT_RESOURCE_TYPE ? p_hint_text : "Resource");
if (p_hint == PROPERTY_HINT_RESOURCE_TYPE) {
String open_in_new = EDITOR_GET("interface/inspector/resources_to_open_in_new_inspector");
for (int i = 0; i < open_in_new.get_slice_count(","); i++) {
String type = open_in_new.get_slicec(',', i).strip_edges();
for (int j = 0; j < p_hint_text.get_slice_count(","); j++) {
String inherits = p_hint_text.get_slicec(',', j);
if (ClassDB::is_parent_class(inherits, type)) {
editor->set_use_sub_inspector(false);
if (p_hint == PROPERTY_HINT_RESOURCE_TYPE) {
String open_in_new = EDITOR_GET("interface/inspector/resources_to_open_in_new_inspector");
for (int i = 0; i < open_in_new.get_slice_count(","); i++) {
String type = open_in_new.get_slicec(',', i).strip_edges();
for (int j = 0; j < p_hint_text.get_slice_count(","); j++) {
String inherits = p_hint_text.get_slicec(',', j);
if (ClassDB::is_parent_class(inherits, type)) {
editor->set_use_sub_inspector(false);
}
}
}
}
}
return editor;
return editor;
}
} break;
case Variant::DICTIONARY: {

View file

@ -704,6 +704,7 @@ class EditorPropertyNodePath : public EditorProperty {
SceneTreeDialog *scene_tree = nullptr;
NodePath base_hint;
bool use_path_from_scene_root = false;
bool pointer_mode = false;
Vector<StringName> valid_types;
void _node_selected(const NodePath &p_path);
@ -714,6 +715,10 @@ class EditorPropertyNodePath : public EditorProperty {
void drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from);
bool is_drop_valid(const Dictionary &p_drag_data) const;
String _get_meta_pointer_property() const;
virtual Variant _get_cache_value(const StringName &p_prop, bool &r_valid) const override;
virtual StringName _get_revert_property() const override;
protected:
virtual void _set_read_only(bool p_read_only) override;
static void _bind_methods();
@ -721,7 +726,7 @@ protected:
public:
virtual void update_property() override;
void setup(const NodePath &p_base_hint, Vector<StringName> p_valid_types, bool p_use_path_from_scene_root = true);
void setup(const NodePath &p_base_hint, Vector<StringName> p_valid_types, bool p_use_path_from_scene_root = true, bool p_pointer_mode = false);
EditorPropertyNodePath();
};

View file

@ -35,6 +35,7 @@
#include "core/core_string_names.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/3d/node_3d.h"
#include "scene/gui/control.h"
@ -43,7 +44,7 @@
#include "scene/property_utils.h"
#define PACKED_SCENE_VERSION 2
#define META_POINTER_PROPERTY_BASE "metadata/_editor_prop_ptr_"
bool SceneState::can_instantiate() const {
return nodes.size() > 0;
}
@ -108,6 +109,8 @@ Node *SceneState::instantiate(GenEditState p_edit_state) const {
HashMap<Ref<Resource>, Ref<Resource>> resources_local_to_scene;
LocalVector<DeferredNodePathProperties> deferred_node_paths;
for (int i = 0; i < nc; i++) {
const NodeData &n = nd[i];
@ -230,9 +233,28 @@ Node *SceneState::instantiate(GenEditState p_edit_state) const {
for (int j = 0; j < nprop_count; j++) {
bool valid;
ERR_FAIL_INDEX_V(nprops[j].name, sname_count, nullptr);
ERR_FAIL_INDEX_V(nprops[j].value, prop_count, nullptr);
if (nprops[j].name & FLAG_PATH_PROPERTY_IS_NODE) {
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);
if (Engine::get_singleton()->is_editor_hint()) {
// If editor, just set the metadata and be it
node->set(META_POINTER_PROPERTY_BASE + String(snames[name_idx]), props[nprops[j].value]);
} else {
// Do an actual deferred sed of the property path.
DeferredNodePathProperties dnp;
dnp.path = props[nprops[j].value];
dnp.base = node;
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] == CoreStringNames::get_singleton()->_script) {
//work around to avoid old script variables from disappearing, should be the proper fix to:
//https://github.com/godotengine/godot/issues/2958
@ -369,6 +391,12 @@ Node *SceneState::instantiate(GenEditState p_edit_state) const {
}
}
for (uint32_t i = 0; i < deferred_node_paths.size(); i++) {
const DeferredNodePathProperties &dnp = deferred_node_paths[i];
Node *other = dnp.base->get_node_or_null(dnp.path);
dnp.base->set(dnp.property, other);
}
for (KeyValue<Ref<Resource>, Ref<Resource>> &E : resources_local_to_scene) {
E.value->setup_local_to_scene();
}
@ -532,6 +560,9 @@ Error SceneState::_parse_node(Node *p_owner, Node *p_node, int p_parent_idx, Has
if (E.name == META_PROPERTY_MISSING_RESOURCES) {
continue; // Ignore this property when packing.
}
if (E.name.begins_with(META_POINTER_PROPERTY_BASE)) {
continue; // do not save.
}
// If instance or inheriting, not saving if property requested so.
if (!states_stack.is_empty()) {
@ -542,8 +573,15 @@ Error SceneState::_parse_node(Node *p_owner, Node *p_node, int p_parent_idx, Has
StringName name = E.name;
Variant value = p_node->get(name);
bool use_deferred_node_path_bit = false;
if (E.type == Variant::OBJECT && missing_resource_properties.has(E.name)) {
if (E.type == Variant::OBJECT && E.hint == PROPERTY_HINT_NODE_TYPE) {
value = p_node->get(META_POINTER_PROPERTY_BASE + E.name);
if (value.get_type() != Variant::NODE_PATH) {
continue; //was never set, ignore.
}
use_deferred_node_path_bit = true;
} else if (E.type == Variant::OBJECT && missing_resource_properties.has(E.name)) {
// Was this missing resource overridden? If so do not save the old value.
Ref<Resource> ures = value;
if (ures.is_null()) {
@ -562,6 +600,9 @@ Error SceneState::_parse_node(Node *p_owner, Node *p_node, int p_parent_idx, Has
NodeData::Property prop;
prop.name = _nm_get_string(name, name_map);
prop.value = _vm_get_variant(value, variant_map);
if (use_deferred_node_path_bit) {
prop.name |= FLAG_PATH_PROPERTY_IS_NODE;
}
nd.properties.push_back(prop);
}
@ -1018,7 +1059,7 @@ Variant SceneState::get_property_value(int p_node, const StringName &p_property,
const NodeData::Property *p = nodes[p_node].properties.ptr();
for (int i = 0; i < pc; i++) {
if (p_property == namep[p[i].name]) {
if (p_property == namep[p[i].name & FLAG_PROP_NAME_MASK]) {
found = true;
return variants[p[i].value];
}
@ -1409,7 +1450,19 @@ int SceneState::get_node_property_count(int p_idx) const {
StringName SceneState::get_node_property_name(int p_idx, int p_prop) const {
ERR_FAIL_INDEX_V(p_idx, nodes.size(), StringName());
ERR_FAIL_INDEX_V(p_prop, nodes[p_idx].properties.size(), StringName());
return names[nodes[p_idx].properties[p_prop].name];
return names[nodes[p_idx].properties[p_prop].name & FLAG_PROP_NAME_MASK];
}
Vector<String> SceneState::get_node_deferred_nodepath_properties(int p_idx) const {
Vector<String> ret;
ERR_FAIL_INDEX_V(p_idx, nodes.size(), ret);
for (int i = 0; i < nodes[p_idx].properties.size(); i++) {
uint32_t idx = nodes[p_idx].properties[i].name;
if (idx & FLAG_PATH_PROPERTY_IS_NODE) {
ret.push_back(names[idx & FLAG_PROP_NAME_MASK]);
}
}
return ret;
}
Variant SceneState::get_node_property_value(int p_idx, int p_prop) const {
@ -1555,13 +1608,16 @@ int SceneState::add_node(int p_parent, int p_owner, int p_type, int p_name, int
return nodes.size() - 1;
}
void SceneState::add_node_property(int p_node, int p_name, int p_value) {
void SceneState::add_node_property(int p_node, int p_name, int p_value, bool p_deferred_node_path) {
ERR_FAIL_INDEX(p_node, nodes.size());
ERR_FAIL_INDEX(p_name, names.size());
ERR_FAIL_INDEX(p_value, variants.size());
NodeData::Property prop;
prop.name = p_name;
if (p_deferred_node_path) {
prop.name |= FLAG_PATH_PROPERTY_IS_NODE;
}
prop.value = p_value;
nodes.write[p_node].properties.push_back(prop);
}
@ -1599,6 +1655,10 @@ void SceneState::add_editable_instance(const NodePath &p_path) {
editable_instances.push_back(p_path);
}
String SceneState::get_meta_pointer_property(const String &p_property) {
return META_POINTER_PROPERTY_BASE + p_property;
}
Vector<String> SceneState::_get_node_groups(int p_idx) const {
Vector<StringName> groups = get_node_groups(p_idx);
Vector<String> ret;

View file

@ -69,6 +69,12 @@ class SceneState : public RefCounted {
Vector<int> groups;
};
struct DeferredNodePathProperties {
Node *base = nullptr;
StringName property;
NodePath path;
};
Vector<NodeData> nodes;
struct ConnectionData {
@ -104,6 +110,8 @@ public:
FLAG_ID_IS_PATH = (1 << 30),
TYPE_INSTANCED = 0x7FFFFFFF,
FLAG_INSTANCE_IS_PLACEHOLDER = (1 << 30),
FLAG_PATH_PROPERTY_IS_NODE = (1 << 30),
FLAG_PROP_NAME_MASK = FLAG_PATH_PROPERTY_IS_NODE - 1,
FLAG_MASK = (1 << 24) - 1,
};
@ -157,6 +165,7 @@ public:
int get_node_property_count(int p_idx) const;
StringName get_node_property_name(int p_idx, int p_prop) const;
Variant get_node_property_value(int p_idx, int p_prop) const;
Vector<String> get_node_deferred_nodepath_properties(int p_idx) const;
int get_connection_count() const;
NodePath get_connection_source(int p_idx) const;
@ -177,7 +186,7 @@ public:
int add_value(const Variant &p_value);
int add_node_path(const NodePath &p_path);
int add_node(int p_parent, int p_owner, int p_type, int p_name, int p_instance, int p_index);
void add_node_property(int p_node, int p_name, int p_value);
void add_node_property(int p_node, int p_name, int p_value, bool p_deferred_node_path = false);
void add_node_group(int p_node, int p_group);
void set_base_scene(int p_idx);
void add_connection(int p_from, int p_to, int p_signal, int p_method, int p_flags, int p_unbinds, const Vector<int> &p_binds);
@ -186,6 +195,9 @@ public:
virtual void set_last_modified_time(uint64_t p_time) { last_modified_time = p_time; }
uint64_t get_last_modified_time() const { return last_modified_time; }
// Used when saving pointers (saves a path property instead).
static String get_meta_pointer_property(const String &p_property);
SceneState();
};

View file

@ -212,6 +212,15 @@ Ref<PackedScene> ResourceLoaderText::_parse_node_tag(VariantParser::ResourcePars
type = SceneState::TYPE_INSTANCED; //no type? assume this was instantiated
}
HashSet<StringName> path_properties;
if (next_tag.fields.has("node_paths")) {
Vector<String> paths = next_tag.fields["node_paths"];
for (int i = 0; i < paths.size(); i++) {
path_properties.insert(paths[i]);
}
}
if (next_tag.fields.has("instance")) {
instance = packed_scene->get_state()->add_value(next_tag.fields["instance"]);
@ -276,9 +285,10 @@ Ref<PackedScene> ResourceLoaderText::_parse_node_tag(VariantParser::ResourcePars
}
if (!assign.is_empty()) {
int nameidx = packed_scene->get_state()->add_name(assign);
StringName assign_name = assign;
int nameidx = packed_scene->get_state()->add_name(assign_name);
int valueidx = packed_scene->get_state()->add_value(value);
packed_scene->get_state()->add_node_property(node_id, nameidx, valueidx);
packed_scene->get_state()->add_node_property(node_id, nameidx, valueidx, path_properties.has(assign_name));
//it's assignment
} else if (!next_tag.name.is_empty()) {
break;
@ -1939,6 +1949,7 @@ Error ResourceFormatSaverTextInstance::save(const String &p_path, const Ref<Reso
Ref<PackedScene> instance = state->get_node_instance(i);
String instance_placeholder = state->get_node_instance_placeholder(i);
Vector<StringName> groups = state->get_node_groups(i);
Vector<String> deferred_node_paths = state->get_node_deferred_nodepath_properties(i);
String header = "[node";
header += " name=\"" + String(name).c_escape() + "\"";
@ -1955,6 +1966,10 @@ Error ResourceFormatSaverTextInstance::save(const String &p_path, const Ref<Reso
header += " index=\"" + itos(index) + "\"";
}
if (deferred_node_paths.size()) {
header += " node_paths=" + Variant(deferred_node_paths).get_construct_string();
}
if (groups.size()) {
// Write all groups on the same line as they're part of a section header.
// This improves readability while not impacting VCS friendliness too much,