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

View File

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

View File

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

View File

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

View File

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

View File

@ -33,7 +33,7 @@ namespace ClassicalSharp {
Size size = new Size( 0, defaultHeight ); Size size = new Size( 0, defaultHeight );
if( !String.IsNullOrEmpty( text ) ) { if( !String.IsNullOrEmpty( text ) ) {
parts = Utils2D.SplitText( GraphicsApi, text, true ); 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; int x = HorizontalDocking == Docking.LeftOrTop ? XOffset : Window.Width - size.Width - XOffset;

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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