render: carry state across renders #203

This commit is contained in:
nick black 2019-12-24 05:33:42 -05:00
parent 5dfe861de4
commit 4b953e33f2
No known key found for this signature in database
GPG Key ID: 5F43400C21CBFACC
3 changed files with 55 additions and 47 deletions

View File

@ -77,11 +77,39 @@ typedef struct ncvisual {
struct notcurses* ncobj; // set iff this ncvisual "owns" its ncplane
} ncvisual;
// current presentation state of the terminal. it is carried across render
// instances. initialize everything to 0 on a terminal reset / startup.
typedef struct renderstate {
// we assemble the encoded output in a POSIX memstream, and keep it around
// between uses. this could be a problem if it ever tremendously spiked, but
// that's a highly unlikely situation.
char* mstream; // buffer for rendering memstream, see open_memstream(3)
FILE* mstreamfp;// FILE* for rendering memstream
size_t mstrsize;// size of rendering memstream
uint32_t curattr;// current attributes set (does not include colors)
unsigned lastr; // foreground rgb
unsigned lastg;
unsigned lastb;
unsigned lastbr; // background rgb
unsigned lastbg;
unsigned lastbb;
// we can elide a color escape iff the color has not changed between the two
// cells and the current cell uses no defaults, or if both the current and
// the last used both defaults.
bool fgelidable;
bool bgelidable;
bool defaultelidable;
} renderstate;
typedef struct notcurses {
pthread_mutex_t lock;
ncplane* top; // the contents of our topmost plane (initially entire screen)
ncplane* stdscr;// aliases some plane from the z-buffer, 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
@ -89,13 +117,6 @@ typedef struct notcurses {
int lfdimy; // lfdimx/lfdimy are 0 until first render
egcpool pool; // duplicate EGCs into this pool
// we assemble the encoded output in a POSIX memstream, and keep it around
// between uses. this could be a problem if it ever tremendously spiked, but
// that's a highly unlikely situation.
char* mstream; // buffer for rendering memstream, see open_memstream(3)
FILE* mstreamfp;// FILE* for rendering memstream
size_t mstrsize;// size of rendering memstream
ncstats stats; // some statistics across the lifetime of the notcurses ctx
ncstats stashstats; // cumulative stats, unaffected by notcurses_reset_stats()

View File

@ -696,8 +696,7 @@ notcurses* notcurses_init(const notcurses_options* opts, FILE* outfp){
ret->renderfp = opts->renderfp;
ret->inputescapes = NULL;
ret->ttyinfp = stdin; // FIXME
ret->mstream = NULL;
ret->mstrsize = 0;
memset(&ret->rstate, 0, sizeof(ret->rstate));
ret->lastframe = NULL;
ret->lfdimy = 0;
ret->lfdimx = 0;
@ -761,7 +760,7 @@ notcurses* notcurses_init(const notcurses_options* opts, FILE* outfp){
free_plane(ret->top);
goto err;
}
if((ret->mstreamfp = open_memstream(&ret->mstream, &ret->mstrsize)) == NULL){
if((ret->rstate.mstreamfp = open_memstream(&ret->rstate.mstream, &ret->rstate.mstrsize)) == NULL){
free_plane(ret->top);
goto err;
}
@ -831,12 +830,12 @@ int notcurses_stop(notcurses* nc){
nc->top = p->z;
free_plane(p);
}
if(nc->mstreamfp){
fclose(nc->mstreamfp);
if(nc->rstate.mstreamfp){
fclose(nc->rstate.mstreamfp);
}
egcpool_dump(&nc->pool);
free(nc->lastframe);
free(nc->mstream);
free(nc->rstate.mstream);
input_free_esctrie(&nc->inputescapes);
ret |= pthread_mutex_destroy(&nc->lock);
stash_stats(nc);

View File

@ -42,9 +42,9 @@ int notcurses_refresh(notcurses* nc){
int ret;
pthread_mutex_lock(&nc->lock);
pthread_cleanup_push(mutex_unlock, &nc->lock);
if(nc->mstream == NULL){
if(nc->rstate.mstream == NULL){
ret = -1; // haven't rendered yet, and thus don't know what should be there
}else if(blocking_write(nc->ttyfd, nc->mstream, nc->mstrsize)){
}else if(blocking_write(nc->ttyfd, nc->rstate.mstream, nc->rstate.mstrsize)){
ret = -1;
}else{
ret = 0;
@ -440,8 +440,6 @@ cellcmp_and_dupfar(egcpool* dampool, cell* damcell, const ncplane* srcplane,
}else{
const char* damegc = egcpool_extended_gcluster(dampool, damcell);
const char* srcegc = extended_gcluster(srcplane, srccell);
assert(strcmp(damegc, "三体"));
assert(strcmp(srcegc, "三体"));
if(strcmp(damegc, srcegc) == 0){
return 0; // EGC match
}
@ -457,22 +455,12 @@ static inline int
notcurses_render_internal(notcurses* nc){
int ret = 0;
int y, x;
FILE* out = nc->mstreamfp;
FILE* out = nc->rstate.mstreamfp;
fseeko(out, 0, SEEK_SET);
// don't write a clearscreen. we only update things that have been changed.
// we explicitly move the cursor at the beginning of each output line, so no
// need to home it expliticly.
prep_optimized_palette(nc, out); // FIXME do what on failure?
uint32_t curattr = 0; // current attributes set (does not include colors)
// FIXME as of at least gcc 9.2.1, we get a false -Wmaybe-uninitialized below
// when using these without explicit initializations. for the life of me, i
// can't see any such path, and valgrind is cool with it, so what ya gonna do?
unsigned lastr = 0, lastg = 0, lastb = 0;
unsigned lastbr = 0, lastbg = 0, lastbb = 0;
// we can elide a color escape iff the color has not changed between the two
// cells and the current cell uses no defaults, or if both the current and
// the last used both defaults.
bool fgelidable = false, bgelidable = false, defaultelidable = false;
// if this fails, struggle bravely on. we can live without a lastframe.
reshape_shadow_fb(nc);
for(y = 0 ; y < nc->stdscr->leny ; ++y){
@ -525,11 +513,11 @@ notcurses_render_internal(notcurses* nc){
// set the style. this can change the color back to the default; if it
// does, we need update our elision possibilities.
bool normalized;
term_setstyles(nc, out, &curattr, &c, &normalized);
term_setstyles(nc, out, &nc->rstate.curattr, &c, &normalized);
if(normalized){
defaultelidable = true;
bgelidable = false;
fgelidable = false;
nc->rstate.defaultelidable = true;
nc->rstate.bgelidable = false;
nc->rstate.fgelidable = false;
}
// we allow these to be set distinctly, but terminfo only supports using
// them both via the 'op' capability. unless we want to generate the 'op'
@ -538,42 +526,42 @@ notcurses_render_internal(notcurses* nc){
// we can elide the default set iff the previous used both defaults
if(cell_fg_default_p(&c) || cell_bg_default_p(&c)){
if(!defaultelidable){
if(!nc->rstate.defaultelidable){
++nc->stats.defaultemissions;
term_emit("op", nc->op, out, false);
}else{
++nc->stats.defaultelisions;
}
// if either is not default, this will get turned off
defaultelidable = true;
fgelidable = false;
bgelidable = false;
nc->rstate.defaultelidable = true;
nc->rstate.fgelidable = false;
nc->rstate.bgelidable = false;
}
// we can elide the foreground set iff the previous used fg and matched
if(!cell_fg_default_p(&c)){
cell_get_fg_rgb(&c, &r, &g, &b);
if(fgelidable && lastr == r && lastg == g && lastb == b){
if(nc->rstate.fgelidable && nc->rstate.lastr == r && nc->rstate.lastg == g && nc->rstate.lastb == b){
++nc->stats.fgelisions;
}else{
term_fg_rgb8(nc, out, r, g, b);
++nc->stats.fgemissions;
fgelidable = true;
nc->rstate.fgelidable = true;
}
lastr = r; lastg = g; lastb = b;
defaultelidable = false;
nc->rstate.lastr = r; nc->rstate.lastg = g; nc->rstate.lastb = b;
nc->rstate.defaultelidable = false;
}
if(!cell_bg_default_p(&c)){
cell_get_bg_rgb(&c, &br, &bg, &bb);
if(bgelidable && lastbr == br && lastbg == bg && lastbb == bb){
if(nc->rstate.bgelidable && nc->rstate.lastbr == br && nc->rstate.lastbg == bg && nc->rstate.lastbb == bb){
++nc->stats.bgelisions;
}else{
term_bg_rgb8(nc, out, br, bg, bb);
++nc->stats.bgemissions;
bgelidable = true;
nc->rstate.bgelidable = true;
}
lastbr = br; lastbg = bg; lastbb = bb;
defaultelidable = false;
nc->rstate.lastbr = br; nc->rstate.lastbg = bg; nc->rstate.lastbb = bb;
nc->rstate.defaultelidable = false;
}
// fprintf(stderr, "[%02d/%02d] 0x%02x 0x%02x 0x%02x %p\n", y, x, r, g, b, p);
term_putc(out, p, &c);
@ -584,15 +572,15 @@ notcurses_render_internal(notcurses* nc){
}
ret |= fflush(out);
fflush(nc->ttyfp);
if(blocking_write(nc->ttyfd, nc->mstream, nc->mstrsize)){
if(blocking_write(nc->ttyfd, nc->rstate.mstream, nc->rstate.mstrsize)){
ret = -1;
}
/*fprintf(stderr, "%lu/%lu %lu/%lu %lu/%lu\n", defaultelisions, defaultemissions,
fgelisions, fgemissions, bgelisions, bgemissions);*/
if(nc->renderfp){
fprintf(nc->renderfp, "%s\n", nc->mstream);
fprintf(nc->renderfp, "%s\n", nc->rstate.mstream);
}
return nc->mstrsize;
return nc->rstate.mstrsize;
}
int notcurses_render(notcurses* nc){