Updating custom class save after game release

Easy Save 2 has been replaced by Easy Save 3, so is no longer supported.
Locked
rrlDev
Posts: 4
Joined: Wed Jun 24, 2015 3:19 am

Updating custom class save after game release

Post by rrlDev »

Hi.

Simple question, I think. I have a custom class that stores pretty much all of the player data for my game, and I save it with Easy Save 2 by using the Manage Types window and adding support for its properties through that. Saving and Loading of it works fine. I use a custom class since I plan on having multiple saves per install of the game (so multiple copies of the class being saved to different tags or paths).

The problem, though, is that if I update the type when I add a new variable to save, the previous saves do not load. The exact line of code I'm using to load is:

Code: Select all

playerMain = ES2.Load<PlayerStats> ("playerMain");
The exception in Unity when loading reads "EndOfStreamException: Failed to read past end of stream."

So, to get around it I just erase the game save and then start it again and it works. It doesn't really bug me all that much having to do that, but then it eventually occurred to me that if I update the class after I release the game, that's a completely unacceptable work around for my end users.

So, my (simple) question is: Am I missing something about how saving custom classes is supposed to work that would solve all my problems, or do I need to save player info in a different manner to accommodate my needs? I wouldn't mind that much just generating unique tags for every variable based on which save slot its suppose to be saving/loading (ex. "playerLevel_save1", "playerLevel_save2", etc.), but the custom class saving seemed perfect for what I wanted at first, though I'll admit I didn't research it too much before using it.

Thank you for any help anyone can provide.
User avatar
Joel
Moodkie Staff
Posts: 4824
Joined: Wed Nov 07, 2012 10:32 pm

Re: Updating custom class save after game release

Post by Joel »

Hi there,

Adding support for a type means that the data is read sequentially to maximise performance. The disadvantage of this is that the structure of the ES2Type needs to match that of the file. To achieve what you require, there are a number of ways you can approach it.

The easiest way might be to modify the ES2UserType file in Assets/Easy Save 2/Types to include a version number which you save with the data. When you load, you can use this version number to load the appropriate variables.

For example, the ES2Type's Write method could look like this:
public override void Write(object obj, ES2Writer writer)
{
	TestType data = (TestType)obj;
	
	// Write the version number, which you increment whenever you add fields to the ES2Type.
	writer.Write(3);
	
	writer.Write(data.myString);
	writer.Write(data.myFloat);
	writer.Write(data.myVector3);
	writer.Write(data.myQuaternion);
	writer.Write(data.myChar);
}
And then the Read method would look something like this:
public override void Read(ES2Reader reader, object c)
{
	TestType data = (TestType)c;
	
	// Read the version number.
	int fileVersion = reader.Read<int>();
	
	// All versions of the file will have this in.
	data.myString = reader.Read<string>();
	data.myFloat = reader.Read<float>();
	
	// These are fields we added in version 2. 
	if(fileVersion >= 2)
	{
		data.myVector3 = reader.Read<Vector3>();
		data.myQuaternion = reader.Read<Quaternion>();
	}
	
	// This is a field we added in version 3.
	if(fileVersion >= 3)
		data.myChar = reader.Read<char>();
}
Hope this helps,

All the best,
Joel
rrlDev
Posts: 4
Joined: Wed Jun 24, 2015 3:19 am

Re: Updating custom class save after game release

Post by rrlDev »

Wow, thanks, that seems like a good idea. I'll definitely test this out with my project, but it looks like it will work well for what I need. I'll post again if I have trouble with it, but it seems simple enough.

Thanks again.
Lordinarius
Posts: 8
Joined: Tue Jun 14, 2016 2:30 pm

Re: Updating custom class save after game release

Post by Lordinarius »

Joel wrote:Hi there,

Adding support for a type means that the data is read sequentially to maximise performance. The disadvantage of this is that the structure of the ES2Type needs to match that of the file. To achieve what you require, there are a number of ways you can approach it.

The easiest way might be to modify the ES2UserType file in Assets/Easy Save 2/Types to include a version number which you save with the data. When you load, you can use this version number to load the appropriate variables.

