add section focus/expansion

This commit is contained in:
silverweed 2025-02-13 17:05:25 +01:00
parent 4d06065933
commit a39bad260e
7 changed files with 111 additions and 23 deletions

View file

@ -37,6 +37,9 @@ struct App_State {
#endif
String8 ntpl_name;
// This is used:
// - in interactive mode: for aux section management
// - in terminal mode: to know which is the first byte to display
u64 base_display_addr;
Delta_Time_Accum delta_time_accum;

View file

@ -12,7 +12,7 @@ void print_help(const char *argv0)
"\n\t-l: display LEN bytes (only in terminal mode)"
"\n\t-k: print information about the TKeys in the file and exit"
"\n\t-n: list the names of the RNTuples found in the file and exit"
"\n\t-s: set first displayed byte to START"
"\n\t-s: set first displayed byte to START (only in terminal mode)"
"\n\t-t: no graphics, output to terminal"
"\n\t-v: print version and copyright info"
"\n\t-w: display WIDTH bytes per column"

View file

@ -170,10 +170,17 @@ void run_main_loop(GLFWwindow *window, Arena *arena, App_State &app)
init_viewer_title(app.viewer, app.fdata, app.inspected_file.name);
}
if ((app.user_input.key_state[KEY_ESC] & KEY_STATE_IS_DOWN) || glfwWindowShouldClose(window)) {
app.should_quit = true; // superfluous right now, but set it just in case.
break;
if (app.user_input.key_state[KEY_ESC] & KEY_STATE_JUST_PRESSED) {
if (app.viewer.aux.buf_size) {
// We were viewing a subsection: back to the main view
app.viewer.aux.buf_size = app.base_display_addr = 0;
} else {
app.should_quit = true;
}
}
if (glfwWindowShouldClose(window))
app.should_quit = true;
b32 focused = glfwGetWindowAttrib(window, GLFW_FOCUSED);
if (focused) {
@ -186,6 +193,7 @@ void run_main_loop(GLFWwindow *window, Arena *arena, App_State &app)
u8 *mouse_btn_state = app.user_input.mouse_btn_state;
monitor_mouse_btn(window, mouse_btn_state, GLFW_MOUSE_BUTTON_LEFT, MOUSE_BTN_LEFT);
monitor_mouse_btn(window, mouse_btn_state, GLFW_MOUSE_BUTTON_RIGHT, MOUSE_BTN_RIGHT);
monitor_mouse_btn(window, mouse_btn_state, GLFW_MOUSE_BUTTON_MIDDLE, MOUSE_BTN_MIDDLE);
app.user_input.mouse_pos.x = static_cast<i32>(mposx);
app.user_input.mouse_pos.y = static_cast<i32>(mposy);

View file

@ -1,3 +1,7 @@
// NOTE: we reserve more space than kMAXZIPBUF for the aux buf so we can fit also a TKey
// or other similar pre-section in the buffer.
#define AUX_BUF_SIZE (2 * kMAXZIPBUF)
constexpr f32 max3(f32 a, f32 b, f32 c)
{
return (a > b) ? (a > c) ? a : (b > c) ? b : c : (b > c) ? b : c;
@ -98,10 +102,10 @@ internal
u32 mem_edit_bg_color_fn(const u8 *data, u64 off, void *user_data)
{
App_State *app = reinterpret_cast<App_State *>(user_data);
off += app->base_display_addr;
u64 abs_off = off + app->base_display_addr;
f32 brighten = 0;
if (app->viewer.hovered_range.start <= off && off < app->viewer.hovered_range.end())
if (app->viewer.hovered_range.start <= abs_off && abs_off < app->viewer.hovered_range.end())
brighten = 0.3;
u32 col = highlight_zstd_header(data, off, *app, brighten);
@ -109,7 +113,12 @@ u32 mem_edit_bg_color_fn(const u8 *data, u64 off, void *user_data)
return col;
i64 hilite_cluster = app->viewer.highlight_cluster ? app->viewer.highlighted_cluster : -1;
Section section = find_section(*app, off, hilite_cluster);
Section section;
if (app->viewer.aux.buf_size && off > app->viewer.aux.section.pre_size) {
section = app->viewer.aux.section;
} else {
section = find_section(*app, abs_off, hilite_cluster);
}
if (section.highlighted)
return imcol(app->viewer.col_highlight, brighten);
@ -184,6 +193,9 @@ void init_viewer(App_State &app, u16 n_cols)
viewer.mem_edit = make_memory_editor(app, (i32)n_cols);
init_viewer_title(viewer, app.fdata, app.inspected_file.name);
// This lives as long as the viewer (i.e. forever)
viewer.aux.buf = (u8 *)malloc(AUX_BUF_SIZE);
#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);
COL(col_page_start, 200, 0, 200);
@ -361,7 +373,12 @@ void update_and_render(Arena *arena, App_State &app, f32 delta_time_ms)
{
f32 *c = app.viewer.col_title;
ImGui::TextColored(ImColor(c[0], c[1], c[2]), "%s", app.viewer.title.c());
String8 title = app.viewer.title;
if (app.viewer.aux.buf_size)
title = push_str8f(scratch.arena, "%s [viewing section \"%s\" at 0x%" PRIX64 "-0x%" PRIX64 "]", title.c(),
section_names[app.viewer.aux.section.id].c(),
app.base_display_addr, app.base_display_addr + app.viewer.aux.section_comp_size);
ImGui::TextColored(ImColor(c[0], c[1], c[2]), "%s", title.c());
}
// Draw stats
@ -391,7 +408,7 @@ void update_and_render(Arena *arena, App_State &app, f32 delta_time_ms)
ImGui::BeginTable("Hex View", 2, ImGuiTableFlags_Resizable);
u64 content_size = app.inspected_file.size - app.base_display_addr;
u64 content_size = app.viewer.aux.buf_size ? app.viewer.aux.buf_size : app.inspected_file.size - app.base_display_addr;
MemoryEditor::Sizes sizes;
app.viewer.mem_edit.CalcSizes(sizes, content_size, app.base_display_addr);
@ -406,16 +423,21 @@ void update_and_render(Arena *arena, App_State &app, f32 delta_time_ms)
// 0 means "invalid", otherwise the actual offset is `hovered_off - 1`.
u64 hovered_off = app.viewer.mem_edit.MouseHovered * (app.viewer.mem_edit.MouseHoveredAddr + 1);
if (LIKELY(app.inspected_file.size)) {
assert(app.base_display_addr < app.inspected_file.size);
void *content = app.inspected_file.mem + app.base_display_addr;
app.last_pinfo = &invalid_pinfo;
app.last_other_root_obj = &invalid_section;
app.viewer.mem_edit.DrawContents(content, content_size, app.base_display_addr);
} else {
ImGui::Text("(File is empty)");
void *content = nullptr;
if (app.viewer.aux.buf_size) {
content = app.viewer.aux.buf;
} else if (LIKELY(app.inspected_file.size)) {
content = app.inspected_file.mem;
}
if (LIKELY(content))
app.viewer.mem_edit.DrawContents(content, content_size, 0);
else
ImGui::Text("(File is empty)");
ImGui::TableNextColumn();
const ImGuiColorEditFlags edit_flags = ImGuiColorEditFlags_NoInputs|ImGuiColorEditFlags_NoLabel;
@ -535,21 +557,30 @@ void update_and_render(Arena *arena, App_State &app, f32 delta_time_ms)
if (hovered_off)
{
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);
u64 abs_hovered_off = hovered_off - 1 + app.base_display_addr;
ImGui::TextColored(ImColor(0.f, 0.65f, 0.f), "Offset: 0x%" PRIX64, abs_hovered_off);
Section hovered_section = app.viewer.aux.buf_size ? app.viewer.aux.section : find_section(app, hovered_off - 1);
Section_Id sec_id = hovered_section.id;
// NOTE: anchor/header/footer sections all have their *info point to their parent rntuple anchor info.
b8 old_version =
(sec_id == Sec_RNTuple_Anchor || sec_id == Sec_RNTuple_Header || sec_id == Sec_RNTuple_Footer) &&
rntuple_is_old_version(((const RNTuple_Anchor_Info *)hovered_section.info)->anchor);
b8 hover_display_grouped = !(app.user_input.key_state[KEY_ALT] & KEY_STATE_IS_DOWN);
const u8 *section_info_data = app.viewer.aux.buf_size ? app.viewer.aux.buf : app.inspected_file.mem;
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);
section_info_data, hover_display_grouped, old_version);
ImGui::TextColored(ImColor(0.5f, 0.5f, 0.5f), "(Hint: hold Alt for single-field hover information)");
ImGui::TextColored(ImColor(0.5f, 0.5f, 0.5f), "(Hint: Shift-Click to jump to the start of this section)");
if (!app.viewer.aux.buf_size)
ImGui::TextColored(ImColor(0.5f, 0.5f, 0.5f), "(Hint: Middle-Click to focus/expand current section)");
else
ImGui::TextColored(ImColor(0.5f, 0.5f, 0.5f), "(Hint: Escape to go back to full view)");
if (hover_info.desc)
imgui_render_string_tree(scratch.arena, hover_info.desc->head, hover_info.highlighted_desc);
app.viewer.hovered_range = hover_info.rng;
app.viewer.hovered_range.start += !!app.viewer.aux.buf_size * app.base_display_addr;
// Shift-clicking on a page section will update the current page in the legend
if ((app.user_input.key_state[KEY_SHIFT] & KEY_STATE_IS_DOWN) &&
@ -562,6 +593,38 @@ void update_and_render(Arena *arena, App_State &app, f32 delta_time_ms)
viewer_jump_to(app, hovered_section.range.start);
}
}
//
// Handle section focusing/expanding.
// This feature isolates a single section and displays it uncompressed, allowing individual values hovering
// even for zipped payloads.
//
if (!app.viewer.aux.buf_size && (app.user_input.mouse_btn_state[MOUSE_BTN_MIDDLE] & MOUSE_BTN_STATE_JUST_PRESSED)) {
// Check if this section is compressed; decompress it if it's the case.
u8 *sec_buf = app.inspected_file.mem + hovered_section.range.start - hovered_section.pre_size;
auto comp = R__getCompressionAlgorithm(sec_buf + hovered_section.pre_size, hovered_section.range.len);
app.viewer.aux.section = hovered_section;
app.base_display_addr = hovered_section.range.start - hovered_section.pre_size;
app.viewer.aux.section.range.start = hovered_section.pre_size;
app.viewer.aux.section_comp_size = hovered_section.range.len + hovered_section.pre_size;
if (comp == ROOT::RCompressionSetting::EAlgorithm::kUndefined) {
// Not a compressed block: just copy it to the aux buffer.
app.viewer.aux.buf_size = hovered_section.range.len + hovered_section.pre_size;
memcpy(app.viewer.aux.buf, sec_buf, min(AUX_BUF_SIZE, app.viewer.aux.buf_size));
} else {
// Copy over the pre-section (usually the TKey)
memcpy(app.viewer.aux.buf, sec_buf, hovered_section.pre_size);
// Decompress the block
i32 src_size = hovered_section.range.len;
i32 tgt_size = kMAXZIPBUF;
i32 unzipped_nbytes;
R__unzip(&src_size, sec_buf + hovered_section.pre_size, &tgt_size,
app.viewer.aux.buf + hovered_section.pre_size, &unzipped_nbytes);
app.viewer.aux.buf_size = hovered_section.pre_size + unzipped_nbytes;
app.viewer.aux.section.range.len = unzipped_nbytes;
}
}
} else {
app.viewer.hovered_range = {};
}

View file

@ -1,3 +1,13 @@
// Contains the data of the currently focused/expanded section (middle-click on the section)
struct Viewer_Aux_Section {
// This is an allocation AUX_BUF_SIZE wide.
u8 *buf;
// The aux section (i.e. all the data inside this struct) is considered valid if and only if this is > 0
u64 buf_size;
Section section;
u64 section_comp_size;
};
struct Viewer {
// Imgui colors
f32 col_section[Sec_COUNT][3];
@ -27,6 +37,8 @@ struct Viewer {
// maps glfw keys to our keys
Input_Key glfw_key_mapping[GLFW_KEY_LAST];
Viewer_Aux_Section aux;
#endif
};

View file

@ -158,6 +158,7 @@ int main(int argc, char **argv)
}
app.ntpl_name = args.ntpl_name; // may be null
if (!is_interactive)
app.base_display_addr = args.start_addr;
u32 walk_tkeys_flags = WTK_NONE;
if (args.print_keys_info)

View file

@ -25,6 +25,7 @@ enum Key_State : u8 {
enum Mouse_Button {
MOUSE_BTN_LEFT,
MOUSE_BTN_RIGHT,
MOUSE_BTN_MIDDLE,
MOUSE_BTN_COUNT
};