O(1) passive damage detection #189 (#197)

* notcurses: set up lastframe #189
* render: o(1) take no prisoners damage detection #189
pull/204/head
Nick Black 5 years ago committed by GitHub
parent 8b1b2ebdf0
commit 9b81de3789
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -6,6 +6,7 @@ notcurses-demo \- Show off some notcurses features
[ \fB\-p \fIpath \fR] [ \fB\-p \fIpath \fR]
[ \fB\-d \fIdelaymult \fR] [ \fB\-d \fIdelaymult \fR]
[ \fB\-k \fR] [ \fB\-k \fR]
[ \fB\-c \fR]
[ \fB\-h / \fB\-\-help \fR] [ \fB\-h / \fB\-\-help \fR]
.IR demospec .IR demospec
.SH OPTIONS .SH OPTIONS
@ -20,6 +21,9 @@ Apply a (floating-point) multiplier to the standard delay of 1s.
Inhibit use of the alternate screen. Necessary if you want the output left Inhibit use of the alternate screen. Necessary if you want the output left
on your terminal after the program exits. on your terminal after the program exits.
.TP .TP
.BR \-c
Do not attempt to seed the PRNG. This is useful when benchmarking.
.TP
.BR \-h ", " \-\-help .BR \-h ", " \-\-help
Print a usage message, and exit with success. Print a usage message, and exit with success.
.TP .TP
@ -45,7 +49,7 @@ contains a set of text-based demonstrations of capabilities from the notcurses l
.P .P
(s)liders—a missing-piece puzzle made up of colorful blocks (s)liders—a missing-piece puzzle made up of colorful blocks
.P .P
(b)leachworm—a great Nothing slowly robs the world of color (w)hiteworm—a great Nothing slowly robs the world of color
.P .P
(v)iew—images and a video are rendered as text (v)iew—images and a video are rendered as text
.P .P

