0

I'm making a game and I am serializing the save game data, the data is simply a few lists of bools, ints and floats.

But about 50% of the time I get an error when the data tries to save or load that says "There is an error in XML document". The error is always located at the end of the file, even after it has changed size or I have added other variables.

It appears that very last part of the XML data is getting copied twice. The last line of the XML should read:

</LevelStats>

But when an error occurs it often reads

</LevelStats>>

Or

</LevelStats>tats>

Or some other small part of the last line duplicated.

Here is the class I am Serializing:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml.Serialization;
using Microsoft.Xna.Framework.Storage;
using System.IO;

namespace Jetpack.Classes
{
    [Serializable]
    public struct LevelStats
    {
        // Player Bests
        public List<float?> fTimeList;
        public List<int?> iScoreList;
        public List<int?> iFuelList;

        // Time
        public List<bool> bBronzeTimeList;
        public List<bool> bSilverTimeList;
        public List<bool> bGoldTimeList;

        // Score
        public List<bool> bBronzeScoreList;
        public List<bool> bSilverScoreList;
        public List<bool> bGoldScoreList;

        // Fuel
        public List<bool> bBronzeFuelList;
        public List<bool> bSilverFuelList;
        public List<bool> bGoldFuelList;

        // Level Complete
        public List<bool> bIsLevelComplete;
    }
}

And here is my methods for saving the loading the data:

region Save & Load Level Stats

    public static void SaveLevelStats(LevelStats levelStats, string filename)
    {
        // Get the path of the save game
        string fullpath = System.IO.Path.Combine(Directory.GetCurrentDirectory(), filename);

        // Open the file, creating it if necessary
        FileStream stream = File.Open(fullpath, FileMode.OpenOrCreate);
        try
        {
            // Convert the object to XML data and put it in the stream
            XmlSerializer serializer = new XmlSerializer(typeof(LevelStats));
            serializer.Serialize(stream, levelStats);
        }
        finally
        {
            // Close the file
            stream.Close();
        }
    }

    public static LevelStats LoadLevelStats(string filename)
    {
        LevelStats levelStats;

        // Get the path of the save game
        string fullpath = System.IO.Path.Combine((Directory.GetCurrentDirectory()), filename);

        // Open the file
        FileStream stream = File.Open(fullpath, FileMode.OpenOrCreate,
        FileAccess.Read);
        try
        {
            // Read the data from the file
            XmlSerializer serializer = new XmlSerializer(typeof(LevelStats));
            levelStats = (LevelStats)serializer.Deserialize(stream);
        }
        finally
        {
            // Close the file
            stream.Close();
        }

        return (levelStats);
    }

    #endregion

And finally here is the XML File

