Models

Introduction

This tutorial teaches how to use data models in IoC+. Models are classes that allow us to store data that can be injected by commands and mediators across contexts. This could be the player’s position, health, or even the level layout.

In this tutorial we continue with the result of the previous tutorial. If this is the first time using IoC+, we suggest to read the introduction first.

The Model

In the previous tutorial we loaded content from either a web source or a constant string based on the platform using services. In the LoadContentCommand we logged the content to Unity’s debugger directly. But let’s say we want to store the data for later use, how would we go about this? The answer is using models. Data models to be exact.

Data models are classes that contain no real functionality, but are there to store a set of data. They can be bound to be injected just like any other class. Let’s create a model to store the data that we load from our source. Create a new script called “ContentModel.cs” and add the following code.

public class ContentModel {

    public string Content;

}

It doesn’t get any simpler than that! Just a simple class to store our content. Let’s open up both the ProductionContext as the DevelopmentContext to inject the model.

using IoCPlus;

public class ProductionContext : Context {

    protected override void SetBindings() {
        base.SetBindings();

        Bind<IContentService>(new OnlineContentService("http://google.com"));
        Bind<ContentModel>();

        On<LoadContentSignal>().Do<LoadContentCommand>();
    }

}
using IoCPlus;

public class DevelopmentContext : Context {

    protected override void SetBindings() {
        base.SetBindings();

        Bind<IContentService, LocalContentService>();
        Bind<ContentModel>();

        On<LoadContentSignal>().Do<LoadContentCommand>();
    }

}

Binding a model injection works exactly the same as binding signal and service injections.

Storing data

Let’s alter our LoadContentCommand to store the loaded content in our new model. Open up the LoadContentCommand and add the following code.

using IoCPlus;

public class LoadContentCommand : Command {

    [Inject] IContentService contentService;
    [Inject] ContentModel contentModel;

    protected override void ExecuteOverTime() {
        contentService.LoadContent(OnContentLoaded);
    }

    private void OnContentLoaded(string content) {
        contentModel.Content = content;
        Release();
    }
}

The LoadContentCommand now injects the ContentModel and stores its loaded content into the model. Notice how we can leave our services untouched? This is because we decoupled our logic into the service, command and model.. beautiful!

The Model in Mediator

Let’s setup a view/mediator to trigger when to load the content and to use the model to log the content in Unity’s debugger. Create a new script called “ContentView.cs” and add the following code.

using IoCPlus;
using UnityEngine;

public class ContentView : View {

    public readonly Signal LoadContentSignal = new Signal();

    public void ShowContent(string content) {
        Debug.Log(content);
    }

    private void Update() {
        if (Input.GetMouseButtonDown(0)) {
            LoadContentSignal.Dispatch();
        }
    }

}

The ContentView will dispatch the LoadContentSignal when the user presses the left mouse button. It is also able to log the content to Unity’s debugger. In a real game we could, instead of logging to the debugger, use the ShowContent() method to update a piece of UI, level or other visual.

The ContentView will need a mediator, but before we do so, let’s add a signal that we can dispatch when our data is loaded. Create a new script called “ContentLoadedSignal.cs” and add the following code.

using IoCPlus;

public class ContentLoadedSignal : Signal { }

Now that we have that signal, let’s create our mediator. Create a new script called “ContentMediator.cs” and add the following code.

using IoCPlus;

public class ContentMediator : Mediator<ContentView> {

    [Inject] LoadContentSignal loadContentSignal;
    [Inject] ContentLoadedSignal contentLoadedSignal;
    [Inject] ContentModel contentModel;

    public override void Initialize() {
        contentLoadedSignal.AddListener(OnContentLoadedSignal);

        view.LoadContentSignal.AddListener(OnViewLoadContentSignal);
    }

    public override void Dispose() {
        contentLoadedSignal.RemoveListener(OnContentLoadedSignal);

        view.LoadContentSignal.RemoveListener(OnViewLoadContentSignal);
    }

    private void OnContentLoadedSignal() {
        view.ShowContent(contentModel.Content);
    }

    private void OnViewLoadContentSignal() {
        loadContentSignal.Dispatch();
    }

}

The ContentMediator injects the load and loaded signals, and also injects the model. It listens to the ContentLoadedSignal, which we still need to bind in the context, and makes its view display the content stored in the model. It also listens to its view to dispatch the LoadContentSignal accordingly.

We could of course give a reference to the model in the view, but this is generally not a good practice. We want our view to be as clean of links to models, services and context-level signal as possible. This way we keep the complexity of our code as limited and isolated as possible. It’s easy to know where we need to change our logic when there is a change in design and the amount of code that needs changing is reduced to a minimum.

Let’s update the context to bind the LoadContentSignal injection, bind the mediator to the view, instantiate the view on enter and dispatch the ContentLoadedSignal after the content is loaded. Open up both the ProductionContext as the DevelopmentContext and add the following code.

using IoCPlus;

public class ProductionContext : Context {

    protected override void SetBindings() {
        base.SetBindings();

        Bind<IContentService>(new OnlineContentService("http://google.com"));
        Bind<ContentModel>();
        Bind<LoadContentSignal>();
        Bind<ContentLoadedSignal>();

        BindMediator<ContentMediator, ContentView>();

        On<EnterContextSignal>().InstantiateView<ContentView>();

        On<LoadContentSignal>().Do<LoadContentCommand>()
                               .Dispatch<ContentLoadedSignal>();
    }

}
using IoCPlus;

public class DevelopmentContext : Context {

    protected override void SetBindings() {
        base.SetBindings();

        Bind<IContentService, LocalContentService>();
        Bind<ContentModel>();
        Bind<LoadContentSignal>();
        Bind<ContentLoadedSignal>();

        BindMediator<ContentMediator, ContentView>();

        On<EnterContextSignal>().InstantiateView<ContentView>();

        On<LoadContentSignal>().Do<LoadContentCommand>()
                               .Dispatch<ContentLoadedSignal>();
    }

}

There! See how we dispatch the ContentLoadedSignal after we perform the LoadContentCommand? Commands in IoC+ are executed after the previous has been released, in the order that they are bound to the signal. Use the ExecuteParallel() method after the On<T>()() method to execute all commands in parallel.

The IoC+ Monitor

Go ahead and open up the IoC+ Monitor to test our results!

We can see that our ContentModel is injected and when the left mouse button is clicked the LoadContentSignal is dispatched to execute the LoadContentCommand and dispatch the ContentLoadedSignal, resulting in the view logging the loaded data to the debugger!

Conclusion

In this tutorial we’ve seen how we can store data in data models to use the data after a command is done executing. Models are injected just like signals and services. We’ve learned that models can be used form many things, like the player’s position or a level layout. Last but not least, we’ve learned how the separation of logic in the context, service, command, model, mediator and view help us to isolate and reduce complexity.