Skip to content

Data Object Registries

Cole Campbell edited this page Mar 3, 2018 · 3 revisions

Overview

In order to facilitate data-driven development, Ultraviolet exposes data objects and data object registries. Data objects are loaded from XML files by a custom XML serializer, and are designed to be uniquely identifiable across all instances of the Ultraviolet Framework.

Creating a new Data Object

Data objects must derive from the UltravioletDataObject class found in the TwistedLogik.Ultraviolet.Content namespace. They must also expose a public constructor which takes two arguments: String key and Guid globalID.

public class MyDataObject : UltravioletDataObject
{
    public MyDataObject(String key, Guid globalID)
        : base(key, globalID)
    {

    }
}

A data object's key is a human-friendly name which must be unique within the current application. A data object's global identifier is a GUID which uniquely identifies the data object across all applications.

In addition to these two pieces of information, once a data object has been loaded into an Ultraviolet process, it gains a local identifier. This value is a 16-bit integer which identifies the data object within the registry which loaded it. The local identifier which is assigned to a data object is just an index into the data registry's internal storage; so long as a data object registry loads the same list of objects in the same order, they will be given the same local identifiers across instances of the application.

Nucleus' XML serializer can handle objects containing most kinds of data, including complex type hierarchies. For this example, we'll just add a simple String property to our object, called Test.

public class MyDataObject : UltravioletDataObject
{
    // ...

    public String Test
    { get; protected set; }
}

We've made our set accessor protected so that it can't be modified at runtime. The Nucleus XML serializer will still be able to set it. Setting it to private can cause problems when data objects inherit from one another, so it's usually best to stick to protected.

Data Object Registries

All data objects must be loaded through an associated data object registry. Each type of data object should have exactly one registry.

To define a registry for a new type of data object, inherit from the UltravioletDataObjectRegistry class in the TwistedLogik.Ultraviolet.Content namespace.

public class MyDataObjectRegistry : UltravioletDataObjectRegistry<MyDataObject>
{
    
}

This abstract class requires you to implement two properties: ReferenceResolutionName and DataElementName.

The ReferenceResolutionName property identifies the registry within the reference resolution system. This is how other data objects can create references to data objects of this type.

public override String ReferenceResolutionName
{
    get { return "mydata"; }
}

Consider two types of data object, Foo and Bar. Every instance of Foo has a reference to an object of type Bar. We can represent this reference as a property of type ResolvedDataObjectReference, and within our object definition XML files, we can set its value using a special string which queries the data object registries:

<Foo ID="fffc6cba-94a3-4100-8355-d231439bbe1d" Key="SOME_FOO">
    <BarRef>@bar:SOME_BAR</BarRef>
</Foo>

In this case, the identifier immediately following the @ symbol is the reference resolution name of the data object registry for the Bar type, and SOME_BAR is the key that identifies a particular Bar instance.

The second property of DataObjectRegistry which must be implemented is DataElementName. This value identifies the name of the elements within the XML definition file which contain data objects that this registry can load.

public override String DataElementName
{
    get { return "MyData"; }
}

In this case, the data object registry will attempt to deserialize any children of the root XML node which have the name MyData, i.e.

<?xml version="1.0" encoding="UTF-8" standalone="yes" ?>
<MyDatas>
    <MyData ID="ccef1b7f-f82e-4a42-96ed-51dedb4a6cbb" Key="EXAMPLE_1">
        <!-- etc -->
    <MyData>
    <MyData ID="c289fb0f-0f7f-47d7-996e-e32f5c605e13" Key="EXAMPLE_2">
        <!-- etc -->
    <MyData>
</MyDatas>

Loading Data Objects

Once your data objects and registries have been defined, you can load your objects from XML using the static DataObjectRegistries class in the TwistedLogik.Nucleus.Data namespace.

First, be sure to register any assemblies which have data object registry types defined within them:

public class Game : UltravioletApplication
{
    protected override void OnInitialized()
    {
        InitializeDataObjectRegistries();
    }

    private void InitializeDataObjectRegistries()
    {
        DataObjectRegistries.Clear();
        DataObjectRegistries.Register(GetType().Assembly);
    }
}

Then you can load the registries during the content loading process. Each object registry needs to be told which files to load using the SetSourceFiles() method; here, we're combining this with the GetAssetFilePathsInDirectory() method to load all XML files in the Content/Data directory.

Calling the static Load() method on DataObjectRegistries will load all registered data object registries.

public class Game : UltravioletApplication
{
    protected override void OnLoadingContent()
    {
        var content = ContentManager.Create("Content");

        LoadDataObjectRegistries(content);
    }

    private void LoadDataObjectRegistries(ContentManager content)
    {
        var myDataFiles = content.GetAssetFilePathsInDirectory("Data", "*.xml");
        DataObjectRegistries.Get<MyData>().SetSourceFiles(myDataFiles);
        DataObjectRegistries.Load();
    }
}

Once the registries have been loaded, you can retrieve a specific registry instance by calling the static Get<T>() method on DataObjectRegistries.

var registry = DataObjectRegistries.Get<MyData>();
var instance = registry.GetObjectByKey("MY_DATA");

Definition Files

The XML format used by the data object deserializer is fairly straightforward. Below the root element there can be multiple object definitions; each of these definition elements should have the name specified by the DataElementName property on the relevant object registry.

Each object definition element must have an ID attribute and a Key attribute. These contain the object's globally-unique identifier and human-readable key, respectively. Elements below this element are mapped directly to properties on the object; the value of the <Foo> element in XML is applied to the Foo property on the object, and so on.

<?xml version="1.0" encoding="utf-8" ?>
<MyDatas>
  <MyData ID="0d335b7d-b5dd-4f36-b55f-be08aee43cb6" Key="MY_DATA">
    <Test>This is a test!</Test>
  </MyData>
</MyDatas>
Clone this wiki locally