Memory allocated when I press keyboard

I’m running a cross-platform project and while I was implementing movement, I noticed that when I left a key on the keyboard pressed for a couple of seconds, it was generating a few kilobytes of garbage. When I tried moving with the controller the problem did not occur.
So I created an empty cross-platform project without any code and was able to find that pressing a key on the keyboard many times (via a lot of typing, or just by leaving keys pressed for some time) actually does generate garbage even though I’m not calling any methods in my Update(…).
Is this a normal behaviour and should I just ignore it?
I know the amount of garbage is probably negligible, but it still bothers me.

Are you using MG 3.6 or later? I just skimmed over the code and don’t see any allocations (I’m on mobile so maybe not 100% thorough :stuck_out_tongue: ). The only thing I can think of is if there are keys that a KeyDown event is triggered for, but no KeyUp event. That would grow the list of keys that a are down very large. But I doubt a bug as big as that is in SDL.

You could run a memory profiler if you care enough.

I’m using the official 3.6 build.I’m using System.GC.GetTotalMemory(false) for simple profiling (And I made sure that printing numbers on screen wasn’t creating any garbage).
You can get the simple test project file here: Link to Google Drive

For me, the number went up when I pressed any key for a few seconds.

Edit: In my project I used willmotil’s StringBuilder class to bypass allocation for printing the memory on the screen.

Ya that’s my old stringbuilder class it bypasses a flaw in c# itself. Using the frame rate class i made with it makes it easier to see when and were garbage is being allocated created and collected on the fly.

See a few posts down i have separated out 4 classes to a single game1.cs project demo to be copy pasted.

Because its in straight c# code it can just be copy pasted to either a gl or dx project.

1 Like

I added the credit for use of your StringBuilder.

Any chance you could help me with the issue where pressing keyboard buttons seem to generate garbage?

On the version im looking at dx is generating garbage even when the user is doing nothing at all.
Gl looks ok.

True, there is some allocation when you start pressing keys. But it is a one-off allocation in KeyboardUtils for a Dictionary. The class constructor for KeyboardUtils populates the dictionary and is executed on first use of the class. This is when KeyboardUtils.ToXna() is called in response to keyboard events polled from SDL. So if you don’t press any keys, the KeyboardUtils class constructor is never called, and the dictionary is never populated.

This was found by running your sample project through ANTS Memory Profiler 8.

Thanks for the experiment KonajuGames. But are you sure it’s only a one-off allocation?
Because for me the longer I am holding any key pressed the more garbage seems to be allocated.
I might have to use the trial of ANTS Memory Profiler 8 to find out myself.

I held keys down, tapped keys continuously. There was no continuous memory allocation.

I downloaded the nightly a couple days ago for my test win7 vs2017…

The results can be seen visually in the monogame game1 demo below in real time output the information is displayed in the game window as seen in the gif.
Pressing keys or doing nothing at all in a windows project dx or a open project gl give differing results. I cannot test this on the phone though.


Gl is Not generating a steady heartbeat of garbage nor are repeated similar keypreses generating garbage.

Dx is generating garbage doing nothing at all. Repeated keypresses will generate additional garbage as well. (if i remember right this is a problem with sharp dx)

To replicate the above test

Copy Paste the below code into a project named Game1 or your own project and change the namespace to that…

The code for the test is shown in full below using a Game1 class, the No Garbage MgStringBuilder class, the FrameRate class and a Timer class. These four classes are placed in a Game1.cs file in succession for simplicity and a content generated spritefont is added thru the content pipeline (MgGenFont) as i have done, shown in the LoadContent method of the given Game1 class.

using System;
using System.Text;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;

