Maelstrom: Added font and string caching to Mac_FontServ

From ae02591c9d6219ba946542d0bfb434af1d9c8cf2 Mon Sep 17 00:00:00 2001
From: Sam Lantinga <[EMAIL REDACTED]>
Date: Thu, 20 Oct 2011 21:53:33 -0400
Subject: [PATCH] Added font and string caching to Mac_FontServ This works very
 well for Maelstrom since there is very little dynamic text.

---
 controls.cpp            |  6 +--
 init.cpp                |  2 +-
 maclib/Mac_FontServ.cpp | 91 +++++++++++++++++++++++++++++++++++------
 maclib/Mac_FontServ.h   | 17 ++++++--
 main.cpp                | 74 ++++++++++++++-------------------
 netlogic/about.cpp      |  2 +-
 netlogic/game.cpp       |  9 ++--
 scores.cpp              |  4 +-
 8 files changed, 135 insertions(+), 70 deletions(-)

diff --git a/controls.cpp b/controls.cpp
index 991f6ff0..9793e4b5 100644
--- a/controls.cpp
+++ b/controls.cpp
@@ -260,7 +260,7 @@ void ConfigureControls(void)
 	}
 	if ( (splash = Load_Title(screen, 100)) == NULL ) {
 		error("Can't load configuration splash!\n");
-		delete chicago;
+		fontserv->FreeFont(chicago);
 		return;
 	}
 	X=(SCREEN_WIDTH-CTL_DIALOG_WIDTH)/2;
@@ -322,7 +322,7 @@ void ConfigureControls(void)
 	for ( i=0; i<NUM_CTLS; ++i ) {
 		fontserv->FreeText(keynames[i]);
 	}
-	delete chicago;
+	fontserv->FreeFont(chicago);
 	delete dialog;
 	if ( valid ) {
 		memcpy(&controls, &newcontrols, sizeof(controls));
@@ -546,7 +546,7 @@ void ShowDawn(void)
 	screen->FreeImage(splash);
 	for ( i=0; i<6; ++i )
 		fontserv->FreeText(text[i]);
-	delete chicago;
+	fontserv->FreeFont(chicago);
 	delete dialog;
 	return;
 }
diff --git a/init.cpp b/init.cpp
index bd100b5e..69b1073b 100644
--- a/init.cpp
+++ b/init.cpp
@@ -121,7 +121,7 @@ void DoIntroScreen(void)
 				Yoff+20-text->h/2, text, NOCLIP);
 		fontserv->FreeText(text);
 	}
-	delete geneva;
+	fontserv->FreeFont(geneva);
 
 	screen->Update();
 }	// -- DoIntroScreen
diff --git a/maclib/Mac_FontServ.cpp b/maclib/Mac_FontServ.cpp
index 31d48dbc..7cc7c798 100644
--- a/maclib/Mac_FontServ.cpp
+++ b/maclib/Mac_FontServ.cpp
@@ -31,6 +31,7 @@
 
 #include "SDL_types.h"
 #include "bitesex.h"
+#include "hashtable.h"
 #include "Mac_FontServ.h"
 
 #define copy_short(S, D)	memcpy(&S, D, 2); D += 2;
@@ -71,12 +72,22 @@ struct FOND {
 	/* The Kerning Table */
 };
 
