From 25d7456145c54b34f5285fe74b9f4d6ce21cee7b Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Deuc=D0=B5?= <shurd@sasktel.net>
Date: Wed, 8 Jan 2025 13:58:05 -0500
Subject: [PATCH] Make the scrollback a ring buffer instead of a linear one.

This was one of the two major perforance issues with previous
versions of SyncTERM.  Using a larger scrollback will not longer
slow down SyncTERM.

The other major issue is updating the pixel data for every change,
but we can't fix that as long as graphics and text are drawn on the
same plane.
---
 src/conio/cterm.c       | 66 +++++++++++++++++++++--------------------
 src/conio/cterm.h       |  4 ++-
 src/syncterm/bbslist.c  | 12 ++++----
 src/syncterm/menu.c     | 24 +++++++++------
 src/syncterm/syncterm.c |  1 -
 src/syncterm/syncterm.h |  3 --
 src/syncterm/term.c     | 60 ++++++++++++++++++++++++++-----------
 7 files changed, 101 insertions(+), 69 deletions(-)

diff --git a/src/conio/cterm.c b/src/conio/cterm.c
index a9f93934b0..590b3f03b2 100644
--- a/src/conio/cterm.c
+++ b/src/conio/cterm.c
@@ -1391,6 +1391,33 @@ scrolldown(struct cterminal *cterm)
 	GOTOXY(x, y);
 }
 
