From 8557082779d2a244306a6a1ced1b30712f401660 Mon Sep 17 00:00:00 2001 From: cancel Date: Tue, 10 Nov 2020 10:58:46 +0900 Subject: [PATCH] Change Qnav stack to from array to linked list This removes the limitation of having a hard-coded maximum number of entries. There is no cost to code size. Iteration speed across Qnav blocks may be slightly slower, because the elements are allocated in arbitrary heap locations, which may prevent the next item from being prefetched across links. But, there are rarely more than 3 entries, and most of the time there are 0 entries. Iteration occurs only when redrawing the Qnav blocks and when quitting. We use a doubly linked list instead of singly linked because we need quick access to the top entry while also needing to iterate from the bottom to top when drawing the entries. An equivalent implementation with a stretchy/dynamic array of pointers was made, but the code size was larger and required more error checks and an extra heap allocation. A theoretical implementation that wants to reduce heap fragmentation could use a contiguous or slab allocation of multiple Qnav blocks/items. This would probably require more code, but may be worth it if this menuing/widget system is extended for reuse across projects in the future. For now, we separately heap allocate each Qnav item and link them together. Not great, but we don't have to worry about picking an arbitrary count for an array of pointers, and we aren't increasing the code size or complexity. Those are important things for this project. --- term_util.c | 47 ++++++++++++++++++++++++----------------------- term_util.h | 6 +++--- 2 files changed, 27 insertions(+), 26 deletions(-) diff --git a/term_util.c b/term_util.c index 8862fbd..c648dc3 100644 --- a/term_util.c +++ b/term_util.c @@ -65,21 +65,21 @@ ORCA_NOINLINE static void qmenu_reprint(Qmenu *qm); Qnav_stack qnav_stack; -void qnav_init() { qnav_stack = (Qnav_stack){.blocks = {0}}; } +void qnav_init() { qnav_stack = (Qnav_stack){0}; } void qnav_deinit() { - while (qnav_stack.count != 0) + while (qnav_stack.top) qnav_stack_pop(); } static ORCA_NOINLINE void qnav_stack_push(Qblock *qb, int height, int width) { #ifndef NDEBUG - for (Usz i = 0; i < qnav_stack.count; ++i) { - assert(qnav_stack.blocks[i] != qb); + for (Qblock *i = qnav_stack.top; i; i = i->down) { + assert(i != qb); } #endif int top = 0, left = 0; int total_h = height + 2, total_w = width + 2; - if (qnav_stack.count > 0) { - WINDOW *w = qnav_stack.blocks[qnav_stack.count - 1]->outer_window; + if (qnav_stack.top) { + WINDOW *w = qnav_stack.top->outer_window; int prev_y, prev_x, prev_h, prev_w; getbegyx(w, prev_y, prev_x); getmaxyx(w, prev_h, prev_w); @@ -106,9 +106,12 @@ static ORCA_NOINLINE void qnav_stack_push(Qblock *qb, int height, int width) { left = 0; } } + qnav_stack.top->up = qb; + } else { + qnav_stack.bottom = qb; } - qnav_stack.blocks[qnav_stack.count] = qb; - ++qnav_stack.count; + qb->down = qnav_stack.top; + qnav_stack.top = qb; qb->outer_window = newpad(total_h, total_w); // This used to be derwin when when used newwin instead of newpad -- not sure // if we should use derwin or subpad now. subpad is probably more compatible. @@ -119,11 +122,7 @@ static ORCA_NOINLINE void qnav_stack_push(Qblock *qb, int height, int width) { qnav_stack.occlusion_dirty = true; } -Qblock *qnav_top_block() { - if (qnav_stack.count == 0) - return NULL; - return qnav_stack.blocks[qnav_stack.count - 1]; -} +Qblock *qnav_top_block() { return qnav_stack.top; } void qblock_init(Qblock *qb, Qblock_type_tag tag) { *qb = (Qblock){0}; @@ -147,10 +146,16 @@ void qnav_free_block(Qblock *qb) { } void qnav_stack_pop(void) { - assert(qnav_stack.count > 0); - if (qnav_stack.count == 0) + assert(qnav_stack.top); + if (!qnav_stack.top) return; - Qblock *qb = qnav_stack.blocks[qnav_stack.count - 1]; + Qblock *qb = qnav_stack.top; + qnav_stack.top = qb->down; + if (qnav_stack.top) + qnav_stack.top->up = NULL; + else + qnav_stack.bottom = NULL; + qnav_stack.occlusion_dirty = true; WINDOW *content_window = qb->content_window; WINDOW *outer_window = qb->outer_window; // erase any stuff underneath where this window is, in case it's outside of @@ -160,20 +165,16 @@ void qnav_stack_pop(void) { qnav_free_block(qb); delwin(content_window); delwin(outer_window); - --qnav_stack.count; - qnav_stack.blocks[qnav_stack.count] = NULL; - qnav_stack.occlusion_dirty = true; } bool qnav_draw(void) { bool drew_any = false; - if (qnav_stack.count < 1) + if (!qnav_stack.bottom) goto done; int term_h, term_w; getmaxyx(stdscr, term_h, term_w); - for (Usz i = 0; i < qnav_stack.count; ++i) { - Qblock *qb = qnav_stack.blocks[i]; - bool is_frontmost = i == qnav_stack.count - 1; + for (Qblock *qb = qnav_stack.bottom; qb; qb = qb->up) { + bool is_frontmost = qb == qnav_stack.top; if (qnav_stack.occlusion_dirty) qblock_print_frame(qb, is_frontmost); switch (qb->tag) { diff --git a/term_util.h b/term_util.h index 3f0d3dd..fe93531 100644 --- a/term_util.h +++ b/term_util.h @@ -56,16 +56,16 @@ typedef enum { Qblock_type_qform, } Qblock_type_tag; -typedef struct { +typedef struct Qblock { Qblock_type_tag tag; WINDOW *outer_window, *content_window; char const *title; + struct Qblock *down, *up; int y, x; } Qblock; typedef struct { - Qblock *blocks[16]; - Usz count; + Qblock *top, *bottom; bool occlusion_dirty; } Qnav_stack;