Saving only certain Components, custom

Discussion and help for Easy Save 3
Post Reply
mpcomplete
Posts: 6
Joined: Tue May 30, 2023 10:32 pm

Saving only certain Components, custom

Post by mpcomplete »

I'm trying to get ES3 to save a list of objects that
- have a reference to the prefab that instantiated them (using AssetReference to point to the prefab)
- have a list of components that need to be saved (only certain components have data to persist)

I've tried a number of different methods and nothing has worked so far.

Any object that I want to save has a SaveObject component attached. My top-level Save method collects all such objects in the scene and saves them. Here is my first attempt:

My Save method:

Code: Select all

    data.Entities = UnityEngine.Object.FindObjectsOfType<SaveObject>().Select(b => b.Save()).ToList();
    ES3.Save("game", data);
and SaveObject looks like:

Code: Select all

public class SaveObject : MonoBehaviour {
  public AssetReference Asset;
  List<Component> Saveables = new();

  public ILoadableObject Save() {
    return new Serialized {
      AssetGUID = Asset.AssetGUID,
      Position = transform.position,
      Rotation = transform.rotation,
      Components = Saveables.ToArray()
    };
  }
  class Serialized : ILoadableObject {
    public string AssetGUID;
    public Vector3 Position;
    public Quaternion Rotation;
    public Component[] Components;
    public void Load() {
      var asset = new AssetReference(AssetGUID);
      asset.InstantiateAsync(Position, Rotation).WaitForCompletion();
    }
  }

  public void RegisterSaveable(Component component) {
    Saveables.Add((Component)component);
  }
}
However, when I did it this way, all the Components were saved by reference and none of their runtime data was saved, and their custom ES3Type Writers were never called. I tried using `memberReferenceMode = ES3.ReferenceMode.ByRefAndValue`, but this didn't work at all. It added a host of junk to the save file, and loading it back threw a bunch of errors about missing components.

Here is attempt #2. Rather than saving a custom Serialized object, I tried passing the list of SaveObjects directly to ES3.Save:

Code: Select all

    data.Entities = UnityEngine.Object.FindObjectsOfType<SaveObject>().ToList();
    ES3.Save("game", data, settings);
additionally, I tried using an ES3AutoSave component on each object with a SaveObject component, to list the components that should be saved.

Code: Select all

public class SaveObject : MonoBehaviour {
  public AssetReference Asset;
  List<Component> Saveables = new();

  public void RegisterSaveable(Component component) {
    GetComponent<ES3AutoSave>().componentsToSave.Add(component);
  }

  void Start() {
    GetComponent<ES3AutoSave>().componentsToSave.Add(this);
    GetComponent<ES3AutoSave>().componentsToSave.Add(transform);
  }
}
I think I then had to select all such prefabs and choose "Enable Easy Save for Prefab". It gave me an error that AssetReference was unsupported, so I created a custom ES3Type for SaveObject. No errors now, but none of the components I added to ES3AutoSave appeared in the save data.

I'm out of ideas at this point. Any help would be much appreciated.
User avatar
Joel
Moodkie Staff
Posts: 4826
Joined: Wed Nov 07, 2012 10:32 pm

Re: Saving only certain Components, custom

Post by Joel »

Hi there,
However, when I did it this way, all the Components were saved by reference and none of their runtime data was saved
This is because fields of Unity reference types are stored by reference:
https://docs.moodkie.com/easy-save-3/es ... ted-types/
It added a host of junk to the save file
Please could you show me the junk that it added to the file?
and loading it back threw a bunch of errors about missing components.
Please could you show me the errors you were getting?

