From 0c7dec86e4970dfb23a7389d36159de12cada93f Mon Sep 17 00:00:00 2001 From: UnknownShadow200 Date: Sat, 5 Sep 2015 07:36:51 +1000 Subject: [PATCH] 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. --- .../2D/Screens/BlockSelectScreen.cs | 2 +- ClassicalSharp/2D/Screens/FpsScreen.cs | 2 +- ClassicalSharp/2D/Widgets/TextInputWidget.cs | 13 ++++-- ClassicalSharp/Network/NetworkProcessor.cs | 2 +- ClassicalSharp/Utils/StringBuffer.cs | 43 ++----------------- 5 files changed, 15 insertions(+), 47 deletions(-) diff --git a/ClassicalSharp/2D/Screens/BlockSelectScreen.cs b/ClassicalSharp/2D/Screens/BlockSelectScreen.cs index 4f748071e..6780c8753 100644 --- a/ClassicalSharp/2D/Screens/BlockSelectScreen.cs +++ b/ClassicalSharp/2D/Screens/BlockSelectScreen.cs @@ -130,7 +130,7 @@ namespace ClassicalSharp { Block block = blocksTable[selectedIndex].BlockId; UpdateBlockInfoString( block ); - string value = buffer.UpdateCachedString(); + string value = buffer.GetString(); Size size = Utils2D.MeasureSize( value, font, true ); int x = startX + ( blockSize * blocksPerRow ) / 2 - size.Width / 2; diff --git a/ClassicalSharp/2D/Screens/FpsScreen.cs b/ClassicalSharp/2D/Screens/FpsScreen.cs index d5de28130..2302255ef 100644 --- a/ClassicalSharp/2D/Screens/FpsScreen.cs +++ b/ClassicalSharp/2D/Screens/FpsScreen.cs @@ -38,7 +38,7 @@ namespace ClassicalSharp { .Append( ref ptr2, ", vertices: " ).AppendNum( ref ptr2, game.Vertices ); } - string textString = text.UpdateCachedString(); + string textString = text.GetString(); fpsTextWidget.SetText( textString ); maxDelta = 0; accumulator = 0; diff --git a/ClassicalSharp/2D/Widgets/TextInputWidget.cs b/ClassicalSharp/2D/Widgets/TextInputWidget.cs index 498de49b2..08c6e7753 100644 --- a/ClassicalSharp/2D/Widgets/TextInputWidget.cs +++ b/ClassicalSharp/2D/Widgets/TextInputWidget.cs @@ -40,7 +40,7 @@ namespace ClassicalSharp { X = 10; DrawTextArgs caretArgs = new DrawTextArgs( graphicsApi, "_", Color.White, false ); chatCaretTexture = Utils2D.MakeTextTexture( boldFont, 0, 0, ref caretArgs ); - string value = chatInputText.UpdateCachedString(); + string value = chatInputText.GetString(); if( chatInputText.Empty ) { caretPos = -1; @@ -50,27 +50,32 @@ namespace ClassicalSharp { if( caretPos == -1 ) { chatCaretTexture.X1 = 10 + size.Width; size.Width += chatCaretTexture.Width; + DrawString( size, value, true ); } else { string subString = chatInputText.GetSubstring( caretPos ); Size trimmedSize = Utils2D.MeasureSize( subString, font, false ); - chatInputText.RestoreLength(); chatCaretTexture.X1 = 10 + trimmedSize.Width; Size charSize = Utils2D.MeasureSize( new String( value[caretPos], 1 ), font, false ); chatCaretTexture.Width = charSize.Width; + DrawString( size, value, false ); } + } + + void DrawString( Size size, string value, bool skipCheck ) { size.Height = Math.Max( size.Height, chatCaretTexture.Height ); - int y = game.Height - ChatInputYOffset - size.Height / 2; + using( Bitmap bmp = Utils2D.CreatePow2Bitmap( size ) ) { using( Graphics g = Graphics.FromImage( bmp ) ) { Utils2D.DrawRect( g, backColour, 0, 0, bmp.Width, bmp.Height ); DrawTextArgs args = new DrawTextArgs( graphicsApi, value, Color.White, false ); - args.SkipPartsCheck = true; + args.SkipPartsCheck = skipCheck; Utils2D.DrawText( g, font, ref args, 0, 0 ); } chatInputTexture = Utils2D.Make2DTexture( graphicsApi, bmp, size, 10, y ); } + chatCaretTexture.Y1 = chatInputTexture.Y1; Y = y; Width = size.Width; diff --git a/ClassicalSharp/Network/NetworkProcessor.cs b/ClassicalSharp/Network/NetworkProcessor.cs index ad1dda4d0..4c6fbef12 100644 --- a/ClassicalSharp/Network/NetworkProcessor.cs +++ b/ClassicalSharp/Network/NetworkProcessor.cs @@ -341,7 +341,7 @@ namespace ClassicalSharp { sentWomId = true; } gzipStream = null; - GC.Collect(); + GC.Collect( 0 ); } break; case PacketId.SetBlock: diff --git a/ClassicalSharp/Utils/StringBuffer.cs b/ClassicalSharp/Utils/StringBuffer.cs index 13b3076bd..fee39b6ef 100644 --- a/ClassicalSharp/Utils/StringBuffer.cs +++ b/ClassicalSharp/Utils/StringBuffer.cs @@ -3,34 +3,11 @@ 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 ); @@ -140,11 +117,8 @@ namespace ClassicalSharp { } } - /// 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 ); + public string GetString() { + return value.TrimEnd( trimEnd ); } static char[] trimEnd = { '\0' }; @@ -153,18 +127,7 @@ namespace ClassicalSharp { } 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; + return GetCopy( length ); } string GetCopy( int len ) {