Optimise string allocations, some other reductions in allocations.

This commit is contained in:
UnknownShadow200 2015-05-10 19:53:01 +10:00
parent bb2a55944f
commit 0352a4da4c
13 changed files with 175 additions and 44 deletions

View File

@ -96,8 +96,8 @@ namespace ClassicalSharp {
}
public override void Dispose() {
if( !String.IsNullOrEmpty( textInput.chatInputText ) ) {
Window.chatInInputBuffer = textInput.chatInputText;
if( !textInput.chatInputText.Empty ) {
Window.chatInInputBuffer = textInput.chatInputText.ToString();
}
chatFont.Dispose();
chatBoldFont.Dispose();
@ -143,7 +143,7 @@ namespace ClassicalSharp {
announcementDisplayTime = DateTime.UtcNow;
if( !String.IsNullOrEmpty( text ) ) {
List<DrawTextArgs> parts = Utils2D.SplitText( GraphicsApi, text, true );
Size size = Utils2D.MeasureSize( Utils.StripColours( text ), announcementFont, true );
Size size = Utils2D.MeasureSize( parts, announcementFont, true );
int x = Window.Width / 2 - size.Width / 2;
int y = Window.Height / 4 - size.Height / 2;
announcementTexture = Utils2D.MakeTextTexture( parts, announcementFont, size, x, y );
@ -180,7 +180,8 @@ namespace ClassicalSharp {
void OpenTextInputBar( string initialText ) {
suppressNextPress = true;
HandlesAllInput = true;
textInput.chatInputText = initialText;
textInput.chatInputText.Clear();
textInput.chatInputText.Append( 0, initialText );
textInput.Init();
}

View File

@ -37,7 +37,7 @@ namespace ClassicalSharp {
Texture ModifyText( int yOffset, string text, Font font ) {
List<DrawTextArgs> parts = Utils2D.SplitText( GraphicsApi, text, true );
Size size = Utils2D.MeasureSize( Utils.StripColours( text ), font, true );
Size size = Utils2D.MeasureSize( parts, font, true );
int x = Window.Width / 2 - size.Width / 2;
int y = Window.Height / 2 - yOffset;
return Utils2D.MakeTextTexture( parts, font, size, x, y );

View File

@ -6,8 +6,10 @@ namespace ClassicalSharp {
public class FpsScreen : Screen {
readonly Font font;
UnsafeString text;
public FpsScreen( Game window ) : base( window ) {
font = new Font( "Arial", 13 );
text = new UnsafeString( 96 );
}
TextWidget fpsTextWidget;
@ -17,20 +19,24 @@ namespace ClassicalSharp {
fpsTextWidget.Render( delta );
}
const string formatString = "FPS: {0} (min {1}), chunks/s: {2}, vertices: {3}";
double accumulator, maxDelta;
int fpsCount;
void UpdateFPS( double delta ) {
int fpsCount;
unsafe void UpdateFPS( double delta ) {
fpsCount++;
maxDelta = Math.Max( maxDelta, delta );
accumulator += delta;
long vertices = Window.Vertices;
if( accumulator >= 1 ) {
string text = String.Format( formatString, (int)( fpsCount / accumulator ),
(int)( 1f / maxDelta ), Window.ChunkUpdates, vertices );
fpsTextWidget.SetText( text );
fixed( char* ptr = text.value ) {
char* ptr2 = ptr;
text.Clear( ptr2 )
.Append( ref ptr2, "FPS: " ).AppendNum( ref ptr2, (int)( fpsCount / accumulator ) )
.Append( ref ptr2, " (min " ).AppendNum( ref ptr2, (int)( 1f / maxDelta ) )
.Append( ref ptr2, "), chunks/s: " ).AppendNum( ref ptr2, Window.ChunkUpdates )
.Append( ref ptr2, ", vertices: " ).AppendNum( ref ptr2, Window.Vertices );
}
fpsTextWidget.SetText( text.value );
maxDelta = 0;
accumulator = 0;
fpsCount = 0;

View File

@ -38,7 +38,7 @@ namespace ClassicalSharp {
GroupName = p.GroupName;
GroupRank = p.GroupRank;
List<DrawTextArgs> parts = Utils2D.SplitText( graphics, Name, true );
Size size = Utils2D.MeasureSize( Utils.StripColours( Name ), font, true );
Size size = Utils2D.MeasureSize( parts, font, true );
Texture = Utils2D.MakeTextTexture( parts, font, size, 0, 0 );
}

View File

@ -32,7 +32,7 @@ namespace ClassicalSharp {
Name = p.DisplayName;
PlayerId = p.ID;
List<DrawTextArgs> parts = Utils2D.SplitText( graphics, Name, true );
Size size = Utils2D.MeasureSize( Utils.StripColours( Name ), font, true );
Size size = Utils2D.MeasureSize( parts, font, true );
Texture = Utils2D.MakeTextTexture( parts, font, size, 0, 0 );
}
}

View File

@ -33,7 +33,7 @@ namespace ClassicalSharp {
Size size = new Size( 0, defaultHeight );
if( !String.IsNullOrEmpty( text ) ) {
parts = Utils2D.SplitText( GraphicsApi, text, true );
size = Utils2D.MeasureSize( Utils.StripColours( text ), font, true );
size = Utils2D.MeasureSize( parts, font, true );
}
int x = HorizontalDocking == Docking.LeftOrTop ? XOffset : Window.Width - size.Width - XOffset;

View File

@ -6,7 +6,7 @@ using System.Windows.Forms;
namespace ClassicalSharp {
public sealed class TextInputWidget : Widget {
public TextInputWidget( Game window, Font font, Font boldFont ) : base( window ) {
HorizontalDocking = Docking.LeftOrTop;
VerticalDocking = Docking.BottomOrRight;
@ -19,6 +19,7 @@ namespace ClassicalSharp {
typingLogPos = Window.ChatInputLog.Count; // Index of newest entry + 1.
this.font = font;
this.boldFont = boldFont;
chatInputText = new UnsafeString( 64 );
}
Texture chatInputTexture, chatCaretTexture;
@ -26,7 +27,7 @@ namespace ClassicalSharp {
int caretPos = -1;
int typingLogPos = 0;
public int ChatInputYOffset;
public string chatInputText = "";
internal UnsafeString chatInputText;
readonly Font font, boldFont;
public override void Render( double delta ) {
@ -39,19 +40,20 @@ namespace ClassicalSharp {
X = 10;
DrawTextArgs caretArgs = new DrawTextArgs( GraphicsApi, "_", Color.White, false );
chatCaretTexture = Utils2D.MakeTextTexture( boldFont, 0, 0, ref caretArgs );
string value = chatInputText.value;
if( chatInputText.Length == 0 ) {
if( chatInputText.Empty ) {
caretPos = -1;
}
Size size = Utils2D.MeasureSize( chatInputText, font, false );
Size size = Utils2D.MeasureSize( value, font, false );
if( caretPos == -1 ) {
chatCaretTexture.X1 = 10 + size.Width;
size.Width += chatCaretTexture.Width;
} else {
Size trimmedSize = Utils2D.MeasureSize( chatInputText.Substring( 0, caretPos ), font, false );
Size trimmedSize = Utils2D.MeasureSize( value.Substring( 0, caretPos ), font, false );
chatCaretTexture.X1 = 10 + trimmedSize.Width;
Size charSize = Utils2D.MeasureSize( chatInputText.Substring( caretPos, 1 ), font, false );
Size charSize = Utils2D.MeasureSize( value.Substring( caretPos, 1 ), font, false );
chatCaretTexture.Width = charSize.Width;
}
size.Height = Math.Max( size.Height, chatCaretTexture.Height );
@ -60,7 +62,7 @@ namespace ClassicalSharp {
using( Bitmap bmp = new Bitmap( size.Width, size.Height ) ) {
using( Graphics g = Graphics.FromImage( bmp ) ) {
Utils2D.DrawRect( g, backColour, 0, 0, bmp.Width, bmp.Height );
DrawTextArgs args = new DrawTextArgs( GraphicsApi, chatInputText, Color.White, false );
DrawTextArgs args = new DrawTextArgs( GraphicsApi, value, Color.White, false );
Utils2D.DrawText( g, font, ref args, 0, 0 );
}
chatInputTexture = Utils2D.Make2DTexture( GraphicsApi, bmp, 10, y );
@ -91,9 +93,9 @@ namespace ClassicalSharp {
}
public void SendTextInBufferAndReset() {
Window.SendChat( chatInputText );
Window.SendChat( chatInputText.ToString() );
typingLogPos = Window.ChatInputLog.Count; // Index of newest entry + 1.
chatInputText = "";
chatInputText.Clear();
Dispose();
}
@ -149,9 +151,9 @@ namespace ClassicalSharp {
public override bool HandlesKeyPress( char key ) {
if( chatInputText.Length < 64 && !IsInvalidChar( key ) ) {
if( caretPos == -1 ) {
chatInputText += key;
chatInputText.Append( chatInputText.Length, key );
} else {
chatInputText = chatInputText.Insert( caretPos, new String( key, 1 ) );
chatInputText.InsertAt( caretPos, key );
caretPos++;
}
Dispose();
@ -162,14 +164,14 @@ namespace ClassicalSharp {
}
void BackspaceKey() {
if( chatInputText.Length > 0 ) {
if( !chatInputText.Empty ) {
if( caretPos == -1 ) {
chatInputText = chatInputText.Remove( chatInputText.Length - 1, 1 );
chatInputText.DeleteAt( chatInputText.Length - 1 );
Dispose();
Init();
} else if( caretPos > 0 ) {
caretPos--;
chatInputText = chatInputText.Remove( caretPos, 1 );
chatInputText.DeleteAt( caretPos );
Dispose();
Init();
}
@ -177,8 +179,8 @@ namespace ClassicalSharp {
}
void DeleteKey() {
if( chatInputText.Length > 0 && caretPos != -1 ) {
chatInputText = chatInputText.Remove( caretPos, 1 );
if( !chatInputText.Empty && caretPos != -1 ) {
chatInputText.DeleteAt( caretPos );
if( caretPos >= chatInputText.Length ) caretPos = -1;
Dispose();
Init();
@ -186,7 +188,7 @@ namespace ClassicalSharp {
}
void RightKey() {
if( chatInputText.Length > 0 && caretPos != -1 ) {
if( !chatInputText.Empty && caretPos != -1 ) {
caretPos++;
if( caretPos >= chatInputText.Length ) caretPos = -1;
Dispose();
@ -195,7 +197,7 @@ namespace ClassicalSharp {
}
void LeftKey() {
if( chatInputText.Length > 0 ) {
if( !chatInputText.Empty ) {
if( caretPos == -1 ) caretPos = chatInputText.Length;
caretPos--;
if( caretPos < 0 ) caretPos = 0;
@ -208,7 +210,8 @@ namespace ClassicalSharp {
if( Window.ChatInputLog.Count > 0 ) {
typingLogPos--;
if( typingLogPos < 0 ) typingLogPos = 0;
chatInputText = Window.ChatInputLog[typingLogPos];
chatInputText.Clear();
chatInputText.Append( 0, Window.ChatInputLog[typingLogPos] );
caretPos = -1;
Dispose();
Init();
@ -218,11 +221,11 @@ namespace ClassicalSharp {
void DownKey() {
if( Window.ChatInputLog.Count > 0 ) {
typingLogPos++;
chatInputText.Clear();
if( typingLogPos >= Window.ChatInputLog.Count ) {
typingLogPos = Window.ChatInputLog.Count;
chatInputText = "";
} else {
chatInputText = Window.ChatInputLog[typingLogPos];
chatInputText.Append( 0, Window.ChatInputLog[typingLogPos] );
}
caretPos = -1;
Dispose();
@ -251,17 +254,17 @@ namespace ClassicalSharp {
}
if( caretPos == -1 ) {
chatInputText += text;
chatInputText.Append( chatInputText.Length, text );
} else {
chatInputText = chatInputText.Insert( caretPos, text );
chatInputText.Append( caretPos, text );
caretPos += text.Length;
}
Dispose();
Init();
return true;
} else if( key == Key.C && controlDown ) {
if( !String.IsNullOrEmpty( chatInputText ) ) {
Clipboard.SetText( chatInputText );
if( !chatInputText.Empty ) {
Clipboard.SetText( chatInputText.ToString() );
}
return true;
}

View File

@ -34,7 +34,7 @@ namespace ClassicalSharp {
List<DrawTextArgs> parts = null;
Size size = new Size( 0, defaultHeight );
parts = Utils2D.SplitText( GraphicsApi, text, true );
size = Utils2D.MeasureSize( Utils.StripColours( text ), font, true );
size = Utils2D.MeasureSize( parts, font, true );
X = CalcAdjOffset( XOffset, Window.Width, size.Width, HorizontalDocking );
Y = CalcAdjOffset( YOffset, Window.Height, size.Height, VerticalDocking );

View File

@ -156,6 +156,7 @@
<Compile Include="Utils\FastColour.cs" />
<Compile Include="Utils\TextureAtlas1D.cs" />
<Compile Include="Utils\TextureAtlas2D.cs" />
<Compile Include="Utils\UnsafeString.cs" />
<Compile Include="Utils\Utils.cs" />
</ItemGroup>
<ItemGroup>

View File

@ -39,6 +39,7 @@ namespace ClassicalSharp {
RaiseEvent( ChatReceived, new ChatEventArgs( text, 0 ) );
}
ChatEventArgs chatArgs = new ChatEventArgs( "", 0 );
public void AddChat( string text, byte type ) {
CpeMessageType cpeType = (CpeMessageType)type;
if( cpeType == 0 ) {
@ -59,7 +60,9 @@ namespace ClassicalSharp {
} else if( cpeType == CpeMessageType.Announcement ) {
Announcement = text;
}
RaiseEvent( ChatReceived, new ChatEventArgs( text, type ) );
chatArgs.Text = text;
chatArgs.Type = type;
RaiseEvent( ChatReceived, chatArgs );
}
const string fileNameFormat = "yyyy-MM-dd";

View File

@ -122,7 +122,7 @@ namespace ClassicalSharp {
public sealed class ChatEventArgs : EventArgs {
public readonly byte Type;
public byte Type;
public string Text;

View File

@ -24,7 +24,7 @@ namespace ClassicalSharp.Renderers {
using( Font font = new Font( "Arial", 14 ) ) {
List<DrawTextArgs> parts = Utils2D.SplitText( Graphics, player.DisplayName, true );
Size size = Utils2D.MeasureSize( Utils.StripColours( player.DisplayName ), font, true );
Size size = Utils2D.MeasureSize( parts, font, true );
nameTexture = Utils2D.MakeTextTexture( parts, font, size, 0, 0 );
nameWidth = size.Width;
nameHeight = size.Height;

117
Utils/UnsafeString.cs Normal file
View File

@ -0,0 +1,117 @@
using System;
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 unsafe class UnsafeString {
internal string value;
internal int capacity;
public UnsafeString( int capacity ) {
this.capacity = capacity;
value = new String( '\0', capacity );
}
public UnsafeString Append( int index, char c ) {
fixed( char* ptr = value ) {
ptr[index] = c;
return this;
}
}
public UnsafeString Append( int index, string s ) {
fixed( char* ptr = value ) {
char* offsetPtr = ptr + index;
return Append( ref offsetPtr, s );
}
}
public UnsafeString Append( ref char* ptr, string s ) {
for( int i = 0; i < s.Length; i++ ) {
*ptr++ = s[i];
}
return this;
}
static char[] numBuffer = new char[20];
public UnsafeString 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 UnsafeString Clear() {
fixed( char* ptr = value ) {
return Clear( ptr );
}
}
public UnsafeString 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++;
}
}
}
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 = ptr + index;
*offsetPtr = c;
}
}
public bool Empty {
get {
for( int i = 0; i < capacity; i++ ) {
if( value[i] != '\0' )
return false;
}
return true;
}
}
public int Length {
get {
int len = capacity;
for( int i = capacity - 1; i >= 0; i-- ) {
if( value[i] != '\0' )
break;
len--;
}
return len;
}
}
public override string ToString() {
return value.TrimEnd( '\0' );
}
}
}