O(1) z-axis moves #623

Replace the singly-linked z-axis with a doubly-linked list,
and reimplement all z-axis moves as O(1) functions.
Eliminate ncplane_move_{above/below}_unsafe(), as there are no
longer unsafe moves.
pull/632/head
nick black 4 years ago
parent a288c2e654
commit 37a4114f42
No known key found for this signature in database
GPG Key ID: 5F43400C21CBFACC

@ -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.

@ -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.

@ -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);**

@ -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);

@ -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);

@ -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);

@ -214,7 +214,7 @@ int main(void){
if(!nc.mouse_enable()){
return EXIT_FAILURE;
}
auto n = nc.get_stdplane(&dimy, &dimx);
std::unique_ptr<Plane*> n = std::make_unique<Plane*>(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;

@ -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");
}

@ -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

@ -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;
}
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(an == NULL){
int ncplane_move_below(ncplane* restrict n, ncplane* restrict below){
if(n == below){
return -1;
}
while(deconst_below != below){
deconst_below = deconst_below->z;
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;
}
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;
}
*an = n->z; // splice n out
an = &n->nc->top;
while(*an){
an = &(*an)->z;
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;
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?

@ -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);

@ -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){

@ -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));

Loading…
Cancel
Save