Repository: CesiumGS/cesium-unreal
Pull request #1636: Metadata: GetUnsignedInteger64
Author: login: Julot94
Created: 2025-03-18T14:12:44Z | Merged: 2025-03-26T17:40:16Z

- Added an access to meta-data values as unsigned 64-bit integer
- Kept outside of UCesiumMetadataValueBlueprintLibrary because uint64 is not supported in blueprints (but this access can be useful in C++ code)


Diff:
diff --git a/Source/CesiumRuntime/Private/CesiumMetadataValue.cpp b/Source/CesiumRuntime/Private/CesiumMetadataValue.cpp
index 514bd3edd..163ab8de6 100644
--- a/Source/CesiumRuntime/Private/CesiumMetadataValue.cpp
+++ b/Source/CesiumRuntime/Private/CesiumMetadataValue.cpp
@@ -333,3 +333,16 @@ TMap<FString, FString> UCesiumMetadataValueBlueprintLibrary::GetValuesAsStrings(
 
   return strings;
 }
+
+
+uint64 CesiumMetadataValueAccess::GetUnsignedInteger64(
+    const FCesiumMetadataValue& Value,
+    uint64 DefaultValue) {
+  return swl::visit(
+      [DefaultValue](auto value) -> uint64 {
+        return CesiumGltf::MetadataConversions<uint64_t, decltype(value)>::
+            convert(value)
+                .value_or(DefaultValue);
+      },
+      Value._value);
+}
diff --git a/Source/CesiumRuntime/Private/Tests/CesiumMetadataValue.spec.cpp b/Source/CesiumRuntime/Private/Tests/CesiumMetadataValue.spec.cpp
index 312af4b1e..701b88a8d 100644
--- a/Source/CesiumRuntime/Private/Tests/CesiumMetadataValue.spec.cpp
+++ b/Source/CesiumRuntime/Private/Tests/CesiumMetadataValue.spec.cpp
@@ -1266,4 +1266,85 @@ void FCesiumMetadataValueSpec::Define() {
       TestEqual("array value as string", *pString, FString());
     });
   });
+
+  Describe("GetUnsignedInteger64", [this]() {
+    const uint64_t defaultValue = static_cast<uint64_t>(0);
+
+    It("gets from in-range integers", [this, defaultValue]() {
+      FCesiumMetadataValue value(std::numeric_limits<uint64_t>::max() - 1);
+      TestEqual<uint64_t>(
+          "uint64_t",
+          CesiumMetadataValueAccess::GetUnsignedInteger64(value, defaultValue),
+          std::numeric_limits<uint64_t>::max() - 1);
+
+      value = FCesiumMetadataValue(std::numeric_limits<int64_t>::max() - 1);
+      TestEqual<uint64_t>(
+          "int64_t",
+          CesiumMetadataValueAccess::GetUnsignedInteger64(value, defaultValue),
+          static_cast<uint64_t>(std::numeric_limits<int64_t>::max() - 1));
+
+      value = FCesiumMetadataValue(static_cast<int16_t>(12345));
+      TestEqual<uint64_t>(
+          "smaller signed integer",
+          CesiumMetadataValueAccess::GetUnsignedInteger64(value, defaultValue),
+          static_cast<uint64_t>(12345));
+
+      value = FCesiumMetadataValue(static_cast<uint8_t>(255));
+      TestEqual<uint64_t>(
+          "smaller unsigned integer",
+          CesiumMetadataValueAccess::GetUnsignedInteger64(value, defaultValue),
+          static_cast<uint64_t>(255));
+    });
+
+    It("gets from boolean", [this, defaultValue]() {
+      FCesiumMetadataValue value(true);
+      TestEqual<uint64_t>(
+          "value",
+          CesiumMetadataValueAccess::GetUnsignedInteger64(value, defaultValue),
+          static_cast<uint64_t>(1));
+    });
+
+    It("gets from in-range floating point number", [this, defaultValue]() {
+      FCesiumMetadataValue value(1234.56f);
+      TestEqual<uint64_t>(
+          "float",
+          CesiumMetadataValueAccess::GetUnsignedInteger64(value, defaultValue),
+          static_cast<uint64_t>(1234));
+    });
+
+    It("gets from string", [this, defaultValue]() {
+      FCesiumMetadataValue value(std::string_view("1234"));
+      TestEqual<uint64_t>(
+          "value",
+          CesiumMetadataValueAccess::GetUnsignedInteger64(value, defaultValue),
+          static_cast<uint64_t>(1234));
+    });
+
+    It("returns default value for out-of-range numbers",
+       [this, defaultValue]() {
+         FCesiumMetadataValue value(-5);
+         TestEqual<uint64_t>(
+             "negative integer",
+             CesiumMetadataValueAccess::GetUnsignedInteger64(
+                 value,
+                 defaultValue),
+             defaultValue);
+
+         value = FCesiumMetadataValue(-59.62f);
+         TestEqual<uint64_t>(
+             "negative floating-point number",
+             CesiumMetadataValueAccess::GetUnsignedInteger64(
+                 value,
+                 defaultValue),
+             defaultValue);
+
+         value = FCesiumMetadataValue(std::numeric_limits<float>::max());
+         TestEqual<uint64_t>(
+             "positive floating-point number",
+             CesiumMetadataValueAccess::GetUnsignedInteger64(
+                 value,
+                 defaultValue),
+             defaultValue);
+       });
+  });
 }
