Skip to content

Commit c81815c

Browse files
committed
Implement global is_equal function for deep equals check of Variant types
1 parent 9f03bbf commit c81815c

File tree

3 files changed

+306
-0
lines changed

3 files changed

+306
-0
lines changed

core/variant/variant_utility.cpp

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1234,6 +1234,108 @@ bool VariantUtilityFunctions::is_same(const Variant &p_a, const Variant &p_b) {
12341234
return p_a.identity_compare(p_b);
12351235
}
12361236

1237+
bool VariantUtilityFunctions::is_equal(const Variant &p_a, const Variant &p_b) {
1238+
const Variant::Type type_a = p_a.get_type();
1239+
const Variant::Type type_b = p_b.get_type();
1240+
1241+
if (type_a != type_b) {
1242+
return false;
1243+
}
1244+
1245+
// Handle non-recursive types.
1246+
if (type_a != Variant::Type::DICTIONARY &&
1247+
type_a != Variant::Type::OBJECT &&
1248+
type_a != Variant::Type::ARRAY) {
1249+
return p_a == p_b;
1250+
}
1251+
1252+
// Handle recursive types, e.g., Array, Dictionary, and Object.
1253+
if (type_a == Variant::Type::ARRAY) {
1254+
const Array arr_a = p_a;
1255+
const Array arr_b = p_b;
1256+
const int64_t size_a = arr_a.size();
1257+
1258+
if (size_a != arr_b.size()) {
1259+
return false;
1260+
}
1261+
1262+
for (int64_t i = 0; i < size_a; ++i) {
1263+
if (!is_equal(arr_a[i], arr_b[i])) {
1264+
return false;
1265+
}
1266+
}
1267+
1268+
return true;
1269+
}
1270+
1271+
if (type_a == Variant::Type::DICTIONARY) {
1272+
const Dictionary dict_a = p_a;
1273+
const Dictionary dict_b = p_b;
1274+
1275+
const Array keys_a = dict_a.keys();
1276+
const Array keys_b = dict_b.keys();
1277+
1278+
if (keys_a.size() != keys_b.size()) {
1279+
return false;
1280+
}
1281+
1282+
for (int64_t i = 0; i < keys_a.size(); ++i) {
1283+
const Variant &key = keys_a[i];
1284+
if (!dict_b.has(key)) {
1285+
return false;
1286+
}
1287+
if (!is_equal(dict_a[key], dict_b[key])) {
1288+
return false;
1289+
}
1290+
}
1291+
1292+
return true;
1293+
}
1294+
1295+
if (type_a == Variant::Type::OBJECT) {
1296+
const Object *obj_a_ptr = p_a;
1297+
const Object *obj_b_ptr = p_b;
1298+
1299+
if (obj_a_ptr == nullptr && obj_b_ptr == nullptr) {
1300+
return true;
1301+
}
1302+
if (obj_a_ptr == nullptr || obj_b_ptr == nullptr) {
1303+
return false;
1304+
}
1305+
1306+
// Optimization: Check for instance equality.
1307+
if (obj_a_ptr == obj_b_ptr) {
1308+
return true;
1309+
}
1310+
1311+
List<PropertyInfo> props_a;
1312+
obj_a_ptr->get_property_list(&props_a);
1313+
List<PropertyInfo> props_b;
1314+
obj_b_ptr->get_property_list(&props_b);
1315+
1316+
if (props_a.size() != props_b.size()) {
1317+
return false;
1318+
}
1319+
1320+
for (const PropertyInfo &prop_info : props_a) {
1321+
if (props_b.find(prop_info) == nullptr) {
1322+
return false;
1323+
}
1324+
}
1325+
1326+
for (const PropertyInfo &prop_info : props_a) {
1327+
if (!is_equal(obj_a_ptr->get(prop_info.name), obj_b_ptr->get(prop_info.name))) {
1328+
return false;
1329+
}
1330+
}
1331+
1332+
return true;
1333+
}
1334+
1335+
ERR_PRINT("Unhandled Variant type in is_equal. This is a bug.");
1336+
return false;
1337+
}
1338+
12371339
#ifdef DEBUG_METHODS_ENABLED
12381340
#define VCALLR *ret = p_func(VariantCasterAndValidate<P>::cast(p_args, Is, r_error)...)
12391341
#define VCALL p_func(VariantCasterAndValidate<P>::cast(p_args, Is, r_error)...)