+static void
+cterm_line_to_scrollback(struct cterminal *cterm, int row)
+{
+	if(cterm->scrollback!=NULL) {
+		int getw;
+
+		cterm->backfilled++;
+		if (cterm->backfilled > cterm->backlines) {
+			cterm->backfilled--;
+			cterm->backstart++;
+			if (cterm->backstart == cterm->backlines)
+				cterm->backstart = 0;
+		}
+		getw = cterm->backwidth;
+		if (getw > cterm->width)
+			getw = cterm->width;
+		if (getw < cterm->backwidth) {
+			memset(cterm->scrollback + cterm->backpos * cterm->backwidth, 0, sizeof(*cterm->scrollback) * cterm->backwidth);
+		}
+		vmem_gettext(cterm->x, row, cterm->x + getw - 1, row, cterm->scrollback + cterm->backpos * cterm->backwidth);
+
+		cterm->backpos++;
+		if (cterm->backpos == cterm->backlines)
+			cterm->backpos = 0;
+	}
+}
+
 void
 cterm_scrollup(struct cterminal *cterm)
 {
@@ -1399,24 +1426,10 @@ cterm_scrollup(struct cterminal *cterm)
 	int maxx = TERM_MAXX;
 	int maxy = TERM_MAXY;
 	int x,y;
-	int getw;
 
-	cterm->backpos++;
 	coord_conv_xy(cterm, CTERM_COORD_TERM, CTERM_COORD_SCREEN, &minx, &miny);
 	coord_conv_xy(cterm, CTERM_COORD_TERM, CTERM_COORD_SCREEN, &maxx, &maxy);
-	if(cterm->scrollback!=NULL) {
-		if(cterm->backpos>cterm->backlines) {
-			memmove(cterm->scrollback, cterm->scrollback + cterm->backwidth, cterm->backwidth * sizeof(*cterm->scrollback) * (cterm->backlines - 1));
-			cterm->backpos--;
-		}
-		getw = cterm->backwidth;
-		if (getw > cterm->width)
-			getw = cterm->width;
-		if (getw < cterm->backwidth) {
-			memset(cterm->scrollback + (cterm->backpos - 1) * cterm->backwidth, 0, sizeof(*cterm->scrollback) * cterm->backwidth);
-		}
-		vmem_gettext(cterm->x, miny, cterm->x + getw - 1, miny, cterm->scrollback + (cterm->backpos - 1) * cterm->backwidth);
-	}
+	cterm_line_to_scrollback(cterm, miny);
 	MOVETEXT(minx, miny + 1, maxx, maxy, minx, miny);
 	CURR_XY(&x, &y);
 	cterm_clrblk(cterm, minx, maxy, minx + TERM_MAXX - 1, maxy);
@@ -1500,26 +1513,15 @@ clear2bol(struct cterminal * cterm)
 void
 cterm_clearscreen(struct cterminal *cterm, char attr)
 {
-	int getw;
-
 	if(!cterm->started)
 		cterm_start(cterm);
 
-	if(cterm->scrollback!=NULL) {
-		cterm->backpos+=cterm->height;
-		if(cterm->backpos>cterm->backlines) {
-			memmove(cterm->scrollback, cterm->scrollback + cterm->backwidth * (cterm->backpos - cterm->backlines), cterm->backwidth * sizeof(*cterm->scrollback) * (cterm->backlines - (cterm->backpos - cterm->backlines)));
-			cterm->backpos=cterm->backlines;
-		}
-		getw = cterm->backwidth;
-		if (getw > cterm->width)
-			getw = cterm->width;
-		if (getw < cterm->backwidth) {
-			memset(cterm->scrollback + (cterm->backpos - cterm->height) * cterm->backwidth, 0, sizeof(*cterm->scrollback) * cterm->backwidth * cterm->height);
-		}
-		vmem_gettext(cterm->x, cterm->y, cterm->x + getw - 1, cterm->y + cterm->height - 1,
-		    cterm->scrollback + (cterm->backpos - cterm->height) * cterm->backwidth);
-	}
+	int minx = TERM_MINX;
+	int miny = TERM_MINY;
+	coord_conv_xy(cterm, CTERM_COORD_TERM, CTERM_COORD_SCREEN, &minx, &miny);
+
+	for (int i = 0; i < cterm->height; i++)
+		cterm_line_to_scrollback(cterm, miny + i);
 	CLRSCR();
 	GOTOXY(CURR_MINX, CURR_MINY);
 }
diff --git a/src/conio/cterm.h b/src/conio/cterm.h
index fc808d51cb..de8bedcbdb 100644
--- a/src/conio/cterm.h
+++ b/src/conio/cterm.h
@@ -99,6 +99,7 @@ struct cterminal {
 	int					right_margin;
 	int					quiet;			// No sounds are made
 	struct vmem_cell	*scrollback;
+	int					backfilled;		// Number of lines copied into scrollback
 	int					backlines;		// Number of lines in scrollback
 	int					backwidth;		// Number of columns in scrollback
 	char				DA[1024];		// Device Attributes
@@ -170,7 +171,8 @@ struct cterminal {
 	link_list_t			notes;
 	sem_t				playnote_thread_terminated;
 	sem_t				note_completed_sem;
-	int					backpos;
+	int					backpos; // Position where new lines will be added
+	int					backstart; // First line of scrollback
 	int					xpos;
 	int					ypos;
 	cterm_log_t			log;
diff --git a/src/syncterm/bbslist.c b/src/syncterm/bbslist.c
index d36c0d98e9..a5ed0a160f 100644
--- a/src/syncterm/bbslist.c
+++ b/src/syncterm/bbslist.c
@@ -368,6 +368,7 @@ viewofflinescroll(void)
 	struct  text_info  txtinfo;
 	struct  text_info  sbtxtinfo;
 	struct mouse_event mevent;
+	int scrollback_pos;
 
 	if (scrollback_buf == NULL)
 		return;
@@ -403,10 +404,11 @@ viewofflinescroll(void)
 	setfont(0, false, 4);
 	drawwin();
 	set_modepalette(palettes[COLOUR_PALETTE]);
-	top = scrollback_pos;
 	gotoxy(1, 1);
 	textattr(uifc.hclr | (uifc.bclr << 4) | BLINK);
 	gettextinfo(&sbtxtinfo);
+	scrollback_pos = scrollback_lines - sbtxtinfo.screenheight;
+	top = scrollback_pos;
 	ciomouse_addevent(CIOLIB_BUTTON_1_DRAG_START);
 	ciomouse_addevent(CIOLIB_BUTTON_1_DRAG_MOVE);
 	ciomouse_addevent(CIOLIB_BUTTON_1_DRAG_END);
@@ -415,10 +417,10 @@ viewofflinescroll(void)
 	showmouse();
 
 	for (i = 0; !i && !quitting;) {
-		if (top < 1)
-			top = 1;
-		if (top > (int)scrollback_lines)
-			top = scrollback_lines;
+		if (top < 0)
+			top = 0;
+		if (top > scrollback_pos)
+			top = scrollback_pos;
 		vmem_puttext(((sbtxtinfo.screenwidth - scrollback_cols) / 2) + 1, 1,
 		    (sbtxtinfo.screenwidth - scrollback_cols) / 2 + scrollback_cols,
 		    sbtxtinfo.screenheight,
diff --git a/src/syncterm/menu.c b/src/syncterm/menu.c
index 7a0af78b61..e17c5d2f4c 100644
--- a/src/syncterm/menu.c
+++ b/src/syncterm/menu.c
@@ -35,19 +35,25 @@ viewscroll(void)
         /* too large for alloca() */
 	scrollback =
 	    malloc((scrollback_buf
-	        == NULL ? 0 : (term.width * sizeof(*scrollback) * settings.backlines))
+	        == NULL ? 0 : (term.width * sizeof(*scrollback) * cterm->backlines))
 	        + (txtinfo.screenheight * txtinfo.screenwidth * sizeof(*scrollback)));
 	if (scrollback == NULL)
 		return;
-	memcpy(scrollback, cterm->scrollback, term.width * sizeof(*scrollback) * settings.backlines);
-	vmem_gettext(1, 1, txtinfo.screenwidth, txtinfo.screenheight, scrollback + (cterm->backpos) * cterm->width);
+	int lines = 0;
+	if (cterm->backstart > 0) {
+		lines = cterm->backlines - cterm->backstart;
+		memcpy(scrollback, cterm->scrollback + term.width * cterm->backstart, term.width * lines * sizeof(*scrollback));
+	}
+	memcpy(scrollback + term.width * lines, cterm->scrollback, term.width * sizeof(*scrollback) * cterm->backpos);
+	int sblines = cterm->backpos + lines;
+	vmem_gettext(1, 1, txtinfo.screenwidth, txtinfo.screenheight, scrollback + sblines * cterm->width);
 	savscrn = savescreen();
 	setfont(0, false, 1);
 	setfont(0, false, 2);
 	setfont(0, false, 3);
 	setfont(0, false, 4);
 	drawwin();
-	top = cterm->backpos;
+	top = sblines;
 	set_modepalette(palettes[COLOUR_PALETTE]);
 	gotoxy(1, 1);
 	textattr(uifc.hclr | (uifc.bclr << 4) | BLINK);
@@ -57,10 +63,10 @@ viewscroll(void)
 	ciomouse_addevent(CIOLIB_BUTTON_4_PRESS);
 	ciomouse_addevent(CIOLIB_BUTTON_5_PRESS);
 	for (i = 0; (!i) && (!quitting);) {
-		if (top < 1)
-			top = 1;
-		if (top > cterm->backpos)
-			top = cterm->backpos;
+		if (top < 0)
+			top = 0;
+		if (top > sblines)
+			top = sblines;
 		vmem_puttext(term.x - 1, term.y - 1, term.x + term.width - 2, term.y + term.height - 2,
 		    scrollback + (term.width * top));
 		cputs("Scrollback");
@@ -86,7 +92,7 @@ viewscroll(void)
 								break;
 							case CIOLIB_BUTTON_5_PRESS:
 								top++;
-								if (top > cterm->backpos)
+								if (top > sblines)
 									i = 1;
 								break;
 						}
diff --git a/src/syncterm/syncterm.c b/src/syncterm/syncterm.c
index 3c7142bf2c..0e4e519021 100644
--- a/src/syncterm/syncterm.c
+++ b/src/syncterm/syncterm.c
@@ -143,7 +143,6 @@ struct syncterm_settings settings;
 char                    *font_names[sizeof(conio_fontdata) / sizeof(struct conio_font_data_struct)];
 struct vmem_cell        *scrollback_buf = NULL;
 unsigned int             scrollback_lines = 0;
-unsigned int             scrollback_pos = 0;
 unsigned int             scrollback_mode = C80;
 unsigned int             scrollback_cols = 80;
 int                      safe_mode = 0;
diff --git a/src/syncterm/syncterm.h b/src/syncterm/syncterm.h
index 5ca4a75652..afb3e27117 100644
--- a/src/syncterm/syncterm.h
+++ b/src/syncterm/syncterm.h
@@ -86,10 +86,7 @@ extern char                    *inpath;
 extern char                    *list_override;
 extern const char              *syncterm_version;
 extern struct vmem_cell        *scrollback_buf;
-extern uint32_t                *scrollback_fbuf;
-extern uint32_t                *scrollback_bbuf;
 extern unsigned int             scrollback_lines;
-extern unsigned int             scrollback_pos;
 extern unsigned int             scrollback_mode;
 extern unsigned int             scrollback_cols;
 extern struct syncterm_settings settings;
diff --git a/src/syncterm/term.c b/src/syncterm/term.c
index 0264b3b4b6..fb49a6c3cb 100644
--- a/src/syncterm/term.c
+++ b/src/syncterm/term.c
@@ -4080,6 +4080,43 @@ normalize_entry(struct bbslist *bbs)
 	}
 }
 
+static void
+finish_scrollback(void)
+{
+	scrollback_buf = cterm->scrollback;
+	scrollback_cols = cterm->backwidth;
+	// TODO: Set scrollback_mode here?
+	if (cterm->scrollback != NULL) {
+		cterm_clearscreen(cterm, cterm->attr); /* Clear screen into
+							* scrollback */
+
+		// Now make the scrollback a linear buffer instead of a ring buffer
+		if (cterm->backstart) {
+			struct vmem_cell     *bottom;
+			int topsz = cterm->backlines - cterm->backstart;
+
+			bottom = malloc(cterm->backwidth * cterm->backstart * sizeof(*bottom));
+			if (bottom) {
+				memcpy(bottom, cterm->scrollback, cterm->backwidth * cterm->backstart * sizeof(*bottom));
+				memmove(cterm->scrollback, cterm->scrollback + cterm->backwidth * cterm->backstart, cterm->backwidth * topsz * sizeof(*cterm->scrollback));
+				memcpy(cterm->scrollback + cterm->backwidth * topsz, bottom, cterm->backwidth * cterm->backstart * sizeof(*bottom));
+				free(bottom);
+				scrollback_lines = cterm->backlines;
+			}
+			else {
+				memmove(cterm->scrollback, cterm->scrollback + cterm->backwidth * cterm->backstart, cterm->backwidth * topsz * sizeof(*cterm->scrollback));
+				scrollback_lines = topsz;
+			}
+		}
+		else {
+			scrollback_lines = cterm->backpos;
+		}
+	}
+	else {
+		scrollback_lines = 0;
+	}
+}
+
 bool
 doterm(struct bbslist *bbs)
 {
@@ -4153,7 +4190,6 @@ doterm(struct bbslist *bbs)
 	else {
 		FREE_AND_NULL(scrollback_buf);
 	}
-	scrollback_lines = 0;
 	scrollback_mode = txtinfo.currmode;
 	cterm = cterm_init(term.height,
 	        term.width,
@@ -4173,7 +4209,6 @@ doterm(struct bbslist *bbs)
 	cterm->mouse_state_change_cbdata = &ms;
 	cterm->mouse_state_query = mouse_state_query;
 	cterm->mouse_state_query_cbdata = &ms;
-	scrollback_cols = term.width;
 	cterm->music_enable = bbs->music;
 	ch[1] = 0;
 	zrqbuf[0] = 0;
@@ -4218,10 +4253,7 @@ doterm(struct bbslist *bbs)
 								uifcmsg("Disconnected",
 								    "`Disconnected`\n\nRemote host dropped connection");
 							check_exit(false);
-							scrollback_pos = cterm->backpos;
-							cterm_clearscreen(cterm, cterm->attr); /* Clear screen into
-                                                                                                * scrollback */
-							scrollback_lines = cterm->backpos;
+							finish_scrollback();
 							cterm_end(cterm, 0);
 							cterm = NULL;
 							// TODO: Do this before the popup to avoid being rude...
@@ -4559,10 +4591,7 @@ doterm(struct bbslist *bbs)
 					    "Selecting Yes closes the connection\n")) {
 						freescreen(savscrn);
 						setup_mouse_events(&ms);
-						scrollback_pos = cterm->backpos;
-						cterm_clearscreen(cterm, cterm->attr); /* Clear screen into
-                                                                                        * scrollback */
-						scrollback_lines = cterm->backpos;
+						finish_scrollback();
 						cterm_end(cterm, 0);
 						cterm = NULL;
 						conn_close();
@@ -4591,10 +4620,7 @@ doterm(struct bbslist *bbs)
 					j = wherey();
 					switch (syncmenu(bbs, &speed)) {
 						case -1:
-							scrollback_pos = cterm->backpos;
-							cterm_clearscreen(cterm, cterm->attr); /* Clear screen into
-                                                                                                * scrollback */
-							scrollback_lines = cterm->backpos;
+							finish_scrollback();
 							cterm_end(cterm, 0);
 							cterm = NULL;
 							conn_close();
@@ -4640,10 +4666,7 @@ doterm(struct bbslist *bbs)
 							break;
 						case 13:
 #endif
-							scrollback_pos = cterm->backpos;
-							cterm_clearscreen(cterm, cterm->attr); /* Clear screen into
-                                                                                                * scrollback */
-							scrollback_lines = cterm->backpos;
+							finish_scrollback();
 							cterm_end(cterm, 0);
 							cterm = NULL;
 							conn_close();
@@ -5089,5 +5112,6 @@ doterm(struct bbslist *bbs)
  *       hidemouse();
  *       hold_update=oldmc;
  */
+	finish_scrollback();
 	return false;
 }
-- 
GitLab