Add recursive comparison to Array and Dictionary
...and expose it to GDScript. Co-authored-by: Emmanuel Leblond <emmanuel.leblond@gmail.com>
This commit is contained in:
parent
1f8497d281
commit
a7aad78fd0
11 changed files with 118 additions and 1 deletions
|
@ -88,6 +88,30 @@ void Array::clear() {
|
||||||
_p->array.clear();
|
_p->array.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool Array::deep_equal(const Array &p_array, int p_recursion_count) const {
|
||||||
|
// Cheap checks
|
||||||
|
ERR_FAIL_COND_V_MSG(p_recursion_count > MAX_RECURSION, true, "Max recursion reached");
|
||||||
|
if (_p == p_array._p) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
const Vector<Variant> &a1 = _p->array;
|
||||||
|
const Vector<Variant> &a2 = p_array._p->array;
|
||||||
|
const int size = a1.size();
|
||||||
|
if (size != a2.size()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Heavy O(n) check
|
||||||
|
p_recursion_count++;
|
||||||
|
for (int i = 0; i < size; i++) {
|
||||||
|
if (!a1[i].deep_equal(a2[i], p_recursion_count)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
bool Array::operator==(const Array &p_array) const {
|
bool Array::operator==(const Array &p_array) const {
|
||||||
return _p == p_array._p;
|
return _p == p_array._p;
|
||||||
}
|
}
|
||||||
|
|
|
@ -56,6 +56,7 @@ public:
|
||||||
bool empty() const;
|
bool empty() const;
|
||||||
void clear();
|
void clear();
|
||||||
|
|
||||||
|
bool deep_equal(const Array &p_array, int p_recursion_count = 0) const;
|
||||||
bool operator==(const Array &p_array) const;
|
bool operator==(const Array &p_array) const;
|
||||||
|
|
||||||
uint32_t hash() const;
|
uint32_t hash() const;
|
||||||
|
|
|
@ -140,6 +140,34 @@ bool Dictionary::erase(const Variant &p_key) {
|
||||||
return _p->variant_map.erase(p_key);
|
return _p->variant_map.erase(p_key);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool Dictionary::deep_equal(const Dictionary &p_dictionary, int p_recursion_count) const {
|
||||||
|
// Cheap checks
|
||||||
|
ERR_FAIL_COND_V_MSG(p_recursion_count > MAX_RECURSION, 0, "Max recursion reached");
|
||||||
|
if (_p == p_dictionary._p) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (_p->variant_map.size() != p_dictionary._p->variant_map.size()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Heavy O(n) check
|
||||||
|
OrderedHashMap<Variant, Variant, VariantHasher, VariantComparator>::Element this_E = _p->variant_map.front();
|
||||||
|
OrderedHashMap<Variant, Variant, VariantHasher, VariantComparator>::Element other_E = p_dictionary._p->variant_map.front();
|
||||||
|
p_recursion_count++;
|
||||||
|
while (this_E && other_E) {
|
||||||
|
if (
|
||||||
|
!this_E.key().deep_equal(other_E.key(), p_recursion_count) ||
|
||||||
|
!this_E.value().deep_equal(other_E.value(), p_recursion_count)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
this_E = this_E.next();
|
||||||
|
other_E = other_E.next();
|
||||||
|
}
|
||||||
|
|
||||||
|
return !this_E && !other_E;
|
||||||
|
}
|
||||||
|
|
||||||
bool Dictionary::operator==(const Dictionary &p_dictionary) const {
|
bool Dictionary::operator==(const Dictionary &p_dictionary) const {
|
||||||
return _p == p_dictionary._p;
|
return _p == p_dictionary._p;
|
||||||
}
|
}
|
||||||
|
|
|
@ -68,6 +68,7 @@ public:
|
||||||
|
|
||||||
bool erase(const Variant &p_key);
|
bool erase(const Variant &p_key);
|
||||||
|
|
||||||
|
bool deep_equal(const Dictionary &p_dictionary, int p_recursion_count = 0) const;
|
||||||
bool operator==(const Dictionary &p_dictionary) const;
|
bool operator==(const Dictionary &p_dictionary) const;
|
||||||
bool operator!=(const Dictionary &p_dictionary) const;
|
bool operator!=(const Dictionary &p_dictionary) const;
|
||||||
|
|
||||||
|
|
|
@ -213,8 +213,12 @@ public:
|
||||||
(*list_element)->get().second = p_value;
|
(*list_element)->get().second = p_value;
|
||||||
return Element(*list_element);
|
return Element(*list_element);
|
||||||
}
|
}
|
||||||
typename InternalList::Element *new_element = list.push_back(Pair<const K *, V>(NULL, p_value));
|
// Incorrectly set the first value of the pair with a value that will
|
||||||
|
// be invalid as soon as we leave this function...
|
||||||
|
typename InternalList::Element *new_element = list.push_back(Pair<const K *, V>(&p_key, p_value));
|
||||||
|
// ...this is needed here in case the hashmap recursively reference itself...
|
||||||
typename InternalMap::Element *e = map.set(p_key, new_element);
|
typename InternalMap::Element *e = map.set(p_key, new_element);
|
||||||
|
// ...now we can set the right value !
|
||||||
new_element->get().first = &e->key();
|
new_element->get().first = &e->key();
|
||||||
|
|
||||||
return Element(new_element);
|
return Element(new_element);
|
||||||
|
|
|
@ -350,4 +350,7 @@ struct _GlobalLock {
|
||||||
#define FALLTHROUGH
|
#define FALLTHROUGH
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
// Limit the depth of recursive algorithms when dealing with Array/Dictionary
|
||||||
|
#define MAX_RECURSION 100
|
||||||
|
|
||||||
#endif // TYPEDEFS_H
|
#endif // TYPEDEFS_H
|
||||||
|
|
|
@ -601,6 +601,37 @@ bool Variant::can_convert_strict(Variant::Type p_type_from, Variant::Type p_type
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool Variant::deep_equal(const Variant &p_variant, int p_recursion_count) const {
|
||||||
|
ERR_FAIL_COND_V_MSG(p_recursion_count > MAX_RECURSION, true, "Max recursion reached");
|
||||||
|
|
||||||
|
// Containers must be handled with recursivity checks
|
||||||
|
switch (type) {
|
||||||
|
case Variant::Type::DICTIONARY: {
|
||||||
|
if (p_variant.type != Variant::Type::DICTIONARY) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const Dictionary v1_as_d = Dictionary(*this);
|
||||||
|
const Dictionary v2_as_d = Dictionary(p_variant);
|
||||||
|
|
||||||
|
return v1_as_d.deep_equal(v2_as_d, p_recursion_count + 1);
|
||||||
|
} break;
|
||||||
|
case Variant::Type::ARRAY: {
|
||||||
|
if (p_variant.type != Variant::Type::ARRAY) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const Array v1_as_a = Array(*this);
|
||||||
|
const Array v2_as_a = Array(p_variant);
|
||||||
|
|
||||||
|
return v1_as_a.deep_equal(v2_as_a, p_recursion_count + 1);
|
||||||
|
} break;
|
||||||
|
default: {
|
||||||
|
return *this == p_variant;
|
||||||
|
} break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
bool Variant::operator==(const Variant &p_variant) const {
|
bool Variant::operator==(const Variant &p_variant) const {
|
||||||
if (type != p_variant.type) { //evaluation of operator== needs to be more strict
|
if (type != p_variant.type) { //evaluation of operator== needs to be more strict
|
||||||
return false;
|
return false;
|
||||||
|
|
|
@ -408,6 +408,7 @@ public:
|
||||||
|
|
||||||
//argsVariant call()
|
//argsVariant call()
|
||||||
|
|
||||||
|
bool deep_equal(const Variant &p_variant, int p_recursion_count = 0) const;
|
||||||
bool operator==(const Variant &p_variant) const;
|
bool operator==(const Variant &p_variant) const;
|
||||||
bool operator!=(const Variant &p_variant) const;
|
bool operator!=(const Variant &p_variant) const;
|
||||||
bool operator<(const Variant &p_variant) const;
|
bool operator<(const Variant &p_variant) const;
|
||||||
|
|
|
@ -231,6 +231,19 @@
|
||||||
[/codeblock]
|
[/codeblock]
|
||||||
</description>
|
</description>
|
||||||
</method>
|
</method>
|
||||||
|
<method name="deep_equal">
|
||||||
|
<return type="bool" />
|
||||||
|
<argument index="0" name="a" type="Variant" />
|
||||||
|
<argument index="1" name="b" type="Variant" />
|
||||||
|
<description>
|
||||||
|
Compares two values by checking their actual contents, recursing into any `Array` or `Dictionary` up to its deepest level.
|
||||||
|
This compares to [code]==[/code] in a number of ways:
|
||||||
|
- For [code]null[/code], [code]int[/code], [code]float[/code], [code]String[/code], [code]Object[/code] and [code]RID[/code] both [code]deep_equal[/code] and [code]==[/code] work the same.
|
||||||
|
- For [code]Dictionary[/code], [code]==[/code] considers equality if, and only if, both variables point to the very same [code]Dictionary[/code], with no recursion or awareness of the contents at all.
|
||||||
|
- For [code]Array[/code], [code]==[/code] considers equality if, and only if, each item in the first [code]Array[/code] is equal to its counterpart in the second [code]Array[/code], as told by [code]==[/code] itself. That implies that [code]==[/code] recurses into [code]Array[/code], but not into [code]Dictionary[/code].
|
||||||
|
In short, whenever a [code]Dictionary[/code] is potentially involved, if you want a true content-aware comparison, you have to use [code]deep_equal[/code].
|
||||||
|
</description>
|
||||||
|
</method>
|
||||||
<method name="deg2rad">
|
<method name="deg2rad">
|
||||||
<return type="float" />
|
<return type="float" />
|
||||||
<argument index="0" name="deg" type="float" />
|
<argument index="0" name="deg" type="float" />
|
||||||
|
|
|
@ -134,6 +134,7 @@ const char *GDScriptFunctions::get_func_name(Function p_func) {
|
||||||
"instance_from_id",
|
"instance_from_id",
|
||||||
"len",
|
"len",
|
||||||
"is_instance_valid",
|
"is_instance_valid",
|
||||||
|
"deep_equal",
|
||||||
};
|
};
|
||||||
|
|
||||||
return _names[p_func];
|
return _names[p_func];
|
||||||
|
@ -1386,6 +1387,10 @@ void GDScriptFunctions::call(Function p_func, const Variant **p_args, int p_arg_
|
||||||
}
|
}
|
||||||
|
|
||||||
} break;
|
} break;
|
||||||
|
case DEEP_EQUAL: {
|
||||||
|
VALIDATE_ARG_COUNT(2);
|
||||||
|
r_ret = p_args[0]->deep_equal(*p_args[1]);
|
||||||
|
} break;
|
||||||
case FUNC_MAX: {
|
case FUNC_MAX: {
|
||||||
ERR_FAIL();
|
ERR_FAIL();
|
||||||
} break;
|
} break;
|
||||||
|
@ -1955,6 +1960,11 @@ MethodInfo GDScriptFunctions::get_info(Function p_func) {
|
||||||
mi.return_val.type = Variant::BOOL;
|
mi.return_val.type = Variant::BOOL;
|
||||||
return mi;
|
return mi;
|
||||||
} break;
|
} break;
|
||||||
|
case DEEP_EQUAL: {
|
||||||
|
MethodInfo mi("deep_equal", PropertyInfo(Variant::NIL, "a"), PropertyInfo(Variant::NIL, "b"));
|
||||||
|
mi.return_val.type = Variant::BOOL;
|
||||||
|
return mi;
|
||||||
|
} break;
|
||||||
default: {
|
default: {
|
||||||
ERR_FAIL_V(MethodInfo());
|
ERR_FAIL_V(MethodInfo());
|
||||||
} break;
|
} break;
|
||||||
|
|
|
@ -126,6 +126,7 @@ public:
|
||||||
INSTANCE_FROM_ID,
|
INSTANCE_FROM_ID,
|
||||||
LEN,
|
LEN,
|
||||||
IS_INSTANCE_VALID,
|
IS_INSTANCE_VALID,
|
||||||
|
DEEP_EQUAL,
|
||||||
FUNC_MAX
|
FUNC_MAX
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue