diff --git a/user/cgi_sockets.c b/user/cgi_sockets.c index 44accb2..4824615 100644 --- a/user/cgi_sockets.c +++ b/user/cgi_sockets.c @@ -278,7 +278,7 @@ static void ICACHE_FLASH_ATTR updateSockRx(Websock *ws, char *data, int len, int case 'i': // requests initial load inp_dbg("Client requests initial load"); - updateNotify_do(ws, TOPIC_INITIAL); + updateNotify_do(ws, TOPIC_INITIAL|TOPIC_FLAG_NOCLEAN); break; case 'm': diff --git a/user/screen.c b/user/screen.c index cf6f5f0..9b76c28 100644 --- a/user/screen.c +++ b/user/screen.c @@ -139,6 +139,24 @@ static struct { static volatile int notifyLock = 0; static volatile ScreenNotifyTopics lockTopics = 0; +static struct { + int x_min, y_min, x_max, y_max; +} scr_dirty; + +#define reset_screen_dirty() do { \ + scr_dirty.x_min = W; \ + scr_dirty.x_max = -1; \ + scr_dirty.y_min = H; \ + scr_dirty.y_max = -1; \ + } while(0) + +#define expand_dirty(y0, y1, x0, x1) do { \ + if ((y0) < scr_dirty.y_min) scr_dirty.y_min = (y0); \ + if ((x0) < scr_dirty.x_min) scr_dirty.x_min = (x0); \ + if ((y1) > scr_dirty.y_max) scr_dirty.y_max = (y1); \ + if ((x1) > scr_dirty.x_max) scr_dirty.x_max = (x1); \ + } while(0) + #define NOTIFY_LOCK() do { \ notifyLock++; \ } while(0) @@ -252,6 +270,7 @@ screen_init(void) { if(DEBUG_HEAP) dbg("Screen buffer size = %d bytes", sizeof(screen)); + reset_screen_dirty(); screen_reset(); } @@ -746,10 +765,12 @@ screen_clear(ClearMode mode) case CLEAR_FROM_CURSOR: clear_range_utf((cursor.y * W) + cursor.x, W * H - 1); + expand_dirty(cursor.y, H-1, 0, W-1); break; case CLEAR_TO_CURSOR: clear_range_utf(0, (cursor.y * W) + cursor.x); + expand_dirty(0, cursor.y, 0, W-1); break; } NOTIFY_DONE(mode == CLEAR_ALL ? TOPIC_CHANGE_CONTENT_ALL : TOPIC_CHANGE_CONTENT_PART); @@ -765,14 +786,17 @@ screen_clear_line(ClearMode mode) switch (mode) { case CLEAR_ALL: clear_row_utf(cursor.y); + expand_dirty(cursor.y, cursor.y, 0, W-1); break; case CLEAR_FROM_CURSOR: clear_range_utf(cursor.y * W + cursor.x, (cursor.y + 1) * W - 1); + expand_dirty(cursor.y, cursor.y, cursor.x, W-1); break; case CLEAR_TO_CURSOR: clear_range_utf(cursor.y * W, cursor.y * W + cursor.x); + expand_dirty(cursor.y, cursor.y, 0, cursor.x); break; } NOTIFY_DONE(TOPIC_CHANGE_CONTENT_PART); @@ -786,6 +810,7 @@ screen_clear_in_line(unsigned int count) screen_clear_line(CLEAR_FROM_CURSOR); } else { clear_range_utf(cursor.y * W + cursor.x, cursor.y * W + cursor.x + count - 1); + expand_dirty(cursor.y, cursor.y, cursor.x, cursor.x + count - 1); } NOTIFY_DONE(TOPIC_CHANGE_CONTENT_PART); } @@ -815,6 +840,7 @@ screen_insert_lines(unsigned int lines) copy_row(i, cursor.y); } } + expand_dirty(cursor.y, BTM, 0, W - 1); NOTIFY_DONE(TOPIC_CHANGE_CONTENT_PART); } @@ -840,6 +866,7 @@ screen_delete_lines(unsigned int lines) clear_range_noutf((movedBlockEnd+1)*W, (BTM+1)*W-1); } + expand_dirty(cursor.y, BTM, 0, W - 1); NOTIFY_DONE(TOPIC_CHANGE_CONTENT_PART); } @@ -863,6 +890,7 @@ screen_insert_characters(unsigned int count) } clear_range_utf(cursor.y * W + cursor.x, cursor.y * W + targetStart - 1); } + expand_dirty(cursor.y, cursor.y, cursor.x, W - 1); NOTIFY_DONE(TOPIC_CHANGE_CONTENT_PART); } @@ -889,6 +917,7 @@ screen_delete_characters(unsigned int count) screen_clear_line(CLEAR_FROM_CURSOR); } + expand_dirty(cursor.y, cursor.y, cursor.x, W - 1); NOTIFY_DONE(TOPIC_CHANGE_CONTENT_PART); } //endregion @@ -991,6 +1020,7 @@ screen_scroll_up(unsigned int lines) clear_range_noutf(y * W, (BTM + 1) * W - 1); done: + expand_dirty(TOP, BTM, 0, W - 1); NOTIFY_DONE(TOPIC_CHANGE_CONTENT_PART); } @@ -1021,6 +1051,7 @@ screen_scroll_down(unsigned int lines) clear_range_noutf(TOP * W, TOP * W + lines * W - 1); done: + expand_dirty(TOP, BTM, 0, W - 1); NOTIFY_DONE(TOPIC_CHANGE_CONTENT_PART); } @@ -1146,6 +1177,7 @@ screen_cursor_move(int dy, int dx, bool scroll) { NOTIFY_LOCK(); int move; + bool scrolled = 0; clear_invalid_hanging(); @@ -1192,7 +1224,10 @@ screen_cursor_move(int dy, int dx, bool scroll) if (was_inside) { move = -(cursor.y - TOP); cursor.y = TOP; - if (scroll) screen_scroll_down((unsigned int) move); + if (scroll) { + screen_scroll_down((unsigned int) move); + scrolled = true; + } } else { // outside the region, just validate that we're not going offscreen @@ -1207,7 +1242,10 @@ screen_cursor_move(int dy, int dx, bool scroll) if (was_inside) { move = cursor.y - BTM; cursor.y = BTM; - if (scroll) screen_scroll_up((unsigned int) move); + if (scroll) { + screen_scroll_up((unsigned int) move); + scrolled = true; + } } else { // outside the region, just validate that we're not going offscreen @@ -1218,7 +1256,11 @@ screen_cursor_move(int dy, int dx, bool scroll) } } - NOTIFY_DONE(TOPIC_CHANGE_CURSOR | (scroll*TOPIC_CHANGE_CONTENT_PART)); + if (scrolled) { + expand_dirty(TOP, BTM, 0, W-1); + } + + NOTIFY_DONE(TOPIC_CHANGE_CURSOR | (scrolled*TOPIC_CHANGE_CONTENT_PART)); } /** @@ -1587,6 +1629,7 @@ putchar_graphic(const char *ch) } Cell *c = &screen[cursor.x + cursor.y * W]; + expand_dirty(cursor.y, cursor.y, cursor.x, cursor.x); // move the rest of the line if we're in Insert Mode if (cursor.x < W-1 && scr.insert_mode) screen_insert_characters(1); @@ -1740,6 +1783,10 @@ struct ScreenSerializeState { ScreenNotifyTopics topics; ScreenNotifyTopics last_topic; ScreenNotifyTopics current_topic; + bool partial; + int x_min, x_max, y_min, y_max; + int i_max; + int i_start; }; /** @@ -1813,7 +1860,46 @@ screenSerializeToBuffer(char *buffer, size_t buf_len, ScreenNotifyTopics topics, topics |= TOPIC_INTERNAL; } - ss->index = 0; + if (topics & TOPIC_CHANGE_CONTENT_PART) { + // reset dirty extents + ss->partial = true; + + ss->x_min = scr_dirty.x_min; + ss->x_max = scr_dirty.x_max; + ss->y_min = scr_dirty.y_min; + ss->y_max = scr_dirty.y_max; + + if (ss->x_min > ss->x_max || ss->y_min > ss->y_max) { + dbg("Partial redraw, but bad bounds!"); + // use full redraw + topics ^= TOPIC_CHANGE_CONTENT_PART; + topics |= TOPIC_CHANGE_CONTENT_ALL; + } else { + // is OK + ss->i_max = ss->y_max * W + ss->x_max; + ss->index = W*ss->y_min + ss->x_min; + dbg("Partial! X %d..%d, Y %d..%d, i_max %d", ss->x_min, ss->x_max, ss->y_min, ss->y_min, ss->i_max); + } + } + + if (topics & TOPIC_CHANGE_CONTENT_ALL) { + // this is a no-clean request, do not purge + // it's also always a full-screen repaint + ss->partial = false; + ss->index = 0; + ss->i_max = W*H-1; + ss->x_min = 0; + ss->x_max = W-1; + ss->y_min = 0; + ss->y_max = H-1; + dbg("Full redraw!"); + } + + ss->i_start = ss->index; + + if ((topics & (TOPIC_CHANGE_CONTENT_ALL | TOPIC_CHANGE_CONTENT_PART)) && !(topics & TOPIC_FLAG_NOCLEAN)) { + reset_screen_dirty(); + } ss->topics = topics; ss->last_topic = 0; // to be filled @@ -1823,11 +1909,22 @@ screenSerializeToBuffer(char *buffer, size_t buf_len, ScreenNotifyTopics topics, bufput_c('U'); // - stands for "update" bufput_utf8(topics); + + if (ss->partial) { + // advance to the first char we want to send + } } int begun_topic = 0; int prev_topic = 0; +#define INC_I() do { \ + i++; \ + if (ss->partial) {\ + if (i%W > ss->x_max) i += (W - ss->x_max + ss->x_min - 1);\ + } \ + } while (0) + #define BEGIN_TOPIC(topic, size) \ if (ss->last_topic == prev_topic) { \ begun_topic = (topic); \ @@ -1945,20 +2042,19 @@ screenSerializeToBuffer(char *buffer, size_t buf_len, ScreenNotifyTopics topics, // no screen mode - wrap it up goto ser_done; } + + // start the screen section } } // screen contents int i = ss->index; - if (i == 0) { + if (i == ss->i_start) { bufput_c(TOPICMARK_SCREEN); // desired update mode is in `ss->current_topic` - - // TODO implement partial - bufput_utf8(0); // Y0 - bufput_utf8(0); // X0 - bufput_utf8(H); // height - bufput_utf8(W); // width - + bufput_utf8(ss->y_min); // Y0 + bufput_utf8(ss->x_min); // X0 + bufput_utf8(ss->y_max - ss->y_min + 1); // height + bufput_utf8(ss->x_max - ss->x_min + 1); // width ss->index = 0; ss->lastBg = 0xFF; ss->lastFg = 0xFF; @@ -1966,19 +2062,20 @@ screenSerializeToBuffer(char *buffer, size_t buf_len, ScreenNotifyTopics topics, ss->lastCharLen = 0; ss->lastSymbol = 0; } - while(i < W*H && remain > 12) { + while(i <= ss->i_max && remain > 12) { cell = cell0 = &screen[i]; // Count how many times same as previous int repCnt = 0; - while (i < W*H + while (i <= ss->i_max && cell->fg == ss->lastFg && cell->bg == ss->lastBg && cell->attrs == ss->lastAttrs && cell->symbol == ss->lastSymbol) { // Repeat repCnt++; - cell = &screen[++i]; + INC_I(); + cell = &screen[i]; // it can go outside the allocated memory here if we went over the top } if (repCnt == 0) { @@ -2023,7 +2120,7 @@ screenSerializeToBuffer(char *buffer, size_t buf_len, ScreenNotifyTopics topics, ss->lastAttrs = cell0->attrs; ss->lastSymbol = cell0->symbol; - i++; + INC_I(); } else { // last character was repeated repCnt times bufput_t_utf8(SEQ_TAG_REPEAT, repCnt); @@ -2031,7 +2128,7 @@ screenSerializeToBuffer(char *buffer, size_t buf_len, ScreenNotifyTopics topics, } ss->index = i; - if (i >= W*H-1) goto ser_done; + if (i >= ss->i_max) goto ser_done; // MORE TO WRITE... bufput_c('\0'); // terminate the string diff --git a/user/screen.h b/user/screen.h index fc496d1..da5a307 100644 --- a/user/screen.h +++ b/user/screen.h @@ -163,6 +163,8 @@ enum ScreenSerializeTopic { TOPIC_CHANGE_CURSOR = (1<<5), TOPIC_INTERNAL = (1<<6), // debugging internal state TOPIC_BELL = (1<<7), // beep + TOPIC_FLAG_NOCLEAN = (1<<15), // do not clean dirty extents + // combos TOPIC_INITIAL = TOPIC_CHANGE_SCREEN_OPTS |