├── .gitignore ├── Assets ├── FastString.cs ├── FastString.cs.meta ├── FastStringTest.cs ├── FastStringTest.cs.meta ├── FastStringTest.unity └── FastStringTest.unity.meta ├── LICENSE ├── ProjectSettings ├── AudioManager.asset ├── ClusterInputManager.asset ├── DynamicsManager.asset ├── EditorBuildSettings.asset ├── EditorSettings.asset ├── GraphicsSettings.asset ├── InputManager.asset ├── NavMeshAreas.asset ├── NetworkManager.asset ├── Physics2DSettings.asset ├── ProjectSettings.asset ├── ProjectVersion.txt ├── QualitySettings.asset ├── TagManager.asset ├── TimeManager.asset └── UnityConnectSettings.asset └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | /[Ll]ibrary/ 2 | /[Tt]emp/ 3 | /[Oo]bj/ 4 | /[Bb]uild/ 5 | /[Bb]uilds/ 6 | /Assets/AssetStoreTools* 7 | 8 | # Autogenerated VS/MD solution and project files 9 | ExportedObj/ 10 | *.csproj 11 | *.unityproj 12 | *.sln 13 | *.suo 14 | *.tmp 15 | *.user 16 | *.userprefs 17 | *.pidb 18 | *.booproj 19 | *.svd 20 | 21 | 22 | # Unity3D generated meta files 23 | *.pidb.meta 24 | 25 | # Unity3D Generated File On Crash Reports 26 | sysinfo.txt 27 | 28 | # Builds 29 | *.apk 30 | *.unitypackage 31 | -------------------------------------------------------------------------------- /Assets/FastString.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | /// 6 | /// Mutable String class, optimized for speed and memory allocations while retrieving the final result as a string. 7 | /// Similar use than StringBuilder, but avoid a lot of allocations done by StringBuilder (conversion of int and float to string, frequent capacity change, etc.) 8 | /// Author: Nicolas Gadenne contact@gaddygames.com 9 | /// 10 | public class FastString 11 | { 12 | ///Immutable string. Generated at last moment, only if needed 13 | private string m_stringGenerated = ""; 14 | ///Is m_stringGenerated is up to date ? 15 | private bool m_isStringGenerated = false; 16 | 17 | ///Working mutable string 18 | private char[] m_buffer = null; 19 | private int m_bufferPos = 0; 20 | private int m_charsCapacity = 0; 21 | 22 | ///Temporary string used for the Replace method 23 | private List m_replacement = null; 24 | 25 | private object m_valueControl = null; 26 | private int m_valueControlInt = int.MinValue; 27 | 28 | public FastString( int initialCapacity = 32 ) 29 | { 30 | m_buffer = new char[ m_charsCapacity = initialCapacity ]; 31 | } 32 | 33 | public bool IsEmpty() 34 | { 35 | return (m_isStringGenerated ? (m_stringGenerated == null) : (m_bufferPos == 0)); 36 | } 37 | 38 | ///Return the string 39 | public override string ToString() 40 | { 41 | if( !m_isStringGenerated ) // Regenerate the immutable string if needed 42 | { 43 | m_stringGenerated = new string( m_buffer, 0, m_bufferPos ); 44 | m_isStringGenerated = true; 45 | } 46 | return m_stringGenerated; 47 | } 48 | 49 | // Value controls methods: use a value to check if the string has to be regenerated. 50 | 51 | ///Return true if the valueControl has changed (and update it) 52 | public bool IsModified( int newControlValue ) 53 | { 54 | bool changed = (newControlValue != m_valueControlInt); 55 | if( changed ) 56 | m_valueControlInt = newControlValue; 57 | return changed; 58 | } 59 | 60 | ///Return true if the valueControl has changed (and update it) 61 | public bool IsModified( object newControlValue ) 62 | { 63 | bool changed = !(newControlValue.Equals( m_valueControl )); 64 | if( changed ) 65 | m_valueControl = newControlValue; 66 | return changed; 67 | } 68 | 69 | // Set methods: 70 | 71 | ///Set a string, no memorry allocation 72 | public void Set( string str ) 73 | { 74 | // We fill the m_chars list to manage future appends, but we also directly set the final stringGenerated 75 | Clear(); 76 | Append( str ); 77 | m_stringGenerated = str; 78 | m_isStringGenerated = true; 79 | } 80 | ///Caution, allocate some memory 81 | public void Set( object str ) 82 | { 83 | Set( str.ToString() ); 84 | } 85 | 86 | ///Append several params: no memory allocation unless params are of object type 87 | public void Set( T1 str1, T2 str2 ) 88 | { 89 | Clear(); 90 | Append( str1 ); Append( str2 ); 91 | } 92 | public void Set( T1 str1, T2 str2, T3 str3 ) 93 | { 94 | Clear(); 95 | Append( str1 ); Append( str2 ); Append( str3 ); 96 | } 97 | public void Set( T1 str1, T2 str2, T3 str3, T4 str4 ) 98 | { 99 | Clear(); 100 | Append( str1 ); Append( str2 ); Append( str3 ); Append( str4 ); 101 | } 102 | ///Allocate a little memory (20 byte) 103 | public void Set( params object[] str ) 104 | { 105 | Clear(); 106 | for( int i=0; iReset the m_char array 113 | public FastString Clear() 114 | { 115 | m_bufferPos = 0; 116 | m_isStringGenerated = false; 117 | return this; 118 | } 119 | 120 | ///Append a string without memory allocation 121 | public FastString Append( string value ) 122 | { 123 | ReallocateIFN( value.Length ); 124 | int n = value.Length; 125 | for( int i=0; iAppend an object.ToString(), allocate some memory 132 | public FastString Append( object value ) 133 | { 134 | Append( value.ToString() ); 135 | return this; 136 | } 137 | 138 | ///Append an int without memory allocation 139 | public FastString Append( int value ) 140 | { 141 | // Allocate enough memory to handle any int number 142 | ReallocateIFN( 16 ); 143 | 144 | // Handle the negative case 145 | if( value < 0 ) 146 | { 147 | value = -value; 148 | m_buffer[ m_bufferPos++ ] = '-'; 149 | } 150 | 151 | // Copy the digits in reverse order 152 | int nbChars = 0; 153 | do 154 | { 155 | m_buffer[ m_bufferPos++ ] = (char)('0' + value%10); 156 | value /= 10; 157 | nbChars++; 158 | } while( value != 0 ); 159 | 160 | // Reverse the result 161 | for( int i=nbChars/2-1; i>=0; i-- ) 162 | { 163 | char c = m_buffer[ m_bufferPos-i-1 ]; 164 | m_buffer[ m_bufferPos-i-1 ] = m_buffer[ m_bufferPos-nbChars+i ]; 165 | m_buffer[ m_bufferPos-nbChars+i ] = c; 166 | } 167 | m_isStringGenerated = false; 168 | return this; 169 | } 170 | 171 | ///Append a float without memory allocation. 172 | public FastString Append( float valueF ) 173 | { 174 | double value = valueF; 175 | m_isStringGenerated = false; 176 | ReallocateIFN( 32 ); // Check we have enough buffer allocated to handle any float number 177 | 178 | // Handle the 0 case 179 | if( value == 0 ) 180 | { 181 | m_buffer[ m_bufferPos++ ] = '0'; 182 | return this; 183 | } 184 | 185 | // Handle the negative case 186 | if( value < 0 ) 187 | { 188 | value = -value; 189 | m_buffer[ m_bufferPos++ ] = '-'; 190 | } 191 | 192 | // Get the 7 meaningful digits as a long 193 | int nbDecimals = 0; 194 | while( value < 1000000 ) 195 | { 196 | value *= 10; 197 | nbDecimals++; 198 | } 199 | long valueLong = (long)System.Math.Round( value ); 200 | 201 | // Parse the number in reverse order 202 | int nbChars = 0; 203 | bool isLeadingZero = true; 204 | while( valueLong != 0 || nbDecimals >= 0 ) 205 | { 206 | // We stop removing leading 0 when non-0 or decimal digit 207 | if( valueLong%10 != 0 || nbDecimals <= 0 ) 208 | isLeadingZero = false; 209 | 210 | // Write the last digit (unless a leading zero) 211 | if( !isLeadingZero ) 212 | m_buffer[ m_bufferPos + (nbChars++) ] = (char)('0' + valueLong%10); 213 | 214 | // Add the decimal point 215 | if( --nbDecimals == 0 && !isLeadingZero ) 216 | m_buffer[ m_bufferPos + (nbChars++) ] = '.'; 217 | 218 | valueLong /= 10; 219 | } 220 | m_bufferPos += nbChars; 221 | 222 | // Reverse the result 223 | for( int i=nbChars/2-1; i>=0; i-- ) 224 | { 225 | char c = m_buffer[ m_bufferPos-i-1 ]; 226 | m_buffer[ m_bufferPos-i-1 ] = m_buffer[ m_bufferPos-nbChars+i ]; 227 | m_buffer[ m_bufferPos-nbChars+i ] = c; 228 | } 229 | 230 | return this; 231 | } 232 | 233 | ///Replace all occurences of a string by another one 234 | public FastString Replace( string oldStr, string newStr ) 235 | { 236 | if( m_bufferPos == 0 ) 237 | return this; 238 | 239 | if( m_replacement == null ) 240 | m_replacement = new List(); 241 | 242 | // Create the new string into m_replacement 243 | for( int i=0; i= oldStr.Length); 252 | } 253 | if( isToReplace ) // Do the replacement 254 | { 255 | i += oldStr.Length-1; 256 | if( newStr != null ) 257 | for( int k=0; k m_charsCapacity ) 277 | { 278 | m_charsCapacity = System.Math.Max( m_charsCapacity + nbCharsToAdd, m_charsCapacity * 2 ); 279 | char[] newChars = new char[ m_charsCapacity ]; 280 | m_buffer.CopyTo( newChars, 0 ); 281 | m_buffer = newChars; 282 | } 283 | } 284 | } -------------------------------------------------------------------------------- /Assets/FastString.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: c5d083d6e8d0cb44787efcd750b8a7d7 3 | timeCreated: 1493820788 4 | licenseType: Pro 5 | MonoImporter: 6 | serializedVersion: 2 7 | defaultReferences: [] 8 | executionOrder: 0 9 | icon: {instanceID: 0} 10 | userData: 11 | assetBundleName: 12 | assetBundleVariant: 13 | -------------------------------------------------------------------------------- /Assets/FastStringTest.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | using UnityEngine.Profiling; 3 | 4 | public class FastStringTest : MonoBehaviour 5 | { 6 | private FastString m_strCustom = new FastString(64); 7 | 8 | private System.Text.StringBuilder m_strBuilder = new System.Text.StringBuilder(64); 9 | 10 | private delegate string Test(); 11 | 12 | private string String_Added() 13 | { 14 | string str = "PI=" + Mathf.PI + "_373=" + 373; 15 | return str.Replace("373", "5428"); 16 | } 17 | 18 | private string String_Concat() 19 | { 20 | return string.Concat("PI=", Mathf.PI, "_373=", 373).Replace("373", "5428"); 21 | } 22 | 23 | private string StringBuilder() 24 | { 25 | m_strBuilder.Length = 0; 26 | m_strBuilder.Append("PI=").Append(Mathf.PI).Append("_373=").Append(373).Replace("373", "5428"); 27 | return m_strBuilder.ToString(); 28 | } 29 | 30 | private string FastString() 31 | { 32 | m_strCustom.Clear(); 33 | m_strCustom.Append("PI=").Append(Mathf.PI).Append("_373=").Append(373).Replace("373", "5428"); 34 | return m_strCustom.ToString(); 35 | } 36 | 37 | private void RunTest(string testName, Test test) 38 | { 39 | Profiler.BeginSample(testName); 40 | string lastResult = null; 41 | for (int i = 0; i < 1000; i++) 42 | lastResult = test(); 43 | Profiler.EndSample(); 44 | Debug.Log( "Check test result: test=" + testName + " result='" + lastResult + "' (" + lastResult.Length + ")" ); 45 | } 46 | 47 | private void Start() 48 | { 49 | Debug.Log("================="); 50 | RunTest("Test #1: string (+) ", String_Added); 51 | RunTest("Test #2: string (.concat) ", String_Concat); 52 | RunTest("Test #3: StringBuilder ", StringBuilder); 53 | RunTest("Test #4: FastString", FastString); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /Assets/FastStringTest.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 5ed4b9528a97ded49b30ba4e1ad16201 3 | timeCreated: 1493820788 4 | licenseType: Pro 5 | MonoImporter: 6 | serializedVersion: 2 7 | defaultReferences: [] 8 | executionOrder: 0 9 | icon: {instanceID: 0} 10 | userData: 11 | assetBundleName: 12 | assetBundleVariant: 13 | -------------------------------------------------------------------------------- /Assets/FastStringTest.unity: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/snozbot/FastString/e43f8323afd4aaaf30badd0e6292cd3ebaead70e/Assets/FastStringTest.unity -------------------------------------------------------------------------------- /Assets/FastStringTest.unity.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: e216bd9c88d5fc34794518120de7bb30 3 | timeCreated: 1493821317 4 | licenseType: Pro 5 | DefaultImporter: 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 snozbot 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /ProjectSettings/AudioManager.asset: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/snozbot/FastString/e43f8323afd4aaaf30badd0e6292cd3ebaead70e/ProjectSettings/AudioManager.asset -------------------------------------------------------------------------------- /ProjectSettings/ClusterInputManager.asset: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/snozbot/FastString/e43f8323afd4aaaf30badd0e6292cd3ebaead70e/ProjectSettings/ClusterInputManager.asset -------------------------------------------------------------------------------- /ProjectSettings/DynamicsManager.asset: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/snozbot/FastString/e43f8323afd4aaaf30badd0e6292cd3ebaead70e/ProjectSettings/DynamicsManager.asset -------------------------------------------------------------------------------- /ProjectSettings/EditorBuildSettings.asset: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/snozbot/FastString/e43f8323afd4aaaf30badd0e6292cd3ebaead70e/ProjectSettings/EditorBuildSettings.asset -------------------------------------------------------------------------------- /ProjectSettings/EditorSettings.asset: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/snozbot/FastString/e43f8323afd4aaaf30badd0e6292cd3ebaead70e/ProjectSettings/EditorSettings.asset -------------------------------------------------------------------------------- /ProjectSettings/GraphicsSettings.asset: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/snozbot/FastString/e43f8323afd4aaaf30badd0e6292cd3ebaead70e/ProjectSettings/GraphicsSettings.asset -------------------------------------------------------------------------------- /ProjectSettings/InputManager.asset: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/snozbot/FastString/e43f8323afd4aaaf30badd0e6292cd3ebaead70e/ProjectSettings/InputManager.asset -------------------------------------------------------------------------------- /ProjectSettings/NavMeshAreas.asset: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/snozbot/FastString/e43f8323afd4aaaf30badd0e6292cd3ebaead70e/ProjectSettings/NavMeshAreas.asset -------------------------------------------------------------------------------- /ProjectSettings/NetworkManager.asset: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/snozbot/FastString/e43f8323afd4aaaf30badd0e6292cd3ebaead70e/ProjectSettings/NetworkManager.asset -------------------------------------------------------------------------------- /ProjectSettings/Physics2DSettings.asset: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/snozbot/FastString/e43f8323afd4aaaf30badd0e6292cd3ebaead70e/ProjectSettings/Physics2DSettings.asset -------------------------------------------------------------------------------- /ProjectSettings/ProjectSettings.asset: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/snozbot/FastString/e43f8323afd4aaaf30badd0e6292cd3ebaead70e/ProjectSettings/ProjectSettings.asset -------------------------------------------------------------------------------- /ProjectSettings/ProjectVersion.txt: -------------------------------------------------------------------------------- 1 | m_EditorVersion: 5.6.0f3 2 | -------------------------------------------------------------------------------- /ProjectSettings/QualitySettings.asset: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/snozbot/FastString/e43f8323afd4aaaf30badd0e6292cd3ebaead70e/ProjectSettings/QualitySettings.asset -------------------------------------------------------------------------------- /ProjectSettings/TagManager.asset: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/snozbot/FastString/e43f8323afd4aaaf30badd0e6292cd3ebaead70e/ProjectSettings/TagManager.asset -------------------------------------------------------------------------------- /ProjectSettings/TimeManager.asset: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/snozbot/FastString/e43f8323afd4aaaf30badd0e6292cd3ebaead70e/ProjectSettings/TimeManager.asset -------------------------------------------------------------------------------- /ProjectSettings/UnityConnectSettings.asset: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/snozbot/FastString/e43f8323afd4aaaf30badd0e6292cd3ebaead70e/ProjectSettings/UnityConnectSettings.asset -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # FastString 2 | Alternative to the [StringBuilder class](https://msdn.microsoft.com/en-us/library/system.text.stringbuilder(v=vs.110).aspx) for Unity games, with minimal memory allocation and faster performance. 3 | Based on [FastString](https://www.reddit.com/r/Unity3D/comments/3zz62z/alternative_to_stringbuilder_without_memory/) by Nicolas Gadenne of [Gaddy Games](http://gaddygames.com/site/) 4 | 5 | The class has been designed to be most useful in common game situations: a concatenation of a few string and data, then used by a Unity api method as an immutable string - every frame. 6 | It handles Append() and Replace() without doing any allocation, except for very rare capacity augmentation (contrary to StringBuilder which surprisingly does capacity change very often). 7 | It also appends float and int numbers without any allocation. 8 | 9 | The only common memory allocation is when you retrieve the final immutable string - but this is only done when required. 10 | 11 | # Running the tests 12 | 13 | A Unity project is provided for testing the performance. 14 | 15 | - To run the performance tests, open the FastStringTest scene and the Profiler ( Window > Profiler ). 16 | - Run the scene in the editor and then stop it again. 17 | - Enter 'Test' in the profile search to see the profile results for each technique. 18 | --------------------------------------------------------------------------------