Changeset - r20544:29afe601d5e7
[Not reviewed]
master
0 3 0
frosch - 11 years ago 2013-07-06 19:00:33
frosch@openttd.org
(svn r25570) -Add: cache for ParagraphLayouts.
3 files changed with 145 insertions and 42 deletions:
0 comments (0 inline, 0 general)
src/gfx_layout.cpp
Show inline comments
 
@@ -21,6 +21,9 @@
 
#endif /* WITH_ICU */
 

	
 

	
 
/** Cache of ParagraphLayout lines. */
 
Layouter::LineCache Layouter::linecache;
 

	
 
/** Cache of Font instances. */
 
Layouter::FontColourMap Layouter::fonts[FS_END];
 

	
 
@@ -134,6 +137,8 @@ ParagraphLayout *Layouter::GetParagraphL
 
	}
 

	
 
	LEErrorCode status = LE_NO_ERROR;
 
	/* ParagraphLayout does not copy "buff", so it must stay valid.
 
	 * "runs" is copied according to the ICU source, but the documentation does not specify anything, so this might break somewhen. */
 
	return new ParagraphLayout(buff, length, &runs, NULL, NULL, NULL, _current_text_dir == TD_RTL ? UBIDI_DEFAULT_RTL : UBIDI_DEFAULT_LTR, false, status);
 
}
 

	
 
@@ -278,6 +283,14 @@ ParagraphLayout::ParagraphLayout(WChar *
 
}
 

	
 
/**
 
 * Reset the position to the start of the paragraph.
 
 */
 
void ParagraphLayout::reflow()
 
{
 
	this->buffer = this->buffer_begin;
 
}
 

	
 
/**
 
 * Construct a new line with a maximum width.
 
 * @param max_width The maximum width of the string.
 
 * @return A Line, or NULL when at the end of the paragraph.
 
@@ -300,7 +313,7 @@ ParagraphLayout::Line *ParagraphLayout::
 
	}
 

	
 
	const WChar *begin = this->buffer;
 
	WChar *last_space = NULL;
 
	const WChar *last_space = NULL;
 
	const WChar *last_char = begin;
 
	int width = 0;
 

	
 
@@ -412,62 +425,77 @@ ParagraphLayout *Layouter::GetParagraphL
 
 */
 
Layouter::Layouter(const char *str, int maxw, TextColour colour, FontSize fontsize)
 
