Saving Maps and Scenarios

freegamer

User avatar

Site Admin

Posts: 119

Joined: Fri Mar 14, 2008 3:11 pm

Post Sun Mar 16, 2008 4:07 am

Saving Maps and Scenarios

If I remember correctly, FreeTrain currently uses serialization to save games. This obviously is far from ideal - new versions most likely invalidate save games. Edit - even worse than that, new / different plugins cause incompatable save games!

I guess what I'm saying is we need a save game format, a way to save that can be (at worst) converted between different versions of FreeTrain, thus allowing us to create scenarios and enrich the rather empty game that currently presents itself to a new player.

One of my favourite things about A-Train was the scenarios which each presented a unique challenge. I think it would do a lot for increasing FreeTrain's appeal to implement a proper save format.
Free Gamer - open source games blog
FreeGameDev forums - open source games development community

c477

User avatar

Settling in

Posts: 18

Joined: Sun Mar 23, 2008 2:11 am

Post Sat Apr 12, 2008 3:17 pm

Re: Saving Maps and Scenarios

freegamer wrote:If I remember correctly, FreeTrain currently uses serialization to save games. This obviously is far from ideal - new versions most likely invalidate save games.


I found some solution around version incompatibility problem.
http://www.codeproject.com/KB/vb/Object ... ation.aspx
As you can see, the code introduced above is written in VisualBasic.
So I translated them into C# code.

  Code:
using System;
using System.Collections.Generic;
using System.Text;
using System.Runtime.Serialization;
using System.Reflection;

namespace nft.framework {
    class FailsafeSurrogate : ISerializationSurrogate, ISurrogateSelector {
        private Assembly _assmeblyToMigrate;
        public FailsafeSurrogate(Assembly migrate) {
            this._assmeblyToMigrate = migrate;
        }

        #region ISerializationSurrogate メンバ

        public void GetObjectData(object obj, SerializationInfo info, StreamingContext context) {
            throw new NotImplementedException();
        }

        public object SetObjectData(object obj, SerializationInfo info, StreamingContext context, ISurrogateSelector selector) {
            string fieldName = null;
            Type entityType;
            foreach (SerializationEntry entry in info) {
                // for each member that was serialized,
                // get matching member in new type
                if (fieldName.IndexOf('+') != -1) {
                    string[] name = fieldName.Split('+');
                    string baseType = name[0];
                    fieldName = name[1];
                    entityType = obj.GetType();
                    // drill into base classes until type found
                    while (!entityType.Name.Equals(baseType)) {
                        entityType = entityType.BaseType;
                    }
                } else {
                    entityType = obj.GetType();
                }
                MemberInfo[] members = entityType.GetMember(fieldName, MemberTypes.Field, BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance);
                if (members.Length > 0) {
                    //entity has a member matching the serialized info
                    FieldInfo newField = members[0] as FieldInfo;
                    object val = entry.Value;
                    if (val != null) {
                        // don't bother adding serialized members with null values
                        if (!newField.FieldType.IsInstanceOfType(val)) {
                            // convert type if changed in new member
                            val = Convert.ChangeType(val, newField.FieldType);
                        }
                    }
                    newField.SetValue(entry, val);
                }
            } // foreach
            return null;
        }

        #endregion

        #region ISurrogateSelector メンバ

        public ISerializationSurrogate GetSurrogate(Type type, StreamingContext context, out ISurrogateSelector selector) {
            if (type.Assembly == _assmeblyToMigrate) {
                selector = this;
                return this;
            } else {
                selector = null;
                return null;
            }
        }

        public void ChainSelector(ISurrogateSelector selector) {
            throw new NotImplementedException("ChainSelector not supported");
        }

        public ISurrogateSelector GetNextSelector() {
            return null;
        }

        #endregion
    }
}


and

  Code:
using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
using System.Runtime.Serialization.Formatters.Binary;
using System.Runtime.Serialization;

namespace nft.framework {
    class SerializationHelper {
        public static void Save(string filename, object obj) {
            FileInfo file = new FileInfo(filename);
            FileStream stream = file.OpenWrite();
            BinaryFormatter bf = CreateFormatter();
            bf.Serialize(stream, obj);
        }

