瀏覽代碼

Static allocator (not tested)
TextData usable with custom allocators

Marko Pintera 10 年之前
父節點
當前提交
babed02408

+ 94 - 41
BansheeCore/Include/BsTextData.h

@@ -10,9 +10,9 @@ namespace BansheeEngine
 	 * @brief	This object takes as input a string, a font and optionally some constraints (like word wrap)
 	 *			and outputs a set of character data you may use for rendering or metrics.
 	 */
-	class TextData
+	class TextDataBase
 	{
-	private:
+	protected:
 		/**
 		 * @brief	Represents a single word as a set of characters, or optionally just a blank space
 		 *			of a certain length.
@@ -186,7 +186,7 @@ namespace BansheeEngine
 			 */
 			bool hasNewlineChar() const { return mHasNewline; }
 		private:
-			friend class TextData;
+			friend class TextDataBase;
 
 			/**
 			 * @brief	Appends a new character to the line.
@@ -213,7 +213,7 @@ namespace BansheeEngine
 			/**
 			 * @brief	Initializes the line. Must be called after construction.
 			 */
-			void init(TextData* textData);
+			void init(TextDataBase* textData);
 
 			/**
 			 * @brief	Finalizes the line. Do not add new characters/words after a line has
@@ -241,7 +241,7 @@ namespace BansheeEngine
 			void calculateBounds();
 
 		private:
-			TextData* mTextData;
+			TextDataBase* mTextData;
 			UINT32 mWordsStart, mWordsEnd;
 
 			UINT32 mWidth;
@@ -261,9 +261,9 @@ namespace BansheeEngine
 		 *
 		 *			After this object is constructed you may call various getter methods to get needed information.
 		 */
-		BS_CORE_EXPORT TextData(const WString& text, const HFont& font, UINT32 fontSize, 
+		BS_CORE_EXPORT TextDataBase(const WString& text, const HFont& font, UINT32 fontSize,
 			UINT32 width = 0, UINT32 height = 0, bool wordWrap = false, bool wordBreak = true);
-		BS_CORE_EXPORT ~TextData();
+		BS_CORE_EXPORT virtual ~TextDataBase() { }
 
 		/**
 		 * @brief	Returns the number of lines that were generated.
@@ -305,6 +305,20 @@ namespace BansheeEngine
 		 */
 		BS_CORE_EXPORT UINT32 getHeight() const;
 
+	protected:
+		/**
+		 * @brief	Copies internally stored data in temporary buffers to a persistent buffer.
+		 *
+		 * @param	text			Text originally used for creating the internal temporary buffer data.
+		 * @param	buffer			Memory location to copy the data to. If null then no data will be copied
+		 *							and the parameter \p size will contain the required buffer size.
+		 * @param	size			Size of the provided memory buffer, or if the buffer is null, this will
+		 *							contain the required buffer size after method exists.
+		 * @param	freeTemporary	If true the internal temporary data will be freed after copying.
+		 *
+		 * @note	Must be called after text data has been constructed and is in the temporary buffers.
+		 */
+		BS_CORE_EXPORT void generatePersistentData(const WString& text, UINT8* buffer, UINT32& size, bool freeTemporary = true);
 	private:
 		friend class TextLine;
 
@@ -331,7 +345,7 @@ namespace BansheeEngine
 		 */
 		const TextWord& getWord(UINT32 idx) const { return mWords[idx]; }
 
-	private:
+	protected:
 		const CHAR_DESC** mChars;
 		UINT32 mNumChars;
 
@@ -344,57 +358,96 @@ namespace BansheeEngine
 		PageInfo* mPageInfos;
 		UINT32 mNumPageInfos;
 
-		void* mData;
-
 		HFont mFont;
 		const FontData* mFontData;
 
 		// Static buffers used to reduce runtime memory allocation
-	private:
-		static BS_THREADLOCAL bool BuffersInitialized;
+	protected:
+		/**
+		 * @brief	Stores per-thread memory buffers used to reduce memory allocation.
+		 */
+		// Note: I could replace this with the global frame allocator to avoid the extra logic
+		struct BufferData
+		{
+			BufferData();
+			~BufferData();
 
-		static BS_THREADLOCAL TextWord* WordBuffer;
-		static BS_THREADLOCAL UINT32 WordBufferSize;
-		static BS_THREADLOCAL UINT32 NextFreeWord;
+			/**
+			 * @brief	Allocates a new word and adds it to the buffer. Returns index of the word
+			 *			in the word buffer.
+			 *
+			 * @param	spacer	Specify true if the word is only to contain spaces. (Spaces are considered
+			 *					a special type of word).
+			 */
+			UINT32 allocWord(bool spacer);
+
+			/**
+			 * @brief	Allocates a new line and adds it to the buffer. Returns index of the line
+			 *			in the line buffer.
+			 */
+			UINT32 allocLine(TextDataBase* textData);
 
-		static BS_THREADLOCAL TextLine* LineBuffer;
-		static BS_THREADLOCAL UINT32 LineBufferSize;
-		static BS_THREADLOCAL UINT32 NextFreeLine;
+			/**
+			 * @brief	Increments the count of characters for the referenced page, and optionally
+			 *			creates page info if it doesn't already exist.
+			 */
+			void addCharToPage(UINT32 page, const FontData& fontData);
 
-		static BS_THREADLOCAL PageInfo* PageBuffer;
-		static BS_THREADLOCAL UINT32 PageBufferSize;
-		static BS_THREADLOCAL UINT32 NextFreePageInfo;
+			/**
+			 * @brief	Resets all allocation counters, but doesn't actually release memory.
+			 */
+			void deallocAll();
+
+			TextWord* WordBuffer;
+			UINT32 WordBufferSize;
+			UINT32 NextFreeWord;
+
+			TextLine* LineBuffer;
+			UINT32 LineBufferSize;
+			UINT32 NextFreeLine;
+
+			PageInfo* PageBuffer;
+			UINT32 PageBufferSize;
+			UINT32 NextFreePageInfo;
+		};
+
+		static BS_THREADLOCAL BufferData* MemBuffer;
 
 		/**
 		 * @brief	Allocates an initial set of buffers that will be reused while parsing
 		 *			text data.
 		 */
 		static void initAlloc();
+	};
 
+	/**
+	 * @copydoc	TextDataBase
+	 */
+	template<class Alloc = GenAlloc>
+	class TextData : public TextDataBase
+	{
+	public:
 		/**
-		 * @brief	Allocates a new word and adds it to the buffer. Returns index of the word
-		 *			in the word buffer.
-		 *
-		 * @param	spacer	Specify true if the word is only to contain spaces. (Spaces are considered
-		 *					a special type of word).
+		 * @copydoc	TextDataBase::TextDataBase
 		 */
-		static UINT32 allocWord(bool spacer);
+		TextData(const WString& text, const HFont& font, UINT32 fontSize,
+			UINT32 width = 0, UINT32 height = 0, bool wordWrap = false, bool wordBreak = true)
+			:TextDataBase(text, font, fontSize, width, height, wordWrap, wordBreak), mData(nullptr)
+		{
+			UINT32 totalBufferSize = 0;
+			generatePersistentData(text, nullptr, totalBufferSize);
 
-		/**
-		 * @brief	Allocates a new line and adds it to the buffer. Returns index of the line
-		 *			in the line buffer.
-		 */
-		static UINT32 allocLine(TextData* textData);
+			mData = (UINT8*)bs_alloc<GenAlloc>(totalBufferSize);
+			generatePersistentData(text, (UINT8*)mData, totalBufferSize);
+		}
 
-		/**
-		 * @brief	Resets all allocation counters.
-		 */
-		static void deallocAll();
+		~TextData()
+		{
+			if (mData != nullptr)
+				bs_free<Alloc>(mData);
+		}
 
-		/**
-		 * @brief	Increments the count of characters for the referenced page, and optionally
-		 *			creates page info if it doesn't already exist.
-		 */
-		static void addCharToPage(UINT32 page, const FontData& fontData);
+	private:
+		UINT8* mData;
 	};
 }