For example, the ES2Type's Write method could look like this:
public override void Write(object obj, ES2Writer writer)
{
	TestType data = (TestType)obj;
	
	// Write the version number, which you increment whenever you add fields to the ES2Type.
	writer.Write(3);
	
	writer.Write(data.myString);
	writer.Write(data.myFloat);
	writer.Write(data.myVector3);
	writer.Write(data.myQuaternion);
	writer.Write(data.myChar);
}
And then the Read method would look something like this:
public override void Read(ES2Reader reader, object c)
{
	TestType data = (TestType)c;
	
	// Read the version number.
	int fileVersion = reader.Read<int>();
	
	// All versions of the file will have this in.
	data.myString = reader.Read<string>();
	data.myFloat = reader.Read<float>();
	
	// These are fields we added in version 2. 
	if(fileVersion >= 2)
	{
		data.myVector3 = reader.Read<Vector3>();
		data.myQuaternion = reader.Read<Quaternion>();
	}
	
	// This is a field we added in version 3.
	if(fileVersion >= 3)
		data.myChar = reader.Read<char>();
}
Hope this helps,

All the best,
Joel
Bump

Hello Joel i was looking solution to this issue. I got something, but don't know is it proper way and wanted to take your opinion. It is giving exception so I thought I could use it. And tried to using try-catch and works like a charm. I always give default values those new variables already, catching that exception is just skipping them.

Code: Select all

public class ES2UserType_SettingsBrigde : ES2Type
{

	public override void Write(object obj, ES2Writer writer)
	{
		SettingsBrigde data = (SettingsBrigde)obj;
        // Add your writer.Write calls here.
		writer.Write(data.FxVolume);
		writer.Write(data.MusicVolume);
		writer.Write(data.shadow);
		writer.Write(data.isBaterySave);
		writer.Write(data.controllerType);
        writer.Write(data.qualityPreset);
	}
	
	public override object Read(ES2Reader reader)
	{
		SettingsBrigde data = GetOrCreate<SettingsBrigde>();
		Read(reader, data);
		return data;
	}

	public override void Read(ES2Reader reader, object c)
	{
		SettingsBrigde data = (SettingsBrigde)c;
        // Add your reader.Read calls here to read the data into the object.
        try
        {
            data.FxVolume = reader.Read<System.Single>();
            data.MusicVolume = reader.Read<System.Single>();
            data.shadow = reader.Read<System.Boolean>();
            data.isBaterySave = reader.Read<System.Boolean>();
            data.controllerType = reader.Read<TouchInputLayout>();
            data.qualityPreset = reader.Read<QualitySetttingsOptions>();
            //A new dummy variable reading
            data.MyProperty = reader.Read<System.Boolean>();
            Debug.Log("Not Reachable");
        }
        catch (System.IO.EndOfStreamException e)
        {
            Debug.Log(e);
            //throw;
        }
        
    }
	
	/* ! Don't modify anything below this line ! */
	public ES2UserType_SettingsBrigde():base(typeof(SettingsBrigde)){}
}

Code: Select all

public bool MyProperty
    {
        get { return myboo; }
        set { Debug.Log("Not Reachable"); myboo = value; }
    }
User avatar
Joel
Moodkie Staff
Posts: 4824
Joined: Wed Nov 07, 2012 10:32 pm

Re: Updating custom class save after game release

Post by Joel »

Hi there,

Catching the EndOfStreamException is certainly one way to do it. You'll just have to ensure that you don't disable exceptions when building (i.e. don't set the Fast but no Exceptions setting in Unity's build settings). However, do note that if another tag is stored in the file after this one, when you reach your dummy variable line it won't throw an EndOfStreamException and instead will try to read the next bytes in the stream as if it is your type. This could throw quite a few different exceptions depending on what the next bytes are, or even no exception at all (in the case of reading a bool, it's likely that no exception will be thrown).

All the best,
Joel
Joel @ Moodkie Interactive
Purchase Easy Save | Contact | Guides | Docs | Getting started
PabloAM
Posts: 26
Joined: Tue Nov 18, 2014 8:43 pm

Re: Updating custom class save after game release

Post by PabloAM »

