This project has moved. For the latest updates, please go here.

Extending Yttrium: Custom Data Structures

Yttrium is prepared to deal with your own custom data structures, packed in your own package.

1 - Interfaces for Custom Data Structures

Any piece of data you can assign or attach to a signal or a bus is a custom data structure: ValueStructues (the actual current value of a signal), Properties and Categories. All these objects have in common that they implement the ICustomData interface. Consequently, IValueStructure, IProperty and ICategory all inherit from ICustomData.
Custom Data plays a special role whenever a math system is serialized or deserialized, because you may store references to core objects (basically signals or buses) in your custom objects. Clearly such data needs special handling, as implemented in the microkernel serializer. By declaring your object as custom data, implementing the ICustomData interface and applying its patterns, you mark it as compatible and yttrium can deal with it.
To ensure a seamless integration, don't forget to register all your custom data types in your package manager to the library:

Service<ILibrary>.Instance.AddCustomDataType<YourType>();

2 - The ICustomData Interface

The interface is defined as follows:

public interface ICustomData
{
    MathIdentifier TypeId { get;}

    bool ReferencesCoreObjects { get;}
    IEnumerable<Signal> CollectSignals();
    IEnumerable<Bus> CollectBuses();

    void Serialize(XmlWriter writer, IDictionary<Guid, Guid> signalMappings, IDictionary<Guid, Guid> busMappings);
}

Also, any instantiable (non-abstract) class implementing ICustomData is expected to have the following static method deserialization and the structure type property implemented

private static YourType Deserialize(XmlReader reader, IDictionary<Guid, Signal> signals, IDictionary<Guid, Bus> buses);
public static MathIdentifier TypeIdentifier { get; }
  • TypeId should return the identifier of the data structure. It is the instance type equivalent of the static TypeIdentifier property and is usually implemented to simply return the value of this static property.
  • ReferencesCoreObjects, CollectSignals and CollectBuses are required to find out whether your data structure stores references to signals or buses, and to return them (hint: implement using yield return) on request.
  • Serialize and Deserialize are used by the internal serializer.
Note that except TypeId and TypeIdentifier you almost never touch those members in user code, hence consider implementing them explicitly.

3 - Serializing and Deserializing Custom Data

Theoretically you could directly use the Serialize member of the ICustomData interface and the static Deserialize method for serialization and deserialization. You can do this, if the actual instance type of the data you want to store is always the same, so you know where you get the static Deserialize from. In all other cases you should rather use the microkernel Serializer class, that provides various methods to easily serialize/deserialize custom data of any instance type. The YttriumSystemBuilderModule that provides simple import/export of whole math system also heavily relies upon this Serializer class.

4 - Implementing ICustomData

In most cases you don't need to implement ICustomData directly, but decide to inherit from ValueStructureBase, PropertyBase or CategoryBase of the packages object model module instead. However, if you don't, an implementation for a complex value data structure might look like this (only shows parts relevant to ICustomData):

private static readonly MathIdentifier _customTypeId = new MathIdentifier("Complex", "Std");
private readonly Complex _dataValue;

public static MathIdentifier TypeIdentifier
{
    get { return _customTypeId; }
}
public override MathIdentifier TypeId
{
    get { return _customTypeId; }
}

// we store no signals or buses here
public bool ReferencesCoreObjects
{
    get { return false; }
}
public virtual IEnumerable<Signal> CollectSignals()
{
    yield break;
}
public virtual IEnumerable<Bus> CollectBuses()
{
    yield break;
}

// (de)serialize the real and the imaginary part to seperate XML child elements
public void Serialize(XmlWriter writer, IDictionary<Guid, Guid> signalMappings,
    IDictionary<Guid, Guid> busMappings)
{
    writer.WriteElementString("Real", _dataValue.Real.ToString(Config.InternalNumberFormat));
    writer.WriteElementString("Imag", _dataValue.Imag.ToString(Config.InternalNumberFormat));
}
private static ComplexValue Deserialize(XmlReader reader, IDictionary<Guid, Signal> signals,
    IDictionary<Guid, Bus> buses)
{
    return new ComplexValue(double.Parse(reader.ReadElementString("Real"), Config.InternalNumberFormat),
        double.Parse(reader.ReadElementString("Imag"), Config.InternalNumberFormat));
}

4.1 - Serializing nested custom data

If you store references to other custom data elements, simply use the Serializer class as follows. The data type in the sample store a boolean variable sticky and an ICategory custom data type category:

public void Serialize(XmlWriter writer, IDictionary<Guid, Guid> signalMappings, IDictionary<Guid, Guid> busMappings)
{
    writer.WriteElementString("Sticky", _sticky.ToString());
    Serializer.Serialize(_category, writer, signalMappings, busMappings);
}
private static YourType Deserialize(XmlReader reader, IDictionary<Guid, Signal> signals, IDictionary<Guid, Bus> buses)
{
    bool sticky = bool.Parse(reader.ReadElementString("Sticky"));
    ICategory category = Serializer.Deserialize<ICategory>(reader, signals, buses);
    return new YourType(category, sticky);
}

4.2 - Serializing nested signals and buses

If you have references to signals (or buses), you should use the mappings passed as parameters and store the mapped id instead of the signal. On Deserialization you then simply get the signal back by accessing the passed mappings. Note that to ensure the mappings are provided correctly you have to return them in the CollectSignals and CollectBuses members. In the following sample, the class holds a reference to a signal in _signal:

public void Serialize(XmlWriter writer, IDictionary<Guid, Guid> signalMappings, IDictionary<Guid, Guid> busMappings)
{
    writer.WriteElementString("MySignal", signalMappings[_signal.InstanceId].ToString());
}
private static YourType Deserialize(XmlReader reader, IDictionary<Guid, Signal> signals, IDictionary<Guid, Bus> buses)
{
    Signal signal = signals[Guid.Parse(reader.ReadElementString("MySignal"))];
    return new YourType(signal);
}

Last edited Jul 29, 2012 at 2:01 PM by cdrnet, version 1

Comments

No comments yet.