@ -10,6 +10,10 @@
#include <notcurses.h> #include <notcurses.h>
#include "demo.h" #include "demo.h"
// ansi terminal definition-4-life
static const int MIN_SUPPORTED_ROWS = 25;
static const int MIN_SUPPORTED_COLS = 80;
static const char DEFAULT_DEMO[] = "iemlubgswvpo"; static const char DEFAULT_DEMO[] = "iemlubgswvpo";
static char datadir[PATH_MAX] = "/usr/share/notcurses"; // FIXME static char datadir[PATH_MAX] = "/usr/share/notcurses"; // FIXME
@ -46,11 +50,12 @@ struct timespec demodelay = {
static void static void
usage(const char* exe, int status){ usage(const char* exe, int status){
FILE* out = status == EXIT_SUCCESS ? stdout : stderr; FILE* out = status == EXIT_SUCCESS ? stdout : stderr;
fprintf(out, "usage: %s [ -h ] [ -k ] [ -d mult ] [ -f renderfile ] demospec\n", exe); fprintf(out, "usage: %s [ -h ] [ -k ] [ -d mult ] [ -c ] [ -f renderfile ] demospec\n", exe);
fprintf(out, " -h: this message\n"); fprintf(out, " -h: this message\n");
fprintf(out, " -k: keep screen; do not switch to alternate\n"); fprintf(out, " -k: keep screen; do not switch to alternate\n");
fprintf(out, " -d: delay multiplier (float)\n"); fprintf(out, " -d: delay multiplier (float)\n");
fprintf(out, " -f: render to file in addition to stdout\n"); fprintf(out, " -f: render to file in addition to stdout\n");
fprintf(out, " -c: constant PRNG seed, useful for benchmarking\n");
fprintf(out, "all demos are run if no specification is provided\n"); fprintf(out, "all demos are run if no specification is provided\n");
fprintf(out, " b: run box\n"); fprintf(out, " b: run box\n");
fprintf(out, " e: run eagles\n"); fprintf(out, " e: run eagles\n");
@ -63,7 +68,7 @@ usage(const char* exe, int status){
fprintf(out, " s: run shuffle\n"); fprintf(out, " s: run shuffle\n");
fprintf(out, " u: run uniblock\n"); fprintf(out, " u: run uniblock\n");
fprintf(out, " v: run view\n"); fprintf(out, " v: run view\n");
fprintf(out, " w: run bleachworm\n"); fprintf(out, " w: run witherworm\n");
exit(status); exit(status);
} }
@ -183,7 +188,7 @@ ext_demos(struct notcurses* nc, const char* demos){
case 'l': ret = luigi_demo(nc); break; case 'l': ret = luigi_demo(nc); break;
case 'v': ret = view_demo(nc); break; case 'v': ret = view_demo(nc); break;
case 'e': ret = eagle_demo(nc); break; case 'e': ret = eagle_demo(nc); break;
case 'w': ret = bleachworm_demo(nc); break; case 'w': ret = witherworm_demo(nc); break;
case 'p': ret = panelreel_demo(nc); break; case 'p': ret = panelreel_demo(nc); break;
default: default:
fprintf(stderr, "Unknown demo specification: %c\n", *demos); fprintf(stderr, "Unknown demo specification: %c\n", *demos);
@ -208,13 +213,17 @@ ext_demos(struct notcurses* nc, const char* demos){
// if it's NULL, there were valid options, but no spec. // if it's NULL, there were valid options, but no spec.
static const char* static const char*
handle_opts(int argc, char** argv, notcurses_options* opts){ handle_opts(int argc, char** argv, notcurses_options* opts){
bool constant_seed = false;
int c; int c;
memset(opts, 0, sizeof(*opts)); memset(opts, 0, sizeof(*opts));
while((c = getopt(argc, argv, "hkd:f:p:")) != EOF){ while((c = getopt(argc, argv, "hckd:f:p:")) != EOF){
switch(c){ switch(c){
case 'h': case 'h':
usage(*argv, EXIT_SUCCESS); usage(*argv, EXIT_SUCCESS);
break; break;
case 'c':
constant_seed = true;
break;
case 'k': case 'k':
opts->inhibit_alternate_screen = true; opts->inhibit_alternate_screen = true;
break; break;
@ -244,6 +253,9 @@ handle_opts(int argc, char** argv, notcurses_options* opts){
usage(*argv, EXIT_FAILURE); usage(*argv, EXIT_FAILURE);
} }
} }
if(!constant_seed){
srand(time(NULL)); // a classic blunder lol
}
const char* demos = argv[optind]; const char* demos = argv[optind];
return demos; return demos;
} }
@ -252,7 +264,6 @@ handle_opts(int argc, char** argv, notcurses_options* opts){
int main(int argc, char** argv){ int main(int argc, char** argv){
struct notcurses* nc; struct notcurses* nc;
notcurses_options nopts; notcurses_options nopts;
struct ncplane* ncp;
if(!setlocale(LC_ALL, "")){ if(!setlocale(LC_ALL, "")){
fprintf(stderr, "Couldn't set locale based on user preferences\n"); fprintf(stderr, "Couldn't set locale based on user preferences\n");
return EXIT_FAILURE; return EXIT_FAILURE;
@ -267,8 +278,9 @@ int main(int argc, char** argv){
if((nc = notcurses_init(&nopts, stdout)) == NULL){ if((nc = notcurses_init(&nopts, stdout)) == NULL){
return EXIT_FAILURE; return EXIT_FAILURE;
} }
if((ncp = notcurses_stdplane(nc)) == NULL){ int dimx, dimy;
fprintf(stderr, "Couldn't get standard plane\n"); notcurses_term_dim_yx(nc, &dimy, &dimx);
if(dimy < MIN_SUPPORTED_ROWS || dimx < MIN_SUPPORTED_COLS){
goto err; goto err;
} }
// no one cares about the leaderscreen. 1s max. // no one cares about the leaderscreen. 1s max.
@ -306,6 +318,10 @@ int main(int argc, char** argv){
return EXIT_SUCCESS; return EXIT_SUCCESS;
err: err:
notcurses_term_dim_yx(nc, &dimy, &dimx);
notcurses_stop(nc); notcurses_stop(nc);
if(dimy < MIN_SUPPORTED_ROWS || dimx < MIN_SUPPORTED_COLS){
fprintf(stderr, "At least an 80x25 terminal is required (current: %dx%d)\n", dimx, dimy);
}
return EXIT_FAILURE; return EXIT_FAILURE;
} }

@ -16,7 +16,7 @@ extern struct timespec demodelay;
char* find_data(const char* datum); char* find_data(const char* datum);
int unicodeblocks_demo(struct notcurses* nc); int unicodeblocks_demo(struct notcurses* nc);
int bleachworm_demo(struct notcurses* nc); int witherworm_demo(struct notcurses* nc);
int box_demo(struct notcurses* nc); int box_demo(struct notcurses* nc);
int maxcolor_demo(struct notcurses* nc); int maxcolor_demo(struct notcurses* nc);
int grid_demo(struct notcurses* nc); int grid_demo(struct notcurses* nc);

@ -160,81 +160,112 @@ lightup_surrounding_cells(struct ncplane* n, const cell* cells, int y, int x){
return 0; return 0;
} }
typedef struct snake {
cell lightup[13];
int x, y;
uint64_t channels;
int prevx, prevy;
} snake;
static void
init_snake(snake* s, int dimy, int dimx){
for(size_t i = 0 ; i < sizeof(s->lightup) / sizeof(*s->lightup) ; ++i){
cell_init(&s->lightup[i]);
}
// start it in the lower center of the screen
s->x = (random() % (dimx / 2)) + (dimx / 4);
s->y = (random() % (dimy * 2 / 3)) + (dimy / 3);
s->channels = 0;
channels_set_fg_rgb(&s->channels, 255, 255, 255);
channels_set_bg_rgb(&s->channels, 20, 20, 20);
s->prevx = 0;
s->prevy = 0;
}
static int
snakey_top(struct notcurses* nc, snake* s){
struct ncplane* n = notcurses_stdplane(nc);
get_surrounding_cells(n, s->lightup, s->y, s->x);
ncplane_cursor_move_yx(n, s->y, s->x);
if(lightup_surrounding_cells(n, s->lightup, s->y, s->x)){
return -1;
}
return 0;
}
static int
snakey(struct notcurses* nc, snake* s, int dimy, int dimx, const struct timespec* iterdelay){
struct ncplane* n = notcurses_stdplane(nc);
int oldy, oldx;
clock_nanosleep(CLOCK_MONOTONIC, 0, iterdelay, NULL);
cell c = CELL_TRIVIAL_INITIALIZER;
do{ // force a move
oldy = s->y;
oldx = s->x;
// FIXME he ought be weighted to avoid light; he's a snake after all
int direction = random() % 4;
switch(direction){
case 0: --s->y; break;
case 1: ++s->x; break;
case 2: ++s->y; break;
case 3: --s->x; break;
}
// keep him away from the sides due to width irregularities
if(s->x < (dimx / 4)){
s->x = dimx / 4;
}else if(s->x >= dimx * 3 / 4){
s->x = dimx * 3 / 4;
}
if(s->y < 0){
s->y = 0;
}else if(s->y >= dimy){
s->y = dimy - 1;
}
ncplane_cursor_move_yx(n, s->y, s->x);
ncplane_at_cursor(n, &c);
// don't allow the snake into the summary zone (test for walls)
if(wall_p(n, &c)){
s->x = oldx;
s->y = oldy;
}
}while((oldx == s->x && oldy == s->y) || (s->x == s->prevx && s->y == s->prevy));
s->prevy = oldy;
s->prevx = oldx;
cell_release(n, &c);
return 0;
}
// each snake wanders around aimlessly, prohibited from entering the summary // each snake wanders around aimlessly, prohibited from entering the summary
// section. it ought light up the cells around it; to do this, we keep an array // section. it ought light up the cells around it; to do this, we keep an array
// of 13 cells with the original colors, which we tune up for the duration of // of 13 cells with the original colors, which we tune up for the duration of
// our colocality (unless they're summary area walls). // our colocality (unless they're summary area walls).
static void * static void *
snake_thread(void* vnc){ snake_thread(void* vnc){
const int snakecount = 3; // FIXME base count off area
struct notcurses* nc = vnc; struct notcurses* nc = vnc;
struct ncplane* n = notcurses_stdplane(nc); struct ncplane* n = notcurses_stdplane(nc);
cell lightup[13];
size_t i;
for(i = 0 ; i < sizeof(lightup) / sizeof(*lightup) ; ++i){
cell_init(&lightup[i]);
}
int dimy, dimx; int dimy, dimx;
ncplane_dim_yx(n, &dimy, &dimx); ncplane_dim_yx(n, &dimy, &dimx);
int x, y; snake snakes[snakecount];
// start it in the lower center of the screen for(int s = 0 ; s < snakecount ; ++s){
x = (random() % (dimx / 2)) + (dimx / 4); init_snake(&snakes[s], dimy, dimx);
y = (random() % (dimy / 2)) + (dimy / 2); }
cell head = CELL_TRIVIAL_INITIALIZER;
uint64_t channels = 0;
channels_set_fg_rgb(&channels, 255, 255, 255);
channels_set_bg_rgb(&channels, 20, 20, 20);
cell_prime(n, &head, "א", 0, channels);
cell c = CELL_TRIVIAL_INITIALIZER;
struct timespec iterdelay = { .tv_sec = 0, .tv_nsec = 1000000000ul / 20, }; struct timespec iterdelay = { .tv_sec = 0, .tv_nsec = 1000000000ul / 20, };
int prevx = 0, prevy = 0;
while(true){ while(true){
pthread_testcancel(); pthread_testcancel();
get_surrounding_cells(n, lightup, y, x); for(int s = 0 ; s < snakecount ; ++s){
ncplane_cursor_move_yx(n, y, x); if(snakey_top(nc, &snakes[s])){
ncplane_at_cursor(n, &c); return NULL;
if(lightup_surrounding_cells(n, lightup, y, x)){ }
}
if(notcurses_render(nc)){
return NULL; return NULL;
} }
notcurses_render(nc); for(int s = 0 ; s < snakecount ; ++s){
int oldy, oldx; if(snakey(nc, &snakes[s], dimy, dimx, &iterdelay)){
clock_nanosleep(CLOCK_MONOTONIC, 0, &iterdelay, NULL); return NULL;
do{ // force a move
oldy = y;
oldx = x;
// FIXME he ought be weighted to avoid light; he's a snake after all
int direction = random() % 4;
switch(direction){
case 0: --y; break;
case 1: ++x; break;
case 2: ++y; break;
case 3: --x; break;
} }
// keep him away from the sides due to width irregularities }
if(x < (dimx / 4)){
x = dimx / 4;
}else if(x >= dimx * 3 / 4){
x = dimx * 3 / 4;
}
if(y < 0){
y = 0;
}else if(y >= dimy){
y = dimy - 1;
}
ncplane_cursor_move_yx(n, y, x);
ncplane_at_cursor(n, &c);
// don't allow the snake into the summary zone (test for walls)
if(wall_p(n, &c)){
x = oldx;
y = oldy;
}
}while((oldx == x && oldy == y) || (x == prevx && y == prevy));
prevy = oldy;
prevx = oldx;
}
cell_release(n, &head); // FIXME won't be released when cancelled
cell_release(n, &c); // FIXME won't be released when cancelled
for(i = 0 ; i < sizeof(lightup) / sizeof(*lightup) ; ++i){
cell_release(n, &lightup[i]);
} }
return NULL; return NULL;
} }
@ -306,7 +337,7 @@ message(struct ncplane* n, int maxy, int maxx, int num, int total,
} }
// Much of this text comes from http://kermitproject.org/utf8.html // Much of this text comes from http://kermitproject.org/utf8.html
int bleachworm_demo(struct notcurses* nc){ int witherworm_demo(struct notcurses* nc){
static const char* strs[] = { static const char* strs[] = {
"Война и мир", "Война и мир",
"Бра́тья Карама́зовы", "Бра́тья Карама́зовы",
@ -537,8 +568,8 @@ int bleachworm_demo(struct notcurses* nc){
NULL NULL
}; };
const char** s; const char** s;
const int steps[] = { 0x100, 0x100, 0x40000, 0x10001, }; const int steps[] = { 0x10040, 0x100, 0x100, 0x10001, };
const int starts[] = { 0x004000, 0x000040, 0x010101, 0x400040, }; const int starts[] = { 0x10101, 0x004000, 0x000040, 0x400040, };
struct ncplane* n = notcurses_stdplane(nc); struct ncplane* n = notcurses_stdplane(nc);
size_t i; size_t i;

@ -230,6 +230,12 @@ egcpool_dump(egcpool* pool){
pool->poolused = 0; pool->poolused = 0;
} }
static inline const char*
egcpool_extended_gcluster(const egcpool* pool, const cell* c){
uint32_t idx = cell_egc_idx(c);
return pool->pool + idx;
}
#ifdef __cplusplus #ifdef __cplusplus
} }
#endif #endif

@ -134,7 +134,6 @@ int ncplane_fadein(ncplane* n, const struct timespec* ts){
} }
} }
} }
ncplane_updamage(n);
notcurses_render(n->nc); notcurses_render(n->nc);
uint64_t nextwake = (iter + 1) * nanosecs_step + startns; uint64_t nextwake = (iter + 1) * nanosecs_step + startns;
struct timespec sleepspec; struct timespec sleepspec;
@ -204,7 +203,6 @@ int ncplane_fadeout(ncplane* n, const struct timespec* ts){
} }
} }
} }
ncplane_updamage(n);
cell* c = &n->defcell; cell* c = &n->defcell;
if(!cell_fg_default_p(c)){ if(!cell_fg_default_p(c)){
channels_get_fg_rgb(pp.channels[pp.cols * y], &r, &g, &b); channels_get_fg_rgb(pp.channels[pp.cols * y], &r, &g, &b);

@ -51,7 +51,6 @@ typedef struct ncplane {
uint32_t attrword; // same deal as in a cell uint32_t attrword; // same deal as in a cell
void* userptr; // slot for the user to stick some opaque pointer void* userptr; // slot for the user to stick some opaque pointer
cell defcell; // cell written anywhere that fb[i].gcluster == 0 cell defcell; // cell written anywhere that fb[i].gcluster == 0
unsigned char* damage;// damage map, one per row
struct notcurses* nc; // notcurses object of which we are a part struct notcurses* nc; // notcurses object of which we are a part
} ncplane; } ncplane;
@ -80,20 +79,32 @@ typedef struct ncvisual {
typedef struct notcurses { typedef struct notcurses {
pthread_mutex_t lock; pthread_mutex_t lock;
int ttyfd; // file descriptor for controlling tty, from opts->ttyfp ncplane* top; // the contents of our topmost plane (initially entire screen)
FILE* ttyfp; // FILE* for controlling tty, from opts->ttyfp ncplane* stdscr;// aliases some plane from the z-buffer, covers screen
FILE* ttyinfp; // FILE* for processing input
unsigned char* damage; // damage map (row granularity) // 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
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
// 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) char* mstream; // buffer for rendering memstream, see open_memstream(3)
FILE* mstreamfp;// FILE* for rendering memstream FILE* mstreamfp;// FILE* for rendering memstream
size_t mstrsize;// size of rendering memstream size_t mstrsize;// size of rendering memstream
int colors; // number of colors usable for this screen
ncstats stats; // some statistics across the lifetime of the notcurses ctx ncstats stats; // some statistics across the lifetime of the notcurses ctx
ncstats stashstats; // cumulative stats, unaffected by notcurses_reset_stats() ncstats stashstats; // cumulative stats, unaffected by notcurses_reset_stats()
// We verify that some terminfo capabilities exist. These needn't be checked // We verify that some terminfo capabilities exist. These needn't be checked
// before further use; just use tiparm() directly. // before further use; just use tiparm() directly.
int colors; // number of colors terminfo reported usable for this screen
char* cup; // move cursor char* cup; // move cursor
bool RGBflag; // terminfo-reported "RGB" flag for 24bpc directcolor char* cuf; // move n cells right
char* cuf1; // move 1 cell right
char* civis; // hide cursor char* civis; // hide cursor
// These might be NULL, and we can more or less work without them. Check! // These might be NULL, and we can more or less work without them. Check!
char* clearscr; // erase screen and home cursor char* clearscr; // erase screen and home cursor
@ -117,12 +128,15 @@ typedef struct notcurses {
char* italoff; // CELL_STYLE_ITALIC (disable) char* italoff; // CELL_STYLE_ITALIC (disable)
char* smkx; // enter keypad transmit mode (keypad_xmit) char* smkx; // enter keypad transmit mode (keypad_xmit)
char* rmkx; // leave keypad transmit mode (keypad_local) char* rmkx; // leave keypad transmit mode (keypad_local)
struct termios tpreserved; // terminal state upon entry bool RGBflag; // terminfo-reported "RGB" flag for 24bpc directcolor
bool suppress_banner; // from notcurses_options
bool CCCflag; // terminfo-reported "CCC" flag for palette set capability bool CCCflag; // terminfo-reported "CCC" flag for palette set capability
ncplane* top; // the contents of our topmost plane (initially entire screen)
ncplane* stdscr;// aliases some plane from the z-buffer, covers screen int ttyfd; // file descriptor for controlling tty, from opts->ttyfp
FILE* ttyfp; // FILE* for controlling tty, from opts->ttyfp
FILE* ttyinfp; // FILE* for processing input
FILE* renderfp; // debugging FILE* to which renderings are written FILE* renderfp; // debugging FILE* to which renderings are written
struct termios tpreserved; // terminal state upon entry
bool suppress_banner; // from notcurses_options
unsigned char inputbuf[BUFSIZ]; unsigned char inputbuf[BUFSIZ];
// we keep a wee ringbuffer of input queued up for delivery. if // we keep a wee ringbuffer of input queued up for delivery. if
// inputbuf_occupied == sizeof(inputbuf), there is no room. otherwise, data // inputbuf_occupied == sizeof(inputbuf), there is no room. otherwise, data
@ -161,19 +175,6 @@ fbcellidx(const ncplane* n, int row, int col){
return row * n->lenx + col; return row * n->lenx + col;
} }
// set all elements of a damage map true or false
static inline void
flash_damage_map(unsigned char* damage, int count, bool val){
if(val){
memset(damage, 0xff, sizeof(*damage) * count);
}else{
memset(damage, 0, sizeof(*damage) * count);
}
}
// mark all lines of the notcurses object touched by this plane as damaged
void ncplane_updamage(ncplane* n);
// For our first attempt, O(1) uniform conversion from 8-bit r/g/b down to // For our first attempt, O(1) uniform conversion from 8-bit r/g/b down to
// ~2.4-bit 6x6x6 cube + greyscale (assumed on entry; I know no way to // ~2.4-bit 6x6x6 cube + greyscale (assumed on entry; I know no way to
// even semi-portably recover the palette) proceeds via: map each 8-bit to // even semi-portably recover the palette) proceeds via: map each 8-bit to
@ -223,8 +224,7 @@ term_emit(const char* name __attribute__ ((unused)), const char* seq,
static inline const char* static inline const char*
extended_gcluster(const ncplane* n, const cell* c){ extended_gcluster(const ncplane* n, const cell* c){
uint32_t idx = cell_egc_idx(c); return egcpool_extended_gcluster(&n->pool, c);
return n->pool.pool + idx;
} }
#define NANOSECS_IN_SEC 1000000000 #define NANOSECS_IN_SEC 1000000000

@ -354,7 +354,6 @@ int ncvisual_render(const ncvisual* ncv, int begy, int begx, int leny, int lenx)
cell_release(ncv->ncp, &c); cell_release(ncv->ncp, &c);
} }
} }
flash_damage_map(ncv->ncp->damage + ncv->placey, y - ncv->placey, true);
return 0; return 0;
} }