+static void
+hash_nuke_string_texture(const void *key, const void *value, void *data)
+{
+	FrameBuf *screen = (FrameBuf *)data;
+
+	delete[] (char*)key;
+	screen->FreeImage((SDL_Texture *)value);
+}
 
 FontServ:: FontServ(FrameBuf *_screen, const char *fontfile)
 {
 	screen = _screen;
 	fontres = new Mac_Resource(fontfile);
-	text_allocated = 0;
+	fonts = NULL;
+	strings = hash_create(screen, hash_hash_string, hash_keymatch_string, hash_nuke_string_texture);
+ 
 	if ( fontres->Error() ) {
 		SetError("Couldn't load resources from %s", fontfile);
 		return;
@@ -90,11 +101,13 @@ FontServ:: FontServ(FrameBuf *_screen, const char *fontfile)
 
 FontServ:: ~FontServ()
 {
-	if ( text_allocated != 0 ) {
-		fprintf(stderr,
-			"FontServ: Warning: %d text surfaces extant\n",
-							text_allocated);
+	while (fonts) {
+		MFont *next = fonts->next;
+		delete fonts;
+		fonts = next;
 	}
+	hash_destroy(strings);
+
 	delete fontres;
 }
 
@@ -109,8 +122,22 @@ FontServ:: NewFont(const char *fontname, int ptsize)
 	int nchars;		/* number of chars including 'missing char' */
 	int nwords;		/* bit image size, in words */
 	int i, swapfont;
-	MFont *font;
-
+	MFont *prev, *font;
+
+	/* First see if we can find the font in our cache */
+	prev = NULL;
+	for (font = fonts; font; prev = font, font = font->next) {
+		if (strcmp(fontname, font->name) == 0 && ptsize == font->ptsize) {
+			/* Move this font to the front so it's found faster */
+			if (prev) {
+				prev->next = font->next;
+				font->next = fonts;
+				fonts = font;
+			}
+			return font;
+		}
+	}
+	
 	/* Get the font family */
 	fond = fontres->Resource("FOND", fontname);
 	if ( fond == NULL ) {
@@ -152,6 +179,8 @@ FontServ:: NewFont(const char *fontname, int ptsize)
 
 	/* Now, Fent.ID is the ID of the correct NFNT resource */
 	font = new MFont;
+	font->name = fontname;
+	font->ptsize = ptsize;
 	font->nfnt = fontres->Resource("NFNT", Fent.ID);
 	if ( font->nfnt == NULL ) {
 		delete font;
@@ -205,9 +234,21 @@ FontServ:: NewFont(const char *fontname, int ptsize)
 		byteswap(font->locTable, nchars+1);
 		byteswap((Uint16 *)font->owTable, nchars);
 	}
+
+	/* Save this font in the cache */
+	font->next = fonts;
+	fonts = font;
+
 	return(font);
 }
 
+void
+FontServ:: FreeFont(MFont *font)
+{
+	/* We'll likely be asked for this again soon, leave it alone */
+	return;
+}
+
 /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
 #define HiByte(word)		((word>>8)&0xFF)
 #define LoByte(word)		(word&0xFF)
@@ -263,6 +304,8 @@ SDL_Texture *
 FontServ:: TextImage(const char *text, MFont *font, Uint8 style,
 			SDL_Color foreground, SDL_Color background)
 {
+	char *key, *keycopy;
+	int keysize;
 	int width, height;
 	SDL_Texture *image;
 	Uint32 *bitmap;
@@ -277,6 +320,14 @@ FontServ:: TextImage(const char *text, MFont *font, Uint8 style,
 	int ascii, i, y;
 	int bit;
 
+	/* First see if we can find it in our cache */
+	keysize = strlen(font->name)+1+8+1+strlen(text)+1;
+	key = SDL_stack_alloc(char, keysize);
+	sprintf(key, "%s:%d:%s", font->name, font->ptsize, text);
+	if (hash_find(strings, key, (const void**)&image)) {
+		return image;
+	}
+
 	switch (style) {
 		case STYLE_NORM:	bold_offset = 0;
 					break;
@@ -327,6 +378,7 @@ FontServ:: TextImage(const char *text, MFont *font, Uint8 style,
 	width = TextWidth(text, font, style);
 	if ( width == 0 ) {
 		SetError("No text to convert");
+		SDL_stack_free(key);
 		return(NULL);
 	}
 	height = (font->header)->fRectHeight;
@@ -381,17 +433,30 @@ FontServ:: TextImage(const char *text, MFont *font, Uint8 style,
 			bitmap[bit_offset++] = color;
 	}
 
-	/* Map the image and return */
+	/* Create the image */
 	image = screen->LoadImage(width, height, bitmap);
-	if (image) {
-		++text_allocated;
-	}
 	delete[] bitmap;
+
+	/* Add it to our cache */
+	keycopy = new char[keysize];
+	strcpy(keycopy, key);
+	hash_insert(strings, keycopy, image);
+	SDL_stack_free(key);
+
 	return(image);
 }
+
 void
 FontServ:: FreeText(SDL_Texture *text)
 {
-	--text_allocated;
-	screen->FreeImage(text);
+	/* We'll likely be asked for this again soon, leave it alone */
+	return;
+}
+
+void
+FontServ:: FlushCache(void)
+{
+	/* We'll flush any strings in the cache and leave the fonts around */
+	hash_destroy(strings);
+	strings = hash_create(screen, hash_hash_string, hash_keymatch_string, hash_nuke_string_texture);
 }
diff --git a/maclib/Mac_FontServ.h b/maclib/Mac_FontServ.h
index f66ea8b9..af9b6fcf 100644
--- a/maclib/Mac_FontServ.h
+++ b/maclib/Mac_FontServ.h
@@ -73,7 +73,12 @@ struct FontHdr {
                rowWords;        /* Row width of bit image in words */
 };
 
-typedef struct {
+typedef struct MFont {
+	/* Data useful for caching */
+	const char *name;
+	int ptsize;
+	struct MFont *next;
+
 	struct FontHdr *header;		/* The NFNT header! */
 
 	/* Variable-length tables */
@@ -85,6 +90,8 @@ typedef struct {
 	Mac_ResData *nfnt;
 } MFont;
 
+struct HashTable;
+
 class FontServ {
 
 public:
@@ -94,8 +101,9 @@ class FontServ {
 	FontServ(FrameBuf *screen, const char *fontfile);
 	~FontServ();
 	
-	/* The font returned by NewFont() should be delete'd */
+	/* The font returned by NewFont() should be freed with FreeFont() */
 	MFont  *NewFont(const char *fontname, int ptsize);
+	void FreeFont(MFont *font);
 
 	/* Determine the final width/height of a text block (in pixels) */
 	Uint16	TextWidth(const char *text, MFont *font, Uint8 style);
@@ -118,6 +126,8 @@ class FontServ {
 	}
 	void FreeText(SDL_Texture *text);
 
+	void FlushCache();
+
 	/* Returns NULL if everything is okay, or an error message if not */
 	char *Error(void) {
 		return(errstr);
@@ -126,7 +136,8 @@ class FontServ {
 private:
 	FrameBuf *screen;
 	Mac_Resource *fontres;
-	int text_allocated;
+	MFont *fonts;
+	HashTable *strings;
 
 	/* Useful for getting error feedback */
 	void SetError(const char *fmt, ...) {
diff --git a/main.cpp b/main.cpp
index e2b02dce..0955e4d2 100644
--- a/main.cpp
+++ b/main.cpp
@@ -448,27 +448,21 @@ int DrawText(int x, int y, const char *text, MFont *font, Uint8 style,
 /* -- Draw the current sound volume */
 static void DrawSoundLevel(void)
 {
-	static int need_init=1;
-	static MFont *geneva;
-	static char text[12];
-	static int xOff, yOff;
-
-	if ( need_init ) {
-		if ( (geneva = fontserv->NewFont("Geneva", 9)) == NULL ) {
-			error("Can't use Geneva font! -- Exiting.\n");
-			exit(255);
-		}
-		xOff = (SCREEN_WIDTH - 512) / 2;
-		yOff = (SCREEN_HEIGHT - 384) / 2;
-		need_init = 0;
-	} else {
-		DrawText(xOff+309-7, yOff+240-6, text, geneva, STYLE_BOLD,
-							0x00, 0x00, 0x00);
+	MFont *geneva;
+	char text[12];
+	int xOff, yOff;
+
+	if ( (geneva = fontserv->NewFont("Geneva", 9)) == NULL ) {
+		error("Can't use Geneva font! -- Exiting.\n");
+		exit(255);
 	}
+
+	xOff = (SCREEN_WIDTH - 512) / 2;
+	yOff = (SCREEN_HEIGHT - 384) / 2;
 	sprintf(text, "%d", gSoundLevel);
 	DrawText(xOff+309-7, yOff+240-6, text, geneva, STYLE_BOLD,
 						30000>>8, 30000>>8, 0xFF);
-	screen->Update();
+	fontserv->FreeFont(geneva);
 }	/* -- DrawSoundLevel */
 
 
@@ -576,7 +570,7 @@ void DrawMainScreen(void)
 		DrawText(wRt-sw, botDiv+42+(index*18), buffer, 
 						font, STYLE_BOLD, R, G, B);
 	}
-	delete font;
+	fontserv->FreeFont(font);
 
 	DrawText(xOff+5, botDiv+46+(10*18)+3, "Last Score: ", 
 					bigfont, STYLE_NORM, 0xFF, 0xFF, 0xFF);
@@ -584,7 +578,7 @@ void DrawMainScreen(void)
 	sw = fontserv->TextWidth("Last Score: ", bigfont, STYLE_NORM);
 	DrawText(xOff+5+sw, botDiv+46+(index*18)+3, buffer, 
 					bigfont, STYLE_NORM, 0xFF, 0xFF, 0xFF);
-	delete bigfont;
+	fontserv->FreeFont(bigfont);
 
 	/* -- Draw the Instructions */
 	offset = 34;
@@ -636,7 +630,7 @@ void DrawMainScreen(void)
 
 	DrawText(xOff+20, yOff+151, VERSION_STRING,
 				font, STYLE_NORM, 0xFF, 0xFF, 0xFF);
-	delete font;
+	fontserv->FreeFont(font);
 
 	DrawSoundLevel();
 
@@ -658,14 +652,14 @@ static void DrawKey(MPoint *pt, const char *key, const char *text, void (*callba
 		error("Can't use Geneva font! -- Exiting.\n");
 		exit(255);
 	}
+
 	screen->QueueBlit(pt->h, pt->v, gKeyIcon);
-	screen->Update();
 
 	DrawText(pt->h+14, pt->v+20, key, geneva, STYLE_BOLD, 0xFF, 0xFF, 0xFF);
 	DrawText(pt->h+13, pt->v+19, key, geneva, STYLE_BOLD, 0x00, 0x00, 0x00);
 	DrawText(pt->h+gKeyIcon->w+3, pt->v+19, text,
 					geneva, STYLE_BOLD, 0xFF, 0xFF, 0x00);
-	delete geneva;
+	fontserv->FreeFont(geneva);
 
 	buttons.Add_Button(pt->h, pt->v, gKeyIcon->w, gKeyIcon->h, callback);
 }	/* -- DrawKey */
@@ -673,30 +667,22 @@ static void DrawKey(MPoint *pt, const char *key, const char *text, void (*callba
 
 void Message(const char *message)
 {
-	static MFont *font;
-	static int xOff;
-	static char *last_message;
+	MFont *font;
+	int xOff;
 
-	if ( ! last_message ) { 	/* Initialize everything */
-		/* This was taken from the DrawMainScreen function */
-		xOff = (SCREEN_WIDTH - 512) / 2;
-
-		if ( (font = fontserv->NewFont("New York", 14)) == NULL ) {
-			error("Can't use New York(14) font! -- Exiting.\n");
-			exit(255);
-		}
-	} else {
-		DrawText(xOff, 25, last_message, font, STYLE_BOLD, 0, 0, 0);
-		delete[] last_message;
+	if (!message) {
+		return;
 	}
-	if ( message ) {
-		DrawText(xOff, 25, message, font, STYLE_BOLD, 0xCC,0xCC,0xCC);
-		last_message = new char[strlen(message)+1];
-		strcpy(last_message, message);
-	} else {
-		last_message = new char[1];
-		last_message[0] = '\0';
+
+	if ( (font = fontserv->NewFont("New York", 14)) == NULL ) {
+		error("Can't use New York(14) font! -- Exiting.\n");
+		exit(255);
 	}
-	screen->Update();
+
+	/* This was taken from the DrawMainScreen function */
+	xOff = (SCREEN_WIDTH - 512) / 2;
+	DrawText(xOff, 25, message, font, STYLE_BOLD, 0xCC,0xCC,0xCC);
+
+	fontserv->FreeFont(font);
 }
 
diff --git a/netlogic/about.cpp b/netlogic/about.cpp
index 0898459d..d48c53ae 100644
--- a/netlogic/about.cpp
+++ b/netlogic/about.cpp
@@ -191,7 +191,7 @@ void DoAbout(void)
 								text2, NOCLIP);
 				fontserv->FreeText(text1);
 				fontserv->FreeText(text2);
-				delete font;
+				fontserv->FreeFont(font);
 			}
 			screen->Update();
 			screen->Fade();
diff --git a/netlogic/game.cpp b/netlogic/game.cpp
index aa698a14..5177473d 100644
--- a/netlogic/game.cpp
+++ b/netlogic/game.cpp
@@ -327,7 +327,7 @@ void NewGame(void)
 
 	DoGameOver();
 	screen->ShowCursor();
-	delete geneva;
+	fontserv->FreeFont(geneva);
 }	/* -- NewGame */
 
 
@@ -405,6 +405,9 @@ static void NextWave(void)
 	int	NewRoids;
 	short	temp;
 
+	/* Flush the font text cache */
+	fontserv->FlushCache();
+	
 	gEnemySprite = NULL;
 
 	/* -- Initialize some variables */
@@ -592,7 +595,7 @@ static void DoGameOver(void)
 			DrawText(160, 380+i*newyork_height, buffer,
 				newyork, STYLE_NORM, 30000>>8, 30000>>8, 0xFF);
 		}
-		delete newyork;
+		fontserv->FreeFont(newyork);
 	}
 	screen->Update();
 
@@ -680,7 +683,7 @@ static void DoGameOver(void)
 				screen->Update();
 			}
 		}
-		delete newyork;
+		fontserv->FreeFont(newyork);
 		SDL_StopTextInput();
 
 		/* In case the user just pressed <Return> */
diff --git a/scores.cpp b/scores.cpp
index 2aaf1c16..41e5456c 100644
--- a/scores.cpp
+++ b/scores.cpp
@@ -156,7 +156,7 @@ int ZapHighScores(void)
 
 	/* Clean up and return */
 	screen->FreeImage(splash);
-	delete chicago;
+	fontserv->FreeFont(chicago);
 	delete dialog;
 	if ( do_clear ) {
 		memset(hScores, 0, sizeof(hScores));
@@ -252,7 +252,7 @@ int GetStartLevel(void)
 	fontserv->FreeText(text2);
 	fontserv->FreeText(text3);
 	fontserv->FreeText(text4);
-	delete chicago;
+	fontserv->FreeFont(chicago);
 	delete dialog;
 	if ( do_level ) {
 		if ( ! startlives || (startlives > 40) )