namespace Game1
{

//the game 1 class

public class Game1 : Game
{
    GraphicsDeviceManager graphics;
    SpriteBatch spriteBatch;
    SpriteFont font;
    MgFrameRate frameRateAndGc;
    public Game1()
    {
        graphics = new GraphicsDeviceManager(this);
        Content.RootDirectory = "Content";
        Window.AllowUserResizing = true;
        this.IsFixedTimeStep = true;
        this.TargetElapsedTime = TimeSpan.FromSeconds(1d / 60);
        this.IsMouseVisible = true;
        //this.Window.ClientSizeChanged += HeyCallMeWhenTheUserResizesTheWindow;
    }
    /// <summary>
    /// Allows the game to perform any initialization it needs to before starting to run.
    /// This is where it can query for any required services and load any non-graphic
    /// related content.  Calling base.Initialize will enumerate through any components
    /// and initialize them as well.
    /// </summary>
    protected override void Initialize()
    {
        // TODO: Add your initialization logic here
        base.Initialize();
    }
    /// <summary>
    /// LoadContent will be called once per game and is the place to load
    /// all of your content.
    /// </summary>
    protected override void LoadContent()
    {
        // Create a new SpriteBatch, which can be used to draw textures.
        spriteBatch = new SpriteBatch(GraphicsDevice);
        // TODO: use this.Content to load your game content here
        font = Content.Load<SpriteFont>("MgGenFont");
        frameRateAndGc = new MgFrameRate(font);
    }
    /// <summary>
    /// UnloadContent will be called once per game and is the place to unload
    /// game-specific content.
    /// </summary>
    protected override void UnloadContent()
    {
        // TODO: Unload any non ContentManager content here
    }
    /// <summary>
    /// Allows the game to run logic such as updating the world,
    /// checking for collisions, gathering input, and playing audio.
    /// </summary>
    /// <param name="gameTime">Provides a snapshot of timing values.</param>
    protected override void Update(GameTime gameTime)
    {
        if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed || Keyboard.GetState().IsKeyDown(Keys.Escape))
            Exit();
        // TODO: Add your update logic here
        frameRateAndGc.Update(gameTime);
        base.Update(gameTime);
    }
    /// <summary>
    /// This is called when the game should draw itself.
    /// </summary>
    /// <param name="gameTime">Provides a snapshot of timing values.</param>
    protected override void Draw(GameTime gameTime)
    {
        GraphicsDevice.Clear(Color.CornflowerBlue);
        // TODO: Add your drawing code here
        spriteBatch.Begin();
        frameRateAndGc.Draw(spriteBatch, gameTime);
        spriteBatch.End();
        base.Draw(gameTime);
    }
}

The frame rate class…