diff --git a/Source/CesiumRuntime/Public/CesiumMetadataValue.h b/Source/CesiumRuntime/Public/CesiumMetadataValue.h
index ac579eb4c..b9fe91a8c 100644
--- a/Source/CesiumRuntime/Public/CesiumMetadataValue.h
+++ b/Source/CesiumRuntime/Public/CesiumMetadataValue.h
@@ -266,6 +266,7 @@ struct CESIUMRUNTIME_API FCesiumMetadataValue {
   TSharedPtr<FCesiumMetadataEnum> _pEnumDefinition;
 
   friend class UCesiumMetadataValueBlueprintLibrary;
+  friend class CesiumMetadataValueAccess;
 };
 
 UCLASS()
@@ -859,3 +860,42 @@ class CESIUMRUNTIME_API UCesiumMetadataValueBlueprintLibrary
   static TMap<FString, FString>
   GetValuesAsStrings(const TMap<FString, FCesiumMetadataValue>& Values);
 };
+
+/**
+ * Grants access to metadata value types that are not currently supported in
+ * Blueprints. This can be useful in C++ code.
+ *
+ * These should be moved to UCesiumMetadataValueBlueprintLibrary if those types
+ * become compatible with Blueprints in the future.
+ */
+class CESIUMRUNTIME_API CesiumMetadataValueAccess {
+
+public:
+  /**
+   * Attempts to retrieve the value as an unsigned 64-bit integer.
+   *
+   * If the value is an integer and between 0 and (2^64 - 1),
+   * it is returned as-is.
+   *
+   * If the value is a floating-point number in the aforementioned range, it
+   * is truncated (rounded toward zero) and returned;
+   *
+   * If the value is a boolean, 1 is returned for true and 0 for false.
+   *
+   * If the value is a string and the entire string can be parsed as an
+   * integer in the valid range, the parsed value is returned. If it can be
+   * parsed as a floating-point number, the parsed value is truncated (rounded
+   * toward zero). In either case, the string is parsed in a locale-independent
+   * way and does not support the use of commas or other delimiters to group
+   * digits together.
+   *
+   * In all other cases, the default value is returned.
+   *
+   * @param DefaultValue The default value to use if the given value cannot
+   * be converted to an uint64.
+   * @return The value as an unsigned 64-bit integer.
+   */
+  static uint64 GetUnsignedInteger64(
+      const FCesiumMetadataValue& Value,
+      uint64 DefaultValue);
+};


Reviews:
user: login: j9liu
body: Thanks @Julot94 for the PR! I have a few small comments, but otherwise this looks great. Thank you for also adding unit tests for this function 😀 
state: CHANGES_REQUESTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-19T15:33:20Z

Review comments:
diff_hunk: @@ -859,3 +860,39 @@ class CESIUMRUNTIME_API UCesiumMetadataValueBlueprintLibrary
   static TMap<FString, FString>
   GetValuesAsStrings(const TMap<FString, FCesiumMetadataValue>& Values);
 };
+
+/// Grants access to metadata value types not currently supported in blueprint.
+/// Can be useful in C++ code.
+/// Should be moved to UCesiumMetadataValueBlueprintLibrary if those types
+/// become compatible with blueprints in the future.
path: Source/CesiumRuntime/Public/CesiumMetadataValue.h
user: login: j9liu
body: For documentation we use `/** */` style comments. I also suggest some minor word tweaking, but hopefully no surprises.

