diff --git a/NEWS.md b/NEWS.md index f45641c3f..0e61d1b05 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,6 +1,11 @@ This document attempts to list user-visible changes and any major internal rearrangements of Notcurses. +* 1.4.2.4 (2020-05-20) + * Removed `ncplane_move_above_unsafe()` and `ncplane_move_below_unsafe()`; + all z-axis moves are now safe. Z-axis moves are all now O(1), rather + than the previous O(N). + * 1.4.2.3 (2020-05-17) * Added `notcurses_canutf8()`, to verify use of UTF-8 encoding. * Fixed bug in `ncvisual_from_plane()` when invoked on the standard plane. diff --git a/USAGE.md b/USAGE.md index c32e002e2..ff2c689d5 100644 --- a/USAGE.md +++ b/USAGE.md @@ -549,7 +549,6 @@ struct ncplane* ncplane_reparent(struct ncplane* n, struct ncplane* newparent); // Duplicate an existing ncplane. The new plane will have the same geometry, // will duplicate all content, and will start with the same rendering state. -// The new plane will be immediately above the old one on the z axis. struct ncplane* ncplane_dup(struct ncplane* n, void* opaque); // Merge the ncplane 'src' down onto the ncplane 'dst'. This is most rigorously @@ -709,13 +708,17 @@ int ncplane_base(struct ncplane* ncp, cell* c); ```c // Splice ncplane 'n' out of the z-buffer, and reinsert it at the top or bottom. -int ncplane_move_top(struct ncplane* n); -int ncplane_move_bottom(struct ncplane* n); +void ncplane_move_top(struct ncplane* n); +void ncplane_move_bottom(struct ncplane* n); // Splice ncplane 'n' out of the z-buffer, and reinsert it below 'below'. +// Returns non-zero if 'n' is already in the desired location. 'n' and +// 'below' must not be the same plane. int ncplane_move_below(struct ncplane* restrict n, struct ncplane* restrict below); // Splice ncplane 'n' out of the z-buffer, and reinsert it above 'above'. +// Returns non-zero if 'n' is already in the desired location. 'n' and +// 'above' must not be the same plane. int ncplane_move_above(struct ncplane* restrict n, struct ncplane* restrict above); // Return the ncplane below this one, or NULL if this is at the stack's bottom. diff --git a/doc/man/man3/notcurses_plane.3.md b/doc/man/man3/notcurses_plane.3.md index c9c2715ee..0d7ad77f2 100644 --- a/doc/man/man3/notcurses_plane.3.md +++ b/doc/man/man3/notcurses_plane.3.md @@ -36,9 +36,9 @@ notcurses_plane - operations on ncplanes **int ncplane_move_bottom(struct ncplane* n);** -**int ncplane_move_above(struct ncplane* n, struct ncplane* above);** +**int ncplane_move_above(struct ncplane* restrict n, struct ncplane* restrict above);** -**int ncplane_move_below(struct ncplane* n, struct ncplane* below);** +**int ncplane_move_below(struct ncplane* restrict n, struct ncplane* restrict below);** **struct ncplane* ncplane_below(struct ncplane* n);** diff --git a/include/ncpp/Plane.hh b/include/ncpp/Plane.hh index 256150ab2..e64672901 100644 --- a/include/ncpp/Plane.hh +++ b/include/ncpp/Plane.hh @@ -271,14 +271,14 @@ namespace ncpp return error_guard (ncplane_move_yx (plane, y, x), -1); } - bool move_top () const NOEXCEPT_MAYBE + void move_top () noexcept { - return error_guard (ncplane_move_top (plane), -1); + ncplane_move_top (plane); } - bool move_bottom () const NOEXCEPT_MAYBE + void move_bottom () noexcept { - return error_guard (ncplane_move_bottom (plane), -1); + ncplane_move_bottom (plane); } bool move_below (Plane &below) const NOEXCEPT_MAYBE @@ -294,19 +294,6 @@ namespace ncpp return move_below (*below); } - bool move_below_unsafe (Plane &below) const NOEXCEPT_MAYBE - { - return error_guard (ncplane_move_below_unsafe (plane, below.plane), -1); - } - - bool move_below_unsafe (Plane *below) const - { - if (below == nullptr) - throw invalid_argument ("'below' must be a valid pointer"); - - return move_below_unsafe (*below); - } - bool move_above (Plane &above) const NOEXCEPT_MAYBE { return error_guard (ncplane_move_above (plane, above.plane), -1); @@ -320,19 +307,6 @@ namespace ncpp return move_above (*above); } - bool move_above_unsafe (Plane &above) const NOEXCEPT_MAYBE - { - return error_guard (ncplane_move_above_unsafe (plane, above.plane), -1); - } - - bool move_above_unsafe (Plane *above) const - { - if (above == nullptr) - throw invalid_argument ("'above' must be a valid pointer"); - - return move_above (*above); - } - bool mergedown (Plane &dst) const { return mergedown (&dst); diff --git a/include/notcurses/notcurses.h b/include/notcurses/notcurses.h index 9c2316e89..e91d6bc72 100644 --- a/include/notcurses/notcurses.h +++ b/include/notcurses/notcurses.h @@ -1124,32 +1124,20 @@ API int ncplane_move_yx(struct ncplane* n, int y, int x); API void ncplane_yx(const struct ncplane* n, int* RESTRICT y, int* RESTRICT x); // Splice ncplane 'n' out of the z-buffer, and reinsert it at the top or bottom. -API int ncplane_move_top(struct ncplane* n); -API int ncplane_move_bottom(struct ncplane* n); +API void ncplane_move_top(struct ncplane* n); +API void ncplane_move_bottom(struct ncplane* n); // Splice ncplane 'n' out of the z-buffer, and reinsert it above 'above'. -API int ncplane_move_above_unsafe(struct ncplane* RESTRICT n, - const struct ncplane* RESTRICT above); - -static inline int -ncplane_move_above(struct ncplane* n, struct ncplane* above){ - if(n == above){ - return -1; - } - return ncplane_move_above_unsafe(n, above); -} +// Returns non-zero if 'n' is already in the desired location. 'n' and +// 'above' must not be the same plane. +API int ncplane_move_above(struct ncplane* RESTRICT n, + struct ncplane* RESTRICT above); // Splice ncplane 'n' out of the z-buffer, and reinsert it below 'below'. -API int ncplane_move_below_unsafe(struct ncplane* RESTRICT n, - const struct ncplane* RESTRICT below); - -static inline int -ncplane_move_below(struct ncplane* n, struct ncplane* below){ - if(n == below){ - return -1; - } - return ncplane_move_below_unsafe(n, below); -} +// Returns non-zero if 'n' is already in the desired location. 'n' and +// 'below' must not be the same plane. +API int ncplane_move_below(struct ncplane* RESTRICT n, + struct ncplane* RESTRICT below); // Return the plane below this one, or NULL if this is at the bottom. API struct ncplane* ncplane_below(struct ncplane* n); diff --git a/python/src/notcurses/build_notcurses.py b/python/src/notcurses/build_notcurses.py index 22b38d40a..8c1951c67 100644 --- a/python/src/notcurses/build_notcurses.py +++ b/python/src/notcurses/build_notcurses.py @@ -134,10 +134,10 @@ int ncplane_move_yx(struct ncplane* n, int y, int x); void ncplane_yx(struct ncplane* n, int* y, int* x); void ncplane_dim_yx(const struct ncplane* n, int* rows, int* cols); int ncplane_putc_yx(struct ncplane* n, int y, int x, const cell* c); -int ncplane_move_top(struct ncplane* n); -int ncplane_move_bottom(struct ncplane* n); -int ncplane_move_below(struct ncplane* n, struct ncplane* below); -int ncplane_move_above(struct ncplane* n, struct ncplane* above); +void ncplane_move_top(struct ncplane* n); +void ncplane_move_bottom(struct ncplane* n); +int ncplane_move_below(struct ncplane* restrict n, struct ncplane* restrict below); +int ncplane_move_above(struct ncplane* restrict n, struct ncplane* restrict above); struct ncplane* ncplane_below(struct ncplane* n); char* notcurses_at_yx(struct notcurses* nc, int yoff, int xoff, uint32_t* attrword, uint64_t* channels); char* ncplane_at_cursor(struct ncplane* n, uint32_t* attrword, uint64_t* channels); diff --git a/src/input/input.cpp b/src/input/input.cpp index 6c073ad2d..12565fc03 100644 --- a/src/input/input.cpp +++ b/src/input/input.cpp @@ -214,7 +214,7 @@ int main(void){ if(!nc.mouse_enable()){ return EXIT_FAILURE; } - auto n = nc.get_stdplane(&dimy, &dimx); + std::unique_ptr n = std::make_unique(nc.get_stdplane(&dimy, &dimx)); ncpp::Plane pplane{PLOTHEIGHT, dimx, dimy - PLOTHEIGHT, 0, nullptr}; struct ncplot_options popts{}; // FIXME would be nice to switch over to exponential at some level @@ -227,14 +227,14 @@ int main(void){ if(!plot){ return EXIT_FAILURE; } - n->set_fg_rgb(0x00, 0x00, 0x00); - n->set_bg_rgb(0xbb, 0x64, 0xbb); - n->styles_on(CellStyle::Underline); - if(n->putstr(0, NCAlign::Center, "mash keys, yo. give that mouse some waggle! ctrl+d exits.") <= 0){ + (*n)->set_fg_rgb(0x00, 0x00, 0x00); + (*n)->set_bg_rgb(0xbb, 0x64, 0xbb); + (*n)->styles_on(CellStyle::Underline); + if((*n)->putstr(0, NCAlign::Center, "mash keys, yo. give that mouse some waggle! ctrl+d exits.") <= 0){ return EXIT_FAILURE; } - n->styles_set(CellStyle::None); - n->set_bg_default(); + (*n)->styles_set(CellStyle::None); + (*n)->set_bg_default(); if(!nc.render()){ return EXIT_FAILURE; } @@ -262,35 +262,35 @@ int main(void){ } mtx.unlock(); } - if(!n->cursor_move(y, 0)){ + if(!(*n)->cursor_move(y, 0)){ break; } - n->set_fg_rgb(0xd0, 0xd0, 0xd0); - n->printf("%c%c%c ", ni.alt ? 'A' : 'a', ni.ctrl ? 'C' : 'c', + (*n)->set_fg_rgb(0xd0, 0xd0, 0xd0); + (*n)->printf("%c%c%c ", ni.alt ? 'A' : 'a', ni.ctrl ? 'C' : 'c', ni.shift ? 'S' : 's'); if(r < 0x80){ - n->set_fg_rgb(128, 250, 64); - if(n->printf("ASCII: [0x%02x (%03d)] '%lc'", r, r, - (wchar_t)(iswprint(r) ? r : printutf8(r))) < 0){ + (*n)->set_fg_rgb(128, 250, 64); + if((*n)->printf("ASCII: [0x%02x (%03d)] '%lc'", r, r, + (wchar_t)(iswprint(r) ? r : printutf8(r))) < 0){ break; } }else{ if(nckey_supppuab_p(r)){ - n->set_fg_rgb(250, 64, 128); - if(n->printf("Special: [0x%02x (%02d)] '%s'", r, r, nckeystr(r)) < 0){ + (*n)->set_fg_rgb(250, 64, 128); + if((*n)->printf("Special: [0x%02x (%02d)] '%s'", r, r, nckeystr(r)) < 0){ break; } if(NCKey::IsMouse(r)){ - if(n->printf(-1, NCAlign::Right, " x: %d y: %d", ni.x, ni.y) < 0){ + if((*n)->printf(-1, NCAlign::Right, " x: %d y: %d", ni.x, ni.y) < 0){ break; } } }else{ - n->set_fg_rgb(64, 128, 250); - n->printf("Unicode: [0x%08x] '%lc'", r, (wchar_t)r); + (*n)->set_fg_rgb(64, 128, 250); + (*n)->printf("Unicode: [0x%08x] '%lc'", r, (wchar_t)r); } } - if(!dim_rows(n)){ + if(!dim_rows(*n)){ break; } const uint64_t sec = (timenow_to_ns() - start) / NANOSECS_IN_SEC; diff --git a/src/lib/debug.c b/src/lib/debug.c index 365a2b863..c15d90432 100644 --- a/src/lib/debug.c +++ b/src/lib/debug.c @@ -2,14 +2,22 @@ void notcurses_debug(notcurses* nc, FILE* debugfp){ const ncplane* n = nc->top; + const ncplane* prev = NULL; int planeidx = 0; fprintf(debugfp, "*************************** notcurses debug state *****************************\n"); while(n){ - fprintf(debugfp, "%04d off y: %3d x: %3d geom y: %3d x: %3d curs y: %3d x: %3d %s %p\n", - planeidx, n->absy, n->absx, n->leny, n->lenx, n->y, n->x, - n == notcurses_stdplane_const(nc) ? "std" : " ", n); - n = n->z; + fprintf(debugfp, "%04d off y: %3d x: %3d geom y: %3d x: %3d curs y: %3d x: %3d %s %p\n", + planeidx, n->absy, n->absx, n->leny, n->lenx, n->y, n->x, + n == notcurses_stdplane_const(nc) ? "std" : " ", n); + if(n->above != prev){ + fprintf(stderr, " WARNING: expected ->above %p, got %p\n", prev, n->above); + } + prev = n; + n = n->below; ++planeidx; } + if(nc->bottom != prev){ + fprintf(stderr, " WARNING: expected ->bottom %p, got %p\n", prev, nc->bottom); + } fprintf(debugfp, "*******************************************************************************\n"); } diff --git a/src/lib/internal.h b/src/lib/internal.h index 668500a69..cf287c152 100644 --- a/src/lib/internal.h +++ b/src/lib/internal.h @@ -65,7 +65,8 @@ typedef struct ncplane { int x, y; // current cursor location within this plane int absx, absy; // origin of the plane relative to the screen int lenx, leny; // size of the plane, [0..len{x,y}) is addressable - struct ncplane* z; // plane below us + struct ncplane* above;// plane above us, NULL if we're on top + struct ncplane* below;// plane below us, NULL if we're on bottom struct ncplane* bnext;// next in the bound list of plane to which we are bound struct ncplane* blist;// head of our own bound list, if any struct ncplane* bound;// plane to which we are bound, if any @@ -267,7 +268,8 @@ typedef struct ncdirect { } ncdirect; typedef struct notcurses { - ncplane* top; // the contents of our topmost plane (initially entire screen) + ncplane* top; // topmost plane, never NULL + ncplane* bottom;// bottommost plane, never NULL ncplane* stdscr;// aliases some plane from the z-buffer, covers screen // the style state of the terminal is carried across render runs diff --git a/src/lib/notcurses.c b/src/lib/notcurses.c index 8d4ba8ee0..71a741e1e 100644 --- a/src/lib/notcurses.c +++ b/src/lib/notcurses.c @@ -291,7 +291,12 @@ ncplane_create(notcurses* nc, ncplane* n, int rows, int cols, cell_init(&p->basecell); p->blist = NULL; p->userptr = opaque; - p->z = nc->top; + p->above = NULL; + if( (p->below = nc->top) ){ // always happens save initial plane + nc->top->above = p; + }else{ + nc->bottom = p; + } nc->top = p; p->nc = nc; nc->stats.fbbytes += fbsize; @@ -368,7 +373,6 @@ ncplane* ncplane_dup(const ncplane* n, void* opaque){ ncplane_cursor_move_yx(newn, n->y, n->x); newn->attrword = attr; newn->channels = chan; - ncplane_move_above_unsafe(newn, n); memmove(newn->fb, n->fb, sizeof(*n->fb) * dimx * dimy); // we dupd the egcpool, so just dup the goffset newn->basecell = n->basecell; @@ -481,21 +485,6 @@ fprintf(stderr, "Can't resize standard plane\n"); yoff, xoff, ylen, xlen); } -// find the pointer on the z-index referencing the specified plane. writing to -// this pointer will remove the plane (and everything below it) from the stack. -static ncplane** -find_above_ncplane(const ncplane* n){ - notcurses* nc = n->nc; - ncplane** above = &nc->top; - while(*above){ - if(*above == n){ - return above; - } - above = &((*above)->z); - } - return NULL; -} - int ncplane_destroy(ncplane* ncp){ if(ncp == NULL){ return 0; @@ -503,11 +492,16 @@ int ncplane_destroy(ncplane* ncp){ if(ncp->nc->stdscr == ncp){ return -1; } - ncplane** above = find_above_ncplane(ncp); - if(above == NULL){ - return -1; + if(ncp->above){ + ncp->above->below = ncp->below; + }else{ + ncp->nc->top = ncp->below; + } + if(ncp->below){ + ncp->below->above = ncp->above; + }else{ + ncp->nc->bottom = ncp->above; } - *above = ncp->z; // splice it out of the list free_plane(ncp); return 0; } @@ -806,11 +800,13 @@ notcurses* notcurses_init(const notcurses_options* opts, FILE* outfp){ goto err; } int dimy, dimx; - update_term_dimensions(ret->ttyfd, &dimy, &dimx); + if(update_term_dimensions(ret->ttyfd, &dimy, &dimx)){ + goto err; + } char* shortname_term = termname(); char* longname_term = longname(); if(!opts->suppress_banner){ - fprintf(stderr, "Term: %dx%d %s (%s)\n", dimx, dimy, + fprintf(stderr, "Term: %dx%d %s (%s)\n", dimy, dimx, shortname_term ? shortname_term : "?", longname_term ? longname_term : "?"); } @@ -826,7 +822,7 @@ notcurses* notcurses_init(const notcurses_options* opts, FILE* outfp){ term_verify_seq(&ret->tcache.smcup, "smcup"); term_verify_seq(&ret->tcache.rmcup, "rmcup"); } - ret->top = ret->stdscr = NULL; + ret->bottom = ret->top = ret->stdscr = NULL; if(ncvisual_init(ffmpeg_log_level(opts->loglevel))){ goto err; } @@ -867,11 +863,8 @@ err: void notcurses_drop_planes(notcurses* nc){ ncplane* p = nc->top; while(p){ - ncplane* tmp = p->z; - if(nc->stdscr == p){ - nc->top = p; - p->z = NULL; - }else{ + ncplane* tmp = p->below; + if(nc->stdscr != p){ free_plane(p); } p = tmp; @@ -883,9 +876,9 @@ int notcurses_stop(notcurses* nc){ if(nc){ ret |= notcurses_stop_minimal(nc); while(nc->top){ - ncplane* p = nc->top; - nc->top = p->z; - free_plane(p); + ncplane* p = nc->top->below; + free_plane(nc->top); + nc->top = p; } if(nc->rstate.mstreamfp){ fclose(nc->rstate.mstreamfp); @@ -1047,76 +1040,88 @@ const char* cell_extended_gcluster(const ncplane* n, const cell* c){ } // 'n' ends up above 'above' -int ncplane_move_above_unsafe(ncplane* restrict n, const ncplane* restrict above){ - if(n->z == above){ - return 0; - } - ncplane** an = find_above_ncplane(n); - if(an == NULL){ +int ncplane_move_above(ncplane* restrict n, ncplane* restrict above){ + if(n == above){ return -1; } - ncplane** aa = find_above_ncplane(above); - if(aa == NULL){ - return -1; + if(n->below != above){ + // splice out 'n' + if(n->below){ + n->below->above = n->above; + }else{ + n->nc->bottom = n->above; + } + if(n->above){ + n->above->below = n->below; + }else{ + n->nc->top = n->below; + } + if(above->above){ + above->above->below = n; + }else{ + n->nc->top = n; + } + above->above = n; + n->below = above; } - ncplane* deconst_above = *aa; - *an = n->z; // splice n out - n->z = deconst_above; // attach above below n - *aa = n; // spline n in above return 0; } // 'n' ends up below 'below' -int ncplane_move_below_unsafe(ncplane* restrict n, const ncplane* restrict below){ - if(below->z == n){ - return 0; +int ncplane_move_below(ncplane* restrict n, ncplane* restrict below){ + if(n == below){ + return -1; } - ncplane* deconst_below = NULL; - ncplane** an = &n->nc->top; - // go down, looking for n and below. if we find below, mark it. if we - // find n, break from the loop. - while(*an){ - if(*an == below){ - deconst_below = *an; - }else if(*an == n){ - *an = n->z; // splice n out + if(n->above != below){ + if(n->below){ + n->below->above = n->above; + }else{ + n->nc->bottom = n->above; } + if(n->above){ + n->above->below = n->below; + }else{ + n->nc->top = n->below; + } + if(below->below){ + below->below->above = n; + }else{ + n->nc->bottom = n; + } + below->below = n; + n->above = below; } - if(an == NULL){ - return -1; - } - while(deconst_below != below){ - deconst_below = deconst_below->z; - } - n->z = deconst_below->z; // reattach subbelow list to n - deconst_below->z = n; // splice n in below return 0; } -int ncplane_move_top(ncplane* n){ - ncplane** an = find_above_ncplane(n); - if(an == NULL){ - return -1; +void ncplane_move_top(ncplane* n){ + if(n->above){ + if( (n->above->below = n->below) ){ + n->below->above = n->above; + }else{ + n->nc->bottom = n->above; + } + n->above = NULL; + if( (n->below = n->nc->top) ){ + n->below->above = n; + } + n->nc->top = n; } - *an = n->z; // splice n out - n->z = n->nc->top; - n->nc->top = n; - return 0; } -int ncplane_move_bottom(ncplane* n){ - ncplane** an = find_above_ncplane(n); - if(an == NULL){ - return -1; +void ncplane_move_bottom(ncplane* n){ + if(n->below){ + if( (n->below->above = n->above) ){ + n->above->below = n->below; + }else{ + n->nc->top = n->below; + } + n->below = NULL; + if( (n->above = n->nc->bottom) ){ + n->above->below = n; + } + n->nc->bottom = n; } - *an = n->z; // splice n out - an = &n->nc->top; - while(*an){ - an = &(*an)->z; - } - *an = n; - n->z = NULL; - return 0; } void ncplane_cursor_yx(const ncplane* n, int* y, int* x){ @@ -1694,7 +1699,7 @@ ncplane* notcurses_top(notcurses* n){ } ncplane* ncplane_below(ncplane* n){ - return n->z; + return n->below; } // FIXME this clears the screen for some reason! what's up? diff --git a/src/lib/render.c b/src/lib/render.c index 7fad66e41..4c2dc61c7 100644 --- a/src/lib/render.c +++ b/src/lib/render.c @@ -1084,7 +1084,7 @@ notcurses_render_internal(notcurses* nc, struct crender* rvec){ free(fb); return -1; } - p = p->z; + p = p->below; } postpaint(fb, nc->lastframe, dimy, dimx, rvec, &nc->pool); free(fb); diff --git a/src/poc/reader.cpp b/src/poc/reader.cpp index 4d3dfa374..118771f88 100644 --- a/src/poc/reader.cpp +++ b/src/poc/reader.cpp @@ -20,6 +20,7 @@ auto main() -> int { opts.physrows = dimy / 8; opts.physcols = dimx / 2; opts.egc = strdup("░"); + // FIXME c++ is crashing //ncpp::Reader nr(nc, 0, 0, &opts); auto nr = ncreader_create(*n, 2, 2, &opts); if(nr == nullptr){ diff --git a/tests/zaxis.cpp b/tests/zaxis.cpp index 840a1441b..feb76beed 100644 --- a/tests/zaxis.cpp +++ b/tests/zaxis.cpp @@ -21,11 +21,11 @@ TEST_CASE("ZAxis") { // if you want to move the plane which is already top+bottom to either, go ahead SUBCASE("StdPlaneOnanism") { - CHECK(0 == ncplane_move_top(n_)); + ncplane_move_top(n_); struct ncplane* top = notcurses_top(nc_); CHECK(n_ == top); CHECK(!ncplane_below(top)); - CHECK(0 == ncplane_move_bottom(n_)); + ncplane_move_bottom(n_); CHECK(!ncplane_below(n_)); } @@ -57,7 +57,7 @@ TEST_CASE("ZAxis") { CHECK(np == top); CHECK(n_ == ncplane_below(top)); CHECK(!ncplane_below(n_)); - CHECK(!ncplane_move_top(np)); + ncplane_move_top(np); // verify it top = notcurses_top(nc_); CHECK(np == top); @@ -73,7 +73,7 @@ TEST_CASE("ZAxis") { CHECK(np == top); CHECK(n_ == ncplane_below(top)); CHECK(!ncplane_below(n_)); - CHECK(!ncplane_move_bottom(np)); + ncplane_move_bottom(np); top = notcurses_top(nc_); CHECK(n_ == top); CHECK(np == ncplane_below(top));