Просмотр исходного кода

Added Localized string classes and updated my TODO/Notes docs a bit

Marko Pintera 12 лет назад
Родитель
Сommit
cfa1c17520

+ 1 - 0
BansheeEngine.sln

@@ -42,6 +42,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
 		Dependencies.txt = Dependencies.txt
 		Dependencies.txt = Dependencies.txt
 		DrawHelper.txt = DrawHelper.txt
 		DrawHelper.txt = DrawHelper.txt
 		EditorWindowDock.txt = EditorWindowDock.txt
 		EditorWindowDock.txt = EditorWindowDock.txt
+		Localization.txt = Localization.txt
 		Notes.txt = Notes.txt
 		Notes.txt = Notes.txt
 		RenderOperation.txt = RenderOperation.txt
 		RenderOperation.txt = RenderOperation.txt
 		TODO.txt = TODO.txt
 		TODO.txt = TODO.txt

+ 4 - 0
CamelotUtility/CamelotUtility.vcxproj

@@ -252,6 +252,7 @@
     <ClCompile Include="Source\CmManagedDataBlock.cpp" />
     <ClCompile Include="Source\CmManagedDataBlock.cpp" />
     <ClCompile Include="Source\CmMemStack.cpp" />
     <ClCompile Include="Source\CmMemStack.cpp" />
     <ClCompile Include="Source\CmRect.cpp" />
     <ClCompile Include="Source\CmRect.cpp" />
+    <ClCompile Include="Source\CmStringTable.cpp" />
     <ClCompile Include="Source\CmTexAtlasGenerator.cpp" />
     <ClCompile Include="Source\CmTexAtlasGenerator.cpp" />
     <ClCompile Include="Source\CmUUID.cpp" />
     <ClCompile Include="Source\CmUUID.cpp" />
     <ClCompile Include="Source\CmWorkQueue.cpp" />
     <ClCompile Include="Source\CmWorkQueue.cpp" />
@@ -268,6 +269,7 @@
     <ClInclude Include="Include\CmFileSerializer.h" />
     <ClInclude Include="Include\CmFileSerializer.h" />
     <ClInclude Include="Include\CmFileSystem.h" />
     <ClInclude Include="Include\CmFileSystem.h" />
     <ClInclude Include="Include\CmFRect.h" />
     <ClInclude Include="Include\CmFRect.h" />
+    <ClInclude Include="Include\CmHString.h" />
     <ClInclude Include="Include\CmInt2.h" />
     <ClInclude Include="Include\CmInt2.h" />
     <ClInclude Include="Include\CmIReflectable.h" />
     <ClInclude Include="Include\CmIReflectable.h" />
     <ClInclude Include="Include\CmKeyValuePair.h" />
     <ClInclude Include="Include\CmKeyValuePair.h" />
@@ -287,6 +289,7 @@
     <ClInclude Include="Include\CmRTTIType.h" />
     <ClInclude Include="Include\CmRTTIType.h" />
     <ClInclude Include="Include\CmMemStack.h" />
     <ClInclude Include="Include\CmMemStack.h" />
     <ClInclude Include="Include\CmString.h" />
     <ClInclude Include="Include\CmString.h" />
+    <ClInclude Include="Include\CmStringTable.h" />
     <ClInclude Include="Include\CmThreadDefines.h" />
     <ClInclude Include="Include\CmThreadDefines.h" />
     <ClInclude Include="Include\CmTime.h" />
     <ClInclude Include="Include\CmTime.h" />
     <ClInclude Include="Include\CmTimer.h" />
     <ClInclude Include="Include\CmTimer.h" />
@@ -318,6 +321,7 @@
     <ClCompile Include="Source\CmRTTIType.cpp" />
     <ClCompile Include="Source\CmRTTIType.cpp" />
     <ClInclude Include="Include\CmWorkQueue.h" />
     <ClInclude Include="Include\CmWorkQueue.h" />
     <ClInclude Include="Include\CmTexAtlasGenerator.h" />
     <ClInclude Include="Include\CmTexAtlasGenerator.h" />
+    <ClCompile Include="Source\CmHString.cpp" />
   </ItemGroup>
   </ItemGroup>
   <ItemGroup>
   <ItemGroup>
     <ClCompile Include="Include\CmAABox.cpp" />
     <ClCompile Include="Include\CmAABox.cpp" />

+ 15 - 0
CamelotUtility/CamelotUtility.vcxproj.filters

@@ -49,6 +49,9 @@
     <Filter Include="Source Files\Threading">
     <Filter Include="Source Files\Threading">
       <UniqueIdentifier>{cb0d2667-8d73-4d4c-9b2b-bc18fbd7fd70}</UniqueIdentifier>
       <UniqueIdentifier>{cb0d2667-8d73-4d4c-9b2b-bc18fbd7fd70}</UniqueIdentifier>
     </Filter>
     </Filter>
+    <Filter Include="Header Files\Localization">
+      <UniqueIdentifier>{bbd99250-46cb-4299-bbfa-34addc87b878}</UniqueIdentifier>
+    </Filter>
   </ItemGroup>
   </ItemGroup>
   <ItemGroup>
   <ItemGroup>
     <ClInclude Include="Include\CmTypes.h">
     <ClInclude Include="Include\CmTypes.h">
@@ -219,6 +222,12 @@
     <ClInclude Include="Include\CmPlatformDefines.h">
     <ClInclude Include="Include\CmPlatformDefines.h">
       <Filter>Header Files\Prerequisites</Filter>
       <Filter>Header Files\Prerequisites</Filter>
     </ClInclude>
     </ClInclude>
+    <ClInclude Include="Include\CmHString.h">
+      <Filter>Header Files\Localization</Filter>
+    </ClInclude>
+    <ClInclude Include="Include\CmStringTable.h">
+      <Filter>Header Files</Filter>
+    </ClInclude>
   </ItemGroup>
   </ItemGroup>
   <ItemGroup>
   <ItemGroup>
     <ClCompile Include="Source\CmMath.cpp">
     <ClCompile Include="Source\CmMath.cpp">
@@ -329,5 +338,11 @@
     <ClCompile Include="Source\CmFRect.cpp">
     <ClCompile Include="Source\CmFRect.cpp">
       <Filter>Source Files\Math</Filter>
       <Filter>Source Files\Math</Filter>
     </ClCompile>
     </ClCompile>