+ 92 - 95
BansheeCore/Source/BsTextData.cpp

@@ -7,7 +7,7 @@ namespace BansheeEngine
 {
 	const int SPACE_CHAR = 32;
 
-	void TextData::TextWord::init(bool spacer)
+	void TextDataBase::TextWord::init(bool spacer)
 	{
 		mWidth = mHeight = 0;
 		mSpacer = spacer;
@@ -18,7 +18,7 @@ namespace BansheeEngine
 	}
 
 	// Assumes charIdx is an index right after last char in the list (if any). All chars need to be sequential.
-	UINT32 TextData::TextWord::addChar(UINT32 charIdx, const CHAR_DESC& desc)
+	UINT32 TextDataBase::TextWord::addChar(UINT32 charIdx, const CHAR_DESC& desc)
 	{
 		UINT32 charWidth = calcCharWidth(mLastChar, desc);
 
@@ -35,12 +35,12 @@ namespace BansheeEngine
 		return charWidth;
 	}
 
-	UINT32 TextData::TextWord::calcWidthWithChar(const CHAR_DESC& desc)
+	UINT32 TextDataBase::TextWord::calcWidthWithChar(const CHAR_DESC& desc)
 	{
 		return mWidth + calcCharWidth(mLastChar, desc);
 	}
 
-	UINT32 TextData::TextWord::calcCharWidth(const CHAR_DESC* prevDesc, const CHAR_DESC& desc)
+	UINT32 TextDataBase::TextWord::calcCharWidth(const CHAR_DESC* prevDesc, const CHAR_DESC& desc)
 	{
 		UINT32 charWidth = desc.xAdvance;
 		if (prevDesc != nullptr)
@@ -61,14 +61,14 @@ namespace BansheeEngine
 		return charWidth;
 	}
 
-	void TextData::TextWord::addSpace(UINT32 spaceWidth)
+	void TextDataBase::TextWord::addSpace(UINT32 spaceWidth)
 	{
 		mSpaceWidth += spaceWidth;
 		mWidth = mSpaceWidth;
 		mHeight = 0;
 	}
 
-	void TextData::TextLine::init(TextData* textData)
+	void TextDataBase::TextLine::init(TextDataBase* textData)
 	{
 		mWidth = 0;
 		mHeight = 0;
@@ -77,50 +77,50 @@ namespace BansheeEngine
 		mWordsStart = mWordsEnd = 0;
 	}
 
-	void TextData::TextLine::finalize(bool hasNewlineChar)
+	void TextDataBase::TextLine::finalize(bool hasNewlineChar)
 	{
 		mHasNewline = hasNewlineChar;
 	}
 
-	void TextData::TextLine::add(UINT32 charIdx, const CHAR_DESC& charDesc)
+	void TextDataBase::TextLine::add(UINT32 charIdx, const CHAR_DESC& charDesc)
 	{
 		UINT32 charWidth = 0;
 		if(mIsEmpty)
 		{
-			mWordsStart = mWordsEnd = allocWord(false);
+			mWordsStart = mWordsEnd = MemBuffer->allocWord(false);
 			mIsEmpty = false;
 		}
 		else
 		{
-			if(TextData::WordBuffer[mWordsEnd].isSpacer())
-				mWordsEnd = allocWord(false);
+			if(MemBuffer->WordBuffer[mWordsEnd].isSpacer())
+				mWordsEnd = MemBuffer->allocWord(false);
 		}
 
-		TextWord& lastWord = TextData::WordBuffer[mWordsEnd];
+		TextWord& lastWord = MemBuffer->WordBuffer[mWordsEnd];
 		charWidth = lastWord.addChar(charIdx, charDesc);
 
 		mWidth += charWidth;
 		mHeight = std::max(mHeight, lastWord.getHeight());
 	}
 
-	void TextData::TextLine::addSpace()
+	void TextDataBase::TextLine::addSpace()
 	{
 		if(mIsEmpty)
 		{
-			mWordsStart = mWordsEnd = allocWord(true);
+			mWordsStart = mWordsEnd = MemBuffer->allocWord(true);
 			mIsEmpty = false;
 		}
 		else
-			mWordsEnd = allocWord(true); // Each space is counted as its own word, to make certain operations easier
+			mWordsEnd = MemBuffer->allocWord(true); // Each space is counted as its own word, to make certain operations easier
 
-		TextWord& lastWord = TextData::WordBuffer[mWordsEnd];
+		TextWord& lastWord = MemBuffer->WordBuffer[mWordsEnd];
 		lastWord.addSpace(mTextData->getSpaceWidth());
 
 		mWidth += mTextData->getSpaceWidth();
 	}
 
 	// Assumes wordIdx is an index right after last word in the list (if any). All words need to be sequential.
-	void TextData::TextLine::addWord(UINT32 wordIdx, const TextWord& word)
+	void TextDataBase::TextLine::addWord(UINT32 wordIdx, const TextWord& word)
 	{
 		if(mIsEmpty)
 		{
@@ -134,7 +134,7 @@ namespace BansheeEngine
 		mHeight = std::max(mHeight, word.getHeight());
 	}
 
-	UINT32 TextData::TextLine::removeLastWord()
+	UINT32 TextDataBase::TextLine::removeLastWord()
 	{
 		if(mIsEmpty)
 		{
@@ -154,7 +154,7 @@ namespace BansheeEngine
 		return lastWord;
 	}
 
-	UINT32 TextData::TextLine::calcWidthWithChar(const CHAR_DESC& desc, bool space)
+	UINT32 TextDataBase::TextLine::calcWidthWithChar(const CHAR_DESC& desc, bool space)
 	{
 		UINT32 charWidth = 0;
 
@@ -165,7 +165,7 @@ namespace BansheeEngine
 			UINT32 word = mWordsEnd;
 			if (!mIsEmpty)
 			{
-				TextWord& lastWord = TextData::WordBuffer[mWordsEnd];
+				TextWord& lastWord = MemBuffer->WordBuffer[mWordsEnd];
 				if (lastWord.isSpacer())
 					charWidth = TextWord::calcCharWidth(nullptr, desc);
 				else
@@ -180,12 +180,12 @@ namespace BansheeEngine
 		return mWidth + charWidth;
 	}
 
-	bool TextData::TextLine::isAtWordBoundary() const
+	bool TextDataBase::TextLine::isAtWordBoundary() const
 	{
-		return mIsEmpty || TextData::WordBuffer[mWordsEnd].isSpacer();
+		return mIsEmpty || MemBuffer->WordBuffer[mWordsEnd].isSpacer();
 	}
 
-	UINT32 TextData::TextLine::fillBuffer(UINT32 page, Vector2* vertices, Vector2* uvs, UINT32* indexes, UINT32 offset, UINT32 size) const
+	UINT32 TextDataBase::TextLine::fillBuffer(UINT32 page, Vector2* vertices, Vector2* uvs, UINT32* indexes, UINT32 offset, UINT32 size) const
 	{
 		UINT32 numQuads = 0;
 
@@ -310,7 +310,7 @@ namespace BansheeEngine
 		return numQuads;
 	}
 
-	UINT32 TextData::TextLine::getNumChars() const
+	UINT32 TextDataBase::TextLine::getNumChars() const
 	{
 		if(mIsEmpty)
 			return 0;
@@ -318,7 +318,7 @@ namespace BansheeEngine
 		UINT32 numChars = 0;
 		for(UINT32 i = mWordsStart; i <= mWordsEnd; i++)
 		{
-			TextWord& word = TextData::WordBuffer[i];
+			TextWord& word = MemBuffer->WordBuffer[i];
 
 			if(word.isSpacer())
 				numChars++;
@@ -329,7 +329,7 @@ namespace BansheeEngine
 		return numChars;
 	}
 
-	void TextData::TextLine::calculateBounds()
+	void TextDataBase::TextLine::calculateBounds()
 	{
 		mWidth = 0;
 		mHeight = 0;
@@ -339,16 +339,16 @@ namespace BansheeEngine
 
 		for(UINT32 i = mWordsStart; i <= mWordsEnd; i++)
 		{
-			TextWord& word = TextData::WordBuffer[i];
+			TextWord& word = MemBuffer->WordBuffer[i];
 
 			mWidth += word.getWidth();
 			mHeight = std::max(mHeight, word.getHeight());
 		}
 	}
 
-	TextData::TextData(const WString& text, const HFont& font, UINT32 fontSize, UINT32 width, UINT32 height, bool wordWrap, bool wordBreak)
+	TextDataBase::TextDataBase(const WString& text, const HFont& font, UINT32 fontSize, UINT32 width, UINT32 height, bool wordWrap, bool wordBreak)
 		:mFont(font), mChars(nullptr), mFontData(nullptr),
-		mNumChars(0), mWords(nullptr), mNumWords(0), mLines(nullptr), mNumLines(0), mPageInfos(nullptr), mNumPageInfos(0), mData(nullptr)
+		mNumChars(0), mWords(nullptr), mNumWords(0), mLines(nullptr), mNumLines(0), mPageInfos(nullptr), mNumPageInfos(0)
 	{
 		// In order to reduce number of memory allocations algorithm first calculates data into temporary buffers and then copies the results
 		initAlloc();
@@ -370,7 +370,7 @@ namespace BansheeEngine
 		bool widthIsLimited = width > 0;
 		mFont = font;
 
-		UINT32 curLineIdx = allocLine(this);
+		UINT32 curLineIdx = MemBuffer->allocLine(this);
 		UINT32 curHeight = mFontData->fontDesc.lineHeight;
 		UINT32 charIdx = 0;
 
@@ -382,14 +382,14 @@ namespace BansheeEngine
 			UINT32 charId = text[charIdx];
 			const CHAR_DESC& charDesc = mFontData->getCharDesc(charId);
 
-			TextLine* curLine = &LineBuffer[curLineIdx];
+			TextLine* curLine = &MemBuffer->LineBuffer[curLineIdx];
 
 			if(text[charIdx] == '\n')
 			{
 				curLine->finalize(true);
 
-				curLineIdx = allocLine(this);
-				curLine = &LineBuffer[curLineIdx];
+				curLineIdx = MemBuffer->allocLine(this);
+				curLine = &MemBuffer->LineBuffer[curLineIdx];
 
 				curHeight += mFontData->fontDesc.lineHeight;
 
@@ -406,15 +406,15 @@ namespace BansheeEngine
 					if (!atWordBoundary) // Need to break word into multiple pieces, or move it to next line
 					{
 						UINT32 lastWordIdx = curLine->removeLastWord();
-						TextWord& lastWord = WordBuffer[lastWordIdx];
+						TextWord& lastWord = MemBuffer->WordBuffer[lastWordIdx];
 
 						bool wordFits = lastWord.calcWidthWithChar(charDesc) <= width;
 						if (wordFits)
 						{
 							curLine->finalize(false);
 
-							curLineIdx = allocLine(this);
-							curLine = &LineBuffer[curLineIdx];
+							curLineIdx = MemBuffer->allocLine(this);
+							curLine = &MemBuffer->LineBuffer[curLineIdx];
 
 							curHeight += mFontData->fontDesc.lineHeight;
 
@@ -427,8 +427,8 @@ namespace BansheeEngine
 								curLine->addWord(lastWordIdx, lastWord);
 								curLine->finalize(false);
 
-								curLineIdx = allocLine(this);
-								curLine = &LineBuffer[curLineIdx];
+								curLineIdx = MemBuffer->allocLine(this);
+								curLine = &MemBuffer->LineBuffer[curLineIdx];
 
 								curHeight += mFontData->fontDesc.lineHeight;
 							}
@@ -438,8 +438,8 @@ namespace BansheeEngine
 								{
 									curLine->finalize(false);
 
-									curLineIdx = allocLine(this);
-									curLine = &LineBuffer[curLineIdx];
+									curLineIdx = MemBuffer->allocLine(this);
+									curLine = &MemBuffer->LineBuffer[curLineIdx];
 
 									curHeight += mFontData->fontDesc.lineHeight;
 								}
@@ -452,8 +452,8 @@ namespace BansheeEngine
 					{
 						curLine->finalize(false);
 
-						curLineIdx = allocLine(this);
-						curLine = &LineBuffer[curLineIdx];
+						curLineIdx = MemBuffer->allocLine(this);
+						curLine = &MemBuffer->LineBuffer[curLineIdx];
 
 						curHeight += mFontData->fontDesc.lineHeight;
 					}
@@ -463,37 +463,43 @@ namespace BansheeEngine
 			if(charId != SPACE_CHAR)
 			{
 				curLine->add(charIdx, charDesc);
-				addCharToPage(charDesc.page, *mFontData);
+				MemBuffer->addCharToPage(charDesc.page, *mFontData);
 			}
 			else
 			{
 				curLine->addSpace();
-				addCharToPage(0, *mFontData);
+				MemBuffer->addCharToPage(0, *mFontData);
 			}
 
 			charIdx++;
 		}
 
-		LineBuffer[curLineIdx].finalize(true);
+		MemBuffer->LineBuffer[curLineIdx].finalize(true);
 
 		// Now that we have all the data we need, allocate the permanent buffers and copy the data
 		mNumChars = (UINT32)text.size();
-		mNumWords = NextFreeWord;
-		mNumLines = NextFreeLine;
-		mNumPageInfos = NextFreePageInfo;
+		mNumWords = MemBuffer->NextFreeWord;
+		mNumLines = MemBuffer->NextFreeLine;
+		mNumPageInfos = MemBuffer->NextFreePageInfo;
+	}
 
+	void TextDataBase::generatePersistentData(const WString& text, UINT8* buffer, UINT32& size, bool freeTemporary)
+	{
 		UINT32 charArraySize = mNumChars * sizeof(const CHAR_DESC*);
 		UINT32 wordArraySize = mNumWords * sizeof(TextWord);
 		UINT32 lineArraySize = mNumLines * sizeof(TextLine);
 		UINT32 pageInfoArraySize = mNumPageInfos * sizeof(PageInfo);
 
-		UINT32 totalBufferSize = charArraySize + wordArraySize + lineArraySize + pageInfoArraySize;
-		mData = bs_alloc(totalBufferSize);
+		if (buffer == nullptr)
+		{
+			size = charArraySize + wordArraySize + lineArraySize + pageInfoArraySize;;
+			return;
+		}
 
-		UINT8* dataPtr = (UINT8*)mData;
+		UINT8* dataPtr = (UINT8*)buffer;
 		mChars = (const CHAR_DESC**)dataPtr;
 
-		for(UINT32 i = 0; i < mNumChars; i++)
+		for (UINT32 i = 0; i < mNumChars; i++)
 		{
 			UINT32 charId = text[i];
 			const CHAR_DESC& charDesc = mFontData->getCharDesc(charId);
@@ -503,76 +509,67 @@ namespace BansheeEngine
 
 		dataPtr += charArraySize;
 		mWords = (TextWord*)dataPtr;
-		memcpy(mWords, &WordBuffer[0], wordArraySize);
+		memcpy(mWords, &MemBuffer->WordBuffer[0], wordArraySize);
 
 		dataPtr += wordArraySize;
 		mLines = (TextLine*)dataPtr;
-		memcpy(mLines, &LineBuffer[0], lineArraySize);
+		memcpy(mLines, &MemBuffer->LineBuffer[0], lineArraySize);
 
 		dataPtr += lineArraySize;
 		mPageInfos = (PageInfo*)dataPtr;
-		memcpy(mPageInfos, &PageBuffer[0], pageInfoArraySize);
-
-		TextData::deallocAll();
-	}
+		memcpy(mPageInfos, &MemBuffer->PageBuffer[0], pageInfoArraySize);
 
-	TextData::~TextData()
-	{
-		if(mData != nullptr)
-			bs_free(mData);
+		if (freeTemporary)
+			MemBuffer->deallocAll();
 	}
 
-	const HTexture& TextData::getTextureForPage(UINT32 page) const 
+	const HTexture& TextDataBase::getTextureForPage(UINT32 page) const 
 	{ 
 		return mFontData->texturePages[page]; 
 	}
 
-	INT32 TextData::getBaselineOffset() const 
+	INT32 TextDataBase::getBaselineOffset() const 
 	{ 
 		return mFontData->fontDesc.baselineOffset; 
 	}
 
-	UINT32 TextData::getLineHeight() const 
+	UINT32 TextDataBase::getLineHeight() const 
 	{ 
 		return mFontData->fontDesc.lineHeight; 
 	}
 
-	UINT32 TextData::getSpaceWidth() const 
+	UINT32 TextDataBase::getSpaceWidth() const 
 	{ 
 		return mFontData->fontDesc.spaceWidth; 
 	}
 
-	bool TextData::BuffersInitialized = false;
-
-	TextData::TextWord* TextData::WordBuffer = nullptr;
-	UINT32 TextData::NextFreeWord = 0;
-	UINT32 TextData::WordBufferSize = 0;
-
-	TextData::TextLine* TextData::LineBuffer = nullptr;
-	UINT32 TextData::NextFreeLine = 0;
-	UINT32 TextData::LineBufferSize = 0;
+	void TextDataBase::initAlloc()
+	{
+		if (MemBuffer == nullptr)
+			MemBuffer = bs_new<BufferData>();
+	}
 
-	TextData::PageInfo* TextData::PageBuffer = nullptr;
-	UINT32 TextData::NextFreePageInfo = 0;
-	UINT32 TextData::PageBufferSize = 0;
+	TextDataBase::BufferData* TextDataBase::MemBuffer = nullptr;
 
-	void TextData::initAlloc()
+	TextDataBase::BufferData::BufferData()
 	{
-		if(!BuffersInitialized)
-		{
-			WordBufferSize = 2000;
-			LineBufferSize = 500;
-			PageBufferSize = 20;
+		WordBufferSize = 2000;
+		LineBufferSize = 500;
+		PageBufferSize = 20;
 
-			WordBuffer = bs_newN<TextWord>(WordBufferSize);
-			LineBuffer = bs_newN<TextLine>(LineBufferSize);
-			PageBuffer = bs_newN<PageInfo>(PageBufferSize);
+		WordBuffer = bs_newN<TextWord>(WordBufferSize);
+		LineBuffer = bs_newN<TextLine>(LineBufferSize);
+		PageBuffer = bs_newN<PageInfo>(PageBufferSize);
+	}
 
-			BuffersInitialized = true;
-		}
+	TextDataBase::BufferData::~BufferData()
+	{
+		bs_deleteN(WordBuffer, WordBufferSize);
+		bs_deleteN(LineBuffer, LineBufferSize);
+		bs_deleteN(PageBuffer, PageBufferSize);
 	}
 
-	UINT32 TextData::allocWord(bool spacer)
+	UINT32 TextDataBase::BufferData::allocWord(bool spacer)
 	{
 		if(NextFreeWord >= WordBufferSize)
 		{
@@ -590,7 +587,7 @@ namespace BansheeEngine
 		return NextFreeWord++;
 	}
 
-	UINT32 TextData::allocLine(TextData* textData)
+	UINT32 TextDataBase::BufferData::allocLine(TextDataBase* textData)
 	{
 		if(NextFreeLine >= LineBufferSize)
 		{
@@ -608,14 +605,14 @@ namespace BansheeEngine
 		return NextFreeLine++;
 	}
 
-	void TextData::deallocAll()
+	void TextDataBase::BufferData::deallocAll()
 	{
 		NextFreeWord = 0;
 		NextFreeLine = 0;
 		NextFreePageInfo = 0;
 	}
 
-	void TextData::addCharToPage(UINT32 page, const FontData& fontData)
+	void TextDataBase::BufferData::addCharToPage(UINT32 page, const FontData& fontData)
 	{
 		if(NextFreePageInfo >= PageBufferSize)
 		{
@@ -638,7 +635,7 @@ namespace BansheeEngine
 		PageBuffer[page].numQuads++;
 	}
 
-	UINT32 TextData::getWidth() const
+	UINT32 TextDataBase::getWidth() const
 	{
 		UINT32 width = 0;
 
@@ -648,7 +645,7 @@ namespace BansheeEngine
 		return width;
 	}
 
-	UINT32 TextData::getHeight() const
+	UINT32 TextDataBase::getHeight() const
 	{
 		UINT32 height = 0;
 

+ 3 - 3
BansheeEngine/Include/BsTextSprite.h

@@ -75,7 +75,7 @@ namespace BansheeEngine
 		 * @param	horzAlign	Specifies how is text horizontally aligned within its bounds.
 		 * @param	vertAlign	Specifies how is text vertically aligned within its bounds.
 		 */
-		static Vector<Vector2I> getAlignmentOffsets(const TextData& textData, 
+		static Vector<Vector2I> getAlignmentOffsets(const TextDataBase& textData, 
 			UINT32 width, UINT32 height, TextHorzAlign horzAlign, TextVertAlign vertAlign);
 
 		/**
@@ -96,7 +96,7 @@ namespace BansheeEngine
 		 *
 		 * @returns	Number of generated quads.
 		 */
-		static UINT32 genTextQuads(UINT32 page, const TextData& textData, UINT32 width, UINT32 height, 
+		static UINT32 genTextQuads(UINT32 page, const TextDataBase& textData, UINT32 width, UINT32 height, 
 			TextHorzAlign horzAlign, TextVertAlign vertAlign, SpriteAnchor anchor, Vector2* vertices, Vector2* uv, UINT32* indices, 
 			UINT32 bufferSizeQuads);
 
@@ -116,7 +116,7 @@ namespace BansheeEngine
 		 *
 		 * @returns	Number of generated quads.
 		 */
-		static UINT32 genTextQuads(const TextData& textData, UINT32 width, UINT32 height, 
+		static UINT32 genTextQuads(const TextDataBase& textData, UINT32 width, UINT32 height,
 			TextHorzAlign horzAlign, TextVertAlign vertAlign, SpriteAnchor anchor, Vector2* vertices, Vector2* uv, UINT32* indices, 
 			UINT32 bufferSizeQuads);
 	};

+ 5 - 1
BansheeEngine/Source/BsGUIHelper.cpp

@@ -41,10 +41,14 @@ namespace BansheeEngine
 
 		if(style.font != nullptr)
 		{
-			TextData textData(text, style.font, style.fontSize, wordWrapWidth, 0, style.wordWrap);
+			bs_frame_mark();
+
+			TextData<FrameAlloc> textData(text, style.font, style.fontSize, wordWrapWidth, 0, style.wordWrap);
 
 			contentWidth += textData.getWidth();
 			contentHeight += textData.getNumLines() * textData.getLineHeight(); 
+
+			bs_frame_clear();
 		}
 
 		return Vector2I(contentWidth, contentHeight);

+ 35 - 31
BansheeEngine/Source/BsGUIInputTool.cpp

@@ -20,50 +20,54 @@ namespace BansheeEngine
 
 		mLineDescs.clear();
 
-		TextData textData(mTextDesc.text, mTextDesc.font, mTextDesc.fontSize, 
-			mTextDesc.width, mTextDesc.height, mTextDesc.wordWrap, mTextDesc.wordBreak);
+		bs_frame_mark();
+		{
+			TextData<FrameAlloc> textData(mTextDesc.text, mTextDesc.font, mTextDesc.fontSize,
+				mTextDesc.width, mTextDesc.height, mTextDesc.wordWrap, mTextDesc.wordBreak);
 
-		UINT32 numLines = textData.getNumLines();
-		UINT32 numPages = textData.getNumPages();
+			UINT32 numLines = textData.getNumLines();
+			UINT32 numPages = textData.getNumPages();
 
-		mNumQuads = 0;
-		for(UINT32 i = 0; i < numPages; i++)
-			mNumQuads += textData.getNumQuadsForPage(i);
+			mNumQuads = 0;
+			for (UINT32 i = 0; i < numPages; i++)
+				mNumQuads += textData.getNumQuadsForPage(i);
 
-		if(mQuads != nullptr)
-			bs_delete<ScratchAlloc>(mQuads);
+			if (mQuads != nullptr)
+				bs_delete<ScratchAlloc>(mQuads);
 
-		mQuads = bs_newN<Vector2, ScratchAlloc>(mNumQuads * 4);
+			mQuads = bs_newN<Vector2, ScratchAlloc>(mNumQuads * 4);
 
-		TextSprite::genTextQuads(textData, mTextDesc.width, mTextDesc.height, mTextDesc.horzAlign, mTextDesc.vertAlign, mTextDesc.anchor, 
-			mQuads, nullptr, nullptr, mNumQuads);
+			TextSprite::genTextQuads(textData, mTextDesc.width, mTextDesc.height, mTextDesc.horzAlign, mTextDesc.vertAlign, mTextDesc.anchor,
+				mQuads, nullptr, nullptr, mNumQuads);
 
-		UINT32 numVerts = mNumQuads * 4;
+			UINT32 numVerts = mNumQuads * 4;
 
-		// Store cached line data
-		UINT32 curCharIdx = 0;
-		UINT32 curLineIdx = 0;
-		Vector<Vector2I> alignmentOffsets = TextSprite::getAlignmentOffsets(textData, mTextDesc.width, 
-			mTextDesc.height, mTextDesc.horzAlign, mTextDesc.vertAlign);
+			// Store cached line data
+			UINT32 curCharIdx = 0;
+			UINT32 curLineIdx = 0;
+			Vector<Vector2I> alignmentOffsets = TextSprite::getAlignmentOffsets(textData, mTextDesc.width,
+				mTextDesc.height, mTextDesc.horzAlign, mTextDesc.vertAlign);
 
-		for(UINT32 i = 0; i < numLines; i++)
-		{
-			const TextData::TextLine& line = textData.getLine(i);
+			for (UINT32 i = 0; i < numLines; i++)
+			{
+				const TextDataBase::TextLine& line = textData.getLine(i);
 
-			// Line has a newline char only if it wasn't created by word wrap and it isn't the last line
-			bool hasNewline = line.hasNewlineChar() && (curLineIdx != (numLines - 1));
+				// Line has a newline char only if it wasn't created by word wrap and it isn't the last line
+				bool hasNewline = line.hasNewlineChar() && (curLineIdx != (numLines - 1));
 
-			UINT32 startChar = curCharIdx;
-			UINT32 endChar = curCharIdx + line.getNumChars() + (hasNewline ? 1 : 0);
-			UINT32 lineHeight = line.getYOffset();
-			INT32 lineYStart = alignmentOffsets[curLineIdx].y;
+				UINT32 startChar = curCharIdx;
+				UINT32 endChar = curCharIdx + line.getNumChars() + (hasNewline ? 1 : 0);
+				UINT32 lineHeight = line.getYOffset();
+				INT32 lineYStart = alignmentOffsets[curLineIdx].y;
 
-			GUIInputLineDesc lineDesc(startChar, endChar, lineHeight, lineYStart, hasNewline);
-			mLineDescs.push_back(lineDesc);
+				GUIInputLineDesc lineDesc(startChar, endChar, lineHeight, lineYStart, hasNewline);
+				mLineDescs.push_back(lineDesc);
 
-			curCharIdx = lineDesc.getEndChar();
-			curLineIdx++;
+				curCharIdx = lineDesc.getEndChar();
+				curLineIdx++;
+			}
 		}
+		bs_frame_clear();
 	}
 
 	Vector2I GUIInputTool::getTextOffset() const

+ 73 - 68
BansheeEngine/Source/BsTextSprite.cpp

@@ -17,103 +17,108 @@ namespace BansheeEngine
 	{
 		gProfilerCPU().beginSample("UpdateTextSprite");
 
-		TextData textData(desc.text, desc.font, desc.fontSize, desc.width, desc.height, desc.wordWrap, desc.wordBreak);
+		bs_frame_mark();
+		{
+			TextData<FrameAlloc> textData(desc.text, desc.font, desc.fontSize, desc.width, desc.height, desc.wordWrap, desc.wordBreak);
 
-		UINT32 numLines = textData.getNumLines();
-		UINT32 numPages = textData.getNumPages();
+			UINT32 numLines = textData.getNumLines();
+			UINT32 numPages = textData.getNumPages();
 
-		// Resize cached mesh array to needed size
-		if(mCachedRenderElements.size() > numPages)
-		{
-			for(UINT32 i = numPages; i < (UINT32)mCachedRenderElements.size(); i++)
+			// Resize cached mesh array to needed size
+			if (mCachedRenderElements.size() > numPages)
 			{
-				auto& renderElem = mCachedRenderElements[i];
+				for (UINT32 i = numPages; i < (UINT32)mCachedRenderElements.size(); i++)
+				{
+					auto& renderElem = mCachedRenderElements[i];
 
-				UINT32 vertexCount = renderElem.numQuads * 4;
-				UINT32 indexCount = renderElem.numQuads * 6;
+					UINT32 vertexCount = renderElem.numQuads * 4;
+					UINT32 indexCount = renderElem.numQuads * 6;
 
-				if(renderElem.vertices != nullptr)
-					bs_deleteN<ScratchAlloc>(renderElem.vertices, vertexCount);
+					if (renderElem.vertices != nullptr)
+						bs_deleteN<ScratchAlloc>(renderElem.vertices, vertexCount);
 
-				if(renderElem.uvs != nullptr)
-					bs_deleteN<ScratchAlloc>(renderElem.uvs, vertexCount);
+					if (renderElem.uvs != nullptr)
+						bs_deleteN<ScratchAlloc>(renderElem.uvs, vertexCount);
 
-				if(renderElem.indexes != nullptr)
-					bs_deleteN<ScratchAlloc>(renderElem.indexes, indexCount);
+					if (renderElem.indexes != nullptr)
+						bs_deleteN<ScratchAlloc>(renderElem.indexes, indexCount);
 
-				if(renderElem.matInfo.material != nullptr)
-				{
-					GUIMaterialManager::instance().releaseMaterial(renderElem.matInfo);
+					if (renderElem.matInfo.material != nullptr)
+					{
+						GUIMaterialManager::instance().releaseMaterial(renderElem.matInfo);
+					}
 				}
 			}
-		}
 
-		if(mCachedRenderElements.size() != numPages)
-			mCachedRenderElements.resize(numPages);
+			if (mCachedRenderElements.size() != numPages)
+				mCachedRenderElements.resize(numPages);
 
-		// Actually generate a mesh
-		UINT32 texPage = 0;
-		for(auto& cachedElem : mCachedRenderElements)
-		{
-			UINT32 newNumQuads = textData.getNumQuadsForPage(texPage);
-			if(newNumQuads != cachedElem.numQuads)
+			// Actually generate a mesh
+			UINT32 texPage = 0;
+			for (auto& cachedElem : mCachedRenderElements)
 			{
-				UINT32 oldVertexCount = cachedElem.numQuads * 4;
-				UINT32 oldIndexCount = cachedElem.numQuads * 6;
+				UINT32 newNumQuads = textData.getNumQuadsForPage(texPage);
+				if (newNumQuads != cachedElem.numQuads)
+				{
+					UINT32 oldVertexCount = cachedElem.numQuads * 4;
+					UINT32 oldIndexCount = cachedElem.numQuads * 6;
 
-				if(cachedElem.vertices != nullptr) bs_deleteN<ScratchAlloc>(cachedElem.vertices, oldVertexCount);
-				if(cachedElem.uvs != nullptr) bs_deleteN<ScratchAlloc>(cachedElem.uvs, oldVertexCount);
-				if(cachedElem.indexes != nullptr) bs_deleteN<ScratchAlloc>(cachedElem.indexes, oldIndexCount);
+					if (cachedElem.vertices != nullptr) bs_deleteN<ScratchAlloc>(cachedElem.vertices, oldVertexCount);
+					if (cachedElem.uvs != nullptr) bs_deleteN<ScratchAlloc>(cachedElem.uvs, oldVertexCount);
+					if (cachedElem.indexes != nullptr) bs_deleteN<ScratchAlloc>(cachedElem.indexes, oldIndexCount);
 
-				cachedElem.vertices = bs_newN<Vector2, ScratchAlloc>(newNumQuads * 4);
-				cachedElem.uvs = bs_newN<Vector2, ScratchAlloc>(newNumQuads * 4);
-				cachedElem.indexes = bs_newN<UINT32, ScratchAlloc>(newNumQuads * 6);
-				cachedElem.numQuads = newNumQuads;
-			}
+					cachedElem.vertices = bs_newN<Vector2, ScratchAlloc>(newNumQuads * 4);
+					cachedElem.uvs = bs_newN<Vector2, ScratchAlloc>(newNumQuads * 4);
+					cachedElem.indexes = bs_newN<UINT32, ScratchAlloc>(newNumQuads * 6);
+					cachedElem.numQuads = newNumQuads;
+				}
 
-			const HTexture& tex = textData.getTextureForPage(texPage);
+				const HTexture& tex = textData.getTextureForPage(texPage);
 
-			bool getNewMaterial = false;
-			if(cachedElem.matInfo.material == nullptr)
-				getNewMaterial = true;
-			else
-			{
-				const GUIMaterialInfo* matInfo = GUIMaterialManager::instance().findExistingTextMaterial(groupId, tex, desc.color);
-				if(matInfo == nullptr)
-				{
+				bool getNewMaterial = false;
+				if (cachedElem.matInfo.material == nullptr)
 					getNewMaterial = true;
-				}
 				else
 				{
-					if(matInfo->material != cachedElem.matInfo.material)
+					const GUIMaterialInfo* matInfo = GUIMaterialManager::instance().findExistingTextMaterial(groupId, tex, desc.color);
+					if (matInfo == nullptr)
 					{
-						GUIMaterialManager::instance().releaseMaterial(cachedElem.matInfo);
 						getNewMaterial = true;
 					}
+					else
+					{
+						if (matInfo->material != cachedElem.matInfo.material)
+						{
+							GUIMaterialManager::instance().releaseMaterial(cachedElem.matInfo);
+							getNewMaterial = true;
+						}
+					}
 				}
-			}
 
-			if(getNewMaterial)
-				cachedElem.matInfo = GUIMaterialManager::instance().requestTextMaterial(groupId, tex, desc.color);
+				if (getNewMaterial)
+					cachedElem.matInfo = GUIMaterialManager::instance().requestTextMaterial(groupId, tex, desc.color);
 
-			texPage++;
-		}
+				texPage++;
+			}
 
-		// Calc alignment and anchor offsets and set final line positions
-		for(UINT32 j = 0; j < numPages; j++)
-		{
-			SpriteRenderElement& renderElem = mCachedRenderElements[j];
+			// Calc alignment and anchor offsets and set final line positions
+			for (UINT32 j = 0; j < numPages; j++)
+			{
+				SpriteRenderElement& renderElem = mCachedRenderElements[j];
 
-			genTextQuads(j, textData, desc.width, desc.height, desc.horzAlign, desc.vertAlign, desc.anchor, 
-				renderElem.vertices, renderElem.uvs, renderElem.indexes, renderElem.numQuads);
+				genTextQuads(j, textData, desc.width, desc.height, desc.horzAlign, desc.vertAlign, desc.anchor,
+					renderElem.vertices, renderElem.uvs, renderElem.indexes, renderElem.numQuads);
+			}
 		}
 
+		bs_frame_clear();
+
 		updateBounds();
 
 		gProfilerCPU().endSample("UpdateTextSprite");
 	}
 
-	UINT32 TextSprite::genTextQuads(UINT32 page, const TextData& textData, UINT32 width, UINT32 height, 
+	UINT32 TextSprite::genTextQuads(UINT32 page, const TextDataBase& textData, UINT32 width, UINT32 height,
 		TextHorzAlign horzAlign, TextVertAlign vertAlign, SpriteAnchor anchor, Vector2* vertices, Vector2* uv, UINT32* indices, UINT32 bufferSizeQuads)
 	{
 		UINT32 numLines = textData.getNumLines();
@@ -125,7 +130,7 @@ namespace BansheeEngine
 		UINT32 quadOffset = 0;
 		for(UINT32 i = 0; i < numLines; i++)
 		{
-			const TextData::TextLine& line = textData.getLine(i);
+			const TextDataBase::TextLine& line = textData.getLine(i);
 			UINT32 writtenQuads = line.fillBuffer(page, vertices, uv, indices, quadOffset, bufferSizeQuads);
 
 			Vector2I position = offset + alignmentOffsets[i];
@@ -143,7 +148,7 @@ namespace BansheeEngine
 	}
 
 
-	UINT32 TextSprite::genTextQuads(const TextData& textData, UINT32 width, UINT32 height, 
+	UINT32 TextSprite::genTextQuads(const TextDataBase& textData, UINT32 width, UINT32 height,
 		TextHorzAlign horzAlign, TextVertAlign vertAlign, SpriteAnchor anchor, Vector2* vertices, Vector2* uv, UINT32* indices, UINT32 bufferSizeQuads)
 	{
 		UINT32 numLines = textData.getNumLines();
@@ -156,7 +161,7 @@ namespace BansheeEngine
 		
 		for(UINT32 i = 0; i < numLines; i++)
 		{
-			const TextData::TextLine& line = textData.getLine(i);
+			const TextDataBase::TextLine& line = textData.getLine(i);
 			for(UINT32 j = 0; j < numPages; j++)
 			{
 				UINT32 writtenQuads = line.fillBuffer(j, vertices, uv, indices, quadOffset, bufferSizeQuads);
@@ -177,14 +182,14 @@ namespace BansheeEngine
 		return quadOffset;
 	}
 
-	Vector<Vector2I> TextSprite::getAlignmentOffsets(const TextData& textData, 
+	Vector<Vector2I> TextSprite::getAlignmentOffsets(const TextDataBase& textData,
 		UINT32 width, UINT32 height, TextHorzAlign horzAlign, TextVertAlign vertAlign)
 	{
 		UINT32 numLines = textData.getNumLines();
 		UINT32 curHeight = 0;
 		for(UINT32 i = 0; i < numLines; i++)
 		{
-			const TextData::TextLine& line = textData.getLine(i);
+			const TextDataBase::TextLine& line = textData.getLine(i);
 			curHeight += line.getYOffset();
 		}
 
@@ -209,7 +214,7 @@ namespace BansheeEngine
 		Vector<Vector2I> lineOffsets;
 		for(UINT32 i = 0; i < numLines; i++)
 		{
-			const TextData::TextLine& line = textData.getLine(i);
+			const TextDataBase::TextLine& line = textData.getLine(i);
 
 			UINT32 horzOffset = 0;
 			switch(horzAlign)

+ 1 - 0
BansheeUtility/BansheeUtility.vcxproj

@@ -305,6 +305,7 @@
     <ClInclude Include="Include\BsSerializedObjectRTTI.h" />
     <ClInclude Include="Include\BsServiceLocator.h" />
     <ClInclude Include="Include\BsSpinLock.h" />
+    <ClInclude Include="Include\BsStaticAlloc.h" />
     <ClInclude Include="Include\BsStringFormat.h" />
     <ClInclude Include="Include\BsStringID.h" />
     <ClInclude Include="Include\BsTaskScheduler.h" />

+ 3 - 0
BansheeUtility/BansheeUtility.vcxproj.filters

@@ -308,6 +308,9 @@
     <ClInclude Include="Include\BsGlobalFrameAlloc.h">
       <Filter>Header Files</Filter>
     </ClInclude>
+    <ClInclude Include="Include\BsStaticAlloc.h">
+      <Filter>Header Files</Filter>
+    </ClInclude>
   </ItemGroup>
   <ItemGroup>
     <ClCompile Include="Source\BsThreadPool.cpp">

+ 19 - 0
BansheeUtility/Include/BsFrameAlloc.h

@@ -14,13 +14,24 @@ namespace BansheeEngine
 	class BS_UTILITY_EXPORT FrameAlloc
 	{
 	private:
+		/**
+		 * @brief	A single block of memory within a frame allocator.
+		 */
 		class MemBlock
 		{
 		public:
 			MemBlock(UINT32 size);
 			~MemBlock();
 
+			/**
+			 * @brief	Allocates a piece of memory within the block. Caller must ensure
+			 *			the block has enough empty space.
+			 */
 			UINT8* alloc(UINT32 amount);
+
+			/**
+			 * @brief	Releases all allocations within a block but doesn't actually free the memory.
+			 */
 			void clear();
 
 			UINT8* mData;
@@ -111,7 +122,15 @@ namespace BansheeEngine
 		BS_THREAD_ID_TYPE mOwnerThread;
 #endif
 
+		/**
+		 * @brief	Allocates a dynamic block of memory of the wanted size. The exact allocation size
+		 *			might be slightly higher in order to store block meta data.
+		 */
 		MemBlock* allocBlock(UINT32 wantedSize);
+
+		/**
+		 * @brief	Frees a memory block.
+		 */
 		void deallocBlock(MemBlock* block);
 	};
 

+ 215 - 0
BansheeUtility/Include/BsStaticAlloc.h

@@ -0,0 +1,215 @@
+#pragma once
+
+#include "BsPrerequisitesUtil.h"
+
+namespace BansheeEngine
+{
+	/**
+	 * @brief	Static allocator that attempts to perform zero heap allocations by always keeping an active
+	 *			stack-allocated buffer. If the size of allocated data goes over the set limit dynamic allocations
+	 *			will occur however.
+	 *
+	 * @note	This kind of allocator is only able to free all of its memory at once. Freeing individual elements
+	 *			will not free the memory until a call to ::clear.
+	 * 			
+	 * @tparam	BlockSize				Size of the initially allocated static block, and minimum size of any dynamically allocated memory.
+	 * @tparam	MaxDynamicMemory		Maximum amount of unused memory allowed in the buffer after a call to ::clear. Keeping active dynamic 
+	 *									buffers can help prevent further memory allocations at the cost of memory. This is not relevant
+	 *									if you stay within the bounds of the statically allocated memory.
+	 */
+	template<int BlockSize = 512, int MaxDynamicMemory = 512>
+	class BS_UTILITY_EXPORT StaticAlloc
+	{
+	private:
+		/**
+		 * @brief	A single block of memory within a static allocator.
+		 */
+		class MemBlock
+		{
+		public:
+			MemBlock(UINT8* data, UINT32 size) 
+				:mData(data), mFreePtr(0), mSize(size),
+				mPrevBlock(nullptr), mNextBlock(nullptr)
+			{ }
+
+			/**
+			 * @brief	Allocates a piece of memory within the block. Caller must ensure
+			 *			the block has enough empty space.
+			 */
+			UINT8* alloc(UINT32 amount)
+			{
+				UINT8* freePtr = &mData[mFreePtr];
+				mFreePtr += amount;
+
+				return freePtr;
+			}
+
+			/**
+			 * @brief	Releases all allocations within a block but doesn't actually free the memory.
+			 */
+			void clear()
+			{
+				mFreePtr = 0;
+			}
+
+			UINT8* mData;
+			UINT32 mFreePtr;
+			UINT32 mSize;
+			MemBlock* mPrevBlock;
+			MemBlock* mNextBlock;
+		};
+
+	public:
+		StaticAlloc()
+			:mStaticBlock(mStaticData, BlockSize), mFreeBlock(&mStaticBlock),
+			mTotalAllocBytes(0)
+		{
+
+		}
+
+		~StaticAlloc()
+		{
+			assert(mFreeBlock == &mStaticBlock && mStaticBlock.mFreePtr == 0);
+
+			freeEmptyDynamicBlocks(mFreeBlock);
+		}
+
+		/**
+		 * @brief	Allocates a new piece of memory of the specified size.
+		 *
+		 * @param	amount	Amount of memory to allocate, in bytes.
+		 */
+		UINT8* alloc(UINT32 amount)
+		{
+#if BS_DEBUG_MODE
+			amount += sizeof(UINT32);
+#endif
+
+			UINT32 freeMem = mFreeBlock->mSize - mFreeBlock->mFreePtr;
+			if (amount > freeMem)
+				allocBlock(amount);
+
+			UINT8* data = mFreeBlock->alloc(amount);
+
+#if BS_DEBUG_MODE
+			mTotalAllocBytes += amount;
+
+			UINT32* storedSize = reinterpret_cast<UINT32*>(data);
+			*storedSize = amount;
+
+			return data + sizeof(UINT32);
+#else
+			return data;
+#endif
+		}
+
+		/**
+		 * @brief	Deallocates a previously allocated piece of memory.
+		 */
+		void dealloc(UINT8* data)
+		{
+			// Dealloc is only used for debug and can be removed if needed. All the actual deallocation
+			// happens in ::clear
+
+#if BS_DEBUG_MODE
+			data -= sizeof(UINT32);
+			UINT32* storedSize = reinterpret_cast<UINT32*>(data);
+			mTotalAllocBytes -= *storedSize;
+#endif
+		}
+
+		void clear()
+		{
+			assert(mTotalAllocBytes == 0);
+			assert(mFreeBlock == &mStaticBlock);
+
+			MemBlock* dynamicBlock = mFreeBlock->mNextBlock;
+			INT32 totalDynamicMemAmount = 0;
+			UINT32 numDynamicBlocks = 0;
+
+			while (dynamicBlock != nullptr)
+			{
+				totalDynamicMemAmount += dynamicBlock->mFreePtr;
+				dynamicBlock->clear();
+			
+				dynamicBlock = dynamicBlock->mNextBlock;
+				numDynamicBlocks++;
+			}
+
+			if (numDynamicBlocks > 1)
+			{
+				freeBlocks(mFreeBlock);
+				allocBlock(std::min(totalDynamicMemAmount, MaxDynamicMemory));
+				mFreeBlock = &mStaticBlock;
+			}
+			else if (numDynamicBlocks == 1 && MaxDynamicMemory == 0)
+			{
+				freeBlocks(mFreeBlock);
+			}
+
+			mFreeBlock->clear();
+		}
+
+	private:
+		UINT8 mStaticData[BlockSize];
+		MemBlock mStaticBlock;
+
+		MemBlock* mFreeBlock;
+		UINT32 mTotalAllocBytes;
+
+		/**
+		 * @brief	Allocates a dynamic block of memory of the wanted size. The exact allocation size
+		 *			might be slightly higher in order to store block meta data.
+		 */
+		MemBlock* allocBlock(UINT32 wantedSize)
+		{
+			UINT32 blockSize = BlockSize;
+			if (wantedSize > blockSize)
+				blockSize = wantedSize;
+
+			MemBlock* dynamicBlock = mFreeBlock->mNextBlock;
+			MemBlock* newBlock = nullptr;
+			while (dynamicBlock != nullptr)
+			{
+				if (dynamicBlock->mSize >= blockSize)
+				{
+					newBlock = dynamicBlock;
+					break;
+				}
+
+				dynamicBlock = dynamicBlock->mNextBlock;
+			}
+
+			if (newBlock == nullptr)
+			{
+				UINT8* data = (UINT8*)reinterpret_cast<UINT8*>(bs_alloc(blockSize + sizeof(MemBlock)));
+				newBlock = new (data)MemBlock(data + sizeof(MemBlock), blockSize);
+				newBlock->mPrevBlock = mFreeBlock;
+				mFreeBlock->mNextBlock = newBlock;
+			}
+
+			mFreeBlock = newBlock;
+			return newBlock;
+		}
+
+		/**
+		 * @brief	Releases memory for any dynamic blocks following the
+		 *			provided block (if there are any).
+		 */
+		void freeBlocks(MemBlock* start)
+		{
+			MemBlock* dynamicBlock = start->mNextBlock;
+			while (dynamicBlock != nullptr)
+			{
+				MemBlock* nextBlock = dynamicBlock->mNextBlock;
+
+				dynamicBlock->~MemBlock();
+				bs_free(dynamicBlock);
+
+				dynamicBlock = nextBlock;
+			}
+
+			start->mNextBlock = nullptr;
+		}
+	};
+}