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 0000000..d1e5137 Binary files /dev/null and b/testdata/multi.root differ