[Tizen] Support Property::Value::GetHash() + Implement lite equality operator for... 91/317491/1
authorEunki, Hong <eunkiki.hong@samsung.com>
Thu, 28 Nov 2024 08:21:18 +0000 (17:21 +0900)
committerEunki Hong <eunkiki.hong@samsung.com>
Thu, 2 Jan 2025 14:55:00 +0000 (23:55 +0900)
Let we support to get hash value of Property
+
Seperate the hash calculation function as internal,
so we can remove duplicated hash calculation logics in dali-core.
+
Support equality operasion for Property::Map and Property::Array.

TODO : For now, we don't consider Eplison when we check equality.
TODO : For now, we only compare hash value, not an exact value of array and map.
It should be supported in future.

Allow to use moved Property::Map & Property::Array

Until now, we make those value's mImpl as nullptr.
So their was a lots of DALI_ASSERT_DEBUG() to check
whether they are moved or not.

Those codes might not be fair than other moved objects implementation.

we allow to use moved object like WeakHandle, or Dali::Vector.
Let we also allow to use them.

And let we make surely assert by DALI_ASSERT_ALWAYS if we need, instead DALI_ASSERT_DEBUG

Change-Id: If08853a1635a8c33317edc1d25531223f7c427ed
Signed-off-by: Eunki, Hong <eunkiki.hong@samsung.com>
16 files changed:
automated-tests/src/dali/utc-Dali-PropertyArray.cpp
automated-tests/src/dali/utc-Dali-PropertyMap.cpp [changed mode: 0755->0644]
automated-tests/src/dali/utc-Dali-PropertyValue.cpp
dali/devel-api/common/hash.cpp
dali/devel-api/common/hash.h
dali/internal/common/hash-utils.h [new file with mode: 0644]
dali/internal/event/common/property-input-impl.h
dali/internal/event/rendering/vertex-buffer-impl.cpp
dali/internal/update/nodes/partial-rendering-data.h
dali/public-api/object/property-array.cpp
dali/public-api/object/property-array.h
dali/public-api/object/property-map.cpp
dali/public-api/object/property-map.h
dali/public-api/object/property-value.cpp
dali/public-api/object/property-value.h
dali/public-api/object/property.h

index 40a7c125ca2bb0c3b84b0f8d2d0a25cdede7b76c..05a87b7f6b74ac6c7d906203032ddebebbdb06bf 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2023 Samsung Electronics Co., Ltd.
+ * Copyright (c) 2024 Samsung Electronics Co., Ltd.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -279,6 +279,21 @@ int UtcDaliPropertyArrayOstream02(void)
   END_TEST;
 }
 
+int UtcDaliPropertyArrayOstream03(void)
+{
+  std::ostringstream oss;
+
+  Property::Array array1;
+  array1.PushBack(0);
+
+  Property::Array array2(std::move(array1));
+
+  oss << array1;
+  DALI_TEST_EQUALS(oss.str().compare("Array(0) = []"), 0, TEST_LOCATION);
+
+  END_TEST;
+}
+
 int UtcDaliPropertyArrayCopyConstructor(void)
 {
   Property::Array array1;
@@ -324,19 +339,10 @@ int UtcDaliPropertyArrayMoveConstructor(void)
   Property::Array array2(std::move(array1));
   DALI_TEST_EQUALS(3u, array2.Size(), TEST_LOCATION);
 
-  // Calling any methods on array1 will debug assert
+  // Calling some methods on array1 will debug assert
   const char* exceptionMessage = "Cannot use an object previously used as an r-value";
-  DALI_TEST_ASSERTION(array1.Count(), exceptionMessage);
-  DALI_TEST_ASSERTION(array1.PushBack(Property::Value()), exceptionMessage);
-  DALI_TEST_ASSERTION(array1.Count(), exceptionMessage);
-  DALI_TEST_ASSERTION(array1.Clear(), exceptionMessage);
-  DALI_TEST_ASSERTION(array1.Reserve(1), exceptionMessage);
-  DALI_TEST_ASSERTION(array1.Resize(1), exceptionMessage);
-  DALI_TEST_ASSERTION(array1.Capacity(), exceptionMessage);
   DALI_TEST_ASSERTION(array1[0], exceptionMessage);
   DALI_TEST_ASSERTION(const_cast<const Property::Array&>(array1)[0], exceptionMessage);
-  DALI_TEST_ASSERTION(Property::Array temp; array1 = temp, exceptionMessage);
-
   END_TEST;
 }
 
@@ -355,34 +361,127 @@ int UtcDaliPropertyArrayMoveAssignmentOperator(void)
   array2 = std::move(array1);
   DALI_TEST_EQUALS(3u, array2.Size(), TEST_LOCATION);
 
-  // Calling any methods on array1 will debug assert
+  // Calling some methods on array1 will debug assert
   const char* exceptionMessage = "Cannot use an object previously used as an r-value";
-  DALI_TEST_ASSERTION(array1.Count(), exceptionMessage);
-  DALI_TEST_ASSERTION(array1.PushBack(Property::Value()), exceptionMessage);
-  DALI_TEST_ASSERTION(array1.Count(), exceptionMessage);
-  DALI_TEST_ASSERTION(array1.Clear(), exceptionMessage);
-  DALI_TEST_ASSERTION(array1.Reserve(1), exceptionMessage);
-  DALI_TEST_ASSERTION(array1.Resize(1), exceptionMessage);
-  DALI_TEST_ASSERTION(array1.Capacity(), exceptionMessage);
   DALI_TEST_ASSERTION(array1[0], exceptionMessage);
   DALI_TEST_ASSERTION(const_cast<const Property::Array&>(array1)[0], exceptionMessage);
-  DALI_TEST_ASSERTION(Property::Array temp; array1 = temp, exceptionMessage);
 
   // Self std::move assignment make compile warning over gcc-13. Let we ignore the warning.
-#if (__GNUC__ >= 13)
+#if(__GNUC__ >= 13)
 #pragma GCC diagnostic push
 #pragma GCC diagnostic ignored "-Wself-move"
 #endif
   // Self assignemnt
   array2 = std::move(array2);
   DALI_TEST_EQUALS(3u, array2.Size(), TEST_LOCATION); // still works, no debug assert
-#if (__GNUC__ >= 13)
+#if(__GNUC__ >= 13)
 #pragma GCC diagnostic pop
 #endif
 
   END_TEST;
 }
 
+int UtcDaliPropertyArrayMovedArrayP1(void)
+{
+  Property::Array array1;
+  array1.PushBack(0);
+  array1.PushBack(1);
+  array1.PushBack(2);
+  DALI_TEST_EQUALS(3u, array1.Size(), TEST_LOCATION);
+
+  Property::Array array2(std::move(array1));
+  DALI_TEST_EQUALS(3u, array2.Size(), TEST_LOCATION);
+
+  // Calling some methods on array1 will debug assert
+  const char* exceptionMessage = "Cannot use an object previously used as an r-value";
+  DALI_TEST_ASSERTION(array1[0], exceptionMessage);
+  DALI_TEST_ASSERTION(const_cast<const Property::Array&>(array1)[0], exceptionMessage);
+
+  // Call some API to moved array
+  Property::Array emptyArray;
+  DALI_TEST_EQUALS(emptyArray.GetHash(), array1.GetHash(), TEST_LOCATION);
+  DALI_TEST_EQUALS(0u, array1.Count(), TEST_LOCATION);
+  DALI_TEST_EQUALS(0u, array1.Capacity(), TEST_LOCATION);
+  DALI_TEST_EQUALS(true, array1.Empty(), TEST_LOCATION);
+  array1.Clear();
+
+  // Test reserve
+  array1.Reserve(4u);
+  DALI_TEST_EQUALS(0u, array1.Count(), TEST_LOCATION);
+  DALI_TEST_EQUALS(4u, array1.Capacity(), TEST_LOCATION);
+
+  array2 = std::move(array1);
+  DALI_TEST_EQUALS(0u, array1.Count(), TEST_LOCATION);
+  DALI_TEST_EQUALS(0u, array1.Capacity(), TEST_LOCATION);
+
+  // Test resize
+  array1.Resize(2u);
+  DALI_TEST_EQUALS(2u, array1.Count(), TEST_LOCATION);
+  DALI_TEST_EQUALS(2u, array1.Capacity(), TEST_LOCATION);
+
+  array2 = std::move(array1);
+  DALI_TEST_EQUALS(0u, array1.Count(), TEST_LOCATION);
+  DALI_TEST_EQUALS(0u, array1.Capacity(), TEST_LOCATION);
+
+  // Test PushBack
+  array1.PushBack(0);
+  array1.PushBack(1);
+  array1.PushBack(2);
+  DALI_TEST_EQUALS(3u, array1.Size(), TEST_LOCATION);
+  DALI_TEST_EQUALS(2, array1[2].Get<int32_t>(), TEST_LOCATION);
+
+  array2 = std::move(array1);
+  DALI_TEST_EQUALS(0u, array1.Count(), TEST_LOCATION);
+  DALI_TEST_EQUALS(0u, array1.Capacity(), TEST_LOCATION);
+
+  // Test copy operator
+  DALI_TEST_EQUALS(3u, array2.Size(), TEST_LOCATION);
+  DALI_TEST_EQUALS(2, array2[2].Get<int32_t>(), TEST_LOCATION);
+
+  array1 = array2;
+
+  DALI_TEST_EQUALS(3u, array1.Size(), TEST_LOCATION);
+  DALI_TEST_EQUALS(2, array1[2].Get<int32_t>(), TEST_LOCATION);
+
+  array2 = std::move(array1);
+  DALI_TEST_EQUALS(0u, array1.Count(), TEST_LOCATION);
+  DALI_TEST_EQUALS(0u, array1.Capacity(), TEST_LOCATION);
+
+  // Test copy moved array
+  Property::Array array3 = array1;
+  DALI_TEST_EQUALS(0u, array3.Count(), TEST_LOCATION);
+  DALI_TEST_EQUALS(0u, array3.Capacity(), TEST_LOCATION);
+
+  Property::Array array4;
+  array4 = array1;
+  DALI_TEST_EQUALS(0u, array4.Count(), TEST_LOCATION);
+  DALI_TEST_EQUALS(0u, array4.Capacity(), TEST_LOCATION);
+
+  // Test move operator
+  DALI_TEST_EQUALS(3u, array2.Size(), TEST_LOCATION);
+  DALI_TEST_EQUALS(2, array2[2].Get<int32_t>(), TEST_LOCATION);
+
+  array1 = std::move(array2);
+
+  DALI_TEST_EQUALS(3u, array1.Size(), TEST_LOCATION);
+  DALI_TEST_EQUALS(2, array1[2].Get<int32_t>(), TEST_LOCATION);
+
+  // Test move moved array
+  DALI_TEST_EQUALS(0u, array2.Count(), TEST_LOCATION);
+  DALI_TEST_EQUALS(0u, array2.Capacity(), TEST_LOCATION);
+
+  Property::Array array5 = std::move(array2);
+  DALI_TEST_EQUALS(0u, array5.Count(), TEST_LOCATION);
+  DALI_TEST_EQUALS(0u, array5.Capacity(), TEST_LOCATION);
+
+  array3 = std::move(array2);
+
+  DALI_TEST_EQUALS(0u, array3.Count(), TEST_LOCATION);
+  DALI_TEST_EQUALS(0u, array3.Capacity(), TEST_LOCATION);
+
+  END_TEST;
+}
+
 int UtcDaliPropertyArrayInitializerListConstructor(void)
 {
   Property::Array array{1, 2, "hello"};
@@ -393,3 +492,196 @@ int UtcDaliPropertyArrayInitializerListConstructor(void)
 
   END_TEST;
 }
+
+int UtcDaliPropertyArrayGetHashP01(void)
+{
+  tet_infoline("Check Property::Array::GetHash()");
+
+  Property::Array array;
+
+  tet_printf("Check empty array is not zero.\n");
+  auto emptyArrayHash = array.GetHash();
+
+  DALI_TEST_NOT_EQUALS(emptyArrayHash, static_cast<decltype(emptyArrayHash)>(0u), Math::MACHINE_EPSILON_100, TEST_LOCATION);
+
+  array.PushBack(1);
+  array.PushBack(Vector2(2.0f, 3.0f));
+  array.PushBack(4);
+
+  DALI_TEST_NOT_EQUALS(emptyArrayHash, array.GetHash(), Math::MACHINE_EPSILON_100, TEST_LOCATION);
+
+  Property::Value& lValue = array.GetElementAt(1);
+
+  auto originalHash = array.GetHash();
+
+  tet_printf("Check if l-value of some element changeness applied.\n");
+
+  DALI_TEST_EQUALS(originalHash, array.GetHash(), TEST_LOCATION);
+
+  lValue       = Property::Value(Vector3(2.0f, 3.0f, 0.0f));
+  auto newHash = array.GetHash();
+
+  DALI_TEST_NOT_EQUALS(originalHash, newHash, Math::MACHINE_EPSILON_100, TEST_LOCATION);
+  DALI_TEST_EQUALS(array.GetElementAt(1).Get<Vector3>(), Vector3(2.0f, 3.0f, 0.0f), TEST_LOCATION);
+
+  tet_printf("Revert l-value as original value, and check it applied.\n");
+  lValue  = Property::Value(Vector2(2.0f, 3.0f));
+  newHash = array.GetHash();
+
+  DALI_TEST_EQUALS(originalHash, newHash, TEST_LOCATION);
+
+  Property::Array otherArray;
+
+  otherArray.PushBack(1);
+  otherArray.PushBack(Vector2(2.0f, 3.0f));
+  otherArray.PushBack(4);
+
+  tet_printf("Check same array return same hash.\n");
+  DALI_TEST_EQUALS(originalHash, otherArray.GetHash(), TEST_LOCATION);
+
+  otherArray.Resize(4);
+
+  tet_printf("Check array with difference size return difference hash.\n");
+  DALI_TEST_NOT_EQUALS(originalHash, otherArray.GetHash(), Math::MACHINE_EPSILON_100, TEST_LOCATION);
+
+  otherArray[3] = Property::Value("5");
+  DALI_TEST_NOT_EQUALS(originalHash, otherArray.GetHash(), Math::MACHINE_EPSILON_100, TEST_LOCATION);
+
+  otherArray.Resize(3);
+
+  tet_printf("Check resized array return same hash.\n");
+  DALI_TEST_EQUALS(originalHash, otherArray.GetHash(), TEST_LOCATION);
+
+  Property::Array otherArray2;
+
+  otherArray2.PushBack(4);
+  otherArray2.PushBack(Vector2(2.0f, 3.0f));
+  otherArray2.PushBack(1);
+
+  tet_printf("Check array with difference order return difference hash.\n");
+  DALI_TEST_NOT_EQUALS(originalHash, otherArray2.GetHash(), Math::MACHINE_EPSILON_100, TEST_LOCATION);
+
+  array.Clear();
+  tet_printf("Check cleared arrayy has same value with empty array.\n");
+  DALI_TEST_EQUALS(emptyArrayHash, array.GetHash(), TEST_LOCATION);
+
+  END_TEST;
+}
+
+int UtcDaliPropertyArrayGetHashP02(void)
+{
+  tet_infoline("Check Property::Array::GetHash() if value is Map or Array.");
+
+  Property::Array array;
+  Property::Array subArray;
+  Property::Map   subMap;
+
+  subArray.PushBack(2);
+  subArray.PushBack(3);
+
+  subMap.Insert(0, "0");
+  subMap.Insert("1", 1);
+
+  array.PushBack(1);
+  array.PushBack(Vector2(2.0f, 3.0f));
+  array.PushBack(subArray);
+  array.PushBack(subMap);
+  array.PushBack(4);
+
+  auto originalHash = array.GetHash();
+
+  Property::Array* subArrayPtr = array.GetElementAt(2).GetArray();
+  Property::Map*   subMapPtr   = array.GetElementAt(3).GetMap();
+
+  DALI_TEST_CHECK(subArrayPtr);
+  DALI_TEST_CHECK(subMapPtr);
+
+  tet_printf("Check if l-value of some element changeness applied.\n");
+
+  subArrayPtr->PushBack(-2);
+  DALI_TEST_NOT_EQUALS(originalHash, array.GetHash(), Math::MACHINE_EPSILON_100, TEST_LOCATION);
+  subArrayPtr->Resize(2);
+  DALI_TEST_EQUALS(originalHash, array.GetHash(), TEST_LOCATION);
+
+  subMapPtr->Insert(2, 2);
+  subMapPtr->Insert("3", "3");
+  auto newHash = array.GetHash();
+  DALI_TEST_NOT_EQUALS(originalHash, newHash, Math::MACHINE_EPSILON_100, TEST_LOCATION);
+  subMapPtr->Remove(2);
+  DALI_TEST_NOT_EQUALS(originalHash, array.GetHash(), Math::MACHINE_EPSILON_100, TEST_LOCATION);
+  DALI_TEST_NOT_EQUALS(newHash, array.GetHash(), Math::MACHINE_EPSILON_100, TEST_LOCATION);
+  subMapPtr->Remove("3");
+  DALI_TEST_EQUALS(originalHash, array.GetHash(), TEST_LOCATION);
+
+  END_TEST;
+}
+
+int UtcDaliPropertyArrayEqualNonFloatType(void)
+{
+  tet_infoline("Check Property::Array equality if all values don't need to consider epsilon");
+
+  Property::Array array1;
+  Property::Array subArray1;
+  Property::Map   subMap1;
+
+  subArray1.PushBack(2);
+  subArray1.PushBack(3);
+
+  subMap1.Insert(0, "0");
+  subMap1.Insert("1", 1);
+
+  array1.PushBack(1);
+  array1.PushBack(false);
+  array1.PushBack(subArray1);
+  array1.PushBack(subMap1);
+  array1.PushBack(4);
+
+  tet_printf("Check self-equality return true\n");
+  DALI_TEST_CHECK(array1 == array1);
+  DALI_TEST_EQUALS(array1, array1, TEST_LOCATION);
+
+  tet_printf("Generate exactly same Property::Array with array1\n");
+
+  Property::Array array2;
+  Property::Array subArray2;
+  Property::Map   subMap2;
+
+  subArray2.PushBack(2);
+  subArray2.PushBack(3);
+
+  subMap2.Insert(0, "0");
+  subMap2.Insert("1", 1);
+
+  array2.PushBack(1);
+  array2.PushBack(false);
+  array2.PushBack(subArray1);
+  array2.PushBack(subMap1);
+  array2.PushBack(4);
+
+  DALI_TEST_CHECK(array1 == array2);
+  DALI_TEST_EQUALS(array1, array2, TEST_LOCATION);
+
+  tet_printf("Change array2\n");
+
+  auto oldCount = array2.Count();
+  array2.PushBack(2);
+  DALI_TEST_CHECK(array1 != array2);
+
+  tet_printf("Change array2 again\n");
+
+  array2.Resize(oldCount);
+  DALI_TEST_CHECK(array1 == array2);
+
+  tet_printf("Change array2\n");
+
+  Property::Value& value = array2.GetElementAt(1);
+  value                  = true;
+  DALI_TEST_CHECK(array1 != array2);
+
+  tet_printf("Change array2 again\n");
+
+  value = false;
+  DALI_TEST_CHECK(array1 == array2);
+
+  END_TEST;
+}
old mode 100755 (executable)
new mode 100644 (file)
index c992baf..11f179f
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2023 Samsung Electronics Co., Ltd.
+ * Copyright (c) 2024 Samsung Electronics Co., Ltd.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -92,31 +92,15 @@ int UtcDaliPropertyMapMoveConstructor(void)
   Property::Map map2(std::move(map1));
   DALI_TEST_EQUALS(3u, map2.Count(), TEST_LOCATION);
 
-  // Calling any methods on map1 will debug assert
+  // Calling some methods on map1 will debug assert
   const char* exceptionMessage = "Cannot use an object previously used as an r-value";
-  DALI_TEST_ASSERTION(map1.Count(), exceptionMessage);
-  DALI_TEST_ASSERTION(map1.Empty(), exceptionMessage);
-  DALI_TEST_ASSERTION(map1.Insert((const char*)"key", Property::Value()), exceptionMessage);
-  DALI_TEST_ASSERTION(map1.Insert(std::string("key"), Property::Value()), exceptionMessage);
-  DALI_TEST_ASSERTION(map1.Insert(0, Property::Value()), exceptionMessage);
   DALI_TEST_ASSERTION(map1.GetValue(0), exceptionMessage);
   DALI_TEST_ASSERTION(map1.GetKey(0), exceptionMessage);
   DALI_TEST_ASSERTION(map1.GetKeyAt(1), exceptionMessage);
   DALI_TEST_ASSERTION(map1.GetPair(0), exceptionMessage);
   DALI_TEST_ASSERTION(map1.GetKeyValue(0), exceptionMessage);
-  DALI_TEST_ASSERTION(map1.Find((const char*)"key"), exceptionMessage);
-  DALI_TEST_ASSERTION(map1.Find(std::string("key")), exceptionMessage);
-  DALI_TEST_ASSERTION(map1.Find(0), exceptionMessage);
-  DALI_TEST_ASSERTION(map1.Find(0, "key"), exceptionMessage);
-  DALI_TEST_ASSERTION(map1.Find("key", Property::INTEGER), exceptionMessage);
-  DALI_TEST_ASSERTION(map1.Find(0, Property::INTEGER), exceptionMessage);
-  DALI_TEST_ASSERTION(map1.Clear(), exceptionMessage);
-  DALI_TEST_ASSERTION(map1.Merge(Property::Map()), exceptionMessage);
-  DALI_TEST_ASSERTION(map1["key"], exceptionMessage);
   DALI_TEST_ASSERTION(const_cast<const Property::Map&>(map1)["key"], exceptionMessage);
-  DALI_TEST_ASSERTION(map1[0], exceptionMessage);
   DALI_TEST_ASSERTION(const_cast<const Property::Map&>(map1)[0], exceptionMessage);
-  DALI_TEST_ASSERTION(Property::Map temp; map1 = temp, exceptionMessage);
 
   END_TEST;
 }
@@ -136,47 +120,162 @@ int UtcDaliPropertyMapMoveAssignmentOperator(void)
   map2 = std::move(map1);
   DALI_TEST_EQUALS(3u, map2.Count(), TEST_LOCATION);
 
-  // Calling any methods on map1 will debug assert
+  // Calling some methods on map1 will debug assert
   const char* exceptionMessage = "Cannot use an object previously used as an r-value";
-  DALI_TEST_ASSERTION(map1.Count(), exceptionMessage);
-  DALI_TEST_ASSERTION(map1.Empty(), exceptionMessage);
-  DALI_TEST_ASSERTION(map1.Insert((const char*)"key", Property::Value()), exceptionMessage);
-  DALI_TEST_ASSERTION(map1.Insert(std::string("key"), Property::Value()), exceptionMessage);
-  DALI_TEST_ASSERTION(map1.Insert(0, Property::Value()), exceptionMessage);
   DALI_TEST_ASSERTION(map1.GetValue(0), exceptionMessage);
   DALI_TEST_ASSERTION(map1.GetKey(0), exceptionMessage);
   DALI_TEST_ASSERTION(map1.GetKeyAt(1), exceptionMessage);
   DALI_TEST_ASSERTION(map1.GetPair(0), exceptionMessage);
   DALI_TEST_ASSERTION(map1.GetKeyValue(0), exceptionMessage);
-  DALI_TEST_ASSERTION(map1.Find((const char*)"key"), exceptionMessage);
-  DALI_TEST_ASSERTION(map1.Find(std::string("key")), exceptionMessage);
-  DALI_TEST_ASSERTION(map1.Find(0), exceptionMessage);
-  DALI_TEST_ASSERTION(map1.Find(0, "key"), exceptionMessage);
-  DALI_TEST_ASSERTION(map1.Find("key", Property::INTEGER), exceptionMessage);
-  DALI_TEST_ASSERTION(map1.Find(0, Property::INTEGER), exceptionMessage);
-  DALI_TEST_ASSERTION(map1.Clear(), exceptionMessage);
-  DALI_TEST_ASSERTION(map1.Merge(Property::Map()), exceptionMessage);
-  DALI_TEST_ASSERTION(map1["key"], exceptionMessage);
   DALI_TEST_ASSERTION(const_cast<const Property::Map&>(map1)["key"], exceptionMessage);
-  DALI_TEST_ASSERTION(map1[0], exceptionMessage);
   DALI_TEST_ASSERTION(const_cast<const Property::Map&>(map1)[0], exceptionMessage);
-  DALI_TEST_ASSERTION(Property::Map temp; map1 = temp, exceptionMessage);
 
   // Self std::move assignment make compile warning over gcc-13. Let we ignore the warning.
-#if (__GNUC__ >= 13)
+#if(__GNUC__ >= 13)
 #pragma GCC diagnostic push
 #pragma GCC diagnostic ignored "-Wself-move"
 #endif
   // Self assignment
   map2 = std::move(map2);
   DALI_TEST_EQUALS(3u, map2.Count(), TEST_LOCATION); // No debug assert as nothing should happen
-#if (__GNUC__ >= 13)
+#if(__GNUC__ >= 13)
 #pragma GCC diagnostic pop
 #endif
 
   END_TEST;
 }
 