UPDATED: My code doesn´t work either :_(

So, how can I save a new variable after release avoiding the problem of: ?????

Code: Select all

NullReferenceException: Object reference not set to an instance of an object
ES2Writer.Write[String] (System.Collections.Generic.List`1 param, .ES2Type type)
ES2Writer.Write[String] (System.Collections.Generic.List`1 param)
ES2UserType_BuyPackInfo.Write (System.Object obj, .ES2Writer writer) (at Assets/Easy Save 2/Types/ES2UserType_BuyPackInfo.cs:14)
Thanks!
------ OLD

Hello,I Tried to use this code:

Code: Select all

public override void Write(object obj, ES2Writer writer)
{
   TestType data = (TestType)obj;
   
    // Write the version number, which you increment whenever you add fields to the ES2Type.
    writer.Write(3);
     
    writer.Write(data.myString);
    writer.Write(data.myFloat);
 writer.Write(data.myVector3);
   writer.Write(data.myQuaternion);
    writer.Write(data.myChar);
}

Code: Select all

public override void Read(ES2Reader reader, object c)
{
  TestType data = (TestType)c;
     
    // Read the version number.
 int fileVersion = reader.Read<int>();
  
    // All versions of the file will have this in.
  data.myString = reader.Read<string>();
    data.myFloat = reader.Read<float>();
   
    // These are fields we added in version 2. 
 if(fileVersion >= 2)
 {
       data.myVector3 = reader.Read<Vector3>();
      data.myQuaternion = reader.Read<Quaternion>();
    }
    
    // This is a field we added in version 3.
   if(fileVersion >= 3)
     data.myChar = reader.Read<char>();
}
but crashes (reading) anyway if you come from clean code version.

Is because I added "version" after release??
It should be added like this ??

Code: Select all

public override void Write(object obj, ES2Writer writer)
{
   TestType data = (TestType)obj;
   
    writer.Write(data.myString);
    writer.Write(data.myFloat);

 // Write the version number, which you increment whenever you add fields to the ES2Type.
    writer.Write(1);
    writer.Write(data.myChar);


}

Code: Select all

public override void Read(ES2Reader reader, object c)
{
  TestType data = (TestType)c;
     
    
    // All versions of the file will have this in.
  data.myString = reader.Read<string>();
    data.myFloat = reader.Read<float>();
   
// Read the version number.
 int fileVersion = reader.Read<int>();
  
    // This is a field we added in version 3.
   if(fileVersion >= 1)
     data.myChar = reader.Read<char>();
}

Thanks!
User avatar
Joel
Moodkie Staff
Posts: 4824
Joined: Wed Nov 07, 2012 10:32 pm

Re: Updating custom class save after game release

Post by Joel »

Hi there,

This is indeed because you've added the version after release.

In this case you would need to use your old ES2Type to load the data, delete the old file, and then create the new file with the new ES2Type.Although undocumented, ES2Reader.Read and ES2Writer.Write methods accept an ES2Type as the final parameter.

All the best,
Joel
Joel @ Moodkie Interactive
Purchase Easy Save | Contact | Guides | Docs | Getting started
PabloAM
Posts: 26
Joined: Tue Nov 18, 2014 8:43 pm

Re: Updating custom class save after game release

Post by PabloAM »

Thanks for reply!

Could you write a code example please??

Btw, should not the plugin add it by default and update the number and add the if condition automatically when we Update it in the editor??
That feature is added in the Easy Save 3???


best regards
User avatar
Joel
Moodkie Staff
Posts: 4824
Joined: Wed Nov 07, 2012 10:32 pm

Re: Updating custom class save after game release

Post by Joel »

With regards to Easy Save 3, it uses named parameters rather than sequential writing, so this becomes a non-issue.

With regards to the example of the last method I describe, it very much depends on how your project is implemented (which I can't assist with), but generally it would be something like this:
MyClass oldData;
// Load your old data using the old ES2Type.
using(var reader = ES2Reader.Create("myFile.txt"))
{
    // Provide your old ES3Type as a parameter when reading.
    oldData = reader.Read<MyClass>(new ES3UserType_MyClass());
}

// Now re-save the loaded data 
using(var writer = ES2Writer.Create("myFile.txt"))
{
    // Now write the data we just loaded with your new ES3Type.
    writer.Write<MyClass>(oldData, new ES3UserType_MyClass());
    writer.Save(false);
}
You should only do this once, and not do it again after the file has already been updated. The easiest way to determine this would simply be to create a file containing anything after performing the update. Then you can check whether this file exists ... if it doesn't, the data in your file needs updating.
Joel @ Moodkie Interactive
Purchase Easy Save | Contact | Guides | Docs | Getting started
Locked