From 1698feaa7fbba4279b370ca4522745b0e892377a Mon Sep 17 00:00:00 2001
From: silverweed <silverweed14@proton.me>
Date: Tue, 12 Nov 2024 11:48:59 +0100
Subject: [PATCH] allow individually hovering over uncompressed page values

---
 src/hover.cpp   | 79 +++++++++++++++++++++++++++++++++++++++++++++++--
 src/render.cpp  |  3 +-
 src/rntuple.cpp |  4 ++-
 src/rntuple.h   |  1 +
 src/types.h     |  6 ++++
 5 files changed, 89 insertions(+), 4 deletions(-)

diff --git a/src/hover.cpp b/src/hover.cpp
index 433062f..827fe28 100644
--- a/src/hover.cpp
+++ b/src/hover.cpp
@@ -688,6 +688,66 @@ struct Sec_Hover_Fn {
     cur_field_off = start_off + size;
   }
 
+  void display_individual_elem(ROOT::Experimental::EColumnType type, u64 elem_idx, u64 n_elems, u64 field_len)
+  {
+    String8 title = push_str8f(arena, "Element %" PRIu64 " / %" PRIu64, elem_idx, n_elems);
+      titled_section(title.c(), [=] {
+      using CT = ROOT::Experimental::EColumnType;
+      switch(type) {
+      case CT::kIndex64:
+      case CT::kIndex32:
+      case CT::kUInt64:
+        return field_le<u64>("Value: %" PRIu64);
+      case CT::kByte:
+      case CT::kUInt8:
+        return field_le<u8>("Value: %u");
+      case CT::kChar:
+        return field_le<char>("Value: %c");
+      case CT::kInt8:
+        return field_le<i8>("Value: %d");
+      case CT::kReal64:
+        return field_le<f64>("Value: %f");
+      case CT::kReal32:
+        return field_le<f32>("Value: %f");
+      // TODO
+      // case CT::kReal16:
+      //   return field_le<f16>("Value: %f");
+      case CT::kInt64:
+        return field_le<i64>("Value: %" PRIi64);
+      case CT::kInt32:
+        return field_le<i32>("Value: %d");
+      case CT::kUInt32:
+        return field_le<u32>("Value: %u");
+      case CT::kInt16:
+        return field_le<i16>("Value: %d");
+      case CT::kUInt16:
+        return field_le<u16>("Value: %u");
+      case CT::kSwitch:
+        titled_section("Switch", [this] {
+          field_le<u64>("Idx: %" PRIu64);
+          field_le<u32>("Tag: %u");
+        });
+        return false;
+       // case CT::kSplitIndex64:
+       // case CT::kSplitIndex32:
+       // case CT::kSplitReal64:
+       // case CT::kSplitReal32:
+       // case CT::kSplitInt64:
+       // case CT::kSplitUInt64:
+       // case CT::kSplitInt32:
+       // case CT::kSplitUInt32:
+       // case CT::kSplitInt16:
+       // case CT::kSplitUInt16:
+       // case CT::kReal32Trunc:
+       // case CT::kReal32Quant:
+       // case CT::kBit:
+      default:
+        range("Payload", field_len);
+        return false;
+      }
+    });
+  }
+
   // ==============================================================
   //                  TOP-LEVEL SECTIONS
   // ==============================================================
