Sadly, it turns out that our memory string optimisation procedures corrupted the headp after the GC was run. Turns out because (I might be wrong though) that the GC appears to try to use leftover string space for heap allocations, which sometimes caused random corruptions but mostly crashes as were directly modifying the underlying string length. Fixes #51.

This commit is contained in:
UnknownShadow200 2015-09-05 07:36:51 +10:00
parent f3306f48fd
commit 0c7dec86e4
5 changed files with 15 additions and 47 deletions

View File

@ -130,7 +130,7 @@ namespace ClassicalSharp {
Block block = blocksTable[selectedIndex].BlockId; Block block = blocksTable[selectedIndex].BlockId;
UpdateBlockInfoString( block ); UpdateBlockInfoString( block );
string value = buffer.UpdateCachedString(); string value = buffer.GetString();
Size size = Utils2D.MeasureSize( value, font, true ); Size size = Utils2D.MeasureSize( value, font, true );
int x = startX + ( blockSize * blocksPerRow ) / 2 - size.Width / 2; int x = startX + ( blockSize * blocksPerRow ) / 2 - size.Width / 2;

View File

@ -38,7 +38,7 @@ namespace ClassicalSharp {
.Append( ref ptr2, ", vertices: " ).AppendNum( ref ptr2, game.Vertices ); .Append( ref ptr2, ", vertices: " ).AppendNum( ref ptr2, game.Vertices );
} }
string textString = text.UpdateCachedString(); string textString = text.GetString();
fpsTextWidget.SetText( textString ); fpsTextWidget.SetText( textString );
maxDelta = 0; maxDelta = 0;
accumulator = 0; accumulator = 0;

View File

@ -40,7 +40,7 @@ namespace ClassicalSharp {
X = 10; X = 10;
DrawTextArgs caretArgs = new DrawTextArgs( graphicsApi, "_", Color.White, false ); DrawTextArgs caretArgs = new DrawTextArgs( graphicsApi, "_", Color.White, false );
chatCaretTexture = Utils2D.MakeTextTexture( boldFont, 0, 0, ref caretArgs ); chatCaretTexture = Utils2D.MakeTextTexture( boldFont, 0, 0, ref caretArgs );
string value = chatInputText.UpdateCachedString(); string value = chatInputText.GetString();
if( chatInputText.Empty ) { if( chatInputText.Empty ) {
caretPos = -1; caretPos = -1;
@ -50,27 +50,32 @@ namespace ClassicalSharp {
if( caretPos == -1 ) { if( caretPos == -1 ) {
chatCaretTexture.X1 = 10 + size.Width; chatCaretTexture.X1 = 10 + size.Width;
size.Width += chatCaretTexture.Width; size.Width += chatCaretTexture.Width;
DrawString( size, value, true );
} else { } else {
string subString = chatInputText.GetSubstring( caretPos ); string subString = chatInputText.GetSubstring( caretPos );
Size trimmedSize = Utils2D.MeasureSize( subString, font, false ); Size trimmedSize = Utils2D.MeasureSize( subString, font, false );
chatInputText.RestoreLength();
chatCaretTexture.X1 = 10 + trimmedSize.Width; chatCaretTexture.X1 = 10 + trimmedSize.Width;
Size charSize = Utils2D.MeasureSize( new String( value[caretPos], 1 ), font, false ); Size charSize = Utils2D.MeasureSize( new String( value[caretPos], 1 ), font, false );
chatCaretTexture.Width = charSize.Width; chatCaretTexture.Width = charSize.Width;
DrawString( size, value, false );
}
} }
size.Height = Math.Max( size.Height, chatCaretTexture.Height );
void DrawString( Size size, string value, bool skipCheck ) {
size.Height = Math.Max( size.Height, chatCaretTexture.Height );
int y = game.Height - ChatInputYOffset - size.Height / 2; int y = game.Height - ChatInputYOffset - size.Height / 2;
using( Bitmap bmp = Utils2D.CreatePow2Bitmap( size ) ) { using( Bitmap bmp = Utils2D.CreatePow2Bitmap( size ) ) {
using( Graphics g = Graphics.FromImage( bmp ) ) { using( Graphics g = Graphics.FromImage( bmp ) ) {
Utils2D.DrawRect( g, backColour, 0, 0, bmp.Width, bmp.Height ); Utils2D.DrawRect( g, backColour, 0, 0, bmp.Width, bmp.Height );
DrawTextArgs args = new DrawTextArgs( graphicsApi, value, Color.White, false ); DrawTextArgs args = new DrawTextArgs( graphicsApi, value, Color.White, false );
args.SkipPartsCheck = true; args.SkipPartsCheck = skipCheck;
Utils2D.DrawText( g, font, ref args, 0, 0 ); Utils2D.DrawText( g, font, ref args, 0, 0 );
} }
chatInputTexture = Utils2D.Make2DTexture( graphicsApi, bmp, size, 10, y ); chatInputTexture = Utils2D.Make2DTexture( graphicsApi, bmp, size, 10, y );
} }
chatCaretTexture.Y1 = chatInputTexture.Y1; chatCaretTexture.Y1 = chatInputTexture.Y1;
Y = y; Y = y;
Width = size.Width; Width = size.Width;

View File

@ -341,7 +341,7 @@ namespace ClassicalSharp {
sentWomId = true; sentWomId = true;
} }
gzipStream = null; gzipStream = null;
GC.Collect(); GC.Collect( 0 );
} break; } break;
case PacketId.SetBlock: case PacketId.SetBlock:

View File

@ -3,34 +3,11 @@ using System.Reflection;
namespace ClassicalSharp { 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 sealed unsafe class StringBuffer {
internal string value; internal string value;
internal int capacity; 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 ) { public StringBuffer( int capacity ) {
this.capacity = capacity; this.capacity = capacity;
value = new String( '\0', capacity ); value = new String( '\0', capacity );
@ -140,11 +117,8 @@ namespace ClassicalSharp {
} }
} }
/// <summary> Hack that modifies the underlying string's length to avoid memory allocations. </summary> public string GetString() {
/// <returns> The underlying string - ***do not*** store this because it is mutable! return value.TrimEnd( trimEnd );
/// You should only use this string for temporary measuring and drawing. </returns>
public string UpdateCachedString() {
return supportsLengthSetting ? SetLength( Length ) : value.TrimEnd( trimEnd );
} }
static char[] trimEnd = { '\0' }; static char[] trimEnd = { '\0' };
@ -153,18 +127,7 @@ namespace ClassicalSharp {
} }
public string GetSubstring( int length ) { public string GetSubstring( int length ) {
return supportsLengthSetting ? SetLength( length ) : GetCopy( length ); return 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 GetCopy( int len ) {