+    <ClCompile Include="Source\CmHString.cpp">
+      <Filter>Source Files</Filter>
+    </ClCompile>
+    <ClCompile Include="Source\CmStringTable.cpp">
+      <Filter>Source Files</Filter>
+    </ClCompile>
   </ItemGroup>
   </ItemGroup>
 </Project>
 </Project>

+ 3 - 0
CamelotUtility/Include/CmFwdDeclUtil.h

@@ -31,6 +31,9 @@ namespace CamelotFramework {
 	class FileSystem;
 	class FileSystem;
 	class Timer;
 	class Timer;
 	class PixelData;
 	class PixelData;
+	class HString;
+	class StringTable;
+	struct LocalizedStringData;
 	// Reflection
 	// Reflection
 	class IReflectable;
 	class IReflectable;
 	class RTTITypeBase;
 	class RTTITypeBase;

+ 35 - 0
CamelotUtility/Include/CmHString.h

@@ -0,0 +1,35 @@
+#pragma once
+
+#include "CmPrerequisitesUtil.h"
+#include <boost/signals.hpp>
+
+namespace CamelotFramework
+{
+	/**
+	 * @brief	String handle. Provides a wrapper around an Unicode string, primarily
+	 * 			for localization purposes.
+	 * 			
+	 *			Actual value for this string is looked up in a global string table based
+	 *			on the provided identifier string and currently active language. If such value 
+	 *			doesn't exist then the identifier is used as is.
+	 */
+	class CM_UTILITY_EXPORT HString
+	{
+	public:
+		HString(const WString& identifierString);
+		~HString();
+
+		operator const WString& () const { return mCachedString; }
+		void setParameter(UINT32 idx, const WString& value);
+
+		boost::signal<void()> onStringModified;
+	private:
+		LocalizedStringData* mStringData;
+		WString* mParameters;
+		boost::signals::connection mUpdateConn;
+
+		WString mCachedString;
+
+		void updateString(const WString& identifierString);
+	};
+}

+ 41 - 0
CamelotUtility/Include/CmString.h

@@ -604,6 +604,47 @@ namespace CamelotFramework
 
 
     /** Checks the String is a valid number value. */
     /** Checks the String is a valid number value. */
     CM_UTILITY_EXPORT bool isNumber(const String& val);
     CM_UTILITY_EXPORT bool isNumber(const String& val);
+
+/** Converts a String to a float. 
+    @returns
+        0.0 if the value could not be parsed, otherwise the float version of the String.
+    */
+    CM_UTILITY_EXPORT float parseFloat(const WString& val, float defaultValue = 0);
+
+    /** Converts a String to a whole number. 
+    @returns
+        0.0 if the value could not be parsed, otherwise the numeric version of the String.
+    */
+    CM_UTILITY_EXPORT int parseInt(const WString& val, int defaultValue = 0);
+
+    /** Converts a String to a whole number. 
+    @returns
+        0.0 if the value could not be parsed, otherwise the numeric version of the String.
+    */
+    CM_UTILITY_EXPORT unsigned int parseUnsignedInt(const WString& val, unsigned int defaultValue = 0);
+
+    /** Converts a String to a whole number. 
+    @returns
+        0.0 if the value could not be parsed, otherwise the numeric version of the String.
+    */
+    CM_UTILITY_EXPORT long parseLong(const WString& val, long defaultValue = 0);
+
+    /** Converts a String to a whole number. 
+    @returns
+        0.0 if the value could not be parsed, otherwise the numeric version of the String.
+    */
+    CM_UTILITY_EXPORT unsigned long parseUnsignedLong(const WString& val, unsigned long defaultValue = 0);
+
+    /** Converts a String to a boolean. 
+    @remarks
+        Returns true if case-insensitive match of the start of the string
+		matches "true", "yes" or "1", false otherwise.
+    */
+    CM_UTILITY_EXPORT bool parseBool(const WString& val, bool defaultValue = 0);
+
+    /** Checks the String is a valid number value. */
+    CM_UTILITY_EXPORT bool isNumber(const WString& val);
+
 	/** @} */
 	/** @} */
 
 
 	void CM_UTILITY_EXPORT __string_throwDataOverflowException();
 	void CM_UTILITY_EXPORT __string_throwDataOverflowException();

+ 294 - 0
CamelotUtility/Include/CmStringTable.h

@@ -0,0 +1,294 @@
+#pragma once
+
+#include "CmPrerequisitesUtil.h"
+#include "CmModule.h"
+#include "boost/signals.hpp"
+
+namespace CamelotFramework
+{
+	/**
+	 * @brief	Loosely based on ISO 639-1 two letter language codes
+	 */
+	enum class Language
+	{
+		Afar, 
+		Abkhazian, 
+		Avestan, 
+		Afrikaans, 
+		Akan, 
+		Amharic, 
+		Aragonese, 
+		Arabic, 
+		Assamese, 
+		Avaric, 
+		Aymara, 
+		Azerbaijani, 
+		Bashkir, 
+		Belarusian, 
+		Bulgarian, 
+		Bihari, 
+		Bislama, 
+		Bambara, 
+		Bengali, 
+		Tibetan, 
+		Breton, 
+		Bosnian, 
+		Catalan, 
+		Chechen, 
+		Chamorro, 
+		Corsican, 
+		Cree, 
+		Czech, 
+		ChurchSlavic,
+		Chuvash, 
+		Welsh, 
+		Danish, 
+		German, 
+		Maldivian, 
+		Bhutani, 
+		Ewe, 		
+		Greek, 
+		EnglishUK, 
+		EnglishUS,
+		Esperanto, 
+		Spanish, 
+		Estonian, 
+		Basque, 
+		Persian, 
+		Fulah, 
+		Finnish, 
+		Fijian, 
+		Faroese, 
+		French, 
+		WesternFrisian, 
+		Irish, 
+		ScottishGaelic, 
+		Galician, 
+		Guarani, 
+		Gujarati, 
+		Manx, 
+		Hausa, 
+		Hebrew, 
+		Hindi, 
+		HiriMotu, 
+		Croatian, 
+		Haitian, 
+		Hungarian, 
+		Armenian, 
+		Herero, 
+		Interlingua, 
+		Indonesian, 
+		Interlingue, 
+		Igbo, 
+		SichuanYi, 
+		Inupiak, 
+		Ido, 
+		Icelandic, 
+		Italian, 
+		Inuktitut, 
+		Japanese, 
+		Javanese, 
+		Georgian, 
+		Kongo, 
+		Kikuyu, 
+		Kuanyama, 
+		Kazakh, 
+		Kalaallisut, 
+		Cambodian, 
+		Kannada, 
+		Korean, 
+		Kanuri, 
+		Kashmiri, 
+		Kurdish, 
+		Komi, 
+		Cornish, 
+		Kirghiz, 
+		Latin, 
+		Luxembourgish, 
+		Ganda, 
+		Limburgish,
+		Lingala, 
+		Laotian, 
+		Lithuanian, 
+		LubaKatanga, 
+		Latvian,
+		Malagasy, 
+		Marshallese, 
+		Maori, 
+		Macedonian, 
+		Malayalam, 
+		Mongolian, 
+		Moldavian, 
+		Marathi, 
+		Malay, 
+		Maltese, 
+		Burmese, 
+		Nauru, 
+		NorwegianBokmal, 
+		Ndebele, 
+		Nepali, 
+		Ndonga, 
+		Dutch, 
+		NorwegianNynorsk, 
+		Norwegian, 
+		Navaho, 
+		Nyanja, 
+		Provençal, 
+		Ojibwa, 
+		Oromo, 
+		Oriya, 
+		Ossetic, 
+		Punjabi, 
+		Pali, 
+		Polish, 
+		Pushto, 
+		Portuguese, 
+		Quechua, 
+		Romansh, 
+		Kirundi, 
+		Romanian, 
+		Russian, 
+		Kinyarwanda, 
+		Sanskrit, 
+		Sardinian, 
+		Sindhi, 
+		NorthernSami, 
+		Sangro, 
+		Sinhalese, 
+		Slovak, 
+		Slovenian, 
+		Samoan, 
+		Shona, 
+		Somali, 
+		Albanian, 
+		Serbian, 
+		Swati,
+		Sesotho,
+		Sundanese, 
+		Swedish, 
+		Swahili, 
+		Tamil, 
+		Telugu, 
+		Tajik, 
+		Thai, 
+		Tigrinya, 
+		Turkmen, 
+		Tagalog, 
+		Setswana, 
+		Tonga, 
+		Turkish, 
+		Tsonga, 
+		Tatar,
+		Twi, 
+		Tahitian, 
+		Uighur, 
+		Ukrainian, 
+		Urdu, 
+		Uzbek, 
+		Venda, 
+		Vietnamese, 
+		Volapuk, 
+		Walloon, 
+		Wolof, 
+		Xhosa, 
+		Yiddish, 
+		Yoruba, 
+		Zhuang,
+		Chinese,
+		Zulu,
+		Count // Not a language, just a quick way to know total number of languages
+	};
+
+	struct LocalizedStringData
+	{
+		struct Common
+		{
+			boost::signal<void(const WString& identifier)> onStringDataModified;
+		};
+
+		struct ParamOffset
+		{
+			ParamOffset()
+				:paramIdx(0), location(0)
+			{ }
+
+			ParamOffset(UINT32 _paramIdx, UINT32 _location)
+				:paramIdx(_paramIdx), location(_location)
+			{ }
+
+			UINT32 paramIdx;
+			UINT32 location;
+		};
+
+		LocalizedStringData();
+		~LocalizedStringData();
+
+		WString string;
+		UINT32 numParameters;
+		ParamOffset* parameterOffsets; 
+
+		Common* commonData;
+
+		WString concatenateString(WString* parameters, UINT32 numParameterValues) const;
+		void updateString(const WString& string);
+	};
+
+	class CM_UTILITY_EXPORT StringTable : public Module<StringTable>
+	{
+		// TODO - When editing string table I will need to ensure that all languages of the same string have the same number of parameters
+
+		struct LanguageData
+		{
+			UnorderedMap<WString, LocalizedStringData*>::type strings;
+		};
+	public:
+		StringTable();
+		~StringTable();
+
+		Language getActiveLanguage() const { return mActiveLanguage; }
+		void setActiveLanguage(Language language);
+
+		void setString(const WString& identifier, Language language, const WString& string);
+		void removeString(const WString& identifier);
+
+		/**
+		 * @brief	Gets a string data for the specified string identifier and currently active language.
+		 *
+		 * @param	identifier		   	Unique string identifier.
+		 * @param	insertIfNonExisting	If true, a new string data for the specified identifier and language will be
+		 * 								added to the table if data doesn't already exist. The data will use the identifier as
+		 * 								the translation string.
+		 *
+		 * @return	The string data. Don't store reference to this data as it may get deleted.
+		 */
+		LocalizedStringData& getStringData(const WString& identifier, bool insertIfNonExisting = true);
+
+		/**
+		 * @brief	Gets a string data for the specified string identifier and language.
+		 *
+		 * @param	identifier		   	Unique string identifier.
+		 * @param	language		   	Language.
+		 * @param	insertIfNonExisting	If true, a new string data for the specified identifier and language will be
+		 * 								added to the table if data doesn't already exist. The data will use the identifier as
+		 * 								the translation string.
+		 *
+		 * @return	The string data. Don't store reference to this data as it may get deleted.
+		 */
+		LocalizedStringData& getStringData(const WString& identifier, Language language, bool insertIfNonExisting = true);
+
+	private:
+		friend class HString;
+
+		static const Language DEFAULT_LANGUAGE;
+
+		Language mActiveLanguage;
+		LanguageData* mActiveLanguageData;
+		LanguageData* mDefaultLanguageData;
+
+		LanguageData* mAllLanguages;
+
+		UnorderedMap<WString, LocalizedStringData::Common*>::type mCommonData;
+
+		void notifyAllStringsChanged();
+	};
+}

+ 43 - 0
CamelotUtility/Source/CmHString.cpp

@@ -0,0 +1,43 @@
+#include "CmHString.h"
+#include "CmStringTable.h"
+
+namespace CamelotFramework
+{
+	HString::HString(const WString& identifierString)
+		:mParameters(nullptr)
+	{
+		mStringData = &StringTable::instance().getStringData(identifierString);
+		mParameters = cm_newN<WString>(mStringData->numParameters);
+		mUpdateConn = mStringData->commonData->onStringDataModified.connect(boost::bind(&HString::updateString, this, _1));
+
+		updateString(identifierString);
+	}
+
+	HString::~HString()
+	{
+		mUpdateConn.disconnect();
+
+		if(mParameters != nullptr)
+			cm_deleteN(mParameters, mStringData->numParameters);
+	}
+
+	void HString::setParameter(UINT32 idx, const WString& value)
+	{
+		mParameters[idx] = value;
+	}
+
+	void HString::updateString(const WString& identifierString)
+	{
+		LocalizedStringData* stringData = &StringTable::instance().getStringData(identifierString);
+
+		// If common data changed re-apply the connections
+		if(stringData->commonData != mStringData->commonData)
+		{
+			mUpdateConn.disconnect();
+			mUpdateConn = stringData->commonData->onStringDataModified.connect(boost::bind(&HString::updateString, this, _1));
+		}
+
+		mStringData = stringData;
+		mCachedString = mStringData->concatenateString(mParameters, mStringData->numParameters);
+	}
+}

+ 70 - 0
CamelotUtility/Source/CmString.cpp

@@ -407,6 +407,76 @@ namespace CamelotFramework
 		return !str.fail() && str.eof();
 		return !str.fail() && str.eof();
 	}
 	}
 
 