/// <summary>
/// Example class to demonstrate no garbage numerical text, 
/// this gives basic game info about frame and update rates as well as gc information.
/// this also lets you set most of the framerate related stuff up.
/// </summary>
public class MgFrameRate
{
    #region _region initial variable and object declarations
    public GraphicsDevice graphics;
    public SpriteBatch spriteBatch;
    public SpriteFont currentFont;
    public MgTimer timer = new MgTimer(1f);
    public bool DisplayFrameRate
    {
        get { return displayFrameRate; }
        set { displayFrameRate = value; }
    }
    public bool UpdateDisplayOnGcChange
    {
        get { return updateDisplayOnGcChange; }
        set { updateDisplayOnGcChange = value; }
    }
    public bool OnlyUpdateDisplayOnGcChange
    {
        get { return onlyUpdateDisplayOnGcChange; }
        set { onlyUpdateDisplayOnGcChange = value; }
    }
    public int UpdatesBetweenMsgs
    {
        get { return updatesbetweenmsgs; }
        set { updatesbetweenmsgs = value; }
    }
    public double DesiredFramesPerSecond
    {
        get { return desiredframespersecond; }
        set { desiredframespersecond = value; }
    }
    // Moccasin (255, 228, 190, 255) // cadet blue 95,160,160,255
    //
    public Color textColor = Color.Black;
    public Color backGroundColor = new Color(64, 64, 64, 100);
    public Color outlineColor = new Color(25, 25, 25, 128);
    private bool displayFrameRate = true;
    private bool updateDisplayOnGcChange = false;
    private bool onlyUpdateDisplayOnGcChange = false; // overriding value
    private double desiredframespersecond = 60;
    private int updatesbetweenmsgs = 60;
    //private static StringBuilder sb_perf_msg = new StringBuilder(2048);
    private static MgStringBuilder mgsb_perf_msg = new MgStringBuilder(2048);
    //bool hascycled = false;
    private double timeRecordedLast = 0d;
    private double timeRecordedNow = 1d;
    private double elapsed = 01d;
    private float fractionsOfASecond = 0f;
    private double targetsecs = .08d;
    private double updates = .01;
    private double frames = .01;
    private double fps = 1f;
    private double averageFps = 01d;
    private int samplesize = 10;
    private double[] fpsSamples;
    private int fpsSamplePointer = 0;
    private long gcnow = 0;
    private long gclast = 0;
    private long gcdiff = 0;
    private long gctotalinc = 0;
    private long gctotallost = 0;
    private long gcrecordedcollects = 0;
    //
    private MgStringBuilder sb_elapsed_time_msg = new StringBuilder("  Time Elapsed (Seconds): ");
    private MgStringBuilder sb_time_measured_msg = new StringBuilder("\n  Time per Measure: ");
    private MgStringBuilder sb_updates_msg = new StringBuilder("\n  Updates Measured: ");
    private MgStringBuilder sb_draws_msg = new StringBuilder("\n  Draws Measured: ");
    private MgStringBuilder sb_drawupdateratio_msg = new StringBuilder("\n  Draw To Update Ratio: ");
    private MgStringBuilder sb_time_fps_msg = new StringBuilder("\n  Fps: ");
    private MgStringBuilder sb_time_avgfps_msg = new StringBuilder("\n  Sampled Fps: ");
    private MgStringBuilder sb_time_cycledseconds_msg = new StringBuilder("\n  Three second timer: ");
    private MgStringBuilder sb_targettime_msg = new StringBuilder("\n  TargetTime (Seconds): ");
    private MgStringBuilder sb_gc_now_msg = new StringBuilder("\n  GC Memory(Kb) Now: ");
    private MgStringBuilder sb_gc_diff_msg = new StringBuilder("\n  GC Memory(Kb) Increased: ");
    private MgStringBuilder sb_gc_lost_msg = new StringBuilder("\n  GC Memory(Kb) Lost: ");
    private MgStringBuilder sb_gc_totalinc_msg = new StringBuilder("\n  GC Memory(Kb) TotalInc: ");
    private MgStringBuilder sb_gc_totallost_msg = new StringBuilder("\n  GC Memory(Kb) TotalLost: ");
    private MgStringBuilder sb_gc_recordedcollects_msg = new StringBuilder("\n  GC Memory collects: ");
    #endregion
    public SpriteFont Font
    {
        set { currentFont = value; }
        private get { return currentFont; }
    }
    public MgFrameRate(SpriteFont sf)
    {
        currentFont = sf;
        //sb_perf_msg = new StringBuilder(2048);
        mgsb_perf_msg = new MgStringBuilder(2048);
        fpsSamples = new double[samplesize];
    }
    public MgFrameRate(Game passin_this, GraphicsDeviceManager gdm, SpriteBatch sb, SpriteFont sf, float desiredframerate, bool vysnc, bool fixedon)
    {
        //sb_perf_msg = new StringBuilder(2048);
        mgsb_perf_msg = new MgStringBuilder(2048);
        SetUpFrameRate(passin_this, gdm, sb, sf, desiredframerate, vysnc, fixedon);
    }
    /// <summary>
    /// Its probably best to call in load;
    /// For the Game parameter pass in the this keyword
    /// Fixed timestep must be set to true to get the desired framerate
    /// Beware your graphics cards global settings. 
    /// They can override monogames settings and give strange results
    /// </summary>
    public void SetUpFrameRate(Game passin_this, GraphicsDeviceManager gdm, SpriteBatch sb, SpriteFont sf, float desiredframerate, bool vysnc, bool fixedon)
    {
        // note you graphics card can override your settings
        graphics = passin_this.GraphicsDevice;
        spriteBatch = sb;
        currentFont = sf;
        targetsecs = passin_this.TargetElapsedTime.TotalSeconds;
        this.DesiredFramesPerSecond = desiredframerate;
        passin_this.TargetElapsedTime = TimeSpan.FromSeconds((double)(1.0d / desiredframerate));
        //
        passin_this.IsFixedTimeStep = fixedon;
        gdm.SynchronizeWithVerticalRetrace = vysnc;
        fpsSamples = new double[samplesize];
        //graphics.GraphicsDevice.PresentationParameters.PresentationInterval = PresentInterval.Default;
    }
    public void SetUpFrameRate(Game passin_this, GraphicsDeviceManager gdm, SpriteBatch sb, SpriteFont sf, float desiredframerate, bool vysnc, bool fixedon, Color textcolor)
    {
        // note you graphics card can override your settings
        graphics = passin_this.GraphicsDevice;
        spriteBatch = sb;
        currentFont = sf;
        targetsecs = passin_this.TargetElapsedTime.TotalSeconds;
        this.DesiredFramesPerSecond = desiredframerate;
        passin_this.TargetElapsedTime = TimeSpan.FromSeconds((double)(1.0d / desiredframerate));
        //
        passin_this.IsFixedTimeStep = fixedon;
        gdm.SynchronizeWithVerticalRetrace = vysnc;
        textColor = textcolor;
        fpsSamples = new double[samplesize];
        //graphics.GraphicsDevice.PresentationParameters.PresentationInterval = PresentInterval.Default;
    }
    public void SetUpFrameRate(Game passin_this, GraphicsDeviceManager gdm, SpriteBatch sb, SpriteFont sf, float desiredframerate, int updates_between_msgs, bool vysnc, bool fixedon, Color textcolor, bool updatedisplayOnGcChange, bool updateDisplayOnGcChangeOnly)
    {
        // note you graphics card can override your settings
        graphics = passin_this.GraphicsDevice;
        spriteBatch = sb;
        currentFont = sf;
        targetsecs = passin_this.TargetElapsedTime.TotalSeconds;
        this.DesiredFramesPerSecond = desiredframerate;
        passin_this.TargetElapsedTime = TimeSpan.FromSeconds((double)(1.0d / desiredframerate));
        UpdatesBetweenMsgs = updates_between_msgs;
        passin_this.IsFixedTimeStep = fixedon;
        gdm.SynchronizeWithVerticalRetrace = vysnc;
        textColor = textcolor;
        UpdateDisplayOnGcChange = updatedisplayOnGcChange;
        OnlyUpdateDisplayOnGcChange = updateDisplayOnGcChangeOnly;
        fpsSamples = new double[samplesize];
    }
    /// <summary>
    /// Updates msgs.
    /// draw should be called to display the framerate
    /// </summary>
    public void Update(GameTime gameTime)
    {
        timer.Update(gameTime);
        if (DisplayFrameRate && ChangeResult(gameTime))
        {
            MyStringBuilderMsg(gameTime);
        }
        updates += 1;
    }
    /// <summary>
    /// Draws msgs.
    /// update needs to also be called to get a proper framerate display. 
    /// e.g. like you would in a actual game.
    /// </summary>
    public void Draw(SpriteBatch spriteBatch, GameTime gameTime)
    {
        Draw(spriteBatch, gameTime, 10f, 10f, textColor);
    }
    public void Draw(SpriteBatch spriteBatch, GameTime gameTime, float x, float y)
    {
        Draw(spriteBatch, gameTime, 10f, 10f, textColor);
    }
    public void Draw(SpriteBatch spriteBatch, GameTime gameTime, float x, float y, Color color)
    {
        if (DisplayFrameRate)
        {
            //spriteBatch.DrawString(currentFont, mgsb_perf_msg.StringBuilder, new Vector2(x, y), textColor);
            spriteBatch.DrawString(currentFont, mgsb_perf_msg, new Vector2(x, y), color);
        }
        frames += 1;
    }
    private bool ChangeResult(GameTime gameTime)
    {
        gcnow = GC.GetTotalMemory(false);
        gcdiff = gcnow - gclast;
        if (OnlyUpdateDisplayOnGcChange)
        {
            if (gcdiff != 0)
                return true;
            else
                return false;
        }
        else
        {
            if (UpdateDisplayOnGcChange)
            {
                if (updates >= UpdatesBetweenMsgs || gcdiff != 0)
                    return true;
                else
                    return false;
            }
            else
            {
                if (updates >= UpdatesBetweenMsgs)
                    return true;
                else
                    return false;
            }
        }
    }
    private void MyStringBuilderMsg(GameTime gameTime)
    {
        timeRecordedLast = timeRecordedNow;
        timeRecordedNow = gameTime.TotalGameTime.TotalSeconds;
        elapsed = timeRecordedNow;
        //
        double timeMeasuredTotalSeconds = (timeRecordedNow - timeRecordedLast);
        fps = frames / timeMeasuredTotalSeconds;
        //
        float ratio = (float)frames / (float)updates;
        //
        fractionsOfASecond += (float)timeMeasuredTotalSeconds * .33f;
        if (fractionsOfASecond > 1f || timeRecordedNow < 3)
        {
            fractionsOfASecond = fractionsOfASecond - 1f;
            if (fractionsOfASecond < 0) { fractionsOfASecond = 0f; }
            // take a sample
            fpsSamplePointer++;
            if (fpsSamplePointer >= samplesize) { fpsSamplePointer = 0; }
            fpsSamples[fpsSamplePointer] = fps;
            averageFps = 0;
            for (int i = 0; i < samplesize; i++) { averageFps += fpsSamples[i]; }
            averageFps /= samplesize;
        }
        //
        mgsb_perf_msg.Length = 0;
        //
        mgsb_perf_msg.Append(sb_elapsed_time_msg).AppendTrim(elapsed);
        mgsb_perf_msg.Append(sb_time_measured_msg).AppendTrim(timeMeasuredTotalSeconds);
        mgsb_perf_msg.Append(sb_updates_msg).Append((int)updates);
        mgsb_perf_msg.Append(sb_draws_msg).Append((int)frames);
        mgsb_perf_msg.Append(sb_drawupdateratio_msg).AppendTrim(ratio);
        mgsb_perf_msg.Append(sb_time_fps_msg).AppendTrim(fps);
        mgsb_perf_msg.Append(sb_time_avgfps_msg).AppendTrim(averageFps);
        mgsb_perf_msg.Append(sb_time_cycledseconds_msg).AppendTrim(fractionsOfASecond);
        mgsb_perf_msg.Append(sb_targettime_msg).AppendTrim(targetsecs);
        mgsb_perf_msg.Append(sb_gc_now_msg).AppendTrim(gcnow / 1000d);
        if (gcdiff < 0)
        {
            mgsb_perf_msg.Append(sb_gc_lost_msg).AppendTrim((double)(gcdiff) / 1024d);
            //
            if (timeRecordedNow > 10)
            {
                gctotallost += gcdiff;
                gcrecordedcollects++;
            }
        }
        else
        {
            mgsb_perf_msg.Append(sb_gc_diff_msg).AppendTrim((double)(gcdiff) / 1024d);
            //
            if (timeRecordedNow > 10)
            {
                gctotalinc += gcdiff;
            }
        }
        mgsb_perf_msg.Append(sb_gc_totalinc_msg).AppendTrim(gctotalinc / 1024d);
        mgsb_perf_msg.Append(sb_gc_totallost_msg).AppendTrim(gctotallost / 1024d);
        mgsb_perf_msg.Append(sb_gc_recordedcollects_msg).AppendTrim(gcrecordedcollects);
        frames = .01;
        updates = .01;
        gclast = gcnow;
    }
}

