Skip to content

Commit 8a5d511

Browse files
committed
Initial checkin
1 parent c508596 commit 8a5d511

23 files changed

+387
-2
lines changed

Assets/FastString.cs

Lines changed: 284 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,284 @@
1+
using UnityEngine;
2+
using System.Collections.Generic;
3+
using System.Text;
4+
5+
///<summary>
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+
///</summary>
10+
public class FastString
11+
{
12+
///<summary>Immutable string. Generated at last moment, only if needed</summary>
13+
private string m_stringGenerated = "";
14+
///<summary>Is m_stringGenerated is up to date ?</summary>
15+
private bool m_isStringGenerated = false;
16+
17+
///<summary>Working mutable string</summary>
18+
private char[] m_buffer = null;
19+
private int m_bufferPos = 0;
20+
private int m_charsCapacity = 0;
21+
22+
///<summary>Temporary string used for the Replace method</summary>
23+
private List<char> 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+
///<summary>Return the string</summary>
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+
///<summary>Return true if the valueControl has changed (and update it)</summary>
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+
///<summary>Return true if the valueControl has changed (and update it)</summary>
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+
///<summary>Set a string, no memorry allocation</summary>
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+
///<summary>Caution, allocate some memory</summary>
81+
public void Set( object str )
82+
{
83+
Set( str.ToString() );
84+
}
85+
86+
///<summary>Append several params: no memory allocation unless params are of object type</summary>
87+
public void Set<T1, T2>( T1 str1, T2 str2 )
88+
{
89+
Clear();
90+
Append( str1 ); Append( str2 );
91+
}
92+
public void Set<T1, T2, T3>( T1 str1, T2 str2, T3 str3 )
93+
{
94+
Clear();
95+
Append( str1 ); Append( str2 ); Append( str3 );
96+
}
97+
public void Set<T1, T2, T3, T4>( T1 str1, T2 str2, T3 str3, T4 str4 )
98+
{
99+
Clear();
100+
Append( str1 ); Append( str2 ); Append( str3 ); Append( str4 );
101+
}
102+
///<summary>Allocate a little memory (20 byte)</summary>
103+
public void Set( params object[] str )
104+
{
105+
Clear();
106+
for( int i=0; i<str.Length; i++ )
107+
Append( str[ i ] );
108+
}
109+
110+
// Append methods, to build the string without allocation
111+
112+
///<summary>Reset the m_char array</summary>
113+
public FastString Clear()
114+
{
115+
m_bufferPos = 0;
116+
m_isStringGenerated = false;
117+
return this;
118+
}
119+
120+
///<summary>Append a string without memory allocation</summary>
121+
public FastString Append( string value )
122+
{
123+
ReallocateIFN( value.Length );
124+
int n = value.Length;
125+
for( int i=0; i<n; i++ )
126+
m_buffer[ m_bufferPos + i ] = value[ i ];
127+
m_bufferPos += n;
128+
m_isStringGenerated = false;
129+
return this;
130+
}
131+
///<summary>Append an object.ToString(), allocate some memory</summary>
132+
public FastString Append( object value )
133+
{
134+
Append( value.ToString() );
135+
return this;
136+
}
137+
138+
///<summary>Append an int without memory allocation</summary>
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+
///<summary>Append a float without memory allocation.</summary>
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+
///<summary>Replace all occurences of a string by another one</summary>
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<char>();
241+
242+
// Create the new string into m_replacement
243+
for( int i=0; i<m_bufferPos; i++ )
244+
{
245+
bool isToReplace = false;
246+
if( m_buffer[ i ] == oldStr[ 0 ] ) // If first character found, check for the rest of the string to replace
247+
{
248+
int k=1;
249+
while( k < oldStr.Length && m_buffer[ i+k ] == oldStr[ k ] )
250+
k++;
251+
isToReplace = (k >= 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<newStr.Length; k++ )
258+
m_replacement.Add( newStr[ k ] );
259+
}
260+
else // No replacement, copy the old character
261+
m_replacement.Add( m_buffer[ i ] );
262+
}
263+
264+
// Copy back the new string into m_chars
265+
ReallocateIFN( m_replacement.Count - m_bufferPos );
266+
for( int k=0; k<m_replacement.Count; k++ )
267+
m_buffer[ k ] = m_replacement[ k ];
268+
m_bufferPos = m_replacement.Count;
269+
m_replacement.Clear();
270+
m_isStringGenerated = false;
271+
return this;
272+
}
273+
274+
private void ReallocateIFN( int nbCharsToAdd )
275+
{
276+
if( m_bufferPos + nbCharsToAdd > 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

Lines changed: 12 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Assets/FastStringTest.cs

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
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+
}

Assets/FastStringTest.cs.meta

Lines changed: 12 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Assets/FastStringTest.unity

12.3 KB
Binary file not shown.

Assets/FastStringTest.unity.meta

Lines changed: 8 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

ProjectSettings/AudioManager.asset

4.04 KB
Binary file not shown.
4.01 KB
Binary file not shown.
4.18 KB
Binary file not shown.
4.01 KB
Binary file not shown.

0 commit comments

Comments
 (0)