Browse Source

Allow overriding how scripted objects are converted to strings
solves #26796

- ADD `String to_string()` method to Object which can be overriden by `String _to_string()` in scripts
- ADD `String to_string(r_valid)` method to ScriptInstance to allow langauges to control how scripted objects are converted to strings
- IMPLEMENT to_string for GDScriptInstance, VisualScriptInstance, and NativeScriptInstance
- ADD Documentation about `Object.to_string` and `Object._to_string`
- Changed `Variant::operator String` to use `obj->to_string()`

Leonard Meagher 6 years ago
parent
commit
f7eb426e2e

+ 1 - 0
core/core_string_names.cpp

@@ -44,6 +44,7 @@ CoreStringNames::CoreStringNames() :
 		_iter_next(StaticCString::create("_iter_next")),
 		_iter_get(StaticCString::create("_iter_get")),
 		get_rid(StaticCString::create("get_rid")),
+		_to_string(StaticCString::create("_to_string")),
 #ifdef TOOLS_ENABLED
 		_sections_unfolded(StaticCString::create("_sections_unfolded")),
 #endif

+ 1 - 0
core/core_string_names.h

@@ -62,6 +62,7 @@ public:
 	StringName _iter_next;
 	StringName _iter_get;
 	StringName get_rid;
+	StringName _to_string;
 #ifdef TOOLS_ENABLED
 	StringName _sections_unfolded;
 #endif

+ 12 - 0
core/object.cpp

@@ -956,6 +956,16 @@ void Object::notification(int p_notification, bool p_reversed) {
 	}
 }
 
+String Object::to_string() {
+	if (script_instance) {
+		bool valid;
+		String ret = script_instance->to_string(&valid);
+		if (valid)
+			return ret;
+	}
+	return "[" + get_class() + ":" + itos(get_instance_id()) + "]";
+}
+
 void Object::_changed_callback(Object *p_changed, const char *p_prop) {
 }
 
@@ -1682,6 +1692,7 @@ void Object::_bind_methods() {
 	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));
+	ClassDB::bind_method(D_METHOD("to_string"), &Object::to_string);
 	ClassDB::bind_method(D_METHOD("get_instance_id"), &Object::get_instance_id);
 
 	ClassDB::bind_method(D_METHOD("set_script", "script"), &Object::set_script);
@@ -1768,6 +1779,7 @@ void Object::_bind_methods() {
 
 #endif
 	BIND_VMETHOD(MethodInfo("_init"));
+	BIND_VMETHOD(MethodInfo(Variant::STRING, "_to_string"));
 
 	BIND_CONSTANT(NOTIFICATION_POSTINITIALIZE);
 	BIND_CONSTANT(NOTIFICATION_PREDELETE);

+ 1 - 0
core/object.h

@@ -658,6 +658,7 @@ public:
 	void call_multilevel(const StringName &p_name, VARIANT_ARG_LIST); // C++ helper
 
 	void notification(int p_notification, bool p_reversed = false);
+	String to_string();
 
 	//used mainly by script, get and set all INCLUDING string
 	virtual Variant getvar(const Variant &p_key, bool *r_valid = NULL) const;

+ 1 - 0
core/script_language.cpp

@@ -30,6 +30,7 @@
 
 #include "script_language.h"
 
+#include "core/core_string_names.h"
 #include "core/project_settings.h"
 
 ScriptLanguage *ScriptServer::_languages[MAX_LANGUAGES];

+ 5 - 0
core/script_language.h

@@ -173,6 +173,11 @@ public:
 	virtual void call_multilevel(const StringName &p_method, const Variant **p_args, int p_argcount);
 	virtual void call_multilevel_reversed(const StringName &p_method, const Variant **p_args, int p_argcount);
 	virtual void notification(int p_notification) = 0;
+	virtual String to_string(bool *r_valid) {
+		if (r_valid)
+			*r_valid = false;
+		return String();
+	}
 
 	//this is used by script languages that keep a reference counter of their own
 	//you can make make Ref<> not die when it reaches zero, so deleting the reference

+ 1 - 1
core/variant.cpp

@@ -1582,7 +1582,7 @@ Variant::operator String() const {
 					};
 				};
 #endif
-				return "[" + _get_obj().obj->get_class() + ":" + itos(_get_obj().obj->get_instance_id()) + "]";
+				return _get_obj().obj->to_string();
 			} else
 				return "[Object:null]";
 

+ 16 - 0
doc/classes/Object.xml

@@ -59,6 +59,22 @@
 				Sets a property. Returns [code]true[/code] if the [code]property[/code] exists.
 			</description>
 		</method>
+		<method name="_to_string" qualifiers="virtual">
+			<return type="String">
+			</return>
+			<description>
+				Returns a [String] representing the object. Default is [code]"[ClassName:RID]"[/code].
+				Override this method to customize the [String] representation of the object when it's being converted to a string, for example: [code]print(obj)[/code].
+			</description>
+		</method>
+		<method name="to_string">
+			<return type="String">
+			</return>
+			<description>
+				Returns a [String] representing the object. Default is [code]"[ClassName:RID]"[/code].
+				Override the method [method _to_string] to customize the [String] representation.
+			</description>
+		</method>
 		<method name="add_user_signal">
 			<return type="void">
 			</return>

+ 22 - 0
modules/gdnative/nativescript/nativescript.cpp

@@ -32,6 +32,7 @@
 
 #include "gdnative/gdnative.h"
 