@ -262,9 +262,7 @@ term_verify_seq(char** gseq, const char* name){
static void static void
free_plane(ncplane* p){ free_plane(ncplane* p){
if(p){ if(p){
ncplane_updamage(p);
egcpool_dump(&p->pool); egcpool_dump(&p->pool);
free(p->damage);
free(p->fb); free(p->fb);
free(p); free(p);
} }
@ -287,13 +285,6 @@ ncplane_create(notcurses* nc, int rows, int cols, int yoff, int xoff){
return NULL; return NULL;
} }
memset(p->fb, 0, fbsize); memset(p->fb, 0, fbsize);
p->damage = malloc(sizeof(*p->damage) * rows);
if(p->damage == NULL){
free(p->fb);
free(p);
return NULL;
}
flash_damage_map(p->damage, rows, false);
p->userptr = NULL; p->userptr = NULL;
p->leny = rows; p->leny = rows;
p->lenx = cols; p->lenx = cols;
@ -341,8 +332,7 @@ ncplane* notcurses_newplane(notcurses* nc, int rows, int cols,
return n; return n;
} }
// can be used on stdscr, unlike ncplane_resize() which prohibits it. sets all // can be used on stdscr, unlike ncplane_resize() which prohibits it.
// members of the plane's damage map to damaged.
static int static int
ncplane_resize_internal(ncplane* n, int keepy, int keepx, int keepleny, ncplane_resize_internal(ncplane* n, int keepy, int keepx, int keepleny,
int keeplenx, int yoff, int xoff, int ylen, int xlen){ int keeplenx, int yoff, int xoff, int ylen, int xlen){
@ -377,19 +367,6 @@ ncplane_resize_internal(ncplane* n, int keepy, int keepx, int keepleny,
if(fb == NULL){ if(fb == NULL){
return -1; return -1;
} }
// if we're the standard plane, the updamage can be charged at the end. it's
// unsafe to call now anyway, because if we shrank, the notcurses damage map
// has already been shrunk down
if(n != n->nc->stdscr){
ncplane_updamage(n); // damage any lines we were on
}
unsigned char* tmpdamage;
if((tmpdamage = realloc(n->damage, sizeof(*n->damage) * ylen)) == NULL){
free(fb);
return -1;
}
n->damage = tmpdamage;
flash_damage_map(n->damage, ylen, true);
// update the cursor, if it would otherwise be off-plane // update the cursor, if it would otherwise be off-plane
if(n->y >= ylen){ if(n->y >= ylen){
n->y = ylen - 1; n->y = ylen - 1;
@ -411,7 +388,6 @@ ncplane_resize_internal(ncplane* n, int keepy, int keepx, int keepleny,
n->lenx = xlen; n->lenx = xlen;
n->leny = ylen; n->leny = ylen;
free(preserved); free(preserved);
ncplane_updamage(n); // damage any lines we're now on
return 0; return 0;
} }
// we currently have maxy rows of maxx cells each. we will be keeping rows // we currently have maxy rows of maxx cells each. we will be keeping rows
@ -448,7 +424,6 @@ ncplane_resize_internal(ncplane* n, int keepy, int keepx, int keepleny,
n->lenx = xlen; n->lenx = xlen;
n->leny = ylen; n->leny = ylen;
free(preserved); free(preserved);
ncplane_updamage(n); // damage any lines we're now on
return 0; return 0;
} }
@ -489,19 +464,6 @@ int notcurses_resize(notcurses* n, int* rows, int* cols){
if(keepx > oldcols){ if(keepx > oldcols){
keepx = oldcols; keepx = oldcols;
} }
unsigned char* tmpdamage;
if((tmpdamage = malloc(sizeof(*n->damage) * *rows)) == NULL){
return -1;
}
if(oldcols < *cols){ // all are busted if rows got bigger
free(n->damage);
flash_damage_map(tmpdamage, *rows, true);
}else if(oldrows <= *rows){ // new rows are pre-busted, old are straight
memcpy(tmpdamage, n->damage, oldrows * sizeof(*tmpdamage));
flash_damage_map(tmpdamage + oldrows, *rows - oldrows, true);
free(n->damage);
}
n->damage = tmpdamage;
if(ncplane_resize_internal(n->stdscr, 0, 0, keepy, keepx, 0, 0, *rows, *cols)){ if(ncplane_resize_internal(n->stdscr, 0, 0, keepy, keepx, 0, 0, *rows, *cols)){
return -1; return -1;
} }
@ -593,6 +555,8 @@ interrogate_terminfo(notcurses* nc, const notcurses_options* opts){
term_verify_seq(&nc->clearscr, "clear"); term_verify_seq(&nc->clearscr, "clear");
term_verify_seq(&nc->cleareol, "el"); term_verify_seq(&nc->cleareol, "el");
term_verify_seq(&nc->clearbol, "el1"); term_verify_seq(&nc->clearbol, "el1");
term_verify_seq(&nc->cuf, "cuf"); // n non-destructive spaces
term_verify_seq(&nc->cuf1, "cuf1"); // non-destructive space
if(prep_special_keys(nc)){ if(prep_special_keys(nc)){
return -1; return -1;
} }
@ -725,6 +689,10 @@ notcurses* notcurses_init(const notcurses_options* opts, FILE* outfp){
ret->ttyinfp = stdin; // FIXME ret->ttyinfp = stdin; // FIXME
ret->mstream = NULL; ret->mstream = NULL;
ret->mstrsize = 0; ret->mstrsize = 0;
ret->lastframe = NULL;
ret->lfdimy = 0;
ret->lfdimx = 0;
egcpool_init(&ret->pool);
if(make_nonblocking(ret->ttyinfp)){ if(make_nonblocking(ret->ttyinfp)){
free(ret); free(ret);
return NULL; return NULL;
@ -783,16 +751,10 @@ notcurses* notcurses_init(const notcurses_options* opts, FILE* outfp){
free_plane(ret->top); free_plane(ret->top);
goto err; goto err;
} }
if((ret->damage = malloc(sizeof(*ret->damage) * ret->stdscr->leny)) == NULL){
free_plane(ret->top);
goto err;
}
if((ret->mstreamfp = open_memstream(&ret->mstream, &ret->mstrsize)) == NULL){ if((ret->mstreamfp = open_memstream(&ret->mstream, &ret->mstrsize)) == NULL){
free(ret->damage);
free_plane(ret->top); free_plane(ret->top);
goto err; goto err;
} }
flash_damage_map(ret->damage, ret->stdscr->leny, false);
// term_emit("clear", ret->clear, ret->ttyfp, false); // term_emit("clear", ret->clear, ret->ttyfp, false);
ret->suppress_banner = opts->suppress_bannner; ret->suppress_banner = opts->suppress_bannner;
if(!opts->suppress_bannner){ if(!opts->suppress_bannner){
@ -856,10 +818,11 @@ int notcurses_stop(notcurses* nc){
nc->top = p->z; nc->top = p->z;
free_plane(p); free_plane(p);
} }
free(nc->damage);
if(nc->mstreamfp){ if(nc->mstreamfp){
fclose(nc->mstreamfp); fclose(nc->mstreamfp);
} }
egcpool_dump(&nc->pool);
free(nc->lastframe);
free(nc->mstream); free(nc->mstream);
input_free_esctrie(&nc->inputescapes); input_free_esctrie(&nc->inputescapes);
ret |= pthread_mutex_destroy(&nc->lock); ret |= pthread_mutex_destroy(&nc->lock);
@ -955,7 +918,6 @@ int ncplane_set_default(ncplane* ncp, const cell* c){
if(ret < 0){ if(ret < 0){
return -1; return -1;
} }
ncplane_updamage(ncp);
return ret; return ret;
} }
@ -991,7 +953,6 @@ int ncplane_move_above_unsafe(ncplane* restrict n, ncplane* restrict above){
*an = n->z; // splice n out *an = n->z; // splice n out
n->z = above; // attach above below n n->z = above; // attach above below n
*aa = n; // spline n in above *aa = n; // spline n in above
ncplane_updamage(n); // conservative (we might not actually be visible)
return 0; return 0;
} }
@ -1004,7 +965,6 @@ int ncplane_move_below_unsafe(ncplane* restrict n, ncplane* restrict below){
*an = n->z; // splice n out *an = n->z; // splice n out
n->z = below->z; // reattach subbelow list to n n->z = below->z; // reattach subbelow list to n
below->z = n; // splice n in below below->z = n; // splice n in below
ncplane_updamage(n); // conservative (we might not actually be visible)
return 0; return 0;
} }
@ -1016,7 +976,6 @@ int ncplane_move_top(ncplane* n){
*an = n->z; // splice n out *an = n->z; // splice n out
n->z = n->nc->top; n->z = n->nc->top;
n->nc->top = n; n->nc->top = n;
ncplane_updamage(n);
return 0; return 0;
} }
@ -1032,7 +991,6 @@ int ncplane_move_bottom(ncplane* n){
} }
*an = n; *an = n;
n->z = NULL; n->z = NULL;
ncplane_updamage(n);
return 0; return 0;
} }
@ -1062,24 +1020,6 @@ ncplane_cursor_stuck(const ncplane* n){
return (n->x == n->lenx && n->y == n->leny); return (n->x == n->lenx && n->y == n->leny);
} }
int cell_duplicate(ncplane* n, cell* targ, const cell* c){
cell_release(n, targ);
targ->attrword = c->attrword;
targ->channels = c->channels;
if(cell_simple_p(c)){
targ->gcluster = c->gcluster;
return !!c->gcluster;
}
size_t ulen = strlen(extended_gcluster(n, c));
// fprintf(stderr, "[%s] (%zu)\n", extended_gcluster(n, c), strlen(extended_gcluster(n, c)));
int eoffset = egcpool_stash(&n->pool, extended_gcluster(n, c), ulen);
if(eoffset < 0){
return -1;
}
targ->gcluster = eoffset + 0x80;
return ulen;
}
static inline void static inline void
cell_set_wide(cell* c){ cell_set_wide(cell* c){
c->channels |= CELL_WIDEASIAN_MASK; c->channels |= CELL_WIDEASIAN_MASK;
@ -1129,7 +1069,6 @@ int ncplane_putc(ncplane* n, const cell* c){
cell_release(n, candidate); cell_release(n, candidate);
} }
} }
n->damage[n->y] = true;
advance_cursor(n, cols); advance_cursor(n, cols);
ncplane_unlock(n); ncplane_unlock(n);
return cols; return cols;
@ -1174,13 +1113,6 @@ int ncplane_cursor_at(const ncplane* n, cell* c, char** gclust){
return 0; return 0;
} }
void cell_release(ncplane* n, cell* c){
if(!cell_simple_p(c)){
egcpool_release(&n->pool, cell_egc_idx(c));
c->gcluster = 0; // don't subject ourselves to double-release problems
}
}
int cell_load(ncplane* n, cell* c, const char* gcluster){ int cell_load(ncplane* n, cell* c, const char* gcluster){
cell_release(n, c); cell_release(n, c);
int bytes; int bytes;
@ -1536,33 +1468,12 @@ int ncplane_box(ncplane* n, const cell* ul, const cell* ur,
return 0; return 0;
} }
// mark all lines of the notcurses object touched by this plane as damaged
void ncplane_updamage(ncplane* n){
int drangelow = n->absy;
int drangehigh = n->absy + n->leny;
if(drangehigh > n->nc->stdscr->leny){
drangehigh = n->nc->stdscr->leny;
}
if(drangelow < n->nc->stdscr->absy){
drangelow = n->nc->stdscr->absy;
}
if(drangelow > n->nc->stdscr->absy + n->nc->stdscr->leny - 1){
drangelow = n->nc->stdscr->absy + n->nc->stdscr->leny - 1;
}
flash_damage_map(n->nc->damage + drangelow, drangehigh - drangelow, true);
}
int ncplane_move_yx(ncplane* n, int y, int x){ int ncplane_move_yx(ncplane* n, int y, int x){
if(n == n->nc->stdscr){ if(n == n->nc->stdscr){
return -1; return -1;
} }
ncplane_updamage(n); // damage any lines we are currently on
bool movedy = n->absy != y;
n->absy = y; n->absy = y;
n->absx = x; n->absx = x;
if(movedy){
ncplane_updamage(n);
}
return 0; return 0;
} }
@ -1602,7 +1513,6 @@ void ncplane_erase(ncplane* n){
egcpool_init(&n->pool); egcpool_init(&n->pool);
cell_load(n, &n->defcell, egc); cell_load(n, &n->defcell, egc);
free(egc); free(egc);
ncplane_updamage(n);
ncplane_unlock(n); ncplane_unlock(n);
} }

