Xbox LIVE Indie Games
Sort Discussions: Previous Discussion Next Discussion
Page 1 of 1 (10 posts)

NTStringBuilder (No Trash) - In your FACE Garbage Collector!

Last post 1/1/2011 6:23 PM by Zombie Monkey Games. 9 replies.
  • 7/6/2010 9:57 PM

    NTStringBuilder (No Trash) - In your FACE Garbage Collector!

    This included class is a wrapper, housing it's own internal string builder which is created when the class is allocated. The class allows you to access the string stored in the builder without a ToString() call - no garbage. In addition I went ahead and included SpaceDude's AppendNumber functionality, allowing you to also easily append numbers to the class's string builder without creating garbage. The result? A single class that allows you to append strings, add numbers to strings and update strings every single frame with no garbage. Great for debuggers, UI, or...whatever.

    Thanks to:


    Now go make your real time debugger that doesn't generate any garbage with all its precise numbers. =)

        public class NTStringBuilder 
        { 
            /// <summary> 
            /// This is the reflected pointer to the actual string our string builder is modifying, which means that calls to it don't require toString and don't make garbage! 
            /// </summary> 
            public string String 
            { 
                get 
                { 
                    return _string; 
                } 
            } 
            private string _string; 
     
            /// <summary> 
            /// The actual string builder used by the string. We can get it and append directly if needed, but we can't reassign it or it'll mess up the pointer. 
            /// </summary> 
            public StringBuilder StringBuilder 
            { 
                get 
                { 
                    return _stringBuilder; 
                } 
            } 
            private StringBuilder _stringBuilder; 
            static char[] numberBuffer = new char[16]; 
     
            /// <summary> 
            /// Declares a new No Trash String Builder. 
            /// </summary> 
            /// <param name="capacity">The starting and max capacity that the internal string builder should be set to. Only make it as big as you need.</param> 
            public NTStringBuilder(int capacity) 
            { 
                _stringBuilder = new StringBuilder(capacity,capacity); 
                _string = (string)_stringBuilder.GetType().GetField( 
        "m_StringValue", BindingFlags.NonPublic | BindingFlags.Instance) 
        .GetValue(_stringBuilder); 
            } 
             
            /// <summary> 
            /// Appends a number to the string without creating garbage. 
            /// </summary> 
            /// <param name="number">The number to append.</param> 
            public void AppendNumber(int number) 
            { 
                AppendNumber(number, 0); 
            } 
     
            /// <summary> 
            /// Appends a number to the string without creating garbage, allows you to specify the minimum digits. 
            /// </summary> 
            /// <param name="number">The number to append.</param> 
            /// <param name="minDigits">The minimum number of digits.</param> 
            public void AppendNumber(int number, int minDigits) 
            { 
                if (number < 0) 
                { 
                    _stringBuilder.Append('-'); 
                    number = -number; 
                } 
                int index = 0; 
                do 
                { 
                    int digit = number % 10; 
                    numberBuffer[index] = (char)('0' + digit); 
                    number /= 10; 
                    ++index; 
                } while (number > 0 || index < minDigits); 
                for (--index; index >= 0; --index) 
                { 
                    _stringBuilder.Append(numberBuffer[index]); 
                } 
            } 
     
            /// <summary> 
            /// Exposes the internal string builder's Append to make things a bit easier. 
            /// </summary> 
            /// <param name="Text"></param> 
            public void Append(string Text) 
            { 
                _stringBuilder.Append(Text); 
            } 
     
            public void Append(string text1, string text2) 
            { 
                _stringBuilder.Append(text1); 
                _stringBuilder.Append(text2); 
            } 
     
            public void Append(string text1, string text2, string text3) 
            { 
                _stringBuilder.Append(text1); 
                _stringBuilder.Append(text2); 
                _stringBuilder.Append(text3); 
            } 
     
            public void Append(string text1, string text2, string text3, string text4) 
            { 
                _stringBuilder.Append(text1); 
                _stringBuilder.Append(text2); 
                _stringBuilder.Append(text3); 
                _stringBuilder.Append(text4); 
            } 
     
            public void Append(string text1, string text2, string text3, string text4, string text5) 
            { 
                _stringBuilder.Append(text1); 
                _stringBuilder.Append(text2); 
                _stringBuilder.Append(text3); 
                _stringBuilder.Append(text4); 
                _stringBuilder.Append(text5); 
            } 
     
            public void Clear() 
            { 
                _stringBuilder.Remove(0, _stringBuilder.Length); 
            } 
        } 

  • 7/6/2010 10:03 PM In reply to

    Re: NTStringBuilder (No Trash) - In your FACE Garbage Collector!

    A link to the sample project can be found here.

    This includes the basic Garbage collector sampler I built the concept from. Originally the class generated 1.5 MB of garbage in about 30 seconds. Currently, it creates absolutely none. =)
  • 7/8/2010 11:31 AM In reply to

    Re: NTStringBuilder (No Trash) - In your FACE Garbage Collector!

    :)

    What do you need the StringBuilder's ToString() method (or a workaround) for anyway? SpriteBatch.DrawString() has an overload that allows you to directly draw the text in a StringBuilder without going over ToString().

    Unless you're trying to use the string for something in which case this hack might be dangerous (as all of .NET considers strings to be immutable, so when you put that string you extracted from the StringBuilder into a Dictionary<string, foo>, the results might be... interesting.)

    If interested, I can even offer a chunk of code that appends floats and doubles to a StringBuilder without generating garbage: StringBuilderHelper.cs and StringBuilderHelper.Tests.cs (unit tests). It also a contains a shorter method to append ints and longs to a StringBuilder.

  • 7/8/2010 1:16 PM In reply to

    Re: NTStringBuilder (No Trash) - In your FACE Garbage Collector!

    Might take a look - so far I haven't had any issues with it. Really its all your flavor of tea.

    Cygon4:
    What do you need the StringBuilder's ToString() method (or a workaround) for anyway? SpriteBatch.DrawString() has an overload that allows you to directly draw the text in a StringBuilder without going over ToString().


    What I'm using to draw strings is a complex method that adds background depth to my text, borders, etc where a string is passed in, so I can't just pass in stringbuilder for a draw. Well actually, potentially I could, but I enjoy my organized object so far. 

    Cygon4:
    Unless you're trying to use the string for something in which case this hack might be dangerous (as all of .NET considers strings to be immutable, so when you put that string you extracted from the StringBuilder into a Dictionary<string, foo>, the results might be... interesting.)


    You are correct that it could be dangerous if you attempt to modify the string - part of why I didn't put a set on it. I see your point but if your being safe with it then there's no worries. Really I only use it to draw numbers onto the screen. Everything else is pulled from static declared strings that generate no garbage anyhow. If you try to assign the string outside of a draw call then yes, things would get sticky and you would probably see errors. So far I only use it for building a large debug output. 

    Cygon4:
    If interested, I can even offer a chunk of code that appends floats and doubles to a StringBuilder without generating garbage: StringBuilderHelper.cs and StringBuilderHelper.Tests.cs (unit tests). It also a contains a shorter method to append ints and longs to a StringBuilder.


    I'll definitely take a look at your helper though and probably integrate your versions of the methods into what I'm using. I will need longs for my experience values so I can certainly appreciate that functionality.

    To me having direct access to the internal string for use works fine. If I need to copy it to a dictionary I can still use the ToString() method to make a perfectly safe copy. I have no idea why I would do that though since my intent is to store everything as integers. Makes for smaller save files and faster lookups.
  • 7/8/2010 1:23 PM In reply to

    Re: NTStringBuilder (No Trash) - In your FACE Garbage Collector!

    Looks cool, this is exactly what I needed about a month ago :) Seeing as I've already found methods around said garbage, I'll have to wait for my next game to try this out.
  • 10/21/2010 8:31 AM In reply to

    Re: NTStringBuilder (No Trash) - In your FACE Garbage Collector!

    Works great!

    You might wanna add which references that are needed
    using System.Text;
    using System.Reflection;
  • 12/8/2010 9:55 PM In reply to

    Re: NTStringBuilder (No Trash) - In your FACE Garbage Collector!

    Sorry about reviving an old thread, but this is probably the best place I could get some help.

    For some reason, this doesn't work in 4.0.  Specifically, this line:

    _string = (

     

    string)_stringBuilder.GetType().GetField(

     

     

     

    "m_StringValue", BindingFlags.NonPublic | BindingFlags.Instance)

     

    .GetValue(_stringBuilder);


    It gives a NullReferenceException.

    Can someone help?
  • 12/8/2010 10:37 PM In reply to

    Re: NTStringBuilder (No Trash) - In your FACE Garbage Collector!

    This code uses private reflection to access internal fields of a system type. That's not recommended, and (as you just discovered) not likely to work the same way from one framework version to the next. For bonus points, this won't work at all on Windows Phone.

    Like Cygon said, why not just pass the StringBuilder directly to SpriteBatch.Draw? If you care about garbage, you should do all your text processing directly with StringBuilder objects. No need to extract a String from them at all.
  • 1/1/2011 6:22 PM In reply to

    Re: NTStringBuilder (No Trash) - In your FACE Garbage Collector!

    Ran into this today - here's my merged NTStringBuilder object, which still exposes methods for properly handling ints, as well as longs and a 2 decimal float now. This version only has the string builder exposed and no longer reflects to the string. I had to rewire a few internal methods to use the StringBuilder directly when measuring and drawing, but after that its all the same. Use it if you wish - I still like it for easy int appending.

    public class NTStringBuilder 
        { 
            /// <summary> 
            /// The actual string builder used by the string. We can get it and append directly if needed, but we can't reassign it or it'll mess up the pointer. 
            /// </summary> 
            public StringBuilder StringBuilder 
            { 
                get 
                { 
                    return _stringBuilder; 
                } 
            } 
            private StringBuilder _stringBuilder; 
            private char[] numberBuffer = new char[25]; 
     
            /// <summary> 
            /// Declares a new No Trash String Builder. 
            /// </summary> 
            /// <param name="capacity">The starting and max capacity that the internal string builder should be set to. Only make it as big as you need.</param> 
            public NTStringBuilder(int capacity) 
            { 
                _stringBuilder = new StringBuilder(capacity, capacity); 
            } 
     
            /// <summary> 
            /// Appends a number to the string without creating garbage. 
            /// </summary> 
            /// <param name="number">The number to append.</param> 
            public void AppendNumber(int number) 
            { 
                AppendNumber(number, 0); 
            } 
     
            /// <summary> 
            /// Appends a long number to the string without creating garbage. 
            /// </summary> 
            /// <param name="number">The long number to append.</param> 
            /// <param name="addCommas">Whether or not to add commas to the number.</param> 
            public void AppendNumber(long number, bool addCommas) 
            { 
                if (number < 0) 
                { 
                    _stringBuilder.Append('-'); 
                    number = -number; 
                } 
                int index = 0; 
                do 
                { 
                    long digit = number % 10; 
                    if ((index + 1) % 4 == 0) 
                    { 
                        numberBuffer[index] = (char)(','); 
                        ++index; 
                    } 
                    numberBuffer[index] = (char)('0' + digit); 
                    number /= 10; 
                    ++index; 
                } while (number > 0); 
                for (--index; index >= 0; --index) 
                { 
                    _stringBuilder.Append(numberBuffer[index]); 
                } 
            } 
     
            /// <summary> 
            /// Appends a 2 decimal floating point number without creating garbage. 
            /// </summary> 
            /// <param name="number">The float to append.</param> 
            public void AppendNumber(float number) 
            { 
                number *= 100f; 
                if (number < 0) 
                { 
                    _stringBuilder.Append('-'); 
                    number = -number; 
                } 
                float original = number; 
                int index = 0; 
                do 
                { 
                    if (index == 2) 
                    { 
                        numberBuffer[index] = (char)('.'); 
                        ++index; 
                    } 
                    int digit = (int)number % 10; 
                    numberBuffer[index] = (char)('0' + digit); 
                    number /= 10; 
                    ++index; 
                } while (number > 0.99f); 
                if (original < 100) 
                { 
                    if (original < 10) 
                    { 
                        numberBuffer[index] = (char)('0'); 
                        ++index; 
                    } 
                    numberBuffer[index] = (char)('.'); 
                    ++index; 
                    numberBuffer[index] = (char)('0'); 
                    ++index; 
                } 
                for (--index; index >= 0; --index) 
                { 
                    _stringBuilder.Append(numberBuffer[index]); 
                } 
            } 
     
            /// <summary> 
            /// Appends a long number without creating garbage. 
            /// </summary> 
            /// <param name="number">The number to append.</param> 
            /// <param name="minDigits">The minimum number of digits, will add 0's to the front of the number.</param> 
            public void AppendNumber(long number, int minDigits) 
            { 
                if (number < 0) 
                { 
                    _stringBuilder.Append('-'); 
                    number = -number; 
                } 
                int index = 0; 
                do 
                { 
                    long digit = number % 10; 
                    numberBuffer[index] = (char)('0' + digit); 
                    number /= 10; 
                    ++index; 
                } while (number > 0 || index < minDigits); 
                for (--index; index >= 0; --index) 
                { 
                    _stringBuilder.Append(numberBuffer[index]); 
                } 
            } 
     
            /// <summary> 
            /// Exposes the internal string builder's Append to make things a bit easier. 
            /// </summary> 
            /// <param name="Text"></param> 
            public void Append(string Text) 
            { 
                _stringBuilder.Append(Text); 
            } 
     
            public void Append(string text1, string text2) 
            { 
                _stringBuilder.Append(text1); 
                _stringBuilder.Append(text2); 
            } 
     
            public void Append(string text1, string text2, string text3) 
            { 
                _stringBuilder.Append(text1); 
                _stringBuilder.Append(text2); 
                _stringBuilder.Append(text3); 
            } 
     
            public void Append(string text1, string text2, string text3, string text4) 
            { 
                _stringBuilder.Append(text1); 
                _stringBuilder.Append(text2); 
                _stringBuilder.Append(text3); 
                _stringBuilder.Append(text4); 
            } 
     
            public void Append(string text1, string text2, string text3, string text4, string text5) 
            { 
                _stringBuilder.Append(text1); 
                _stringBuilder.Append(text2); 
                _stringBuilder.Append(text3); 
                _stringBuilder.Append(text4); 
                _stringBuilder.Append(text5); 
            } 
     
            /// <summary> 
            /// Clears out the string builder. 
            /// </summary> 
            public void Clear() 
            { 
                _stringBuilder.Remove(0, _stringBuilder.Length); 
            } 
     
            /// <summary> 
            /// Appends a number to the string without creating garbage, allows you to specify the minimum digits. 
            /// </summary> 
            /// <param name="number">The number to append.</param> 
            /// <param name="minDigits">The minimum number of digits.</param> 
            public void AppendNumber(int number, int minDigits) 
            { 
                if (number < 0) 
                { 
                    _stringBuilder.Append('-'); 
                    number = -number; 
                } 
                int index = 0; 
                do 
                { 
                    int digit = number % 10; 
                    numberBuffer[index] = (char)('0' + digit); 
                    number /= 10; 
                    ++index; 
                } while (number > 0 || index < minDigits); 
                for (--index; index >= 0; --index) 
                { 
                    _stringBuilder.Append(numberBuffer[index]); 
                } 
            } 
        } 

  • 1/1/2011 6:23 PM In reply to

    Re: NTStringBuilder (No Trash) - In your FACE Garbage Collector!

    *double post*
Page 1 of 1 (10 posts) Previous Discussion Next Discussion