XML Serialization crashing on some computers (Huge RAM usage)

So my Save / Load system uses XML.
It has been working without problems so far, but now one of my testers has crash when trying to load game or starting a new game. Error seems to be in XML Serialization for some reason…

Here’s my XML Handler

namespace MyGame
{
    public class SaveHandler
    {
        public static void SaveData(object IClass, string filename)
        {
            StreamWriter writer = null;
            try
            {
                XmlSerializer xmlSerializer = new XmlSerializer((IClass.GetType()));
                writer = new StreamWriter(filename);
                xmlSerializer.Serialize(writer, IClass);
            }
            finally
            {
                if (writer != null)
                    writer.Close();
                writer = null;
            }
    }
}

public class LoadHandler<T>
{
    public static Type type;
    
    public LoadHandler()
    {
        type = typeof(T);
    }
    
    public T LoadData(string filename)
    {
        if (!System.IO.File.Exists(filename))
            { return default(T); }

        T result;
        XmlSerializer xmlserializer = new XmlSerializer(type);
        FileStream fs = new FileStream(filename, FileMode.Open, FileAccess.Read, FileShare.Read);
        
        try
        { result = (T)xmlserializer.Deserialize(fs); }
        catch
        { return default(T); }
        
        fs.Close();
        return result;
    }
}
}

The error says

Unhandled Exception: System.InvalidOperationException: 
There was an error reflecting type 'MyGame.SavedGame'. ---> System.InvalidOperationException: 
There was an error reflection field 'PlayerPos'. ---> System.InvalidOperationException: 
There was an error reflecting type 'Microsoft.Xna.Framework.Vector2'. ---> System.IO.FileLoadException: 
Could not load file or assemply 'System.Runtime.Serialization, Version=4.0.0.0, 
Culture=neutral, PiublicKeyToken=dsf2q34sdaf231' or one of its dependencies.
 Not enough storage is available to process this command. (Exception from HRESULT: 0x800700008)
at System.ModuleHandle.ResolveType(RuntimeModule module, Int32 typeToken, IntPtr* typeInstArgs, Int32 typeInstCount, IntPtr* methodInstArgs, Int32 methodInstCount, 0projectHandleOnStackType)

pic of crash

Please help :frowning:

Did you read this or just copied it and hoped for someone to point out the obvious?

I did read it, but I didn’t understand why that would be a problem.

Now I asked my tester to send picture of RAM usage, and the game takes whopping 1.7GB of RAM… On my computer it uses around 110-130MB.
The game doesn’t use all of his RAM, and there is still lots of free space on hard disk, but something is definitely wrong.

How can this be, what can cause that amount of RAM usage?

First I load my main Content: Textures, Font, Sound Effects and Music.
Then I create another thread to loop through levels, save data and settings, which are XML files. On my Main Thread I show loading screen.
I also create Textures with rendertargets out of my tile based levels, but do that only once in the beginning, and create recolors of some Textures during loading.

After I have loaded all of my content I run Garbage Collector manually.

Even when you call GC.Collect() it is not immediate, the GC runs on its own routine schedule and no matter what you do, you have zero control over it.

Are both systems 64bit and app too?

What are the users hardware specs? 2GB? 4GB? do they have shared GPU RAM if on a laptop? this can run a 4GB system down to 3GB or less…

I don’t think GC is relevant here, it won’t just randomly not collect 1.5gb of data, it’s most likely a problem wiht loading/saving data (I believe)

There seams to be a memory leak somewhere else. Stuff that will never be collected by the gc. Try to recreate the steps your friends took to get the crash and debug the program with the .net memory profiler which is provided by Visual studio

Debug -> Performance Profiler… -> Memory usage OR Performance Wizard -> .net memory allocations.

You can also display the gc managed memory ingame with
long totalmemory = GC.GetTotalMemory(false);

If you divide this by 1024 you get the amount of KBytes. Again by 1024 and you get Mb.

It’s possible that even if that number is low the ram is full with unmanaged stuff

1 Like

My friend just starts the game and selects New Game (or Continue) and it crashes. I’m not 100% sure but I think that he had crashes few times before even the Main Menu loaded up, that was before I moved XML Loading to another thread.

I don’t have access to his laptop and he doesn’t have Visual Studio, but here’s Memory Usage test on my computer: Memory Test result

Edit: The game currently uses MonoGame 3.5, and its DesktopGL Project. My friend has Windows 10, and should have OpenAL / .NET Framework 4.5.1 installed.

You’ll have to show us some sample XML file and some of the code you use to process that XML file. My first guess is something to do with culture differences if the other user has a different language.

Cultural difference sounds highly possible reason, my friend is big anime / Japan fan and (probably) has Japanese settings on the computer.


I use Path.Combine to build paths to access XML files, and when loading Content I use //FolderName//Filename.extension

Saving / Loading data is handled with XML Serialization seen in my first post.

The data is loaded with 

LoadHandler<Settings> loadedSettings = new LoadHandler<Settings>();
settings = loadedSettings.LoadData(filePath);

XML file

<?xml version="1.0" encoding="utf-8"?>

