using System; using System.Reflection; namespace ClassicalSharp { // Class used to minimise memory allocations of strings. // Really, only useful for FpsScreen and TextInputWidget as they allocate lots of very small strings. // Seriously, you should *not* use this anywhere else, as Empty and Length are O(N). internal sealed unsafe class StringBuffer { internal string value; internal int capacity; static readonly FieldInfo arrayField, lengthField; static bool supportsLengthSetting; static StringBuffer() { if( OpenTK.Configuration.RunningOnMono ) return; arrayField = typeof( String ).GetField( "m_arrayLength", BindingFlags.NonPublic | BindingFlags.Instance ); lengthField = typeof( String ).GetField( "m_stringLength", BindingFlags.NonPublic | BindingFlags.Instance ); // Make sure we are running on a framework that has both methods - // otherwise we play it safe and just use TrimEnd(). if( arrayField != null && lengthField != null ) { supportsLengthSetting = true; } else { arrayField = null; lengthField = null; } } public StringBuffer( int capacity ) { this.capacity = capacity; value = new String( '\0', capacity ); } public StringBuffer Append( int index, char c ) { fixed( char* ptr = value ) { ptr[index] = c; return this; } } public StringBuffer Append( int index, string s ) { fixed( char* ptr = value ) { char* offsetPtr = ptr + index; return Append( ref offsetPtr, s ); } } public StringBuffer Append( ref char* ptr, string s ) { for( int i = 0; i < s.Length; i++ ) { *ptr++ = s[i]; } return this; } public StringBuffer Append( ref char* ptr, char c) { *ptr++ = c; return this; } static char[] numBuffer = new char[20]; public StringBuffer AppendNum( ref char* ptr, long num ) { int index = 0; numBuffer[index++] = (char)( '0' + ( num % 10 ) ); num /= 10; while( num > 0 ) { numBuffer[index++] = (char)( '0' + ( num % 10 ) ); num /= 10; } for( int i = index - 1; i >= 0; i-- ) { *ptr++ = numBuffer[i]; } return this; } public StringBuffer Clear() { fixed( char* ptr = value ) { return Clear( ptr ); } } public StringBuffer Clear( char* ptr ) { for( int i = 0; i < capacity; i++ ) { *ptr++ = '\0'; } return this; } public void DeleteAt( int index ) { fixed( char* ptr = value ) { char* offsetPtr = ptr + index; for( int i = index; i < capacity - 1; i++ ) { *offsetPtr = *( offsetPtr + 1 ); offsetPtr++; } *offsetPtr = '\0'; } } public void InsertAt( int index, char c ) { fixed( char* ptr = value ) { char* offsetPtr = ptr + capacity - 1; for( int i = capacity - 1; i > index; i-- ) { *offsetPtr = *( offsetPtr - 1 ); offsetPtr--; } *offsetPtr = c; } } public bool Empty { get { fixed( char* ptr = value ) { for( int i = 0; i < capacity; i++ ) { if( ptr[i] != '\0' ) return false; } } return true; } } public int Length { get { int len = capacity; fixed( char* ptr = value ) { for( int i = capacity - 1; i >= 0; i-- ) { if( ptr[i] != '\0' ) break; len--; } } return len; } } /// Hack that modifies the underlying string's length to avoid memory allocations. /// The underlying string - ***do not*** store this because it is mutable! /// You should only use this string for temporary measuring and drawing. public string UpdateCachedString() { return supportsLengthSetting ? SetLength( Length ) : value.TrimEnd( trimEnd ); } static char[] trimEnd = { '\0' }; public override string ToString() { return GetCopy( Length ); } public string GetSubstring( int length ) { return supportsLengthSetting ? SetLength( length ) : GetCopy( length ); } public void RestoreLength() { if( supportsLengthSetting ) SetLength( Length ); } string SetLength( int len ) { arrayField.SetValue( value, len + 1 ); lengthField.SetValue( value, len ); return value; } string GetCopy( int len ) { string copiedString = new String( '\0', len ); fixed( char* src = value, dst = copiedString ) { for( int i = 0; i < len; i++ ) { dst[i] = src[i]; } } return copiedString; } } }