+#include "core/core_string_names.h"
 #include "core/global_constants.h"
 #include "core/io/file_access_encrypted.h"
 #include "core/os/file_access.h"
@@ -771,6 +772,27 @@ void NativeScriptInstance::notification(int p_notification) {
 	call_multilevel("_notification", args, 1);
 }
 
+String NativeScriptInstance::to_string(bool *r_valid) {
+	if (has_method(CoreStringNames::get_singleton()->_to_string)) {
+		Variant::CallError ce;
+		Variant ret = call(CoreStringNames::get_singleton()->_to_string, NULL, 0, ce);
+		if (ce.error == Variant::CallError::CALL_OK) {
+			if (ret.get_type() != Variant::STRING) {
+				if (r_valid)
+					*r_valid = false;
+				ERR_EXPLAIN("Wrong type for " + CoreStringNames::get_singleton()->_to_string + ", must be a String.");
+				ERR_FAIL_V(String());
+			}
+			if (r_valid)
+				*r_valid = true;
+			return ret.operator String();
+		}
+	}
+	if (r_valid)
+		*r_valid = false;
+	return String();
+}
+
 void NativeScriptInstance::refcount_incremented() {
 	Variant::CallError err;
 	call("_refcount_incremented", NULL, 0, err);

+ 1 - 0
modules/gdnative/nativescript/nativescript.h

@@ -208,6 +208,7 @@ public:
 	virtual bool has_method(const StringName &p_method) const;
 	virtual Variant call(const StringName &p_method, const Variant **p_args, int p_argcount, Variant::CallError &r_error);
 	virtual void notification(int p_notification);
+	String to_string(bool *r_valid);
 	virtual Ref<Script> get_script() const;
 	virtual MultiplayerAPI::RPCMode get_rpc_mode(const StringName &p_method) const;
 	virtual MultiplayerAPI::RPCMode get_rset_mode(const StringName &p_variable) const;

+ 22 - 0
modules/gdscript/gdscript.cpp

@@ -30,6 +30,7 @@
 
 #include "gdscript.h"
 
+#include "core/core_string_names.h"
 #include "core/engine.h"
 #include "core/global_constants.h"
 #include "core/io/file_access_encrypted.h"
@@ -1234,6 +1235,27 @@ void GDScriptInstance::notification(int p_notification) {
 	}
 }
 
+String GDScriptInstance::to_string(bool *r_valid) {
+	if (has_method(CoreStringNames::get_singleton()->_to_string)) {
+		Variant::CallError ce;
+		Variant ret = call(CoreStringNames::get_singleton()->_to_string, NULL, 0, ce);
+		if (ce.error == Variant::CallError::CALL_OK) {
+			if (ret.get_type() != Variant::STRING) {
+				if (r_valid)
+					*r_valid = false;
+				ERR_EXPLAIN("Wrong type for " + CoreStringNames::get_singleton()->_to_string + ", must be a String.");
+				ERR_FAIL_V(String());
+			}
+			if (r_valid)
+				*r_valid = true;
+			return ret.operator String();
+		}
+	}
+	if (r_valid)
+		*r_valid = false;
+	return String();
+}
+
 Ref<Script> GDScriptInstance::get_script() const {
 
 	return script;

+ 1 - 0
modules/gdscript/gdscript.h

@@ -251,6 +251,7 @@ public:
 	Variant debug_get_member_by_index(int p_idx) const { return members[p_idx]; }
 
 	virtual void notification(int p_notification);
+	String to_string(bool *r_valid);
 
 	virtual Ref<Script> get_script() const;
 

+ 22 - 0
modules/visual_script/visual_script.cpp

@@ -30,6 +30,7 @@
 
 #include "visual_script.h"
 
+#include "core/core_string_names.h"
 #include "core/os/os.h"
 #include "core/project_settings.h"
 #include "scene/main/node.h"
@@ -1976,6 +1977,27 @@ void VisualScriptInstance::notification(int p_notification) {
 	call(VisualScriptLanguage::singleton->notification, &whatp, 1, ce); //do as call
 }
 
+String VisualScriptInstance::to_string(bool *r_valid) {
+	if (has_method(CoreStringNames::get_singleton()->_to_string)) {
+		Variant::CallError ce;
+		Variant ret = call(CoreStringNames::get_singleton()->_to_string, NULL, 0, ce);
+		if (ce.error == Variant::CallError::CALL_OK) {
+			if (ret.get_type() != Variant::STRING) {
+				if (r_valid)
+					*r_valid = false;
+				ERR_EXPLAIN("Wrong type for " + CoreStringNames::get_singleton()->_to_string + ", must be a String.");
+				ERR_FAIL_V(String());
+			}
+			if (r_valid)
+				*r_valid = true;
+			return ret.operator String();
+		}
+	}
+	if (r_valid)
+		*r_valid = false;
+	return String();
+}
+
 Ref<Script> VisualScriptInstance::get_script() const {
 
 	return script;

+ 1 - 0
modules/visual_script/visual_script.h

@@ -405,6 +405,7 @@ public:
 	virtual bool has_method(const StringName &p_method) const;
 	virtual Variant call(const StringName &p_method, const Variant **p_args, int p_argcount, Variant::CallError &r_error);
 	virtual void notification(int p_notification);
+	String to_string(bool *r_valid);
 
 	bool set_variable(const StringName &p_variable, const Variant &p_value) {