<Settings xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
    <ScreenWidth>1920</ScreenWidth>
    <ScreenHeight>1080</ScreenHeight>
    <WindowIsFullscreen>false</WindowIsFullscreen>    
    <WindowIsBorderless>false</WindowIsBorderless>
    <Music>0.7</Music>
    <SFX>1</SFX>
</Settings>

Creatures / Monsters / NPCs etc use Homemade animation tool that allows me to create animations without having to depend on pixel perfect sheets.

<?xml version="1.0" encoding="utf-8"?>
<animation name="npc_idle_animation" looping="True" length="3">
    <frame ID="0" frameDuration="8" frameX="0" frameY="0" frameWidth="16" frameHeight="19" texture="npc_idle_sheet.png" />
    <frame ID="1" frameDuration="8" frameX="16" frameY="0" frameWidth="16" frameHeight="19" texture="npc_idle_sheet.png" />
    <frame ID="2" frameDuration="8" frameX="32" frameY="0" frameWidth="16" frameHeight="19" texture="npc_idle_sheet.png" />
</animation>

The data is loaded with

XDocument doc = XDocument.Load(filePath);
var header = doc.Element("animation");
        
foreach (var attrib in header.Attributes())
{
    switch (attrib.Name.ToString())
    {
        case "name": name = attrib.Value; break;
        case "looping": looping = attrib.Value == "true"; break;
        case "length":
        {
             int dummy = 0;
             if (int.TryParse(attrib.Value, out dummy))
             { frames = new List<int, Frame>(); }
             break;
         }
     }
}

var xframes = doc.Descendants("frame");
        
foreach (var xframe in xframes)
{
    AnimationFrame newFrame = new AnimationFrame();
    foreach (var attrib in xframe.Attributes())
    {
        int parsed = 0;
        int.TryParse(attrib.Value, out parsed);
        switch (attrib.Name.ToString())
        {
             case "ID": newFrame.Index = parsed; break;
             case "frameDuration": newFrame.Duration = parsed; break;
             case "frameX": newFrame.X = parsed; break;
             case "frameY": newFrame.Y = parsed; break;
             case "frameWidth": newFrame.Width = parsed; break;
             case "frameHeight": newFrame.Height = parsed; break;
             case "texture": { newFrame.TextureFileName = attrib.Value; break; }
        }
    }
frames.Add(newFrame);
}

Level Collision data looks like this, and is loaded similarly to animations

<?xml version="1.0" encoding="utf-8"?>
<Colliders>
    <collider type="1" xPos="0" yPos="0" width="480" height="80" />
    <collider type="1" xPos="0" yPos="80" width="32" height="224" />
    <collider type="1" xPos="448" yPos="80" width="32" height="224" />
    <collider type="15" xPos="240" yPos="160" width="80" height="16" />
    <collider type="15" xPos="224" yPos="288" width="48" height="16" />
    <collider type="1" xPos="0" yPos="304" width="480" height="64" />
</Colliders>

Any time you are writing or reading numbers, use CultureInfo.InvariantCulture. This will use a consistent formatting regardless of the region setting on your PC. The original post showed the error to come from deserializing a Vector2 which contains two floating-point numbers. These are the number types that usually cause these errors across different regions. Many European regions use a comma as a decimal separator whereas most other regions use a period as the decimal separator.

Try setting your PC to a different region, such as en-US, and load your game. This should help show where issues with parsing of XML files are located.

1 Like

You can try Greek locale that has period-comma reversed.

Also France. They use a half-whitespace separator for thousands.

Okay, I got it working but was too busy with life to share my solution.

In Game1() I have:

System.Globalization.CultureInfo ci = (System.Globalization.CultureInfo)System.Globalization.CultureInfo.CurrentCulture.Clone();
ci.NumberFormat.CurrencyDecimalSeparator = ".";
System.Globalization.CultureInfo.DefaultThreadCurrentCulture = ci;
System.Globalization.CultureInfo.DefaultThreadCurrentUICulture = ci;

Also, every Parse / TryParse function I have is now something like this:

public static float ConvertStringToFloat(string floatFromString)
{
double result = -1;

System.Globalization.CultureInfo ci = (System.Globalization.CultureInfo)System.Globalization.CultureInfo.CurrentCulture.Clone();
ci.NumberFormat.CurrencyDecimalSeparator = ".";

if (!double.TryParse(floatFromString, System.Globalization.NumberStyles.Any, ci, out result) &&
!double.TryParse(floatFromString, System.Globalization.NumberStyles.Any, System.Globalization.CultureInfo.GetCultureInfo("en-US"), out result) &&
!double.TryParse(floatFromString, System.Globalization.NumberStyles.Any, System.Globalization.CultureInfo.InvariantCulture, out result) && 
!double.TryParse(floatFromString, System.Globalization.NumberStyles.Any, System.Globalization.CultureInfo.CurrentCulture, out result))
{ result = -1; }

return (float)result;
}

And finally, I don’t use Vector2 to save Player’s position in a file, instead I use ints: int PlayerPosX and int PlayerPosY.

Thanks for help everyone!

2 Likes