+int UtcDaliPropertyMapMovedMapP1(void)
+{
+  Property::Map map1;
+  map1["hello"] = 1;
+  map1["world"] = 2;
+  map1[10]      = "DALi";
+  DALI_TEST_EQUALS(3u, map1.Count(), TEST_LOCATION);
+
+  Property::Map map2;
+  map2[10] = "DALi again";
+  DALI_TEST_EQUALS(1u, map2.Count(), TEST_LOCATION);
+
+  map2 = std::move(map1);
+  DALI_TEST_EQUALS(3u, map2.Count(), TEST_LOCATION);
+
+  // Calling some methods on map1 will debug assert
+  const char* exceptionMessage = "Cannot use an object previously used as an r-value";
+  DALI_TEST_ASSERTION(map1.GetValue(0), exceptionMessage);
+  DALI_TEST_ASSERTION(map1.GetKey(0), exceptionMessage);
+  DALI_TEST_ASSERTION(map1.GetKeyAt(1), exceptionMessage);
+  DALI_TEST_ASSERTION(map1.GetPair(0), exceptionMessage);
+  DALI_TEST_ASSERTION(map1.GetKeyValue(0), exceptionMessage);
+  DALI_TEST_ASSERTION(const_cast<const Property::Map&>(map1)["key"], exceptionMessage);
+  DALI_TEST_ASSERTION(const_cast<const Property::Map&>(map1)[0], exceptionMessage);
+
+  // Call some API to moved map
+  Property::Map emptyMap;
+  DALI_TEST_EQUALS(emptyMap.GetHash(), map1.GetHash(), TEST_LOCATION);
+  DALI_TEST_EQUALS(0u, map1.Count(), TEST_LOCATION);
+  DALI_TEST_EQUALS(true, map1.Empty(), TEST_LOCATION);
+  map1.Clear();
+
+  DALI_TEST_EQUALS(false, map1.Remove(10), TEST_LOCATION);
+  DALI_TEST_EQUALS(false, map1.Remove("hello"), TEST_LOCATION);
+
+  Property::Value* nullValuePtr = nullptr;
+  DALI_TEST_EQUALS(nullValuePtr, map1.Find(10), TEST_LOCATION);
+  DALI_TEST_EQUALS(nullValuePtr, map1.Find("hello"), TEST_LOCATION);
+  DALI_TEST_EQUALS(nullValuePtr, map1.Find(10, "hello"), TEST_LOCATION);
+  DALI_TEST_EQUALS(nullValuePtr, map1.Find(10, Property::Type::FLOAT), TEST_LOCATION);
+  DALI_TEST_EQUALS(nullValuePtr, map1.Find("hello", Property::Type::FLOAT), TEST_LOCATION);
+
+  // Test Insert
+  map1["hello"] = 1;
+  map1.Insert("world", 2);
+  map1[10] = "DALi";
+  DALI_TEST_EQUALS(3u, map1.Count(), TEST_LOCATION);
+  DALI_TEST_EQUALS("DALi", map1.Find(10)->Get<std::string>(), TEST_LOCATION);
+  DALI_TEST_EQUALS(1, map1.Find("hello")->Get<int32_t>(), TEST_LOCATION);
+
+  map2 = std::move(map1);
+  DALI_TEST_EQUALS(0u, map1.Count(), TEST_LOCATION);
+
+  // Test Insert 2 (Index key first)
+  map1[10]      = "DALi";
+  map1["hello"] = 1;
+  map1.Insert("world", 2);
+  DALI_TEST_EQUALS(3u, map1.Count(), TEST_LOCATION);
+  DALI_TEST_EQUALS("DALi", map1.Find(10)->Get<std::string>(), TEST_LOCATION);
+  DALI_TEST_EQUALS(1, map1.Find("hello")->Get<int32_t>(), TEST_LOCATION);
+
+  map2 = std::move(map1);
+  DALI_TEST_EQUALS(0u, map1.Count(), TEST_LOCATION);
+
+  // Test Insert 3 (Insert key first)
+  map1.Insert(4, 2);
+  map1[10]      = "DALi";
+  map1["hello"] = 1;
+  DALI_TEST_EQUALS(3u, map1.Count(), TEST_LOCATION);
+  DALI_TEST_EQUALS("DALi", map1.Find(10)->Get<std::string>(), TEST_LOCATION);
+  DALI_TEST_EQUALS(1, map1.Find("hello")->Get<int32_t>(), TEST_LOCATION);
+
+  map2 = std::move(map1);
+  DALI_TEST_EQUALS(0u, map1.Count(), TEST_LOCATION);
+
+  // Test Insert 4 (Insert string first)
+  map1.Insert("world", 2);
+  map1[10]      = "DALi";
+  map1["hello"] = 1;
+  DALI_TEST_EQUALS(3u, map1.Count(), TEST_LOCATION);
+  DALI_TEST_EQUALS("DALi", map1.Find(10)->Get<std::string>(), TEST_LOCATION);
+  DALI_TEST_EQUALS(1, map1.Find("hello")->Get<int32_t>(), TEST_LOCATION);
+
+  map2 = std::move(map1);
+  DALI_TEST_EQUALS(0u, map1.Count(), TEST_LOCATION);
+
+  // Test Merge
+  map1.Merge(map2);
+  DALI_TEST_EQUALS(3u, map1.Count(), TEST_LOCATION);
+
+  map2 = std::move(map1);
+  DALI_TEST_EQUALS(0u, map1.Count(), TEST_LOCATION);
+
+  // Test copy operator
+  DALI_TEST_EQUALS(3u, map2.Count(), TEST_LOCATION);
+
+  map1 = map2;
+
+  DALI_TEST_EQUALS(3u, map1.Count(), TEST_LOCATION);
+
+  map2 = std::move(map1);
+  DALI_TEST_EQUALS(0u, map1.Count(), TEST_LOCATION);
+
+  // Test copy moved map
+  Property::Map map3 = map1;
+  DALI_TEST_EQUALS(0u, map3.Count(), TEST_LOCATION);
+
+  Property::Map map4;
+  map4 = map1;
+  DALI_TEST_EQUALS(0u, map4.Count(), TEST_LOCATION);
+
+  // Test move operator
+  DALI_TEST_EQUALS(3u, map2.Count(), TEST_LOCATION);
+
+  map1 = std::move(map2);
+
+  DALI_TEST_EQUALS(3u, map1.Count(), TEST_LOCATION);
+
+  // Test move moved map
+  DALI_TEST_EQUALS(0u, map2.Count(), TEST_LOCATION);
+  Property::Map map5 = std::move(map2);
+
+  DALI_TEST_EQUALS(0u, map5.Count(), TEST_LOCATION);
+
+  map3 = std::move(map2);
+
+  DALI_TEST_EQUALS(0u, map3.Count(), TEST_LOCATION);
+
+  END_TEST;
+}
+
 int UtcDaliPropertyMapConstOperator(void)
 {
   Property::Map map;
@@ -724,6 +823,21 @@ int UtcDaliPropertyMapOstream02(void)
   END_TEST;
 }
 
+int UtcDaliPropertyMapOstream03(void)
+{
+  std::ostringstream oss;
+
+  Property::Map map;
+  map.Insert(100, 9);
+
+  Property::Map map2(std::move(map));
+
+  oss << map;
+  DALI_TEST_EQUALS(oss.str().compare("Map(0) = {}"), 0, TEST_LOCATION);
+
+  END_TEST;
+}
+
 int UtcDaliPropertyKeyConstructorP(void)
 {
   Property::Key key1("aKey");
@@ -861,3 +975,195 @@ int UtcDaliPropertyMapNestedInitializerListConstructor(void)
 
   END_TEST;
 }
+
+int UtcDaliPropertyMapGetHashP01(void)
+{
+  tet_infoline("Check Property::Map::GetHash()");
+
+  Property::Map map;
+
+  tet_printf("Check empty map is not zero.\n");
+  auto emptyMapHash = map.GetHash();
+
+  DALI_TEST_NOT_EQUALS(emptyMapHash, static_cast<decltype(emptyMapHash)>(0u), Math::MACHINE_EPSILON_100, TEST_LOCATION);
+
+  map.Insert(1, 1);
+  map.Insert(2, Vector2(2.0f, 3.0f));
+  map.Insert("3", 4);
+
+  DALI_TEST_NOT_EQUALS(emptyMapHash, map.GetHash(), Math::MACHINE_EPSILON_100, TEST_LOCATION);
+
+  Property::Value* lValuePtr = map.Find(2);
+
+  auto originalHash = map.GetHash();
+
+  tet_printf("Check if l-value of some element changeness applied.\n");
+
+  DALI_TEST_EQUALS(originalHash, map.GetHash(), TEST_LOCATION);
+
+  (*lValuePtr) = Property::Value(Vector3(2.0f, 3.0f, 0.0f));
+  auto newHash = map.GetHash();
+
+  DALI_TEST_NOT_EQUALS(originalHash, newHash, Math::MACHINE_EPSILON_100, TEST_LOCATION);
+  DALI_TEST_EQUALS(map.Find(2)->Get<Vector3>(), Vector3(2.0f, 3.0f, 0.0f), TEST_LOCATION);
+
+  tet_printf("Revert l-value as original value, and check it applied.\n");
+  (*lValuePtr) = Property::Value(Vector2(2.0f, 3.0f));
+  newHash      = map.GetHash();
+
+  DALI_TEST_EQUALS(originalHash, newHash, TEST_LOCATION);
+
+  Property::Map otherMap;
+
+  otherMap.Insert(1, 1);
+  otherMap.Insert(2, Vector2(2.0f, 3.0f));
+  otherMap.Insert("3", 4);
+
+  tet_printf("Check same map return same hash.\n");
+  DALI_TEST_EQUALS(originalHash, otherMap.GetHash(), TEST_LOCATION);
+
+  Property::Value value = otherMap[44]; ///< Dummy action to create empty element.
+
+  tet_printf("Check map with difference size return difference hash.\n");
+  DALI_TEST_NOT_EQUALS(originalHash, otherMap.GetHash(), Math::MACHINE_EPSILON_100, TEST_LOCATION);
+
+  otherMap[44] = Property::Value("44");
+  DALI_TEST_NOT_EQUALS(originalHash, otherMap.GetHash(), Math::MACHINE_EPSILON_100, TEST_LOCATION);
+
+  otherMap.Remove(44);
+
+  tet_printf("Check removed map return same hash.\n");
+  DALI_TEST_EQUALS(originalHash, otherMap.GetHash(), TEST_LOCATION);
+
+  Property::Map otherMap2;
+
+  otherMap2.Insert(2, Vector2(2.0f, 3.0f));
+  otherMap2.Insert(1, 1);
+  otherMap2.Insert("3", 4);
+
+  tet_printf("Check map with difference order return same hash.\n");
+  DALI_TEST_EQUALS(originalHash, otherMap2.GetHash(), TEST_LOCATION);
+
+  map.Clear();
+  tet_printf("Check cleared map has same value with empty map.\n");
+  DALI_TEST_EQUALS(emptyMapHash, map.GetHash(), TEST_LOCATION);
+
+  END_TEST;
+}
+
+int UtcDaliPropertyMapGetHashP02(void)
+{
+  tet_infoline("Check Property::Map::GetHash() if value is Map or Array.");
+
+  Property::Map   map;
+  Property::Array subArray;
+  Property::Map   subMap;
+
+  subArray.PushBack(2);
+  subArray.PushBack(3);
+
+  subMap.Insert(0, "0");
+  subMap.Insert("1", 1);
+
+  map.Insert(1, 1);
+  map.Insert(2, Vector2(2.0f, 3.0f));
+  map.Insert(3, subArray);
+  map.Insert("4", subMap);
+  map.Insert("5", 4);
+
+  auto originalHash = map.GetHash();
+
+  Property::Array* subArrayPtr = map.Find(3)->GetArray();
+  Property::Map*   subMapPtr   = map.Find("4")->GetMap();
+
+  DALI_TEST_CHECK(subArrayPtr);
+  DALI_TEST_CHECK(subMapPtr);
+
+  tet_printf("Check if l-value of some element changeness applied.\n");
+
+  subArrayPtr->PushBack(-2);
+  DALI_TEST_NOT_EQUALS(originalHash, map.GetHash(), Math::MACHINE_EPSILON_100, TEST_LOCATION);
+  subArrayPtr->Resize(2);
+  DALI_TEST_EQUALS(originalHash, map.GetHash(), TEST_LOCATION);
+
+  subMapPtr->Insert(2, 2);
+  subMapPtr->Insert("3", "3");
+  auto newHash = map.GetHash();
+  DALI_TEST_NOT_EQUALS(originalHash, newHash, Math::MACHINE_EPSILON_100, TEST_LOCATION);
+  subMapPtr->Remove(2);
+  DALI_TEST_NOT_EQUALS(originalHash, map.GetHash(), Math::MACHINE_EPSILON_100, TEST_LOCATION);
+  DALI_TEST_NOT_EQUALS(newHash, map.GetHash(), Math::MACHINE_EPSILON_100, TEST_LOCATION);
+  subMapPtr->Remove("3");
+  DALI_TEST_EQUALS(originalHash, map.GetHash(), TEST_LOCATION);
+
+  END_TEST;
+}
+
+int UtcDaliPropertyMapEqualNonFloatType(void)
+{
+  tet_infoline("Check Property::Map equality if all values don't need to consider epsilon");
+
+  Property::Map   map1;
+  Property::Array subArray1;
+  Property::Map   subMap1;
+
+  subArray1.PushBack(2);
+  subArray1.PushBack(3);
+
+  subMap1.Insert(0, "0");
+  subMap1.Insert("1", 1);
+
+  map1.Insert(1, 1);
+  map1.Insert(2, false);
+  map1.Insert(3, subArray1);
+  map1.Insert("4", subMap1);
+  map1.Insert("5", 4);
+
+  tet_printf("Check self-equality return true\n");
+  DALI_TEST_CHECK(map1 == map1);
+  DALI_TEST_EQUALS(map1, map1, TEST_LOCATION);
+
+  tet_printf("Generate exactly same Property::Map with map1\n");
+
+  Property::Map   map2;
+  Property::Array subArray2;
+  Property::Map   subMap2;
+
+  subArray2.PushBack(2);
+  subArray2.PushBack(3);
+
+  subMap2.Insert("1", 1);
+  subMap2.Insert(0, "0");
+
+  map2.Insert(3, subArray1);
+  map2.Insert(2, false);
+  map2.Insert(1, 1);
+  map2.Insert("5", 4);
+  map2.Insert("4", subMap1);
+
+  DALI_TEST_CHECK(map1 == map2);
+  DALI_TEST_EQUALS(map1, map2, TEST_LOCATION);
+
+  tet_printf("Change map2\n");
+
+  map2.Insert(999, "999");
+  DALI_TEST_CHECK(map1 != map2);
+
+  tet_printf("Change map2 again\n");
+
+  map2.Remove(999);
+  DALI_TEST_CHECK(map1 == map2);
+
+  tet_printf("Change map2\n");
+
+  Property::Value* valuePtr = map2.Find(2);
+  *valuePtr                 = true;
+  DALI_TEST_CHECK(map1 != map2);
+
+  tet_printf("Change map2 again\n");
+
+  *valuePtr = false;
+  DALI_TEST_CHECK(map1 == map2);
+
+  END_TEST;
+}
index 75401d6b73d2ed6be712027c4cd3caeee01b0bf5..32817b5a68cb706e3608ef5109019774f2e2d14f 100644 (file)
@@ -560,7 +560,7 @@ int UtcDaliPropertyValueMoveConstructorArray(void)
   DALI_TEST_EQUALS(3u, array.Size(), TEST_LOCATION);
 
   Property::Value value(std::move(array));
-  DALI_TEST_ASSERTION(array.Size(), "Cannot use an object previously used as an r-value"); // Our local variable should become invalid
+  DALI_TEST_EQUALS(0u, array.Size(), TEST_LOCATION);
 
   Property::Array* arrayPtr = value.GetArray();
   DALI_TEST_CHECK(arrayPtr);
@@ -578,7 +578,7 @@ int UtcDaliPropertyValueMoveConstructorMap(void)
   DALI_TEST_EQUALS(3u, map.Count(), TEST_LOCATION);
 
   Property::Value value(std::move(map));
-  DALI_TEST_ASSERTION(map.Count(), "Cannot use an object previously used as an r-value"); // Our local variable should become invalid
+  DALI_TEST_EQUALS(0u, map.Count(), TEST_LOCATION);
 
   Property::Map* mapPtr = value.GetMap();
   DALI_TEST_CHECK(mapPtr);