        public static object Load(string filename, Type type, out bool schemaChange) {
            object obj = null;
            schemaChange = false;
            FileInfo file = new FileInfo(filename);
            if (true /*file.Exists*/) {
                FileStream stream = file.OpenRead();
                BinaryFormatter bf = CreateFormatter();
                try {
                    obj = bf.Deserialize(stream);
                } catch (SerializationException se) {
                    // standad deserialization didn't work so attempt schema migration
                    stream.Seek(0, SeekOrigin.Begin);
                    bf = CreateFormatter(type);
                    obj = bf.Deserialize(stream);
                    schemaChange = true;
                } finally {
                    stream.Close();
                }
            }
            return obj;
        }

        private static BinaryFormatter CreateFormatter(Type type) {
            ISurrogateSelector selector = new FailsafeSurrogate(type.Assembly);
            return new BinaryFormatter(selector, new StreamingContext(StreamingContextStates.All));
        }

        private static BinaryFormatter CreateFormatter() {
            BinaryFormatter formatter = new BinaryFormatter();
            formatter.Context = new StreamingContext(StreamingContextStates.Persistence);
            return formatter;
        }
    }
}


these code seems to be working correctly.

c477

User avatar

Settling in

Posts: 18

Joined: Sun Mar 23, 2008 2:11 am

Post Sat Apr 12, 2008 3:26 pm

Re: Saving Maps and Scenarios

I have to say, the code above is not suitable for FreeTrain project without modifying properly.
Because FailsafeSurrogate class does not supports deserialization consist of multi Assembly.
Or you may implement more smart restoration code for missing field in some case.

c477

User avatar

Settling in

Posts: 18

Joined: Sun Mar 23, 2008 2:11 am

Post Sun Apr 13, 2008 3:20 am

Re: Saving Maps and Scenarios

There are mistakes in FailsafeSurrogate at line 26 and 51.
  Code:
+               fieldName = entry.Name;


  Code:
-                    newField.SetValue(entry, val);
+                   newField.SetValue(obj, val);

freegamer

User avatar

Site Admin

Posts: 119

Joined: Fri Mar 14, 2008 3:11 pm

Post Sun Apr 13, 2008 12:03 pm

Re: Saving Maps and Scenarios

Don't forget you have SVN access if you want to create a branch and do this work.
Free Gamer - open source games blog
FreeGameDev forums - open source games development community

c477

User avatar

Settling in

Posts: 18

Joined: Sun Mar 23, 2008 2:11 am

Post Fri Apr 18, 2008 2:26 pm

Re: Saving Maps and Scenarios

Hmm...I have forgotten the way to commit SVN and have lost previous environment.
I tried again in these days, but I can't overcome authorization error.
(Though I have searched solution on Web by Google, and tried some settings.)
I'm using TortoiseSVN and PuTTY agent, and successfully committed against
the project on SoruceForge.jp. If you have any advice, please tell me.

By the way, I applied above source codes to FreeTrain on my local machine.
I have modified the codes suitable for FreeTrain framework.
But I have encountered unusual exception that
"Cannot add the same member twice to a SerializationInfo object."
while deserializing Commercial object or Station object.
I suppose this error is caused because those two class has "type" field
which hides the same field of super class by using "new" keyword.
Because the location which throws the exception is out of user code,
I can't found any solution except for rename those field.
But why default BinaryFormatter can deserialize those object without errors?
There are some solution to avoid such errors, aren't they?

dmarks

Settling in

Posts: 11

Joined: Wed Mar 19, 2008 11:24 am

Post Sun Jun 22, 2008 10:49 am

Re: Saving Maps and Scenarios

c477, if you can't get a committing environment set up, how about sharing with us a diff of your work? Posting it to the dev mailing list would be the most convenient, perhaps.

freegamer

User avatar

Site Admin

Posts: 119

Joined: Fri Mar 14, 2008 3:11 pm

Post Sun Jun 22, 2008 11:10 am

Re: Saving Maps and Scenarios

It is worth noting that Sourceforge required all users to change their passwords at near the beginning of 2008 - something that had me stumped for a few days on why I could not commit to my Sourceforge-based SVN repos.
Free Gamer - open source games blog
FreeGameDev forums - open source games development community

Return to Core Development

Who is online

Users browsing this forum: No registered users and 1 guest

cron
Powered by phpBB © 2000, 2002, 2005, 2007 phpBB Group.
Designed by Vjacheslav Trushkin for Free Forums/DivisionCore.