Custom classes and polymorphism

Easy Save 2 has been replaced by Easy Save 3, so is no longer supported.
Locked
viveleroi
Posts: 17
Joined: Fri Feb 03, 2017 1:40 am

Custom classes and polymorphism

Post by viveleroi »

I'm writing save logic for an "inventory" component. This inventory holds any number of items and I'm trying to figure out how best to save/load them.

Essentially, I have an abstract base "Item" class, and every item is a subclass. So I might have "Battery", "Hammer", "Coin", etc.

In order to read correctly, I need to know the type. I've looked through the forums and found http://www.moodkie.com/forum/viewtopic. ... nent#p2968 - which would work for any custom type, not just components and is something I could use here.

Is that the recommended way to handle reading not-yet-known types?

I need to use this logic anyway because an item may have a variable list of components which need to be saved as well.
User avatar
Joel
Moodkie Staff
Posts: 4852
Joined: Wed Nov 07, 2012 10:32 pm

Re: Custom classes and polymorphism

Post by Joel »

Hi there,

A method which may be more applicable to your circumstance is described in the final post of this thread here: http://www.moodkie.com/forum/viewtopic. ... 956&p=2618

All the best,
Joel
Joel @ Moodkie Interactive
Purchase Easy Save | Contact | Guides | Docs | Getting started
viveleroi
Posts: 17
Joined: Fri Feb 03, 2017 1:40 am

Re: Custom classes and polymorphism

Post by viveleroi »

Unfortunately that's just not a feasible solution because eventually I'll have hundreds of items and some day would like to allow for mods to add custom items. Hard-coding every single one into the read method is going to get ugly fast.
User avatar
Joel
Moodkie Staff
Posts: 4852
Joined: Wed Nov 07, 2012 10:32 pm

Re: Custom classes and polymorphism

Post by Joel »

Easy Save 3 has the ability to handle polymorphism by default, but as it's currently in alpha we can't give a date as to when this would be available.

- Joel
Joel @ Moodkie Interactive
Purchase Easy Save | Contact | Guides | Docs | Getting started
viveleroi
Posts: 17
Joined: Fri Feb 03, 2017 1:40 am

Re: Custom classes and polymorphism

Post by viveleroi »

I haven't tested this, pending fixing a type issue we're discussing in another thread, but what if I did this:

During write, I write the typeof to the file:

Code: Select all

writer.Write(data.GetType().ToString());
Then during read, I dynamically instantiate it:

Code: Select all

var typeStr = reader.Read<string>();
var type = Type.GetType(typeStr);

if (type != null) {
    data = (Item) Activator.CreateInstance(type);
}
I haven't tested this but should be close to what I think will work.
viveleroi
Posts: 17
Joined: Fri Feb 03, 2017 1:40 am

Re: Custom classes and polymorphism

Post by viveleroi »

Yup, that works perfectly.

For anyone stumbling upon this thread in the future, here's my code:

Code: Select all

using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class ES2UserType_Item : ES2Type {
	public override void Write(object obj, ES2Writer writer) {
		Item data = (Item)obj;

		writer.Write(data.GetType().ToString());
		writer.Write(data.quantity);

		// Get all supported components
		var supportedComponents = new List<Component>();
		foreach(var component in data.gameObject.GetComponents<Component>()) {
			if (ES2TypeManager.GetES2Type(component.GetType()) != null) {
				supportedComponents.Add(component);
			}
		}

		// Write count so we know how many to load
		writer.Write(supportedComponents.Count);

		// Save each component
		foreach(var component in supportedComponents) {
			var es2Type = ES2TypeManager.GetES2Type(component.GetType());
			writer.Write(es2Type.hash);
			writer.Write(component);
		}
	}
	
	public override object Read(ES2Reader reader) {
		Item data = null;

		// Read back the actual item type
		var typeStr = reader.Read<string>();
		var type = Type.GetType(typeStr);

		if (type != null) {
			// Instantiate the item directly
			data = (Item) Activator.CreateInstance(type);

			// Set quantity
			data.quantity = reader.Read<System.Int32>();

			// How many components do we need to load?
			int componentCount = reader.Read<int>();

			// Restore components
			for(int i = 0; i < componentCount; i++){
				var es2Type = ES2TypeManager.GetES2Type(reader.Read<int>());

				// Get component from GameObject, or add it if it doesn't have one.
				Component component = data.gameObject.GetComponent(es2Type.type);
				if (component == null) {
					component = data.gameObject.AddComponent(es2Type.type);
				}

				reader.Read<System.Object>(es2Type, component);
			}
		}
		// @todo log an error?

		return data;
	}
	
	/* ! Don't modify anything below this line ! */
	public ES2UserType_Item():base(typeof(Item)){}
}
User avatar
Joel
Moodkie Staff
Posts: 4852
Joined: Wed Nov 07, 2012 10:32 pm

Re: Custom classes and polymorphism

Post by Joel »

Glad that's working for you. Do be aware that there's a performance cost to Activator.CreateInstance if calling it regularly (around 11x slower than new).

All the best,
Joel
Joel @ Moodkie Interactive
Purchase Easy Save | Contact | Guides | Docs | Getting started
viveleroi
Posts: 17
Joined: Fri Feb 03, 2017 1:40 am

Re: Custom classes and polymorphism

Post by viveleroi »

That's good to know. Functional saves/loads are my focus right now and I'm finally pretty close to having all that work done - just doing some cleanup, testing, etc.

Eventually I'll figure out how best to support mods. While hard-coding instance checks would be a pain for myself, since my game will likely have a hundred or more items, it will be impossible for modders who provide their own items. So I'll revisit how I handle this when I overhaul things to make way for mods.

I think in that case, a custom type or custom save logic will be essential. If that's the case though, I'll need to ensure those custom types are registered correctly with ES2 .
Locked