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\-d \fIdelaymult \fR]
[ \fB\-k \fR]
[ \fB\-c \fR]
[ \fB\-h / \fB\-\-help \fR]
.IR demospec
.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
on your terminal after the program exits.
.TP
.BR \-c
Do not attempt to seed the PRNG. This is useful when benchmarking.
.TP
.BR \-h ", " \-\-help
Print a usage message, and exit with success.
.TP
@ -45,7 +49,7 @@ contains a set of text-based demonstrations of capabilities from the notcurses l
.P
(s)liders—a missing-piece puzzle made up of colorful blocks
.P
(b)leachworm—a great Nothing slowly robs the world of color
(w)hiteworm—a great Nothing slowly robs the world of color
.P
(v)iew—images and a video are rendered as text
.P

@ -10,6 +10,10 @@
#include <notcurses.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 char datadir[PATH_MAX] = "/usr/share/notcurses"; // FIXME
@ -46,11 +50,12 @@ struct timespec demodelay = {
static void
usage(const char* exe, int status){
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, " -k: keep screen; do not switch to alternate\n");
fprintf(out, " -d: delay multiplier (float)\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, " b: run box\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, " u: run uniblock\n");
fprintf(out, " v: run view\n");
fprintf(out, " w: run bleachworm\n");
fprintf(out, " w: run witherworm\n");
exit(status);
}
@ -183,7 +188,7 @@ ext_demos(struct notcurses* nc, const char* demos){
case 'l': ret = luigi_demo(nc); break;
case 'v': ret = view_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;
default:
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.
static const char*
handle_opts(int argc, char** argv, notcurses_options* opts){
bool constant_seed = false;
int c;
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){
case 'h':
usage(*argv, EXIT_SUCCESS);
break;
case 'c':
constant_seed = true;
break;
case 'k':
opts->inhibit_alternate_screen = true;
break;
@ -244,6 +253,9 @@ handle_opts(int argc, char** argv, notcurses_options* opts){
usage(*argv, EXIT_FAILURE);
}
}
if(!constant_seed){
srand(time(NULL)); // a classic blunder lol
}
const char* demos = argv[optind];
return demos;
}
@ -252,7 +264,6 @@ handle_opts(int argc, char** argv, notcurses_options* opts){
int main(int argc, char** argv){
struct notcurses* nc;
notcurses_options nopts;
struct ncplane* ncp;
if(!setlocale(LC_ALL, "")){
fprintf(stderr, "Couldn't set locale based on user preferences\n");
return EXIT_FAILURE;
@ -267,8 +278,9 @@ int main(int argc, char** argv){
if((nc = notcurses_init(&nopts, stdout)) == NULL){
return EXIT_FAILURE;
}
if((ncp = notcurses_stdplane(nc)) == NULL){
fprintf(stderr, "Couldn't get standard plane\n");
int dimx, dimy;
notcurses_term_dim_yx(nc, &dimy, &dimx);
if(dimy < MIN_SUPPORTED_ROWS || dimx < MIN_SUPPORTED_COLS){
goto err;
}
// no one cares about the leaderscreen. 1s max.
@ -306,6 +318,10 @@ int main(int argc, char** argv){
return EXIT_SUCCESS;
err:
notcurses_term_dim_yx(nc, &dimy, &dimx);
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;
}

@ -16,7 +16,7 @@ extern struct timespec demodelay;
char* find_data(const char* datum);
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 maxcolor_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;
}
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
// 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
// our colocality (unless they're summary area walls).
static void *
snake_thread(void* vnc){
const int snakecount = 3; // FIXME base count off area
struct notcurses* nc = vnc;
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;
ncplane_dim_yx(n, &dimy, &dimx);
int x, y;
// start it in the lower center of the screen
x = (random() % (dimx / 2)) + (dimx / 4);
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;
snake snakes[snakecount];
for(int s = 0 ; s < snakecount ; ++s){
init_snake(&snakes[s], dimy, dimx);
}
struct timespec iterdelay = { .tv_sec = 0, .tv_nsec = 1000000000ul / 20, };
int prevx = 0, prevy = 0;
while(true){
pthread_testcancel();
get_surrounding_cells(n, lightup, y, x);
ncplane_cursor_move_yx(n, y, x);
ncplane_at_cursor(n, &c);
if(lightup_surrounding_cells(n, lightup, y, x)){
for(int s = 0 ; s < snakecount ; ++s){
if(snakey_top(nc, &snakes[s])){
return NULL;
}
}
if(notcurses_render(nc)){
return NULL;
}
notcurses_render(nc);
int oldy, oldx;
clock_nanosleep(CLOCK_MONOTONIC, 0, &iterdelay, 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;
for(int s = 0 ; s < snakecount ; ++s){
if(snakey(nc, &snakes[s], dimy, dimx, &iterdelay)){
return NULL;
}
// 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;
}
@ -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
int bleachworm_demo(struct notcurses* nc){
int witherworm_demo(struct notcurses* nc){
static const char* strs[] = {
"Война и мир",
"Бра́тья Карама́зовы",
@ -537,8 +568,8 @@ int bleachworm_demo(struct notcurses* nc){
NULL
};
const char** s;
const int steps[] = { 0x100, 0x100, 0x40000, 0x10001, };
const int starts[] = { 0x004000, 0x000040, 0x010101, 0x400040, };
const int steps[] = { 0x10040, 0x100, 0x100, 0x10001, };
const int starts[] = { 0x10101, 0x004000, 0x000040, 0x400040, };
struct ncplane* n = notcurses_stdplane(nc);
size_t i;

@ -230,6 +230,12 @@ egcpool_dump(egcpool* pool){
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
}
#endif

@ -134,7 +134,6 @@ int ncplane_fadein(ncplane* n, const struct timespec* ts){
}
}
}
ncplane_updamage(n);
notcurses_render(n->nc);
uint64_t nextwake = (iter + 1) * nanosecs_step + startns;
struct timespec sleepspec;
@ -204,7 +203,6 @@ int ncplane_fadeout(ncplane* n, const struct timespec* ts){
}
}
}
ncplane_updamage(n);
cell* c = &n->defcell;
if(!cell_fg_default_p(c)){
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
void* userptr; // slot for the user to stick some opaque pointer
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
} ncplane;
@ -80,20 +79,32 @@ typedef struct ncvisual {
typedef struct notcurses {
pthread_mutex_t lock;
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
unsigned char* damage; // damage map (row granularity)
ncplane* top; // the contents of our topmost plane (initially entire screen)
ncplane* stdscr;// aliases some plane from the z-buffer, covers screen
// 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)
FILE* mstreamfp;// FILE* for 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 stashstats; // cumulative stats, unaffected by notcurses_reset_stats()
// We verify that some terminfo capabilities exist. These needn't be checked
// before further use; just use tiparm() directly.
int colors; // number of colors terminfo reported usable for this screen
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
// These might be NULL, and we can more or less work without them. Check!
char* clearscr; // erase screen and home cursor
@ -117,12 +128,15 @@ typedef struct notcurses {
char* italoff; // CELL_STYLE_ITALIC (disable)
char* smkx; // enter keypad transmit mode (keypad_xmit)
char* rmkx; // leave keypad transmit mode (keypad_local)
struct termios tpreserved; // terminal state upon entry
bool suppress_banner; // from notcurses_options
bool RGBflag; // terminfo-reported "RGB" flag for 24bpc directcolor
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
struct termios tpreserved; // terminal state upon entry
bool suppress_banner; // from notcurses_options
unsigned char inputbuf[BUFSIZ];
// we keep a wee ringbuffer of input queued up for delivery. if
// 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;
}
// 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
// ~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
@ -223,8 +224,7 @@ term_emit(const char* name __attribute__ ((unused)), const char* seq,
static inline const char*
extended_gcluster(const ncplane* n, const cell* c){
uint32_t idx = cell_egc_idx(c);
return n->pool.pool + idx;
return egcpool_extended_gcluster(&n->pool, c);
}
#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);
}
}
flash_damage_map(ncv->ncp->damage + ncv->placey, y - ncv->placey, true);
return 0;
}

@ -262,9 +262,7 @@ term_verify_seq(char** gseq, const char* name){
static void
free_plane(ncplane* p){
if(p){
ncplane_updamage(p);
egcpool_dump(&p->pool);
free(p->damage);
free(p->fb);
free(p);
}
@ -287,13 +285,6 @@ ncplane_create(notcurses* nc, int rows, int cols, int yoff, int xoff){
return NULL;
}
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->leny = rows;
p->lenx = cols;
@ -341,8 +332,7 @@ ncplane* notcurses_newplane(notcurses* nc, int rows, int cols,
return n;
}
// can be used on stdscr, unlike ncplane_resize() which prohibits it. sets all
// members of the plane's damage map to damaged.
// can be used on stdscr, unlike ncplane_resize() which prohibits it.
static int
ncplane_resize_internal(ncplane* n, int keepy, int keepx, int keepleny,
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){
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
if(n->y >= ylen){
n->y = ylen - 1;
@ -411,7 +388,6 @@ ncplane_resize_internal(ncplane* n, int keepy, int keepx, int keepleny,
n->lenx = xlen;
n->leny = ylen;
free(preserved);
ncplane_updamage(n); // damage any lines we're now on
return 0;
}
// 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->leny = ylen;
free(preserved);
ncplane_updamage(n); // damage any lines we're now on
return 0;
}
@ -489,19 +464,6 @@ int notcurses_resize(notcurses* n, int* rows, int* cols){
if(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)){
return -1;
}
@ -593,6 +555,8 @@ interrogate_terminfo(notcurses* nc, const notcurses_options* opts){
term_verify_seq(&nc->clearscr, "clear");
term_verify_seq(&nc->cleareol, "el");
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)){
return -1;
}
@ -725,6 +689,10 @@ notcurses* notcurses_init(const notcurses_options* opts, FILE* outfp){
ret->ttyinfp = stdin; // FIXME
ret->mstream = NULL;
ret->mstrsize = 0;
ret->lastframe = NULL;
ret->lfdimy = 0;
ret->lfdimx = 0;
egcpool_init(&ret->pool);
if(make_nonblocking(ret->ttyinfp)){
free(ret);
return NULL;
@ -783,16 +751,10 @@ notcurses* notcurses_init(const notcurses_options* opts, FILE* outfp){
free_plane(ret->top);
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){
free(ret->damage);
free_plane(ret->top);
goto err;
}
flash_damage_map(ret->damage, ret->stdscr->leny, false);
// term_emit("clear", ret->clear, ret->ttyfp, false);
ret->suppress_banner = opts->suppress_bannner;
if(!opts->suppress_bannner){
@ -856,10 +818,11 @@ int notcurses_stop(notcurses* nc){
nc->top = p->z;
free_plane(p);
}
free(nc->damage);
if(nc->mstreamfp){
fclose(nc->mstreamfp);
}
egcpool_dump(&nc->pool);
free(nc->lastframe);
free(nc->mstream);
input_free_esctrie(&nc->inputescapes);
ret |= pthread_mutex_destroy(&nc->lock);
@ -955,7 +918,6 @@ int ncplane_set_default(ncplane* ncp, const cell* c){
if(ret < 0){
return -1;
}
ncplane_updamage(ncp);
return ret;
}
@ -991,7 +953,6 @@ int ncplane_move_above_unsafe(ncplane* restrict n, ncplane* restrict above){
*an = n->z; // splice n out
n->z = above; // attach above below n
*aa = n; // spline n in above
ncplane_updamage(n); // conservative (we might not actually be visible)
return 0;
}
@ -1004,7 +965,6 @@ int ncplane_move_below_unsafe(ncplane* restrict n, ncplane* restrict below){
*an = n->z; // splice n out
n->z = below->z; // reattach subbelow list to n
below->z = n; // splice n in below
ncplane_updamage(n); // conservative (we might not actually be visible)
return 0;
}
@ -1016,7 +976,6 @@ int ncplane_move_top(ncplane* n){
*an = n->z; // splice n out
n->z = n->nc->top;
n->nc->top = n;
ncplane_updamage(n);
return 0;
}
@ -1032,7 +991,6 @@ int ncplane_move_bottom(ncplane* n){
}
*an = n;
n->z = NULL;
ncplane_updamage(n);
return 0;
}
@ -1062,24 +1020,6 @@ ncplane_cursor_stuck(const ncplane* n){
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
cell_set_wide(cell* c){
c->channels |= CELL_WIDEASIAN_MASK;
@ -1129,7 +1069,6 @@ int ncplane_putc(ncplane* n, const cell* c){
cell_release(n, candidate);
}
}
n->damage[n->y] = true;
advance_cursor(n, cols);
ncplane_unlock(n);
return cols;
@ -1174,13 +1113,6 @@ int ncplane_cursor_at(const ncplane* n, cell* c, char** gclust){
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){
cell_release(n, c);
int bytes;
@ -1536,33 +1468,12 @@ int ncplane_box(ncplane* n, const cell* ul, const cell* ur,
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){
if(n == n->nc->stdscr){
return -1;
}
ncplane_updamage(n); // damage any lines we are currently on
bool movedy = n->absy != y;
n->absy = y;
n->absx = x;
if(movedy){
ncplane_updamage(n);
}
return 0;
}
@ -1602,7 +1513,6 @@ void ncplane_erase(ncplane* n){
egcpool_init(&n->pool);
cell_load(n, &n->defcell, egc);
free(egc);
ncplane_updamage(n);
ncplane_unlock(n);
}

@ -1,4 +1,5 @@
#include <ctype.h>
#include <limits.h>
#include <unistd.h>
#include <sys/poll.h>
#include "internal.h"
@ -98,24 +99,33 @@ prep_optimized_palette(notcurses* nc, FILE* out __attribute__ ((unused))){
return 0;
}
/*
// reshape the shadow framebuffer to match the stdplane's dimensions, throwing
// away the old one.
static int
reshape_shadow_fb(notcurses* nc){
const size_t size = sizeof(nc->shadowbuf) * nc->stdscr->leny * nc->stdscr->lenx;
cell* fb = malloc(size);
if(nc->lfdimx == nc->stdscr->lenx && nc->lfdimy == nc->stdscr->leny){
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){
free(nc->lastframe);
nc->lastframe = NULL;
nc->lfdimx = 0;
nc->lfdimy = 0;
return -1;
}
free(nc->shadowbuf);
nc->shadowbuf = fb;
nc->shadowy = nc->stdscr->leny;
nc->shadowx = nc->stdscr->lenx;
nc->lastframe = fb;
// FIXME more memset()tery than we need, both wasting work and wrecking
// damage detection for the upcoming render
memset(nc->lastframe, 0, size);
nc->lastframe = fb;
nc->lfdimy = nc->stdscr->leny;
nc->lfdimx = nc->stdscr->lenx;
memset(fb, 0, size);
egcpool_dump(&nc->pool);
return 0;
}
*/
// 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
@ -135,9 +145,8 @@ reshape_shadow_fb(notcurses* nc){
// whichever one occurs at the top with a non-transparent α (α < 3). To effect
// tail recursion, though, we instead write first, and then recurse, blending
// as we descend. α <= 0 is opaque. α >= 3 is fully transparent.
static ncplane*
dig_visible_cell(cell* c, int y, int x, ncplane* p, int falpha, int balpha,
bool* damage){
static inline ncplane*
dig_visible_cell(cell* c, int y, int x, ncplane* p, int falpha, int balpha){
// once we decide on our glyph, it cannot be changed by anything below, so
// lock in this plane for the actual cell return.
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;
cell_set_fchannel(c, cell_get_fchannel(vis)); // 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){
cell_set_bchannel(c, cell_get_bchannel(vis)); // FIXME blend it in
balpha -= (CELL_ALPHA_TRANSPARENT - nalpha);
if(p->damage[poffy]){
*damage = true;
p->damage[poffy] = false;
}
}
if((falpha <= 0 && balpha <= 0) || !p->z){ // done!
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*
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);
return dig_visible_cell(c, y, x, n, CELL_ALPHA_TRANSPARENT,
CELL_ALPHA_TRANSPARENT, damage);
return dig_visible_cell(c, y, x, n, CELL_ALPHA_TRANSPARENT, CELL_ALPHA_TRANSPARENT);
}
// 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;
}
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
notcurses_render_internal(notcurses* nc){
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
// the last used both defaults.
bool fgelidable = false, bgelidable = false, defaultelidable = false;
/*if(nc->stdscr->leny != nc->shadowy || nc->stdscr->lenx != nc->shadowx){
reshape_shadow_fb(nc);
}*/
// if this fails, struggle bravely on. we can live without a lastframe.
reshape_shadow_fb(nc);
for(y = 0 ; y < nc->stdscr->leny ; ++y){
bool linedamaged = false; // have we repositioned the cursor to start line?
bool newdamage = nc->damage[y];
// fprintf(stderr, "nc->damage[%d] (%p) = %u\n", y, nc->damage + y, nc->damage[y]);
if(newdamage){
nc->damage[y] = 0;
}
// move to the beginning of the line, in case our accounting was befouled
// by wider- (or narrower-) than-reported characters
// how many characters have we elided? it's not worthwhile to invoke a
// cursor movement with cup if we only elided one or two. set to INT_MAX
// whenever we're on a new line.
int needmove = INT_MAX;
for(x = 0 ; x < nc->stdscr->lenx ; ++x){
unsigned r, g, b, br, bg, bb;
ncplane* p;
cell c; // no need to initialize
p = visible_cell(&c, y, x, nc->top, &newdamage);
assert(p);
p = visible_cell(&c, y, x, nc->top);
// 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
// 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))){
continue;
continue; // needmove will be reset as we restart the line
}
if(!linedamaged){
if(newdamage){
term_emit("cup", tiparm(nc->cup, y, x), out, false);
nc->stats.cellelisions += x;
nc->stats.cellemissions += (nc->stdscr->lenx - x);
linedamaged = true;
}else{
// lastframe has already been sized to match the current size, so no need
// to check whether we're within its bounds. just check the cell.
if(nc->lastframe){
cell* oldcell = &nc->lastframe[fbcellidx(nc->stdscr, y, x)];
if(cellcmp_and_dupfar(&nc->pool, oldcell, p, &c) == 0){
// no need to emit a cell; what we rendered appears to already be
// 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;
}
}
++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
// does, we need update our elision possibilities.
bool normalized;
@ -498,9 +581,6 @@ notcurses_render_internal(notcurses* nc){
++x;
}
}
if(linedamaged == false){
nc->stats.cellelisions += x;
}
}
ret |= fflush(out);
fflush(nc->ttyfp);

Loading…
Cancel
Save