Просмотр исходного кода

Allow for getting/setting indexed properties of objects using get/set_indexed

Performance is around the same as using pure set() through GDScript.
Bojidar Marinov 8 лет назад
Родитель
Сommit
0cf9597758

+ 6 - 14
core/io/marshalls.cpp

@@ -324,7 +324,6 @@ Error decode_variant(Variant &r_variant, const uint8_t *p_buffer, int p_len, int
 				ERR_FAIL_COND_V(len < 12, ERR_INVALID_DATA);
 				Vector<StringName> names;
 				Vector<StringName> subnames;
-				StringName prop;
 
 				uint32_t namecount = strlen &= 0x7FFFFFFF;
 				uint32_t subnamecount = decode_uint32(buf + 4);
@@ -333,9 +332,10 @@ Error decode_variant(Variant &r_variant, const uint8_t *p_buffer, int p_len, int
 				len -= 12;
 				buf += 12;
 
+				if (flags & 2) // Obsolete format with property seperate from subpath
+					subnamecount++;
+
 				uint32_t total = namecount + subnamecount;
-				if (flags & 2)
-					total++;
 
 				if (r_len)
 					(*r_len) += 12;
@@ -359,10 +359,8 @@ Error decode_variant(Variant &r_variant, const uint8_t *p_buffer, int p_len, int
 
 					if (i < namecount)
 						names.push_back(str);
-					else if (i < namecount + subnamecount)
-						subnames.push_back(str);
 					else
-						prop = str;
+						subnames.push_back(str);
 
 					buf += strlen + pad;
 					len -= strlen + pad;
@@ -371,7 +369,7 @@ Error decode_variant(Variant &r_variant, const uint8_t *p_buffer, int p_len, int
 						(*r_len) += 4 + strlen + pad;
 				}
 
-				r_variant = NodePath(names, subnames, flags & 1, prop);
+				r_variant = NodePath(names, subnames, flags & 1);
 
 			} else {
 				//old format, just a string
@@ -919,8 +917,6 @@ Error encode_variant(const Variant &p_variant, uint8_t *r_buffer, int &r_len, bo
 				uint32_t flags = 0;
 				if (np.is_absolute())
 					flags |= 1;
-				if (np.get_property() != StringName())
-					flags |= 2;
 
 				encode_uint32(flags, buf + 8);
 
@@ -930,8 +926,6 @@ Error encode_variant(const Variant &p_variant, uint8_t *r_buffer, int &r_len, bo
 			r_len += 12;
 
 			int total = np.get_name_count() + np.get_subname_count();
-			if (np.get_property() != StringName())
-				total++;
 
 			for (int i = 0; i < total; i++) {
 
@@ -939,10 +933,8 @@ Error encode_variant(const Variant &p_variant, uint8_t *r_buffer, int &r_len, bo
 
 				if (i < np.get_name_count())
 					str = np.get_name(i);
-				else if (i < np.get_name_count() + np.get_subname_count())
-					str = np.get_subname(i - np.get_subname_count());
 				else
-					str = np.get_property();
+					str = np.get_subname(i - np.get_name_count());
 
 				CharString utf8 = str.utf8();
 

+ 1 - 5
core/io/resource_format_binary.cpp

@@ -267,7 +267,6 @@ Error ResourceInteractiveLoaderBinary::parse_variant(Variant &r_v) {
 
 			Vector<StringName> names;
 			Vector<StringName> subnames;
-			StringName property;
 			bool absolute;
 
 			int name_count = f->get_16();
@@ -279,9 +278,8 @@ Error ResourceInteractiveLoaderBinary::parse_variant(Variant &r_v) {
 				names.push_back(_get_string());
 			for (uint32_t i = 0; i < subname_count; i++)
 				subnames.push_back(_get_string());
-			property = _get_string();
 
-			NodePath np = NodePath(names, subnames, absolute, property);
+			NodePath np = NodePath(names, subnames, absolute);
 
 			r_v = np;
 
@@ -1454,7 +1452,6 @@ void ResourceFormatSaverBinaryInstance::write_variant(const Variant &p_property,
 				f->store_32(get_string_index(np.get_name(i)));
 			for (int i = 0; i < np.get_subname_count(); i++)
 				f->store_32(get_string_index(np.get_subname(i)));
-			f->store_32(get_string_index(np.get_property()));
 
 		} break;
 		case Variant::_RID: {
@@ -1685,7 +1682,6 @@ void ResourceFormatSaverBinaryInstance::_find_resources(const Variant &p_variant
 				get_string_index(np.get_name(i));
 			for (int i = 0; i < np.get_subname_count(); i++)
 				get_string_index(np.get_subname(i));
-			get_string_index(np.get_property());
 
 		} break;
 		default: {}

+ 45 - 31
core/node_path.cpp

@@ -48,8 +48,6 @@ uint32_t NodePath::hash() const {
 		h = h ^ ssn[i].hash();
 	}
 
-	h = h ^ data->property.hash();
-
 	return h;
 }
 
@@ -81,13 +79,6 @@ StringName NodePath::get_name(int p_idx) const {
 	return data->path[p_idx];
 }
 
-StringName NodePath::get_property() const {
-
-	if (!data)
-		return StringName();
-	return data->property;
-}
-
 int NodePath::get_subname_count() const {
 
 	if (!data)
@@ -128,9 +119,6 @@ bool NodePath::operator==(const NodePath &p_path) const {
 	if (data->subpath.size() != p_path.data->subpath.size())
 		return false;
 
-	if (data->property != p_path.data->property)
-		return false;
-
 	for (int i = 0; i < data->path.size(); i++) {
 
 		if (data->path[i] != p_path.data->path[i])
@@ -184,8 +172,6 @@ NodePath::operator String() const {
 		ret += ":" + data->subpath[i].operator String();
 	}
 
-	if (data->property.operator String() != "")
-		ret += ":" + String(data->property);
 	return ret;
 }
 
@@ -205,6 +191,7 @@ Vector<StringName> NodePath::get_names() const {
 		return data->path;
 	return Vector<StringName>();
 }
+
 Vector<StringName> NodePath::get_subnames() const {
 
 	if (data)
@@ -212,6 +199,21 @@ Vector<StringName> NodePath::get_subnames() const {
 	return Vector<StringName>();
 }
 
+StringName NodePath::get_concatenated_subnames() const {
+	ERR_FAIL_COND_V(!data, StringName());
+
+	if (!data->concatenated_subpath) {
+		int spc = data->subpath.size();
+		String concatenated;
+		const StringName *ssn = data->subpath.ptr();
+		for (int i = 0; i < spc; i++) {
+			concatenated += i == 0 ? ssn[i].operator String() : "." + ssn[i];
+		}
+		data->concatenated_subpath = concatenated;
+	}
+	return data->concatenated_subpath;
+}
+
 NodePath NodePath::rel_path_to(const NodePath &p_np) const {
 
 	ERR_FAIL_COND_V(!is_absolute(), NodePath());
@@ -250,10 +252,23 @@ NodePath NodePath::rel_path_to(const NodePath &p_np) const {
 	if (relpath.size() == 0)
 		relpath.push_back(".");
 
-	return NodePath(relpath, p_np.get_subnames(), false, p_np.get_property());
+	return NodePath(relpath, p_np.get_subnames(), false);
+}
+
+NodePath NodePath::get_as_property_path() const {
+
+	if (data->has_slashes || !data->path.size()) {
+		return NodePath(Vector<StringName>(), data->subpath, false);
+	} else {
+		ERR_FAIL_COND_V(data->path.size() != 1, NodePath());
+
+		Vector<StringName> new_path = data->subpath;
+		new_path.insert(0, data->path[0]);
+		return NodePath(Vector<StringName>(), new_path, false);
+	}
 }
 
-NodePath::NodePath(const Vector<StringName> &p_path, bool p_absolute, const String &p_property) {
+NodePath::NodePath(const Vector<StringName> &p_path, bool p_absolute) {
 
 	data = NULL;
 
@@ -264,14 +279,14 @@ NodePath::NodePath(const Vector<StringName> &p_path, bool p_absolute, const Stri
 	data->refcount.init();
 	data->absolute = p_absolute;
 	data->path = p_path;
-	data->property = p_property;
+	data->has_slashes = true;
 }
 
-NodePath::NodePath(const Vector<StringName> &p_path, const Vector<StringName> &p_subpath, bool p_absolute, const String &p_property) {
+NodePath::NodePath(const Vector<StringName> &p_path, const Vector<StringName> &p_subpath, bool p_absolute) {
 
 	data = NULL;
 
-	if (p_path.size() == 0)
+	if (p_path.size() == 0 && p_subpath.size() == 0)
 		return;
 
 	data = memnew(Data);
@@ -279,7 +294,7 @@ NodePath::NodePath(const Vector<StringName> &p_path, const Vector<StringName> &p
 	data->absolute = p_absolute;
 	data->path = p_path;
 	data->subpath = p_subpath;
-	data->property = p_property;
+	data->has_slashes = true;
 }
 
 void NodePath::simplify() {
@@ -325,6 +340,7 @@ NodePath::NodePath(const String &p_path) {
 
 	int absolute = (path[0] == '/') ? 1 : 0;
 	bool last_is_slash = true;
+	bool has_slashes = false;
 	int slices = 0;
 	int subpath_pos = path.find(":");
 
@@ -337,16 +353,13 @@ NodePath::NodePath(const String &p_path) {
 			if (path[i] == ':' || path[i] == 0) {
 
 				String str = path.substr(from, i - from);
-				if (path[i] == ':') {
-					if (str == "") {
-						ERR_EXPLAIN("Invalid NodePath: " + p_path);
-						ERR_FAIL();
-					}
-					subpath.push_back(str);
-				} else {
-					//property can be empty
-					property = str;
+				if (str == "") {
+					if (path[i] == 0) continue; // Allow end-of-path :
+
+					ERR_EXPLAIN("Invalid NodePath: " + p_path);
+					ERR_FAIL();
 				}
+				subpath.push_back(str);
 
 				from = i + 1;
 			}
@@ -360,6 +373,7 @@ NodePath::NodePath(const String &p_path) {
 		if (path[i] == '/') {
 
 			last_is_slash = true;
+			has_slashes = true;
 		} else {
 
 			if (last_is_slash)
@@ -369,13 +383,13 @@ NodePath::NodePath(const String &p_path) {
 		}
 	}
 
-	if (slices == 0 && !absolute && !property)
+	if (slices == 0 && !absolute && !subpath.size())
 		return;
 
 	data = memnew(Data);
 	data->refcount.init();
 	data->absolute = absolute ? true : false;
-	data->property = property;
+	data->has_slashes = has_slashes;
 	data->subpath = subpath;
 
 	if (slices == 0)

+ 7 - 6
core/node_path.h

@@ -41,10 +41,11 @@ class NodePath {
 	struct Data {
 
 		SafeRefCount refcount;
-		StringName property;
 		Vector<StringName> path;
 		Vector<StringName> subpath;
+		StringName concatenated_subpath;
 		bool absolute;
+		bool has_slashes;
 	};
 
 	Data *data;
@@ -53,7 +54,7 @@ class NodePath {
 public:
 	_FORCE_INLINE_ StringName get_sname() const {
 
-		if (data && data->path.size() == 1 && data->subpath.empty() && !data->property) {
+		if (data && data->path.size() == 1 && data->subpath.empty()) {
 			return data->path[0];
 		} else {
 			return operator String();
@@ -67,13 +68,13 @@ public:
 	StringName get_subname(int p_idx) const;
 	Vector<StringName> get_names() const;
 	Vector<StringName> get_subnames() const;
+	StringName get_concatenated_subnames() const;
 
 	NodePath rel_path_to(const NodePath &p_np) const;
+	NodePath get_as_property_path() const;
 
 	void prepend_period();
 
-	StringName get_property() const;
-
 	NodePath get_parent() const;
 
 	uint32_t hash() const;
@@ -88,8 +89,8 @@ public:
 	void simplify();
 	NodePath simplified() const;
 
-	NodePath(const Vector<StringName> &p_path, bool p_absolute, const String &p_property = "");
-	NodePath(const Vector<StringName> &p_path, const Vector<StringName> &p_subpath, bool p_absolute, const String &p_property = "");
+	NodePath(const Vector<StringName> &p_path, bool p_absolute);
+	NodePath(const Vector<StringName> &p_path, const Vector<StringName> &p_subpath, bool p_absolute);
 	NodePath(const NodePath &p_path);
 	NodePath(const String &p_path);
 	NodePath();

+ 123 - 0
core/object.cpp

@@ -517,6 +517,80 @@ Variant Object::get(const StringName &p_name, bool *r_valid) const {
 	}
 }
 
+void Object::set_indexed(const Vector<StringName> &p_names, const Variant &p_value, bool *r_valid) {
+	if (p_names.empty()) {
+		if (r_valid)
+			*r_valid = false;
+		return;
+	}
+	if (p_names.size() == 1) {
+		set(p_names[0], p_value, r_valid);
+		return;
+	}
+
+	bool valid = false;
+	if (!r_valid) r_valid = &valid;
+
+	List<Variant> value_stack;
+
+	value_stack.push_back(get(p_names[0], r_valid));
+
+	if (!*r_valid) {
+		value_stack.clear();
+		return;
+	}
+
+	for (int i = 1; i < p_names.size() - 1; i++) {
+		value_stack.push_back(value_stack.back()->get().get_named(p_names[i], r_valid));
+
+		if (!*r_valid) {
+			value_stack.clear();
+			return;
+		}
+	}
+
+	value_stack.push_back(p_value); // p_names[p_names.size() - 1]
+
+	for (int i = p_names.size() - 1; i > 0; i--) {
+
+		value_stack.back()->prev()->get().set_named(p_names[i], value_stack.back()->get(), r_valid);
+		value_stack.pop_back();
+
+		if (!*r_valid) {
+			value_stack.clear();
+			return;
+		}
+	}
+
+	set(p_names[0], value_stack.back()->get(), r_valid);
+	value_stack.pop_back();
+
+	ERR_FAIL_COND(!value_stack.empty());
+}
+
+Variant Object::get_indexed(const Vector<StringName> &p_names, bool *r_valid) const {
+	if (p_names.empty()) {
+		if (r_valid)
+			*r_valid = false;
+		return Variant();
+	}
+	bool valid = false;
+
+	Variant current_value = get(p_names[0]);
+	for (int i = 1; i < p_names.size(); i++) {
+		current_value = current_value.get_named(p_names[i], &valid);
+
+		if (!valid) {
+			if (r_valid)
+				*r_valid = false;
+			return Variant();
+		}
+	}
+	if (r_valid)
+		*r_valid = true;
+	return current_value;
+}
+
 void Object::get_property_list(List<PropertyInfo> *p_list, bool p_reversed) const {
 
 	if (script_instance && p_reversed) {
@@ -1416,6 +1490,16 @@ Variant Object::_get_bind(const String &p_name) const {
 	return get(p_name);
 }
 
+void Object::_set_indexed_bind(const NodePath &p_name, const Variant &p_value) {
+
+	set_indexed(p_name.get_as_property_path().get_subnames(), p_value);
+}
+
+Variant Object::_get_indexed_bind(const NodePath &p_name) const {
+
+	return get_indexed(p_name.get_as_property_path().get_subnames());
+}
+
 void Object::initialize_class() {
 
 	static bool initialized = false;
@@ -1513,6 +1597,8 @@ void Object::_bind_methods() {
 	ClassDB::bind_method(D_METHOD("is_class", "type"), &Object::is_class);
 	ClassDB::bind_method(D_METHOD("set", "property", "value"), &Object::_set_bind);
 	ClassDB::bind_method(D_METHOD("get", "property"), &Object::_get_bind);
+	ClassDB::bind_method(D_METHOD("set_indexed", "property", "value"), &Object::_set_indexed_bind);
+	ClassDB::bind_method(D_METHOD("get_indexed", "property"), &Object::_get_indexed_bind);
 	ClassDB::bind_method(D_METHOD("get_property_list"), &Object::_get_property_list_bind);
 	ClassDB::bind_method(D_METHOD("get_method_list"), &Object::_get_method_list_bind);
 	ClassDB::bind_method(D_METHOD("notification", "what", "reversed"), &Object::notification, DEFVAL(false));
@@ -1661,6 +1747,43 @@ Variant::Type Object::get_static_property_type(const StringName &p_property, boo
 	return Variant::NIL;
 }
 
+Variant::Type Object::get_static_property_type_indexed(const Vector<StringName> &p_path, bool *r_valid) const {
+
+	bool valid = false;
+	Variant::Type t = get_static_property_type(p_path[0], &valid);
+	if (!valid) {
+		if (r_valid)
+			*r_valid = false;
+
+		return Variant::NIL;
+	}
+
+	Variant::CallError ce;
+	Variant check = Variant::construct(t, NULL, 0, ce);
+
+	for (int i = 1; i < p_path.size(); i++) {
+		if (check.get_type() == Variant::OBJECT || check.get_type() == Variant::DICTIONARY || check.get_type() == Variant::ARRAY) {
+			// We cannot be sure about the type of properties this types can have
+			if (r_valid)
+				*r_valid = false;
+			return Variant::NIL;
+		}
+
+		check = check.get_named(p_path[i], &valid);
+
+		if (!valid) {
+			if (r_valid)
+				*r_valid = false;
+			return Variant::NIL;
+		}
+	}
+
+	if (r_valid)
+		*r_valid = true;
+
+	return check.get_type();
+}
+
 bool Object::is_queued_for_deletion() const {
 	return _is_queued_for_deletion;
 }

+ 5 - 0
core/object.h

@@ -477,6 +477,8 @@ private:
 	Array _get_incoming_connections() const;
 	void _set_bind(const String &p_set, const Variant &p_value);
 	Variant _get_bind(const String &p_name) const;
+	void _set_indexed_bind(const NodePath &p_name, const Variant &p_value);
+	Variant _get_indexed_bind(const NodePath &p_name) const;
 
 	void *_script_instance_bindings[MAX_SCRIPT_INSTANCE_BINDINGS];
 
@@ -627,6 +629,8 @@ public:
 
 	void set(const StringName &p_name, const Variant &p_value, bool *r_valid = NULL);
 	Variant get(const StringName &p_name, bool *r_valid = NULL) const;
+	void set_indexed(const Vector<StringName> &p_names, const Variant &p_value, bool *r_valid = NULL);
+	Variant get_indexed(const Vector<StringName> &p_names, bool *r_valid = NULL) const;
 
 	void get_property_list(List<PropertyInfo> *p_list, bool p_reversed = false) const;
 
@@ -687,6 +691,7 @@ public:
 	bool is_blocking_signals() const;
 
 	Variant::Type get_static_property_type(const StringName &p_property, bool *r_valid = NULL) const;
+	Variant::Type get_static_property_type_indexed(const Vector<StringName> &p_path, bool *r_valid = NULL) const;
 
 	virtual void get_translatable_strings(List<String> *p_strings) const;
 

+ 2 - 2
core/variant_call.cpp

@@ -446,7 +446,7 @@ struct _VariantCall {
 	VCALL_LOCALMEM1R(NodePath, get_name);
 	VCALL_LOCALMEM0R(NodePath, get_subname_count);
 	VCALL_LOCALMEM1R(NodePath, get_subname);
-	VCALL_LOCALMEM0R(NodePath, get_property);
+	VCALL_LOCALMEM0R(NodePath, get_concatenated_subnames);
 	VCALL_LOCALMEM0R(NodePath, is_empty);
 
 	VCALL_LOCALMEM0R(Dictionary, size);
@@ -1590,7 +1590,7 @@ void register_variant_methods() {
 	ADDFUNC1R(NODE_PATH, STRING, NodePath, get_name, INT, "idx", varray());
 	ADDFUNC0R(NODE_PATH, INT, NodePath, get_subname_count, varray());
 	ADDFUNC1R(NODE_PATH, STRING, NodePath, get_subname, INT, "idx", varray());
-	ADDFUNC0R(NODE_PATH, STRING, NodePath, get_property, varray());
+	ADDFUNC0R(NODE_PATH, STRING, NodePath, get_concatenated_subnames, varray());
 	ADDFUNC0R(NODE_PATH, BOOL, NodePath, is_empty, varray());
 
 	ADDFUNC0R(DICTIONARY, INT, Dictionary, size, varray());

+ 27 - 13
editor/animation_editor.cpp

@@ -966,7 +966,9 @@ void AnimationKeyEditor::_cleanup_animation(Ref<Animation> p_animation) {
 		Object *obj = NULL;
 
 		RES res;
-		Node *node = root->get_node_and_resource(p_animation->track_get_path(i), res);
+		Vector<StringName> leftover_path;
+
+		Node *node = root->get_node_and_resource(p_animation->track_get_path(i), res, leftover_path);
 
 		if (res.is_valid()) {
 			obj = res.ptr();
@@ -975,7 +977,7 @@ void AnimationKeyEditor::_cleanup_animation(Ref<Animation> p_animation) {
 		}
 
 		if (obj && p_animation->track_get_type(i) == Animation::TYPE_VALUE) {
-			valid_type = obj->get_static_property_type(p_animation->track_get_path(i).get_property(), &prop_exists);
+			valid_type = obj->get_static_property_type_indexed(leftover_path, &prop_exists);
 		}
 
 		if (!obj && cleanup_tracks->is_pressed()) {
@@ -1315,7 +1317,9 @@ void AnimationKeyEditor::_track_editor_draw() {
 		Object *obj = NULL;
 
 		RES res;
-		Node *node = root ? root->get_node_and_resource(animation->track_get_path(idx), res) : (Node *)NULL;
+		Vector<StringName> leftover_path;
+
+		Node *node = root ? root->get_node_and_resource(animation->track_get_path(idx), res, leftover_path) : (Node *)NULL;
 
 		if (res.is_valid()) {
 			obj = res.ptr();
@@ -1324,7 +1328,8 @@ void AnimationKeyEditor::_track_editor_draw() {
 		}
 
 		if (obj && animation->track_get_type(idx) == Animation::TYPE_VALUE) {
-			valid_type = obj->get_static_property_type(animation->track_get_path(idx).get_property(), &prop_exists);
+			// While leftover_path might be still empty, we wouldn't be able to get here anyway
+			valid_type = obj->get_static_property_type_indexed(leftover_path, &prop_exists);
 		}
 
 		if (/*mouse_over.over!=MouseOver::OVER_NONE &&*/ idx == mouse_over.track) {
@@ -1649,26 +1654,34 @@ PropertyInfo AnimationKeyEditor::_find_hint_for_track(int p_idx, NodePath &r_bas
 		return PropertyInfo();
 
 	RES res;
-	Node *node = root->get_node_and_resource(path, res);
+	Vector<StringName> leftover_path;
+	Node *node = root->get_node_and_resource(path, res, leftover_path, true);
 
 	if (node) {
 		r_base_path = node->get_path();
 	}
 
-	String property = path.get_property();
-	if (property == "")
+	if (leftover_path.empty())
 		return PropertyInfo();
 
-	List<PropertyInfo> pinfo;
+	Variant property_info_base;
 	if (res.is_valid())
-		res->get_property_list(&pinfo);
+		property_info_base = res;
 	else if (node)
-		node->get_property_list(&pinfo);
+		property_info_base = node;
+
+	for (int i = 0; i < leftover_path.size() - 1; i++) {
+		property_info_base = property_info_base.get_named(leftover_path[i]);
+	}
+
+	List<PropertyInfo> pinfo;
+	property_info_base.get_property_list(&pinfo);
 
 	for (List<PropertyInfo>::Element *E = pinfo.front(); E; E = E->next()) {
 
-		if (E->get().name == property)
+		if (E->get().name == leftover_path[leftover_path.size() - 1]) {
 			return E->get();
+		}
 	}
 
 	return PropertyInfo();
@@ -2780,7 +2793,8 @@ void AnimationKeyEditor::_track_editor_gui_input(const Ref<InputEvent> &p_input)
 							Object *obj = NULL;
 
 							RES res;
-							Node *node = root->get_node_and_resource(animation->track_get_path(idx), res);
+							Vector<StringName> leftover_path;
+							Node *node = root->get_node_and_resource(animation->track_get_path(idx), res, leftover_path);
 
 							if (res.is_valid()) {
 								obj = res.ptr();
@@ -2789,7 +2803,7 @@ void AnimationKeyEditor::_track_editor_gui_input(const Ref<InputEvent> &p_input)
 							}
 
 							if (obj) {
-								valid_type = obj->get_static_property_type(animation->track_get_path(idx).get_property(), &prop_exists);
+								valid_type = obj->get_static_property_type_indexed(leftover_path, &prop_exists);
 							}
 
 							text += "type: " + Variant::get_type_name(v.get_type()) + "\n";

+ 3 - 3
editor/plugins/animation_tree_editor_plugin.cpp

@@ -1196,14 +1196,14 @@ void AnimationTreeEditor::_edit_filters() {
 		if (base) {
 			NodePath np = E->get();
 
-			if (np.get_property() != StringName()) {
+			if (np.get_subname_count() == 1) {
 				Node *n = base->get_node(np);
 				Skeleton *s = Object::cast_to<Skeleton>(n);
 				if (s) {
 
 					String skelbase = E->get().substr(0, E->get().find(":"));
 
-					int bidx = s->find_bone(np.get_property());
+					int bidx = s->find_bone(np.get_subname(0));
 
 					if (bidx != -1) {
 						int bparent = s->get_bone_parent(bidx);
@@ -1213,7 +1213,7 @@ void AnimationTreeEditor::_edit_filters() {
 							String bpn = skelbase + ":" + s->get_bone_name(bparent);
 							if (pm.has(bpn)) {
 								parent = pm[bpn];
-								descr = np.get_property();
+								descr = np.get_subname(0);
 							}
 						} else {
 

+ 1 - 1
editor/scene_tree_dock.cpp

@@ -1050,7 +1050,7 @@ void SceneTreeDock::perform_node_renames(Node *p_base, List<Pair<NodePath, NodeP
 									//will be renamed
 									NodePath rel_path = new_root_path.rel_path_to(E->get().second);
 
-									NodePath new_path = NodePath(rel_path.get_names(), track_np.get_subnames(), false, track_np.get_property());
+									NodePath new_path = NodePath(rel_path.get_names(), track_np.get_subnames(), false);
 									if (new_path == track_np)
 										continue; //bleh
 									editor_data->get_undo_redo().add_do_method(anim.ptr(), "track_set_path", i, new_path);

+ 2 - 2
modules/gdnative/gdnative/node_path.cpp

@@ -93,10 +93,10 @@ godot_string GDAPI godot_node_path_get_subname(const godot_node_path *p_self, co
 	return dest;
 }
 
-godot_string GDAPI godot_node_path_get_property(const godot_node_path *p_self) {
+godot_string GDAPI godot_node_path_get_concatenated_subnames(const godot_node_path *p_self) {
 	godot_string dest;
 	const NodePath *self = (const NodePath *)p_self;
-	memnew_placement(&dest, String(self->get_property()));
+	memnew_placement(&dest, String(self->get_concatenated_subnames()));
 	return dest;
 }
 

+ 1 - 1
modules/gdnative/gdnative_api.json

@@ -2898,7 +2898,7 @@
         ]
       },
       {
-        "name": "godot_node_path_get_property",
+        "name": "godot_node_path_get_concatenated_subnames",
         "return_type": "godot_string",
         "arguments": [
           ["const godot_node_path *", "p_self"]

+ 1 - 1
modules/gdnative/include/gdnative/node_path.h

@@ -73,7 +73,7 @@ godot_int GDAPI godot_node_path_get_subname_count(const godot_node_path *p_self)
 
 godot_string GDAPI godot_node_path_get_subname(const godot_node_path *p_self, const godot_int p_idx);
 
-godot_string GDAPI godot_node_path_get_property(const godot_node_path *p_self);
+godot_string GDAPI godot_node_path_get_concatenated_subnames(const godot_node_path *p_self);
 
 godot_bool GDAPI godot_node_path_is_empty(const godot_node_path *p_self);
 

+ 45 - 50
scene/animation/animation_cache.cpp

@@ -87,95 +87,90 @@ void AnimationCache::_update_cache() {
 
 		Ref<Resource> res;
 
-		if (np.get_subname_count()) {
-
-			if (animation->track_get_type(i) == Animation::TYPE_TRANSFORM) {
+		if (animation->track_get_type(i) == Animation::TYPE_TRANSFORM) {
 
+			if (np.get_subname_count() > 1) {
 				path_cache.push_back(Path());
 				ERR_EXPLAIN("Transform tracks can't have a subpath: " + np);
 				ERR_CONTINUE(animation->track_get_type(i) == Animation::TYPE_TRANSFORM);
 			}
 
-			RES res;
-
-			for (int j = 0; j < np.get_subname_count(); j++) {
-				res = j == 0 ? node->get(np.get_subname(j)) : res->get(np.get_subname(j));
-				if (res.is_null())
-					break;
-			}
+			Spatial *sp = Object::cast_to<Spatial>(node);
 
-			if (res.is_null()) {
+			if (!sp) {
 
 				path_cache.push_back(Path());
-				ERR_EXPLAIN("Invalid Track SubPath in Animation: " + np);
-				ERR_CONTINUE(res.is_null());
+				ERR_EXPLAIN("Transform track not of type Spatial: " + np);
+				ERR_CONTINUE(!sp);
 			}
 
-			path.resource = res;
-			path.object = res.ptr();
-
-		} else {
-
-			if (animation->track_get_type(i) == Animation::TYPE_TRANSFORM) {
-				StringName property = np.get_property();
+			if (np.get_subname_count() == 1) {
+				StringName property = np.get_subname(0);
 				String ps = property;
 
-				Spatial *sp = Object::cast_to<Spatial>(node);
+				Skeleton *sk = Object::cast_to<Skeleton>(node);
+				if (!sk) {
 
-				if (!sp) {
+					path_cache.push_back(Path());
+					ERR_EXPLAIN("Property defined in Transform track, but not a Skeleton!: " + np);
+					ERR_CONTINUE(!sk);
+				}
 
+				int idx = sk->find_bone(ps);
+				if (idx == -1) {
 					path_cache.push_back(Path());
-					ERR_EXPLAIN("Transform track not of type Spatial: " + np);
-					ERR_CONTINUE(!sp);
+					ERR_EXPLAIN("Property defined in Transform track, but not a Skeleton Bone!: " + np);
+					ERR_CONTINUE(idx == -1);
 				}
 
-				if (ps != "") {
+				path.bone_idx = idx;
+				path.skeleton = sk;
+			}
 
-					Skeleton *sk = Object::cast_to<Skeleton>(node);
-					if (!sk) {
+			path.spatial = sp;
 
-						path_cache.push_back(Path());
-						ERR_EXPLAIN("Property defined in Transform track, but not a Skeleton!: " + np);
-						ERR_CONTINUE(!sk);
-					}
+		} else {
+			if (np.get_subname_count() > 0) {
 
-					int idx = sk->find_bone(ps);
-					if (idx == -1) {
+				RES res;
+				Vector<StringName> leftover_subpath;
 
-						path_cache.push_back(Path());
-						ERR_EXPLAIN("Property defined in Transform track, but not a Skeleton Bone!: " + np);
-						ERR_CONTINUE(idx == -1);
-					}
+				// We don't want to cache the last resource unless it is a method call
+				bool is_method = animation->track_get_type(i) == Animation::TYPE_METHOD;
+				root->get_node_and_resource(np, res, leftover_subpath, is_method);
 
-					path.bone_idx = idx;
-					path.skeleton = sk;
+				if (res.is_valid()) {
+					path.resource = res;
+				} else {
+					path.node = node;
 				}
+				path.object = res.is_valid() ? res.ptr() : (Object *)node;
+				path.subpath = leftover_subpath;
 
-				path.spatial = sp;
-			}
+			} else {
 
-			path.node = node;
-			path.object = node;
+				path.node = node;
+				path.object = node;
+				path.subpath = np.get_subnames();
+			}
 		}
 
 		if (animation->track_get_type(i) == Animation::TYPE_VALUE) {
 
-			if (np.get_property().operator String() == "") {
+			if (np.get_subname_count() == 0) {
 
 				path_cache.push_back(Path());
 				ERR_EXPLAIN("Value Track lacks property: " + np);
-				ERR_CONTINUE(np.get_property().operator String() == "");
+				ERR_CONTINUE(np.get_subname_count() == 0);
 			}
 
-			path.property = np.get_property();
-
 		} else if (animation->track_get_type(i) == Animation::TYPE_METHOD) {
 
-			if (np.get_property().operator String() != "") {
+			if (path.subpath.size() != 0) { // Trying to call a method of a non-resource
 
 				path_cache.push_back(Path());
 				ERR_EXPLAIN("Method Track has property: " + np);
-				ERR_CONTINUE(np.get_property().operator String() != "");
+				ERR_CONTINUE(path.subpath.size() != 0);
 			}
 		}
 
@@ -226,7 +221,7 @@ void AnimationCache::set_track_value(int p_idx, const Variant &p_value) {
 		return;
 
 	ERR_FAIL_COND(!p.object);
-	p.object->set(p.property, p_value);
+	p.object->set_indexed(p.subpath, p_value);
 }
 
 void AnimationCache::call_track(int p_idx, const StringName &p_method, const Variant **p_args, int p_argcount, Variant::CallError &r_error) {

+ 1 - 1
scene/animation/animation_cache.h

@@ -46,7 +46,7 @@ class AnimationCache : public Object {
 		Spatial *spatial;
 
 		int bone_idx;
-		StringName property;
+		Vector<StringName> subpath;
 		bool valid;
 		Path() {
 			object = NULL;

+ 15 - 15
scene/animation/animation_player.cpp

@@ -242,7 +242,8 @@ void AnimationPlayer::_generate_node_caches(AnimationData *p_anim) {
 
 		p_anim->node_cache[i] = NULL;
 		RES resource;
-		Node *child = parent->get_node_and_resource(a->track_get_path(i), resource);
+		Vector<StringName> leftover_path;
+		Node *child = parent->get_node_and_resource(a->track_get_path(i), resource, leftover_path);
 		if (!child) {
 			ERR_EXPLAIN("On Animation: '" + p_anim->name + "', couldn't resolve track:  '" + String(a->track_get_path(i)) + "'");
 		}
@@ -250,9 +251,9 @@ void AnimationPlayer::_generate_node_caches(AnimationData *p_anim) {
 		uint32_t id = resource.is_valid() ? resource->get_instance_id() : child->get_instance_id();
 		int bone_idx = -1;
 
-		if (a->track_get_path(i).get_property() && Object::cast_to<Skeleton>(child)) {
+		if (a->track_get_path(i).get_subname_count() == 1 && Object::cast_to<Skeleton>(child)) {
 
-			bone_idx = Object::cast_to<Skeleton>(child)->find_bone(a->track_get_path(i).get_property());
+			bone_idx = Object::cast_to<Skeleton>(child)->find_bone(a->track_get_path(i).get_subname(0));
 			if (bone_idx == -1) {
 
 				continue;
@@ -289,8 +290,8 @@ void AnimationPlayer::_generate_node_caches(AnimationData *p_anim) {
 				p_anim->node_cache[i]->skeleton = Object::cast_to<Skeleton>(child);
 				if (p_anim->node_cache[i]->skeleton) {
 
-					StringName bone_name = a->track_get_path(i).get_property();
-					if (bone_name.operator String() != "") {
+					if (a->track_get_path(i).get_subname_count() == 1) {
+						StringName bone_name = a->track_get_path(i).get_subname(0);
 
 						p_anim->node_cache[i]->bone_idx = p_anim->node_cache[i]->skeleton->find_bone(bone_name);
 						if (p_anim->node_cache[i]->bone_idx < 0) {
@@ -311,24 +312,23 @@ void AnimationPlayer::_generate_node_caches(AnimationData *p_anim) {
 
 		if (a->track_get_type(i) == Animation::TYPE_VALUE) {
 
-			StringName property = a->track_get_path(i).get_property();
-			if (!p_anim->node_cache[i]->property_anim.has(property)) {
+			if (!p_anim->node_cache[i]->property_anim.has(a->track_get_path(i).get_concatenated_subnames())) {
 
 				TrackNodeCache::PropertyAnim pa;
-				pa.prop = property;
+				pa.subpath = leftover_path;
 				pa.object = resource.is_valid() ? (Object *)resource.ptr() : (Object *)child;
 				pa.special = SP_NONE;
 				pa.owner = p_anim->node_cache[i];
 				if (false && p_anim->node_cache[i]->node_2d) {
 
-					if (pa.prop == SceneStringNames::get_singleton()->transform_pos)
+					if (leftover_path.size() == 1 && leftover_path[0] == SceneStringNames::get_singleton()->transform_pos)
 						pa.special = SP_NODE2D_POS;
-					else if (pa.prop == SceneStringNames::get_singleton()->transform_rot)
+					else if (leftover_path.size() == 1 && leftover_path[0] == SceneStringNames::get_singleton()->transform_rot)
 						pa.special = SP_NODE2D_ROT;
-					else if (pa.prop == SceneStringNames::get_singleton()->transform_scale)
+					else if (leftover_path.size() == 1 && leftover_path[0] == SceneStringNames::get_singleton()->transform_scale)
 						pa.special = SP_NODE2D_SCALE;
 				}
-				p_anim->node_cache[i]->property_anim[property] = pa;
+				p_anim->node_cache[i]->property_anim[a->track_get_path(i).get_concatenated_subnames()] = pa;
 			}
 		}
 	}
@@ -396,7 +396,7 @@ void AnimationPlayer::_animation_process_animation(AnimationData *p_anim, float
 
 				//StringName property=a->track_get_path(i).get_property();
 
-				Map<StringName, TrackNodeCache::PropertyAnim>::Element *E = nc->property_anim.find(a->track_get_path(i).get_property());
+				Map<StringName, TrackNodeCache::PropertyAnim>::Element *E = nc->property_anim.find(a->track_get_path(i).get_concatenated_subnames());
 				ERR_CONTINUE(!E); //should it continue, or create a new one?
 
 				TrackNodeCache::PropertyAnim *pa = &E->get();
@@ -434,7 +434,7 @@ void AnimationPlayer::_animation_process_animation(AnimationData *p_anim, float
 
 							case SP_NONE: {
 								bool valid;
-								pa->object->set(pa->prop, value, &valid); //you are not speshul
+								pa->object->set_indexed(pa->subpath, value, &valid); //you are not speshul
 #ifdef DEBUG_ENABLED
 								if (!valid) {
 									ERR_PRINTS("Failed setting track value '" + String(pa->owner->path) + "'. Check if property exists or the type of key is valid. Animation '" + a->get_name() + "' at node '" + get_path() + "'.");
@@ -615,7 +615,7 @@ void AnimationPlayer::_animation_update_transforms() {
 
 			case SP_NONE: {
 				bool valid;
-				pa->object->set(pa->prop, pa->value_accum, &valid); //you are not speshul
+				pa->object->set_indexed(pa->subpath, pa->value_accum, &valid); //you are not speshul
 #ifdef DEBUG_ENABLED
 				if (!valid) {
 					ERR_PRINTS("Failed setting key at time " + rtos(playback.current.pos) + " in Animation '" + get_current_animation() + "' at Node '" + get_path() + "', Track '" + String(pa->owner->path) + "'. Check if property exists or the type of key is right for the property");

+ 1 - 1
scene/animation/animation_player.h

@@ -83,7 +83,7 @@ private:
 
 			TrackNodeCache *owner;
 			SpecialProperty special; //small optimization
-			StringName prop;
+			Vector<StringName> subpath;
 			Object *object;
 			Variant value_accum;
 			uint64_t accum_pass;

+ 9 - 11
scene/animation/animation_tree_player.cpp

@@ -811,7 +811,7 @@ void AnimationTreePlayer::_process_animation(float p_delta) {
 		t.scale.y = 0;
 		t.scale.z = 0;
 
-		t.value = t.object->get(t.property);
+		t.value = t.object->get_indexed(t.subpath);
 		t.value.zero();
 
 		t.skip = false;
@@ -890,8 +890,8 @@ void AnimationTreePlayer::_process_animation(float p_delta) {
 		if (t.skip || !t.object)
 			continue;
 
-		if (t.property) { // value track
-			t.object->set(t.property, t.value);
+		if (t.subpath.size()) { // value track
+			t.object->set_indexed(t.subpath, t.value);
 			continue;
 		}
 
@@ -1475,7 +1475,8 @@ AnimationTreePlayer::Track *AnimationTreePlayer::_find_track(const NodePath &p_p
 	ERR_FAIL_COND_V(!parent, NULL);
 
 	RES resource;
-	Node *child = parent->get_node_and_resource(p_path, resource);
+	Vector<StringName> leftover_path;
+	Node *child = parent->get_node_and_resource(p_path, resource, leftover_path);
 	if (!child) {
 		String err = "Animation track references unknown Node: '" + String(p_path) + "'.";
 		WARN_PRINT(err.ascii().get_data());
@@ -1483,21 +1484,18 @@ AnimationTreePlayer::Track *AnimationTreePlayer::_find_track(const NodePath &p_p
 	}
 
 	ObjectID id = child->get_instance_id();
-	StringName property;
 	int bone_idx = -1;
 
-	if (p_path.get_property()) {
+	if (p_path.get_subname_count()) {
 
 		if (Object::cast_to<Skeleton>(child))
-			bone_idx = Object::cast_to<Skeleton>(child)->find_bone(p_path.get_property());
-		if (bone_idx == -1)
-			property = p_path.get_property();
+			bone_idx = Object::cast_to<Skeleton>(child)->find_bone(p_path.get_subname(0));
 	}
 
 	TrackKey key;
 	key.id = id;
 	key.bone_idx = bone_idx;
-	key.property = property;
+	key.subpath_concatenated = p_path.get_concatenated_subnames();
 
 	if (!track_map.has(key)) {
 
@@ -1507,7 +1505,7 @@ AnimationTreePlayer::Track *AnimationTreePlayer::_find_track(const NodePath &p_p
 		tr.skeleton = Object::cast_to<Skeleton>(child);
 		tr.spatial = Object::cast_to<Spatial>(child);
 		tr.bone_idx = bone_idx;
-		tr.property = property;
+		if (bone_idx == -1) tr.subpath = leftover_path;
 
 		track_map[key] = tr;
 	}

+ 3 - 3
scene/animation/animation_tree_player.h

@@ -78,14 +78,14 @@ private:
 	struct TrackKey {
 
 		uint32_t id;
-		StringName property;
+		StringName subpath_concatenated;
 		int bone_idx;
 
 		inline bool operator<(const TrackKey &p_right) const {
 
 			if (id == p_right.id) {
 				if (bone_idx == p_right.bone_idx) {
-					return property < p_right.property;
+					return subpath_concatenated < p_right.subpath_concatenated;
 				} else
 					return bone_idx < p_right.bone_idx;
 			} else
@@ -99,7 +99,7 @@ private:
 		Spatial *spatial;
 		Skeleton *skeleton;
 		int bone_idx;
-		StringName property;
+		Vector<StringName> subpath;
 
 		Vector3 loc;
 		Quat rot;

+ 75 - 53
scene/animation/tween.cpp

@@ -264,12 +264,12 @@ Variant &Tween::_get_initial_val(InterpolateData &p_data) {
 			if (p_data.type == TARGETING_PROPERTY) {
 
 				bool valid = false;
-				initial_val = object->get(p_data.target_key, &valid);
+				initial_val = object->get_indexed(p_data.target_key, &valid);
 				ERR_FAIL_COND_V(!valid, p_data.initial_val);
 			} else {
 
 				Variant::CallError error;
-				initial_val = object->call(p_data.target_key, NULL, 0, error);
+				initial_val = object->call(p_data.target_key[0], NULL, 0, error);
 				ERR_FAIL_COND_V(error.error != Variant::CallError::CALL_OK, p_data.initial_val);
 			}
 			return initial_val;
@@ -296,12 +296,12 @@ Variant &Tween::_get_delta_val(InterpolateData &p_data) {
 			if (p_data.type == FOLLOW_PROPERTY) {
 
 				bool valid = false;
-				final_val = target->get(p_data.target_key, &valid);
+				final_val = target->get_indexed(p_data.target_key, &valid);
 				ERR_FAIL_COND_V(!valid, p_data.initial_val);
 			} else {
 
 				Variant::CallError error;
-				final_val = target->call(p_data.target_key, NULL, 0, error);
+				final_val = target->call(p_data.target_key[0], NULL, 0, error);
 				ERR_FAIL_COND_V(error.error != Variant::CallError::CALL_OK, p_data.initial_val);
 			}
 
@@ -462,6 +462,9 @@ Variant Tween::_run_equation(InterpolateData &p_data) {
 
 			result = r;
 		} break;
+		default: {
+			result = initial_val;
+		} break;
 	};
 #undef APPLY_EQUATION
 
@@ -479,7 +482,7 @@ bool Tween::_apply_tween_value(InterpolateData &p_data, Variant &value) {
 		case FOLLOW_PROPERTY:
 		case TARGETING_PROPERTY: {
 			bool valid = false;
-			object->set(p_data.key, value, &valid);
+			object->set_indexed(p_data.key, value, &valid);
 			return valid;
 		}
 
@@ -489,9 +492,9 @@ bool Tween::_apply_tween_value(InterpolateData &p_data, Variant &value) {
 			Variant::CallError error;
 			if (value.get_type() != Variant::NIL) {
 				Variant *arg[1] = { &value };
-				object->call(p_data.key, (const Variant **)arg, 1, error);
+				object->call(p_data.key[0], (const Variant **)arg, 1, error);
 			} else {
-				object->call(p_data.key, NULL, 0, error);
+				object->call(p_data.key[0], NULL, 0, error);
 			}
 
 			if (error.error == Variant::CallError::CALL_OK)
@@ -548,7 +551,7 @@ void Tween::_tween_process(float p_delta) {
 			continue;
 		else if (prev_delaying) {
 
-			emit_signal("tween_started", object, data.key);
+			emit_signal("tween_started", object, NodePath(Vector<StringName>(), data.key, false));
 			_apply_tween_value(data, data.initial_val);
 		}
 
@@ -562,7 +565,7 @@ void Tween::_tween_process(float p_delta) {
 			case INTER_PROPERTY:
 			case INTER_METHOD: {
 				Variant result = _run_equation(data);
-				emit_signal("tween_step", object, data.key, data.elapsed, result);
+				emit_signal("tween_step", object, NodePath(Vector<StringName>(), data.key, false), data.elapsed, result);
 				_apply_tween_value(data, result);
 				if (data.finish)
 					_apply_tween_value(data, data.final_val);
@@ -574,22 +577,22 @@ void Tween::_tween_process(float p_delta) {
 
 						switch (data.args) {
 							case 0:
-								object->call_deferred(data.key);
+								object->call_deferred(data.key[0]);
 								break;
 							case 1:
-								object->call_deferred(data.key, data.arg[0]);
+								object->call_deferred(data.key[0], data.arg[0]);
 								break;
 							case 2:
-								object->call_deferred(data.key, data.arg[0], data.arg[1]);
+								object->call_deferred(data.key[0], data.arg[0], data.arg[1]);
 								break;
 							case 3:
-								object->call_deferred(data.key, data.arg[0], data.arg[1], data.arg[2]);
+								object->call_deferred(data.key[0], data.arg[0], data.arg[1], data.arg[2]);
 								break;
 							case 4:
-								object->call_deferred(data.key, data.arg[0], data.arg[1], data.arg[2], data.arg[3]);
+								object->call_deferred(data.key[0], data.arg[0], data.arg[1], data.arg[2], data.arg[3]);
 								break;
 							case 5:
-								object->call_deferred(data.key, data.arg[0], data.arg[1], data.arg[2], data.arg[3], data.arg[4]);
+								object->call_deferred(data.key[0], data.arg[0], data.arg[1], data.arg[2], data.arg[3], data.arg[4]);
 								break;
 						}
 					} else {
@@ -601,17 +604,18 @@ void Tween::_tween_process(float p_delta) {
 							&data.arg[3],
 							&data.arg[4],
 						};
-						object->call(data.key, (const Variant **)arg, data.args, error);
+						object->call(data.key[0], (const Variant **)arg, data.args, error);
 					}
 				}
 				break;
+			default: {}
 		}
 
 		if (data.finish) {
-			emit_signal("tween_completed", object, data.key);
+			emit_signal("tween_completed", object, NodePath(Vector<StringName>(), data.key, false));
 			// not repeat mode, remove completed action
 			if (!repeat)
-				call_deferred("_remove", object, data.key, true);
+				call_deferred("_remove", object, NodePath(Vector<StringName>(), data.key, false), true);
 		}
 	}
 	pending_update--;
@@ -690,7 +694,7 @@ bool Tween::start() {
 	return true;
 }
 
-bool Tween::reset(Object *p_object, String p_key) {
+bool Tween::reset(Object *p_object, StringName p_key) {
 
 	pending_update++;
 	for (List<InterpolateData>::Element *E = interpolates.front(); E; E = E->next()) {
@@ -700,7 +704,7 @@ bool Tween::reset(Object *p_object, String p_key) {
 		if (object == NULL)
 			continue;
 
-		if (object == p_object && (data.key == p_key || p_key == "")) {
+		if (object == p_object && (data.concatenated_key == p_key || p_key == "")) {
 
 			data.elapsed = 0;
 			data.finish = false;
@@ -727,7 +731,7 @@ bool Tween::reset_all() {
 	return true;
 }
 
-bool Tween::stop(Object *p_object, String p_key) {
+bool Tween::stop(Object *p_object, StringName p_key) {
 
 	pending_update++;
 	for (List<InterpolateData>::Element *E = interpolates.front(); E; E = E->next()) {
@@ -736,7 +740,7 @@ bool Tween::stop(Object *p_object, String p_key) {
 		Object *object = ObjectDB::get_instance(data.id);
 		if (object == NULL)
 			continue;
-		if (object == p_object && (data.key == p_key || p_key == ""))
+		if (object == p_object && (data.concatenated_key == p_key || p_key == ""))
 			data.active = false;
 	}
 	pending_update--;
@@ -758,7 +762,7 @@ bool Tween::stop_all() {
 	return true;
 }
 
-bool Tween::resume(Object *p_object, String p_key) {
+bool Tween::resume(Object *p_object, StringName p_key) {
 
 	set_active(true);
 	_set_process(true);
@@ -770,7 +774,7 @@ bool Tween::resume(Object *p_object, String p_key) {
 		Object *object = ObjectDB::get_instance(data.id);
 		if (object == NULL)
 			continue;
-		if (object == p_object && (data.key == p_key || p_key == ""))
+		if (object == p_object && (data.concatenated_key == p_key || p_key == ""))
 			data.active = true;
 	}
 	pending_update--;
@@ -792,12 +796,12 @@ bool Tween::resume_all() {
 	return true;
 }
 
-bool Tween::remove(Object *p_object, String p_key) {
+bool Tween::remove(Object *p_object, StringName p_key) {
 	_remove(p_object, p_key, false);
 	return true;
 }
 
-void Tween::_remove(Object *p_object, String p_key, bool first_only) {
+void Tween::_remove(Object *p_object, StringName p_key, bool first_only) {
 
 	if (pending_update != 0) {
 		call_deferred("_remove", p_object, p_key, first_only);
@@ -810,7 +814,7 @@ void Tween::_remove(Object *p_object, String p_key, bool first_only) {
 		Object *object = ObjectDB::get_instance(data.id);
 		if (object == NULL)
 			continue;
-		if (object == p_object && (data.key == p_key || p_key == "")) {
+		if (object == p_object && (data.concatenated_key == p_key || p_key == "")) {
 			for_removal.push_back(E);
 			if (first_only) {
 				break;
@@ -850,8 +854,9 @@ bool Tween::seek(real_t p_time) {
 
 			data.finish = true;
 			data.elapsed = (data.delay + data.duration);
-		} else
+		} else {
 			data.finish = false;
+		}
 
 		switch (data.type) {
 			case INTER_PROPERTY:
@@ -993,12 +998,14 @@ bool Tween::_calc_delta_val(const Variant &p_initial_val, const Variant &p_final
 	return true;
 }
 
-bool Tween::interpolate_property(Object *p_object, String p_property, Variant p_initial_val, Variant p_final_val, real_t p_duration, TransitionType p_trans_type, EaseType p_ease_type, real_t p_delay) {
+bool Tween::interpolate_property(Object *p_object, NodePath p_property, Variant p_initial_val, Variant p_final_val, real_t p_duration, TransitionType p_trans_type, EaseType p_ease_type, real_t p_delay) {
 	if (pending_update != 0) {
 		_add_pending_command("interpolate_property", p_object, p_property, p_initial_val, p_final_val, p_duration, p_trans_type, p_ease_type, p_delay);
 		return true;
 	}
-	if (p_initial_val.get_type() == Variant::NIL) p_initial_val = p_object->get(p_property);
+	p_property = p_property.get_as_property_path();
+
+	if (p_initial_val.get_type() == Variant::NIL) p_initial_val = p_object->get_indexed(p_property.get_subnames());
 
 	// convert INT to REAL is better for interpolaters
 	if (p_initial_val.get_type() == Variant::INT) p_initial_val = p_initial_val.operator real_t();
@@ -1013,7 +1020,7 @@ bool Tween::interpolate_property(Object *p_object, String p_property, Variant p_
 	ERR_FAIL_COND_V(p_delay < 0, false);
 
 	bool prop_valid = false;
-	p_object->get(p_property, &prop_valid);
+	p_object->get_indexed(p_property.get_subnames(), &prop_valid);
 	ERR_FAIL_COND_V(!prop_valid, false);
 
 	InterpolateData data;
@@ -1023,7 +1030,8 @@ bool Tween::interpolate_property(Object *p_object, String p_property, Variant p_
 	data.elapsed = 0;
 
 	data.id = p_object->get_instance_id();
-	data.key = p_property;
+	data.key = p_property.get_subnames();
+	data.concatenated_key = p_property.get_concatenated_subnames();
 	data.initial_val = p_initial_val;
 	data.final_val = p_final_val;
 	data.duration = p_duration;
@@ -1038,7 +1046,7 @@ bool Tween::interpolate_property(Object *p_object, String p_property, Variant p_
 	return true;
 }
 
-bool Tween::interpolate_method(Object *p_object, String p_method, Variant p_initial_val, Variant p_final_val, real_t p_duration, TransitionType p_trans_type, EaseType p_ease_type, real_t p_delay) {
+bool Tween::interpolate_method(Object *p_object, StringName p_method, Variant p_initial_val, Variant p_final_val, real_t p_duration, TransitionType p_trans_type, EaseType p_ease_type, real_t p_delay) {
 	if (pending_update != 0) {
 		_add_pending_command("interpolate_method", p_object, p_method, p_initial_val, p_final_val, p_duration, p_trans_type, p_ease_type, p_delay);
 		return true;
@@ -1065,7 +1073,8 @@ bool Tween::interpolate_method(Object *p_object, String p_method, Variant p_init
 	data.elapsed = 0;
 
 	data.id = p_object->get_instance_id();
-	data.key = p_method;
+	data.key.push_back(p_method);
+	data.concatenated_key = p_method;
 	data.initial_val = p_initial_val;
 	data.final_val = p_final_val;
 	data.duration = p_duration;
@@ -1102,7 +1111,8 @@ bool Tween::interpolate_callback(Object *p_object, real_t p_duration, String p_c
 	data.elapsed = 0;
 
 	data.id = p_object->get_instance_id();
-	data.key = p_callback;
+	data.key.push_back(p_callback);
+	data.concatenated_key = p_callback;
 	data.duration = p_duration;
 	data.delay = 0;
 
@@ -1154,7 +1164,8 @@ bool Tween::interpolate_deferred_callback(Object *p_object, real_t p_duration, S
 	data.elapsed = 0;
 
 	data.id = p_object->get_instance_id();
-	data.key = p_callback;
+	data.key.push_back(p_callback);
+	data.concatenated_key = p_callback;
 	data.duration = p_duration;
 	data.delay = 0;
 
@@ -1185,12 +1196,15 @@ bool Tween::interpolate_deferred_callback(Object *p_object, real_t p_duration, S
 	return true;
 }
 
-bool Tween::follow_property(Object *p_object, String p_property, Variant p_initial_val, Object *p_target, String p_target_property, real_t p_duration, TransitionType p_trans_type, EaseType p_ease_type, real_t p_delay) {
+bool Tween::follow_property(Object *p_object, NodePath p_property, Variant p_initial_val, Object *p_target, NodePath p_target_property, real_t p_duration, TransitionType p_trans_type, EaseType p_ease_type, real_t p_delay) {
 	if (pending_update != 0) {
 		_add_pending_command("follow_property", p_object, p_property, p_initial_val, p_target, p_target_property, p_duration, p_trans_type, p_ease_type, p_delay);
 		return true;
 	}
-	if (p_initial_val.get_type() == Variant::NIL) p_initial_val = p_object->get(p_initial_val);
+	p_property = p_property.get_as_property_path();
+	p_target_property = p_target_property.get_as_property_path();
+
+	if (p_initial_val.get_type() == Variant::NIL) p_initial_val = p_object->get_indexed(p_property.get_subnames());
 
 	// convert INT to REAL is better for interpolaters
 	if (p_initial_val.get_type() == Variant::INT) p_initial_val = p_initial_val.operator real_t();
@@ -1205,11 +1219,11 @@ bool Tween::follow_property(Object *p_object, String p_property, Variant p_initi
 	ERR_FAIL_COND_V(p_delay < 0, false);
 
 	bool prop_valid = false;
-	p_object->get(p_property, &prop_valid);
+	p_object->get_indexed(p_property.get_subnames(), &prop_valid);
 	ERR_FAIL_COND_V(!prop_valid, false);
 
 	bool target_prop_valid = false;
-	Variant target_val = p_target->get(p_target_property, &target_prop_valid);
+	Variant target_val = p_target->get_indexed(p_target_property.get_subnames(), &target_prop_valid);
 	ERR_FAIL_COND_V(!target_prop_valid, false);
 
 	// convert INT to REAL is better for interpolaters
@@ -1223,10 +1237,11 @@ bool Tween::follow_property(Object *p_object, String p_property, Variant p_initi
 	data.elapsed = 0;
 
 	data.id = p_object->get_instance_id();
-	data.key = p_property;
+	data.key = p_property.get_subnames();
+	data.concatenated_key = p_property.get_concatenated_subnames();
 	data.initial_val = p_initial_val;
 	data.target_id = p_target->get_instance_id();
-	data.target_key = p_target_property;
+	data.target_key = p_target_property.get_subnames();
 	data.duration = p_duration;
 	data.trans_type = p_trans_type;
 	data.ease_type = p_ease_type;
@@ -1236,7 +1251,7 @@ bool Tween::follow_property(Object *p_object, String p_property, Variant p_initi
 	return true;
 }
 
-bool Tween::follow_method(Object *p_object, String p_method, Variant p_initial_val, Object *p_target, String p_target_method, real_t p_duration, TransitionType p_trans_type, EaseType p_ease_type, real_t p_delay) {
+bool Tween::follow_method(Object *p_object, StringName p_method, Variant p_initial_val, Object *p_target, StringName p_target_method, real_t p_duration, TransitionType p_trans_type, EaseType p_ease_type, real_t p_delay) {
 	if (pending_update != 0) {
 		_add_pending_command("follow_method", p_object, p_method, p_initial_val, p_target, p_target_method, p_duration, p_trans_type, p_ease_type, p_delay);
 		return true;
@@ -1273,10 +1288,11 @@ bool Tween::follow_method(Object *p_object, String p_method, Variant p_initial_v
 	data.elapsed = 0;
 
 	data.id = p_object->get_instance_id();
-	data.key = p_method;
+	data.key.push_back(p_method);
+	data.concatenated_key = p_method;
 	data.initial_val = p_initial_val;
 	data.target_id = p_target->get_instance_id();
-	data.target_key = p_target_method;
+	data.target_key.push_back(p_target_method);
 	data.duration = p_duration;
 	data.trans_type = p_trans_type;
 	data.ease_type = p_ease_type;
@@ -1286,11 +1302,15 @@ bool Tween::follow_method(Object *p_object, String p_method, Variant p_initial_v
 	return true;
 }
 
-bool Tween::targeting_property(Object *p_object, String p_property, Object *p_initial, String p_initial_property, Variant p_final_val, real_t p_duration, TransitionType p_trans_type, EaseType p_ease_type, real_t p_delay) {
+bool Tween::targeting_property(Object *p_object, NodePath p_property, Object *p_initial, NodePath p_initial_property, Variant p_final_val, real_t p_duration, TransitionType p_trans_type, EaseType p_ease_type, real_t p_delay) {
+
 	if (pending_update != 0) {
 		_add_pending_command("targeting_property", p_object, p_property, p_initial, p_initial_property, p_final_val, p_duration, p_trans_type, p_ease_type, p_delay);
 		return true;
 	}
+	p_property = p_property.get_as_property_path();
+	p_initial_property = p_initial_property.get_as_property_path();
+
 	// convert INT to REAL is better for interpolaters
 	if (p_final_val.get_type() == Variant::INT) p_final_val = p_final_val.operator real_t();
 
@@ -1304,11 +1324,11 @@ bool Tween::targeting_property(Object *p_object, String p_property, Object *p_in
 	ERR_FAIL_COND_V(p_delay < 0, false);
 
 	bool prop_valid = false;
-	p_object->get(p_property, &prop_valid);
+	p_object->get_indexed(p_property.get_subnames(), &prop_valid);
 	ERR_FAIL_COND_V(!prop_valid, false);
 
 	bool initial_prop_valid = false;
-	Variant initial_val = p_initial->get(p_initial_property, &initial_prop_valid);
+	Variant initial_val = p_initial->get_indexed(p_initial_property.get_subnames(), &initial_prop_valid);
 	ERR_FAIL_COND_V(!initial_prop_valid, false);
 
 	// convert INT to REAL is better for interpolaters
@@ -1322,9 +1342,10 @@ bool Tween::targeting_property(Object *p_object, String p_property, Object *p_in
 	data.elapsed = 0;
 
 	data.id = p_object->get_instance_id();
-	data.key = p_property;
+	data.key = p_property.get_subnames();
+	data.concatenated_key = p_property.get_concatenated_subnames();
 	data.target_id = p_initial->get_instance_id();
-	data.target_key = p_initial_property;
+	data.target_key = p_initial_property.get_subnames();
 	data.initial_val = initial_val;
 	data.final_val = p_final_val;
 	data.duration = p_duration;
@@ -1339,7 +1360,7 @@ bool Tween::targeting_property(Object *p_object, String p_property, Object *p_in
 	return true;
 }
 
-bool Tween::targeting_method(Object *p_object, String p_method, Object *p_initial, String p_initial_method, Variant p_final_val, real_t p_duration, TransitionType p_trans_type, EaseType p_ease_type, real_t p_delay) {
+bool Tween::targeting_method(Object *p_object, StringName p_method, Object *p_initial, StringName p_initial_method, Variant p_final_val, real_t p_duration, TransitionType p_trans_type, EaseType p_ease_type, real_t p_delay) {
 	if (pending_update != 0) {
 		_add_pending_command("targeting_method", p_object, p_method, p_initial, p_initial_method, p_final_val, p_duration, p_trans_type, p_ease_type, p_delay);
 		return true;
@@ -1376,9 +1397,10 @@ bool Tween::targeting_method(Object *p_object, String p_method, Object *p_initia
 	data.elapsed = 0;
 
 	data.id = p_object->get_instance_id();
-	data.key = p_method;
+	data.key.push_back(p_method);
+	data.concatenated_key = p_method;
 	data.target_id = p_initial->get_instance_id();
-	data.target_key = p_initial_method;
+	data.target_key.push_back(p_initial_method);
 	data.initial_val = initial_val;
 	data.final_val = p_final_val;
 	data.duration = p_duration;

+ 14 - 13
scene/animation/tween.h

@@ -86,12 +86,13 @@ private:
 		bool call_deferred;
 		real_t elapsed;
 		ObjectID id;
-		StringName key;
+		Vector<StringName> key;
+		StringName concatenated_key;
 		Variant initial_val;
 		Variant delta_val;
 		Variant final_val;
 		ObjectID target_id;
-		StringName target_key;
+		Vector<StringName> target_key;
 		real_t duration;
 		TransitionType trans_type;
 		EaseType ease_type;
@@ -132,7 +133,7 @@ private:
 
 	void _tween_process(float p_delta);
 	void _set_process(bool p_process, bool p_force = false);
-	void _remove(Object *p_object, String p_key, bool first_only);
+	void _remove(Object *p_object, StringName p_key, bool first_only);
 
 protected:
 	bool _set(const StringName &p_name, const Variant &p_value);
@@ -156,34 +157,34 @@ public:
 	float get_speed_scale() const;
 
 	bool start();
-	bool reset(Object *p_object, String p_key);
+	bool reset(Object *p_object, StringName p_key);
 	bool reset_all();
-	bool stop(Object *p_object, String p_key);
+	bool stop(Object *p_object, StringName p_key);
 	bool stop_all();
-	bool resume(Object *p_object, String p_key);
+	bool resume(Object *p_object, StringName p_key);
 	bool resume_all();
-	bool remove(Object *p_object, String p_key);
+	bool remove(Object *p_object, StringName p_key);
 	bool remove_all();
 
 	bool seek(real_t p_time);
 	real_t tell() const;
 	real_t get_runtime() const;
 
-	bool interpolate_property(Object *p_object, String p_property, Variant p_initial_val, Variant p_final_val, real_t p_duration, TransitionType p_trans_type, EaseType p_ease_type, real_t p_delay = 0);
+	bool interpolate_property(Object *p_object, NodePath p_property, Variant p_initial_val, Variant p_final_val, real_t p_duration, TransitionType p_trans_type, EaseType p_ease_type, real_t p_delay = 0);
 
-	bool interpolate_method(Object *p_object, String p_method, Variant p_initial_val, Variant p_final_val, real_t p_duration, TransitionType p_trans_type, EaseType p_ease_type, real_t p_delay = 0);
+	bool interpolate_method(Object *p_object, StringName p_method, Variant p_initial_val, Variant p_final_val, real_t p_duration, TransitionType p_trans_type, EaseType p_ease_type, real_t p_delay = 0);
 
 	bool interpolate_callback(Object *p_object, real_t p_duration, String p_callback, VARIANT_ARG_DECLARE);
 
 	bool interpolate_deferred_callback(Object *p_object, real_t p_duration, String p_callback, VARIANT_ARG_DECLARE);
 
-	bool follow_property(Object *p_object, String p_property, Variant p_initial_val, Object *p_target, String p_target_property, real_t p_duration, TransitionType p_trans_type, EaseType p_ease_type, real_t p_delay = 0);
+	bool follow_property(Object *p_object, NodePath p_property, Variant p_initial_val, Object *p_target, NodePath p_target_property, real_t p_duration, TransitionType p_trans_type, EaseType p_ease_type, real_t p_delay = 0);
 
-	bool follow_method(Object *p_object, String p_method, Variant p_initial_val, Object *p_target, String p_target_method, real_t p_duration, TransitionType p_trans_type, EaseType p_ease_type, real_t p_delay = 0);
+	bool follow_method(Object *p_object, StringName p_method, Variant p_initial_val, Object *p_target, StringName p_target_method, real_t p_duration, TransitionType p_trans_type, EaseType p_ease_type, real_t p_delay = 0);
 
-	bool targeting_property(Object *p_object, String p_property, Object *p_initial, String p_initial_property, Variant p_final_val, real_t p_duration, TransitionType p_trans_type, EaseType p_ease_type, real_t p_delay = 0);
+	bool targeting_property(Object *p_object, NodePath p_property, Object *p_initial, NodePath p_initial_property, Variant p_final_val, real_t p_duration, TransitionType p_trans_type, EaseType p_ease_type, real_t p_delay = 0);
 
-	bool targeting_method(Object *p_object, String p_method, Object *p_initial, String p_initial_method, Variant p_final_val, real_t p_duration, TransitionType p_trans_type, EaseType p_ease_type, real_t p_delay = 0);
+	bool targeting_method(Object *p_object, StringName p_method, Object *p_initial, StringName p_initial_method, Variant p_final_val, real_t p_duration, TransitionType p_trans_type, EaseType p_ease_type, real_t p_delay = 0);
 
 	Tween();
 	~Tween();

+ 23 - 14
scene/main/node.cpp

@@ -2466,24 +2466,19 @@ bool Node::has_node_and_resource(const NodePath &p_path) const {
 		return false;
 	Node *node = get_node(p_path);
 
-	if (p_path.get_subname_count()) {
+	bool result = false;
 
-		RES r;
-		for (int j = 0; j < p_path.get_subname_count(); j++) {
-			r = j == 0 ? node->get(p_path.get_subname(j)) : r->get(p_path.get_subname(j));
-			if (r.is_null())
-				return false;
-		}
-	}
+	node->get_indexed(p_path.get_subnames(), &result);
 
-	return true;
+	return result;
 }
 
 Array Node::_get_node_and_resource(const NodePath &p_path) {
 
 	Node *node;
 	RES res;
-	node = get_node_and_resource(p_path, res);
+	Vector<StringName> leftover_path;
+	node = get_node_and_resource(p_path, res, leftover_path);
 	Array result;
 
 	if (node)
@@ -2496,21 +2491,35 @@ Array Node::_get_node_and_resource(const NodePath &p_path) {
 	else
 		result.push_back(Variant());
 
+	result.push_back(NodePath(Vector<StringName>(), leftover_path, false));
+
 	return result;
 }
 
-Node *Node::get_node_and_resource(const NodePath &p_path, RES &r_res) const {
+Node *Node::get_node_and_resource(const NodePath &p_path, RES &r_res, Vector<StringName> &r_leftover_subpath, bool p_last_is_property) const {
 
 	Node *node = get_node(p_path);
 	r_res = RES();
+	r_leftover_subpath = Vector<StringName>();
 	if (!node)
 		return NULL;
 
 	if (p_path.get_subname_count()) {
 
-		for (int j = 0; j < p_path.get_subname_count(); j++) {
-			r_res = j == 0 ? node->get(p_path.get_subname(j)) : r_res->get(p_path.get_subname(j));
-			ERR_FAIL_COND_V(r_res.is_null(), node);
+		int j = 0;
+		// If not p_last_is_property, we shouldn't consider the last one as part of the resource
+		for (; j < p_path.get_subname_count() - p_last_is_property; j++) {
+			RES new_res = j == 0 ? node->get(p_path.get_subname(j)) : r_res->get(p_path.get_subname(j));
+
+			if (new_res.is_null()) {
+				break;
+			}
+
+			r_res = new_res;
+		}
+		for (; j < p_path.get_subname_count(); j++) {
+			// Put the rest of the subpath in the leftover path
+			r_leftover_subpath.push_back(p_path.get_subname(j));
 		}
 	}
 

+ 1 - 1
scene/main/node.h

@@ -242,7 +242,7 @@ public:
 	Node *get_node(const NodePath &p_path) const;
 	Node *find_node(const String &p_mask, bool p_recursive = true, bool p_owned = true) const;
 	bool has_node_and_resource(const NodePath &p_path) const;
-	Node *get_node_and_resource(const NodePath &p_path, RES &r_res) const;
+	Node *get_node_and_resource(const NodePath &p_path, RES &r_res, Vector<StringName> &r_leftover_subpath, bool p_last_is_property = true) const;
 
 	Node *get_parent() const;
 	_FORCE_INLINE_ SceneTree *get_tree() const {