core/variant/variant_utility.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -152,4 +152,5 @@ struct VariantUtilityFunctions {
152152
static uint64_t rid_allocate_id();
153153
static RID rid_from_int64(uint64_t p_base);
154154
static bool is_same(const Variant &p_a, const Variant &p_b);
155+
static bool is_equal(const Variant &a, const Variant &b);
155156
};

tests/core/variant/test_variant_utility.h

Lines changed: 203 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232

3333
#include "core/variant/variant_utility.h"
3434

35+
#include "scene/main/node.h"
3536
#include "tests/test_macros.h"
3637

3738
namespace TestVariantUtility {
@@ -128,4 +129,206 @@ TEST_CASE("[VariantUtility] Type conversion") {
128129
}
129130
}
130131

132+
TEST_CASE("[VariantUtility] is_equal deep equality checks") {
133+
SUBCASE("Different Types") {
134+
Variant v_int = 5;
135+
Variant v_str = "hello";
136+
Variant v_arr = Array();
137+
Variant v_dict = Dictionary();
138+
Variant v_null_obj = Variant();
139+
Node *node = memnew(Node);
140+
141+
CHECK_FALSE(VariantUtilityFunctions::is_equal(v_int, v_str));
142+
CHECK_FALSE(VariantUtilityFunctions::is_equal(v_str, v_int));
143+
CHECK_FALSE(VariantUtilityFunctions::is_equal(v_int, v_arr));
144+
CHECK_FALSE(VariantUtilityFunctions::is_equal(v_arr, v_dict));
145+
CHECK_FALSE(VariantUtilityFunctions::is_equal(v_dict, node));
146+
CHECK_FALSE(VariantUtilityFunctions::is_equal(node, v_null_obj));
147+
CHECK_FALSE(VariantUtilityFunctions::is_equal(v_int, v_null_obj));
148+
}
149+
150+
SUBCASE("Basic Types") {
151+
CHECK(VariantUtilityFunctions::is_equal(Variant(10), Variant(10)));
152+
CHECK(VariantUtilityFunctions::is_equal(Variant(3.14), Variant(3.14)));
153+
CHECK(VariantUtilityFunctions::is_equal(Variant(true), Variant(true)));
154+
CHECK(VariantUtilityFunctions::is_equal(Variant("test"), Variant("test")));
155+
CHECK(VariantUtilityFunctions::is_equal(Variant(Vector2(1, 2)), Variant(Vector2(1, 2))));
156+
CHECK(VariantUtilityFunctions::is_equal(Variant(), Variant())); // Nil == Nil
157+
158+
CHECK_FALSE(VariantUtilityFunctions::is_equal(Variant(10), Variant(11)));
159+
CHECK_FALSE(VariantUtilityFunctions::is_equal(Variant(3.14), Variant(3.141)));
160+
CHECK_FALSE(VariantUtilityFunctions::is_equal(Variant(true), Variant(false)));
161+
CHECK_FALSE(VariantUtilityFunctions::is_equal(Variant("test"), Variant("Test")));
162+
CHECK_FALSE(VariantUtilityFunctions::is_equal(Variant("test"), Variant("")));
163+
CHECK_FALSE(VariantUtilityFunctions::is_equal(Variant(Vector2(1, 2)), Variant(Vector2(1, 3))));
164+
CHECK_FALSE(VariantUtilityFunctions::is_equal(Variant(5), Variant()));
165+
}
166+
167+
SUBCASE("Arrays") {
168+
Array a1, a2;
169+
CHECK_MESSAGE(VariantUtilityFunctions::is_equal(a1, a2), "Empty arrays should be equal.");
170+
171+
a1.append(1);
172+
a1.append("hello");
173+
a2.append(1);
174+
a2.append("hello");
175+
CHECK_MESSAGE(VariantUtilityFunctions::is_equal(a1, a2), "Arrays with identical basic elements should be equal.");
176+
177+
Array a3;
178+
a3.append(1);
179+
a3.append("world");
180+
CHECK_FALSE_MESSAGE(VariantUtilityFunctions::is_equal(a1, a3), "Arrays with different element values should not be equal.");
181+
182+
Array a4;
183+
a4.append(1);
184+
a4.append(2);
185+
CHECK_FALSE_MESSAGE(VariantUtilityFunctions::is_equal(a1, a4), "Arrays with different element types at the same index should not be equal.");
186+
187+
Array a5;
188+
a5.append(1);
189+
CHECK_FALSE_MESSAGE(VariantUtilityFunctions::is_equal(a1, a5), "Arrays with different sizes should not be equal.");
190+
CHECK_FALSE_MESSAGE(VariantUtilityFunctions::is_equal(a5, a1), "Arrays with different sizes should not be equal.");
191+
192+
Array nested1, nested2, inner1, inner2;
193+
inner1.append(true);
194+
inner1.append(2.5);
195+
inner2.append(true);
196+
inner2.append(2.5);
197+
nested1.append("outer");
198+
nested1.append(inner1);
199+
nested2.append("outer");
200+
nested2.append(inner2);
201+
CHECK_MESSAGE(VariantUtilityFunctions::is_equal(nested1, nested2), "Arrays with equal nested arrays should be equal.");
202+
203+
Array nested3, inner3;
204+
inner3.append(true);
205+
inner3.append(3.0);
206+
nested3.append("outer");
207+
nested3.append(inner3);
208+
CHECK_FALSE_MESSAGE(VariantUtilityFunctions::is_equal(nested1, nested3), "Arrays with unequal nested arrays should not be equal.");
209+
}
210+
211+
SUBCASE("Dictionaries") {
212+
Dictionary d1, d2;
213+
CHECK_MESSAGE(VariantUtilityFunctions::is_equal(d1, d2), "Empty dictionaries should be equal.");
214+
215+
d1["key1"] = 100;
216+
d1["key2"] = "val";
217+
d2["key1"] = 100;
218+
d2["key2"] = "val";
219+
CHECK_MESSAGE(VariantUtilityFunctions::is_equal(d1, d2), "Dictionaries with identical key-value pairs should be equal.");
220+
221+
Dictionary d3;
222+
d3["key2"] = "val";
223+
d3["key1"] = 100;
224+
CHECK_MESSAGE(VariantUtilityFunctions::is_equal(d1, d3), "Dictionaries with identical key-value pairs in different order should be equal.");
225+
226+
Dictionary d4;
227+
d4["key1"] = 100;
228+
d4["key2"] = "VAL";
229+
CHECK_FALSE_MESSAGE(VariantUtilityFunctions::is_equal(d1, d4), "Dictionaries with different values for the same key should not be equal.");
230+
231+
Dictionary d5;
232+
d5["key1"] = 100;
233+
d5["key_other"] = "val";
234+
CHECK_FALSE_MESSAGE(VariantUtilityFunctions::is_equal(d1, d5), "Dictionaries with different keys should not be equal.");
235+
CHECK_FALSE_MESSAGE(VariantUtilityFunctions::is_equal(d5, d1), "Dictionaries with different keys should not be equal.");
236+
237+
Dictionary d6;
238+
d6["key1"] = 100;
239+
CHECK_FALSE_MESSAGE(VariantUtilityFunctions::is_equal(d1, d6), "Dictionaries with different sizes should not be equal.");
240+
CHECK_FALSE_MESSAGE(VariantUtilityFunctions::is_equal(d6, d1), "Dictionaries with different sizes should not be equal.");
241+
242+
Dictionary nested_d1, nested_d2, inner_d1, inner_d2;
243+
inner_d1["sub_key"] = 9.9;
244+
inner_d2["sub_key"] = 9.9;
245+
nested_d1["level1"] = inner_d1;
246+
nested_d1["another"] = false;
247+
nested_d2["level1"] = inner_d2;
248+
nested_d2["another"] = false;
249+
CHECK_MESSAGE(VariantUtilityFunctions::is_equal(nested_d1, nested_d2), "Dictionaries with equal nested dictionaries should be equal.");
250+
251+
Dictionary nested_d3, inner_d3;
252+
inner_d3["sub_key"] = 10.0;
253+
nested_d3["level1"] = inner_d3;
254+
nested_d3["another"] = false;
255+
CHECK_FALSE_MESSAGE(VariantUtilityFunctions::is_equal(nested_d1, nested_d3), "Dictionaries with unequal nested dictionaries should not be equal.");
256+
}
257+
258+
SUBCASE("Objects") {
259+
Variant null_obj1;
260+
Variant null_obj2;
261+
CHECK_MESSAGE(VariantUtilityFunctions::is_equal(null_obj1, null_obj2), "Two null object Variants should be equal.");
262+
263+
Node *node1 = memnew(Node);
264+
Node *node2 = memnew(Node);
265+
Node *node3 = memnew(Node);
266+
267+
CHECK_FALSE_MESSAGE(VariantUtilityFunctions::is_equal(null_obj1, node1), "A null object Variant and a non-null object Variant should not be equal.");
268+
CHECK_FALSE_MESSAGE(VariantUtilityFunctions::is_equal(node1, null_obj1), "A non-null object Variant and a null object Variant should not be equal.");
269+
270+
CHECK_MESSAGE(VariantUtilityFunctions::is_equal(node1, node2), "Two distinct Node instances with default properties should be equal.");
271+
272+
node1->set_name("TestNode");
273+
node2->set_name("TestNode");
274+
CHECK_MESSAGE(VariantUtilityFunctions::is_equal(node1, node2), "Two Node instances with the same property value should be equal.");
275+
276+
node3->set_name("AnotherNode");
277+
CHECK_FALSE_MESSAGE(VariantUtilityFunctions::is_equal(node1, node3), "Two Node instances with different property values should not be equal.");
278+
279+
Object *base_obj1_ptr = memnew(Object);
280+
Object *base_obj2_ptr = memnew(Object);
281+
Variant v_base_obj1 = base_obj1_ptr;
282+
Variant v_base_obj2 = base_obj2_ptr;
283+
284+
CHECK_MESSAGE(VariantUtilityFunctions::is_equal(v_base_obj1, v_base_obj2), "Two distinct base Object instances should be equal if properties match.");
285+
286+
base_obj1_ptr->set_meta("info", "data1");
287+
base_obj2_ptr->set_meta("info", "data1");
288+
CHECK_MESSAGE(VariantUtilityFunctions::is_equal(v_base_obj1, v_base_obj2), "Two objects with the same meta values should be equal.");
289+
290+
base_obj2_ptr->set_meta("info", "data2");
291+
CHECK_FALSE_MESSAGE(VariantUtilityFunctions::is_equal(v_base_obj1, v_base_obj2), "Two objects with different meta values should not be equal.");
292+
293+
memdelete(base_obj1_ptr);
294+
memdelete(base_obj2_ptr);
295+
}
296+
297+
SUBCASE("Mixed Recursive Types") {
298+
Array mixed_a1, mixed_a2;
299+
Dictionary mixed_d1a, mixed_d1b;
300+
mixed_d1a["value"] = 50;
301+
mixed_d1b["value"] = 50;
302+
mixed_a1.append(mixed_d1a);
303+
mixed_a1.append("common");
304+
mixed_a2.append(mixed_d1b);
305+
mixed_a2.append("common");
306+
CHECK_MESSAGE(VariantUtilityFunctions::is_equal(mixed_a1, mixed_a2), "Arrays containing equal dictionaries should be equal.");
307+
308+
Array mixed_a3;
309+
Dictionary mixed_d1c;
310+
mixed_d1c["value"] = 51;
311+
mixed_a3.append(mixed_d1c);
312+
mixed_a3.append("common");
313+
CHECK_FALSE_MESSAGE(VariantUtilityFunctions::is_equal(mixed_a1, mixed_a3), "Arrays containing unequal dictionaries should not be equal.");
314+
315+
Dictionary mixed_d2a, mixed_d2b;
316+
Array mixed_a1a, mixed_a1b;
317+
mixed_a1a.append(true);
318+
mixed_a1b.append(true);
319+
mixed_d2a["list"] = mixed_a1a;
320+
mixed_d2a["id"] = 1;
321+
mixed_d2b["list"] = mixed_a1b;
322+
mixed_d2b["id"] = 1;
323+
CHECK_MESSAGE(VariantUtilityFunctions::is_equal(mixed_d2a, mixed_d2b), "Dictionaries containing equal arrays should be equal.");
324+
325+
Dictionary mixed_d2c;
326+
Array mixed_a1c;
327+
mixed_a1c.append(false);
328+
mixed_d2c["list"] = mixed_a1c;
329+
mixed_d2c["id"] = 1;
330+
CHECK_FALSE_MESSAGE(VariantUtilityFunctions::is_equal(mixed_d2a, mixed_d2c), "Dictionaries containing unequal arrays should not be equal.");
331+
}
332+
}
333+
131334
} // namespace TestVariantUtility

0 commit comments

Comments
 (0)