@@ -918,8 +978,23 @@ struct Sec_Hover_Fn {
       if (zipped) {
         range("Payload", section.range.len - section.post_size - ROOTZIP_RANGE_LEN);
       } else {
-        // TODO: improve (hover individual elements?)
-        range("Payload", section.range.len - section.post_size);
+        Page_Info_Node *pinfo = (Page_Info_Node *)section.info;
+        b8 display_individual = !display_grouped;
+        if (display_individual && pinfo) {
+          assert(is_pow2(pinfo->bits_per_elem));
+          u64 n_elems = std::abs(pinfo->n_elems);
+          u64 field_len = pinfo->bits_per_elem / 8;
+          // align cur_field_off to the start of the element
+          u64 off_in_elems = off - cur_field_off;
+          off_in_elems = (off_in_elems & ~(field_len - 1));
+          u64 elem_idx = off_in_elems / field_len;
+          cur_field_off += off_in_elems;
+          display_individual_elem(pinfo->elem_type, elem_idx, n_elems, field_len);
+          // advance to the end of the section
+          cur_field_off += field_len * (n_elems - elem_idx - 1);
+        } else {
+          range("Payload", section.range.len - section.post_size);
+        }
       }
       b8 has_checksum = section.post_size > 0;
       if (has_checksum)
diff --git a/src/render.cpp b/src/render.cpp
index e97fe52..15c64f7 100644
--- a/src/render.cpp
+++ b/src/render.cpp
@@ -411,7 +411,8 @@ void update_and_render(Arena *arena, App_State &app, f32 delta_time_ms)
                                                            app.inspected_file.mem, hover_display_grouped, old_version);
         ImGui::TextColored(ImColor(0.5f, 0.5f, 0.5f), "(Hint: hold Alt for single-field hover information)");
         ImGui::TextColored(ImColor(0.5f, 0.5f, 0.5f), "(Hint: Shift-Click to jump to the start of this section)");
-        imgui_render_string_tree(scratch.arena, hover_info.desc->head, hover_info.highlighted_desc);
+        if (hover_info.desc)
+          imgui_render_string_tree(scratch.arena, hover_info.desc->head, hover_info.highlighted_desc);
         app.viewer.hovered_range = hover_info.rng;
 
         // Shift-clicking on a page section will update the current page in the legend
diff --git a/src/rntuple.cpp b/src/rntuple.cpp
index ab7123a..d288199 100644
--- a/src/rntuple.cpp
+++ b/src/rntuple.cpp
@@ -242,7 +242,8 @@ RNTuple_Data get_rntuple_data(Arena *arena, const Inspected_File &file, const TF
 
     for (const RClusterDescriptor::RColumnRange &col_range : cluster_desc.GetColumnRangeIterable()) {
       const auto &col_descriptor = descriptor.GetColumnDescriptor(col_range.fPhysicalColumnId);
-      const char *elem_type_name = RColumnElementBase::GetColumnTypeName(col_descriptor.GetType());
+      ROOT::Experimental::EColumnType elem_type = col_descriptor.GetType();
+      const char *elem_type_name = RColumnElementBase::GetColumnTypeName(elem_type);
       const auto &field_desc = descriptor.GetFieldDescriptor(col_descriptor.GetFieldId());
       const String8 owner_field_name = build_fully_qualified_field_name(arena, descriptor, &field_desc);
 
@@ -258,6 +259,7 @@ RNTuple_Data get_rntuple_data(Arena *arena, const Inspected_File &file, const TF
         // If in the future we get RNTuples with more than 4B clusters we can just change the type to u64.
         assert(cluster_desc.GetId() <= UINT_MAX);
         pinfo->cluster_id = cluster_desc.GetId();
+        pinfo->elem_type = elem_type;
         pinfo->elem_type_name = push_str8f(arena, "%s", elem_type_name);
         pinfo->owner_field_name = owner_field_name;
         pinfo->bits_per_elem = col_descriptor.GetBitsOnStorage();
diff --git a/src/rntuple.h b/src/rntuple.h
index 258aa2d..f5f2529 100644
--- a/src/rntuple.h
+++ b/src/rntuple.h
@@ -21,6 +21,7 @@ struct Page_Info_Node {
   u64 page_id;
 
   u8 bits_per_elem;
+  ROOT::Experimental::EColumnType elem_type;
   String8 elem_type_name;
   String8 owner_field_name;
 
diff --git a/src/types.h b/src/types.h
index 2c6abf8..9f2bcc5 100644
--- a/src/types.h
+++ b/src/types.h
@@ -11,6 +11,7 @@ using b32 = int32_t;
 using b32x = int;
 
 using i8 = int8_t;
+using i16 = int16_t;
 using i32 = int32_t;
 using i64 = int64_t;
 
@@ -46,3 +47,8 @@ u16 bswap(u16 x) { return x; }
 u32 bswap(u32 x) { return x; }
 u64 bswap(u64 x) { return x; }
 #endif
+
+constexpr b8 is_pow2(u64 x)
+{
+  return (x & (x - 1)) == 0;
+}