Browse Source

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.
master
cancel 5 years ago
parent
commit
8557082779
  1. 47
      term_util.c
  2. 6
      term_util.h

47
term_util.c

@ -65,21 +65,21 @@ ORCA_NOINLINE static void qmenu_reprint(Qmenu *qm);
Qnav_stack qnav_stack; Qnav_stack qnav_stack;
void qnav_init() { qnav_stack = (Qnav_stack){.blocks = {0}}; } void qnav_init() { qnav_stack = (Qnav_stack){0}; }
void qnav_deinit() { void qnav_deinit() {
while (qnav_stack.count != 0) while (qnav_stack.top)
qnav_stack_pop(); qnav_stack_pop();
} }
static ORCA_NOINLINE void qnav_stack_push(Qblock *qb, int height, int width) { static ORCA_NOINLINE void qnav_stack_push(Qblock *qb, int height, int width) {
#ifndef NDEBUG #ifndef NDEBUG
for (Usz i = 0; i < qnav_stack.count; ++i) { for (Qblock *i = qnav_stack.top; i; i = i->down) {
assert(qnav_stack.blocks[i] != qb); assert(i != qb);
} }
#endif #endif
int top = 0, left = 0; int top = 0, left = 0;
int total_h = height + 2, total_w = width + 2; int total_h = height + 2, total_w = width + 2;
if (qnav_stack.count > 0) { if (qnav_stack.top) {
WINDOW *w = qnav_stack.blocks[qnav_stack.count - 1]->outer_window; WINDOW *w = qnav_stack.top->outer_window;
int prev_y, prev_x, prev_h, prev_w; int prev_y, prev_x, prev_h, prev_w;
getbegyx(w, prev_y, prev_x); getbegyx(w, prev_y, prev_x);
getmaxyx(w, prev_h, prev_w); 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; left = 0;
} }
} }
qnav_stack.top->up = qb;
} else {
qnav_stack.bottom = qb;
} }
qnav_stack.blocks[qnav_stack.count] = qb; qb->down = qnav_stack.top;
++qnav_stack.count; qnav_stack.top = qb;
qb->outer_window = newpad(total_h, total_w); qb->outer_window = newpad(total_h, total_w);
// This used to be derwin when when used newwin instead of newpad -- not sure // 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. // 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; qnav_stack.occlusion_dirty = true;
} }
Qblock *qnav_top_block() { Qblock *qnav_top_block() { return qnav_stack.top; }
if (qnav_stack.count == 0)
return NULL;
return qnav_stack.blocks[qnav_stack.count - 1];
}
void qblock_init(Qblock *qb, Qblock_type_tag tag) { void qblock_init(Qblock *qb, Qblock_type_tag tag) {
*qb = (Qblock){0}; *qb = (Qblock){0};
@ -147,10 +146,16 @@ void qnav_free_block(Qblock *qb) {
} }
void qnav_stack_pop(void) { void qnav_stack_pop(void) {
assert(qnav_stack.count > 0); assert(qnav_stack.top);
if (qnav_stack.count == 0) if (!qnav_stack.top)
return; 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 *content_window = qb->content_window;
WINDOW *outer_window = qb->outer_window; WINDOW *outer_window = qb->outer_window;
// erase any stuff underneath where this window is, in case it's outside of // 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); qnav_free_block(qb);
delwin(content_window); delwin(content_window);
delwin(outer_window); delwin(outer_window);
--qnav_stack.count;
qnav_stack.blocks[qnav_stack.count] = NULL;
qnav_stack.occlusion_dirty = true;
} }
bool qnav_draw(void) { bool qnav_draw(void) {
bool drew_any = false; bool drew_any = false;
if (qnav_stack.count < 1) if (!qnav_stack.bottom)
goto done; goto done;
int term_h, term_w; int term_h, term_w;
getmaxyx(stdscr, term_h, term_w); getmaxyx(stdscr, term_h, term_w);
for (Usz i = 0; i < qnav_stack.count; ++i) { for (Qblock *qb = qnav_stack.bottom; qb; qb = qb->up) {
Qblock *qb = qnav_stack.blocks[i]; bool is_frontmost = qb == qnav_stack.top;
bool is_frontmost = i == qnav_stack.count - 1;
if (qnav_stack.occlusion_dirty) if (qnav_stack.occlusion_dirty)
qblock_print_frame(qb, is_frontmost); qblock_print_frame(qb, is_frontmost);
switch (qb->tag) { switch (qb->tag) {

6
term_util.h

@ -56,16 +56,16 @@ typedef enum {
Qblock_type_qform, Qblock_type_qform,
} Qblock_type_tag; } Qblock_type_tag;
typedef struct { typedef struct Qblock {
Qblock_type_tag tag; Qblock_type_tag tag;
WINDOW *outer_window, *content_window; WINDOW *outer_window, *content_window;
char const *title; char const *title;
struct Qblock *down, *up;
int y, x; int y, x;
} Qblock; } Qblock;
typedef struct { typedef struct {
Qblock *blocks[16]; Qblock *top, *bottom;
Usz count;
bool occlusion_dirty; bool occlusion_dirty;
} Qnav_stack; } Qnav_stack;

Loading…
Cancel
Save