The no garbage stringbuilder class as well…

public sealed class MgStringBuilder
{
    private static char decimalseperator = '.';
    private static char minus = '-';
    private static char plus = '+';
    // im considering pulling this, 
    // its a hack solution that can still fail 
    // for a edge case that is rare.
    private static StringBuilder last;
    private StringBuilder sb;
    // this is a property to ensure copy by value reference.
    // seen summary notes
    /// <summary>
    /// It is recommended you avoid this unless needed. 
    /// it is possible to create garbage with it.
    /// </summary>
    public StringBuilder StringBuilder
    {
        get
        {
            return sb;
        }
        private set
        {
            sb = value;
            /*last = sb;*/
        }
    }
    public int Length
    {
        get { return StringBuilder.Length; }
        set { StringBuilder.Length = value; }
    }
    public int Capacity
    {
        get { return StringBuilder.Capacity; }
        set { StringBuilder.Capacity = value; }
    }
    public void Clear()
    {
        Length = 0;
        sb.Length = 0;
    }
    // constructors
    public MgStringBuilder()
    {
        StringBuilder = StringBuilder;
        if (sb == null) { sb = new StringBuilder(); }
        //if (last == null) { last = new StringBuilder(); }
    }
    public MgStringBuilder(int capacity)
    {
        StringBuilder = new StringBuilder(capacity);
        if (sb == null) { sb = new StringBuilder(); }
        //if (last == null) { last = new StringBuilder(); }
    }
    public MgStringBuilder(StringBuilder sb)
    {
        StringBuilder = sb;
        if (sb == null) { sb = new StringBuilder(); }
        //if (last == null) { last = new StringBuilder(); }
    }
    public MgStringBuilder(string s)
    {
        StringBuilder = new StringBuilder(s);
        if (sb == null) { sb = new StringBuilder(); }
        //if (last == null) { last = new StringBuilder(); }
    }
    public static void CheckSeperator()
    {
        decimalseperator = Convert.ToChar(System.Globalization.CultureInfo.CurrentCulture.NumberFormat.NumberDecimalSeparator);
    }
    // operators
    public static implicit operator MgStringBuilder(StringBuilder sb)
    {
        return new MgStringBuilder(sb);
    }
    public static implicit operator StringBuilder(MgStringBuilder msb)
    {
        return msb.StringBuilder;
    }
    public static MgStringBuilder operator +(MgStringBuilder sbm, MgStringBuilder s)
    {
        sbm.StringBuilder.Append(s);
        return sbm;
    }
    //test
    public static MgStringBuilder operator +(MgStringBuilder sbm, string s)
    {
        sbm.StringBuilder.Append(s);
        return sbm;
    }
    public void AppendAt(int index, StringBuilder s)
    {
        int len = this.StringBuilder.Length;
        int reqcapacity = (index + s.Length + 1) - this.StringBuilder.Capacity;
        if (reqcapacity > 0)
            this.StringBuilder.Capacity += reqcapacity;
        int initialLength = StringBuilder.Length;
        //If we append near the end we can run out of space in the for loop. Make sure we are large enough
        if (StringBuilder.Length < index + s.Length)
        {
            StringBuilder.Length = index + s.Length;
        }
        //If our appendAt is outside the scope we need to add spaces until then
        if (index > initialLength - 1)
        {
            for (int j = initialLength - 1; j < index; j++)
            {
                StringBuilder[j] = ' ';
            }
        }
        for (int i = 0; i < s.Length; i++)
        {
            this.StringBuilder[i + index] = (char)(s[i]);
        }
    }
    public MgStringBuilder Append(StringBuilder s)
    {
        int len = this.StringBuilder.Length;
        int reqcapacity = (s.Length + len) - this.StringBuilder.Capacity;
        //int reqcapacity = (s.Length + len +1) - this.StringBuilder.Capacity;
        if (reqcapacity > 0)
            this.StringBuilder.Capacity += reqcapacity;
        this.StringBuilder.Length = len + s.Length;
        for (int i = 0; i < s.Length; i++)
        {
            this.StringBuilder[i + len] = (char)(s[i]);
        }
        return this;
    }
    public MgStringBuilder Append(string s)
    {
        this.StringBuilder.Append(s);
        return this;
    }
    public MgStringBuilder Append(bool value)
    {
        this.StringBuilder.Append(value);
        return this;
    }
    public MgStringBuilder Append(byte value)
    {
        // basics
        int num = value;
        if (num == 0)
        {
            sb.Append('0');
            return this;
        }
        int place = 100;
        if (num >= place * 10)
        {
            // just append it
            sb.Append(num);
            return this;
        }
        // part 1 pull integer digits
        bool addzeros = false;
        while (place > 0)
        {
            if (num >= place)
            {
                addzeros = true;
                int modulator = place * 10;
                int val = num % modulator;
                int dc = val / place;
                sb.Append((char)(dc + 48));
            }
            else
            {
                if (addzeros) { sb.Append('0'); }
            }
            place = (int)(place * .1);
        }
        return this;
    }
    public MgStringBuilder Append(short value)
    {
        int num = value;
        // basics
        if (num < 0)
        {
            // Negative.
            sb.Append(minus);
            num = -num;
        }
        if (value == 0)
        {
            sb.Append('0');
            return this;
        }
        int place = 10000;
        if (num >= place * 10)
        {
            // just append it, if its this big, this isn't a science calculator, its a edge case.
            sb.Append(num);
            return this;
        }
        // part 1 pull integer digits
        bool addzeros = false;
        while (place > 0)
        {
            if (num >= place)
            {
                addzeros = true;
                int modulator = place * 10;
                int val = num % modulator;
                int dc = val / place;
                sb.Append((char)(dc + 48));
            }
            else
            {
                if (addzeros) { sb.Append('0'); }
            }
            place = (int)(place * .1);
        }
        return this;
    }
    public MgStringBuilder Append(int value)
    {
        // basics
        if (value < 0)
        {
            // Negative.
            sb.Append(minus);
            value = -value;
        }
        if (value == 0)
        {
            sb.Append('0');
            return this;
        }
        int place = 1000000000;
        if (value >= place * 10)
        {
            // just append it
            sb.Append(value);
            return this;
        }
        // part 1 pull integer digits
        int n = (int)(value);
        bool addzeros = false;
        while (place > 0)
        {
            if (n >= place)
            {
                addzeros = true;
                int modulator = place * 10;
                int val = n % modulator;
                int dc = val / place;
                sb.Append((char)(dc + 48));
            }
            else
            {
                if (addzeros) { sb.Append('0'); }
            }
            place = (int)(place * .1);
        }
        return this;
    }
    public MgStringBuilder Append(long value)
    {
        // basics
        if (value < 0)
        {
            // Negative.
            sb.Append(minus);
            value = -value;
        }
        if (value == 0)
        {
            sb.Append('0');
            return this;
        }
        long place = 10000000000000000L;
        if (value >= place * 10)
        {
            // just append it,
            sb.Append(value);
            return this;
        }
        // part 1 pull integer digits
        long n = (long)(value);
        bool addzeros = false;
        while (place > 0)
        {
            if (n >= place)
            {
                addzeros = true;
                long modulator = place * 10L;
                long val = n % modulator;
                long dc = val / place;
                sb.Append((char)(dc + 48));
            }
            else
            {
                if (addzeros) { sb.Append('0'); }
            }
            place = (long)(place * .1);
        }
        return this;
    }
    public MgStringBuilder Append(float value)
    {
        // basics
        bool addZeros = false;
        int n = (int)(value);
        int place = 100000000;
        if (value < 0)
        {
            // Negative.
            sb.Append(minus);
            value = -value;
        }
        if (value == 0)
        {
            sb.Append('0');
            return this;
        }
        // fix march 18-17
        // values not zero value is at least a integer
        if (value <= -1f || value >= 1f)
        {
            place = 100000000;
            if (value >= place * 10)
            {
                // just append it, if its this big its a edge case.
                sb.Append(value);
                return this;
            }
            // part 1 pull integer digits
            // int n =  // moved
            addZeros = false;
            while (place > 0)
            {
                if (n >= place)
                {
                    addZeros = true;
                    int modulator = place * 10;
                    int val = n % modulator;
                    int dc = val / place;
                    sb.Append((char)(dc + 48));
                }
                else
                {
                    if (addZeros) { sb.Append('0'); }
                }
                place = (int)(place * .1);
            }
        }
        else
            sb.Append('0');
        sb.Append(decimalseperator);
        // part 2 
        // floating point part now it can have about 28 digits but uh ya.. nooo lol
        place = 1000000;
        // pull decimal to integer digits, based on the number of place digits
        int dn = (int)((value - (float)(n)) * place * 10);
        // ... march 17 testing... cut out extra zeros case 1
        if (dn == 0)
        {
            sb.Append('0');
            return this;
        }
        addZeros = true;
        while (place > 0)
        {
            if (dn >= place)
            {
                //addzeros = true;
                int modulator = place * 10;
                int val = dn % modulator;
                int dc = val / place;
                sb.Append((char)(dc + 48));
                if (val - dc * place == 0) // && trimEndZeros this would be a acstetic
                {
                    return this;
                }
            }
            else
            {
                if (addZeros) { sb.Append('0'); }
            }
            place = (int)(place * .1);
        }
        return this;
    }
    public MgStringBuilder Append(double value)
    {
        // basics
        bool addZeros = false;
        long n = (long)(value);
        long place = 10000000000000000L;
        if (value < 0) // is Negative.
        {
            sb.Append(minus);
            value = -value;
        }
        if (value == 0) // is Zero
        {
            sb.Append('0');
            return this;
        }
        if (value <= -1d || value >= 1d) // is a Integer
        {
            if (value >= place * 10)
            {
                sb.Append(value); // is big, just append its a edge case.
                return this;
            }
            // part 1 pull integer digits
            addZeros = false;
            while (place > 0)
            {
                if (n >= place)
                {
                    addZeros = true;
                    long modulator = place * 10;
                    long val = n % modulator;
                    long dc = val / place;
                    sb.Append((char)(dc + 48));
                }
                else
                    if (addZeros) { sb.Append('0'); }
                place = (long)(place * .1d);
            }
        }
        else
            sb.Append('0');
        sb.Append(decimalseperator);
        // part 2 
        // floating point part now it can have about 28 digits but uh ya.. nooo lol
        place = 1000000000000000L;
        // pull decimal to integer digits, based on the number of place digits
        long dn = (long)((value - (double)(n)) * place * 10);
        if (dn == 0)
        {
            sb.Append('0');
            return this;
        }
        addZeros = true;
        while (place > 0)
        {
            if (dn >= place)
            {
                long modulator = place * 10;
                long val = dn % modulator;
                long dc = val / place;
                sb.Append((char)(dc + 48));
                if (val - dc * place == 0) // && trimEndZeros  aectetic
                {
                    return this;
                }
            }
            else
                if (addZeros) { sb.Append('0'); }
            place = (long)(place * .1);
        }
        return this;
    }
    public MgStringBuilder Append(Vector2 value)
    {
        Append("(");
        Append(value.X);
        Append(",");
        Append(value.Y);
        Append(")");
        return this;
    }
    public MgStringBuilder Append(Vector3 value)
    {
        Append("(");
        Append(value.X);
        Append(",");
        Append(value.Y);
        Append(",");
        Append(value.Z);
        Append(")");
        return this;
    }
    public MgStringBuilder Append(Vector4 value)
    {
        Append("(");
        Append(value.X);
        Append(",");
        Append(value.Y);
        Append(",");
        Append(value.Z);
        Append(",");
        Append(value.W);
        Append(")");
        return this;
    }
    public MgStringBuilder Append(Color value)
    {
        Append("(");
        Append(value.R);
        Append(",");
        Append(value.G);
        Append(",");
        Append(value.B);
        Append(",");
        Append(value.A);
        Append(")");
        return this;
    }
    public MgStringBuilder AppendTrim(float value)
    {
        // basics
        bool addZeros = false;
        int n = (int)(value);
        int place = 100000000;
        if (value < 0)
        {
            // Negative.
            sb.Append(minus);
            value = -value;
        }
        if (value == 0)
        {
            sb.Append('0');
            return this;
        }
        // fix march 18-17
        // values not zero value is at least a integer
        if (value <= -1f || value >= 1f)
        {
            place = 100000000;
            if (value >= place * 10)
            {
                // just append it, if its this big its a edge case.
                sb.Append(value);
                return this;
            }
            // part 1 pull integer digits
            // int n =  // moved
            addZeros = false;
            while (place > 0)
            {
                if (n >= place)
                {
                    addZeros = true;
                    int modulator = place * 10;
                    int val = n % modulator;
                    int dc = val / place;
                    sb.Append((char)(dc + 48));
                }
                else
                {
                    if (addZeros) { sb.Append('0'); }
                }
                place = (int)(place * .1);
            }
        }
        else
            sb.Append('0');
        sb.Append(decimalseperator);
        // part 2 
        // floating point part now it can have about 28 digits but uh ya.. nooo lol
        place = 100;
        // pull decimal to integer digits, based on the number of place digits
        int dn = (int)((value - (float)(n)) * place * 10);
        // ... march 17 testing... cut out extra zeros case 1
        if (dn == 0)
        {
            sb.Append('0');
            return this;
        }
        addZeros = true;
        while (place > 0)
        {
            if (dn >= place)
            {
                //addzeros = true;
                int modulator = place * 10;
                int val = dn % modulator;
                int dc = val / place;
                sb.Append((char)(dc + 48));
                if (val - dc * place == 0) // && trimEndZeros this would be a acstetic
                {
                    return this;
                }
            }
            else
            {
                if (addZeros) { sb.Append('0'); }
            }
            place = (int)(place * .1);
        }
        return this;
    }
    public MgStringBuilder AppendTrim(double value)
    {
        // basics
        bool addZeros = false;
        long n = (long)(value);
        long place = 10000000000000000L;
        if (value < 0) // is Negative.
        {
            sb.Append(minus);
            value = -value;
        }
        if (value == 0) // is Zero
        {
            sb.Append('0');
            return this;
        }
        if (value <= -1d || value >= 1d) // is a Integer
        {
            if (value >= place * 10)
            {
                sb.Append(value); // is big, just append its a edge case.
                return this;
            }
            // part 1 pull integer digits
            addZeros = false;
            while (place > 0)
            {
                if (n >= place)
                {
                    addZeros = true;
                    long modulator = place * 10;
                    long val = n % modulator;
                    long dc = val / place;
                    sb.Append((char)(dc + 48));
                }
                else
                    if (addZeros) { sb.Append('0'); }
                place = (long)(place * .1);
            }
        }
        else
            sb.Append('0');
        sb.Append(decimalseperator);
        // part 2 
        // floating point part now it can have about 28 digits but uh ya.. nooo lol
        place = 100L;
        // pull decimal to integer digits, based on the number of place digits
        long dn = (long)((value - (double)(n)) * place * 10);
        if (dn == 0)
        {
            sb.Append('0');
            return this;
        }
        addZeros = true;
        while (place > 0)
        {
            if (dn >= place)
            {
                long modulator = place * 10;
                long val = dn % modulator;
                long dc = val / place;
                sb.Append((char)(dc + 48));
                if (val - dc * place == 0) // && trimEndZeros  aectetic
                {
                    return this;
                }
            }
            else
                if (addZeros) { sb.Append('0'); }
            place = (long)(place * .1);
        }
        return this;
    }
    public MgStringBuilder AppendTrim(Vector2 value)
    {
        Append("(");
        AppendTrim(value.X);
        Append(",");
        AppendTrim(value.Y);
        Append(")");
        return this;
    }
    public MgStringBuilder AppendTrim(Vector3 value)
    {
        Append("(");
        AppendTrim(value.X);
        Append(",");
        AppendTrim(value.Y);
        Append(",");
        AppendTrim(value.Z);
        Append(")");
        return this;
    }
    public MgStringBuilder AppendTrim(Vector4 value)
    {
        Append("(");
        AppendTrim(value.X);
        Append(",");
        AppendTrim(value.Y);
        Append(",");
        AppendTrim(value.Z);
        Append(",");
        AppendTrim(value.W);
        Append(")");
        return this;
    }
    public void AppendLine(StringBuilder s)
    {
        sb.AppendLine();
        Append(s);
    }
    public void AppendLine(string s)
    {
        sb.AppendLine();
        sb.Append(s);
    }
    public MgStringBuilder AppendLine()
    {
        sb.AppendLine();
        return this;
    }
    public MgStringBuilder Insert(int index, StringBuilder s)
    {
        this.StringBuilder.Insert(index, s);
        return this;
    }
    public MgStringBuilder Remove(int index, int length)
    {
        this.StringBuilder.Remove(index, length);
        return this;
    }
    public char[] ToCharArray()
    {
        char[] a = new char[sb.Length];
        sb.CopyTo(0, a, 0, sb.Length);
        return a;
    }
    public override string ToString()
    {
        return sb.ToString();
    }
}

