Click or drag to resize

PersistentFieldAttribute Class

A simple attribute for the fields that need (de)serialization.
Inheritance Hierarchy

Namespace:  KSPDev.ConfigUtils
Assembly:  KSPDev_Utils.1.1 (in KSPDev_Utils.1.1.dll) Version: 1.1 for KSP v1.6+
Syntax
C#
[AttributeUsageAttribute(AttributeTargets.Field)]
public sealed class PersistentFieldAttribute : BasePersistentFieldAttribute
Request Example View Source

The PersistentFieldAttribute type exposes the following members.

Constructors
  NameDescription
Public methodPersistentFieldAttribute
Initializes a new instance of the PersistentFieldAttribute class
Top
Properties
  NameDescription
Public propertyisCollection
Specifies if the annotated field is a collection of values.
Top
Remarks

The readonly fields cannot be restored from a persistent state. However, they can be written out.

By default the ordinal values are handled via StandardOrdinaryTypesProto and the collection fields via GenericCollectionTypeProto. These proto handlers can be changed in the annotation by assigning properties ordinaryTypeProto and/or collectionTypeProto.

Examples
Below is a simple usage of the attribute.
class ClassWithPersistentFields {
  [PersistentField("my/listField", isCollection = true)]
  private List<string> sampleList = new List<string>();

  internal struct ComplexType {
    [PersistentField("val1", group = "nevermind")]
    public bool boolVal;
    [PersistentField("val2")]
    public Color colorVal;
  }

  [PersistentField("my/setField", isCollection = true, group = "abc")]
  private HashSet<ComplexType> sampleSet = new HashSet<ComplexType>();

  void SaveConfigs() {
    // Save a default group of fields: only field "sampleList" qualifies.
    ConfigAccessor.WriteFieldsIntoFile("settings.cfg", instance: this);
    /* The following structure in the file will be created:
     * {
     *   my
     *   {
     *     listField: string1
     *     listField: string2
     *   }
     * }
     */

    // Save a specific group of fields: only field "sampleSet" belongs to group "abc".
    sampleSet.Add(new ComplexType() { boolVal = true, colorVal = Color.black });
    sampleSet.Add(new ComplexType() { boolVal = false, colorVal = Color.white });
    ConfigAccessor.WriteFieldsIntoFile("settings.cfg", instance: this, group: "abc");
    /* The following structure in the file will be created:
     * {
     *     setField
     *     {
     *       val1: True
     *       val2: 0,0,0,1
     *     }
     *     setField
     *     {
     *       val1: false
     *       val2: 1,1,1,1
     *     }
     *   }
     * }
     */
  }
}

Note that the group is ignored in the nested types. I.e. in ComplexType in this case. However, if ComplexType was an immediate target of the WriteFieldsIntoFile call then the group would be considered.

Visibility of the annotated field is also important. The persistent field attributes are only visible in the child class if they were public or protected in the parent. The private field annotations are not inherited and need to be handled at the level of the declaring class.

class Parent {
  [PersistentField("parent_private")]
  private int field1 = 1;

  [PersistentField("parent_protected")]
  protected int field2 = 2;

  [PersistentField("parent_public")]
  public int field3 = 3;
}

class Child : Parent {
  [PersistentField("child_private")]
  private int field1 = 10;

  void SaveConfig() {
    // Save all fields in the inherited type. 
    ConfigAccessor.WriteFieldsIntoFile("settings.cfg", instance: this);
    /* The following structure in the file will be created:
     * {
     *     parent_protected: 2
     *     parent_public: 3
     *     child_private: 10
     * }
     */

    // Save all fields in the base type. 
    ConfigAccessor.WriteFieldsIntoFile("settings.cfg", instance: (Parent) this);
    /* The following structure in the file will be created:
     * {
     *     parent_private: 1
     *     parent_protected: 2
     *     parent_public: 3
     * }
     */
  }
}

The code above implies that in a common case unsealed class should put the private fields in a group other than default to avoid settings collision.

When the type of the field is different from a primitive C# type or a common Unity 4 type you may need to provide a custom value handler to deal with (de)serializing. E.g. for an ordinary type it may look like this:

class CustomType {
  [PersistentField("my/custom/type", ordinaryTypeProto = typeof(MyTypeProto))]
  private MyType field1;
}

Or your custom class can implement a KSP interface IConfigNode, and it will be invoked during the field saving and restoring.

class NodeCustomType : IConfigNode {
  public virtual void Save(ConfigNode node) {
  }
  public virtual void Load(ConfigNode node) {
  }
}

In case of your type is really simple, and you can serialize it into a plain string, you may choose to implement IPersistentField instead. It works in a similar way but the source/target of the persistense is a string instead of a config node.

If your custom type is a collection that cannot be handled by the standard proto you can provide your own collection proto handler. Note that if you do then the annotated field will be treated as a collection. In fact, when you set isCollection = true what actually happens is just assigning GenericCollectionTypeProto as a collection proto handler.

class CustomTypes {
  [PersistentField("my/custom/type", collectionTypeProto = typeof(MyCollectionProto))]
  private MyCollection field1;
}
For more examples on custom proto handlers see AbstractOrdinaryValueTypeProto and AbstractCollectionTypeProto.
See Also