internal void accum_dt_ms(Delta_Time_Accum &accum, f32 dt) { if (accum.count < accum.max) { assert(accum.start == 0); accum.base[accum.count++] = dt; } else { accum.base[accum.start++] = dt; if (accum.start == accum.max) accum.start = 0; } } internal f32 calc_avg_dt_ms(const Delta_Time_Accum &accum) { f32 res = 0; for (u16 idx = 0; idx < accum.count; ++idx) res += accum.base[idx]; if (accum.count) res /= accum.count; return res; } internal String8 to_pretty_size(Arena *arena, u64 bytes) { if (bytes >= GiB(1)) return push_str8f(arena, "%.1f GiB", (f32)bytes / GiB(1)); if (bytes >= MiB(1)) return push_str8f(arena, "%.1f MiB", (f32)bytes / MiB(1)); if (bytes >= KiB(1)) return push_str8f(arena, "%.1f KiB", (f32)bytes / KiB(1)); return push_str8f(arena, "%zu B", bytes); } internal u32 mem_edit_bg_color_fn(const u8 *, u64 off, void *user_data) { App_State *app = reinterpret_cast(user_data); const RNTuple_Data &rdata = app->rndata; u64 rblob_sz = rdata.rblob_header_size; off += app->vsettings.base_display_addr; #define COL(c) (ImColor((c)[0], (c)[1], (c)[2])) // Handle pages // fast case: offset from same page info as previous if (off < app->last_pinfo->range.end()) return COL(app->vsettings.col_page); // still fast case: offset from next page info if (app->last_pinfo->next) app->last_pinfo = app->last_pinfo->next; if (app->last_pinfo && app->last_pinfo->range.start <= off && off <= app->last_pinfo->range.end()) return COL(app->vsettings.col_page); if (off <= rdata.root_file_header_size) return COL(app->vsettings.col_tfile); if (rdata.rng_anchor_key.start <= off && off <= rdata.rng_anchor_key.end()) return COL(app->vsettings.col_key); if (rdata.rng_header.start - rblob_sz <= off && off <= rdata.rng_header.start) return COL(app->vsettings.col_key); if (rdata.rng_footer.start - rblob_sz <= off && off <= rdata.rng_footer.start) return COL(app->vsettings.col_key); if (rdata.rng_anchor.start <= off && off <= rdata.rng_anchor.end()) return COL(app->vsettings.col_anchor); if (rdata.rng_header.start <= off && off <= rdata.rng_header.end()) return COL(app->vsettings.col_header); if (rdata.rng_footer.start <= off && off <= rdata.rng_footer.end()) return COL(app->vsettings.col_footer); // Slow page group lookup, hopefully rare (ideally only done once) // printf("%lu vs %lu\n", off, rdata.page_groups[rdata.n_page_groups - 1].range.end()); for (Page_Info_Chunk *chunk = rdata.page_chunks; chunk; chunk = chunk->next) { if (chunk->range.start <= off && off <= chunk->range.end()) { printf("slow path for 0x%lX (last pinfo: 0x%lX - 0x%lX)\n", off, app->last_pinfo->range.start, app->last_pinfo->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) { if (pinfo->range.start <= off && off <= pinfo->range.end()) { app->last_pinfo = pinfo; if (pinfo->range.start == off) return COL(app->vsettings.col_page_start); else return COL(app->vsettings.col_page); } } } fprintf(stderr, "Offset 0x%lX is in chunk 0x%lX - 0x%lX, but found in no page_info range!\n", off, chunk->range.start, chunk->range.end()); assert(false); } } #undef COL return IM_COL32(0, 0, 0, 0); } internal MemoryEditor make_memory_editor(App_State &app) { MemoryEditor mem_edit; // mem_edit.ReadOnly = true; mem_edit.Cols = 32; mem_edit.OptShowDataPreview = true; mem_edit.BgColorFn = mem_edit_bg_color_fn; mem_edit.BgColorFnUserData = &app; return mem_edit; } internal Viewer_Settings make_viewer_settings() { Viewer_Settings settings {}; #define COL(c, r, g, b) settings.c[0] = r/255.0, settings.c[1] = g/255.0, settings.c[2] = b/255.0 COL(col_anchor, 150, 150, 0); COL(col_header, 150, 0, 50); COL(col_footer, 50, 0, 150); COL(col_key, 0, 100, 50); COL(col_tfile, 90, 90, 90); COL(col_page, 125, 0, 125); COL(col_page_start, 75, 0, 75); #undef COL return settings; } internal void update_and_render(Arena *arena, App_State &app, f32 delta_time_ms) { accum_dt_ms(app.delta_time_accum, delta_time_ms); ImGui::SetNextWindowPos({ 0, 0 }); ImGui::SetNextWindowSize({ (f32)app.win_data.width, (f32)app.win_data.height }); Temp scratch = temp_begin(arena); defer { temp_end(scratch); }; const u64 inspected_max_size = 2048; u64 text_buf_size = min(app.inspected_file_size * 2, inspected_max_size); char *text_buf = arena_push_array_nozero(scratch.arena, text_buf_size + 1); // Convert file content to human readable // @Speed: maybe do this only when the file changes for (u64 i = 0; i < text_buf_size / 2; ++i) sprintf(&text_buf[2 * i], "%02X", app.inspected_fmem[i]); text_buf[text_buf_size] = 0; const auto main_win_flags = ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoDecoration; if (ImGui::Begin("main", nullptr, main_win_flags)) { String8 ntpl_desc = rntuple_description(scratch.arena, app.rndata); ImGui::Text("RNTuple '%s' (%s) from file \"%s\"", app.ntpl_name, ntpl_desc.c(), app.inspected_fname); // Draw stats { ImGui::SameLine(); f32 avg_dt = calc_avg_dt_ms(app.delta_time_accum); String8 mem_used = to_pretty_size(scratch.arena, arena->mem_used); String8 mem_peak = to_pretty_size(scratch.arena, arena->mem_peak_used); String8 stat_txt = push_str8f(scratch.arena, "mem used: %s (peak: %s) | avg dt: %.1f", mem_used.c(), mem_peak.c(), avg_dt); f32 pos_x = (ImGui::GetCursorPosX() + ImGui::GetColumnWidth() - ImGui::CalcTextSize(stat_txt.c()).x - ImGui::GetScrollX() - 2 * ImGui::GetStyle().ItemSpacing.x); if (pos_x > ImGui::GetCursorPosX()) ImGui::SetCursorPosX(pos_x); ImGui::Text("%s", stat_txt.c()); } ImGui::Separator(); // Draw main content { static MemoryEditor mem_edit = make_memory_editor(app); ImGui::BeginTable("Hex View", 2); ImGui::TableNextColumn(); assert(app.vsettings.base_display_addr < app.inspected_file_size); void *content = app.inspected_fmem + app.vsettings.base_display_addr; u64 content_size = app.inspected_file_size - app.vsettings.base_display_addr; app.last_pinfo = &invalid_pinfo; mem_edit.DrawContents(content, content_size, app.vsettings.base_display_addr); ImGui::TableNextColumn(); ImGuiColorEditFlags flags = ImGuiColorEditFlags_NoInputs|ImGuiColorEditFlags_NoLabel; ImGui::ColorEdit3("_TFile", app.vsettings.col_tfile, flags); ImGui::SameLine(); if (ImGui::Button("TFile")) app.vsettings.base_display_addr = 0; ImGui::ColorEdit3("_RNTuple Anchor", app.vsettings.col_anchor, flags); ImGui::SameLine(); if (ImGui::Button("RNTuple Anchor")) app.vsettings.base_display_addr = app.rndata.rng_anchor.start; ImGui::ColorEdit3("_RNTuple Header", app.vsettings.col_header, flags); ImGui::SameLine(); if (ImGui::Button("RNTuple Header")) app.vsettings.base_display_addr = app.rndata.rng_header.start; ImGui::ColorEdit3("_RNTuple Footer", app.vsettings.col_footer, flags); ImGui::SameLine(); if (ImGui::Button("RNTuple Footer")) app.vsettings.base_display_addr = app.rndata.rng_footer.start; ImGui::ColorEdit3("_TKey Header", app.vsettings.col_key, flags); ImGui::SameLine(); if (ImGui::Button("TKey Header")) {} // TODO app.vsettings.base_display_addr = app.rndatarng_footer.start; ImGui::Separator(); ImGui::Text("Num pages: %lu", app.rndata.n_pages); ImGui::Text("Num elements: %lu", app.rndata.n_elems); ImGui::EndTable(); } ImGui::End(); } }