ncpile creation/destruction #1078

pull/1134/head
nick black 4 years ago committed by Nick Black
parent b747af2ae8
commit de082c7ba2

@ -1,10 +1,10 @@
#include "internal.h"
void notcurses_debug(notcurses* nc, FILE* debugfp){
const ncplane* n = nc->top;
static void
ncpile_debug(const ncpile* p, FILE* debugfp){
const ncplane* n = p->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 %p %.8s\n",
planeidx, n->absy, n->absx, n->leny, n->lenx, n->y, n->x, n, n->name);
@ -12,9 +12,6 @@ void notcurses_debug(notcurses* nc, FILE* debugfp){
fprintf(debugfp, " bound %p → %p ← %p binds %p\n",
n->boundto, n->bnext, n->bprev, n->blist);
}
if(n->bnext == n || (n->boundto == n && n != nc->stdplane) || n->blist == n){
fprintf(debugfp, "WARNING: bound pointers target self\n");
}
if(n->bprev && (*n->bprev != n)){
fprintf(stderr, " WARNING: expected *->bprev %p, got %p\n", n, *n->bprev);
}
@ -25,8 +22,18 @@ void notcurses_debug(notcurses* nc, FILE* debugfp){
n = n->below;
++planeidx;
}
if(nc->bottom != prev){
fprintf(stderr, " WARNING: expected ->bottom %p, got %p\n", prev, nc->bottom);
if(p->bottom != prev){
fprintf(stderr, " WARNING: expected ->bottom %p, got %p\n", prev, p->bottom);
}
}
void notcurses_debug(notcurses* nc, FILE* debugfp){
const ncpile* p = ncplane_pile(nc->stdplane);
fprintf(debugfp, " ************************** notcurses debug state *****************************\n");
const ncpile* p0 = p;
do{
ncpile_debug(p0, debugfp);
p0 = p0->next;
}while(p != p0);
fprintf(debugfp, " ******************************************************************************\n");
}

@ -46,16 +46,11 @@ struct esctrie;
// and we can't go throwing C++ syntax into this header. so it goes.
// A plane is memory for some rectilinear virtual window, plus current cursor
// state for that window. A notcurses context describes a single terminal, and
// has a z-order of planes (I see no advantage to maintaining a poset, and we
// instead just use a list, top-to-bottom). Every cell on the terminal is part
// of at least one plane, and at least one plane covers the entirety of the
// terminal (this plane is created during initialization).
//
// Functions update these virtual planes over a series of API calls. Eventually,
// notcurses_render() is called. We then do a depth buffer blit of updated
// cells. A cell is updated if the topmost plane including that cell updates it,
// not simply if any plane updates it.
// state for that window, and part of a pile. Each pile has a total order along
// its z-axis. Functions update these virtual planes over a series of API
// calls. Eventually, notcurses_render() is called. We then do a depth buffer
// blit of updated cells. A cell is updated if the topmost plane including that
// cell updates it, not simply if any plane updates it.
//
// A plane may be partially or wholly offscreen--this might occur if the
// screen is resized, for example. Offscreen portions will not be rendered.
@ -63,7 +58,7 @@ struct esctrie;
//
// The framebuffer 'fb' is a set of rows. For scrolling, we interpret it as a
// circular buffer of rows. 'logrow' is the index of the row at the logical top
// of the plane.
// of the plane. It only changes from 0 if the plane is scrollable.
typedef struct ncplane {
cell* fb; // "framebuffer" of character cells
int logrow; // logical top row, starts at 0, add one for each scroll
@ -89,7 +84,7 @@ typedef struct ncplane {
void* userptr; // slot for the user to stick some opaque pointer
int (*resizecb)(struct ncplane*); // callback after parent is resized
cell basecell; // cell written anywhere that fb[i].gcluster == 0
struct notcurses* nc; // notcurses object of which we are a part
struct ncpile* pile; // pile of which we are a part
char* name; // used only for debugging
ncalign_e align; // relative to parent plane, for automatic realignment
uint16_t stylemask; // same deal as in a cell
@ -303,23 +298,31 @@ typedef struct ncdirect {
uint64_t flags; // copied in ncdirect_init() from param
} ncdirect;
typedef struct notcurses {
typedef struct ncpile {
ncplane* top; // topmost plane, never NULL
ncplane* bottom; // bottommost plane, never NULL
ncplane* stdplane;// standard plane, covers screen
ncplane* root; // first plane of the root set
struct notcurses* nc; // notcurses context
struct ncpile *prev, *next; // circular list
} ncpile;
// the standard pile can be reached through ->stdplane.
typedef struct notcurses {
ncplane* stdplane; // standard plane, covers screen
// the style state of the terminal is carried across render runs
renderstate rstate;
// we keep a copy of the last rendered frame. this facilitates O(1)
// notcurses_at_yx() and O(1) damage detection (at the cost of some memory).
cell* lastframe;// last rendered framebuffer, NULL until first render
cell* lastframe;// last rasterized framebuffer, NULL until first rasterization
egcpool pool; // egcpool for lastframe
int lfdimx; // dimensions of lastframe, unchanged by screen resize
int lfdimy; // lfdimx/lfdimy are 0 until first render
egcpool pool; // duplicate EGCs into this pool
int lfdimy; // lfdimx/lfdimy are 0 until first rasterization
int cursory; // desired cursor placement according to user. -1 is a don't-
int cursorx; // care, otherwise moved here after each render.
int cursory; // desired cursor placement according to user.
int cursorx; // -1 is don't-care, otherwise moved here after each render.
ncstats stats; // some statistics across the lifetime of the notcurses ctx
ncstats stashstats; // cumulative stats, unaffected by notcurses_stats_reset()
@ -333,6 +336,7 @@ typedef struct notcurses {
FILE* renderfp; // debugging FILE* to which renderings are written
tinfo tcache; // terminfo cache
struct termios tpreserved; // terminal state upon entry
pthread_mutex_t pilelock; // guards pile list
bool suppress_banner; // from notcurses_options
// desired margins (best-effort only), copied in from notcurses_options
@ -375,6 +379,16 @@ mbstr_find_codepoint(const char* s, char32_t cp, int* col){
return -1;
}
static inline ncpile*
ncplane_pile(ncplane* n){
return n->pile;
}
static inline const ncpile*
ncplane_pile_const(const ncplane* n){
return n->pile;
}
static inline ncplane*
ncplane_stdplane(ncplane* n){
return notcurses_stdplane(ncplane_notcurses(n));

@ -271,12 +271,28 @@ int update_term_dimensions(int fd, int* rows, int* cols){
return 0;
}
// destroy an empty ncpile. only call with pilelock held.
static void
ncpile_destroy(ncpile* pile){
if(pile){
pile->prev->next = pile->next;
pile->next->prev = pile->prev;
free(pile);
}
}
void free_plane(ncplane* p){
if(p){
// ncdirect fakes an ncplane with no ->nc
if(ncplane_notcurses(p)){
// ncdirect fakes an ncplane with no ->pile
if(ncplane_pile(p)){
--ncplane_notcurses(p)->stats.planes;
ncplane_notcurses(p)->stats.fbbytes -= sizeof(*p->fb) * p->leny * p->lenx;
if(p->above == NULL && p->below == NULL){
struct notcurses* nc = ncplane_notcurses(p);
pthread_mutex_lock(&nc->pilelock);
ncpile_destroy(ncplane_pile(p));
pthread_mutex_unlock(&nc->pilelock);
}
}
egcpool_dump(&p->pool);
free(p->name);
@ -285,17 +301,42 @@ void free_plane(ncplane* p){
}
}
// create a new ncpile. only call with pilelock held.
static ncpile*
ncpile_create(notcurses* nc){
ncpile* ret = malloc(sizeof(*ret));
if(ret){
ret->nc = nc;
ret->top = NULL;
ret->bottom = NULL;
ret->root = NULL;
if(nc->stdplane){
ret->prev = ncplane_pile(nc->stdplane)->prev;
ncplane_pile(nc->stdplane)->prev->next = ret;
ret->next = ncplane_pile(nc->stdplane);
ncplane_pile(nc->stdplane)->prev = ret;
}else{
ret->prev = ret;
ret->next = ret;
}
}
return ret;
}
// create a new ncplane at the specified location (relative to the true screen,
// having origin at 0,0), having the specified size, and put it at the top of
// the planestack. its cursor starts at its origin; its style starts as null.
// a plane may exceed the boundaries of the screen, but must have positive
// size in both dimensions. bind the plane to 'n', which may be NULL. if bound
// to a plane, this plane moves when that plane moves, and move targets are
// relative to that plane.
// there's a denormalized case we also must handle, that of the "fake" isolated
// ncplane created by ncdirect for rendering visuals. in that case (and only in
// that case), nc is NULL.
ncplane* ncplane_new_internal(notcurses* nc, ncplane* n, const ncplane_options* nopts){
// size in both dimensions. bind the plane to 'n', which may be NULL to create
// a new pile. if bound to a plane instead, this plane moves when that plane
// moves, and coordinates to move to are relative to that plane.
// there are two denormalized case we also must handle, that of the "fake"
// isolated ncplane created by ncdirect for rendering visuals. in that case
// (and only in that case), nc is NULL (as is n). there's also creation of the
// initial standard plane, in which case nc is not NULL, but nc->stdplane *is*
// (as once more is n).
ncplane* ncplane_new_internal(notcurses* nc, ncplane* n,
const ncplane_options* nopts){
if(nopts->flags > NCPLANE_OPTION_HORALIGNED){
logwarn(nc, "Provided unsupported flags %016lx\n", nopts->flags);
}
@ -327,6 +368,7 @@ ncplane* ncplane_new_internal(notcurses* nc, ncplane* n, const ncplane_options*
p->align = nopts->x;
}else{
p->absx = nopts->x;
p->align = NCALIGN_UNALIGNED;
}
p->absx += n->absx;
p->absy = nopts->y + n->absy;
@ -335,8 +377,7 @@ ncplane* ncplane_new_internal(notcurses* nc, ncplane* n, const ncplane_options*
}
p->bprev = &n->blist;
*p->bprev = p;
}else{ // new standard plane
assert(!(nopts->flags & NCPLANE_OPTION_HORALIGNED));
}else{ // new root plane, new pile
assert(0 == nopts->y);
assert(0 == nopts->x);
p->absx = (nc ? nc->margin_l : 0);
@ -344,6 +385,8 @@ ncplane* ncplane_new_internal(notcurses* nc, ncplane* n, const ncplane_options*
p->bnext = NULL;
p->bprev = NULL;
p->boundto = p;
p->absx = nopts->x;
p->align = NCALIGN_UNALIGNED;
}
p->resizecb = nopts->resizecb;
p->stylemask = 0;
@ -352,17 +395,27 @@ ncplane* ncplane_new_internal(notcurses* nc, ncplane* n, const ncplane_options*
cell_init(&p->basecell);
p->userptr = nopts->userptr;
p->above = NULL;
if( (p->nc = nc) ){ // every plane has a notcurses object
if( (p->below = nc->top) ){ // always happens save initial plane
nc->top->above = p;
}else{
nc->bottom = p;
}
nc->top = p;
nc->stats.fbbytes += fbsize;
++nc->stats.planes;
}else{ // fake ncplane backing ncdirect object
if(nc == NULL){ // fake ncplane backing ncdirect object
p->below = NULL;
}else{
pthread_mutex_lock(&nc->pilelock);
ncpile* pile = n ? ncplane_pile(n) : NULL;
if( (p->pile = pile) ){ // existing pile
if( (p->below = pile->top) ){ // always happens save initial plane
pile->top->above = p;
}else{
pile->bottom = p;
}
pile->top = p;
nc->stats.fbbytes += fbsize;
++nc->stats.planes;
}else{ // new pile
p->pile = ncpile_create(nc);
p->pile->top = p;
p->pile->bottom = p;
p->below = NULL;
}
pthread_mutex_unlock(&nc->pilelock);
}
loginfo(nc, "Created new %dx%d plane \"%s\" @ %dx%d\n",
nopts->rows, nopts->cols, p->name, p->absy, p->absx);
@ -392,6 +445,10 @@ const ncplane* notcurses_stdplane_const(const notcurses* nc){
}
ncplane* ncplane_create(ncplane* n, const ncplane_options* nopts){
if((nopts->flags & NCPLANE_OPTION_HORALIGNED) && !n){
logerror(ncplane_notcurses(n), "Can't align a root plane");
return NULL;
}
return ncplane_new_internal(ncplane_notcurses(n), n, nopts);
}
@ -620,12 +677,12 @@ int ncplane_destroy(ncplane* ncp){
if(ncp->above){
ncp->above->below = ncp->below;
}else{
ncplane_notcurses(ncp)->top = ncp->below;
ncplane_pile(ncp)->top = ncp->below;
}
if(ncp->below){
ncp->below->above = ncp->above;
}else{
ncplane_notcurses(ncp)->bottom = ncp->above;
ncplane_pile(ncp)->bottom = ncp->above;
}
if(ncp->bprev){
if( (*ncp->bprev = ncp->bnext) ){
@ -921,6 +978,11 @@ notcurses* notcurses_init(const notcurses_options* opts, FILE* outfp){
if(outfp == NULL){
outfp = stdout;
}
if(pthread_mutex_init(&ret->pilelock, NULL)){
fprintf(stderr, "Couldn't initialize pile mutex\n");
free(ret);
return NULL;
}
ret->margin_t = opts->margin_t;
ret->margin_b = opts->margin_b;
ret->margin_l = opts->margin_l;
@ -1002,25 +1064,25 @@ notcurses* notcurses_init(const notcurses_options* opts, FILE* outfp){
terminfostr(&ret->tcache.smcup, "smcup");
terminfostr(&ret->tcache.rmcup, "rmcup");
}
ret->bottom = ret->top = ret->stdplane = NULL;
if(ncvisual_init(ffmpeg_log_level(ret->loglevel))){
goto err;
}
ret->stdplane = NULL;
if((ret->stdplane = create_initial_ncplane(ret, dimy, dimx)) == NULL){
goto err;
}
if(ret->ttyfd >= 0){
if(ret->tcache.smkx && tty_emit("smkx", ret->tcache.smkx, ret->ttyfd)){
free_plane(ret->top);
free_plane(ret->stdplane);
goto err;
}
if(ret->tcache.civis && tty_emit("civis", ret->tcache.civis, ret->ttyfd)){
free_plane(ret->top);
free_plane(ret->stdplane);
goto err;
}
}
if((ret->rstate.mstreamfp = open_memstream(&ret->rstate.mstream, &ret->rstate.mstrsize)) == NULL){
free_plane(ret->top);
free_plane(ret->stdplane);
goto err;
}
ret->rstate.x = ret->rstate.y = -1;
@ -1029,7 +1091,7 @@ notcurses* notcurses_init(const notcurses_options* opts, FILE* outfp){
if(ret->ttyfd >= 0){
if(ret->tcache.smcup){
if(tty_emit("smcup", ret->tcache.smcup, ret->ttyfd)){
free_plane(ret->top);
free_plane(ret->stdplane);
goto err;
}
// explicit clear even though smcup *might* clear
@ -1048,6 +1110,7 @@ notcurses* notcurses_init(const notcurses_options* opts, FILE* outfp){
return ret;
err:
fprintf(stderr, "Alas, you will not be going to space today.\n");
// FIXME looks like we have some memory leaks on this error path?
tcsetattr(ret->ttyfd, TCSANOW, &ret->tpreserved);
drop_signals(ret);
@ -1055,8 +1118,10 @@ err:
return NULL;
}
void notcurses_drop_planes(notcurses* nc){
ncplane* p = nc->top;
// updates *pile to point at (*pile)->next, frees all but standard pile/plane
static void
ncpile_drop(notcurses* nc, ncpile** pile){
ncplane* p = (*pile)->top;
while(p){
ncplane* tmp = p->below;
if(nc->stdplane != p){
@ -1064,7 +1129,24 @@ void notcurses_drop_planes(notcurses* nc){
}
p = tmp;
}
nc->top = nc->bottom = nc->stdplane;
ncpile* tmp = (*pile)->next;
if(*pile != ncplane_pile(nc->stdplane)){
ncpile_destroy(*pile);
}
*pile = tmp;
}
// drop all piles and all planes, save the standard plane and its pile
void notcurses_drop_planes(notcurses* nc){
pthread_mutex_lock(&nc->pilelock);
ncpile* p = ncplane_pile(nc->stdplane);
ncpile* p0 = p;
do{
ncpile_drop(nc, &p);
}while(p0 != p);
pthread_mutex_unlock(&nc->pilelock);
ncplane_pile(nc->stdplane)->top = nc->stdplane;
ncplane_pile(nc->stdplane)->bottom = nc->stdplane;
nc->stdplane->above = nc->stdplane->below = NULL;
}
@ -1072,10 +1154,9 @@ int notcurses_stop(notcurses* nc){
int ret = 0;
if(nc){
ret |= notcurses_stop_minimal(nc);
while(nc->top){
ncplane* p = nc->top->below;
free_plane(nc->top);
nc->top = p;
if(nc->stdplane){
notcurses_drop_planes(nc);
free_plane(nc->stdplane);
}
if(nc->rstate.mstreamfp){
fclose(nc->rstate.mstreamfp);
@ -1135,6 +1216,7 @@ int notcurses_stop(notcurses* nc){
}
}
del_curterm(cur_term);
ret |= pthread_mutex_destroy(&nc->pilelock);
free(nc);
}
return ret;
@ -1248,22 +1330,25 @@ int ncplane_move_above(ncplane* restrict n, ncplane* restrict above){
if(n == above){
return -1;
}
if(ncplane_pile(n) != ncplane_pile(above)){ // can't move among piles
return -1;
}
if(n->below != above){
// splice out 'n'
if(n->below){
n->below->above = n->above;
}else{
ncplane_notcurses(n)->bottom = n->above;
ncplane_pile(n)->bottom = n->above;
}
if(n->above){
n->above->below = n->below;
}else{
ncplane_notcurses(n)->top = n->below;
ncplane_pile(n)->top = n->below;
}
if( (n->above = above->above) ){
above->above->below = n;
}else{
ncplane_notcurses(n)->top = n;
ncplane_pile(n)->top = n;
}
above->above = n;
n->below = above;
@ -1276,21 +1361,24 @@ int ncplane_move_below(ncplane* restrict n, ncplane* restrict below){
if(n == below){
return -1;
}
if(ncplane_pile(n) != ncplane_pile(below)){ // can't move among piles
return -1;
}
if(n->above != below){
if(n->below){
n->below->above = n->above;
}else{
ncplane_notcurses(n)->bottom = n->above;
ncplane_pile(n)->bottom = n->above;
}
if(n->above){
n->above->below = n->below;
}else{
ncplane_notcurses(n)->top = n->below;
ncplane_pile(n)->top = n->below;
}
if( (n->below = below->below) ){
below->below->above = n;
}else{
ncplane_notcurses(n)->bottom = n;
ncplane_pile(n)->bottom = n;
}
below->below = n;
n->above = below;
@ -1303,13 +1391,13 @@ void ncplane_move_top(ncplane* n){
if( (n->above->below = n->below) ){
n->below->above = n->above;
}else{
ncplane_notcurses(n)->bottom = n->above;
ncplane_pile(n)->bottom = n->above;
}
n->above = NULL;
if( (n->below = ncplane_notcurses(n)->top) ){
if( (n->below = ncplane_pile(n)->top) ){
n->below->above = n;
}
ncplane_notcurses(n)->top = n;
ncplane_pile(n)->top = n;
}
}
@ -1318,13 +1406,13 @@ void ncplane_move_bottom(ncplane* n){
if( (n->below->above = n->above) ){
n->above->below = n->below;
}else{
ncplane_notcurses(n)->top = n->below;
ncplane_pile(n)->top = n->below;
}
n->below = NULL;
if( (n->above = ncplane_notcurses(n)->bottom) ){
if( (n->above = ncplane_pile(n)->bottom) ){
n->above->below = n;
}
ncplane_notcurses(n)->bottom = n;
ncplane_pile(n)->bottom = n;
}
}
@ -1898,11 +1986,11 @@ void ncplane_erase(ncplane* n){
}
ncplane* notcurses_top(notcurses* n){
return n->top;
return ncplane_pile(n->stdplane)->top;
}
ncplane* notcurses_bottom(notcurses* n){
return n->bottom;
return ncplane_pile(n->stdplane)->bottom;
}
ncplane* ncplane_below(ncplane* n){
@ -2021,11 +2109,11 @@ void ncplane_translate(const ncplane* src, const ncplane* dst,
}
notcurses* ncplane_notcurses(ncplane* n){
return n->nc;
return ncplane_pile(n)->nc;
}
const notcurses* ncplane_notcurses_const(const ncplane* n){
return n->nc;
return ncplane_pile_const(n)->nc;
}
ncplane* ncplane_parent(ncplane* n){

@ -1039,7 +1039,7 @@ static int
notcurses_render_internal(notcurses* nc, struct crender* rvec){
int dimy, dimx;
ncplane_dim_yx(nc->stdplane, &dimy, &dimx);
ncplane* p = nc->top;
ncplane* p = ncplane_pile(nc->stdplane)->top;
while(p){
paint(p, rvec, nc->stdplane->leny, nc->stdplane->lenx,
nc->stdplane->absy, nc->stdplane->absx);

@ -724,6 +724,7 @@ TEST_CASE("NCPlane") {
// FIXME verify with ncplane_at_cursor_cell()
CHECK(0 == ncplane_destroy(ncp));
}
SUBCASE("MoveToLowerRight") {
int ncols, nrows;
ncplane_dim_yx(n_, &nrows, &ncols);

Loading…
Cancel
Save