You may also need the timer class for this example.

public class MgTimer
{
    // this is your timing value it can be anything as long as it stays constant and never changes
    public const float STANDARD_UNIT_TIME_IN_SIXTIETH_SECONDS = 60f;
    float timenow = 0f;
    float pausetimeset = 0f;
    float pauseDurationInSixtiethSeconds = 0f;
    float timelast = 0f;
    float elapsedTimeSinceLastwasChecked = 0f;
    bool triggered = false;
    float elapsedAdjustedTime = 0f;
    /// <summary>
    /// 60f is one second or trigger occurs 60 times per second
    /// </summary>
    /// <param name="timeInFramesPerSecond"></param>
    public MgTimer(float timeInFramesPerSecond)
    {
        pauseDurationInSixtiethSeconds = 1f / STANDARD_UNIT_TIME_IN_SIXTIETH_SECONDS * timeInFramesPerSecond;
    }
    public bool IsTriggered
    {
        get { return triggered; }
    }
    public float ElapsedTiming
    {
        get { /*return elapsedAdjustedTime;*/ return elapsedTimeSinceLastwasChecked; }
    }
    /// <summary>
    /// returns the rate of change to 
    /// </summary>
    public void Update(GameTime gameTime)
    {
        timenow = (float)gameTime.TotalGameTime.TotalSeconds;
        if (pausetimeset < timenow)
        {
            triggered = true;
            // non ratioed elapsed time
            elapsedTimeSinceLastwasChecked = timenow - timelast;
            timelast = timenow;
            // set result if we are running slowly this elapsed time will be greater then normal
            elapsedAdjustedTime = (float)(elapsedTimeSinceLastwasChecked / pauseDurationInSixtiethSeconds) * pauseDurationInSixtiethSeconds;
            // Set the pausetimer to the specified duration;
            pausetimeset = timenow + pauseDurationInSixtiethSeconds;
        }
        else
        {
            elapsedAdjustedTime = 0f;
            triggered = false;
        }
    }
}

Ending namespace bracket

}

Could/Would this affect keyboard input responsiveness?

Not at all. You wouldn’t even notice.

That’s what I figured. Wishful thinking on my part lol :stuck_out_tongue: