Making inst_to_dict
and dict_to_inst
recursive
Fixes #6533 Making GDScript inst_to_dict/dict_to_inst utility functions recursive. Adding also a new macro to validate the number of the required arguments and another to validate that an argument is boolean.
This commit is contained in:
parent
04692d83cb
commit
f1f96e9023
10 changed files with 224 additions and 101 deletions
|
@ -77,8 +77,10 @@
|
|||
<method name="dict_to_inst">
|
||||
<return type="Object" />
|
||||
<param index="0" name="dictionary" type="Dictionary" />
|
||||
<param index="1" name="deep" type="bool" default="false" />
|
||||
<description>
|
||||
Converts a [param dictionary] (created with [method inst_to_dict]) back to an Object instance. Can be useful for deserializing.
|
||||
If [param deep] is [code]true[/code], the method converts any inner instances recursively. Use this option only if you're sure that instances have no circular references to each other as it can lead to an endless conversion loop.
|
||||
</description>
|
||||
</method>
|
||||
<method name="get_stack">
|
||||
|
@ -106,8 +108,10 @@
|
|||
<method name="inst_to_dict">
|
||||
<return type="Dictionary" />
|
||||
<param index="0" name="instance" type="Object" />
|
||||
<param index="1" name="deep" type="bool" default="false" />
|
||||
<description>
|
||||
Returns the passed [param instance] converted to a Dictionary. Can be useful for serializing.
|
||||
If [param deep] is [code]true[/code], the method converts any inner instances recursively. Use this option only if you're sure that instances have no circular references to each other as it can lead to an endless conversion loop.
|
||||
[b]Note:[/b] Cannot be used to serialize objects with built-in scripts attached or objects allocated within built-in scripts.
|
||||
[codeblock]
|
||||
var foo = "bar"
|
||||
|
|
|
@ -56,6 +56,20 @@
|
|||
return; \
|
||||
}
|
||||
|
||||
#define VALIDATE_MIN_MAX_ARG_COUNT(m_min_count, m_max_count) \
|
||||
if (p_arg_count < m_min_count) { \
|
||||
r_error.error = Callable::CallError::CALL_ERROR_TOO_FEW_ARGUMENTS; \
|
||||
r_error.expected = m_min_count; \
|
||||
*r_ret = Variant(); \
|
||||
return; \
|
||||
} \
|
||||
if (p_arg_count > m_max_count) { \
|
||||
r_error.error = Callable::CallError::CALL_ERROR_TOO_MANY_ARGUMENTS; \
|
||||
r_error.expected = m_max_count; \
|
||||
*r_ret = Variant(); \
|
||||
return; \
|
||||
}
|
||||
|
||||
#define VALIDATE_ARG_INT(m_arg) \
|
||||
if (p_args[m_arg]->get_type() != Variant::INT) { \
|
||||
r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT; \
|
||||
|
@ -74,11 +88,22 @@
|
|||
return; \
|
||||
}
|
||||
|
||||
#define VALIDATE_ARG_BOOL(m_arg) \
|
||||
if (p_args[m_arg]->get_type() != Variant::BOOL) { \
|
||||
r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT; \
|
||||
r_error.argument = m_arg; \
|
||||
r_error.expected = Variant::BOOL; \
|
||||
*r_ret = Variant(); \
|
||||
return; \
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
#define VALIDATE_ARG_COUNT(m_count)
|
||||
#define VALIDATE_MIN_MAX_ARG_COUNT(m_min_count, m_max_count)
|
||||
#define VALIDATE_ARG_INT(m_arg)
|
||||
#define VALIDATE_ARG_NUM(m_arg)
|
||||
#define VALIDATE_ARG_BOOL(m_arg)
|
||||
|
||||
#endif
|
||||
|
||||
|
@ -242,36 +267,38 @@ struct GDScriptUtilityFunctionsDefinitions {
|
|||
}
|
||||
}
|
||||
|
||||
static inline void inst_to_dict(Variant *r_ret, const Variant **p_args, int p_arg_count, Callable::CallError &r_error) {
|
||||
VALIDATE_ARG_COUNT(1);
|
||||
static inline Variant _inst_to_dict(const Variant *p_var, bool p_deep, int p_recursion_count, Callable::CallError &r_error) {
|
||||
if (p_recursion_count > MAX_RECURSION) {
|
||||
ERR_PRINT("Max recursion reached");
|
||||
return Variant();
|
||||
}
|
||||
|
||||
if (p_args[0]->get_type() == Variant::NIL) {
|
||||
*r_ret = Variant();
|
||||
} else if (p_args[0]->get_type() != Variant::OBJECT) {
|
||||
if (p_var->get_type() == Variant::NIL) {
|
||||
return Variant();
|
||||
} else if (p_var->get_type() != Variant::OBJECT) {
|
||||
r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT;
|
||||
r_error.argument = 0;
|
||||
r_error.expected = Variant::OBJECT;
|
||||
*r_ret = Variant();
|
||||
} else {
|
||||
Object *obj = *p_args[0];
|
||||
if (!obj) {
|
||||
*r_ret = Variant();
|
||||
return Variant();
|
||||
}
|
||||
|
||||
Object *obj = *p_var;
|
||||
if (!obj) {
|
||||
return Variant();
|
||||
} else if (!obj->get_script_instance() || obj->get_script_instance()->get_language() != GDScriptLanguage::get_singleton()) {
|
||||
r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT;
|
||||
r_error.argument = 0;
|
||||
r_error.expected = Variant::DICTIONARY;
|
||||
*r_ret = RTR("Not a script with an instance");
|
||||
return;
|
||||
} else {
|
||||
return RTR("Not a script with an instance");
|
||||
}
|
||||
|
||||
GDScriptInstance *ins = static_cast<GDScriptInstance *>(obj->get_script_instance());
|
||||
Ref<GDScript> base = ins->get_script();
|
||||
if (base.is_null()) {
|
||||
r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT;
|
||||
r_error.argument = 0;
|
||||
r_error.expected = Variant::DICTIONARY;
|
||||
*r_ret = RTR("Not based on a script");
|
||||
return;
|
||||
return RTR("Not based on a script");
|
||||
}
|
||||
|
||||
GDScript *p = base.ptr();
|
||||
|
@ -288,11 +315,7 @@ struct GDScriptUtilityFunctionsDefinitions {
|
|||
r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT;
|
||||
r_error.argument = 0;
|
||||
r_error.expected = Variant::DICTIONARY;
|
||||
*r_ret = Variant();
|
||||
|
||||
*r_ret = RTR("Not based on a resource file");
|
||||
|
||||
return;
|
||||
return RTR("Not based on a resource file");
|
||||
}
|
||||
|
||||
NodePath cp(sname, Vector<StringName>(), false);
|
||||
|
@ -303,35 +326,49 @@ struct GDScriptUtilityFunctionsDefinitions {
|
|||
|
||||
for (const KeyValue<StringName, GDScript::MemberInfo> &E : base->member_indices) {
|
||||
if (!d.has(E.key)) {
|
||||
d[E.key] = ins->members[E.value.index];
|
||||
}
|
||||
}
|
||||
*r_ret = d;
|
||||
Variant member = ins->members[E.value.index];
|
||||
if (p_deep && member.get_type() == Variant::OBJECT) {
|
||||
member = _inst_to_dict(&member, p_deep, ++p_recursion_count, r_error);
|
||||
}
|
||||
d[E.key] = member;
|
||||
}
|
||||
}
|
||||
|
||||
static inline void dict_to_inst(Variant *r_ret, const Variant **p_args, int p_arg_count, Callable::CallError &r_error) {
|
||||
VALIDATE_ARG_COUNT(1);
|
||||
return d;
|
||||
}
|
||||
|
||||
if (p_args[0]->get_type() != Variant::DICTIONARY) {
|
||||
static inline void inst_to_dict(Variant *r_ret, const Variant **p_args, int p_arg_count, Callable::CallError &r_error) {
|
||||
VALIDATE_MIN_MAX_ARG_COUNT(1, 2);
|
||||
|
||||
bool deep = false;
|
||||
if (p_arg_count > 1) {
|
||||
VALIDATE_ARG_BOOL(1);
|
||||
deep = *p_args[1];
|
||||
}
|
||||
|
||||
*r_ret = _inst_to_dict(p_args[0], deep, 1, r_error);
|
||||
}
|
||||
|
||||
static inline Variant _dict_to_inst(const Variant *p_var, bool p_deep, int p_recursion_count, Callable::CallError &r_error) {
|
||||
if (p_recursion_count > MAX_RECURSION) {
|
||||
ERR_PRINT("Max recursion reached");
|
||||
return Variant();
|
||||
}
|
||||
|
||||
if (p_var->get_type() != Variant::DICTIONARY) {
|
||||
r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT;
|
||||
r_error.argument = 0;
|
||||
r_error.expected = Variant::DICTIONARY;
|
||||
*r_ret = Variant();
|
||||
|
||||
return;
|
||||
return Variant();
|
||||
}
|
||||
|
||||
Dictionary d = *p_args[0];
|
||||
Dictionary d = *p_var;
|
||||
|
||||
if (!d.has("@path")) {
|
||||
r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT;
|
||||
r_error.argument = 0;
|
||||
r_error.expected = Variant::OBJECT;
|
||||
*r_ret = RTR("Invalid instance dictionary format (missing @path)");
|
||||
|
||||
return;
|
||||
return RTR("Invalid instance dictionary format (missing @path)");
|
||||
}
|
||||
|
||||
Ref<Script> scr = ResourceLoader::load(d["@path"]);
|
||||
|
@ -339,8 +376,7 @@ struct GDScriptUtilityFunctionsDefinitions {
|
|||
r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT;
|
||||
r_error.argument = 0;
|
||||
r_error.expected = Variant::OBJECT;
|
||||
*r_ret = RTR("Invalid instance dictionary format (can't load script at @path)");
|
||||
return;
|
||||
return RTR("Invalid instance dictionary format (can't load script at @path)");
|
||||
}
|
||||
|
||||
Ref<GDScript> gdscr = scr;
|
||||
|
@ -349,9 +385,7 @@ struct GDScriptUtilityFunctionsDefinitions {
|
|||
r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT;
|
||||
r_error.argument = 0;
|
||||
r_error.expected = Variant::OBJECT;
|
||||
*r_ret = Variant();
|
||||
*r_ret = RTR("Invalid instance dictionary format (invalid script at @path)");
|
||||
return;
|
||||
return RTR("Invalid instance dictionary format (invalid script at @path)");
|
||||
}
|
||||
|
||||
NodePath sub;
|
||||
|
@ -365,41 +399,45 @@ struct GDScriptUtilityFunctionsDefinitions {
|
|||
r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT;
|
||||
r_error.argument = 0;
|
||||
r_error.expected = Variant::OBJECT;
|
||||
*r_ret = Variant();
|
||||
*r_ret = RTR("Invalid instance dictionary (invalid subclasses)");
|
||||
return;
|
||||
return RTR("Invalid instance dictionary (invalid subclasses)");
|
||||
}
|
||||
}
|
||||
*r_ret = gdscr->_new(nullptr, -1 /*skip initializer*/, r_error);
|
||||
Variant ret = gdscr->_new(nullptr, -1 /*skip initializer*/, r_error);
|
||||
|
||||
if (r_error.error != Callable::CallError::CALL_OK) {
|
||||
*r_ret = RTR("Cannot instantiate GDScript class.");
|
||||
return;
|
||||
return RTR("Cannot instantiate GDScript class.");
|
||||
}
|
||||
|
||||
GDScriptInstance *ins = static_cast<GDScriptInstance *>(static_cast<Object *>(*r_ret)->get_script_instance());
|
||||
GDScriptInstance *ins = static_cast<GDScriptInstance *>(static_cast<Object *>(ret)->get_script_instance());
|
||||
Ref<GDScript> gd_ref = ins->get_script();
|
||||
|
||||
for (KeyValue<StringName, GDScript::MemberInfo> &E : gd_ref->member_indices) {
|
||||
if (d.has(E.key)) {
|
||||
ins->members.write[E.value.index] = d[E.key];
|
||||
Variant member = d[E.key];
|
||||
if (p_deep && member.get_type() == Variant::DICTIONARY) {
|
||||
member = _dict_to_inst(&member, p_deep, ++p_recursion_count, r_error);
|
||||
}
|
||||
ins->members.write[E.value.index] = member;
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static inline void dict_to_inst(Variant *r_ret, const Variant **p_args, int p_arg_count, Callable::CallError &r_error) {
|
||||
VALIDATE_MIN_MAX_ARG_COUNT(1, 2);
|
||||
|
||||
bool deep = false;
|
||||
if (p_arg_count > 1) {
|
||||
VALIDATE_ARG_BOOL(1);
|
||||
deep = *p_args[1];
|
||||
}
|
||||
|
||||
*r_ret = _dict_to_inst(p_args[0], deep, 1, r_error);
|
||||
}
|
||||
|
||||
static inline void Color8(Variant *r_ret, const Variant **p_args, int p_arg_count, Callable::CallError &r_error) {
|
||||
if (p_arg_count < 3) {
|
||||
r_error.error = Callable::CallError::CALL_ERROR_TOO_FEW_ARGUMENTS;
|
||||
r_error.expected = 3;
|
||||
*r_ret = Variant();
|
||||
return;
|
||||
}
|
||||
if (p_arg_count > 4) {
|
||||
r_error.error = Callable::CallError::CALL_ERROR_TOO_MANY_ARGUMENTS;
|
||||
r_error.expected = 4;
|
||||
*r_ret = Variant();
|
||||
return;
|
||||
}
|
||||
VALIDATE_MIN_MAX_ARG_COUNT(3, 4);
|
||||
|
||||
VALIDATE_ARG_INT(0);
|
||||
VALIDATE_ARG_INT(1);
|
||||
|
@ -717,8 +755,8 @@ void GDScriptUtilityFunctions::register_functions() {
|
|||
REGISTER_FUNC(_char, true, Variant::STRING, ARG("char", Variant::INT));
|
||||
REGISTER_VARARG_FUNC(range, false, Variant::ARRAY);
|
||||
REGISTER_CLASS_FUNC(load, false, "Resource", ARG("path", Variant::STRING));
|
||||
REGISTER_FUNC(inst_to_dict, false, Variant::DICTIONARY, ARG("instance", Variant::OBJECT));
|
||||
REGISTER_FUNC(dict_to_inst, false, Variant::OBJECT, ARG("dictionary", Variant::DICTIONARY));
|
||||
REGISTER_FUNC_DEF(inst_to_dict, false, false, Variant::DICTIONARY, ARG("instance", Variant::OBJECT), ARG("deep", Variant::BOOL));
|
||||
REGISTER_FUNC_DEF(dict_to_inst, false, false, Variant::OBJECT, ARG("dictionary", Variant::DICTIONARY), ARG("deep", Variant::BOOL));
|
||||
REGISTER_FUNC_DEF(Color8, true, 255, Variant::COLOR, ARG("r8", Variant::INT), ARG("g8", Variant::INT), ARG("b8", Variant::INT), ARG("a8", Variant::INT));
|
||||
REGISTER_VARARG_FUNC(print_debug, false, Variant::NIL);
|
||||
REGISTER_FUNC_NO_ARGS(print_stack, false, Variant::NIL);
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
extends Resource
|
||||
|
||||
@export var text: String
|
||||
@export var qux: Resource
|
||||
|
||||
|
||||
func _init(p_text = "", p_qux = null):
|
||||
text = p_text
|
||||
qux = p_qux
|
|
@ -0,0 +1,9 @@
|
|||
[gd_resource type="Resource" load_steps=2 format=3 uid="uid://cqd4xsl30rr7b"]
|
||||
|
||||
[ext_resource type="Script" path="./bar.notest.gd" id="1_bar"]
|
||||
[ext_resource type="Script" path="./qux.tres" id="1_qux"]
|
||||
|
||||
[resource]
|
||||
script = ExtResource("1_bar")
|
||||
text = "lorem ipsum"
|
||||
qux = ExtResource("1_qux")
|
|
@ -0,0 +1,9 @@
|
|||
extends Resource
|
||||
|
||||
@export var number: int
|
||||
@export var bar: Resource
|
||||
|
||||
|
||||
func _init(p_number = 0, p_bar = null):
|
||||
number = p_number
|
||||
bar = p_bar
|
|
@ -0,0 +1,9 @@
|
|||
[gd_resource type="Resource" load_steps=2 format=3 uid="uid://cqd4xsl30rr7a"]
|
||||
|
||||
[ext_resource type="Script" path="./foo.notest.gd" id="1_foo"]
|
||||
[ext_resource type="Script" path="./bar.tres" id="1_bar"]
|
||||
|
||||
[resource]
|
||||
script = ExtResource("1_foo")
|
||||
number = 42
|
||||
bar = ExtResource("1_bar")
|
|
@ -0,0 +1,27 @@
|
|||
extends Resource
|
||||
|
||||
var foo = preload("./foo.tres")
|
||||
|
||||
|
||||
func test():
|
||||
var obj:Object = foo
|
||||
var dict:Dictionary = inst_to_dict(obj)
|
||||
var dict_ok = true
|
||||
dict_ok = dict_ok && dict.get("number") == 42
|
||||
dict_ok = dict_ok && dict.get("bar") is Resource
|
||||
if not dict_ok:
|
||||
printerr("Can't convert instance to dictionary properly")
|
||||
|
||||
dict = inst_to_dict(obj, true)
|
||||
print(dict.keys())
|
||||
print(dict.values())
|
||||
|
||||
var inst = dict_to_inst(dict, true)
|
||||
var equals = true
|
||||
equals = equals && foo.number == inst.number
|
||||
equals = equals && foo.bar.text == inst.bar.text
|
||||
equals = equals && foo.bar.qux.decimal == inst.bar.qux.decimal
|
||||
if not equals:
|
||||
printerr("Can't revert from instance to dictionary properly")
|
||||
|
||||
print('ok')
|
|
@ -0,0 +1,4 @@
|
|||
GDTEST_OK
|
||||
["@subpath", "@path", &"number", &"bar"]
|
||||
[^"", "res://utility_functions/foo.notest.gd", 42, { "@subpath": ^"", "@path": "res://utility_functions/bar.notest.gd", &"text": "lorem ipsum", &"qux": { "@subpath": ^"", "@path": "res://utility_functions/qux.notest.gd", &"decimal": 0.5 } }]
|
||||
ok
|
|
@ -0,0 +1,7 @@
|
|||
extends Resource
|
||||
|
||||
@export var decimal: float
|
||||
|
||||
|
||||
func _init(p_decimal = ""):
|
||||
decimal = p_decimal
|
|
@ -0,0 +1,7 @@
|
|||
[gd_resource type="Resource" load_steps=2 format=3 uid="uid://cqd4xsl30rr7c"]
|
||||
|
||||
[ext_resource type="Script" path="./qux.notest.gd" id="1_qux"]
|
||||
|
||||
[resource]
|
||||
script = ExtResource("1_qux")
|
||||
decimal = 0.5
|
Loading…
Reference in a new issue