mirror of
https://github.com/dankamongmen/notcurses.git
synced 2024-11-18 03:25:55 +00:00
render: carry state across renders #203
This commit is contained in:
parent
5dfe861de4
commit
4b953e33f2
@ -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()
|
||||
|
||||
|
@ -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);
|
||||
|
@ -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){
|
||||
|
Loading…
Reference in New Issue
Block a user