@@ -1413,10 +1413,16 @@ int UtcDaliPropertyValueEqualArrayType(void)
   DALI_TEST_NOT_EQUALS(value4, value6, Math::MACHINE_EPSILON_100, TEST_LOCATION);
   DALI_TEST_NOT_EQUALS(value5, value6, Math::MACHINE_EPSILON_100, TEST_LOCATION);
 
-  // TODO : Currently array comparision not implemented.
-  // If we impelemnt array comparision, replace below line.
-  // DALI_TEST_EQUALS(value1, value2, TEST_LOCATION);
-  DALI_TEST_NOT_EQUALS(value1, value2, Math::MACHINE_EPSILON_100, TEST_LOCATION);
+  // TODO : Currently array comparision with epsilon was not implemented.
+  // For safety, let we compare only if hash value is same
+  if(value1.GetHash() == value2.GetHash())
+  {
+    DALI_TEST_EQUALS(value1, value2, TEST_LOCATION);
+  }
+  else
+  {
+    tet_printf("Hash value not mathed. But it should return true exceptually. We need to implement it\n");
+  }
 
   END_TEST;
 }
@@ -1479,10 +1485,16 @@ int UtcDaliPropertyValueEqualMapType(void)
   DALI_TEST_NOT_EQUALS(value4, value6, Math::MACHINE_EPSILON_100, TEST_LOCATION);
   DALI_TEST_NOT_EQUALS(value5, value6, Math::MACHINE_EPSILON_100, TEST_LOCATION);
 
-  // TODO : Currently map comparision not implemented.
-  // If we impelemnt map comparision, replace below line.
-  // DALI_TEST_EQUALS(value1, value2, TEST_LOCATION);
-  DALI_TEST_NOT_EQUALS(value1, value2, Math::MACHINE_EPSILON_100, TEST_LOCATION);
+  // TODO : Currently map comparision with epsilon was not implemented.
+  // For safety, let we compare only if hash value is same
+  if(value1.GetHash() == value2.GetHash())
+  {
+    DALI_TEST_EQUALS(value1, value2, TEST_LOCATION);
+  }
+  else
+  {
+    tet_printf("Hash value not mathed. But it should return true exceptually. We need to implement it\n");
+  }
 
   END_TEST;
 }
@@ -1758,3 +1770,150 @@ int UtcDaliPropertyValueOutputStream(void)
 
   END_TEST;
 }
+
+int UtcDaliPropertyValueGetHashP01(void)
+{
+  tet_infoline("Check Property::Value::GetHash() type equality.");
+
+  const float     a[] = {1.0f, 2.0f, 3.0f, 4.0f, 5.0f, 6.0f, 7.0f, 8.0f, 9.0f, 10.0f, 11.0f, 12.0f, 13.0f, 14.0f, 15.0f, 16.0f};
+  const float     b[] = {16.0f, 2.0f, 3.0f, 4.0f, 5.0f, 6.0f, 7.0f, 8.0f, 9.0f, 10.0f, 11.0f, 12.0f, 13.0f, 14.0f, 15.0f, 1.0f};
+  Property::Value valueList[] =
+    {
+      Property::Value(static_cast<bool>(true)),
+      Property::Value(static_cast<float>(7.0f)),
+      Property::Value(static_cast<int32_t>(32)),
+      Property::Value(Vector2(1.0f, 2.0f)),
+      Property::Value(Vector3(1.1f, 2.2f, 3.3f)),
+      Property::Value(Vector4(1.2f, 2.1f, 3.4f, 4.3f)),
+      Property::Value(Matrix3(1.0f, 2.0f, 3.0f, 4.0f, 5.0f, 6.0f, 7.0f, 8.0f, 9.0f)),
+      Property::Value(Matrix(a)),
+      Property::Value(Rect<int32_t>(3, 2, 5, 4)),
+      Property::Value(AngleAxis(Radian(2.0f), Vector3(0.0f, 1.0f, 0.0f))),
+      Property::Value(std::string("Hello, World!")),
+      Property::Value(Extents(8, 4, 2, 5)),
+      Property::Value(Property::Array({1.0f, Vector2(2.0f, 3.0f), static_cast<int32_t>(4), std::string("Five"), Matrix(a)})),
+      Property::Value(Property::Map({{1, 1.0f}, {"2", Vector4(2.0f, 3.0f, 4.0f, 5.0f)}, {3, static_cast<int32_t>(6)}, {4, std::string("Lucky")}})),
+    };
+  Property::Value otherValueList[] =
+    {
+      Property::Value(static_cast<bool>(false)),
+      Property::Value(static_cast<float>(1.0f)),
+      Property::Value(static_cast<int32_t>(4)),
+      Property::Value(Vector2(2.0f, 1.0f)),
+      Property::Value(Vector3(2.2f, 1.1f, 3.3f)),
+      Property::Value(Vector4(2.1f, 1.2f, 3.4f, 4.3f)),
+      Property::Value(Matrix3(7.0f, 2.0f, 3.0f, 4.0f, 5.0f, 6.0f, 7.0f, 8.0f, 9.0f)),
+      Property::Value(Matrix(b)),
+      Property::Value(Rect<int32_t>(2, 3, 4, 5)),
+      Property::Value(AngleAxis(Radian(20.0f), Vector3(0.0f, 1.0f, 0.0f))),
+      Property::Value(std::string("Hell, o, World!")),
+      Property::Value(Extents(4, 8, 5, 2)),
+      Property::Value(Property::Array({5.0f, Vector2(4.0f, 3.0f), static_cast<int32_t>(2), std::string("ONE"), Matrix(b)})),
+      Property::Value(Property::Map({{"1", 5.0f}, {2, Vector4(4.0f, 3.0f, 2.0f, 1.0f)}, {"three", static_cast<int32_t>(0)}, {1, std::string("-1")}})),
+    };
+  const int valueCount = sizeof(valueList) / sizeof(valueList[0]);
+
+  // Compare Value and HashValue
+  for(int i = 0; i < valueCount; ++i)
+  {
+    // Check self comparision
+    DALI_TEST_EQUALS(valueList[i].GetHash(), valueList[i].GetHash(), TEST_LOCATION);
+    // Check same value comparision
+    Property::Value copiedValue = valueList[i];
+    DALI_TEST_EQUALS(valueList[i].GetHash(), copiedValue.GetHash(), TEST_LOCATION);
+    // Check not equal value comparision
+    DALI_TEST_NOT_EQUALS(valueList[i].GetHash(), otherValueList[i].GetHash(), Math::MACHINE_EPSILON_100, TEST_LOCATION);
+    // Check empty type value
+    DALI_TEST_NOT_EQUALS(valueList[i].GetHash(), Property::Value().GetHash(), Math::MACHINE_EPSILON_100, TEST_LOCATION);
+  }
+
+  // Compare with empty type.
+  auto emptyValueHash = Property::Value().GetHash();
+
+  DALI_TEST_EQUALS(Property::Value().GetHash(), emptyValueHash, TEST_LOCATION);
+  DALI_TEST_NOT_EQUALS(Property::Value().GetHash(), static_cast<decltype(emptyValueHash)>(0u), Math::MACHINE_EPSILON_100, TEST_LOCATION);
+
+  END_TEST;
+}
+
+int UtcDaliPropertyValueGetHashP02(void)
+{
+  tet_infoline("Check Property::Value::GetHash() equality for Property::Array.");
+
+  const float     a[]   = {1.0f, 2.0f, 3.0f, 4.0f, 5.0f, 6.0f, 7.0f, 8.0f, 9.0f, 10.0f, 11.0f, 12.0f, 13.0f, 14.0f, 15.0f, 16.0f};
+  Property::Value value = Property::Value(Property::Array({1.0f, Vector2(2.0f, 3.0f), static_cast<int32_t>(4), std::string("Five"), Matrix(a)}));
+
+  const auto originHash = value.GetHash();
+
+  Property::Array* arrayPtr = value.GetArray();
+
+  tet_printf("Check value's hash is not equal with array itself's hash.\n");
+  DALI_TEST_CHECK(arrayPtr);
+  DALI_TEST_NOT_EQUALS(originHash, arrayPtr->GetHash(), Math::MACHINE_EPSILON_100, TEST_LOCATION);
+
+  Property::Array copiedArray = *arrayPtr;
+  Property::Value copiedValue = Property::Value(copiedArray);
+
+  // Compare with copied array.
+  DALI_TEST_EQUALS(originHash, copiedValue.GetHash(), TEST_LOCATION);
+
+  arrayPtr->PushBack(Property::Value(6.0f));
+
+  const auto newHash = value.GetHash();
+
+  DALI_TEST_NOT_EQUALS(originHash, newHash, Math::MACHINE_EPSILON_100, TEST_LOCATION);
+
+  // Add same property value to copied array
+  copiedArray.PushBack(Property::Value(6.0f));
+  Property::Value copiedValue2 = Property::Value(copiedArray);
+
+  // Compare with copied array.
+  DALI_TEST_NOT_EQUALS(copiedValue.GetHash(), copiedValue2.GetHash(), Math::MACHINE_EPSILON_100, TEST_LOCATION);
+  DALI_TEST_EQUALS(newHash, copiedValue2.GetHash(), TEST_LOCATION);
+
+  END_TEST;
+}
+
+int UtcDaliPropertyValueGetHashP03(void)
+{
+  tet_infoline("Check Property::Value::GetHash() equality for Property::Map.");
+
+  const float     a[]   = {1.0f, 2.0f, 3.0f, 4.0f, 5.0f, 6.0f, 7.0f, 8.0f, 9.0f, 10.0f, 11.0f, 12.0f, 13.0f, 14.0f, 15.0f, 16.0f};
+  Property::Value value = Property::Value(Property::Map({{0, 1.0f}, {1, Vector2(2.0f, 3.0f)}, {"2", static_cast<int32_t>(4)}, {"3", std::string("Five")}, {4, Matrix(a)}}));
+
+  const auto originHash = value.GetHash();
+
+  Property::Map* mapPtr = value.GetMap();
+
+  tet_printf("Check value's hash is not equal with map itself's hash.\n");
+  DALI_TEST_CHECK(mapPtr);
+  DALI_TEST_NOT_EQUALS(originHash, mapPtr->GetHash(), Math::MACHINE_EPSILON_100, TEST_LOCATION);
+
+  Property::Map   copiedMap   = *mapPtr;
+  Property::Value copiedValue = Property::Value(copiedMap);
+
+  // Compare with copied map.
+  DALI_TEST_EQUALS(originHash, copiedValue.GetHash(), TEST_LOCATION);
+
+  mapPtr->Insert(5, Property::Value(5));
+  mapPtr->Insert(6, Property::Value(std::string("6")));
+  mapPtr->Insert("7", Property::Value(7));
+  mapPtr->Insert("8", Property::Value(std::string("8")));
+
+  const auto newHash = value.GetHash();
+
+  DALI_TEST_NOT_EQUALS(originHash, newHash, Math::MACHINE_EPSILON_100, TEST_LOCATION);
+
+  // Add same property value to copied map, seperated order
+  copiedMap.Insert(6, Property::Value(std::string("6")));
+  copiedMap.Insert("8", Property::Value(std::string("8")));
+  copiedMap.Insert(5, Property::Value(5));
+  copiedMap.Insert("7", Property::Value(7));
+  Property::Value copiedValue2 = Property::Value(copiedMap);
+
+  // Compare with copied array.
+  DALI_TEST_NOT_EQUALS(copiedValue.GetHash(), copiedValue2.GetHash(), Math::MACHINE_EPSILON_100, TEST_LOCATION);
+  DALI_TEST_EQUALS(newHash, copiedValue2.GetHash(), TEST_LOCATION);
+
+  END_TEST;
+}
index 38ebd5a6ee6a99e7528275de45e0b2ecacf64abd..bef6e9b52e5179ccaeafcdec55746e807f6d7d3a 100644 (file)
 // CLASS HEADER
 #include <dali/devel-api/common/hash.h>
 
