Browse Source

WIP String formatting

Marko Pintera 10 years ago
parent
commit
6efb3a1dee

+ 1 - 0
BansheeUtility/BansheeUtility.vcxproj

@@ -293,6 +293,7 @@
     <ClInclude Include="Include\BsRect3.h" />
     <ClInclude Include="Include\BsServiceLocator.h" />
     <ClInclude Include="Include\BsSpinLock.h" />
+    <ClInclude Include="Include\BsStringFormat.h" />
     <ClInclude Include="Include\BsTaskScheduler.h" />
     <ClInclude Include="Include\BsTestOutput.h" />
     <ClInclude Include="Include\BsTestSuite.h" />

+ 3 - 0
BansheeUtility/BansheeUtility.vcxproj.filters

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

+ 17 - 3
BansheeUtility/Include/BsString.h

@@ -1,18 +1,18 @@
 #pragma once
 
-namespace BansheeEngine 
+namespace BansheeEngine
 {
 	/**
 	 * @brief	Basic string that uses Banshee memory allocators.
 	 */
 	template <typename T>
-	using BasicString = std::basic_string<T, std::char_traits<T>, StdAlloc<T>>;
+	using BasicString = std::basic_string < T, std::char_traits<T>, StdAlloc<T> > ;
 
 	/**
 	 * @brief	Basic string stream that uses Banshee memory allocators.
 	 */
 	template <typename T>
-	using BasicStringStream = std::basic_stringstream<T, std::char_traits<T>, StdAlloc<T>>;
+	using BasicStringStream = std::basic_stringstream < T, std::char_traits<T>, StdAlloc<T> > ;
 
 	/**
 	 * @brief	Wide string used primarily for handling Unicode text.
@@ -35,7 +35,12 @@ namespace BansheeEngine
 	 * 			strings consisting of ASCII text.
 	 */
 	typedef BasicStringStream<char> StringStream;
+}
+
+#include "BsStringFormat.h"
 
