2024-07-25 10:25:05 +00:00
|
|
|
internal
|
2024-07-25 15:01:08 +00:00
|
|
|
Byte_Range get_section_range(const App_State &app, Section_Id sec)
|
2024-07-25 10:25:05 +00:00
|
|
|
{
|
|
|
|
switch (sec) {
|
2024-07-25 15:01:08 +00:00
|
|
|
default: return { 0, 0 };
|
2024-07-25 10:25:05 +00:00
|
|
|
case Sec_TFile_Header: return Byte_Range { 0, app.tfile_data.root_file_header_size };
|
|
|
|
case Sec_TFile_Object: return app.tfile_data.rng_root_file_obj;
|
|
|
|
case Sec_TFile_Info: return app.tfile_data.rng_root_file_info;
|
|
|
|
case Sec_TFile_FreeList: return app.tfile_data.rng_root_file_free;
|
|
|
|
case Sec_TKey_List: return app.rndata.rng_tkeys_list;
|
|
|
|
case Sec_RNTuple_Anchor: return app.rndata.rng_anchor;
|
|
|
|
case Sec_RNTuple_Header: return app.rndata.rng_header;
|
|
|
|
case Sec_RNTuple_Footer: return app.rndata.rng_footer;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-07-11 12:59:59 +00:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2024-07-11 13:27:38 +00:00
|
|
|
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);
|
|
|
|
}
|
|
|
|
|
2024-07-10 19:47:37 +00:00
|
|
|
internal
|
2024-07-25 10:25:05 +00:00
|
|
|
Section find_section(App_State &app, u64 off)
|
2024-07-11 12:00:43 +00:00
|
|
|
{
|
2024-07-25 10:25:05 +00:00
|
|
|
const RNTuple_Data &rdata = app.rndata;
|
|
|
|
const TFile_Data &tdata = app.tfile_data;
|
2024-07-16 10:04:38 +00:00
|
|
|
u64 rblob_sz = rdata.rblob_header_size; // @Incomplete
|
2024-07-25 10:25:05 +00:00
|
|
|
i64 hilite_cluster = app.viewer.highlight_cluster ? app.viewer.highlighted_cluster : -1;
|
|
|
|
b8 hilite = false;
|
2024-07-11 12:27:19 +00:00
|
|
|
|
2024-07-15 13:54:22 +00:00
|
|
|
// TFile start
|
2024-07-25 10:25:05 +00:00
|
|
|
if (off <= tdata.root_file_header_size) return { Sec_TFile_Header, { 0, tdata.root_file_header_size }, 0, hilite };
|
|
|
|
if (tdata.rng_root_file_obj.start <= off && off < tdata.rng_root_file_obj.end()) return { Sec_TFile_Object, tdata.rng_root_file_obj, 0, hilite };
|
|
|
|
if (tdata.rng_root_file_info.start <= off && off < tdata.rng_root_file_info.end()) return { Sec_TFile_Info, tdata.rng_root_file_info, 0, hilite };
|
|
|
|
if (tdata.rng_root_file_free.start <= off && off < tdata.rng_root_file_free.end()) return { Sec_TFile_FreeList, tdata.rng_root_file_free, 0, hilite };
|
|
|
|
|
|
|
|
/// Handle pages
|
|
|
|
{
|
|
|
|
// fast case: `off` is in the same page info as previous `off`.
|
|
|
|
if (app.last_pinfo->range.start < off && off < app.last_pinfo->range.end()) {
|
|
|
|
hilite = hilite_cluster >= 0 && app.last_pinfo->cluster_id == (u64)hilite_cluster;
|
|
|
|
return { Sec_Page, app.last_pinfo->range, app.last_pinfo->checksum_size(), hilite };
|
|
|
|
}
|
2024-07-15 09:29:32 +00:00
|
|
|
|
2024-07-25 10:25:05 +00:00
|
|
|
// 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;
|
|
|
|
if (app.last_pinfo && app.last_pinfo->range.start <= off && off < app.last_pinfo->range.end()) {
|
|
|
|
hilite = hilite_cluster >= 0 && app.last_pinfo->cluster_id == (u64)hilite_cluster;
|
|
|
|
return { Sec_Page, app.last_pinfo->range, app.last_pinfo->checksum_size(), hilite };
|
|
|
|
}
|
2024-07-15 13:54:22 +00:00
|
|
|
}
|
|
|
|
|
2024-07-25 10:25:05 +00:00
|
|
|
if (rdata.rng_anchor_key.start <= off && off < rdata.rng_anchor.end())
|
|
|
|
return { Sec_RNTuple_Anchor, rdata.rng_anchor, 8, hilite };
|
2024-07-16 12:34:51 +00:00
|
|
|
|
2024-07-25 10:25:05 +00:00
|
|
|
if (rdata.rng_header.start - rblob_sz <= off && off < rdata.rng_header.end())
|
|
|
|
return { Sec_RNTuple_Header, rdata.rng_header, 8, hilite };
|
2024-07-16 12:34:51 +00:00
|
|
|
|
2024-07-25 10:25:05 +00:00
|
|
|
if (rdata.rng_footer.start - rblob_sz <= off && off < rdata.rng_footer.end())
|
|
|
|
return { Sec_RNTuple_Footer, rdata.rng_footer, 8, hilite };
|
2024-07-16 12:34:51 +00:00
|
|
|
|
2024-07-25 10:25:05 +00:00
|
|
|
if (rdata.rng_tkeys_list.start <= off && off < rdata.rng_tkeys_list.end())
|
|
|
|
return { Sec_TKey_List, rdata.rng_tkeys_list, 0, hilite };
|
2024-07-23 08:41:55 +00:00
|
|
|
|
2024-07-16 12:34:51 +00:00
|
|
|
// @Speed
|
|
|
|
for (u64 cg_idx = 0; cg_idx < rdata.n_cluster_groups; ++cg_idx) {
|
|
|
|
Cluster_Group_Info &cg_info = rdata.cluster_groups[cg_idx];
|
2024-07-25 10:25:05 +00:00
|
|
|
if (cg_info.rng_page_list.start - rblob_sz <= off && off < cg_info.rng_page_list.end())
|
|
|
|
return { Sec_Page_List, cg_info.rng_page_list, 8, hilite };
|
2024-07-16 12:34:51 +00:00
|
|
|
}
|
2024-07-12 16:29:35 +00:00
|
|
|
|
2024-07-15 13:54:22 +00:00
|
|
|
// Slow page group lookup, ideally only done once per render when last_pinfo is invalid.
|
2024-07-15 09:29:32 +00:00
|
|
|
for (Page_Info_Chunk *chunk = rdata.page_chunks; chunk; chunk = chunk->next) {
|
2024-07-25 10:25:05 +00:00
|
|
|
// 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)
|
|
|
|
return { Sec_Page, { chunk->range.start, 0 }, 0, hilite };
|
|
|
|
|
2024-07-16 09:12:50 +00:00
|
|
|
if (chunk->range.start <= off && off < chunk->range.end()) {
|
2024-07-15 09:29:32 +00:00
|
|
|
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];
|
2024-07-16 09:12:50 +00:00
|
|
|
if (off < group.range.start || off >= group.range.end())
|
2024-07-15 09:29:32 +00:00
|
|
|
continue;
|
|
|
|
|
|
|
|
for (Page_Info_Node *pinfo = group.first; pinfo; pinfo = pinfo->next) {
|
2024-07-16 09:12:50 +00:00
|
|
|
if (pinfo->range.start <= off && off < pinfo->range.end()) {
|
2024-07-25 10:25:05 +00:00
|
|
|
app.last_pinfo = pinfo;
|
|
|
|
hilite = hilite_cluster >= 0 && pinfo->cluster_id == (u64)hilite_cluster;
|
|
|
|
return { Sec_Page, pinfo->range, pinfo->checksum_size(), hilite };
|
2024-07-15 09:29:32 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2024-07-15 09:54:45 +00:00
|
|
|
|
|
|
|
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);
|
2024-07-12 16:29:35 +00:00
|
|
|
}
|
|
|
|
}
|
2024-07-11 12:00:43 +00:00
|
|
|
|
2024-07-25 10:25:05 +00:00
|
|
|
return {};
|
|
|
|
}
|
|
|
|
|
2024-07-25 15:01:08 +00:00
|
|
|
internal
|
|
|
|
ImColor imcol(const f32 col[3])
|
|
|
|
{
|
|
|
|
return ImColor(col[0], col[1], col[2]);
|
|
|
|
}
|
|
|
|
|
2024-07-25 10:25:05 +00:00
|
|
|
internal
|
|
|
|
u32 mem_edit_bg_color_fn(const u8 *, u64 off, void *user_data)
|
|
|
|
{
|
|
|
|
App_State *app = reinterpret_cast<App_State *>(user_data);
|
|
|
|
off += app->viewer.base_display_addr;
|
|
|
|
|
|
|
|
Section section = find_section(*app, off);
|
|
|
|
|
2024-07-25 15:01:08 +00:00
|
|
|
if (section.highlighted) return imcol(app->viewer.col_highlight);
|
|
|
|
if (section.id == Sec_Page && off == section.range.start) return imcol(app->viewer.col_page_start);
|
|
|
|
if (off < section.range.start) return imcol(app->viewer.col_key);
|
|
|
|
if (section.range.end() - section.post_size <= off && off < section.range.end()) return imcol(app->viewer.col_checksum);
|
|
|
|
return imcol(app->viewer.col_section[section.id]);
|
2024-07-11 12:00:43 +00:00
|
|
|
}
|
|
|
|
|
2024-07-25 08:44:50 +00:00
|
|
|
internal
|
|
|
|
void mem_edit_interact_fn(const u8 *, u64 off, void *user_data)
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
2024-07-11 12:00:43 +00:00
|
|
|
internal
|
2024-07-25 15:01:08 +00:00
|
|
|
MemoryEditor make_memory_editor(App_State &app, i32 n_cols)
|
2024-07-10 19:41:48 +00:00
|
|
|
{
|
|
|
|
MemoryEditor mem_edit;
|
2024-07-25 15:01:08 +00:00
|
|
|
mem_edit.Cols = n_cols ? n_cols : 32;
|
2024-07-11 12:27:19 +00:00
|
|
|
mem_edit.OptShowDataPreview = true;
|
2024-07-19 14:31:48 +00:00
|
|
|
// Do nothing on write.
|
|
|
|
// Note that we don't use ReadOnly = true because that disables selecting bytes
|
|
|
|
mem_edit.WriteFn = [] (ImU8*, size_t, ImU8) {};
|
2024-07-11 12:00:43 +00:00
|
|
|
mem_edit.BgColorFn = mem_edit_bg_color_fn;
|
2024-07-11 12:27:19 +00:00
|
|
|
mem_edit.BgColorFnUserData = &app;
|
2024-07-25 08:44:50 +00:00
|
|
|
mem_edit.InteractFn = mem_edit_interact_fn;
|
|
|
|
mem_edit.InteractFnUserData = &app;
|
2024-07-10 19:41:48 +00:00
|
|
|
return mem_edit;
|
|
|
|
}
|
|
|
|
|
2024-07-11 12:59:59 +00:00
|
|
|
internal
|
2024-07-25 15:01:08 +00:00
|
|
|
void make_viewer(App_State &app, u16 n_cols)
|
2024-07-11 12:27:19 +00:00
|
|
|
{
|
2024-07-16 12:34:51 +00:00
|
|
|
Viewer viewer {};
|
2024-07-25 15:01:08 +00:00
|
|
|
viewer.mem_edit = make_memory_editor(app, (i32)n_cols);
|
2024-07-16 12:34:51 +00:00
|
|
|
|
|
|
|
#define COL(c, r, g, b) viewer.c[0] = r/255.0, viewer.c[1] = g/255.0, viewer.c[2] = b/255.0
|
2024-07-11 14:29:44 +00:00
|
|
|
COL(col_key, 0, 100, 50);
|
2024-07-16 09:12:50 +00:00
|
|
|
COL(col_page_start, 200, 0, 200);
|
2024-07-16 10:04:38 +00:00
|
|
|
COL(col_checksum, 134, 65, 25);
|
2024-07-19 19:02:27 +00:00
|
|
|
COL(col_highlight, 190, 190, 190);
|
2024-07-25 10:25:05 +00:00
|
|
|
#define COL_S(c, r, g, b) viewer.col_section[c][0] = r/255.0, viewer.col_section[c][1] = g/255.0, viewer.col_section[c][2] = b/255.0
|
|
|
|
COL_S(Sec_RNTuple_Anchor, 150, 150, 0);
|
|
|
|
COL_S(Sec_RNTuple_Header, 150, 0, 50);
|
|
|
|
COL_S(Sec_RNTuple_Footer, 50, 0, 150);
|
|
|
|
COL_S(Sec_TFile_Header, 90, 90, 90);
|
|
|
|
COL_S(Sec_TFile_Object, 120, 120, 120);
|
|
|
|
COL_S(Sec_TFile_Info, 95, 76, 76);
|
|
|
|
COL_S(Sec_TFile_FreeList, 60, 60, 90);
|
|
|
|
COL_S(Sec_Page, 125, 0, 125);
|
|
|
|
COL_S(Sec_Page_List, 60, 110, 120);
|
|
|
|
COL_S(Sec_TKey_List, 100, 140, 100);
|
2024-07-11 12:27:19 +00:00
|
|
|
#undef COL
|
2024-07-25 10:25:05 +00:00
|
|
|
#undef COL_S
|
2024-07-16 12:34:51 +00:00
|
|
|
|
|
|
|
app.viewer = viewer;
|
|
|
|
}
|
|
|
|
|
|
|
|
internal
|
|
|
|
void viewer_jump_to(Viewer &viewer, u64 addr)
|
|
|
|
{
|
|
|
|
viewer.base_display_addr = addr;
|
|
|
|
viewer.mem_edit.GotoAddr = 0;
|
2024-07-11 12:27:19 +00:00
|
|
|
}
|
|
|
|
|
2024-07-15 16:02:55 +00:00
|
|
|
internal
|
2024-07-16 12:34:51 +00:00
|
|
|
void viewer_jump_to_page(App_State &app, u64 page_idx)
|
2024-07-15 16:02:55 +00:00
|
|
|
{
|
|
|
|
assert(app.rndata.n_pages > 0);
|
|
|
|
page_idx = (page_idx + app.rndata.n_pages) % app.rndata.n_pages;
|
|
|
|
|
|
|
|
// @Speed
|
|
|
|
Page_Info_Node *page = app.rndata.pages;
|
|
|
|
for (u64 i = 0; i < page_idx; ++i) {
|
|
|
|
page = page->next;
|
|
|
|
assert(page);
|
|
|
|
}
|
|
|
|
|
2024-07-16 12:34:51 +00:00
|
|
|
app.viewer.latest_page_gone_to = page_idx;
|
|
|
|
viewer_jump_to(app.viewer, page->range.start);
|
2024-07-15 16:02:55 +00:00
|
|
|
}
|
|
|
|
|
2024-07-18 15:43:44 +00:00
|
|
|
internal
|
|
|
|
void viewer_jump_to_page_list(App_State &app, u64 page_list_idx)
|
|
|
|
{
|
|
|
|
assert(app.rndata.n_cluster_groups > 0);
|
|
|
|
page_list_idx = (page_list_idx + app.rndata.n_cluster_groups) % app.rndata.n_cluster_groups;
|
|
|
|
|
|
|
|
Cluster_Group_Info &cg_info = app.rndata.cluster_groups[page_list_idx];
|
|
|
|
|
|
|
|
app.viewer.latest_page_list_gone_to = page_list_idx;
|
|
|
|
viewer_jump_to(app.viewer, cg_info.rng_page_list.start);
|
|
|
|
}
|
|
|
|
|
2024-07-19 14:31:48 +00:00
|
|
|
internal
|
|
|
|
void viewer_jump_to_cluster(App_State &app, u64 cluster_idx)
|
|
|
|
{
|
|
|
|
assert(app.rndata.n_clusters > 0);
|
|
|
|
cluster_idx = (cluster_idx + app.rndata.n_clusters) % app.rndata.n_clusters;
|
|
|
|
|
2024-07-23 08:43:28 +00:00
|
|
|
// @Speed: this is slow! Consider an acceleration structure, or maybe we can reuse
|
|
|
|
// Page_Info_Groups + binary search? (depends on whether cluster_idx are sorted)
|
2024-07-19 14:31:48 +00:00
|
|
|
Page_Info_Node *page = app.rndata.pages;
|
|
|
|
for (u64 i = 0; i < app.rndata.n_pages; ++i) {
|
|
|
|
if (page->cluster_id == cluster_idx)
|
|
|
|
break;
|
|
|
|
page = page->next;
|
|
|
|
assert(page);
|
|
|
|
}
|
|
|
|
|
|
|
|
app.viewer.highlighted_cluster = cluster_idx;
|
|
|
|
viewer_jump_to(app.viewer, page->range.start);
|
|
|
|
}
|
|
|
|
|
2024-07-10 19:47:37 +00:00
|
|
|
internal
|
2024-07-11 12:59:59 +00:00
|
|
|
void update_and_render(Arena *arena, App_State &app, f32 delta_time_ms)
|
2024-07-10 17:38:16 +00:00
|
|
|
{
|
2024-07-11 14:29:44 +00:00
|
|
|
accum_dt_ms(app.delta_time_accum, delta_time_ms);
|
2024-07-11 12:00:43 +00:00
|
|
|
|
2024-07-10 17:38:16 +00:00
|
|
|
ImGui::SetNextWindowPos({ 0, 0 });
|
|
|
|
ImGui::SetNextWindowSize({ (f32)app.win_data.width, (f32)app.win_data.height });
|
2024-07-10 18:11:42 +00:00
|
|
|
|
2024-07-16 15:50:30 +00:00
|
|
|
Temp scratch = scratch_begin(&arena, 1);
|
|
|
|
defer { scratch_end(scratch); };
|
2024-07-10 18:11:42 +00:00
|
|
|
|
2024-07-11 12:27:19 +00:00
|
|
|
const auto main_win_flags = ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoDecoration;
|
|
|
|
if (ImGui::Begin("main", nullptr, main_win_flags)) {
|
2024-07-11 12:59:59 +00:00
|
|
|
|
2024-07-12 09:58:55 +00:00
|
|
|
String8 ntpl_desc = rntuple_description(scratch.arena, app.rndata);
|
2024-07-18 13:32:32 +00:00
|
|
|
ImGui::Text("RNTuple '%s' (%s) from file \"%s\"", app.ntpl_name.c(), ntpl_desc.c(), app.inspected_file.name.c());
|
2024-07-11 12:59:59 +00:00
|
|
|
|
2024-07-11 13:27:38 +00:00
|
|
|
// Draw stats
|
2024-07-11 12:59:59 +00:00
|
|
|
{
|
|
|
|
ImGui::SameLine();
|
|
|
|
|
|
|
|
f32 avg_dt = calc_avg_dt_ms(app.delta_time_accum);
|
|
|
|
|
2024-07-12 09:58:55 +00:00
|
|
|
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);
|
2024-07-11 13:27:38 +00:00
|
|
|
|
|
|
|
f32 pos_x = (ImGui::GetCursorPosX() + ImGui::GetColumnWidth() - ImGui::CalcTextSize(stat_txt.c()).x
|
2024-07-11 12:59:59 +00:00
|
|
|
- ImGui::GetScrollX() - 2 * ImGui::GetStyle().ItemSpacing.x);
|
|
|
|
if (pos_x > ImGui::GetCursorPosX())
|
|
|
|
ImGui::SetCursorPosX(pos_x);
|
|
|
|
|
2024-07-11 13:27:38 +00:00
|
|
|
ImGui::Text("%s", stat_txt.c());
|
2024-07-11 12:59:59 +00:00
|
|
|
}
|
|
|
|
|
2024-07-11 12:27:19 +00:00
|
|
|
ImGui::Separator();
|
|
|
|
|
2024-07-11 12:59:59 +00:00
|
|
|
// Draw main content
|
2024-07-11 12:27:19 +00:00
|
|
|
{
|
2024-07-19 14:31:48 +00:00
|
|
|
const i64 step_i64 = 1;
|
2024-07-18 15:43:44 +00:00
|
|
|
|
2024-07-16 09:12:50 +00:00
|
|
|
ImGui::BeginTable("Hex View", 2, ImGuiTableFlags_Resizable);
|
2024-07-11 12:27:19 +00:00
|
|
|
|
|
|
|
ImGui::TableNextColumn();
|
2024-07-12 08:07:27 +00:00
|
|
|
|
2024-07-16 12:34:51 +00:00
|
|
|
assert(app.viewer.base_display_addr < app.inspected_file.size);
|
|
|
|
void *content = app.inspected_file.mem + app.viewer.base_display_addr;
|
|
|
|
u64 content_size = app.inspected_file.size - app.viewer.base_display_addr;
|
2024-07-15 09:54:45 +00:00
|
|
|
app.last_pinfo = &invalid_pinfo;
|
2024-07-16 15:58:33 +00:00
|
|
|
app.viewer.mem_edit.DrawContents(content, content_size, app.viewer.base_display_addr);
|
2024-07-11 12:27:19 +00:00
|
|
|
|
|
|
|
ImGui::TableNextColumn();
|
2024-07-12 08:07:27 +00:00
|
|
|
ImGuiColorEditFlags flags = ImGuiColorEditFlags_NoInputs|ImGuiColorEditFlags_NoLabel;
|
|
|
|
|
2024-07-25 10:25:05 +00:00
|
|
|
// Unique sections: just display a button that jumps to the start of it and show their size
|
|
|
|
for (u32 i = 0; i < Sec_COUNT; ++i) {
|
2024-07-25 15:01:08 +00:00
|
|
|
Byte_Range range = get_section_range(app, static_cast<Section_Id>(i));
|
|
|
|
if (!range.len) continue;
|
2024-07-25 10:25:05 +00:00
|
|
|
|
|
|
|
String8 sec_name = section_names[i];
|
|
|
|
String8 col_label = push_str8f(scratch.arena, "_%s", sec_name.c());
|
|
|
|
ImGui::ColorEdit3(col_label.c(), app.viewer.col_section[i], flags);
|
|
|
|
ImGui::SameLine();
|
|
|
|
if (ImGui::Button(sec_name.c()))
|
2024-07-25 15:01:08 +00:00
|
|
|
viewer_jump_to(app.viewer, range.start);
|
2024-07-25 10:25:05 +00:00
|
|
|
ImGui::SameLine();
|
2024-07-25 15:01:08 +00:00
|
|
|
ImGui::Text("%s", to_pretty_size(scratch.arena, range.len).c());
|
2024-07-25 10:25:05 +00:00
|
|
|
}
|
2024-07-15 13:54:22 +00:00
|
|
|
|
2024-07-25 10:25:05 +00:00
|
|
|
// Repeated sections: allow jumping to the N-th
|
2024-07-16 12:34:51 +00:00
|
|
|
ImGui::ColorEdit3("_Page Start", app.viewer.col_page_start, flags);
|
2024-07-15 13:54:22 +00:00
|
|
|
ImGui::SameLine();
|
2024-07-25 10:25:05 +00:00
|
|
|
if (ImGui::Button("Page Start"))
|
|
|
|
viewer_jump_to_page(app, 0);
|
2024-07-15 13:54:22 +00:00
|
|
|
|
2024-07-25 10:25:05 +00:00
|
|
|
ImGui::ColorEdit3("_Page", app.viewer.col_section[Sec_Page], flags);
|
2024-07-16 12:34:51 +00:00
|
|
|
ImGui::SameLine();
|
2024-07-25 10:25:05 +00:00
|
|
|
if (ImGui::Button("Page"))
|
|
|
|
viewer_jump_to_page(app, app.viewer.latest_page_gone_to);
|
2024-07-15 16:02:55 +00:00
|
|
|
ImGui::SameLine();
|
|
|
|
{
|
2024-07-19 14:31:48 +00:00
|
|
|
const i64 step_fast_i64 = app.rndata.n_pages / 100;
|
|
|
|
i64 page_to_go_to = app.viewer.latest_page_gone_to;
|
2024-07-15 16:02:55 +00:00
|
|
|
ImGui::PushItemWidth(100.f);
|
2024-07-19 14:31:48 +00:00
|
|
|
if (ImGui::InputScalar("##page_viewed", ImGuiDataType_S64, &page_to_go_to, &step_i64, &step_fast_i64, "%u"))
|
2024-07-16 12:34:51 +00:00
|
|
|
viewer_jump_to_page(app, page_to_go_to);
|
2024-07-15 16:02:55 +00:00
|
|
|
ImGui::PopItemWidth();
|
|
|
|
}
|
2024-07-16 12:34:51 +00:00
|
|
|
ImGui::SameLine();
|
|
|
|
ImGui::Text("%s", to_pretty_size(scratch.arena, app.rndata.tot_page_size).c());
|
2024-07-11 12:27:19 +00:00
|
|
|
|
2024-07-16 12:34:51 +00:00
|
|
|
ImGui::ColorEdit3("_Checksum", app.viewer.col_checksum, flags);
|
2024-07-16 10:04:38 +00:00
|
|
|
ImGui::SameLine();
|
|
|
|
if (ImGui::Button("Checksum")) {} // TODO jump to next checksum
|
|
|
|
|
2024-07-25 10:25:05 +00:00
|
|
|
ImGui::ColorEdit3("_Page List", app.viewer.col_section[Sec_Page_List], flags);
|
2024-07-16 12:34:51 +00:00
|
|
|
ImGui::SameLine();
|
2024-07-18 15:43:44 +00:00
|
|
|
if (ImGui::Button("Page List")) viewer_jump_to_page_list(app, app.viewer.latest_page_list_gone_to);
|
|
|
|
ImGui::SameLine();
|
|
|
|
{
|
2024-07-19 14:31:48 +00:00
|
|
|
i64 page_list_to_go_to = app.viewer.latest_page_list_gone_to;
|
2024-07-18 15:43:44 +00:00
|
|
|
ImGui::PushItemWidth(80.f);
|
2024-07-19 14:31:48 +00:00
|
|
|
if (ImGui::InputScalar("##page_list_viewed", ImGuiDataType_S64, &page_list_to_go_to, &step_i64, nullptr, "%u"))
|
2024-07-18 15:43:44 +00:00
|
|
|
viewer_jump_to_page_list(app, page_list_to_go_to);
|
|
|
|
ImGui::PopItemWidth();
|
|
|
|
}
|
2024-07-16 12:34:51 +00:00
|
|
|
ImGui::SameLine();
|
|
|
|
ImGui::Text("%s", to_pretty_size(scratch.arena, app.rndata.tot_page_list_size).c());
|
|
|
|
|
2024-07-23 08:41:55 +00:00
|
|
|
// -------------------------------
|
2024-07-12 16:29:35 +00:00
|
|
|
ImGui::Separator();
|
2024-07-18 13:32:32 +00:00
|
|
|
String8 root_version_str = push_str8f(scratch.arena, "%u.%u.%u",
|
|
|
|
app.tfile_data.root_version_major,
|
|
|
|
app.tfile_data.root_version_minor,
|
|
|
|
app.tfile_data.root_version_patch);
|
|
|
|
ImGui::Text("ROOT version: %s", root_version_str.c());
|
2024-07-18 13:56:55 +00:00
|
|
|
ImGui::Text("TFile compression: %u", app.tfile_data.compression);
|
2024-07-12 16:29:35 +00:00
|
|
|
ImGui::Text("Num pages: %lu", app.rndata.n_pages);
|
|
|
|
ImGui::Text("Num elements: %lu", app.rndata.n_elems);
|
|
|
|
|
2024-07-19 14:31:48 +00:00
|
|
|
{
|
|
|
|
const i64 step_fast_i64 = app.rndata.n_clusters / 100;
|
|
|
|
i64 cluster_to_highlight = app.viewer.highlighted_cluster;
|
|
|
|
ImGui::PushItemWidth(100.f);
|
|
|
|
if (ImGui::InputScalar("##highlighted_cluster", ImGuiDataType_S64, &cluster_to_highlight, &step_i64, &step_fast_i64, "%u")) {
|
2024-07-25 10:25:05 +00:00
|
|
|
app.viewer.highlight_cluster = true;
|
2024-07-19 14:31:48 +00:00
|
|
|
viewer_jump_to_cluster(app, cluster_to_highlight);
|
|
|
|
}
|
|
|
|
ImGui::PopItemWidth();
|
|
|
|
ImGui::SameLine();
|
|
|
|
ImGui::Checkbox("Highlight cluster", &app.viewer.highlight_cluster);
|
|
|
|
}
|
|
|
|
|
2024-07-11 12:27:19 +00:00
|
|
|
ImGui::EndTable();
|
|
|
|
}
|
2024-07-11 12:00:43 +00:00
|
|
|
|
2024-07-10 17:38:16 +00:00
|
|
|
ImGui::End();
|
|
|
|
}
|
|
|
|
}
|
2024-07-25 15:01:08 +00:00
|
|
|
|
|
|
|
internal
|
|
|
|
Term_Viewer make_term_viewer(u16 n_cols)
|
|
|
|
{
|
|
|
|
Term_Viewer viewer {};
|
|
|
|
viewer.max_cols = n_cols ? n_cols : 32;
|
|
|
|
|
|
|
|
viewer.col_key = ACol_Green;
|
|
|
|
viewer.col_page_start = ACol_Bright_Magenta;
|
|
|
|
viewer.col_checksum = ACol_Yellow;
|
|
|
|
viewer.col_highlight = ACol_Bright_White;
|
|
|
|
viewer.col_section[Sec_RNTuple_Anchor] = ACol_Bright_Yellow;
|
|
|
|
viewer.col_section[Sec_RNTuple_Header] = ACol_Cyan;
|
|
|
|
viewer.col_section[Sec_RNTuple_Footer] = ACol_Blue;
|
|
|
|
viewer.col_section[Sec_TFile_Header] = ACol_White;
|
|
|
|
viewer.col_section[Sec_TFile_Object] = ACol_Grey;
|
|
|
|
viewer.col_section[Sec_TFile_Info] = ACol_Red;
|
|
|
|
viewer.col_section[Sec_TFile_FreeList] = ACol_Bright_Yellow;
|
|
|
|
viewer.col_section[Sec_Page] = ACol_Magenta;
|
|
|
|
viewer.col_section[Sec_Page_List] = ACol_Bright_Cyan;
|
|
|
|
viewer.col_section[Sec_TKey_List] = ACol_Bright_Green;
|
|
|
|
return viewer;
|
|
|
|
}
|
|
|
|
|
|
|
|
internal
|
|
|
|
Ansi_Color viewer_to_ansi_color(const Viewer &viewer, const Term_Viewer &tviewer, u32 col)
|
|
|
|
{
|
|
|
|
if (col == imcol(viewer.col_key)) return tviewer.col_key;
|
|
|
|
if (col == imcol(viewer.col_page_start)) return tviewer.col_page_start;
|
|
|
|
if (col == imcol(viewer.col_checksum)) return tviewer.col_checksum;
|
|
|
|
if (col == imcol(viewer.col_highlight)) return tviewer.col_highlight;
|
2024-07-25 15:18:50 +00:00
|
|
|
for (u64 section = 1; section < Sec_COUNT; ++section)
|
2024-07-25 15:01:08 +00:00
|
|
|
if (col == imcol(viewer.col_section[section]))
|
|
|
|
return tviewer.col_section[section];
|
|
|
|
return ACol_None;
|
|
|
|
}
|
|
|
|
|
|
|
|
internal
|
2024-07-25 15:18:50 +00:00
|
|
|
String8 render_legend_to_string(Arena *arena, const Term_Viewer &viewer, const App_State &app)
|
2024-07-25 15:01:08 +00:00
|
|
|
{
|
|
|
|
Temp scratch = scratch_begin(&arena, 1);
|
|
|
|
defer { scratch_end(scratch); };
|
|
|
|
|
|
|
|
struct String8_Node {
|
|
|
|
String8_Node *next;
|
|
|
|
String8 str;
|
|
|
|
} *head = nullptr, *tail = nullptr;
|
|
|
|
|
2024-07-25 15:18:50 +00:00
|
|
|
String8 color_none = ansi_color_table[ACol_None];
|
|
|
|
u64 tot_len = 0;
|
|
|
|
for (u64 section = 1; section < Sec_COUNT; ++section) {
|
2024-07-25 15:01:08 +00:00
|
|
|
Ansi_Color color = viewer.col_section[section];
|
|
|
|
String8 color_str = ansi_color_table[color];
|
|
|
|
String8 sec_name = section_names[section];
|
2024-07-25 15:18:50 +00:00
|
|
|
Byte_Range range = get_section_range(app, static_cast<Section_Id>(section));
|
|
|
|
String8 s;
|
|
|
|
if (range.len)
|
|
|
|
s = push_str8f(scratch.arena, "%s%20s %s0x%lX - 0x%lX [%lu B]\n",
|
|
|
|
color_str.c(), sec_name.c(), color_none.c(),
|
|
|
|
range.start, range.end(), range.len);
|
|
|
|
else if (section == Sec_Page)
|
|
|
|
s = push_str8f(scratch.arena, "%s%20s %s(%lu) [%lu B] \n",
|
|
|
|
color_str.c(), sec_name.c(), color_none.c(), app.rndata.n_pages, app.rndata.tot_page_size);
|
|
|
|
else if (section == Sec_Page_List)
|
|
|
|
s = push_str8f(scratch.arena, "%s%20s %s(%lu) [%lu B]\n",
|
|
|
|
color_str.c(), sec_name.c(), color_none.c(), app.rndata.n_cluster_groups, app.rndata.tot_page_list_size);
|
2024-07-25 15:01:08 +00:00
|
|
|
String8_Node *node = arena_push<String8_Node>(scratch.arena);
|
|
|
|
node->str = s;
|
|
|
|
if (!tail) {
|
|
|
|
head = node;
|
|
|
|
} else {
|
|
|
|
tail->next = node;
|
|
|
|
}
|
|
|
|
tail = node;
|
|
|
|
tot_len += s.size;
|
|
|
|
}
|
|
|
|
|
|
|
|
String8 legend { arena_push_array_nozero<u8>(arena, tot_len + 1), tot_len + 1 };
|
|
|
|
legend.str[tot_len] = 0;
|
|
|
|
u64 cur_size = 0;
|
|
|
|
|
|
|
|
for (String8_Node *node = head; node; node = node->next) {
|
|
|
|
memcpy(legend.str + cur_size, node->str.str, node->str.size);
|
|
|
|
cur_size += node->str.size;
|
|
|
|
}
|
|
|
|
|
|
|
|
return legend;
|
|
|
|
}
|
|
|
|
|
|
|
|
internal
|
|
|
|
String8 render_range_to_string(Arena *arena, App_State &app, u64 len, u64 n_cols)
|
|
|
|
{
|
|
|
|
Term_Viewer viewer = make_term_viewer(n_cols);
|
|
|
|
|
2024-07-25 15:18:50 +00:00
|
|
|
String8 legend = render_legend_to_string(arena, viewer, app);
|
2024-07-25 15:01:08 +00:00
|
|
|
|
2024-07-25 15:18:50 +00:00
|
|
|
// scratch must be created after `render_legend_to_string`
|
|
|
|
Temp scratch = scratch_begin(&arena, 1);
|
|
|
|
defer { scratch_end(scratch); };
|
|
|
|
|
|
|
|
String8 ntpl_desc = rntuple_description(scratch.arena, app.rndata);
|
|
|
|
String8 header_str = push_str8f(scratch.arena, "RNTuple '%s' (%s) from file \"%s\"",
|
|
|
|
app.ntpl_name.c(), ntpl_desc.c(), app.inspected_file.name.c());
|
|
|
|
|
2024-07-25 15:01:08 +00:00
|
|
|
// NOTE: +3 because we need 2 chars for each byte + 1 whitespace.
|
|
|
|
u64 buf_size = (ACOL_MAX_LEN + 3) * len;
|
|
|
|
u64 n_newlines = len / viewer.max_cols;
|
|
|
|
buf_size += n_newlines;
|
2024-07-25 15:18:50 +00:00
|
|
|
buf_size += 1; // initial newline
|
|
|
|
buf_size += header_str.size;
|
|
|
|
buf_size += 2; // two newlines
|
2024-07-25 15:01:08 +00:00
|
|
|
buf_size += 2; // two newlines
|
|
|
|
buf_size += legend.size;
|
|
|
|
buf_size += 1; // trailing zero
|
|
|
|
u8 *buf = arena_push_array_nozero<u8>(arena, buf_size);
|
|
|
|
buf[len] = 0;
|
|
|
|
|
|
|
|
u64 start = app.viewer.base_display_addr;
|
|
|
|
|
|
|
|
// We need to properly initialize this before calling mem_edit_bg_color_fn!
|
|
|
|
app.last_pinfo = &invalid_pinfo;
|
|
|
|
|
|
|
|
const u8 *data = app.inspected_file.mem;
|
|
|
|
u64 max_addr = app.inspected_file.size;
|
|
|
|
assert(start <= max_addr);
|
|
|
|
len = min(len, max_addr - start);
|
|
|
|
|
|
|
|
u8 *cur = buf;
|
2024-07-25 15:18:50 +00:00
|
|
|
*cur++ = '\n';
|
|
|
|
|
|
|
|
// header
|
|
|
|
memcpy(cur, header_str.str, header_str.size);
|
|
|
|
cur += header_str.size;
|
|
|
|
*cur++ = '\n';
|
|
|
|
*cur++ = '\n';
|
|
|
|
|
2024-07-25 15:01:08 +00:00
|
|
|
for (u64 i = 0; i < len; ++i) {
|
|
|
|
u64 off = start + i;
|
|
|
|
|
|
|
|
/// Select color
|
|
|
|
u32 byte_col = mem_edit_bg_color_fn(data, off, &app);
|
|
|
|
// piggyback off mem_edit_bg_color_fn instead of rewriting the color-choosing logic.
|
|
|
|
// Kinda dumb code but we don't need speed here since we're printing this one-off.
|
|
|
|
Ansi_Color acol = viewer_to_ansi_color(app.viewer, viewer, byte_col);
|
|
|
|
String8 acol_str = ansi_color_table[acol];
|
|
|
|
memcpy(cur, acol_str.str, acol_str.size);
|
|
|
|
cur += acol_str.size;
|
|
|
|
|
|
|
|
/// Write the human-readable byte
|
|
|
|
String8 byte_str = push_str8f(scratch.arena, "%02X ", data[off]);
|
|
|
|
memcpy(cur, byte_str.str, byte_str.size);
|
|
|
|
cur += byte_str.size;
|
|
|
|
|
|
|
|
if ((i + 1) % viewer.max_cols == 0)
|
|
|
|
*cur++ = '\n';
|
|
|
|
|
|
|
|
assert((u64)(cur - buf) < (u64)buf_size);
|
|
|
|
}
|
|
|
|
|
|
|
|
*cur++ = '\n';
|
|
|
|
*cur++ = '\n';
|
|
|
|
memcpy(cur, legend.str, legend.size);
|
|
|
|
cur += legend.size;
|
|
|
|
|
|
|
|
*cur = 0;
|
|
|
|
|
|
|
|
return { buf, (u64)(cur - buf) };
|
|
|
|
}
|