+	float parseFloat(const WString& val, float defaultValue)
+	{
+		// Use istringstream for direct correspondence with toString
+		WStringStream str(val);
+		float ret = defaultValue;
+		str >> ret;
+
+		return ret;
+	}
+
+	int parseInt(const WString& val, int defaultValue)
+	{
+		// Use istringstream for direct correspondence with toString
+		WStringStream str(val);
+		int ret = defaultValue;
+		str >> ret;
+
+		return ret;
+	}
+
+	unsigned int parseUnsignedInt(const WString& val, unsigned int defaultValue)
+	{
+		// Use istringstream for direct correspondence with toString
+		WStringStream str(val);
+		unsigned int ret = defaultValue;
+		str >> ret;
+
+		return ret;
+	}
+
+	long parseLong(const WString& val, long defaultValue)
+	{
+		// Use istringstream for direct correspondence with toString
+		WStringStream str(val);
+		long ret = defaultValue;
+		str >> ret;
+
+		return ret;
+	}
+
+	unsigned long parseUnsignedLong(const WString& val, unsigned long defaultValue)
+	{
+		// Use istringstream for direct correspondence with toString
+		WStringStream str(val);
+		unsigned long ret = defaultValue;
+		str >> ret;
+
+		return ret;
+	}
+
+	bool parseBool(const WString& val, bool defaultValue)
+	{
+		if ((StringUtil::startsWith(val, L"true") || StringUtil::startsWith(val, L"yes")
+			|| StringUtil::startsWith(val, L"1")))
+			return true;
+		else if ((StringUtil::startsWith(val, L"false") || StringUtil::startsWith(val, L"no")
+			|| StringUtil::startsWith(val, L"0")))
+			return false;
+		else
+			return defaultValue;
+	}
+
+	bool isNumber(const WString& val)
+	{
+		WStringStream str(val);
+		float tst;
+		str >> tst;
+		return !str.fail() && str.eof();
+	}
+
 	void __string_throwDataOverflowException()
 	void __string_throwDataOverflowException()
 	{
 	{
 		CM_EXCEPT(InternalErrorException, "Data overflow! Size doesn't fit into 32 bits.");
 		CM_EXCEPT(InternalErrorException, "Data overflow! Size doesn't fit into 32 bits.");

+ 278 - 0
CamelotUtility/Source/CmStringTable.cpp

@@ -0,0 +1,278 @@
+#include "CmStringTable.h"
+#include "CmException.h"
+
+namespace CamelotFramework
+{
+	const Language StringTable::DEFAULT_LANGUAGE = Language::EnglishUS;
+
+	LocalizedStringData::LocalizedStringData()
+		:commonData(nullptr), parameterOffsets(nullptr), numParameters(0)
+	{
+
+	}
+
+	LocalizedStringData::~LocalizedStringData()
+	{
+		if(parameterOffsets != nullptr)
+			cm_deleteN(parameterOffsets, numParameters);
+	}
+
+	WString LocalizedStringData::concatenateString(WString* parameters, UINT32 numParameterValues) const
+	{
+		WStringStream fullString;
+		UINT32 prevIdx = 0;
+
+		// A safeguard in case translated strings have different number of parameters
+		UINT32 actualNumParameters = std::min(numParameterValues, numParameters);
+
+		if(parameters != nullptr)
+		{
+			for(UINT32 i = 0; i < actualNumParameters; i++)
+			{
+				fullString<<string.substr(prevIdx, parameterOffsets[i].location - prevIdx);
+				fullString<<parameters[parameterOffsets[i].paramIdx];
+
+				prevIdx = parameterOffsets[i].location;
+			}
+		}
+
+		fullString<<string.substr(prevIdx, string.size() - prevIdx);
+
+		return fullString.str();
+	}
+
+	void LocalizedStringData::updateString(const WString& _string)
+	{
+		if(parameterOffsets != nullptr)
+			cm_deleteN(parameterOffsets, numParameters);
+
+		Vector<ParamOffset>::type paramOffsets;
+
+		INT32 lastBracket = -1;
+		WStringStream bracketChars;
+		WStringStream cleanString;
+		UINT32 numRemovedChars = 0;
+		for(UINT32 i = 0; i < (UINT32)_string.size(); i++)
+		{
+			if(lastBracket == -1)
+			{
+				// If current char is non-escaped opening bracket start parameter definition
+				if(_string[i] == '{' && (i == 0 || _string[i - 1] != '\\')) 
+					lastBracket = i;
+				else
+					cleanString<<_string[i];
+			}
+			else
+			{
+				if(isdigit(_string[i]))
+					bracketChars<<_string[i];
+				else
+				{
+					// If current char is non-escaped closing bracket end parameter definition
+					UINT32 numParamChars = (UINT32)bracketChars.gcount();
+					if(_string[i] == '}' && numParamChars > 0 && (i == 0 || _string[i - 1] != '\\')) 
+					{
+						UINT32 paramIdx = parseUnsignedInt(bracketChars.str());
+						paramOffsets.push_back(ParamOffset(i - numRemovedChars, paramIdx));
+
+						numRemovedChars += numParamChars;
+					}
+					else
+					{
+						// Last bracket wasn't really a parameter
+						for(UINT32 j = lastBracket; j <= i; j++)
+							cleanString<<_string[j];
+					}
+
+					lastBracket = -1;
+					bracketChars.clear();
+				}
+			}
+		}
+
+		string = cleanString.str();
+		numParameters = (UINT32)paramOffsets.size();
+
+		// Try to find out of order param offsets and fix them
+		std::sort(begin(paramOffsets), end(paramOffsets), 
+			[&] (const ParamOffset& a, const ParamOffset& b) { return a.paramIdx < b.paramIdx; } );
+
+		if(paramOffsets.size() > 0)
+		{
+			UINT32 sequentialIdx = 0;
+			UINT32 lastParamIdx = paramOffsets[0].paramIdx;
+			for(UINT32 i = 0; i < numParameters; i++)
+			{
+				if(paramOffsets[i].paramIdx == lastParamIdx)
+				{
+					paramOffsets[i].paramIdx = sequentialIdx;
+					continue;
+				}
+
+				lastParamIdx = paramOffsets[i].paramIdx;
+				sequentialIdx++;
+
+				paramOffsets[i].paramIdx = sequentialIdx;
+			}
+		}
+
+		// Re-sort based on location since we find that more useful at runtime
+		std::sort(begin(paramOffsets), end(paramOffsets), 
+			[&] (const ParamOffset& a, const ParamOffset& b) { return a.location < b.location; } );
+
+		parameterOffsets = cm_newN<ParamOffset>(numParameters);
+		for(UINT32 i = 0; i < numParameters; i++)
+			parameterOffsets[i] = paramOffsets[i];
+	}
+
+	StringTable::StringTable()
+		:mActiveLanguageData(nullptr), mDefaultLanguageData(nullptr), mAllLanguages(nullptr)
+	{
+		mAllLanguages = cm_newN<LanguageData>((UINT32)Language::Count);
+
+		mDefaultLanguageData = &(mAllLanguages[(UINT32)DEFAULT_LANGUAGE]);
+		mActiveLanguageData = mDefaultLanguageData;
+		mActiveLanguage = DEFAULT_LANGUAGE;
+	}
+	
+	StringTable::~StringTable()
+	{
+		for(UINT32 i = 0; i < (UINT32)Language::Count; i++)
+		{
+			for(auto& iter : mAllLanguages[i].strings)
+				cm_delete(iter.second);
+		}
+
+		cm_deleteN(mAllLanguages, (UINT32)Language::Count);
+
+		for(auto& common : mCommonData)
+			cm_delete(common.second);
+	}
+
+	void StringTable::setActiveLanguage(Language language)
+	{
+		if(language == mActiveLanguage)
+			return;
+
+		mActiveLanguageData = &(mAllLanguages[(UINT32)language]);
+		mActiveLanguage = language;
+
+		notifyAllStringsChanged();
+	}
+
+	void StringTable::setString(const WString& identifier, Language language, const WString& string)
+	{
+		LanguageData* curLanguage = &(mAllLanguages[(UINT32)language]);
+
+		auto iterFind = curLanguage->strings.find(identifier);
+
+		LocalizedStringData* stringData;
+		if(iterFind == curLanguage->strings.end())
+		{
+			auto iterFindCommon = mCommonData.find(identifier);
+
+			LocalizedStringData::Common* common = nullptr;
+			if(iterFindCommon == mCommonData.end())
+			{
+				common = cm_new<LocalizedStringData::Common>();
+				mCommonData[identifier] = common;
+			}
+			else
+				common = iterFindCommon->second;
+
+			stringData = cm_new<LocalizedStringData>();
+			curLanguage->strings[identifier] = stringData;
+			stringData->commonData = common;
+		}
+		else
+		{
+			stringData = iterFind->second;
+		}
+
+		stringData->updateString(string);
+
+		if(mActiveLanguage == language)
+		{
+			if(!stringData->commonData->onStringDataModified.empty())
+				stringData->commonData->onStringDataModified(identifier);
+		}
+	}
+
+	void StringTable::removeString(const WString& identifier)
+	{
+		// Order of operations is very important here, in case a string that is in use
+		// is removed. In that case we want the string to be marked as modified and it should
+		// call getStringData which will generate a new entry for the string.
+		
+		LocalizedStringData* stringData = nullptr;
+		for(UINT32 i = 0; i < (UINT32)Language::Count; i++)
+		{
+			auto findIter = mAllLanguages[i].strings.find(identifier);
+			if(findIter != mAllLanguages[i].strings.end())
+			{
+				if(mActiveLanguage == (Language)i)
+					stringData = findIter->second;
+				else
+					cm_delete(findIter->second);
+
+				mAllLanguages[i].strings.erase(findIter);
+			}
+		}
+
+		auto findIterCommon = mCommonData.find(identifier);
+
+		LocalizedStringData::Common* common = nullptr;
+		if(findIterCommon != mCommonData.end())
+		{
+			common = findIterCommon->second;
+			mCommonData.erase(findIterCommon);
+
+			if(!common->onStringDataModified.empty())
+				common->onStringDataModified(identifier);
+		}
+
+		if(stringData != nullptr)
+			cm_delete(stringData);
+
+		if(common != nullptr)
+			cm_delete(common);
+	}
+
+	LocalizedStringData& StringTable::getStringData(const WString& identifier, bool insertIfNonExisting)
+	{
+		return getStringData(identifier, mActiveLanguage, insertIfNonExisting);
+	}
+
+	LocalizedStringData& StringTable::getStringData(const WString& identifier, Language language, bool insertIfNonExisting)
+	{
+		LanguageData* curLanguage = &(mAllLanguages[(UINT32)language]);
+
+		auto iterFind = curLanguage->strings.find(identifier);
+		if(iterFind != curLanguage->strings.end())
+			return *iterFind->second;
+
+		auto defaultIterFind = mDefaultLanguageData->strings.find(identifier);
+		if(defaultIterFind != mDefaultLanguageData->strings.end())
+			return *defaultIterFind->second;
+
+		if(insertIfNonExisting)
+		{
+			setString(identifier, DEFAULT_LANGUAGE, identifier);
+
+			auto defaultIterFind = mDefaultLanguageData->strings.find(identifier);
+			if(defaultIterFind != mDefaultLanguageData->strings.end())
+				return *defaultIterFind->second;
+		}
+
+		CM_EXCEPT(InvalidParametersException, "There is no string data for the provided identifier.");
+	}
+
+	void StringTable::notifyAllStringsChanged()
+	{
+		for(auto& iter : mCommonData)
+		{
+			if(!iter.second->onStringDataModified.empty())
+				iter.second->onStringDataModified(iter.first);
+		}
+	}
+}

+ 14 - 0
Localization.txt

@@ -0,0 +1,14 @@
+Test HString:
+ - Test parameters
+ - Test changing language
+ - Test changing string
+ - Test removing string
+
+ HString needs to be introduced in:
+ - Label, Button, Toggle, Listbox
+   - Also needs a callback so those elements can be marked as dirty when it is changed
+ - Context menu & menu bar
+   - How do I change this when HString is modified or language is changed?
+   - Allow the user to set custom menu name (name used only for display)
+     - Normally the name provided in menu path is used but user can set a localized string if he wants
+ - Title bar & window names

+ 56 - 12
Notes.txt

@@ -1,10 +1,3 @@
-----------------------------------------------------------------------------------------------
-General longterm systems:   
-   - Camera controls + world grid
-   - Frustum culling and octree (or some other) acceleration structure
-   - Render queue and sorting
-
-----------------------------------------------------------------------------------------------
 Reminders:
 Reminders:
   - Assets manifest
   - Assets manifest
      - I need to be able to create a list of assets used in the game. Assets referenced from the editor
      - I need to be able to create a list of assets used in the game. Assets referenced from the editor
@@ -35,6 +28,32 @@ Reminders:
     - Instead of using selective input I might be able to block all input using event filter (by adding return values to them)
     - Instead of using selective input I might be able to block all input using event filter (by adding return values to them)
     - Some kind of GUISelectiveInput class? I think it makes sense to move it out of GUIManager
     - Some kind of GUISelectiveInput class? I think it makes sense to move it out of GUIManager
   - GUI ignores image in GUIContent for most elements
   - GUI ignores image in GUIContent for most elements
+  - Each view (i.e. camera) of the scene should be put into its own thread
+  - How do I handle multiple mesh formats? Some files need animation, other don't. Some would mabye like to use QTangent, others the proper tangent frame.
+    - Asset postprocessor? Imports a regular mesh using normal importers and then postprocesses it into a specialized format?
+  - Load texture mips separately so we can unload HQ textures from far away objects (like UE3)
+  - Add Unified shader so I can easily switch between HLSL and GLSL shaders (they need same parameters usually, just different code)
+    - Maybe just add support for Cg and force everyone to use that? - I'd like to be able to just switch out renderer in a single location and that everything keeps on working without 
+	  further modifications.
+  - Port boost threads to std threads (CmThreadDefines.h)
+  - Remove HardwarePixelBuffer (DX11 doesn't use it, and DX9 and OpenGL textures can be rewritten so they have its methods internally)
+  - Multihead device
+  - 3D rendering (use low level hardware methods for it)
+  - Don't forget to check out Unity DX11 documentation on how to implement DX11 features (http://docs.unity3d.com/Documentation/Manual/DirectX11.html)
+  - Go to Game Engine Architecture book and make a list of Utility systems we will need (Config files, Parsers, File I/O etc)
+  - Go to GEA book and read about resource managers before implementing them
+    - Actually I should re-read most of the chapers in the book, or all of it
+  - OpenGL non-Win32 window files haven't been properly parsed or tested
+    - Since I probably can't compile them, try adding them to VS and see what intellisense says?
+  - Textures and all other buffers keep a copy of their data in system memory. If there are memory constraints we might need a way to avoid this.
+  - Make sure my Log system uses XML + HTML
+  - There is an issue that custom-UIs won't have their mesh shared. For example most game UIs will be advanced and will 
+   likely use on GUIWidget per element. However currently I only perform batching within a single widget which 
+   doesn't help in the mentioned case.
+  - Later add InputMap class in which you can bind certain actions (like move left, fire, etc.) to Keyboard, Joystick or Mouse buttons.
+  - Add a field that tracks % of resource deserialization in BinarySerializer
+  - Add GL Texture buffers (They're equivalent to DX11 buffers) - http://www.opengl.org/wiki/Buffer_Texture
+  - Instead of doing setThisPtr on every CoreGpuObject, use intrusive shared_ptr instead?
 
 
 Potential optimizations:
 Potential optimizations:
  - bulkPixelConversion is EXTREMELY poorly unoptimized. Each pixel it calls a separate method that does redudant operations every pixel.
  - bulkPixelConversion is EXTREMELY poorly unoptimized. Each pixel it calls a separate method that does redudant operations every pixel.
@@ -44,7 +63,10 @@ Potential optimizations:
   - Maybe get rid of CPU UI buffers completely.
   - Maybe get rid of CPU UI buffers completely.
  - UI shader resolution params for gui should be in a separate constant buffer
  - UI shader resolution params for gui should be in a separate constant buffer
 
 
- Memory allocation critical areas:
+----------------------------------------------------------------------------------------------
+More detailed thought out system descriptions:
+
+ <<<<Memory allocation critical areas>>>>
  - TextUtility a lot of allocs
  - TextUtility a lot of allocs
  - Binding gpu params. It gets copied in DeferredRenderContext
  - Binding gpu params. It gets copied in DeferredRenderContext
  - GameObjectHandle often allocates its internal data
  - GameObjectHandle often allocates its internal data
@@ -54,8 +76,22 @@ Potential optimizations:
  - Creating SceneObjects and Components - I might want to pool them, as I suspect user might alloc many per frame
  - Creating SceneObjects and Components - I might want to pool them, as I suspect user might alloc many per frame
  - Log logMsg
  - Log logMsg
 
 
-----------------------------------------------------------------------------------------------
-More detailed thought out system descriptions:
+<<Multithreaded GUI rendering>>
+ - Event handling and normal "update" will still be done on the main thread
+ - At the beginning of each frame a GUI mesh update is queued on the GUI thread
+ - Since we're queuing the update at the beggining of the frame we will be using last frames transform and gui element states.
+   - When queing we need to make sure to store GUIWidget transform, and specific element states (e.g. "text" in GUILabel)
+ - At the end of simulation frame wait until GUI update is complete. After both simulation and GUI updates are complete, proceed with submitting it to render system.
+
+<<Figure out how to store texture references in a font>>
+ - Currently I store a copy of the textures but how do I automatically update the font if they change?
+ - Flesh out the dependencies system?
+ - I can import texture as normal, and keep it as an actual TextureHandle, only keep it hidden
+   if it was created automatically (by FontImporter) for example?
+    - But then who deletes the texture?
+	- Set up an "internalResource" system where resources hold references to each other and also release them?
+	 - In inspector they can be expanded as children of the main resource, but cannot be directly modified?
+	 - Deleting the main resource deletes the children too
 
 
 <<<<SceneManager/SceneObject>>>>
 <<<<SceneManager/SceneObject>>>>
 Two major parts:
 Two major parts:
@@ -136,7 +172,6 @@ Questions/Notes:
     - Drawing GUI element bounds when debugging GUI
     - Drawing GUI element bounds when debugging GUI
     - Drawing a wireframe selection effect when a mesh is selected in the scene
     - Drawing a wireframe selection effect when a mesh is selected in the scene
 
 
-
 <<<<Multithreaded memory allocator>>>>
 <<<<Multithreaded memory allocator>>>>
  - Singlethreaded implementation from Game Engine Gems 2 book
  - Singlethreaded implementation from Game Engine Gems 2 book
  - Small and medium allocators separate for each thread. Memory overhead should be minimal considering how small the pages are. But performance benefits are great.
  - Small and medium allocators separate for each thread. Memory overhead should be minimal considering how small the pages are. But performance benefits are great.
@@ -231,4 +266,13 @@ Classes derive from ISharedMemoryBuffer
    - Also make sure to run off a thread pool (WorkQueue class already exists that provides needed interface)
    - Also make sure to run off a thread pool (WorkQueue class already exists that provides needed interface)
   - The way I handle rendering currently is to discard simulation results if gpu thread isn't finished.
   - The way I handle rendering currently is to discard simulation results if gpu thread isn't finished.
      - This reduces input lag but at worst case scenario the effect of multithreading might be completely eliminated as
      - This reduces input lag but at worst case scenario the effect of multithreading might be completely eliminated as
-	   GPU ends up waiting for GPU, just because it was few milliseconds late. Maybe better to wait for GPU?
+	   GPU ends up waiting for GPU, just because it was few milliseconds late. Maybe better to wait for GPU?
+
+
+<<<Localization notes for MUCH LATER>>>
+ - It would be nice if HString identifier hash was being generated at compile time
+ - I still need an easy way to edit the string table (Editor, importer or similar)
+ - I might need font localization for non-standard character sets (e.g. russian, greek, asian, etc.)
+   - I probably don't want to use one huge set of textures containing both latin and asian characters but want to keep them separate
+   - Also asian sets might be too large for textures, in which case generating them at runtime might be necessary (or parsing string table and
+     generating textures from only used characters)

+ 1 - 68
TODO.txt

@@ -9,14 +9,6 @@ GUIWidget::updateMeshes leaks. If I leave the game running I can see memory cont
  - BansheeApplication should probably derive from Camlelot application. Right now user needs to know the difference between 
  - BansheeApplication should probably derive from Camlelot application. Right now user needs to know the difference between 
    gApplication and gBansheeApp, which is non-intuitive (e.g. retrieving a window can be done on gApplication, but running main loop can happen on both
    gApplication and gBansheeApp, which is non-intuitive (e.g. retrieving a window can be done on gApplication, but running main loop can happen on both
 
 
-GUI INPUT REFACTOR:
-Possibly also forward doubleclick from OS?
-
-GUI SYSTEM WRAP UP:
- GUIManager drag events should only trigger after a mouse moves a few pixels
-  - Right now it is breaking double click as start/end drag events get sent out twice
-
- Double click (Input box select all)
  Windows drag and drop detect
  Windows drag and drop detect
   - http://www.codeguru.com/cpp/misc/misc/draganddrop/article.php/c349/Drag-And-Drop-between-Window-Controls.htm
   - http://www.codeguru.com/cpp/misc/misc/draganddrop/article.php/c349/Drag-And-Drop-between-Window-Controls.htm
   - http://www.catch22.net/tuts/drop-target
   - http://www.catch22.net/tuts/drop-target
@@ -49,8 +41,6 @@ GUIDragManager
  - SINCE currently non-active elements ignore drag events, add GUIElement::acceptsMouseDrop property
  - SINCE currently non-active elements ignore drag events, add GUIElement::acceptsMouseDrop property
    - Such elements will receive mouse drag and mouse up events if other element is active, and they can filter if they want to accept it
    - Such elements will receive mouse drag and mouse up events if other element is active, and they can filter if they want to accept it
 
 
-Later add InputMap class in which you can bind certain actions (like move left, fire, etc.) to Keyboard, Joystick or Mouse buttons.
-
 -----------
 -----------
 
 
  - My test model is rendering back faces. I need to flip them.
  - My test model is rendering back faces. I need to flip them.
@@ -60,34 +50,6 @@ Later add InputMap class in which you can bind certain actions (like move left,
 Immediate TODO:
 Immediate TODO:
  - A way to update mesh buffers without recreating vertex/index buffers (Setting data currently does exactly that)
  - A way to update mesh buffers without recreating vertex/index buffers (Setting data currently does exactly that)
 
 
-------------------------------------------------------------------------------------------------
-Longterm plans:
-
-<<Multithreaded GUI rendering>>
- - Event handling and normal "update" will still be done on the main thread
- - At the beginning of each frame a GUI mesh update is queued on the GUI thread
- - Since we're queuing the update at the beggining of the frame we will be using last frames transform and gui element states.
-   - When queing we need to make sure to store GUIWidget transform, and specific element states (e.g. "text" in GUILabel)
- - At the end of simulation frame wait until GUI update is complete. After both simulation and GUI updates are complete, proceed with submitting it to render system.
-
-<<Figure out how to store texture references in a font>>
- - Currently I store a copy of the textures but how do I automatically update the font if they change?
- - Flesh out the dependencies system?
- - I can import texture as normal, and keep it as an actual TextureHandle, only keep it hidden
-   if it was created automatically (by FontImporter) for example?
-    - But then who deletes the texture?
-	- Set up an "internalResource" system where resources hold references to each other and also release them?
-	 - In inspector they can be expanded as children of the main resource, but cannot be directly modified?
-	 - Deleting the main resource deletes the children too
-
-<<MessageBox class>>
- - A good first test case for my GUI windows
-
-<<Other>>
- - There is an issue that custom-UIs won't have their mesh shared. For example most game UIs will be advanced and will 
-   likely use on GUIWidget per element. However currently I only perform batching within a single widget which 
-   doesn't help in the mentioned case.
-
 ----------------------------------------------------------------------------------------------
 ----------------------------------------------------------------------------------------------
 Other:
 Other:
  - Move Debug to CamelotCore and add SetFillMode
  - Move Debug to CamelotCore and add SetFillMode
@@ -100,7 +62,6 @@ High priority:
 
 
 ----------------------------------------------------------------------------------------------
 ----------------------------------------------------------------------------------------------
 Medium priority:
 Medium priority:
- - Add a field that tracks % of resource deserialization in BinarySerializer
  - Mesh loading:
  - Mesh loading:
   - Example Freefall mesh has one index per vertex, and there are 17k+ vertices. I think I need a post-process step that optimizes them.
   - Example Freefall mesh has one index per vertex, and there are 17k+ vertices. I think I need a post-process step that optimizes them.
   - Imported FBX meshes are too big
   - Imported FBX meshes are too big
@@ -150,7 +111,6 @@ Low priority TODO
     due to thread safety and atomics used by shared_ptr. However I still need some guarantee that objects queued in RenderSystem won't be destroyed
     due to thread safety and atomics used by shared_ptr. However I still need some guarantee that objects queued in RenderSystem won't be destroyed
 	by the sim. thread.
 	by the sim. thread.
   - A way to bind buffers to a Pass, while specifying buffer range
   - A way to bind buffers to a Pass, while specifying buffer range
-  - Add GL Texture buffers (They're equivalent to DX11 buffers) - http://www.opengl.org/wiki/Buffer_Texture
   - Better creation of PrimaryWindow
   - Better creation of PrimaryWindow
     - RENDERWINDOWDESC accepts a "externalWindow" flag and an "externalHandle" so when creating the primary window with RenderSystem::initialize we don't always need to create a new window
     - RENDERWINDOWDESC accepts a "externalWindow" flag and an "externalHandle" so when creating the primary window with RenderSystem::initialize we don't always need to create a new window
     - Actually new OpenGL seems to support creating context without a window with the help of wglCreateContextAttribsARB and wglMakeCurrent:
     - Actually new OpenGL seems to support creating context without a window with the help of wglCreateContextAttribsARB and wglMakeCurrent:
@@ -168,43 +128,16 @@ Low priority TODO
    - Material::setParamBlock is commented out
    - Material::setParamBlock is commented out
  - onMovedOrResized is still used by Viewport while that same callback is offered by RenderWindowManager. There is no need to have them in both places.
  - onMovedOrResized is still used by Viewport while that same callback is offered by RenderWindowManager. There is no need to have them in both places.
  - Texture "ScaleToFit" will cause the texture to repeat instead of clipping the image. e.g. a 50x20 texture placed on an 50x100 area will repeat 5x
  - Texture "ScaleToFit" will cause the texture to repeat instead of clipping the image. e.g. a 50x20 texture placed on an 50x100 area will repeat 5x
+
 ----------------------------------------------------------------------------------------------
 ----------------------------------------------------------------------------------------------
 Optional:
 Optional:
  - Need better handling for shader techniques. Some Materials are able to run on all renderers yet I can only specify one. This is problematic
  - Need better handling for shader techniques. Some Materials are able to run on all renderers yet I can only specify one. This is problematic
     for Materials for things like text and sprites, which should run on all renderers, even if user adds a new one
     for Materials for things like text and sprites, which should run on all renderers, even if user adds a new one
  - Add precompiled headers to all projects
  - Add precompiled headers to all projects
- - If possible, make sure GLSL uses EntryPoint and Profile fields I have added to GpuProgram
- - Move all x86 libs to x86 folders. Move all binaries to x86 folders as well
  - Serializable callbacks can't be null otherwise compiler complains
  - Serializable callbacks can't be null otherwise compiler complains
  - FBX importer can be greatly sped up by implementing a better allocator
  - FBX importer can be greatly sped up by implementing a better allocator
  - Extend texture copy so it accepts different subregions & subresources (currently only entire resource can be copied)
  - Extend texture copy so it accepts different subregions & subresources (currently only entire resource can be copied)
  - Need a way to convert MSAA render texture into a normal render texture
  - Need a way to convert MSAA render texture into a normal render texture
  - Vertex buffer start offset is not supported when calling Draw methods
  - Vertex buffer start offset is not supported when calling Draw methods
- - Instead of doing setThisPtr on every CoreGpuObject, use intrusive shared_ptr instead?
- - Renderer::render(CameraPtr) is currently commented out because I moved Camera out of Camelot and I didn't want to bother figuring how to port this
  - When rendering Scale9Grid GUI elements the stretched center will cause linear interpolation to kick in and blend the edges with the border parts of the texture.
  - When rendering Scale9Grid GUI elements the stretched center will cause linear interpolation to kick in and blend the edges with the border parts of the texture.
   - I should use point filtering for scale9grid, but that doesn't work in general case for stretched textures as they would look bad
   - I should use point filtering for scale9grid, but that doesn't work in general case for stretched textures as they would look bad
-
- ----------------------------------------------------------------------------------------------
-After polish and ideas:
- - Each view (i.e. camera) of the scene should be put into its own thread
- - How do I handle multiple mesh formats? Some files need animation, other don't. Some would mabye like to use QTangent, others the proper tangent frame.
-  - Asset postprocessor? Imports a regular mesh using normal importers and then postprocesses it into a specialized format?
- - Load texture mips separately so we can unload HQ textures from far away objects (like UE3)
- - Add Unified shader so I can easily switch between HLSL and GLSL shaders (they need same parameters usually, just different code)
-    - Maybe just add support for Cg and force everyone to use that? - I'd like to be able to just switch out renderer in a single location and that everything keeps on working without 
-	  further modifications.
- - Port boost threads to std threads (CmThreadDefines.h)
- - Remove HardwarePixelBuffer (DX11 doesn't use it, and DX9 and OpenGL textures can be rewritten so they have its methods internally)
- - Multihead device
- - 3D rendering (use low level hardware methods for it)
- - Don't forget to check out Unity DX11 documentation on how to implement DX11 features (http://docs.unity3d.com/Documentation/Manual/DirectX11.html)
- - Go to Game Engine Architecture book and make a list of Utility systems we will need (Config files, Parsers, File I/O etc)
- - Go to GEA book and read about resource managers before implementing them
-   - Actually I should re-read most of the chapers in the book, or all of it
- - OpenGL non-Win32 window files haven't been properly parsed or tested
-   - Since I probably can't compile them, try adding them to VS and see what intellisense says?
- - Textures and all other buffers keep a copy of their data in system memory. If there are memory constraints we might need a way to avoid this.
- - Move all multiplatform files (window creation, cursor, etc.) into a separate PlatformSpecific folder. So anyone porting it to a new platform
-   knows that he only needs to change those files.
- - Make sure my Log system uses XML + HTML