-namespace Dali
-{
-namespace //unnamed namespace
-{
-/*
- * djb2 (http://www.cse.yorku.ca/~oz/hash.html)
- */
-
-inline void HashString(const char* string, std::size_t& hash)
-{
-  while(int c = *string++)
-  {
-    hash = hash * 33 + c;
-  }
-}
-
-inline void HashString(const char* string, std::size_t& hash, char terminator)
-{
-  char c;
-  while((c = *string++) && c != terminator)
-  {
-    hash = hash * 33 + c;
-  }
-}
-
-inline void HashStringView(const std::string_view& string, std::size_t& hash)
-{
-  for(auto c : string)
-  {
-    hash = hash * 33 + c;
-  }
-}
-
-inline void HashStringView(const std::string_view& string, std::size_t& hash, char terminator)
-{
-  for(auto c : string)
-  {
-    if(c == terminator)
-    {
-      break;
-    }
-    hash = hash * 33 + static_cast<std::size_t>(c);
-  }
-}
-
-inline void HashBuffer(const std::vector<std::uint8_t>& buffer, std::size_t& hash)
-{
-  for(const auto& c : buffer)
-  {
-    hash = hash * 33 + c;
-  }
-}
+// INTERNAL INCLUDES
+#include <dali/internal/common/hash-utils.h>
 
-inline void HashBuffer(const Dali::Vector<std::uint8_t>& buffer, std::size_t& hash)
+namespace Dali
 {
-  for(const auto& c : buffer)
-  {
-    hash = hash * 33 + c;
-  }
-}
-
-} // unnamed namespace
-
 std::size_t CalculateHash(const std::string& toHash)
 {
-  std::size_t hash(INITIAL_HASH_VALUE);
+  std::size_t hash(Dali::Internal::HashUtils::INITIAL_HASH_VALUE);
 
-  HashString(toHash.c_str(), hash);
+  Dali::Internal::HashUtils::HashString(toHash.c_str(), hash);
 
   return hash;
 }
 
 std::size_t CalculateHash(const std::string& toHash, char terminator)
 {
-  std::size_t hash(INITIAL_HASH_VALUE);
+  std::size_t hash(Dali::Internal::HashUtils::INITIAL_HASH_VALUE);
 
-  HashString(toHash.c_str(), hash, terminator);
+  Dali::Internal::HashUtils::HashString(toHash.c_str(), hash, terminator);
 
   return hash;
 }
 
 std::size_t CalculateHash(const std::string& string1, const std::string& string2)
 {
-  std::size_t hash(INITIAL_HASH_VALUE);
+  std::size_t hash(Dali::Internal::HashUtils::INITIAL_HASH_VALUE);
 
-  HashString(string1.c_str(), hash);
-  HashString(string2.c_str(), hash);
+  Dali::Internal::HashUtils::HashString(string1.c_str(), hash);
+  Dali::Internal::HashUtils::HashString(string2.c_str(), hash);
 
   return hash;
 }
 
 std::size_t CalculateHash(const std::string_view& toHash)
 {
-  std::size_t hash(INITIAL_HASH_VALUE);
+  std::size_t hash(Dali::Internal::HashUtils::INITIAL_HASH_VALUE);
 
-  HashStringView(toHash, hash);
+  Dali::Internal::HashUtils::HashStringView(toHash, hash);
 
   return hash;
 }
 
 std::size_t CalculateHash(const std::string_view& toHash, char terminator)
 {
-  std::size_t hash(INITIAL_HASH_VALUE);
+  std::size_t hash(Dali::Internal::HashUtils::INITIAL_HASH_VALUE);
 
-  HashStringView(toHash, hash, terminator);
+  Dali::Internal::HashUtils::HashStringView(toHash, hash, terminator);
 
   return hash;
 }
 
 std::size_t CalculateHash(const std::string_view& string1, const std::string_view& string2)
 {
-  std::size_t hash(INITIAL_HASH_VALUE);
+  std::size_t hash(Dali::Internal::HashUtils::INITIAL_HASH_VALUE);
 
-  HashStringView(string1, hash);
-  HashStringView(string2, hash);
+  Dali::Internal::HashUtils::HashStringView(string1, hash);
+  Dali::Internal::HashUtils::HashStringView(string2, hash);
 
   return hash;
 }
 
 std::size_t CalculateHash(const std::vector<std::uint8_t>& toHash)
 {
-  std::size_t hash(INITIAL_HASH_VALUE);
+  std::size_t hash(Dali::Internal::HashUtils::INITIAL_HASH_VALUE);
 
-  HashBuffer(toHash, hash);
+  Dali::Internal::HashUtils::HashBuffer(toHash, hash);
 
   return hash;
 }
 
 std::size_t CalculateHash(const Dali::Vector<std::uint8_t>& toHash)
 {
-  std::size_t hash(INITIAL_HASH_VALUE);
+  std::size_t hash(Dali::Internal::HashUtils::INITIAL_HASH_VALUE);
 
-  HashBuffer(toHash, hash);
+  Dali::Internal::HashUtils::HashBuffer(toHash, hash);
 
   return hash;
 }
index fedd7a088cabde6e96a34d818af04a8da014dca5..83cc036f76936f1ecfb211512afa82b9491e1df0 100644 (file)
 // EXTERNAL INCLUDES
 #include <string>
 #include <string_view>
-#include <vector>
 
 //INTERNAL INCLUDES
 #include <dali/public-api/common/dali-common.h>
 #include <dali/public-api/common/dali-vector.h>
+#include <dali/public-api/common/vector-wrapper.h>
 
 namespace Dali
 {
-enum
-{
-  INITIAL_HASH_VALUE = 5381
-};
-
 /**
  * @brief Create a hash code for a string
  * @param toHash string to hash
diff --git a/dali/internal/common/hash-utils.h b/dali/internal/common/hash-utils.h
new file mode 100644 (file)
index 0000000..81639ec
--- /dev/null
@@ -0,0 +1,122 @@
+#ifndef DALI_INTERNAL_HASH_UTILS_H
+#define DALI_INTERNAL_HASH_UTILS_H
+
+/*
+ * Copyright (c) 2024 Samsung Electronics Co., Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+// EXTERNAL INCLUDES
+#include <string>
+#include <string_view>
+
+//INTERNAL INCLUDES
+#include <dali/public-api/common/dali-common.h>
+#include <dali/public-api/common/dali-vector.h>
+#include <dali/public-api/common/vector-wrapper.h>
+
+namespace Dali::Internal::HashUtils
+{
+constexpr std::size_t INITIAL_HASH_VALUE = 5381;
+
+/*
+ * djb2 (http://www.cse.yorku.ca/~oz/hash.html)
+ */
+
+[[maybe_unused]] inline std::size_t HashString(const char* string, std::size_t& hash)
+{
+  while(int c = *string++)
+  {
+    hash = hash * 33 + c;
+  }
+  return hash;
+}
+
+[[maybe_unused]] inline std::size_t HashString(const char* string, std::size_t& hash, char terminator)
+{
+  char c;
+  while((c = *string++) && c != terminator)
+  {
+    hash = hash * 33 + c;
+  }
+  return hash;
+}
+
+[[maybe_unused]] inline std::size_t HashStringView(const std::string_view& string, std::size_t& hash)
+{
+  for(auto c : string)
+  {
+    hash = hash * 33 + c;
+  }
+  return hash;
+}
+
+[[maybe_unused]] inline std::size_t HashStringView(const std::string_view& string, std::size_t& hash, char terminator)
+{
+  for(auto c : string)
+  {
+    if(c == terminator)
+    {
+      break;
+    }
+    hash = hash * 33 + static_cast<std::size_t>(c);
+  }
+  return hash;
+}
+
+[[maybe_unused]] inline std::size_t HashBuffer(const std::vector<std::uint8_t>& buffer, std::size_t& hash)
+{
+  for(const auto& c : buffer)
+  {
+    hash = hash * 33 + c;
+  }
+  return hash;
+}
+
+[[maybe_unused]] inline std::size_t HashBuffer(const Dali::Vector<std::uint8_t>& buffer, std::size_t& hash)
+{
+  for(const auto& c : buffer)
+  {
+    hash = hash * 33 + c;
+  }
+  return hash;
+}
+
+template<typename T>
+[[maybe_unused]] inline std::size_t HashRawBuffer(const T* buffer, std::size_t bufferSize, std::size_t& hash)
+{
+  if constexpr(sizeof(T) == 1u)
+  {
+    while(bufferSize--)
+    {
+      hash = hash * 33 + *(buffer++);
+    }
+    return hash;
+  }
+  else
+  {
+    return HashRawBuffer(reinterpret_cast<const std::uint8_t*>(buffer), sizeof(T) * bufferSize, hash);
+  }
+}
+
+template<typename T>
+[[maybe_unused]] inline std::size_t HashRawValue(const T& value, std::size_t& hash)
+{
+  return HashRawBuffer(reinterpret_cast<const std::uint8_t*>(&value), sizeof(T), hash);
+}
+
+} // namespace Dali::Internal::HashUtils
+
+#endif // DALI_INTERNAL_HASH_UTILS_H
index ab2ee093944068ce9850e27448d6960c0557a2da..a66f8c0f864090a73c70e1904a3a2ae66db37952 100644 (file)
@@ -2,7 +2,7 @@
 #define DALI_INTERNAL_PROPERTY_INPUT_IMPL_H
 
 /*
- * Copyright (c) 2023 Samsung Electronics Co., Ltd.
+ * Copyright (c) 2024 Samsung Electronics Co., Ltd.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -23,6 +23,7 @@
 
 // INTERNAL INCLUDES
 #include <dali/internal/common/buffer-index.h>
+#include <dali/internal/common/hash-utils.h>
 #include <dali/public-api/math/matrix.h>
 #include <dali/public-api/math/matrix3.h>
 #include <dali/public-api/math/quaternion.h>
 #include <dali/public-api/math/vector4.h>
 #include <dali/public-api/object/property-input.h>
 
-#if defined(ANDROID) || defined(WIN32) || defined(__APPLE__)
-namespace std
-{
-uint64_t _Hash_bytes(const void* bytes, uint64_t size, uint64_t seed);
-
-}
-#endif
-
 namespace Dali
 {
 namespace Internal
@@ -322,53 +315,53 @@ public:
     return false;
   }
 
-  std::uint64_t Hash(BufferIndex bufferIndex, uint64_t seed) const
+  std::size_t Hash(BufferIndex bufferIndex, std::size_t seed) const
   {
     switch(GetType())
     {
       case Property::BOOLEAN:
       {
-        return std::_Hash_bytes(&GetBoolean(bufferIndex), sizeof(bool), seed);
+        return Dali::Internal::HashUtils::HashRawValue(GetBoolean(bufferIndex), seed);
       }
 
       case Property::INTEGER:
       {
-        return std::_Hash_bytes(&GetInteger(bufferIndex), sizeof(int), seed);
+        return Dali::Internal::HashUtils::HashRawValue(GetInteger(bufferIndex), seed);
       }
 
       case Property::FLOAT:
       {
-        return std::_Hash_bytes(&GetFloat(bufferIndex), sizeof(float), seed);
+        return Dali::Internal::HashUtils::HashRawValue(GetFloat(bufferIndex), seed);
       }
 
       case Property::VECTOR2:
       {
-        return std::_Hash_bytes(&GetVector2(bufferIndex), sizeof(Vector2), seed);
+        return Dali::Internal::HashUtils::HashRawBuffer<float>(GetVector2(bufferIndex).AsFloat(), 2, seed);
       }
 
       case Property::VECTOR3:
       {
-        return std::_Hash_bytes(&GetVector3(bufferIndex), sizeof(Vector3), seed);
+        return Dali::Internal::HashUtils::HashRawBuffer<float>(GetVector3(bufferIndex).AsFloat(), 3, seed);
       }
 
       case Property::VECTOR4:
       {
-        return std::_Hash_bytes(&GetVector4(bufferIndex), sizeof(Vector4), seed);
+        return Dali::Internal::HashUtils::HashRawBuffer<float>(GetVector4(bufferIndex).AsFloat(), 4, seed);
       }
 
       case Property::ROTATION:
       {
-        return std::_Hash_bytes(&GetQuaternion(bufferIndex), sizeof(Quaternion), seed);
+        return Dali::Internal::HashUtils::HashRawBuffer<float>(GetQuaternion(bufferIndex).AsVector().AsFloat(), 4, seed);
       }
 
       case Property::MATRIX:
       {
-        return std::_Hash_bytes(&GetMatrix(bufferIndex), sizeof(Matrix), seed);
+        return Dali::Internal::HashUtils::HashRawBuffer<float>(GetMatrix(bufferIndex).AsFloat(), 16, seed);
       }
 
       case Property::MATRIX3:
       {
-        return std::_Hash_bytes(&GetMatrix3(bufferIndex), sizeof(Matrix3), seed);
+        return Dali::Internal::HashUtils::HashRawBuffer<float>(GetMatrix3(bufferIndex).AsFloat(), 9, seed);
       }
 
       default:
index 7666908a54b99cab87acfdb7f209236b1b674f4a..9520727c366e97823c34c37507bef900b385ebd0 100644 (file)
 #include <dali/internal/update/manager/update-manager.h>
 #include <dali/public-api/rendering/vertex-buffer.h>
 
-#if defined(ANDROID) || defined(WIN32) || defined(__APPLE__)
-namespace std
-{
-uint64_t _Hash_bytes(const void* bytes, uint64_t size, uint64_t seed)
-{
-  for(uint64_t i = 0; i < size; i++)
-  {
-    seed = seed * 31 + reinterpret_cast<const unsigned char*>(bytes)[i];
-  }
-
-  return seed;
-}
-} // namespace std
-#endif
-
 namespace Dali
 {
 namespace Internal
index 9ae9397a2edc220ca2031fb01e58749a6633c55e..a7c20bee704dbf045d495867ce82c067c2363a90 100644 (file)
@@ -29,11 +29,11 @@ namespace Dali::Internal::SceneGraph
  */
 struct PartialRenderingData
 {
-  Matrix   matrix{};              /// Model-view matrix
-  Vector4  color{};               /// Color
-  Vector4  updatedPositionSize{}; /// Updated position/size (x, y, width, height)
-  Vector3  size{};                /// Size
-  uint32_t hash{0u};              /// Last frame's hash
+  Matrix      matrix{};              /// Model-view matrix
+  Vector4     color{};               /// Color
+  Vector4     updatedPositionSize{}; /// Updated position/size (x, y, width, height)
+  Vector3     size{};                /// Size
+  std::size_t hash{0u};              /// Last frame's hash
 
   bool mVisible : 1; /// Visible state. It is depends on node's visibility (Not hashed)
   bool mUpdated : 1; /// IsUpdated return true at this frame. Will be reset at UpdateNodes time. (Not hashed)
@@ -58,11 +58,11 @@ struct PartialRenderingData
    */
   void CalculateHash()
   {
-    hash = Dali::INITIAL_HASH_VALUE;
-    AddToHash(hash, &matrix, sizeof(decltype(matrix)));
-    AddToHash(hash, &color, sizeof(decltype(color)));
-    AddToHash(hash, &updatedPositionSize, sizeof(decltype(updatedPositionSize)));
-    AddToHash(hash, &size, sizeof(decltype(size)));
+    hash = Dali::Internal::HashUtils::INITIAL_HASH_VALUE;
+    Dali::Internal::HashUtils::HashRawBuffer<float>(matrix.AsFloat(), 16, hash);
+    Dali::Internal::HashUtils::HashRawBuffer<float>(color.AsFloat(), 4, hash);
+    Dali::Internal::HashUtils::HashRawBuffer<float>(updatedPositionSize.AsFloat(), 4, hash);
+    Dali::Internal::HashUtils::HashRawBuffer<float>(size.AsFloat(), 3, hash);
   }
 
   /**
@@ -119,16 +119,6 @@ struct PartialRenderingData
   {
     mUpdateDecay = Decay::EXPIRED;
   }
-
-private:
-  void AddToHash(uint32_t& aHash, void* el, size_t numBytes)
-  {
-    uint8_t* elBytes = static_cast<uint8_t*>(el);
-    for(size_t i = 0; i < numBytes; ++i)
-    {
-      aHash = aHash * 33 + *elBytes++;
-    }
-  }
 };
 
 } // namespace Dali::Internal::SceneGraph
index 9d204fc4989ace92bc53f39234557fcb235ba14c..640500f4f8eb70c539142f7fa0080f7b63f65a3d 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2022 Samsung Electronics Co., Ltd.
+ * Copyright (c) 2024 Samsung Electronics Co., Ltd.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
 // CLASS HEADER
 #include <dali/public-api/object/property-array.h>
 
+// EXTERNAL INCLUDES
+#include <limits>
+
 // INTERNAL INCLUDES
+#include <dali/internal/common/hash-utils.h>
 #include <dali/public-api/common/vector-wrapper.h>
 
 namespace Dali
 {
 namespace
 {
-}; // unnamed namespace
+constexpr std::size_t NOT_HASHED    = 0u;
+constexpr std::size_t ALWAYS_REHASH = std::numeric_limits<std::size_t>::max();
+} // namespace
 
 struct Property::Array::Impl
 {
   typedef std::vector<Value> Array;
 
+public:
+  std::size_t GetHash() const
+  {
+    std::size_t hash = mHash;
+    if(hash == ALWAYS_REHASH || hash == NOT_HASHED)
+    {
+      hash = Dali::Internal::HashUtils::INITIAL_HASH_VALUE;
+
+      for(const auto& iter : mArray)
+      {
+        // Use ordered hash operation.
+        Dali::Internal::HashUtils::HashRawValue(iter.GetHash(), hash);
+      }
+
+      if(mHash != ALWAYS_REHASH)
+      {
+        mHash = hash;
+      }
+    }
+    return hash;
+  }
+
+public:
   Array mArray;
+
+  mutable std::size_t mHash{NOT_HASHED};
 };
 
 Property::Array::Array()
@@ -51,7 +82,11 @@ Property::Array::Array(const std::initializer_list<Property::Value>& values)
 Property::Array::Array(const Property::Array& other)
 : mImpl(new Impl)
 {
-  mImpl->mArray = other.mImpl->mArray;
+  if(DALI_LIKELY(other.mImpl))
+  {
+    mImpl->mArray = other.mImpl->mArray;
+    // Keep mHash as NOT_HASHED.
+  }
 }
 
 Property::Array::Array(Property::Array&& other) noexcept
@@ -67,43 +102,70 @@ Property::Array::~Array()
 
 Property::Array::SizeType Property::Array::Count() const
 {
-  DALI_ASSERT_DEBUG(mImpl && "Cannot use an object previously used as an r-value");
-  return mImpl->mArray.size();
+  return DALI_LIKELY(mImpl) ? mImpl->mArray.size() : 0;
 }
 
 void Property::Array::PushBack(const Value& value)
 {
-  DALI_ASSERT_DEBUG(mImpl && "Cannot use an object previously used as an r-value");
+  if(DALI_UNLIKELY(!mImpl))
+  {
+    mImpl = new Impl();
+  }
+
+  if(mImpl->mHash != ALWAYS_REHASH && mImpl->mHash != NOT_HASHED)
+  {
+    // Use ordered hash operation.
+    Dali::Internal::HashUtils::HashRawValue(value.GetHash(), mImpl->mHash);
+  }
   mImpl->mArray.push_back(value);
 }
 
 void Property::Array::Clear()
 {
-  DALI_ASSERT_DEBUG(mImpl && "Cannot use an object previously used as an r-value");
-  mImpl->mArray.clear();
+  if(DALI_LIKELY(mImpl))
+  {
+    mImpl->mArray.clear();
+    mImpl->mHash = NOT_HASHED;
+  }
 }
 
 void Property::Array::Reserve(SizeType size)
 {
-  DALI_ASSERT_DEBUG(mImpl && "Cannot use an object previously used as an r-value");
+  if(DALI_UNLIKELY(!mImpl))
+  {
+    mImpl = new Impl();
+  }
+
   mImpl->mArray.reserve(size);
 }
 
 void Property::Array::Resize(SizeType size)
 {
-  DALI_ASSERT_DEBUG(mImpl && "Cannot use an object previously used as an r-value");
-  mImpl->mArray.resize(size);
+  if(DALI_UNLIKELY(!mImpl))
+  {
+    mImpl = new Impl();
+  }
+
+  if(mImpl->mArray.size() != size)
+  {
+    mImpl->mArray.resize(size);
+
+    // Just reset hash as zero.
+    if(mImpl->mHash != ALWAYS_REHASH)
+    {
+      mImpl->mHash = NOT_HASHED;
+    }
+  }
 }
 
 Property::Array::SizeType Property::Array::Capacity()
 {
-  DALI_ASSERT_DEBUG(mImpl && "Cannot use an object previously used as an r-value");
-  return mImpl->mArray.capacity();
+  return DALI_LIKELY(mImpl) ? mImpl->mArray.capacity() : 0;
 }
 
 const Property::Value& Property::Array::operator[](SizeType index) const
 {
-  DALI_ASSERT_DEBUG(mImpl && "Cannot use an object previously used as an r-value");
+  DALI_ASSERT_ALWAYS(mImpl && "Cannot use an object previously used as an r-value");
 
   // Note says no bounds checking is performed so we don't need to verify mImpl as Count() will return 0 anyway
   return mImpl->mArray[index];
@@ -111,7 +173,13 @@ const Property::Value& Property::Array::operator[](SizeType index) const
 
 Property::Value& Property::Array::operator[](SizeType index)
 {
-  DALI_ASSERT_DEBUG(mImpl && "Cannot use an object previously used as an r-value");
+  DALI_ASSERT_ALWAYS(mImpl && "Cannot use an object previously used as an r-value");
+
+  // Mark as we should rehash always. (Since new value might be changed by application side anytime.)
+  if(mImpl->mHash != ALWAYS_REHASH)
+  {
+    mImpl->mHash = ALWAYS_REHASH;
+  }
 
   // Note says no bounds checking is performed so we don't need to verify mImpl as Count() will return 0 anyway
   return mImpl->mArray[index];
@@ -119,11 +187,21 @@ Property::Value& Property::Array::operator[](SizeType index)
 
 Property::Array& Property::Array::operator=(const Property::Array& other)
 {
-  DALI_ASSERT_DEBUG(mImpl && "Cannot use an object previously used as an r-value");
-
   if(this != &other)
   {
-    mImpl->mArray = other.mImpl->mArray;
+    if(DALI_UNLIKELY(other.mImpl == nullptr))
+    {
+      Clear();
+    }
+    else
+    {
+      if(DALI_UNLIKELY(!mImpl))
+      {
+        mImpl = new Impl();
+      }
+      mImpl->mArray = other.mImpl->mArray;
+      mImpl->mHash  = other.mImpl->mHash;
+    }
   }
   return *this;
 }
@@ -139,17 +217,38 @@ Property::Array& Property::Array::operator=(Property::Array&& other) noexcept
   return *this;
 }
 
+bool Property::Array::operator==(const Property::Array& rhs) const
+{
+  // TODO : Need to check epsilon for float comparison in future. For now, just compare hash value and count.
+  return Count() == rhs.Count() && GetHash() == rhs.GetHash();
+}
+
+std::size_t Property::Array::GetHash() const
+{
+  return DALI_LIKELY(mImpl) ? mImpl->GetHash() : Dali::Internal::HashUtils::INITIAL_HASH_VALUE;
+}
+
 std::ostream& operator<<(std::ostream& stream, const Property::Array& array)
 {
   stream << "Array(" << array.Count() << ") = [";
-  for(Property::Array::SizeType i = 0; i < array.Count(); ++i)
+
+  if(DALI_LIKELY(array.mImpl))
   {
-    if(i > 0)
+    for(Property::Array::SizeType i = 0; i < array.Count(); ++i)
+    {
+      if(i > 0)
+      {
+        stream << ", ";
+      }
+      stream << array.GetElementAt(i);
+    }
+
+    if(array.mImpl->mHash != NOT_HASHED)
     {
-      stream << "";
+      stream << "(hash=" << array.mImpl->mHash << ")";
     }
-    stream << array.GetElementAt(i);
   }
+
   stream << "]";
 
   return stream;
index 70c74a6eaeb0828ad50c1d0aa46f8922da76c1e8..65194453109be87c2917403d9d91fd22559b0c41 100644 (file)
@@ -2,7 +2,7 @@
 #define DALI_PROPERTY_ARRAY_H
 
 /*
- * Copyright (c) 2022 Samsung Electronics Co., Ltd.
+ * Copyright (c) 2024 Samsung Electronics Co., Ltd.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -226,6 +226,51 @@ public:
    */
   Array& operator=(Array&& other) noexcept;
 
+  /**
+   * @brief Equality operator.
+   *
+   * @SINCE_2_3.54
+   * @param[in] rhs A reference for comparison
+   * @return True if equal type and equal value.
+   */
+  bool operator==(const Array& rhs) const;
+
+  /**
+   * @brief Inequality operator.
+   *
+   * @SINCE_2_3.54
+   * @param[in] rhs A reference for comparison
+   * @return True if not equal
+   */
+  bool operator!=(const Array& rhs) const
+  {
+    return !(*this == rhs);
+  }
+
+  /**
+   * @brief Get hash value of the array.
+   *
+   * @note The order of key/value pairs is considered.
+   * For example, Array({1, 2}) and Array({2, 1}) might not have same hash value.
+   * And the type of Property::Value is considered.
+   * For example, Array({1}) and Array({1.0f}) might not have same hash value.
+   *
+   * @warning Hash might spend O(N) per each call.
+   * @warning Hash don't consider floating point precision. So even if two values equality return true,
+   * they can have different hash value.
+   * @code
+   * Property::Array v1({1.0f}});
+   * Property::Array v2({1.0000001192092896f}); // 1.0f + FLT_EPSILON
+   * assert(v1 == v2); // true
+   * assert(v1.GetHash() == v2.GetHash()); // false, because of floating point precision issue.
+   * @endcode
+   *
+   * @SINCE_2_3.54
+   *
+   * @return Get the hashed value.
+   */
+  std::size_t GetHash() const;
+
   /**
    * @brief Output to stream.
    * @SINCE_1_1.28
index 41ec6f0c45e26cdc56375fcfada0deaab6bbd7fb..ebbf2a0c650567ddcab9629163cf13cb0ca45b60 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2022 Samsung Electronics Co., Ltd.
+ * Copyright (c) 2024 Samsung Electronics Co., Ltd.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
 
 // EXTERNAL INCLUDES
 #include <dali/integration-api/debug.h>
+#include <limits>
 
 // INTERNAL INCLUDES
+#include <dali/internal/common/hash-utils.h>
 #include <dali/public-api/common/vector-wrapper.h>
 
 namespace Dali
@@ -33,12 +35,47 @@ typedef std::vector<StringValuePair> StringValueContainer;
 using IndexValuePair      = std::pair<Property::Index, Property::Value>;
 using IndexValueContainer = std::vector<IndexValuePair>;
 
+constexpr std::size_t NOT_HASHED    = 0u;
+constexpr std::size_t ALWAYS_REHASH = std::numeric_limits<std::size_t>::max();
+
 }; // unnamed namespace
 
 struct Property::Map::Impl
 {
+public:
+  std::size_t GetHash() const
+  {
+    std::size_t hash = mHash;
+    if(hash == ALWAYS_REHASH || hash == NOT_HASHED)
+    {
+      hash = Dali::Internal::HashUtils::INITIAL_HASH_VALUE;
+
+      for(const auto& iter : mStringValueContainer)
+      {
+        // Use unordered hash operation.
+        auto valueHash = iter.second.GetHash();
+        hash += Dali::Internal::HashUtils::HashStringView(std::string_view(iter.first), valueHash);
+      }
+      for(const auto& iter : mIndexValueContainer)
+      {
+        // Use unordered hash operation.
+        auto valueHash = iter.second.GetHash();
+        hash += Dali::Internal::HashUtils::HashRawValue(iter.first, valueHash);
+      }
+
+      if(mHash != ALWAYS_REHASH)
+      {
+        mHash = hash;
+      }
+    }
+    return hash;
+  }
+
+public:
   StringValueContainer mStringValueContainer;
   IndexValueContainer  mIndexValueContainer;
+
+  mutable std::size_t mHash{NOT_HASHED};
 };
 
 Property::Map::Map()
@@ -71,8 +108,12 @@ Property::Map::Map(const std::initializer_list<KeyValuePair>& values)
 Property::Map::Map(const Property::Map& other)
 : mImpl(new Impl)
 {
-  mImpl->mStringValueContainer = other.mImpl->mStringValueContainer;
-  mImpl->mIndexValueContainer  = other.mImpl->mIndexValueContainer;
+  if(DALI_LIKELY(other.mImpl))
+  {
+    mImpl->mStringValueContainer = other.mImpl->mStringValueContainer;
+    mImpl->mIndexValueContainer  = other.mImpl->mIndexValueContainer;
+    // Keep mHash as NOT_HASHED.
+  }
 }
 
 Property::Map::Map(Property::Map&& other) noexcept
@@ -88,31 +129,58 @@ Property::Map::~Map()
 
 Property::Map::SizeType Property::Map::Count() const
 {
-  DALI_ASSERT_DEBUG(mImpl && "Cannot use an object previously used as an r-value");
-  return mImpl->mStringValueContainer.size() + mImpl->mIndexValueContainer.size();
+  if(DALI_LIKELY(mImpl))
+  {
+    return mImpl->mStringValueContainer.size() + mImpl->mIndexValueContainer.size();
+  }
+  return 0;
 }
 
 bool Property::Map::Empty() const
 {
-  DALI_ASSERT_DEBUG(mImpl && "Cannot use an object previously used as an r-value");
-  return mImpl->mStringValueContainer.empty() && mImpl->mIndexValueContainer.empty();
+  if(DALI_LIKELY(mImpl))
+  {
+    return mImpl->mStringValueContainer.empty() && mImpl->mIndexValueContainer.empty();
+  }
+  return true;
 }
 
 void Property::Map::Insert(std::string key, Value value)
 {
-  DALI_ASSERT_DEBUG(mImpl && "Cannot use an object previously used as an r-value");
+  if(DALI_UNLIKELY(!mImpl))
+  {
+    mImpl = new Impl();
+  }
+
+  if(mImpl->mHash != ALWAYS_REHASH && mImpl->mHash != NOT_HASHED)
+  {
+    // Use unordered hash operation.
+    auto valueHash = value.GetHash();
+    mImpl->mHash += Dali::Internal::HashUtils::HashStringView(std::string_view(key), valueHash);
+  }
   mImpl->mStringValueContainer.push_back(std::make_pair(std::move(key), std::move(value)));
 }
 
 void Property::Map::Insert(Property::Index key, Value value)
 {
-  DALI_ASSERT_DEBUG(mImpl && "Cannot use an object previously used as an r-value");
+  if(DALI_UNLIKELY(!mImpl))
+  {
+    mImpl = new Impl();
+  }
+
+  if(mImpl->mHash != ALWAYS_REHASH && mImpl->mHash != NOT_HASHED)
+  {
+    // Use unordered hash operation.
+    auto valueHash = value.GetHash();
+    mImpl->mHash += Dali::Internal::HashUtils::HashRawValue(key, valueHash);
+  }
   mImpl->mIndexValueContainer.push_back(std::make_pair(key, std::move(value)));
 }
 
 Property::Value& Property::Map::GetValue(SizeType position) const
 {
-  DALI_ASSERT_DEBUG(mImpl && "Cannot use an object previously used as an r-value");
+  DALI_ASSERT_ALWAYS(mImpl && "Cannot use an object previously used as an r-value");
+
   SizeType numStringKeys = mImpl->mStringValueContainer.size();
   SizeType numIndexKeys  = mImpl->mIndexValueContainer.size();
   DALI_ASSERT_ALWAYS(position < (numStringKeys + numIndexKeys) && "position out-of-bounds");
@@ -131,7 +199,8 @@ const std::string& Property::Map::GetKey(SizeType position) const
 {
   DALI_LOG_WARNING_NOFN("DEPRECATION WARNING: GetKey() is deprecated and will be removed from next release.\n");
 
-  DALI_ASSERT_DEBUG(mImpl && "Cannot use an object previously used as an r-value");
+  DALI_ASSERT_ALWAYS(mImpl && "Cannot use an object previously used as an r-value");
+
   SizeType numStringKeys = mImpl->mStringValueContainer.size();
   DALI_ASSERT_ALWAYS(position < numStringKeys && "position out-of-bounds");
 
@@ -140,7 +209,7 @@ const std::string& Property::Map::GetKey(SizeType position) const
 
 Property::Key Property::Map::GetKeyAt(SizeType position) const
 {
-  DALI_ASSERT_DEBUG(mImpl && "Cannot use an object previously used as an r-value");
+  DALI_ASSERT_ALWAYS(mImpl && "Cannot use an object previously used as an r-value");
 
   SizeType numStringKeys = mImpl->mStringValueContainer.size();
   SizeType numIndexKeys  = mImpl->mIndexValueContainer.size();
@@ -162,7 +231,7 @@ StringValuePair& Property::Map::GetPair(SizeType position) const
 {
   DALI_LOG_WARNING_NOFN("DEPRECATION WARNING: GetPair() is deprecated and will be removed from next release.\n");
 
-  DALI_ASSERT_DEBUG(mImpl && "Cannot use an object previously used as an r-value");
+  DALI_ASSERT_ALWAYS(mImpl && "Cannot use an object previously used as an r-value");
 
   SizeType numStringKeys = mImpl->mStringValueContainer.size();
 
@@ -173,11 +242,10 @@ StringValuePair& Property::Map::GetPair(SizeType position) const
 
 KeyValuePair Property::Map::GetKeyValue(SizeType position) const
 {
-  DALI_ASSERT_DEBUG(mImpl && "Cannot use an object previously used as an r-value");
+  DALI_ASSERT_ALWAYS(mImpl && "Cannot use an object previously used as an r-value");
 
   SizeType numStringKeys = mImpl->mStringValueContainer.size();
   SizeType numIndexKeys  = mImpl->mIndexValueContainer.size();
-
   DALI_ASSERT_ALWAYS(position < (numStringKeys + numIndexKeys) && "position out-of-bounds");
 
   if(position < numStringKeys)
@@ -194,13 +262,20 @@ KeyValuePair Property::Map::GetKeyValue(SizeType position) const
 
 Property::Value* Property::Map::Find(std::string_view key) const
 {
-  DALI_ASSERT_DEBUG(mImpl && "Cannot use an object previously used as an r-value");
-
-  for(auto&& iter : mImpl->mStringValueContainer)
+  if(DALI_LIKELY(mImpl))
   {
-    if(key == iter.first)
+    for(auto&& iter : mImpl->mStringValueContainer)
     {
-      return &iter.second;
+      if(key == iter.first)
+      {
+        if(mImpl->mHash != ALWAYS_REHASH)
+        {
+          // Mark as we cannot assume that hash is valid anymore.
+          // Recalculate hash always after now.
+          mImpl->mHash = ALWAYS_REHASH;
+        }
+        return &iter.second;
+      }
     }
   }
   return nullptr; // Not found
@@ -208,13 +283,20 @@ Property::Value* Property::Map::Find(std::string_view key) const
 
 Property::Value* Property::Map::Find(Property::Index key) const
 {
-  DALI_ASSERT_DEBUG(mImpl && "Cannot use an object previously used as an r-value");
-
-  for(auto&& iter : mImpl->mIndexValueContainer)
+  if(DALI_LIKELY(mImpl))
   {
-    if(iter.first == key)
+    for(auto&& iter : mImpl->mIndexValueContainer)
     {
-      return &iter.second;
+      if(iter.first == key)
+      {
+        if(mImpl->mHash != ALWAYS_REHASH)
+        {
+          // Mark as we cannot assume that hash is valid anymore.
+          // Recalculate hash always after now.
+          mImpl->mHash = ALWAYS_REHASH;
+        }
+        return &iter.second;
+      }
     }
   }
   return nullptr; // Not found
@@ -232,13 +314,20 @@ Property::Value* Property::Map::Find(Property::Index indexKey, std::string_view
 
 Property::Value* Property::Map::Find(std::string_view key, Property::Type type) const
 {
-  DALI_ASSERT_DEBUG(mImpl && "Cannot use an object previously used as an r-value");
-
-  for(auto&& iter : mImpl->mStringValueContainer)
+  if(DALI_LIKELY(mImpl))
   {
-    if((iter.second.GetType() == type) && (key == iter.first))
+    for(auto&& iter : mImpl->mStringValueContainer)
     {
-      return &iter.second;
+      if((iter.second.GetType() == type) && (key == iter.first))
+      {
+        if(mImpl->mHash != ALWAYS_REHASH)
+        {
+          // Mark as we cannot assume that hash is valid anymore.
+          // Recalculate hash always after now.
+          mImpl->mHash = ALWAYS_REHASH;
+        }
+        return &iter.second;
+      }
     }
   }
   return nullptr; // Not found
@@ -246,13 +335,20 @@ Property::Value* Property::Map::Find(std::string_view key, Property::Type type)
 
 Property::Value* Property::Map::Find(Property::Index key, Property::Type type) const
 {
-  DALI_ASSERT_DEBUG(mImpl && "Cannot use an object previously used as an r-value");
-
-  for(auto&& iter : mImpl->mIndexValueContainer)
+  if(DALI_LIKELY(mImpl))
   {
-    if((iter.second.GetType() == type) && (iter.first == key))
+    for(auto&& iter : mImpl->mIndexValueContainer)
     {
-      return &iter.second;
+      if((iter.second.GetType() == type) && (iter.first == key))
+      {
+        if(mImpl->mHash != ALWAYS_REHASH)
+        {
+          // Mark as we cannot assume that hash is valid anymore.
+          // Recalculate hash always after now.
+          mImpl->mHash = ALWAYS_REHASH;
+        }
+        return &iter.second;
+      }
     }
   }
   return nullptr; // Not found
@@ -260,47 +356,72 @@ Property::Value* Property::Map::Find(Property::Index key, Property::Type type) c
 
 void Property::Map::Clear()
 {
-  DALI_ASSERT_DEBUG(mImpl && "Cannot use an object previously used as an r-value");
-
-  mImpl->mStringValueContainer.clear();
-  mImpl->mIndexValueContainer.clear();
+  if(DALI_LIKELY(mImpl))
+  {
+    mImpl->mStringValueContainer.clear();
+    mImpl->mIndexValueContainer.clear();
+    mImpl->mHash = NOT_HASHED;
+  }
 }
 
 bool Property::Map::Remove(Property::Index key)
 {
-  DALI_ASSERT_DEBUG(mImpl && "Cannot use an object previously used as an r-value");
-
-  auto iter = std::find_if(mImpl->mIndexValueContainer.begin(), mImpl->mIndexValueContainer.end(), [key](const IndexValuePair& element) { return element.first == key; });
-  if(iter != mImpl->mIndexValueContainer.end())
+  if(DALI_LIKELY(mImpl))
   {
-    mImpl->mIndexValueContainer.erase(iter);
-    return true;
+    auto iter = std::find_if(mImpl->mIndexValueContainer.begin(), mImpl->mIndexValueContainer.end(), [key](const IndexValuePair& element) { return element.first == key; });
+    if(iter != mImpl->mIndexValueContainer.end())
+    {
+      if(mImpl->mHash != ALWAYS_REHASH && mImpl->mHash != NOT_HASHED)
+      {
+        // Use unordered hash operation.
+        auto valueHash = iter->second.GetHash();
+        mImpl->mHash -= Dali::Internal::HashUtils::HashRawValue(key, valueHash);
+      }
+      mImpl->mIndexValueContainer.erase(iter);
+      return true;
+    }
   }
   return false;
 }
 
 bool Property::Map::Remove(std::string_view key)
 {
-  DALI_ASSERT_DEBUG(mImpl && "Cannot use an object previously used as an r-value");
-
-  auto iter = std::find_if(mImpl->mStringValueContainer.begin(), mImpl->mStringValueContainer.end(), [key](const StringValuePair& element) { return element.first == key; });
-  if(iter != mImpl->mStringValueContainer.end())
+  if(DALI_LIKELY(mImpl))
   {
-    mImpl->mStringValueContainer.erase(iter);
-    return true;
+    auto iter = std::find_if(mImpl->mStringValueContainer.begin(), mImpl->mStringValueContainer.end(), [key](const StringValuePair& element) { return element.first == key; });
+    if(iter != mImpl->mStringValueContainer.end())
+    {
+      if(mImpl->mHash != ALWAYS_REHASH && mImpl->mHash != NOT_HASHED)
+      {
+        // Use unordered hash operation.
+        auto valueHash = iter->second.GetHash();
+        mImpl->mHash -= Dali::Internal::HashUtils::HashStringView(key, valueHash);
+      }
+      mImpl->mStringValueContainer.erase(iter);
+      return true;
+    }
   }
   return false;
 }
 
 void Property::Map::Merge(const Property::Map& from)
 {
-  DALI_ASSERT_DEBUG(mImpl && "Cannot use an object previously used as an r-value");
+  if(DALI_UNLIKELY(!mImpl))
+  {
+    mImpl = new Impl();
+  }
 
   // Ensure we're not attempting to merge with ourself
   if(this != &from)
   {
     if(Count())
     {
+      // Just reset hash as zero. (Since incremental merge is complex.)
+      if(mImpl->mHash != ALWAYS_REHASH)
+      {
+        mImpl->mHash = NOT_HASHED;
+      }
+
       for(auto&& iter : from.mImpl->mStringValueContainer)
       {
         (*this)[iter.first] = iter.second;
@@ -321,7 +442,7 @@ void Property::Map::Merge(const Property::Map& from)
 
 const Property::Value& Property::Map::operator[](std::string_view key) const
 {
-  DALI_ASSERT_DEBUG(mImpl && "Cannot use an object previously used as an r-value");
+  DALI_ASSERT_ALWAYS(mImpl && "Cannot use an object previously used as an r-value");
 
   for(auto&& iter : mImpl->mStringValueContainer)
   {
@@ -336,7 +457,17 @@ const Property::Value& Property::Map::operator[](std::string_view key) const
 
 Property::Value& Property::Map::operator[](std::string_view key)
 {
-  DALI_ASSERT_DEBUG(mImpl && "Cannot use an object previously used as an r-value");
+  if(DALI_UNLIKELY(!mImpl))
+  {
+    mImpl = new Impl();
+  }
+
+  if(mImpl->mHash != ALWAYS_REHASH)
+  {
+    // Mark as we cannot assume that hash is valid anymore.
+    // Recalculate hash always after now.
+    mImpl->mHash = ALWAYS_REHASH;
+  }
 
   for(auto&& iter : mImpl->mStringValueContainer)
   {
@@ -353,7 +484,7 @@ Property::Value& Property::Map::operator[](std::string_view key)
 
 const Property::Value& Property::Map::operator[](Property::Index key) const
 {
-  DALI_ASSERT_DEBUG(mImpl && "Cannot use an object previously used as an r-value");
+  DALI_ASSERT_ALWAYS(mImpl && "Cannot use an object previously used as an r-value");
 
   for(auto&& iter : mImpl->mIndexValueContainer)
   {
@@ -368,7 +499,17 @@ const Property::Value& Property::Map::operator[](Property::Index key) const
 
 Property::Value& Property::Map::operator[](Property::Index key)
 {
-  DALI_ASSERT_DEBUG(mImpl && "Cannot use an object previously used as an r-value");
+  if(DALI_UNLIKELY(!mImpl))
+  {
+    mImpl = new Impl();
+  }
+
+  if(mImpl->mHash != ALWAYS_REHASH)
+  {
+    // Mark as we cannot assume that hash is valid anymore.
+    // Recalculate hash always after now.
+    mImpl->mHash = ALWAYS_REHASH;
+  }
 
   for(auto&& iter : mImpl->mIndexValueContainer)
   {
@@ -385,12 +526,22 @@ Property::Value& Property::Map::operator[](Property::Index key)
 
 Property::Map& Property::Map::operator=(const Property::Map& other)
 {
-  DALI_ASSERT_DEBUG(mImpl && "Cannot use an object previously used as an r-value");
-
   if(this != &other)
   {
-    mImpl->mStringValueContainer = other.mImpl->mStringValueContainer;
-    mImpl->mIndexValueContainer  = other.mImpl->mIndexValueContainer;
+    if(DALI_UNLIKELY(other.mImpl == nullptr))
+    {
+      Clear();
+    }
+    else
+    {
+      if(DALI_UNLIKELY(!mImpl))
+      {
+        mImpl = new Impl();
+      }
+      mImpl->mStringValueContainer = other.mImpl->mStringValueContainer;
+      mImpl->mIndexValueContainer  = other.mImpl->mIndexValueContainer;
+      mImpl->mHash                 = other.mImpl->mHash;
+    }
   }
   return *this;
 }
@@ -406,11 +557,22 @@ Property::Map& Property::Map::operator=(Property::Map&& other) noexcept
   return *this;
 }
 
+bool Property::Map::operator==(const Property::Map& rhs) const
+{
+  // TODO : Need to check epsilon for float comparison in future. For now, just compare hash value and count.
+  return Count() == rhs.Count() && GetHash() == rhs.GetHash();
+}
+
+std::size_t Property::Map::GetHash() const
+{
+  return DALI_LIKELY(mImpl) ? mImpl->GetHash() : Dali::Internal::HashUtils::INITIAL_HASH_VALUE;
+}
+
 std::ostream& operator<<(std::ostream& stream, const Property::Map& map)
 {
   stream << "Map(" << map.Count() << ") = {";
 
-  if(map.mImpl)
+  if(DALI_LIKELY(map.mImpl))
   {
     int32_t count = 0;
     // Output the String-Value pairs
@@ -432,6 +594,11 @@ std::ostream& operator<<(std::ostream& stream, const Property::Map& map)
       }
       stream << iter.first << ":" << iter.second;
     }
+
+    if(map.mImpl->mHash != NOT_HASHED)
+    {
+      stream << "(hash=" << map.mImpl->mHash << ")";
+    }
   }
 
   stream << "}";
index 99ac90e933eb846d5d18285f0b7a45a4ea8ab4fe..a9031b63e4466f45d02e83f7c340b56a9657202d 100755 (executable)
@@ -2,7 +2,7 @@
 #define DALI_PROPERTY_MAP_H
 
 /*
- * Copyright (c) 2022 Samsung Electronics Co., Ltd.
+ * Copyright (c) 2024 Samsung Electronics Co., Ltd.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -370,6 +370,51 @@ public:
    */
   Map& operator=(Map&& other) noexcept;
 
+  /**
+   * @brief Equality operator.
+   *
+   * @SINCE_2_3.54
+   * @param[in] rhs A reference for comparison
+   * @return True if equal type and equal value.
+   */
+  bool operator==(const Map& rhs) const;
+
+  /**
+   * @brief Inequality operator.
+   *
+   * @SINCE_2_3.54
+   * @param[in] rhs A reference for comparison
+   * @return True if not equal
+   */
+  bool operator!=(const Map& rhs) const
+  {
+    return !(*this == rhs);
+  }
+
+  /**
+   * @brief Get hash value of the map.
+   *
+   * @note The order of key/value pairs is not considered.
+   * For example, Map({{"key", 1}, {"key2", 2}}) and Map({{"key2", 2}, {"key", 1}}) will have same hash value.
+   * But the type of Property::Value is considered.
+   * For example, Map({{"key", 1}}) and Map({{"key", 1.0f}}) might not have same hash value.
+   *
+   * @warning Hash might spend O(N) per each call.
+   * @warning Hash don't consider floating point precision. So even if two values equality return true,
+   * they can have different hash value.
+   * @code
+   * Property::Map v1({{1, 1.0f}});
+   * Property::Map v2({{1, 1.0000001192092896f}}); // 1.0f + FLT_EPSILON
+   * assert(v1 == v2); // true
+   * assert(v1.GetHash() == v2.GetHash()); // false, because of floating point precision issue.
+   * @endcode
+   *
+   * @SINCE_2_3.54
+   *
+   * @return Get the hashed value.
+   */
+  std::size_t GetHash() const;
+
   /**
    * @brief Output to stream.
    * @SINCE_1_1.28
index f1c63321280ec2725cba479897558b568ef0d381..0a79eb961124b7352d432c78c52801c67e049cef 100644 (file)
@@ -39,6 +39,8 @@
 #include <dali/public-api/object/property-map.h>
 #include <dali/public-api/object/property-types.h>
 
+#include <dali/internal/common/hash-utils.h>
+
 /**
  * As the Implementation is bit complex because of Small Buffer Optimization (SBO)
  * In the Property::Value class and Tagged Union implementation in the Impl class.
@@ -85,6 +87,8 @@ struct Property::Value::Impl
 
   Impl()
   {
+    // DevNote - This feature should be keeped to be opitmized.
+    // See patch with name : Added small buffer optimization to Property::Value.
     static_assert(sizeof(Impl) == 16);
     static_assert(alignof(Impl) == alignof(Impl*));
 
@@ -177,7 +181,7 @@ struct Property::Value::Impl
 
   Type GetType() const
   {
-    return mData.mType.type;
+    return mData.mMetadata.type;
   }
 
   bool GetBool() const
@@ -486,10 +490,12 @@ struct Property::Value::Impl
         return mData.mExtents.member == other.mData.mExtents.member;
       }
       case Property::ARRAY:
+      {
+        return mData.mArray.member == other.mData.mArray.member;
+      }
       case Property::MAP:
       {
-        // TODO : Need to support this case
-        return false;
+        return mData.mMap.member == other.mData.mMap.member;
       }
     }
     return false;
@@ -497,13 +503,14 @@ struct Property::Value::Impl
 
   void SetType(Type typeValue)
   {
-    mData.mType.type = typeValue;
+    mData.mMetadata.type = typeValue;
+    mData.mMetadata.hash = 0u; ///< Reset hash value when changing type.
   }
 
   bool ConvertType(const Property::Type targetType)
   {
     bool                 converted = false;
-    const Property::Type inputType = mData.mType.type;
+    const Property::Type inputType = mData.mMetadata.type;
 
     if(inputType == targetType)
     {
@@ -600,6 +607,110 @@ struct Property::Value::Impl
     return converted;
   }
 
+  std::size_t GetHash() const
+  {
+    std::size_t hash = mData.mMetadata.hash;
+    if(hash == 0u)
+    {
+      hash = Dali::Internal::HashUtils::INITIAL_HASH_VALUE;
+
+      switch(GetType())
+      {
+        case Property::NONE:
+        {
+          break;
+        }
+        case Property::BOOLEAN:
+        {
+          Dali::Internal::HashUtils::HashRawValue(mData.mBool.member, hash);
+          break;
+        }
+        case Property::FLOAT:
+        {
+          Dali::Internal::HashUtils::HashRawValue(mData.mFloat.member, hash);
+          break;
+        }
+        case Property::INTEGER:
+        {
+          Dali::Internal::HashUtils::HashRawValue(mData.mInt.member, hash);
+          break;
+        }
+        case Property::VECTOR2:
+        {
+          Dali::Internal::HashUtils::HashRawBuffer<float>(mData.mVector2.member.AsFloat(), 2, hash);
+          break;
+        }
+        case Property::VECTOR3:
+        {
+          Dali::Internal::HashUtils::HashRawBuffer<float>(mData.mVector3.member.AsFloat(), 3, hash);
+          break;
+        }
+        case Property::VECTOR4:
+        {
+          Dali::Internal::HashUtils::HashRawBuffer<float>(mData.mVector4.member->AsFloat(), 4, hash);
+          break;
+        }
+        case Property::MATRIX3:
+        {
+          Dali::Internal::HashUtils::HashRawBuffer<float>(mData.mMatrix3.member->AsFloat(), 9, hash);
+          break;
+        }
+        case Property::MATRIX:
+        {
+          Dali::Internal::HashUtils::HashRawBuffer<float>(mData.mMatrix.member->AsFloat(), 16, hash);
+          break;
+        }
+        case Property::RECTANGLE:
+        {
+          Dali::Internal::HashUtils::HashRawValue(mData.mRect.member->x, hash);
+          Dali::Internal::HashUtils::HashRawValue(mData.mRect.member->y, hash);
+          Dali::Internal::HashUtils::HashRawValue(mData.mRect.member->width, hash);
+          Dali::Internal::HashUtils::HashRawValue(mData.mRect.member->height, hash);
+          break;
+        }
+        case Property::ROTATION:
+        {
+          Dali::Internal::HashUtils::HashRawValue(mData.mAngleAxis.member->angle.radian, hash);
+          Dali::Internal::HashUtils::HashRawBuffer<float>(mData.mAngleAxis.member->axis.AsFloat(), 3, hash);
+          break;
+        }
+        case Property::STRING:
+        {
+          Dali::Internal::HashUtils::HashStringView(std::string_view(*mData.mString.member), hash);
+          break;
+        }
+        case Property::EXTENTS:
+        {
+          Dali::Internal::HashUtils::HashRawValue(mData.mExtents.member.start, hash);
+          Dali::Internal::HashUtils::HashRawValue(mData.mExtents.member.end, hash);
+          Dali::Internal::HashUtils::HashRawValue(mData.mExtents.member.top, hash);
+          Dali::Internal::HashUtils::HashRawValue(mData.mExtents.member.bottom, hash);
+          break;
+        }
+        case Property::ARRAY:
+        {
+          hash ^= mData.mArray.member.GetHash();
+          break;
+        }
+        case Property::MAP:
+        {
+          hash ^= mData.mMap.member.GetHash();
+          break;
+        }
+      }
+
+      // Don't store hash result for array and map type. (Since their value might be changed later)
+      if(!(mData.mMetadata.type == Property::ARRAY || mData.mMetadata.type == Property::MAP))
+      {
+        mData.mMetadata.hash = hash;
+      }
+    }
+
+    // For here, we can store only 24 bit hash value as maximum.
+    // So need to append the type info after hash calculate.
+    return ((static_cast<std::size_t>(mData.mMetadata.type) << 24) | (static_cast<std::size_t>(hash) & 0x00ffffff));
+  }
+
 private:
   /**
    * This helper function takes a typed(Tp) memory location( member)
@@ -689,6 +800,15 @@ private:
     }
   }
 
+  /**
+   * Data for property type and hash value.
+   */
+  struct Metadata
+  {
+    Type             type : 8;
+    mutable uint32_t hash : 24;
+  }; // TODO : Tell the compiler it is okay to use a single 32 bit load. For example : __attribute__((packed, aligned(4))) for gcc
+
   /*
    * This wrapper struct is used for
    * storing Type in every union member
@@ -700,8 +820,8 @@ private:
   template<typename T>
   struct UnionMember
   {
-    Type type;
-    T    member;
+    Metadata metadata;
+    T        member;
   };
 
   /**
@@ -742,10 +862,8 @@ private:
     UnionMember<AngleAxis*>      mAngleAxis;
     UnionMember<std::string*>    mString;
     UnionMember<Rect<int32_t>*>  mRect;
-    struct
-    {
-      Type type;
-    } mType;
+
+    Metadata mMetadata;
   };
 
   Data mData;
@@ -1348,6 +1466,11 @@ bool Property::Value::Get(Extents& extentsValue) const
   return converted;
 }
 
+std::size_t Property::Value::GetHash() const
+{
+  return Read().GetHash();
+}
+
 std::ostream& operator<<(std::ostream& stream, const Property::Value& value)
 {
   const auto& obj = value.Read();
index 74eea20ca03b55af55dfaa090221477bc0bd042d..fcf2a7d55a8b2e70a5a38ced0217ccb0e062cf98 100644 (file)
@@ -266,7 +266,6 @@ public:
    * @SINCE_2_1.31
    * @param[in] rhs A reference for comparison
    * @return True if equal type and equal value.
-   * @note Property::ARRAY and Property::MAP don't support equality operator now. Always return false.
    */
   bool operator==(const Value& rhs) const;
 
@@ -276,7 +275,6 @@ public:
    * @SINCE_2_1.31
    * @param[in] rhs A reference for comparison
    * @return True if not equal
-   * @note Property::ARRAY and Property::MAP don't support equality operator now. Always return true.
    */
   bool operator!=(const Value& rhs) const
   {
@@ -544,6 +542,24 @@ public:
    */
   bool Get(Extents& extentsValue) const;
 
+  /**
+   * @brief Get hash value of the value.
+   *
+   * @warning Hash don't consider floating point precision. So even if two values equality return true,
+   * they can have different hash value.
+   * @code
+   * Property::Value v1(1.0f);
+   * Property::Value v2(1.0000001192092896f); // 1.0f + FLT_EPSILON
+   * assert(v1 == v2); // true
+   * assert(v1.GetHash() == v2.GetHash()); // false, because of floating point precision issue.
+   * @endcode
+   *
+   * @SINCE_2_3.54
+   *
+   * @return Get the hashed value.
+   */
+  std::size_t GetHash() const;
+
   /**
    * @brief Output to stream.
    * @SINCE_1_0.0
index b9cdf0b5d47c3a685d2dbaedef5861efb3ff45b4..1a13be0fd3134201554b94ea0dab8964026a1db3 100644 (file)
@@ -2,7 +2,7 @@
 #define DALI_PROPERTY_H
 
 /*
- * Copyright (c) 2020 Samsung Electronics Co., Ltd.
+ * Copyright (c) 2024 Samsung Electronics Co., Ltd.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -78,7 +78,7 @@ struct DALI_CORE_API Property
    * @brief Enumeration for the property types supported.
    * @SINCE_1_0.0
    */
-  enum Type
+  enum Type : uint8_t
   {
     NONE, ///< No type @SINCE_1_0.0