{
 
	const CharType *buffer_last = lastof(this->buffer);
 
	CharType *buff = this->buffer;
 

	
 
	FontState state(colour, fontsize);
 
	WChar c = 0;
 

	
 
	do {
 
		Font *f = GetFont(state.fontsize, state.cur_colour);
 
		CharType *buff_begin = buff;
 
		FontMap fontMapping;
 
		/* Scan string for end of line */
 
		const char *lineend = str;
 
		for (;;) {
 
			size_t len = Utf8Decode(&c, lineend);
 
			if (c == '\0' || c == '\n') break;
 
			lineend += len;
 
		}
 

	
 
		LineCacheItem& line = GetCachedParagraphLayout(str, lineend - str, state);
 
		if (line.layout != NULL) {
 
			/* Line is in cache */
 
			str = lineend + 1;
 
			state = line.state_after;
 
			line.layout->reflow();
 
		} else {
 
			/* Line is new, layout it */
 
			const CharType *buffer_last = lastof(line.buffer);
 
			CharType *buff_begin = line.buffer;
 
			CharType *buff = buff_begin;
 
			FontMap &fontMapping = line.runs;
 
			Font *f = GetFont(state.fontsize, state.cur_colour);
 

	
 
		/*
 
		 * Go through the whole string while adding Font instances to the font map
 
		 * whenever the font changes, and convert the wide characters into a format
 
		 * usable by ParagraphLayout.
 
		 */
 
		for (; buff < buffer_last;) {
 
			c = Utf8Consume(const_cast<const char **>(&str));
 
			if (c == '\0' || c == '\n') {
 
				break;
 
			} else if (c >= SCC_BLUE && c <= SCC_BLACK) {
 
				state.SetColour((TextColour)(c - SCC_BLUE));
 
			} else if (c == SCC_PREVIOUS_COLOUR) { // Revert to the previous colour.
 
				state.SetPreviousColour();
 
			} else if (c == SCC_TINYFONT) {
 
				state.SetFontSize(FS_SMALL);
 
			} else if (c == SCC_BIGFONT) {
 
				state.SetFontSize(FS_LARGE);
 
			} else {
 
				buff += AppendToBuffer(buff, buffer_last, c);
 
				continue;
 
			/*
 
			 * Go through the whole string while adding Font instances to the font map
 
			 * whenever the font changes, and convert the wide characters into a format
 
			 * usable by ParagraphLayout.
 
			 */
 
			for (; buff < buffer_last;) {
 
				c = Utf8Consume(const_cast<const char **>(&str));
 
				if (c == '\0' || c == '\n') {
 
					break;
 
				} else if (c >= SCC_BLUE && c <= SCC_BLACK) {
 
					state.SetColour((TextColour)(c - SCC_BLUE));
 
				} else if (c == SCC_PREVIOUS_COLOUR) { // Revert to the previous colour.
 
					state.SetPreviousColour();
 
				} else if (c == SCC_TINYFONT) {
 
					state.SetFontSize(FS_SMALL);
 
				} else if (c == SCC_BIGFONT) {
 
					state.SetFontSize(FS_LARGE);
 
				} else {
 
					buff += AppendToBuffer(buff, buffer_last, c);
 
					continue;
 
				}
 

	
 
				if (!fontMapping.Contains(buff - buff_begin)) {
 
					fontMapping.Insert(buff - buff_begin, f);
 
				}
 
				f = GetFont(state.fontsize, state.cur_colour);
 
			}
 

	
 
			/* Better safe than sorry. */
 
			*buff = '\0';
 

	
 
			if (!fontMapping.Contains(buff - buff_begin)) {
 
				fontMapping.Insert(buff - buff_begin, f);
 
			}
 
			f = GetFont(state.fontsize, state.cur_colour);
 
			line.layout = GetParagraphLayout(buff_begin, buff, fontMapping);
 
			line.state_after = state;
 
		}
 

	
 
		/* Better safe than sorry. */
 
		*buff = '\0';
 

	
 
		if (!fontMapping.Contains(buff - buff_begin)) {
 
			fontMapping.Insert(buff - buff_begin, f);
 
		}
 
		ParagraphLayout *p = GetParagraphLayout(buff_begin, buff, fontMapping);
 

	
 
		/* Copy all lines into a local cache so we can reuse them later on more easily. */
 
		ParagraphLayout::Line *l;
 
		while ((l = p->nextLine(maxw)) != NULL) {
 
		while ((l = line.layout->nextLine(maxw)) != NULL) {
 
			*this->Append() = l;
 
		}
 

	
 
		delete p;
 

	
 
	} while (c != '\0' && buff < buffer_last);
 
	} while (c != '\0');
 
}
 

	
 
/**
 
@@ -507,4 +535,40 @@ void Layouter::ResetFontCache(FontSize s
 
		delete it->second;
 
	}
 
	fonts[size].Clear();
 

	
 
	/* We must reset the linecache since it references the just freed fonts */
 
	ResetLineCache();
 
}
 

	
 
/**
 
 * Get reference to cache item.
 
 * If the item does not exist yet, it is default constructed.
 
 * @param str Source string of the line (including colour and font size codes).
 
 * @param len Length of \a str in bytes (no termination).
 
 * @param state State of the font at the beginning of the line.
 
 * @return Reference to cache item.
 
 */
 
Layouter::LineCacheItem &Layouter::GetCachedParagraphLayout(const char *str, size_t len, const FontState &state)
 
{
 
	LineCacheKey key;
 
	key.state_before = state;
 
	key.str.assign(str, len);
 
	return linecache[key];
 
}
 

	
 
/**
 
 * Clear line cache.
 
 */
 
void Layouter::ResetLineCache()
 
{
 
	linecache.clear();
 
}
 

	
 
/**
 
 * Reduce the size of linecache if necessary to prevent infinite growth.
 
 */
 
void Layouter::ReduceLineCache()
 
{
 
	/* TODO LRU cache would be fancy, but not exactly necessary */
 
	if (linecache.size() > 4096) ResetLineCache();
 
}
src/gfx_layout.h
Show inline comments
 
@@ -16,6 +16,9 @@
 
#include "gfx_func.h"
 
#include "core/smallmap_type.hpp"
 

	
 
#include <map>
 
#include <string>
 

	
 
#ifdef WITH_ICU
 
#include "layout/ParagraphLayout.h"
 
#define ICU_FONTINSTANCE : public LEFontInstance
 
@@ -32,6 +35,7 @@ struct FontState {
 
	TextColour cur_colour;   ///< Current text colour.
 
	TextColour prev_colour;  ///< Text colour from before the last colour switch.
 

	
 
	FontState() : fontsize(FS_END), cur_colour(TC_INVALID), prev_colour(TC_INVALID) {}
 
	FontState(TextColour colour, FontSize fontsize) : fontsize(fontsize), cur_colour(colour), prev_colour(colour) {}
 

	
 
	/**
 
@@ -143,10 +147,11 @@ public:
 
	};
 

	
 
	const WChar *buffer_begin; ///< Begin of the buffer.
 
	WChar *buffer;             ///< The current location in the buffer.
 
	const WChar *buffer;       ///< The current location in the buffer.
 
	FontMap &runs;             ///< The fonts we have to use for this paragraph.
 

	
 
	ParagraphLayout(WChar *buffer, int length, FontMap &runs);
 
	void reflow();
 
	Line *nextLine(int max_width);
 
};
 
#endif /* !WITH_ICU */
 
@@ -166,7 +171,36 @@ class Layouter : public AutoDeleteSmallV
 
	size_t AppendToBuffer(CharType *buff, const CharType *buffer_last, WChar c);
 
	ParagraphLayout *GetParagraphLayout(CharType *buff, CharType *buff_end, FontMap &fontMapping);
 

	
 
	CharType buffer[DRAW_STRING_BUFFER]; ///< Buffer for the text that is going to be drawn.
 
	/** Key into the linecache */
 
	struct LineCacheKey {
 
		FontState state_before;  ///< Font state at the beginning of the line.
 
		std::string str;         ///< Source string of the line (including colour and font size codes).
 

	
 
		/** Comparison operator for std::map */
 
		bool operator<(const LineCacheKey &other) const
 
		{
 
			if (this->state_before.fontsize != other.state_before.fontsize) return this->state_before.fontsize < other.state_before.fontsize;
 
			if (this->state_before.cur_colour != other.state_before.cur_colour) return this->state_before.cur_colour < other.state_before.cur_colour;
 
			if (this->state_before.prev_colour != other.state_before.prev_colour) return this->state_before.prev_colour < other.state_before.prev_colour;
 
			return this->str < other.str;
 
		}
 
	};
 
	/** Item in the linecache */
 
	struct LineCacheItem {
 
		/* Stuff that cannot be freed until the ParagraphLayout is freed */
 
		CharType buffer[DRAW_STRING_BUFFER]; ///< Accessed by both ICU's and our ParagraphLayout::nextLine.
 
		FontMap runs;                        ///< Accessed by our ParagraphLayout::nextLine.
 

	
 
		FontState state_after;   ///< Font state after the line.
 
		ParagraphLayout *layout; ///< Layout of the line.
 

	
 
		LineCacheItem() : layout(NULL) {}
 
		~LineCacheItem() { delete layout; }
 
	};
 
	typedef std::map<LineCacheKey, LineCacheItem> LineCache;
 
	static LineCache linecache;
 

	
 
	static LineCacheItem &GetCachedParagraphLayout(const char *str, size_t len, const FontState &state);
 

	
 
	typedef SmallMap<TextColour, Font *> FontColourMap;
 
	static FontColourMap fonts[FS_END];
 
@@ -177,6 +211,8 @@ public:
 
	Dimension GetBounds();
 

	
 
	static void ResetFontCache(FontSize size);
 
	static void ResetLineCache();
 
	static void ReduceLineCache();
 
};
 

	
 
#endif /* GFX_LAYOUT_H */
src/openttd.cpp
Show inline comments
 
@@ -61,6 +61,7 @@
 
#include "game/game_config.hpp"
 
#include "town.h"
 
#include "subsidy_func.h"
 
#include "gfx_layout.h"
 

	
 

	
 
#include "linkgraph/linkgraphschedule.h"
 
@@ -1318,6 +1319,8 @@ void StateGameLoop()
 

	
 
	ClearStorageChanges(false);
 

	
 
	Layouter::ReduceLineCache();
 

	
 
	if (_game_mode == GM_EDITOR) {
 
		RunTileLoop();
 
		CallVehicleTicks();
0 comments (0 inline, 0 general)