Also are your SaveObject Components created at runtime (and/or the GameObjects they're attached to)? If so then a reference to them won't exist with that ID when loading.

All the best,
Joel
Joel @ Moodkie Interactive
Purchase Easy Save | Contact | Guides | Docs | Getting started
mpcomplete
Posts: 6
Joined: Tue May 30, 2023 10:32 pm

Re: Saving only certain Components, custom

Post by mpcomplete »

are your SaveObject Components created at runtime
No, all components statically exist on the object prefabs. The data I want to save is:
- a reference to a toplevel prefab, which is instantiated on load directly from the prefab (if the prefab changes after a save is made, I want the new prefab to be used when loading)
- a list of the data to save for each component (since components are not created at runtime, I don't need to record references or have easy save add them to the object when loading it back)
Please could you show me the junk that it added to the file?
Here's a snippet of the Components section with the "junk" I mentioned:

Code: Select all

					"Components" : [
						{
							"__type" : "Crafter,Assembly-CSharp",
							"_ES3Ref" : "7200367471519923903",
							"goID" : "7305482433124781229",
							"CurrentRecipe" : {
								"_ES3Ref" : "3548133366622195416"
							},
							"InputQueue" : {
							},
							"OutputQueue" : {{
									"_ES3Ref" : "6136114156670819634",
									"ObjectPrefab" : {
										"_ES3Ref" : "8535742076879409394",
										"goID" : "8482832533775620206"
									}
								}:2
							}
						}
					]
For reference, CurrentRecipe is a (thing derived from a) ScriptableObject, and OutputQueue is a Dictionary<ScriptableObject, int>. Two parts in particular perplex me:

Code: Select all

							"__type" : "Crafter,Assembly-CSharp",
							"_ES3Ref" : "7200367471519923903",
							"goID" : "7305482433124781229",
The only necessary bit is the __type. There is already exactly one `Crafter` Component on this object so it is fully identified by the type.

Code: Select all


							"OutputQueue" : {{
									"_ES3Ref" : "6136114156670819634",
									"ObjectPrefab" : {
										"_ES3Ref" : "8535742076879409394",
										"goID" : "8482832533775620206"
									}
								}:2
							}
I'd expect OutputQueue to look more like { {"_ES3Ref" : "6136114156670819634"}:2 }. Not sure what ObjectPrefab is but it seems superfluous.
Please could you show me the errors you were getting?
They are errors my code throws when a component expects to be part of an object with other components but they are missing. It appears that ES3 failed to instantiate the toplevel prefab correctly?

Code: Select all

Easy Save 3 Loaded GameObject (Crafter) is missing required component Animator
UnityEngine.Debug:Assert (bool,string)
MonoExtensions:InitComponentFromChildren<UnityEngine.Animator> (UnityEngine.MonoBehaviour,UnityEngine.Animator&,bool) (at Assets/Scripts/ClassExtensions.cs:113)
Crafter:Awake () (at Assets/Building/Crafter.cs:158)
UnityEngine.GameObject:AddComponent (System.Type)
ES3Types.ES3ComponentType:GetOrAddComponent (UnityEngine.GameObject,System.Type) (at Assets/Plugins/Easy Save 3/Scripts/Types/ES3ComponentType.cs:114)
ES3Types.ES3ComponentType:ReadObject<object> (ES3Reader) (at Assets/Plugins/Easy Save 3/Scripts/Types/ES3ComponentType.cs:82)
ES3Types.ES3ObjectType:Read<object> (ES3Reader) (at Assets/Plugins/Easy Save 3/Scripts/Types/ES3ObjectType.cs:52)
ES3Types.ES3ObjectType:Read<object> (ES3Reader) (at Assets/Plugins/Easy Save 3/Scripts/Types/ES3ObjectType.cs:47)
ES3Reader:ReadObject<object> (ES3Types.ES3Type) (at Assets/Plugins/Easy Save 3/Scripts/Readers/ES3Reader.cs:253)
ES3Reader:Read<object> (ES3Types.ES3Type) (at Assets/Plugins/Easy Save 3/Scripts/Readers/ES3Reader.cs:282)
ES3Types.ES3CollectionType:ReadICollection<object> (ES3Reader,System.Collections.Generic.ICollection`1<object>,ES3Types.ES3Type) (at Assets/Plugins/Easy Save 3/Scripts/Types/Collection Types/ES3CollectionType.cs:52)
ES3Types.ES3ArrayType:Read (ES3Reader) (at Assets/Plugins/Easy Save 3/Scripts/Types/Collection Types/ES3ArrayType.cs:36)
ES3Types.ES3Type:ReadProperties (ES3Reader,object) (at Assets/Plugins/Easy Save 3/Scripts/Types/ES3Type.cs:153)
ES3Types.ES3ReflectedObjectType:ReadObject<object> (ES3Reader) (at Assets/Plugins/Easy Save 3/Scripts/Types/Reflected Types/ES3ReflectedObjectType.cs:26)
ES3Types.ES3ObjectType:Read<object> (ES3Reader) (at Assets/Plugins/Easy Save 3/Scripts/Types/ES3ObjectType.cs:52)
ES3Types.ES3ObjectType:Read<object> (ES3Reader) (at Assets/Plugins/Easy Save 3/Scripts/Types/ES3ObjectType.cs:47)
ES3Reader:ReadObject<object> (ES3Types.ES3Type) (at Assets/Plugins/Easy Save 3/Scripts/Readers/ES3Reader.cs:253)
ES3Reader:Read<object> (ES3Types.ES3Type) (at Assets/Plugins/Easy Save 3/Scripts/Readers/ES3Reader.cs:282)
ES3Types.ES3ListType:Read (ES3Reader) (at Assets/Plugins/Easy Save 3/Scripts/Types/Collection Types/ES3ListType.cs:64)
ES3Types.ES3Type:ReadProperties (ES3Reader,object) (at Assets/Plugins/Easy Save 3/Scripts/Types/ES3Type.cs:153)
ES3Types.ES3ReflectedObjectType:ReadObject<SaveData> (ES3Reader) (at Assets/Plugins/Easy Save 3/Scripts/Types/Reflected Types/ES3ReflectedObjectType.cs:26)
ES3Types.ES3ObjectType:Read<SaveData> (ES3Reader) (at Assets/Plugins/Easy Save 3/Scripts/Types/ES3ObjectType.cs:52)
ES3Reader:ReadObject<SaveData> (ES3Types.ES3Type) (at Assets/Plugins/Easy Save 3/Scripts/Readers/ES3Reader.cs:253)
ES3Reader:Read<SaveData> (ES3Types.ES3Type) (at Assets/Plugins/Easy Save 3/Scripts/Readers/ES3Reader.cs:282)
ES3Reader:Read<SaveData> (string) (at Assets/Plugins/Easy Save 3/Scripts/Readers/ES3Reader.cs:201)
ES3:Load<SaveData> (string,ES3Settings) (at Assets/Plugins/Easy Save 3/Scripts/ES3.cs:443)
SaveData:Load3 () (at Assets/Scripts/Serialization/SaveLoad.cs:87)
SaveData:LoadFromFile (int) (at Assets/Scripts/Serialization/SaveLoad.cs:70)
GameManager/<GameLoop>d__25:MoveNext () (at Assets/Scripts/Game/GameManager.cs:115)
System.Runtime.CompilerServices.AsyncTaskMethodBuilder:Start<GameManager/<GameLoop>d__25> (GameManager/<GameLoop>d__25&)
GameManager:GameLoop (TaskScope)
GameManager/<Run>d__19:MoveNext () (at Assets/Scripts/Game/GameManager.cs:74)
System.Runtime.CompilerServices.AsyncTaskMethodBuilder:Start<GameManager/<Run>d__19> (GameManager/<Run>d__19&)
GameManager:Run (TaskScope)
TaskScope/<>c__DisplayClass12_0/<<Start>b__0>d:MoveNext () (at Assets/Scripts/Tasks/TaskScope.cs:47)
System.Runtime.CompilerServices.AsyncVoidMethodBuilder:Start<TaskScope/<>c__DisplayClass12_0/<<Start>b__0>d> (TaskScope/<>c__DisplayClass12_0/<<Start>b__0>d&)
TaskScope/<>c__DisplayClass12_0:<Start>b__0 ()
UnityEngine.UnitySynchronizationContext:ExecuteTasks ()

Easy Save 3 Loaded GameObject (Crafter) is missing required component SaveObject
UnityEngine.Debug:Assert (bool,string)
MonoExtensions:InitComponent<SaveObject> (UnityEngine.MonoBehaviour,SaveObject&,bool) (at Assets/Scripts/ClassExtensions.cs:103)
Crafter:Awake () (at Assets/Building/Crafter.cs:158)
UnityEngine.GameObject:AddComponent (System.Type)
ES3Types.ES3ComponentType:GetOrAddComponent (UnityEngine.GameObject,System.Type) (at Assets/Plugins/Easy Save 3/Scripts/Types/ES3ComponentType.cs:114)
ES3Types.ES3ComponentType:ReadObject<object> (ES3Reader) (at Assets/Plugins/Easy Save 3/Scripts/Types/ES3ComponentType.cs:82)
ES3Types.ES3ObjectType:Read<object> (ES3Reader) (at Assets/Plugins/Easy Save 3/Scripts/Types/ES3ObjectType.cs:52)
ES3Types.ES3ObjectType:Read<object> (ES3Reader) (at Assets/Plugins/Easy Save 3/Scripts/Types/ES3ObjectType.cs:47)
ES3Reader:ReadObject<object> (ES3Types.ES3Type) (at Assets/Plugins/Easy Save 3/Scripts/Readers/ES3Reader.cs:253)
ES3Reader:Read<object> (ES3Types.ES3Type) (at Assets/Plugins/Easy Save 3/Scripts/Readers/ES3Reader.cs:282)
ES3Types.ES3CollectionType:ReadICollection<object> (ES3Reader,System.Collections.Generic.ICollection`1<object>,ES3Types.ES3Type) (at Assets/Plugins/Easy Save 3/Scripts/Types/Collection Types/ES3CollectionType.cs:52)
ES3Types.ES3ArrayType:Read (ES3Reader) (at Assets/Plugins/Easy Save 3/Scripts/Types/Collection Types/ES3ArrayType.cs:36)
ES3Types.ES3Type:ReadProperties (ES3Reader,object) (at Assets/Plugins/Easy Save 3/Scripts/Types/ES3Type.cs:153)
ES3Types.ES3ReflectedObjectType:ReadObject<object> (ES3Reader) (at Assets/Plugins/Easy Save 3/Scripts/Types/Reflected Types/ES3ReflectedObjectType.cs:26)
ES3Types.ES3ObjectType:Read<object> (ES3Reader) (at Assets/Plugins/Easy Save 3/Scripts/Types/ES3ObjectType.cs:52)
ES3Types.ES3ObjectType:Read<object> (ES3Reader) (at Assets/Plugins/Easy Save 3/Scripts/Types/ES3ObjectType.cs:47)
ES3Reader:ReadObject<object> (ES3Types.ES3Type) (at Assets/Plugins/Easy Save 3/Scripts/Readers/ES3Reader.cs:253)
ES3Reader:Read<object> (ES3Types.ES3Type) (at Assets/Plugins/Easy Save 3/Scripts/Readers/ES3Reader.cs:282)
ES3Types.ES3ListType:Read (ES3Reader) (at Assets/Plugins/Easy Save 3/Scripts/Types/Collection Types/ES3ListType.cs:64)
ES3Types.ES3Type:ReadProperties (ES3Reader,object) (at Assets/Plugins/Easy Save 3/Scripts/Types/ES3Type.cs:153)
ES3Types.ES3ReflectedObjectType:ReadObject<SaveData> (ES3Reader) (at Assets/Plugins/Easy Save 3/Scripts/Types/Reflected Types/ES3ReflectedObjectType.cs:26)
ES3Types.ES3ObjectType:Read<SaveData> (ES3Reader) (at Assets/Plugins/Easy Save 3/Scripts/Types/ES3ObjectType.cs:52)
ES3Reader:ReadObject<SaveData> (ES3Types.ES3Type) (at Assets/Plugins/Easy Save 3/Scripts/Readers/ES3Reader.cs:253)
ES3Reader:Read<SaveData> (ES3Types.ES3Type) (at Assets/Plugins/Easy Save 3/Scripts/Readers/ES3Reader.cs:282)
ES3Reader:Read<SaveData> (string) (at Assets/Plugins/Easy Save 3/Scripts/Readers/ES3Reader.cs:201)
ES3:Load<SaveData> (string,ES3Settings) (at Assets/Plugins/Easy Save 3/Scripts/ES3.cs:443)
SaveData:Load3 () (at Assets/Scripts/Serialization/SaveLoad.cs:87)
SaveData:LoadFromFile (int) (at Assets/Scripts/Serialization/SaveLoad.cs:70)
GameManager/<GameLoop>d__25:MoveNext () (at Assets/Scripts/Game/GameManager.cs:115)
System.Runtime.CompilerServices.AsyncTaskMethodBuilder:Start<GameManager/<GameLoop>d__25> (GameManager/<GameLoop>d__25&)
GameManager:GameLoop (TaskScope)
GameManager/<Run>d__19:MoveNext () (at Assets/Scripts/Game/GameManager.cs:74)
System.Runtime.CompilerServices.AsyncTaskMethodBuilder:Start<GameManager/<Run>d__19> (GameManager/<Run>d__19&)
GameManager:Run (TaskScope)
TaskScope/<>c__DisplayClass12_0/<<Start>b__0>d:MoveNext () (at Assets/Scripts/Tasks/TaskScope.cs:47)
System.Runtime.CompilerServices.AsyncVoidMethodBuilder:Start<TaskScope/<>c__DisplayClass12_0/<<Start>b__0>d> (TaskScope/<>c__DisplayClass12_0/<<Start>b__0>d&)
TaskScope/<>c__DisplayClass12_0:<Start>b__0 ()
UnityEngine.UnitySynchronizationContext:ExecuteTasks ()
User avatar
Joel
Moodkie Staff
Posts: 4826
Joined: Wed Nov 07, 2012 10:32 pm

Re: Saving only certain Components, custom

Post by Joel »

Hi there,
No, all components statically exist on the object prefabs.
If they exist on a prefab then this would indicate that they are instantiated at runtime, as the prefab is instantiated at runtime.
The only necessary bit is the __type. There is already exactly one `Crafter` Component on this object so it is fully identified by the type.
The serializer can't guarantee that there is only one Component of a specific type on a GameObject so it must resolve the Components by reference, which is why the other data is included.
I'd expect OutputQueue to look more like { {"_ES3Ref" : "6136114156670819634"}:2 }. Not sure what ObjectPrefab is but it seems superfluous.
ObjectPrefab is a field of the object which will be serialized as you're telling to to serialize everything by reference and value.
They are errors my code throws when a component expects to be part of an object with other components but they are missing. It appears that ES3 failed to instantiate the toplevel prefab correctly?
This is because you're only saving the Components, and as the Components are instantiated at runtime, a reference ID to them and their parent prefab instance won't exist at runtime. This means when you load, the prefab the Component belonged to no longer exists and it needs to make a new GameObject to attach the Component to.

In your case you should be saving and loading the prefab instance itself (following the Saving and Loading Prefab Instances guide) to ensure that a reference to them exists.

Also if you private message me your invoice number I can send you the upcoming update which includes an ES3GameObject Component which has a components field which allows you to specify which Components on a GameObject are serialized when saving and loading GameObjects/prefab instances.

All the best,
Joel
Joel @ Moodkie Interactive
Purchase Easy Save | Contact | Guides | Docs | Getting started
mpcomplete
Posts: 6
Joined: Tue May 30, 2023 10:32 pm

Re: Saving only certain Components, custom

Post by mpcomplete »

If they exist on a prefab then this would indicate that they are instantiated at runtime
Ah sorry, I thought you were asking if the Components were being added programmatically (which is not the case). You are correct.
In your case you should be saving and loading the prefab instance itself
That's what I was attempting to do with the following snippet:

Code: Select all

    data.Entities = UnityEngine.Object.FindObjectsOfType<SaveObject>().ToList();
    ES3.Save("game", data, settings);
What's the correct way to save the prefab instance? I also tried

Code: Select all

    data.Entities = UnityEngine.Object.FindObjectsOfType<SaveObject>().Select(o => o.gameObject).ToList();
    ES3.Save("game", data, settings);
but this only saved a reference to a gameobject and nothing else, despite the gameobject having an ES3AutoSave component with a populated componentsToSave list.
User avatar
Joel
Moodkie Staff
Posts: 4826
Joined: Wed Nov 07, 2012 10:32 pm

Re: Saving only certain Components, custom

Post by Joel »

Hi there,

In both of your examples you're saving a script with a field/property named Entities when you should be saving a List<GameObject>. Fields of Object types will be saved by reference, not by value, so it won't be saving the prefab itself, just a reference to it.

Also just to clarify, have you followed the following instruction from the guide?
To save and load a prefab instance:

1. Right-click the prefab and select Enable Easy Save for Prefab.
Joel @ Moodkie Interactive
Purchase Easy Save | Contact | Guides | Docs | Getting started
mpcomplete
Posts: 6
Joined: Tue May 30, 2023 10:32 pm

Re: Saving only certain Components, custom

Post by mpcomplete »

have you followed the following instruction from the guide?

1. Right-click the prefab and select Enable Easy Save for Prefab.
Yes.
you should be saving a List<GameObject>
I see, so to save a list of GameObjects, they have to be a top-level key passed to ES3.Save? I've just tried that, and now it is saving the full GameObject hierarchy with tons of data I don't care about saving (like child transforms and MeshRenderers).

To reiterate, what I want to save is:
- a reference to the prefab that each object was instantiated from,
- a list of component-specific data that I've explicitly marked as relevant.

Am I correct in assuming this is not a workflow EasySave is designed to easily support?
mpcomplete
Posts: 6
Joined: Tue May 30, 2023 10:32 pm

Re: Saving only certain Components, custom

Post by mpcomplete »

Update: I spoke too soon. I've managed to achieve the result I want. Here's my final setup:

1. Every object (prefab) that should be saved has a SaveObject component and an ES3AutoSave component. For all such prefabs, select "Enable Easy Save for Prefab" via the context menu (which adds an ES3Prefab component, and perhaps other stuff?).
2. Every component that wants to save its data registers itself with SaveObject.
3. Use [ES3Serializable] and [ES3NonSerializable] attributes on component data to control what is saved if it differs from default.
4. Ensure the scene has an ES3ReferenceMgr in it. I had to manually click "Refresh" on this object any time I updated "Enable Easy Save for Prefab".

Code:

Code: Select all

public class SaveObject : MonoBehaviour {
  ES3AutoSave ES3AutoSave;

  public void RegisterSaveable(Component component) {
    ES3AutoSave.componentsToSave.Add(component);
  }

  void Awake() {
    ES3AutoSave = GetComponent<ES3AutoSave>();
    ES3AutoSave.componentsToSave.Add(this);
    ES3AutoSave.componentsToSave.Add(transform);
  }
  
  public static void SaveAll(string key) {
    var settings = new ES3Settings { memberReferenceMode = ES3.ReferenceMode.ByRef };
    var objects = UnityEngine.Object.FindObjectsOfType<SaveObject>().Select(b => b.gameObject).ToList();
    ES3.Save(key, objects, settings);
  }
  public static void LoadAll(string key) {
    var settings = new ES3Settings { memberReferenceMode = ES3.ReferenceMode.ByRef };
    ES3.Load<List<GameObject>>(key, settings);
  }
}
mpcomplete
Posts: 6
Joined: Tue May 30, 2023 10:32 pm

Re: Saving only certain Components, custom

Post by mpcomplete »

Thanks for all your prompt replies!
User avatar
Joel
Moodkie Staff
Posts: 4826
Joined: Wed Nov 07, 2012 10:32 pm

Re: Saving only certain Components, custom

Post by Joel »

Glad you managed to find a solution which works for you :)

All the best,
Joel
Joel @ Moodkie Interactive
Purchase Easy Save | Contact | Guides | Docs | Getting started
Post Reply