From 9bf2b1e0c563bf8f096bf34a773ab87dd93b5d8e Mon Sep 17 00:00:00 2001
From: silverweed <silverweed14@proton.me>
Date: Fri, 24 Jan 2025 12:05:32 +0100
Subject: [PATCH] improve title

---
 src/render.cpp      |  30 +++++++++++++++++------
 src/str.cpp         |  11 +++++++++
 src/tfile.cpp       |  58 ++++++++++++++++++++++++--------------------
 testdata/multi.root | Bin 0 -> 2382 bytes
 4 files changed, 65 insertions(+), 34 deletions(-)
 create mode 100644 testdata/multi.root

diff --git a/src/render.cpp b/src/render.cpp
index 50aae8f..05ed1fb 100644
--- a/src/render.cpp
+++ b/src/render.cpp
@@ -143,19 +143,33 @@ void init_viewer(Arena *arena, App_State &app, u16 n_cols)
   viewer.mem_edit = make_memory_editor(app, (i32)n_cols);
   
   // Init title
-  if (app.tfile_data.sections[Sec_RNTuple_Anchor].head) {
+  u32 n_ntuples = app.tfile_data.sections[Sec_RNTuple_Anchor].count;
+  if (n_ntuples) {
     Temp scratch = scratch_begin(&arena, 1);
     defer { scratch_end(scratch); };
 
-    const RNTuple_Anchor_Info *anchor = (const RNTuple_Anchor_Info *)app.tfile_data.sections[Sec_RNTuple_Anchor].head->info;
-    String8 ntpl_desc = rntuple_description(scratch.arena, anchor->anchor);
-    if (rntuple_is_old_version(anchor->anchor)) {
-      viewer.col_title[0] = 1.f;
-      viewer.title = push_str8f(arena, "\"%s\" (%s) from file \"%s\" ** old version, some data missing! **", 
-                                anchor->name.c(), ntpl_desc.c(), app.inspected_file.name.c());
+    if (n_ntuples == 1) {
+      Section *sec = app.tfile_data.sections[Sec_RNTuple_Anchor].head;
+      const RNTuple_Anchor_Info *anchor = (const RNTuple_Anchor_Info *)sec->info;
+      String8 ntpl_desc = rntuple_description(scratch.arena, anchor->anchor);
+      if (rntuple_is_old_version(anchor->anchor)) {
+        viewer.col_title[0] = 1.f;
+        viewer.title = push_str8f(arena, "\"%s\" (%s) from file \"%s\" ** old version, some data missing! **", 
+                                  anchor->name.c(), ntpl_desc.c(), app.inspected_file.name.c());
+      } else {
+        viewer.col_title[0] = viewer.col_title[1] = viewer.col_title[2] = 1.f;
+        viewer.title = push_str8f(arena, "\"%s\" (%s) from file \"%s\"", anchor->name.c(), ntpl_desc.c(), app.inspected_file.name.c());
+      }
     } else {
+      String8 title = str8("RNTuples");
+      for (Section *sec = app.tfile_data.sections[Sec_RNTuple_Anchor].head; sec; sec = sec->next) {
+        const RNTuple_Anchor_Info *anchor = (const RNTuple_Anchor_Info *)sec->info;
+        title = str8_concat(scratch.arena, title, push_str8f(scratch.arena, " \"%s\",", anchor->name));
+      }
+      title.size -= 1; // strip trailing comma
+      title = str8_concat(scratch.arena, title, push_str8f(scratch.arena, " from file \"%s\"", app.inspected_file.name.c()));
       viewer.col_title[0] = viewer.col_title[1] = viewer.col_title[2] = 1.f;
-      viewer.title = push_str8f(arena, "\"%s\" (%s) from file \"%s\"", anchor->name.c(), ntpl_desc.c(), app.inspected_file.name.c());
+      viewer.title = str8_from_buf(arena, title.str, title.size);
     }
   } else {
     viewer.col_title[0] = viewer.col_title[1] = viewer.col_title[2] = 1.f;
diff --git a/src/str.cpp b/src/str.cpp
index 8898c7b..7872403 100644
--- a/src/str.cpp
+++ b/src/str.cpp
@@ -40,6 +40,17 @@ String8 str8_from_buf(Arena *arena, const u8 *buf, u64 size)
   return s;
 }
 
+String8 str8_concat(Arena *arena, String8 a, String8 b)
+{
+  String8 res;
+  res.size = a.size + b.size;
+  res.str = arena_push_array_nozero<u8>(arena, res.size + 1);
+  memcpy(res.str, a.str, a.size);
+  memcpy(res.str + a.size, b.str, b.size);
+  res.str[res.size] = 0;
+  return res;
+}
+
 internal
 String8 to_pretty_size(Arena *arena, u64 bytes)
 {
diff --git a/src/tfile.cpp b/src/tfile.cpp
index 9a14a4e..84ecd1b 100644
--- a/src/tfile.cpp
+++ b/src/tfile.cpp
@@ -79,8 +79,9 @@ enum {
 // Examines the innards of a TFile to get byte range info about the TKeys.
 // Additionally, it returns the names of all the RNTuples stored in it.
 // `data` should point to the beginning of a ROOT file.
+// If `ntpl_name` is non-empty, skip all RNTuples that are not the one specified.
 internal
-b8 walk_tkeys(Arena *arena, const u8 *data, u64 data_len, u32 flags, TFile_Data &tfile_data)
+b8 walk_tkeys(Arena *arena, const u8 *data, u64 data_len, u32 flags, TFile_Data &tfile_data, String8 ntpl_name)
 { 
   u64 cur = 4; // offset of header version
   if (data_len < cur) {
@@ -270,30 +271,34 @@ b8 walk_tkeys(Arena *arena, const u8 *data, u64 data_len, u32 flags, TFile_Data
       if (key_has_class_name("ROOT::RNTuple") || key_has_class_name("ROOT::Experimental::RNTuple")) {
         u64 name_off = cname_off + cname_len + 1;
         u8 name_len = data[name_off];
-        RNTuple_Anchor_Info *rntuple_info = arena_push<RNTuple_Anchor_Info>(arena);
-        rntuple_info->offset_in_file = cur;
-        if (name_len) {
-          rntuple_info->name = str8_from_buf(arena, data + name_off + 1, name_len);
+        const char *rntuple_name = (const char *)data + name_off + 1;
+        if (!ntpl_name.size || strncmp((const char *)ntpl_name.str, rntuple_name, name_len) == 0) {
+          RNTuple_Anchor_Info *rntuple_info = arena_push<RNTuple_Anchor_Info>(arena);
+          rntuple_info->offset_in_file = cur;
+          if (name_len) {
+            rntuple_info->name = str8_from_buf(arena, data + name_off + 1, name_len);
+          }
+
+          u64 anchor_seek = cur + keylen;
+          RNTuple_Anchor_On_Disk anchor;
+          memcpy(&anchor, data + anchor_seek, sizeof(anchor));
+
+          rntuple_info->anchor = ROOT::Experimental::Internal::CreateAnchor(
+            bswap(anchor.epoch_be), bswap(anchor.major_be), bswap(anchor.minor_be), bswap(anchor.patch_be),
+            bswap(anchor.seek_header_be), bswap(anchor.nbytes_header_be), bswap(anchor.len_header_be),
+            bswap(anchor.seek_footer_be), bswap(anchor.nbytes_footer_be), bswap(anchor.len_footer_be),
+            bswap(anchor.max_key_size_be));
+
+          Section *sec_anchor = push_section(arena, tfile_data, Sec_RNTuple_Anchor);
+          sec_anchor->info = rntuple_info;
+          sec_anchor->id = Sec_RNTuple_Anchor;
+          sec_anchor->range.start = anchor_seek;
+          sec_anchor->range.len = n_bytes - keylen;
+          sec_anchor->pre_size = keylen;
+          sec_anchor->post_size = 8;
+        } else {
+          fprintf(stderr, "Note: skipped RNTuple '%s' because it doesn't match the name '%s' we're looking for.\n", rntuple_name, ntpl_name.c());
         }
-
-        u64 anchor_seek = cur + keylen;
-        RNTuple_Anchor_On_Disk anchor;
-        memcpy(&anchor, data + anchor_seek, sizeof(anchor));
-
-        rntuple_info->anchor = ROOT::Experimental::Internal::CreateAnchor(
-          bswap(anchor.epoch_be), bswap(anchor.major_be), bswap(anchor.minor_be), bswap(anchor.patch_be),
-          bswap(anchor.seek_header_be), bswap(anchor.nbytes_header_be), bswap(anchor.len_header_be),
-          bswap(anchor.seek_footer_be), bswap(anchor.nbytes_footer_be), bswap(anchor.len_footer_be),
-          bswap(anchor.max_key_size_be));
-
-        Section *sec_anchor = push_section(arena, tfile_data, Sec_RNTuple_Anchor);
-        sec_anchor->info = rntuple_info;
-        sec_anchor->id = Sec_RNTuple_Anchor;
-        sec_anchor->range.start = anchor_seek;
-        sec_anchor->range.len = n_bytes - keylen;
-        sec_anchor->pre_size = keylen;
-        sec_anchor->post_size = 8;
-        
       } else if (key_has_class_name("RBlob")) {
         if (!sections[Sec_Page].head) {
           push_section(arena, tfile_data, Sec_Page)->pre_size = keylen;
@@ -365,6 +370,7 @@ b8 walk_tkeys(Arena *arena, const u8 *data, u64 data_len, u32 flags, TFile_Data
   return true;
 }
 
+// Creates the RNTuple_Header and Footer sections by associating them to their proper RBlobs and Anchor.
 internal
 void map_rntuple_rblobs(Arena *arena, TFile_Data &tfile_data)
 {
@@ -425,9 +431,9 @@ void map_rntuple_rblobs(Arena *arena, TFile_Data &tfile_data)
 }
 
 internal
-b8 get_tfile_data(Arena *arena, const Inspected_File &file, u32 walk_tkeys_flags, String8 &ntpl_name, TFile_Data &tfile_data)
+b8 get_tfile_data(Arena *arena, const Inspected_File &file, u32 walk_tkeys_flags, String8 ntpl_name, TFile_Data &tfile_data)
 {  
-  b8 success = walk_tkeys(arena, file.mem, file.size, walk_tkeys_flags, tfile_data);
+  b8 success = walk_tkeys(arena, file.mem, file.size, walk_tkeys_flags, tfile_data, ntpl_name);
   if (success) {
     // TODO: if ntpl_name is non-empty, only pick the given RNTuple
     map_rntuple_rblobs(arena, tfile_data);
diff --git a/testdata/multi.root b/testdata/multi.root
new file mode 100644
index 0000000000000000000000000000000000000000..d1e51377d57255c56c6c92e2e0e9f04db9b6b8fa
GIT binary patch
literal 2382
zcmb7G3s6%>6uo&#AcT+nL5Vaf$XEU#Ukwo@@)H3SD6J@ohz62@fY`<w6e&`$Q6ngV
zqEbbv4x_fE7BGAiwX{06DptqR4k%PnaI`|zNp~NKBamtJ&TMw~y}f(R?!EWCWQ#-+
zfDuCg-~;Hz08s1DI07{)Y8)B>WfXvBg!(-|Nm+REz;B@@Pb!o7UUkZrCuqq#nUwkn
zt>fj3n|;6sC~6;sIA-*OMHxZ?Gc#8x$#BcYl-?c#9s7~K06UNe2Q{@XL;1g?z9g}0
zNq^@z*ga?uY<zkgS14KykQ`4*0jLFV{Kow?ySADDJ1PauG1*XjKXFTzB(U@R+`N6=
zJUlO|++Yn|C|V`yF}DWxvn_+Bp2GE&)`sjF!uwOJR`yE4rCaHVMP5cydVxjcf?~6^
zLyi0CXqzTXhaw+Ohay66u)R&E06<P5>7>e`nF_T!05agnYLq=-%=X~Krix|7^1U^6
z4UNi6*SdT9hem+LFfcZ^X0h!YT|IpKgE?WmsMv(WlvMs&p;(eHm2D|3DlRGAA>XAa
z-&<LApyqJx(Yg~~pK3UL_S?pDO)bi{j*Bp#6UwC-gDfH>-aqVw;ZM<3``YKWmm_vE
z@+zg{xf3oV^YPrH0i^1yl-d)}OHk2Vb4hnq_)@Kbuk7lsQmIrWXts|siXMg}bvLcL
znZ{&5xSa84b68%2!r}#y1T*!GvCCK*DJJ08Yr$U(pitv0=mhWy0?jq}{?Ak$csmc^
zOP8a58F8#t@@!s7#a~QtKf<}U#Kz_Jh0Da_O-_{0hr1q$Wel{afINE)iS4aPBdUW2
z+YqNmU|?KKLataSpoHQi<pQBuIJS(!R4*5(dz5b2h#O-)#HoA4eJsr#xx|XngS^>j
z8UZx)Yl{f@fh*pZtW^Hw+9BUPMMZ0fNAx48-N7Thm<_%PK=<8?cSrG>#2FVkJ|^ux
zc}yPm!iLMAG=vtFks~pPmt+f8WeTzvXRQ&LhKX`l3pcWb8CmNEeAMMglA{=56u3`=
zd`k1K)|fVqana^=(FuiTE`AYy)iz+6Y1yJfJeyKmAN{C9#Xs;r-15BRhSgK2rV*xX
z@TgJX9NLVX#eO3WL7D044<c1b`uAOy?`&0gS#~{`QvR^8vSa#y*?^PyZ@q_>4efnL
zsH~i2ck=YNt-C$=;#^_#cds{O3B<!`*V|5QFgk0wR4SG2Ns23v+(>;rHF+uRv-Yx2
z%tary_Vl;)j?i_sHcuN+u6WvTY+7ba>h21Y^Ih>R?$q=4$G^8ZyT@ow%{<!2BctA5
z*#$;)mQ_R!_sx%Wp84ub+T65Gx70-CMz4y&%iBQyoOSq4nrU>zt1CHKWw%oLX2%aW
z-#!hVEW<uioy`Him$jsna+%jTr#j`y#Bn?M$pKaO(9gW1GhF+B-5K}C_38*}QrB^x
zLVA_+_LuP`ELDN>pT6oghfTYi6Hvf9$=qX-L2=gv1qXI4DC5-<$NVmiG_rl9V?F8k
zM*HY@$Na{L%^CnyN2K=E52Aua+E-5^aVKftd6h4u8=yUMB>cd@j4X+_*GdWWn42LT
zB#u>v-0KTzHvo4=(G@F2fpmMSSpM=C!vDfyZt;GlX|NF=C)G_%Y@!P4<Z2<d|7{g4
zqd@`#F3A(w{SEmX#<jhvjzf(yo^f~0T8|NBajbPuo!B33Oi-ybx{j_cLyyVSH!w6Z
znqq8XVrph?VPR=yJ$33d8y0K2?FVf3jG41$+1bx_aBy^Tc6M={<L2h>;pye&?c?j~
zH`hNPATTI6IAq>@4pPJg3&IvI3J>Qkj);tmibj=fZxX(+gLA{|s*3?@d9Y{&;;vO6
zbtAQ$N~=C<E^nefVwE_kgY_|HR!)%6EJg2q^ilUmE%;jXQG-8GA9Ho(y<4lRMR||@
zLmxMi@-(K8E;w@-;Rtu4#&EUjq7M49k*4_!swG|bx%&AeT})b;x&3j_FoE9hAc0qt
cIZo;#`cHsAMw9+ipr?5K`wBU7dDpZ33wx)MA^-pY

literal 0
HcmV?d00001