In order to use our contexts solution in future, i need to explain how all elements will interact with each other. In this article we'll introduce some new terms and all of them will be extensively described. Let's create a terms list:

  • Context
  • DataSource
  • View
  • Binding

Context

As was described in this article, contexts is solution to provide nested objects architecture which provides common interface to work with. Context tree can be presented like this: ctx-2 As you can see it is similar to tree or when you insert one box into another. If reference such kind of architecture to boxes example we have very important rule noticed: "Bigger box contains smaller boxes". So most global logic must contain most of concrete logic. This approach provides control of your code structure over design and programming stage and helps you to avoid adverse practices. Also it is very similar to OOP practices. It's a composite pattern after all.

DataSource

When you design some logic, in most cases you'll work with data. As usual data is presented by some class instance which contains fields and methods. Fields represents state of some entity and methods are used to interact with that entity. In our case Context itself is an interaction layer of entity presentation and DataSource is data layer with optionally methods to control, but not interact with. For example we want to work with some User. It can be described with a class similar to this:

public class User
{
    public string FirstName;
    public string LastName;
    public int Age;
}

And now we want to create a way different logic to work with it based on it's age. For example to provide different types of good proposals. To make this we need to create two contexts for different behaviours:

public class AdolescentContext : Context<User>
{
    public AdolescentContext(User user):base(user)
    {
        AddChild(new WearProposal());
        AddChild(new ComicsProposal());
        // ... etc.
    }
}

public class AdultContext : Context<User>
{
    public AdultContext(User user):base(user)
    {
        AddChild(new AdultToyProposal());
        AddChild(new WeaponProposals());
        // ... etc.
    }
}

Where WearProposal, ComicsProposal, AdultToyProposal and WeaponProposals is a Context nested types itself and implements custom logic to get and order proposals, control it's layout in view and similar things. The only thing we need now to do, is to create new user context and serve logic there:

public class UserContext : Context<User>
{
    public UserContext(User user):base(user)
    {
        if(user.Age >= 21)
            AddChild(new AdultContext(user));
        else
            AddChild(new AdolescentContext(user));
    }
}

It simple to use and allow you to reuse blocks in different contexts. All we did can be explained in these picture: ctx---data-1

View

View is the thing that users see and can interact with. Also it contains any necessery logic such as animation control, text, images effects, models and so on. This part of architecture necessary only for client side applications and not strictly dependent on contexts (controllers). In other words - application can have no view at all, but if it has - we need to present it in a right way. In my case client side is Unity application and view is the all things user see on screen such as UI, characters, enviroment and so on. All these things will have no own logic at all and will be controlled over parent contexts. How to do that i will explain below. But returning to view itself. As i've noticed above, application can contain no view, but otherwise - how we want to organize it in our case? Yep, that's right, context can contain view. So for every entity presented by any context we can create a view. For nested contexts we also will have nested views. Main concept presented on this picture: ctx---view In our purposes we must implement several derived from Context classes to implement basic view logic. As example we will have SceneContext - to present context which have some gameObject view in scene, RectTransformContext for every UI element. Both of them must implement constructor which requires prefabs in some way, by string path in Resources folder or prefab link directly. For ui i prefer to divide all cointent to panels and controls, so we need to derive from RectTransformContext two additional classes: Panel and Control. To see how it will goes let's change last picture a little bit: panel-example-1 As you can see context creates (instantiates) view and has reference to it. The only thing it's need for is to destroy view when context will be destroyed. But we also want to present some labels, pictures, buttons, input fields and etc. in our view. So what the solution? Let's change our scheme again: panel-with-data Now you can see how data, controler(context) and view will interact with each other. This is a MVC part of architecture which allows you to work with data, logic and view separately. As i have noticed above view must not be directry dependent on context cause logic must not know about view anything except it were created or need to be destroyed. We must not allow direct links to view's components such as labels, images, layout and so on. But how we can pass values to that elements or get user interaction in controller if we have no direct links? All these relations marked with blue arrows and they can be implemented in a way different from direct access. It's Bindings time!

Bindings

Bindings is the way to connect two variables with each other. It designed using Mediator pattern. Let me explain it in picture: binding
Binding is an entity which references two object's fields or properties to each other and allow you to syncronize object's field or properties values. If there were no suitable field or property to bind, it will not be initialized and will not work without causing exceptions. Also this approach allow you change views without changing logic. As usage example i can provide a game with dynamic asset loading. For example we have some UI control and some label must not be visible in new version, we simply remove it from prefab, update bundles on our host and allow users to load it. When it will be instantiated there will be no binding and it will be not shown. No exceptions, no client update needed, entire happiness. Also bindings with all required data is stored on a view side so it will not affect any logic. So basic explanation of it's behaviour contain several steps:

  1. When context creates view it collects all it's bindings and pass DataSource to it calling Bind() method.
  2. Binding finds referenced DataSource field and stores it's MemberInfo (FieldInfo, ProperyInfo), also same thing goes with local binded member (as example Label's text propery).
  3. In some update event called by some side (Update, OnEnable or if target is Observable it's Changed event) binding gets 1st member value and passes it to 2nd member.
    This flow shown with yellow, red and blue arrows on a picture above. Also we can control which side will cause changes. There will be only three variaties:
  • ModelToView - for labels, icons and so on.
  • ViewToModel - for buttons and input fields.
  • TwoWay - for complex inputs which can be controlled via several controls, like slider with additional input field or plus and minus buttons.

Conclusion

As you can see all these solutions can be used for different types of applications and allow you to write all of them in similar way. Also it solves alot of problems and uses some of best programming practices to help you avoid mistakes. It provide weak referencing to view, logic reusage, abstraction and atomicity to help you control entire development flow.