<?xml version="1.0"?>
<LevelStats xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
  <fTimeList>
    <float xsi:nil="true" />
    <float>19.016552</float>
    <float>9.766692</float>
    <float>62.9992142</float>
    <float>11.6666632</float>
    <float xsi:nil="true" />
    <float xsi:nil="true" />
    <float xsi:nil="true" />
    <float xsi:nil="true" />
    <float xsi:nil="true" />
    <float xsi:nil="true" />
  </fTimeList>
  <iScoreList>
    <int xsi:nil="true" />
    <int>690</int>
    <int>390</int>
    <int>690</int>
    <int>200</int>
    <int xsi:nil="true" />
    <int xsi:nil="true" />
    <int xsi:nil="true" />
    <int xsi:nil="true" />
    <int xsi:nil="true" />
    <int xsi:nil="true" />
  </iScoreList>
  <iFuelList>
    <int xsi:nil="true" />
    <int>293</int>
    <int>206</int>
    <int>1134</int>
    <int>202</int>
    <int xsi:nil="true" />
    <int xsi:nil="true" />
    <int xsi:nil="true" />
    <int xsi:nil="true" />
    <int xsi:nil="true" />
    <int xsi:nil="true" />
  </iFuelList>
  <bBronzeTimeList>
    <boolean>false</boolean>
    <boolean>true</boolean>
    <boolean>false</boolean>
    <boolean>false</boolean>
    <boolean>false</boolean>
    <boolean>false</boolean>
    <boolean>false</boolean>
    <boolean>false</boolean>
    <boolean>false</boolean>
    <boolean>false</boolean>
    <boolean>false</boolean>
  </bBronzeTimeList>
  <bSilverTimeList>
    <boolean>false</boolean>
    <boolean>true</boolean>
    <boolean>false</boolean>
    <boolean>false</boolean>
    <boolean>false</boolean>
    <boolean>false</boolean>
    <boolean>false</boolean>
    <boolean>false</boolean>
    <boolean>false</boolean>
    <boolean>false</boolean>
    <boolean>false</boolean>
  </bSilverTimeList>
  <bGoldTimeList>
    <boolean>false</boolean>
    <boolean>true</boolean>
    <boolean>true</boolean>
    <boolean>false</boolean>
    <boolean>true</boolean>
    <boolean>false</boolean>
    <boolean>false</boolean>
    <boolean>false</boolean>
    <boolean>false</boolean>
    <boolean>false</boolean>
    <boolean>false</boolean>
  </bGoldTimeList>
  <bBronzeScoreList>
    <boolean>false</boolean>
    <boolean>false</boolean>
    <boolean>true</boolean>
    <boolean>false</boolean>
    <boolean>false</boolean>
    <boolean>false</boolean>
    <boolean>false</boolean>
    <boolean>false</boolean>
    <boolean>false</boolean>
    <boolean>false</boolean>
    <boolean>false</boolean>
  </bBronzeScoreList>
  <bSilverScoreList>
    <boolean>false</boolean>
    <boolean>true</boolean>
    <boolean>false</boolean>
    <boolean>true</boolean>
    <boolean>false</boolean>
    <boolean>false</boolean>
    <boolean>false</boolean>
    <boolean>false</boolean>
    <boolean>false</boolean>
    <boolean>false</boolean>
    <boolean>false</boolean>
  </bSilverScoreList>
  <bGoldScoreList>
    <boolean>false</boolean>
    <boolean>false</boolean>
    <boolean>false</boolean>
    <boolean>false</boolean>
    <boolean>false</boolean>
    <boolean>false</boolean>
    <boolean>false</boolean>
    <boolean>false</boolean>
    <boolean>false</boolean>
    <boolean>false</boolean>
    <boolean>false</boolean>
  </bGoldScoreList>
  <bBronzeFuelList>
    <boolean>false</boolean>
    <boolean>true</boolean>
    <boolean>false</boolean>
    <boolean>true</boolean>
    <boolean>false</boolean>
    <boolean>false</boolean>
    <boolean>false</boolean>
    <boolean>false</boolean>
    <boolean>false</boolean>
    <boolean>false</boolean>
    <boolean>false</boolean>
  </bBronzeFuelList>
  <bSilverFuelList>
    <boolean>false</boolean>
    <boolean>true</boolean>
    <boolean>false</boolean>
    <boolean>false</boolean>
    <boolean>false</boolean>
    <boolean>false</boolean>
    <boolean>false</boolean>
    <boolean>false</boolean>
    <boolean>false</boolean>
    <boolean>false</boolean>
    <boolean>false</boolean>
  </bSilverFuelList>
  <bGoldFuelList>
    <boolean>false</boolean>
    <boolean>true</boolean>
    <boolean>true</boolean>
    <boolean>false</boolean>
    <boolean>true</boolean>
    <boolean>false</boolean>
    <boolean>false</boolean>
    <boolean>false</boolean>
    <boolean>false</boolean>
    <boolean>false</boolean>
    <boolean>false</boolean>
  </bGoldFuelList>
  <bIsLevelComplete>
    <boolean>false</boolean>
    <boolean>true</boolean>
    <boolean>true</boolean>
    <boolean>true</boolean>
    <boolean>true</boolean>
    <boolean>false</boolean>
    <boolean>false</boolean>
    <boolean>false</boolean>
    <boolean>false</boolean>
    <boolean>false</boolean>
    <boolean>false</boolean>
  </bIsLevelComplete>
</LevelStats>

Any help you can provide me with will be greatly appreciated.

Thank you.

Community
  • 1
  • 1
Jonathan Dunn
  • 289
  • 2
  • 15
  • 1
    Since SaveLevelStats is static, is it possible you are calling this from more than one thread? Writing to a file stream, as the serializer is doing, is not thread safe. – crad Dec 17 '13 at 00:41
  • How would I go about making sure it is using only a single thread? – Jonathan Dunn Dec 17 '13 at 00:45
  • 1
    There are a few ways to do thread safe file writes. Now that you know what the issue might be, you can find many answers on here for thread safe file writing in c#. Maybe edit with some more code if you can't solve it. – crad Dec 17 '13 at 00:49
  • 1
    The simplest solution is probably to use a lock, both when reading and writing the file. – crad Dec 17 '13 at 00:53
  • I made the Methods non-static and it seems to work perfectly now, thank you, if you want to post this as the answer I would be happy to accept. – Jonathan Dunn Dec 17 '13 at 00:54
  • Now you are likely creating multiple instances which are just less likely to be reading or writing at the same time. I think you should describe how you are working with that class. But I will post as answer anyway. – crad Dec 17 '13 at 00:57

1 Answers1

1

Since SaveLevelStats is static, is it possible you are calling this from more than one thread?

Writing to a file stream, as the serializer is doing, is not thread safe.

Simplest solution would be to use a lock both when reading and writing the file. Be careful if you are still reading and/or writing from multiple threads.

crad
  • 433
  • 3
  • 6
  • I am struggling to lock my methods, could you demonstrate how it might look on my methods? – Jonathan Dunn Dec 17 '13 at 01:08
  • 1
    Here is a good discussion of good locking practices http://stackoverflow.com/questions/1330507/best-c-sharp-solution-for-multithreaded-threadsafe-read-write-locking check out the answer by Joseph Kingry – crad Dec 17 '13 at 01:13