+namespace BansheeEngine
+{
     /**
      * @brief	Utility class for manipulating Strings.
      */
@@ -180,6 +185,15 @@ namespace BansheeEngine
          */
 		static const WString replaceAll(const WString& source, const WString& replaceWhat, const WString& replaceWithWhat);
 
+		/**
+		 * @copydoc	StringFormat::format
+		 */
+		template<class T, class... Args>
+		static BasicString<T> format(const T* source, Args&& ...args)
+		{
+			return StringFormat::format(source, std::forward<Args>(args)...);
+		}
+
 		/**
 		 * @brief	Constant blank string, useful for returning by ref where local does not exist.
 		 */

+ 235 - 0
BansheeUtility/Include/BsStringFormat.h

@@ -0,0 +1,235 @@
+namespace BansheeEngine
+{
+	/**
+	 * @brief	Helper class used for string formatting operations.
+	 */
+	class StringFormat
+	{
+	private:
+		/**
+		 * @brief	Data structure used during string formatting. It holds
+		 *			information about parameter identifiers to replace with
+		 *			actual parameters.
+		 */
+		struct FormatParamRange
+		{
+			FormatParamRange() { }
+			FormatParamRange(UINT32 start, UINT32 identifierSize, UINT32 paramIdx)
+				:start(start), identifierSize(identifierSize), paramIdx(paramIdx)
+			{ }
+
+			UINT32 start;
+			UINT32 identifierSize;
+			UINT32 paramIdx;
+		};
+
+		/**
+		 * @brief	Structure that holds value of a parameter during string
+		 *			formatting.
+		 */
+		template<class T>
+		struct ParamData
+		{
+			T* buffer;
+			UINT32 size;
+		};
+
+	public:
+		/**
+		 * @brief	Formats the provided string by replacing the identifiers with the provided parameters.
+		 *			The identifiers are represented like "{0}, {1}" in the source string, where the number
+		 *			represents the position of the parameter that will be used for replacing the identifier.
+		 *			
+		 * @note	You may use "\" to escape identifier brackets.
+		 *			Maximum identifier number is 19 (for a total of 20 unique identifiers. e.g. {20} won't be recognized as an identifier).
+		 *			Total number of parameters that can be referenced is 200.
+		 */
+		template<class T, class... Args>
+		static BasicString<T> format(const T* source, Args&& ...args)
+		{
+			UINT32 strLength = getLength(source);
+
+			ParamData<T> parameters[MAX_PARAMS];
+			memset(parameters, 0, sizeof(parameters));
+			getParams<T>(parameters, 0, std::forward<Args>(args)...);
+
+			T bracketChars[MAX_IDENTIFIER_SIZE + 1];
+			UINT32 bracketWriteIdx = 0;
+
+			FormatParamRange paramRanges[MAX_PARAM_REFERENCES];
+			memset(paramRanges, 0, sizeof(paramRanges));
+			UINT32 paramRangeWriteIdx = 0;
+
+			// Determine parameter positions
+			INT32 lastBracket = -1;
+			bool escaped = false;
+			UINT32 charWriteIdx = 0;
+			for (UINT32 i = 0; i < strLength; i++)
+			{
+				if (source[i] == '\\' && !escaped && paramRangeWriteIdx < MAX_PARAM_REFERENCES)
+				{
+					escaped = true;
+					paramRanges[paramRangeWriteIdx++] = FormatParamRange(charWriteIdx, 1, 0);
+					continue;
+				}
+
+				if (lastBracket == -1)
+				{
+					// If current char is non-escaped opening bracket start parameter definition
+					if (source[i] == '{' && !escaped)
+						lastBracket = i;
+					else
+						charWriteIdx++;
+				}
+				else
+				{
+					if (isdigit(source[i]) && bracketWriteIdx < MAX_IDENTIFIER_SIZE)
+						bracketChars[bracketWriteIdx++] = source[i];
+					else
+					{
+						// If current char is non-escaped closing bracket end parameter definition
+						UINT32 numParamChars = bracketWriteIdx;
+						bool processedBracket = false;
+						if (source[i] == '}' && numParamChars > 0 && !escaped)
+						{
+							bracketChars[bracketWriteIdx] = '\0';
+							UINT32 paramIdx = strToInt(bracketChars);
+							if (paramIdx < MAX_PARAMS && paramRangeWriteIdx < MAX_PARAM_REFERENCES) // Check if exceeded maximum parameter limit
+							{
+								charWriteIdx += parameters[paramIdx].size;
+								paramRanges[paramRangeWriteIdx++] = FormatParamRange(charWriteIdx, numParamChars + 2, paramIdx);
+
+								processedBracket = true;
+							}
+						}
+
+						if (!processedBracket)
+						{
+							// Last bracket wasn't really a parameter
+							for (UINT32 j = lastBracket; j <= i; j++)
+								charWriteIdx++;
+						}
+
+						lastBracket = -1;
+						bracketWriteIdx = 0;
+					}
+				}
+
+				escaped = false;
+			}
+
+			// Copy the clean string into output buffer
+			UINT32 finalStringSize = charWriteIdx;
+
+			T* outputBuffer = (T*)bs_alloc(finalStringSize * sizeof(T));
+			UINT32 copySourceIdx = 0;
+			UINT32 copyDestIdx = 0;
+			for (UINT32 i = 0; i < paramRangeWriteIdx; i++)
+			{
+				const FormatParamRange& rangeInfo = paramRanges[i];
+				UINT32 copySize = rangeInfo.start - copyDestIdx;
+				UINT32 paramSize = parameters[rangeInfo.paramIdx].size;
+
+				memcpy(outputBuffer + copyDestIdx, source + copySourceIdx, copySize);
+				copySourceIdx += copySize + rangeInfo.identifierSize;
+				copyDestIdx += copySize;
+
+				memcpy(outputBuffer + copyDestIdx, parameters[rangeInfo.paramIdx].buffer, paramSize);
+				copyDestIdx += paramSize;
+			}
+
+			memcpy(outputBuffer + copyDestIdx, source + copySourceIdx, finalStringSize - copyDestIdx);
+
+			BasicString<T> outputStr(outputBuffer, finalStringSize);
+			bs_free(outputBuffer);
+
+			for (UINT32 i = 0; i < MAX_PARAMS; i++)
+			{
+				if (parameters[i].buffer != nullptr)
+					bs_free(parameters[i].buffer);
+			}
+
+			return outputStr;
+		}
+
+	private:
+		/**
+		 * @brief	Set of methods that can be specialized so we have a generalized way for retrieving length
+		 *			of strings of different types.
+		 */
+		static UINT32 getLength(const char* source) { return (UINT32)strlen(source); }
+
+		/**
+		 * @brief	Set of methods that can be specialized so we have a generalized way for retrieving length
+		 *			of strings of different types.
+		 */
+		static UINT32 getLength(const wchar_t* source) { return (UINT32)wcslen(source); }
+
+		/**
+		 * @brief	Parses the string and returns an integer value extracted from string characters.
+		 */
+		static UINT32 strToInt(const char* buffer)
+		{
+			return (UINT32)strtoul(buffer, nullptr, 10);
+		}
+
+		/**
+		 * @brief	Parses the string and returns an integer value extracted from string characters.
+		 */
+		static UINT32 strToInt(const wchar_t* buffer)
+		{
+			return (UINT32)wcstoul(buffer, nullptr, 10);
+		}
+
+		/**
+		 * @brief	Converts all the provided parameters into string representations and populates the provided
+		 *			"parameter" array.
+		 */
+		template<class P, class... Args>
+		static void getParams(ParamData<char>* parameters, UINT32 idx, P&& param, Args&& ...args)
+		{
+			if (idx >= MAX_PARAMS)
+				return;
+
+			std::basic_string<char> sourceParam = std::to_string(param);
+			parameters[idx].buffer = (char*)bs_alloc(sourceParam.size() * sizeof(char));
+			parameters[idx].size = (UINT32)sourceParam.size();
+
+			sourceParam.copy(parameters[idx].buffer, parameters[idx].size, 0);
+			
+			getParams(parameters, idx + 1, std::forward<Args>(args)...);
+		}
+
+		/**
+		 * @brief	Converts all the provided parameters into string representations and populates the provided
+		 *			"parameter" array.
+		 */
+		template<class P, class... Args>
+		static void getParams(ParamData<wchar_t>* parameters, UINT32 idx, P&& param, Args&& ...args)
+		{
+			if (idx >= MAX_PARAMS)
+				return;
+
+			std::basic_string<wchar_t> sourceParam = std::to_wstring(param);
+			parameters[idx].buffer = (wchar_t*)bs_alloc(sourceParam.size() * sizeof(wchar_t));
+			parameters[idx].size = (UINT32)sourceParam.size();
+			
+			sourceParam.copy(parameters[idx].buffer, parameters[idx].size, 0);
+
+			getParams(parameters, idx + 1, std::forward<Args>(args)...);
+		}
+
+		/**
+		 * @brief	Helper method for parameter size calculation. Used as a stopping point in template recursion.
+		 */
+		template<class T>
+		static void getParams(ParamData<T>* parameters, UINT32 idx)
+		{
+			// Do nothing
+		}
+
+		static const UINT32 MAX_PARAMS = 20;
+		static const UINT32 MAX_IDENTIFIER_SIZE = 2;
+		static const UINT32 MAX_PARAM_REFERENCES = 200;
+	};
+}

+ 68 - 10
TODO.txt

@@ -9,20 +9,77 @@ Possibly set up automatic refresh in debug mode after initialization? As an ad-h
 ----------------------------------------------------------------------
 Project library
 
-Finish ProjectLibrary script interface (this includes Library and DirectoryEntry classes)
-Create ImportOptions script interface
-Figure out how to deal with C# LibraryEntries (if I just make them c++ wrappers I feel it 
- would be too slow (they'd need to store a path, and then I'd have to lookup that path in ProjectLibrary whenever any property
- - Keep a separate C# version of the entries? It gets upated when ProjectLibrary callbacks are triggered.
- - OR integrate C# entries directly in ProjectLibrary? (i.e. some kind of special field that holds a C# version of it)
- - Performance implications shouldn't matter. It is not something that is called every frame and we're unlikely to iterate over the entire hierarchy. 
-   If such case is needed then I can introduce a more performant specialized wrapper. Therefore C# wrapper should just store a path and then 
-   lookup the entry in ProjectLibrary
-Make sure C# ProjectLibrary triggers same callbacks as its c++ counterpart
+Almost all of PRojectLibrary functionality is completely untested
+
+ProjectLibrary needs a method that searches for all entries with a specific name (or a subset of a name), and all entries of a specific type
+ - Can I make Library/Resource entries faster so I can do this properly from C#?
+
 Figure out how to create a C# BuiltinResources class. It should be able to load arbitrary resources from a non-project folder
  - I should move all the GUI element style creation out of code and make it just pure data (once I have an editor for it)
  - Will also need a debug button to reimport all the built-in resources
 
+----------------------------------------------------------------------
+VisualStudio integration
+
+Need to be able to generate a VS solution/project based on source files in the project
+Open visual studio at a specific file/line
+
+1. Detect all installed VS versions with their paths
+     - 32bit OS: HKEY_LOCAL_MACHINE\SOFTWARE\Wow6432Node\Microsoft\VisualStudio\10.0\InstallDir
+     - 64bit OS: HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\VisualStudio\10.0\InstallDir
+   - In case of Express version replace "VisualStudio" with "VCSExpress"
+   - If "InstallDir" variable doesn't exist then that version isn't installed (its parent key might still exist though) 
+   - TODO - VS Community edition??
+   - Executable for Pro is "devenv.exe" and for Express is vcsexpress.exe and for Community is ??
+2. Detect if we have a VS running with our solution
+   - Need to find CLSID, which is located in same registry folder as "InstallDir" and is named "ThisVersionDTECLSID"
+   - Use EnvDTE to detect and open VS
+    - EnvDTE doesn't work with VSExpress!
+     - Use normal shell command?
+      - devenv /edit FILE_PATH /command "edit.goto FILE_LINE"
+
+None of the new VS functionality has been tested
+
+----------------------------------------------------------------------
+BuiltinResources
+ - All data in ..\..\BuiltinData (if we start in /bin/Release/BansheeEd.exe)
+ - Need BuiltinEngine and BuiltinEditor (separate classes) that internally do pretty much the same thing
+   - Derive them from common base since most of the functionality will be reused
+   - Make sure that commonly used resources are available directly and aren't required to call Load
+ - Also have ..\..\RawBuiltinData where all the non-processed assets are
+ - When compiled as debug, define Preprocess command which goes over all assets in ..\..\RawBuiltinData, imports them and outputs them to ..\..\BuiltinData
+   - Preprocess can be done fully in C++
+
+I need to make a GUISkin a resource so I can save it
+ - It should be editable from the Editor (only when in debug mode) and not generated in code
+ - Then in Preprocess it can just get copied from RawBuiltinData subfolder to the actual folder
+ - Before I have editing capability it can just be generated in Preprocess step
+
+Preprocess will also need to generate any Shaders or SpriteTextures
+ - POSSIBLY wait until I get generate these assets directly in Editor, then just copy them over to Builtin
+ - This way I can fully avoid writing any preprocess code
+
+I still need to be able to access most of these assets from C++ (e.g. GUI element styles)
+ - I need to add BuiltinResources.Load which should allow me to load all needed resources, from C++ or C#
+
+----------------------------------------------------------------------
+Resources
+ - Load/Unload/UnloadUnused
+ - Uses same paths as ProjectLibrary
+ - It uses different logic depending on Application.isEditor
+   - If not in editor then a different path is used
+   - That path is ..\..\Data (relative to the executable)
+   - When in editor it also does special ProjectLibrary check to ensure that resource is included in final project
+ - Internally just calls Resources (C++) Load/Unload/UnloadUnused
+ - Need a flag in ProjectLibrary to include a resource in the final build
+   - Need to be able to set that flag from C# (Likely through ProjectLibrary) interface
+ - The final build procedure for the game would then be:
+   - Copy all the prebuilt binaries (Banshee libraries, Banshee assemblies, 3rd party libraries and prebuilt executable) from Editor install folder to output folder
+    - Which set of binaries is used depends on selected platform (e.g. win/mac/linux or 32/64bit)
+   - Recompile script assemblies if needed and copy them from project Internal folder to output folder
+   - Copy the Builtin resources for engine from Editor install folder to output folder
+   - Copy all the resources marked with the flag mentioned above to \Data subfolder in the output folder, preserving the same asset structure
+
 ----------------------------------------------------------------------
 Simple stuff
 
@@ -35,6 +92,7 @@ ColorPicker
 Other simple stuff:
  - VS integration (open VS project on doubleclick, generate project files, open on specific line)
  - C# wrapper for GUISkin (and a way to assign the current skin to a window)
+ - Need to add IsPointerDoubleClicked to Input (C++ and C#)
 
 ----------------------------------------------------------------------
 Handles