Skip to content

Data Object Registries

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

In order to facilitate data-driven development, Ultraviolet implements data objects and data object registries as a means to easily load arbitrary data types from XML. Objects loaded in this fashion are uniquely identifiable across all instances of the Ultraviolet Framework.

Defining Data Object Types

Data objects must derive from the UltravioletDataObject class found in the Ultraviolet.Content namespace. They must also expose a public constructor which takes two arguments: a string called key and a globally-unique identifier called 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.

Ultraviolet' 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. Ultraviolet's custom 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 Ultraviolet.Content namespace.

public class MyDataObjectRegistry : UltravioletDataObjectRegistry<MyDataObject>
{
    
}

Reference Resolution

A registry's ReferenceResolutionName property uniquely identifies it within the reference resolution system. This is how other data objects can create references to data objects of this type. By default, the value of this property is the name of the data type contained by the registry, but you can override this property and specify your own value if necessary.

So what do we mean when we talk about reference resolution? 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.

Data Element Names

A registry's DataElementName property identifies the name of the elements within the XML definition file which contain data objects that this registry can load. Like the ReferenceResolutionName property, this property defaults to the name of the data type contained by the registry, but it can be overridden.

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 Ultraviolet.Core.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