diff --git a/src/hover.cpp b/src/hover.cpp index 77044d1..ae2ecf5 100644 --- a/src/hover.cpp +++ b/src/hover.cpp @@ -307,7 +307,7 @@ struct Sec_Hover_Fn { // An unspecified range of bytes void range(const char *desc, u64 range_len, Display_Range_Fn display_val = hover_display_generic_range) { - if (cur_field_off + range_len > section.range.end()) + if (range_len == 0 || cur_field_off + range_len > section.range.end()) return; String8_Node *dsc = display_val(arena, info.desc, desc, data + cur_field_off, range_len); b8 hovered = cur_field_off <= off && off < cur_field_off + range_len; @@ -340,11 +340,9 @@ struct Sec_Hover_Fn { return was_zipped; } - // Returns the key len - u16 tkey(const char *title = "TKey") + void tkey(const char *title = "TKey") { - u16 keylen = 0; - titled_section(title, [this, &keylen] { + titled_section(title, [this] { u16 version_be; if (!read(&version_be, cur_field_off + 4)) return; @@ -363,7 +361,7 @@ struct Sec_Hover_Fn { }); field_be<u32>("Obj Len: %u"); field<u32>("Datetime: ", hover_display_datetime_str); - field_be<u16>("Key Len: %u", &keylen); + field_be<u16>("Key Len: %u"); field_be<u16>("Cycle: %u"); if (is_big) { field_be<u64>("Seek Key: 0x%" PRIX64); @@ -376,8 +374,6 @@ struct Sec_Hover_Fn { field_str8<u8>("Obj Name: %s"); field_str8<u8>("Obj Title: %s"); }, HoverSec_HideIfNotHovered); - - return keylen; } void envelope_preamble() @@ -1072,6 +1068,9 @@ struct Sec_Hover_Fn { String8 name = class_name->size ? *class_name : str8("(Unknown)"); titled_section(name.c(), [this] { tkey(); + // Not sure what this is, but sometimes we get extra bytes from the end of the key + // to the start of the actual payload. + range("???", section.range.start - cur_field_off); range("Payload", section.range.len - section.post_size); }); } diff --git a/src/render.cpp b/src/render.cpp index ae06f45..9cb6e8a 100644 --- a/src/render.cpp +++ b/src/render.cpp @@ -137,10 +137,30 @@ MemoryEditor make_memory_editor(App_State &app, i32 n_cols) } internal -void init_viewer(App_State &app, u16 n_cols) +void init_viewer(Arena *arena, App_State &app, u16 n_cols) { Viewer &viewer = app.viewer; viewer.mem_edit = make_memory_editor(app, (i32)n_cols); + + // Init title + if (app.tfile_data.sections[Sec_RNTuple_Anchor].head) { + Temp scratch = scratch_begin(&arena, 1); + defer { scratch_end(scratch); }; + + const ROOT::RNTuple &anchor = *(const ROOT::RNTuple *)app.tfile_data.sections[Sec_RNTuple_Anchor].head->info; + String8 ntpl_desc = rntuple_description(scratch.arena, anchor); + if (rntuple_is_old_version(anchor)) { + viewer.col_title[0] = 1.f; + viewer.title = push_str8f(arena, "\"%s\" (%s) from file \"%s\" ** old version, some data missing! **", + app.ntpl_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\"", app.ntpl_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, "(no RNTuple) from file \"%s\"", app.inspected_file.name.c()); + } #define COL(c, r, g, b) viewer.c[0] = r/255.0, viewer.c[1] = g/255.0, viewer.c[2] = b/255.0 COL(col_key, 0, 100, 50); @@ -251,7 +271,7 @@ void viewer_jump_to_page_list(App_State &app, u64 page_list_idx) } internal -void viewer_jump_to_section(App_State &app, Section_Id id, u64 sec_idx) +void viewer_jump_to_section(App_State &app, Section_Id id, u64 &sec_idx) { // special cases if (id == Sec_Page_List) { @@ -314,17 +334,14 @@ void update_and_render(Arena *arena, App_State &app, f32 delta_time_ms) Temp scratch = scratch_begin(&arena, 1); defer { scratch_end(scratch); }; + printf("-----------------------\n"); + const auto main_win_flags = ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoDecoration; if (ImGui::Begin("main", nullptr, main_win_flags)) { - // FIXME :multiple_anchors: - const ROOT::RNTuple &anchor = *(const ROOT::RNTuple *)app.tfile_data.sections[Sec_RNTuple_Anchor].head->info; - String8 ntpl_desc = rntuple_description(scratch.arena, anchor); - if (rntuple_is_old_version(anchor)) { - ImGui::TextColored(ImColor(1.f, 0.f, 0.f), "\"%s\" (%s) from file \"%s\" ** old version, some data missing! **", - app.ntpl_name.c(), ntpl_desc.c(), app.inspected_file.name.c()); - } else { - ImGui::Text("\"%s\" (%s) from file \"%s\"", app.ntpl_name.c(), ntpl_desc.c(), app.inspected_file.name.c()); + { + f32 *c = app.viewer.col_title; + ImGui::TextColored(ImColor(c[0], c[1], c[2]), "%s", app.viewer.title.c()); } // Draw stats @@ -397,19 +414,24 @@ void update_and_render(Arena *arena, App_State &app, f32 delta_time_ms) if (ImGui::Button(sec_name.c())) viewer_jump_to_section(app, static_cast<Section_Id>(i), app.viewer.latest_section_gone_to[i]); ImGui::SameLine(); - ImGui::Text("%s", to_pretty_size(scratch.arena, app.tfile_data.sections[i].tot_size).c()); + ImGui::Text("%s", to_pretty_size(scratch.arena, app.tfile_data.sections[i].tot_size).c()); if (app.tfile_data.sections[i].count > 1) { + // } else { + // ImGui::Text("%s (%u)", to_pretty_size(scratch.arena, app.tfile_data.sections[i].tot_size).c(), app.tfile_data.sections[i].count); ImGui::SameLine(); i64 sec_to_go_to = app.viewer.latest_section_gone_to[i]; - ImGui::PushItemWidth(80.f); + ImGui::PushItemWidth(120.f); if (ImGui::InputScalar(push_str8f(scratch.arena, "##%s_viewed", sec_name.c()).c(), ImGuiDataType_S64, &sec_to_go_to, &step_i64, nullptr, "%u") && ImGui::IsItemDeactivatedAfterEdit()) { - viewer_jump_to_section(app, static_cast<Section_Id>(i), sec_to_go_to); + app.viewer.latest_section_gone_to[i] = sec_to_go_to; + viewer_jump_to_section(app, static_cast<Section_Id>(i), app.viewer.latest_section_gone_to[i]); } ImGui::PopItemWidth(); + ImGui::SameLine(); + ImGui::Text(" / %u", app.tfile_data.sections[i].count); } } @@ -494,7 +516,7 @@ void update_and_render(Arena *arena, App_State &app, f32 delta_time_ms) ImGui::TextColored(ImColor(0.f, 0.65f, 0.f), "Offset: 0x%" PRIX64, hovered_off - 1); Section hovered_section = find_section(app, hovered_off - 1); b8 hover_display_grouped = !(app.user_input.key_state[KEY_ALT] & KEY_STATE_IS_DOWN); - b8 old_version = rntuple_is_old_version(anchor); + b8 old_version = hovered_section.from_old_version_rntuple; Sec_Hover_Info hover_info = get_section_hover_info(scratch.arena, hovered_section, hovered_off - 1, 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)"); diff --git a/src/render.h b/src/render.h index f29a4df..a1d05b9 100644 --- a/src/render.h +++ b/src/render.h @@ -10,6 +10,9 @@ struct Viewer { #ifndef RNT_NO_GFX MemoryEditor mem_edit; + String8 title; + f32 col_title[3]; + b8 highlight_cluster; u64 highlighted_cluster; diff --git a/src/rntuple.cpp b/src/rntuple.cpp index 4cfe89f..dd674fe 100644 --- a/src/rntuple.cpp +++ b/src/rntuple.cpp @@ -19,12 +19,6 @@ void compute_tot_sections_size(Sections *sections) } } -internal -b8 rntuple_is_old_version(const ROOT::RNTuple &ntuple) -{ - return ntuple.GetVersionEpoch() == 0 && ntuple.GetVersionMajor() < 3; -} - internal String8 rntuple_description(Arena *arena, const ROOT::RNTuple &ntuple) { @@ -491,7 +485,7 @@ Section find_section(App_State &app, u64 off, i64 hilite_cluster = -1) Section sec {}; - for (u32 i = 1; i < Sec_COUNT; ++i) { + for (u32 i = 1; i < Sec_COUNT_Regular; ++i) { for (Section *sec = app.tfile_data.sections[i].head; sec; sec = sec->next) { if (sec->range.start - sec->pre_size <= off && off < sec->range.end()) { return *sec; @@ -499,90 +493,104 @@ Section find_section(App_State &app, u64 off, i64 hilite_cluster = -1) } } - u64 rblob_sz = app.tfile_data.sections[Sec_Page].head->pre_size; + // Page / PageList lookup. + // If we don't have a Sec_Page section it means we don't have any RNTuple, so skip it. + if (app.tfile_data.sections[Sec_Page].head) { + u64 rblob_sz = app.tfile_data.sections[Sec_Page].head->pre_size; - /// Page fast lookup (relative to app.last_pinfo) - { - // fast case: `off` is in the same page info as previous `off`. - for (i32 i = 0; i < 2; ++i) { - u64 pre_size = app.last_pinfo->is_first_in_cluster ? rblob_sz : 0; - if (app.last_pinfo->range.start - pre_size < off && off < app.last_pinfo->range.end()) { + /// Page fast lookup (relative to app.last_pinfo) + { + // fast case: `off` is in the same page info as previous `off`. + for (i32 i = 0; i < 2; ++i) { + u64 pre_size = app.last_pinfo->is_first_in_cluster ? rblob_sz : 0; + if (app.last_pinfo->range.start - pre_size <= off && off < app.last_pinfo->range.end()) { + sec.id = Sec_Page; + sec.range = app.last_pinfo->range; + sec.pre_size = pre_size; + sec.post_size = app.last_pinfo->checksum_size(); + sec.highlighted = hilite_cluster >= 0 && app.last_pinfo->cluster_id == (u64)hilite_cluster; + sec.info = app.last_pinfo; + return sec; + } + + // Try another common and still fast case: `off is in the next page info as the previous. + if (app.last_pinfo->next) // don't check if it's checksum, since it's the first byte of the page + app.last_pinfo = app.last_pinfo->next; + else + break; + } + } + + // Check if we're in a page list + // @Speed: linear search through all cluster groups + for (u64 cg_idx = 0; cg_idx < rdata.n_cluster_groups; ++cg_idx) { + Cluster_Group_Info &cg_info = rdata.cluster_groups[cg_idx]; + if (cg_info.rng_page_list.start - rblob_sz <= off && off < cg_info.rng_page_list.end()) { + sec.id = Sec_Page_List; + sec.range = cg_info.rng_page_list; + sec.pre_size = rblob_sz; + sec.post_size = 8; + return sec; + } + } + + // Slow page group lookup, ideally only done once per render when last_pinfo is invalid. + for (Page_Info_Chunk *chunk = rdata.page_chunks; chunk; chunk = chunk->next) { + // If we're at the start of a chunk, return a fake Sec_Page used to highlight the RBlob header bytes. + if (chunk->range.start - rblob_sz <= off && off < chunk->range.start) { sec.id = Sec_Page; - sec.range = app.last_pinfo->range; - sec.pre_size = pre_size; - sec.post_size = app.last_pinfo->checksum_size(); - sec.highlighted = hilite_cluster >= 0 && app.last_pinfo->cluster_id == (u64)hilite_cluster; - sec.info = app.last_pinfo; + sec.range = { chunk->range.start, 0 }; + sec.pre_size = rblob_sz; return sec; } - // Try another common and still fast case: `off is in the next page info as the previous. - if (app.last_pinfo->next) // don't check if it's checksum, since it's the first byte of the page - app.last_pinfo = app.last_pinfo->next; - else - break; - } - } - - // Check if we're in a page list - // @Speed: linear search through all cluster groups - for (u64 cg_idx = 0; cg_idx < rdata.n_cluster_groups; ++cg_idx) { - Cluster_Group_Info &cg_info = rdata.cluster_groups[cg_idx]; - if (cg_info.rng_page_list.start - rblob_sz <= off && off < cg_info.rng_page_list.end()) { - sec.id = Sec_Page_List; - sec.range = cg_info.rng_page_list; - sec.pre_size = rblob_sz; - sec.post_size = 8; - return sec; - } - } + if (chunk->range.start <= off && off < chunk->range.end()) { + for (u64 group_idx = chunk->first_group; group_idx < rdata.n_page_groups; ++group_idx) { + const Page_Info_Group &group = rdata.page_groups[group_idx]; + if (off < group.range.start || off >= group.range.end()) + continue; - // Slow page group lookup, ideally only done once per render when last_pinfo is invalid. - for (Page_Info_Chunk *chunk = rdata.page_chunks; chunk; chunk = chunk->next) { - // If we're at the start of a chunk, return a fake Sec_Page used to highlight the RBlob header bytes. - if (chunk->range.start - rblob_sz <= off && off < chunk->range.start) { - sec.id = Sec_Page; - sec.range = { chunk->range.start, 0 }; - sec.pre_size = rblob_sz; - return sec; - } - - if (chunk->range.start <= off && off < chunk->range.end()) { - for (u64 group_idx = chunk->first_group; group_idx < rdata.n_page_groups; ++group_idx) { - const Page_Info_Group &group = rdata.page_groups[group_idx]; - if (off < group.range.start || off >= group.range.end()) - continue; - - for (Page_Info_Node *pinfo = group.first; pinfo; pinfo = pinfo->next) { - u64 pre_size = pinfo->is_first_in_cluster ? rblob_sz : 0; - if (pinfo->range.start - pre_size <= off && off < pinfo->range.end()) { - app.last_pinfo = pinfo; - sec.id = Sec_Page; - sec.range = pinfo->range; - sec.pre_size = pre_size; - sec.post_size = pinfo->checksum_size(); - sec.highlighted = hilite_cluster >= 0 && pinfo->cluster_id == (u64)hilite_cluster; - sec.info = pinfo; - return sec; + for (Page_Info_Node *pinfo = group.first; pinfo; pinfo = pinfo->next) { + u64 pre_size = pinfo->is_first_in_cluster ? rblob_sz : 0; + if (pinfo->range.start - pre_size <= off && off < pinfo->range.end()) { + app.last_pinfo = pinfo; + sec.id = Sec_Page; + sec.range = pinfo->range; + sec.pre_size = pre_size; + sec.post_size = pinfo->checksum_size(); + sec.highlighted = hilite_cluster >= 0 && pinfo->cluster_id == (u64)hilite_cluster; + sec.info = pinfo; + return sec; + } } } - } - fprintf(stderr, "Offset 0x%" PRIX64 " is in chunk 0x%" PRIX64 " - 0x%" PRIX64 ", but found in no page_info range!\n", - off, chunk->range.start, chunk->range.end()); - assert(false); + fprintf(stderr, "Offset 0x%" PRIX64 " is in chunk 0x%" PRIX64 " - 0x%" PRIX64 ", but found in no page_info range!\n", + off, chunk->range.start, chunk->range.end()); + assert(false); + } } } - // Copypasted from the fast page lookup + // Lookup other root objects. + // @Copypasted from the fast page lookup if (app.tfile_data.sections[Sec_Other].count > 0) { // fast case: `off` is in the same obj as previous `off`. for (i32 i = 0; i < 2; ++i) { const Section &other_sec = *app.last_other_root_obj; - if (other_sec.range.start - other_sec.pre_size < off && off < other_sec.range.end()) { + if (i == 1) + printf("checking if 0x%lX is in the next root obj, i.e. 0x%lX - 0x%lX - %s\n", + off, other_sec.range.start - other_sec.pre_size, other_sec.range.end(), + (other_sec.range.start - other_sec.pre_size <= off && off < other_sec.range.end()) ? "yes" : "no"); + // else + // printf("checking if 0x%lX is in the first root obj, i.e. 0x%lX - 0x%lX - %s\n", + // off, other_sec.range.start - other_sec.pre_size, other_sec.range.end(), + // (other_sec.range.start - other_sec.pre_size < off && off < other_sec.range.end()) ? "yes" : "no"); + if (other_sec.range.start - other_sec.pre_size <= off && off < other_sec.range.end()) { return other_sec; } + // Try another common and still fast case: `off is in the next root object as the previous. if (app.last_other_root_obj->next) app.last_other_root_obj = app.last_other_root_obj->next; else @@ -590,9 +598,12 @@ Section find_section(App_State &app, u64 off, i64 hilite_cluster = -1) } // Slow linear lookup, ideally only done once per render when last_other_root_obj is invalid. + printf("page fault\n"); for (Section *other_sec = app.tfile_data.sections[Sec_Other].head; other_sec; other_sec = other_sec->next) { if (other_sec->range.start - other_sec->pre_size <= off && off < other_sec->range.end()) { app.last_other_root_obj = other_sec; + printf("off 0x%lX: last root obj set to 0x%lX - 0x%lX\n", + off, other_sec->range.start - other_sec->pre_size, other_sec->range.end()); return *other_sec; } } diff --git a/src/rntuple.h b/src/rntuple.h index 535d50b..bd2049c 100644 --- a/src/rntuple.h +++ b/src/rntuple.h @@ -65,8 +65,11 @@ enum Section_Id { Sec_RNTuple_Header, Sec_RNTuple_Footer, Sec_Page_List, - Sec_Page, Sec_Free_Slot, + + Sec_COUNT_Regular, + // sections past this have a special handling in find_section() (i.e. fast lookup) + Sec_Page = Sec_COUNT_Regular, // any other object stored in a TKey Sec_Other, @@ -78,9 +81,10 @@ struct Section { Section_Id id; Byte_Range range; - u64 pre_size; // usually the TKey header, excluded from `range` - u64 post_size; // usually the checksum, included in `range` + u32 pre_size; // usually the TKey header, excluded from `range` + u32 post_size; // usually the checksum, included in `range` b8 highlighted; + b8 from_old_version_rntuple; // Optional pointer to the specific information for the section. // Here are the actual types: @@ -103,8 +107,8 @@ internal const String8 section_names[] = { str8("RNTuple Header"), str8("RNTuple Footer"), str8("Page List"), - str8("Page"), str8("Free Slot"), + str8("Page"), str8("Other"), }; static_assert(countof(section_names) == Sec_COUNT); @@ -169,3 +173,10 @@ struct RNTuple_Data { internal Section *push_section(Arena *arena, TFile_Data &tdata, Section_Id id); + +internal +b8 rntuple_is_old_version(const ROOT::RNTuple &ntuple) +{ + return ntuple.GetVersionEpoch() == 0 && ntuple.GetVersionMajor() < 3; +} + diff --git a/src/rntviewer.cpp b/src/rntviewer.cpp index 4d23096..8cd5200 100644 --- a/src/rntviewer.cpp +++ b/src/rntviewer.cpp @@ -191,7 +191,7 @@ int main(int argc, char **argv) } #ifndef RNT_NO_GFX - init_viewer(app, args.n_cols); + init_viewer(arena, app, args.n_cols); // Init imgui and GLFW GLFWwindow *window = init_glfw(app, 1600, 900); diff --git a/src/tfile.cpp b/src/tfile.cpp index 2a49933..24dea1b 100644 --- a/src/tfile.cpp +++ b/src/tfile.cpp @@ -249,7 +249,7 @@ b8 walk_tkeys(Arena *arena, const u8 *data, u64 data_len, u32 flags, TFile_Data (u64)(n_bytes - keylen) != sections[Sec_TKey_List].head->range.len) { fprintf(stderr, "Warning: inconsistent key list data, the file may have not parsed properly.\n"); - fprintf(stderr, " (keylen: %u vs %lu, nbytes: %u vs %lu)\n", + fprintf(stderr, " (keylen: %u vs %u, nbytes: %u vs %lu)\n", keylen, sections[Sec_TKey_List].head->pre_size, n_bytes, sections[Sec_TKey_List].head->range.len); } @@ -304,6 +304,7 @@ b8 walk_tkeys(Arena *arena, const u8 *data, u64 data_len, u32 flags, TFile_Data sec_anchor->range.len = n_bytes - keylen; sec_anchor->pre_size = keylen; sec_anchor->post_size = 8; + sec_anchor->from_old_version_rntuple = rntuple_is_old_version(*rntuple); } else if (key_has_class_name("RBlob")) { if (!sections[Sec_Page].head) { @@ -415,11 +416,12 @@ void map_rntuple_rblobs(Arena *arena, TFile_Data &tfile_data) } if (best_anchor_idx >= 0) { found_sec[best_anchor_idx] = true; - Section *sec_header = push_section(arena, tfile_data, sec_id); - sec_header->range.start = tkey->rng.start + tkey->rng.len; - sec_header->range.len = sec_id == Sec_RNTuple_Header ? best_anchor->GetNBytesHeader() : best_anchor->GetNBytesFooter(); - sec_header->pre_size = tkey->rng.len; - sec_header->post_size = 8; + Section *section = push_section(arena, tfile_data, sec_id); + section->range.start = tkey->rng.start + tkey->rng.len; + section->range.len = sec_id == Sec_RNTuple_Header ? best_anchor->GetNBytesHeader() : best_anchor->GetNBytesFooter(); + section->pre_size = tkey->rng.len; + section->post_size = 8; + section->from_old_version_rntuple = rntuple_is_old_version(*best_anchor); } } }