```suggestion
/**
 * Grants access to metadata value types that are not currently supported in
 * Blueprints. This can be useful in C++ code.
 *
 * These should be moved to UCesiumMetadataValueBlueprintLibrary if those types
 * become compatible with Blueprints in the future.
 */
```
created_at: 2025-03-19T15:14:50Z
author_association: CONTRIBUTOR
diff_hunk: @@ -128,6 +128,18 @@ int64 UCesiumMetadataValueBlueprintLibrary::GetInteger64(
       Value._value);
 }
 
+uint64 FCesiumMetadataValueAccess::GetUnsignedInteger64(
path: Source/CesiumRuntime/Private/CesiumMetadataValue.cpp
user: login: j9liu
body: Nitpick: I would move this function to the end of the file, to keep all of the existing `UCesiumMetadataValueBlueprintLibrary` functions grouped together. It just helps with organization :smile:
created_at: 2025-03-19T15:24:37Z
author_association: CONTRIBUTOR
diff_hunk: @@ -394,6 +394,101 @@ void FCesiumMetadataValueSpec::Define() {
        });
   });
 
+  Describe("GetUnsignedInteger64", [this]() {
path: Source/CesiumRuntime/Private/Tests/CesiumMetadataValue.spec.cpp
user: login: j9liu
body: Same nitpick here.
created_at: 2025-03-19T15:26:12Z
author_association: CONTRIBUTOR
diff_hunk: @@ -394,6 +394,101 @@ void FCesiumMetadataValueSpec::Define() {
        });
   });
 
+  Describe("GetUnsignedInteger64", [this]() {
+    const uint64_t defaultValue = static_cast<uint64_t>(0);
+
+    It("gets from in-range integers", [this, defaultValue]() {
+      FCesiumMetadataValue value(std::numeric_limits<uint64_t>::max() - 1);
+      TestEqual<uint64_t>(
+          "uint64_t",
+          FCesiumMetadataValueAccess::GetUnsignedInteger64(
+              value,
+              defaultValue),
+          std::numeric_limits<uint64_t>::max() - 1);
+
+      value = FCesiumMetadataValue(std::numeric_limits<int64_t>::max() - 1);
+      TestEqual<uint64_t>(
+          "uint64_t",
path: Source/CesiumRuntime/Private/Tests/CesiumMetadataValue.spec.cpp
user: login: j9liu
body: ```suggestion
          "int64_t",
```
created_at: 2025-03-19T15:26:58Z
author_association: CONTRIBUTOR
diff_hunk: @@ -859,3 +860,39 @@ class CESIUMRUNTIME_API UCesiumMetadataValueBlueprintLibrary
   static TMap<FString, FString>
   GetValuesAsStrings(const TMap<FString, FCesiumMetadataValue>& Values);
 };
+
+/// Grants access to metadata value types not currently supported in blueprint.
+/// Can be useful in C++ code.
+/// Should be moved to UCesiumMetadataValueBlueprintLibrary if those types
+/// become compatible with blueprints in the future.
+class CESIUMRUNTIME_API FCesiumMetadataValueAccess {
path: Source/CesiumRuntime/Public/CesiumMetadataValue.h
user: login: j9liu
body: We can drop the letter prefix from the name since this isn't a `USTRUCT` (F) or `UCLASS` (U)

```suggestion
class CESIUMRUNTIME_API CesiumMetadataValueAccess {
```
created_at: 2025-03-19T15:27:48Z
author_association: CONTRIBUTOR

PR comments:
user: login: Julot94
created_at: 2025-03-19T15:51:18Z
body: Thank you for reviewing the PR @j9liu !
I have validated the proposals of changes, but I'm a novice in github PR, and I don't know if I am supposed to add a commit by hand to implement the remaining work (typically, rename FCesiumMetadataValueAccess as CesiumMetadataValueAccess in CesiumMetaDataValue.cpp, and move the implementation to the bottom of the file as you suggested...) and perform the renaming in all places
author_association: CONTRIBUTOR
user: login: j9liu
created_at: 2025-03-20T14:05:03Z
body: Hi @Julot94! Yeah, the Github UI is limited. For those larger changes, we usually make them on our local branch and push them upstream. Does that help?
author_association: CONTRIBUTOR
user: login: Julot94
created_at: 2025-03-20T16:45:17Z
body: OK, I will do the changes asap
author_association: CONTRIBUTOR
user: login: Julot94
created_at: 2025-03-21T08:56:02Z
body: Hi @j9liu 
I have added a commit with the suggested improvements
author_association: CONTRIBUTOR
user: login: j9liu
created_at: 2025-03-26T17:40:12Z
body: Thanks @Julot94 ! Merging now.
author_association: CONTRIBUTOR