@ -1,4 +1,5 @@
#include <ctype.h> #include <ctype.h>
#include <limits.h>
#include <unistd.h> #include <unistd.h>
#include <sys/poll.h> #include <sys/poll.h>
#include "internal.h" #include "internal.h"
@ -98,24 +99,33 @@ prep_optimized_palette(notcurses* nc, FILE* out __attribute__ ((unused))){
return 0; return 0;
} }
/*
// reshape the shadow framebuffer to match the stdplane's dimensions, throwing // reshape the shadow framebuffer to match the stdplane's dimensions, throwing
// away the old one. // away the old one.
static int static int
reshape_shadow_fb(notcurses* nc){ reshape_shadow_fb(notcurses* nc){
const size_t size = sizeof(nc->shadowbuf) * nc->stdscr->leny * nc->stdscr->lenx; if(nc->lfdimx == nc->stdscr->lenx && nc->lfdimy == nc->stdscr->leny){
cell* fb = malloc(size); return 0; // no change
}
const size_t size = sizeof(*nc->lastframe) * nc->stdscr->leny * nc->stdscr->lenx;
cell* fb = realloc(nc->lastframe, size);
if(fb == NULL){ if(fb == NULL){
free(nc->lastframe);
nc->lastframe = NULL;
nc->lfdimx = 0;
nc->lfdimy = 0;
return -1; return -1;
} }
free(nc->shadowbuf); nc->lastframe = fb;
nc->shadowbuf = fb; // FIXME more memset()tery than we need, both wasting work and wrecking
nc->shadowy = nc->stdscr->leny; // damage detection for the upcoming render
nc->shadowx = nc->stdscr->lenx; memset(nc->lastframe, 0, size);
nc->lastframe = fb;
nc->lfdimy = nc->stdscr->leny;
nc->lfdimx = nc->stdscr->lenx;
memset(fb, 0, size); memset(fb, 0, size);
egcpool_dump(&nc->pool);
return 0; return 0;
} }
*/
// Find the topmost cell for this coordinate by walking down the z-buffer, // Find the topmost cell for this coordinate by walking down the z-buffer,
// looking for an intersecting ncplane. Once we've found one, check it for // looking for an intersecting ncplane. Once we've found one, check it for
@ -135,9 +145,8 @@ reshape_shadow_fb(notcurses* nc){
// whichever one occurs at the top with a non-transparent α (α < 3). To effect // whichever one occurs at the top with a non-transparent α (α < 3). To effect
// tail recursion, though, we instead write first, and then recurse, blending // tail recursion, though, we instead write first, and then recurse, blending
// as we descend. α <= 0 is opaque. α >= 3 is fully transparent. // as we descend. α <= 0 is opaque. α >= 3 is fully transparent.
static ncplane* static inline ncplane*
dig_visible_cell(cell* c, int y, int x, ncplane* p, int falpha, int balpha, dig_visible_cell(cell* c, int y, int x, ncplane* p, int falpha, int balpha){
bool* damage){
// once we decide on our glyph, it cannot be changed by anything below, so // once we decide on our glyph, it cannot be changed by anything below, so
// lock in this plane for the actual cell return. // lock in this plane for the actual cell return.
ncplane* glyphplane = NULL; ncplane* glyphplane = NULL;
@ -164,20 +173,12 @@ dig_visible_cell(cell* c, int y, int x, ncplane* p, int falpha, int balpha,
c->attrword = vis->attrword; c->attrword = vis->attrword;
cell_set_fchannel(c, cell_get_fchannel(vis)); // FIXME blend it in cell_set_fchannel(c, cell_get_fchannel(vis)); // FIXME blend it in
falpha -= (CELL_ALPHA_TRANSPARENT - nalpha); // FIXME blend it in falpha -= (CELL_ALPHA_TRANSPARENT - nalpha); // FIXME blend it in
if(p->damage[poffy]){
*damage = true;
p->damage[poffy] = false;
}
} }
} }
} }
if(balpha > 0 && (nalpha = cell_get_bg_alpha(vis)) < CELL_ALPHA_TRANSPARENT){ if(balpha > 0 && (nalpha = cell_get_bg_alpha(vis)) < CELL_ALPHA_TRANSPARENT){
cell_set_bchannel(c, cell_get_bchannel(vis)); // FIXME blend it in cell_set_bchannel(c, cell_get_bchannel(vis)); // FIXME blend it in
balpha -= (CELL_ALPHA_TRANSPARENT - nalpha); balpha -= (CELL_ALPHA_TRANSPARENT - nalpha);
if(p->damage[poffy]){
*damage = true;
p->damage[poffy] = false;
}
} }
if((falpha <= 0 && balpha <= 0) || !p->z){ // done! if((falpha <= 0 && balpha <= 0) || !p->z){ // done!
return glyphplane ? glyphplane : p; return glyphplane ? glyphplane : p;
@ -191,10 +192,9 @@ dig_visible_cell(cell* c, int y, int x, ncplane* p, int falpha, int balpha,
} }
static inline ncplane* static inline ncplane*
visible_cell(cell* c, int y, int x, ncplane* n, bool* damage){ visible_cell(cell* c, int y, int x, ncplane* n){
cell_init(c); cell_init(c);
return dig_visible_cell(c, y, x, n, CELL_ALPHA_TRANSPARENT, return dig_visible_cell(c, y, x, n, CELL_ALPHA_TRANSPARENT, CELL_ALPHA_TRANSPARENT);
CELL_ALPHA_TRANSPARENT, damage);
} }
// write the cell's UTF-8 grapheme cluster to the provided FILE*. returns the // write the cell's UTF-8 grapheme cluster to the provided FILE*. returns the
@ -385,6 +385,74 @@ term_fg_rgb8(notcurses* nc, FILE* out, unsigned r, unsigned g, unsigned b){
return 0; return 0;
} }
static inline void
pool_release(egcpool* pool, cell* c){
if(!cell_simple_p(c)){
egcpool_release(pool, cell_egc_idx(c));
c->gcluster = 0; // don't subject ourselves to double-release problems
}
}
void cell_release(ncplane* n, cell* c){
pool_release(&n->pool, c);
}
// Duplicate one cell onto another, possibly crossing ncplanes.
static inline int
cell_duplicate_far(egcpool* tpool, cell* targ, const ncplane* splane, const cell* c){
pool_release(tpool, targ);
targ->attrword = c->attrword;
targ->channels = c->channels;
if(cell_simple_p(c)){
targ->gcluster = c->gcluster;
return !!c->gcluster;
}
size_t ulen = strlen(extended_gcluster(splane, c));
// fprintf(stderr, "[%s] (%zu)\n", extended_gcluster(n, c), strlen(extended_gcluster(n, c)));
int eoffset = egcpool_stash(tpool, extended_gcluster(splane, c), ulen);
if(eoffset < 0){
return -1;
}
targ->gcluster = eoffset + 0x80;
return ulen;
}
// Duplicate one cell onto another when they share a plane. Convenience wrapper.
int cell_duplicate(ncplane* n, cell* targ, const cell* c){
return cell_duplicate_far(&n->pool, targ, n, c);
}
// the heart of damage detection. compare two cells (from two different planes)
// for equality. if they are equal, return 0. otherwise, dup the second onto
// the first and return non-zero.
static int
cellcmp_and_dupfar(egcpool* dampool, cell* damcell, const ncplane* srcplane,
const cell* srccell){
if(damcell->attrword == srccell->attrword){
if(damcell->channels == srccell->channels){
bool damsimple = cell_simple_p(damcell);
bool srcsimple = cell_simple_p(srccell);
if(damsimple == srcsimple){
if(damsimple){
if(damcell->gcluster == srccell->gcluster){
return 0; // simple match
}
}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
}
}
}
}
}
cell_duplicate_far(dampool, damcell, srcplane, srccell);
return 1;
}
static inline int static inline int
notcurses_render_internal(notcurses* nc){ notcurses_render_internal(notcurses* nc){
int ret = 0; int ret = 0;
@ -405,40 +473,55 @@ notcurses_render_internal(notcurses* nc){
// cells and the current cell uses no defaults, or if both the current and // cells and the current cell uses no defaults, or if both the current and
// the last used both defaults. // the last used both defaults.
bool fgelidable = false, bgelidable = false, defaultelidable = false; bool fgelidable = false, bgelidable = false, defaultelidable = false;
/*if(nc->stdscr->leny != nc->shadowy || nc->stdscr->lenx != nc->shadowx){ // if this fails, struggle bravely on. we can live without a lastframe.
reshape_shadow_fb(nc); reshape_shadow_fb(nc);
}*/
for(y = 0 ; y < nc->stdscr->leny ; ++y){ for(y = 0 ; y < nc->stdscr->leny ; ++y){
bool linedamaged = false; // have we repositioned the cursor to start line? // how many characters have we elided? it's not worthwhile to invoke a
bool newdamage = nc->damage[y]; // cursor movement with cup if we only elided one or two. set to INT_MAX
// fprintf(stderr, "nc->damage[%d] (%p) = %u\n", y, nc->damage + y, nc->damage[y]); // whenever we're on a new line.
if(newdamage){ int needmove = INT_MAX;
nc->damage[y] = 0;
}
// move to the beginning of the line, in case our accounting was befouled
// by wider- (or narrower-) than-reported characters
for(x = 0 ; x < nc->stdscr->lenx ; ++x){ for(x = 0 ; x < nc->stdscr->lenx ; ++x){
unsigned r, g, b, br, bg, bb; unsigned r, g, b, br, bg, bb;
ncplane* p; ncplane* p;
cell c; // no need to initialize cell c; // no need to initialize
p = visible_cell(&c, y, x, nc->top, &newdamage); p = visible_cell(&c, y, x, nc->top);
assert(p);
// don't try to print a wide character on the last column; it'll instead // don't try to print a wide character on the last column; it'll instead
// be printed on the next line. they probably shouldn't be admitted, but // be printed on the next line. they probably shouldn't be admitted, but
// we can end up with one due to a resize. // we can end up with one due to a resize.
// FIXME but...print what, exactly, instead?
if((x + 1 >= nc->stdscr->lenx && cell_double_wide_p(&c))){ if((x + 1 >= nc->stdscr->lenx && cell_double_wide_p(&c))){
continue; continue; // needmove will be reset as we restart the line
} }
if(!linedamaged){ // lastframe has already been sized to match the current size, so no need
if(newdamage){ // to check whether we're within its bounds. just check the cell.
term_emit("cup", tiparm(nc->cup, y, x), out, false); if(nc->lastframe){
nc->stats.cellelisions += x; cell* oldcell = &nc->lastframe[fbcellidx(nc->stdscr, y, x)];
nc->stats.cellemissions += (nc->stdscr->lenx - x); if(cellcmp_and_dupfar(&nc->pool, oldcell, p, &c) == 0){
linedamaged = true; // no need to emit a cell; what we rendered appears to already be
}else{ // here. no updates are performed to elision state nor lastframe.
++nc->stats.cellelisions;
if(needmove < INT_MAX){
++needmove;
}
if(cell_double_wide_p(&c)){
if(needmove < INT_MAX){
++needmove;
}
++nc->stats.cellelisions;
++x;
}
continue; continue;
} }
} }
++nc->stats.cellemissions;
if(needmove > 8){ // FIXME cuf and cuf1 aren't guaranteed!
term_emit("cup", tiparm(nc->cup, y, x), out, false);
}else if(needmove > 1){
term_emit("cuf", tiparm(nc->cuf, needmove), out, false);
}else if(needmove){
term_emit("cuf1", tiparm(nc->cuf1), out, false);
}
needmove = 0;
// set the style. this can change the color back to the default; if it // set the style. this can change the color back to the default; if it
// does, we need update our elision possibilities. // does, we need update our elision possibilities.
bool normalized; bool normalized;
@ -498,9 +581,6 @@ notcurses_render_internal(notcurses* nc){
++x; ++x;
} }
} }
if(linedamaged == false){
nc->stats.cellelisions += x;
}
} }
ret |= fflush(out); ret |= fflush(out);
fflush(nc->ttyfp); fflush(nc->ttyfp);

Loading…
Cancel
Save