Simplifying UI data exchange: method presented, feedback requested

ISSUE:

I’m not pleased with the way data is populated into and read from UI controls, or at least my understanding of it. Here is how I understand data-exchange between PROCESSES and the UI works.

To populate UI controls with process output data, it appears one must either

  1. Lookup the value during the UI element’s Update() function, by manually creating a script for this individual control. In here one must find the value (in your “processes”) and use it to change the displayed value.

  2. Whenever your value changes in your process (or at the end of your process), find ALL the UI elements that show it, and change the value they display.

To use data from the UI as input for a process, it appears one must either

  1. Find that control when you need the value, and extract it’s display value.

  2. Update everything affected by specifying a function(s) in the control’s OnValueChanged component.

Not only does this prove cumbersome if a variable is displayed in more than one control or used in various processes, but I found myself writing nearly identical code, quite a few times.

QUESTION(s):

I tried the following (see “attempt” below), which works ok, but still, I’d like to know if there is a better way to do this. Are there any major problems with doing it this way? My primary goal is to reduce and reuse code and objects as much as possible, and to decrease development time, but will this reduce performance significantly? Is there anything similar already built into Unity, that I should try instead? How many programming rules am I breaking (no type-casting, using “global variables “, etc…)? Or is this, against all odds, a good idea?

ATTEMPT:

First I created this static class to hold ALL the data I wanted to exchange between ui elements and other processes. With this class we can Get or Set any of these values, anywhere in the program, simply by calling ValueDisplayExchange.setValue and ValueDisplayExchange.getValue. Each value stored/retrieved is referenced by a unique (case sensitive) string.

using UnityEngine;
using System.Collections;
using System.Collections.Generic;
public static class ValueDisplayExchange
{
    private static Dictionary <string,string> values;
    static ValueDisplayExchange()
    {
        values=new Dictionary<string, string>();
        values["Default"] = "No Value";
    }
    public static string getValue(string value_name)
        //looks up the value, based on the valueName string
    {
        string value="";
        values.TryGetValue(value_name,out value);
        return value;
    }
    public static void setValue(string value_name,string value)
        //adjusts or creates the value, under the name value_name
    {
        values[value_name] = value;
    }

}

Then I created a couple of Premade UI Elements, adding a script component to each, and converted them to PreFabs.

The Textbox Prefab’s code looks like this:

using UnityEngine;
using UnityEngine.UI;
using System.Collections;

public class DataExchangeTextScript : MonoBehaviour {
    public string preValueText;
    public string dataName;

    private Text textbox;
    // Use this for initialization
    void Start () {
        textbox = GetComponent<Text> ();
    }
   
    // Update is called once per frame
    void Update () {
        textbox.text = preValueText + ValueDisplayExchange.getValue (dataName);
        Debug.Log ("updated de text");
    }
}

The important thing to note is that the preValueText and valueName are global variables, which means that they can be set in the editor. This allows us to change the ValueName (which specifies which value we want to exchange) for each instance of the prefab, WITHOUT LEAVING THE EDITOR.

1902096--122664--upload_2014-12-29_21-54-3.png

The code for a DataExchangeSlider PreFab, which has input capabilities, is a little more complex. Also, since the ValueDisplayExchange class works only on strings, and a slider works only on floats, conversion is also need.

using UnityEngine;
using UnityEngine.UI;
using System.Collections;

public class DataExchangeSlider : MonoBehaviour {
    public string preValueText;
    public string valueName;
    private Slider slide;
    // Use this for initialization
    void Start () {
        slide = GetComponent<Slider> ();
    }
   
    // Update is called once per frame
    void Update () {
        float value;
        if (!float.TryParse (ValueDisplayExchange.getValue (valueName), out value))
                        value = 0;
        slide.value = value;
        Debug.Log ("updated de slide");
    }

    public void SetValue(float fvalue)
    {
        ValueDisplayExchange.setValue (valueName, fvalue.ToString ());
    }
}

The OnValueChanged component of the Prefab is setup to reference the SetValueFunction defined above.

1902096--122662--upload_2014-12-29_21-52-27.png

Usage Examples:

  1. Create an Instance of a DataExchangeSliderPreFab, and a DataExchangeTextPreFab. Give them the same Value Name in the editor. Now play, and you will see the text box show whatever value you move the slider to.

  2. The “players score” can be updated from several locations (within OnCollision functions, on a GameObject’s update function, when a cheat-code is entered, etc…) and is shown in multiple places (the game screen, the “player details” screen, etc…). In each place the score is updated, the same, single line of code, can be called to update the “players score”. All the various UI elements that display the score, will simply set their Data Name value to “players score”.

I think what you are looking for is a good way to do data binding. The UI system doesn’t really handle this currently. As far as

… programming rules only matter so much as you/your team agree to the way you will write code and remain consistent. As a rule of thumb doing stuff in update is going to have a cost, and that cost depends on the complexity of the operation. If this particular way of binding is useful to you and doesn’t slow things down, then go with it.

As for the UI components that use floats, you could just make a Dictionary<string, float> in your static class and create the necessary get & set methods. Of course DataExchangeSlider and other classes will need to use the correct methods on ValueDisplayExchange.

I like the idea of storing floats as ACTUAL floats, rather than as strings- faster and more accurate. The issue I’m running into with this is: while a slider uses floats, if I want to use a value taken from (bound to) a slider, but display it in a textbox: the textbox will need to check BOTH the string dictionary AND then the float dictionary, in order to find the value. Hmm, perhaps a third dictionary? Dictionary<string, someEnumThatSpecifiesTypeOfData>, which basically tells me which dictionary to look in. I feel like there is a better way, but I’m not sure what it is. What do you think?

If this particular way of binding is useful to you and doesn’t slow things down, then go with it.

My fear is that as the program progresses, gets more and more complex, this performance hit from this method will get too expensive. On my tiny test program its certainly fast enough, but will it scale poorly?

Keeping with what you have, I would create a flag in the textbox script that says if the value needs to be converted from a number.

public bool usesFloatConversion = false;
void Update () {
  if(usesFloatConversion) {
  textbox.text = preValueText + ValueDisplayExchange.getValue (dataName).ToString();
  }
  else {
  textbox.text = preValueText + ValueDisplayExchange.getValue (dataName);
  }
}

Performance wise it will add a branch penalty, so you could just have two of these scripts for text items. One that expects the value to be string and one that doesn’t.

I’m guessing the Unity folks would probably suggest you use the profiler to pin point performance issues. You could work around that if you are familiar with the IL enough to spot allocations and such. I use a messenger system from the wiki to handle this kind of stuff. Check out Advanced C# Messenger. Basically I have binding scripts attached to UI Elements that need them. When a value changes a message is sent and only subscribers get the message then interpret it as they need to. The flip side is that when something like a slider changes, it sends out the message also. A typical message looks like this:

Messenger.Broadcast<MonoBehaviour,float>("message", this, newValue);

The text component has a flag as I mentioned above that says if the message is a float and needs conversion.

Hey there,

You could always hook up your components with delegates. That way when they change you get a call back. It has the advantage of not having to be checked every frame.

//Super quick code example.

private class DataExchange<T>
{
    private Action<T> onDataChanged;
    public event Action<T> OnDataChanged
    {
        add
        {
            onDataChanged += value;
        }

        remove
        {
            onDataChanged -= value;
        }
    }

    private T dataValue;
    public T DataValue
    {
        get
        {
            return dataValue;
        }

        set
        {
            if( !value.Equals(dataValue) )
            {
                dataValue = value;
                onDataChanged.Invoke(dataValue);
            }
        }
    }
}

As you can see the OnDataChanged event only gets called when there is new data (better performance). This also uses generics to make sure you get the value you want, no casting required.

From there you could set up some manager that holds all the DataExchange’s in a list. Anyone who wants to know when the data is update could just grab it from the manager and subscribe to the OnDataChanged delegate.

So, I went a little nuts after reading your previous post. I came up with the attached results, which does indeed create separate dictionaries for string, float and int variables. I like this because of added precision; now it can be used for non-GUI data-exchange too… An Enumeration and public variable are used to select the InputField’s datatype, from with the editor, to make sure it stores the value in the correct table. Package attached, includes code & samples.

Wow, just saw your post BMayne. Going to need to figure it out, but looks really powerful!

1906038–122939–SimpleDataExchange.unitypackage (35.7 KB)

1 Like

Say BMayne, I’m working on implementing your suggestion now. But I realized there might be a way to do it without the caller needing to define a specific function that is passed to the onDataChanged Action. Ideally, they would pass instead a REFERENCE to the variable they want updated on every SetValue call.
In C, and C++, I would be able to keep a list of POINTERS for each base type. When the data is changed with SetValue(), we can update the values all those pointers are pointing at. Is it even possible to do this with C#? If so I’m not sure how to get around the compiler error generated on this line:
Dictionary <string,ref string> stringReferenceDictionaryByName; error: Type expected (at “ref”) - I realize dictionary is not the right container for this (we want non-unique key-values), but the idea would be the same.

I have implemented OnChangeValue subscriptions, in the same DataExchange class, giving the user the option of using the subscription functions, or not. Thanks for pointing me in the right direction with that one BMayne!

I’m wondering if this is worth putting out there, for free, on the asset store. I looked on there before writing this myself, but didn’t find anything (though I AM awful at searches). Regardless, I’ve attached it here for forum members, at least. Any feedback is appreciated.

1909047–123152–SimpleDataExchangeV2.unitypackage (39.8 KB)

You are very welcome. To my knowledge you can’t create pointer like functionality in c#. Ref, out, and unsafe(which you can’t use in Unity) are the only things similar. You could always use reflection to grab the variables but that defeats performance.

Would you be able to do pointer like functionality by having a Class i.e a ‘ReferenceString’ with a ‘public string Value’ property that you could get/set. so maybe something like

Dictionary <string,ReferenceString> WrappedStringReferenceDictionaryByName;

because I would think

then to get/set you would be making a call to WrappedStringReferenceDictionaryByName[“PlayerHealth”].Value = 15;

Might be a bit of a hack?

I’m sure I understand what you are saying. The goal is to allow any-process to bind a particular variable in it’s scope, to a “global” named-variable that all process (including the UI) can change, without the process in question needing to “get” the new value. Even if I used a ReferenceString class in a particular process, how does your example update this variable whenever the named value is changed?

In the code I submitted above, the process would need to create a function, like (void SetVar(string data_param){localstring=param;}), and pass this function to the DataExchange class (here is where the EVENT stuff comes in) in order to subscribe to any changes in value. this function is called whenever any-process calls SetValue(string valuename,string data).
I was hoping to get around the need for the user to create this function, and instead just pass the local variable (“localstring” in the example above), as a reference. With this method the SetValue calls would go through ALL references with that name, and update them. Perhaps you meant something like this? (If so, a dictionary would not be the right container, we would need non-unique keys, but I think I understand your idea now.)

I guess the next question is, would I rather force users to create functions, or use a special class like your ReferenceString to hold otherwise standard data. Which would you prefer? I’d guess using that reference class is less coding for the user, but also less flexibility (we can choose to do some kind of computation/conversion/checks in those subscription functions).

You are really on the outer fringe/outside of my knowledge/ability with c# with actually understanding how to solve this problem, My actual needs for Unity’s UI is very low as it’s just a toy project I’m having fun with, I just found it incredibly incredibly incredibly painful that there is no support for binding ui elements. I just need to display a value, update it if it changes with as little code as possible. So far I can update any value with 1 line of code with very simple assignment which leaves me fairly satisfied for what I need. Although I’ll need similar duplicate like behaviors for any other controls I need, where the only one I can foresee myself needing is a slider (healthbars and such). I’m sure it won’t pose too much of a problem…hopefully?

I do not know enough right now to answer your question there on what is better. I’m not currently using the reference class as I am currently using a globally accessible Dictionary that looks like this:

public Dictionary<string, UnityEngine.UI.Text> UITextBindings = new Dictionary<string, UnityEngine.UI.Text>();

and any time in my code where I am updating a property I just make a call to UIManager.Instance.UITextBindings["BindingNameOnView].text = “footext”;

and if it’s not a string it’s usually a primitive float/int I just add a .ToString() to the end of it.

The only last thought I really have for a ‘final solution’ is that I would need to go into the documentation for wpf/mvvm and finding out how it implements NotifyPropertyChanged for all properties, how it finds out “where” it is displayed currently in the view and updating the values as in my mind there still “Magic” involved somewhere. But I am not sure how many hundred lines of code that uses or what I need to start with. I’m very reluctant to get deep in an unsatisfactory solution when I’m just playing with Unity.

Sorry I couldnt be of any further help, I will continue thinking about this to see if I can get anywhere

If I make any progress I’ll make a post but right now I’m just playing around and having fun with unity and I dont know where that will take me.

The code example that has been supplied here is how wpf and windows forums does it. It just that most of the code uses T4s (basically a code generator) to generate the subscriptions to the functions behind the scenes. If you dig into the source code for your projects you are able to see this

As you can see in the example attached I made a button and I show the Form code (The one you edit) and the Designer code (the auto generated one). They use EventHandlers which are a type of delegate.

1910294--123225--WinF.png

EDIT: I dont know how to format code or what I need to press?

I’ll admit this is really difficult, I’m working with code that I half understand a quarter of. I have got ‘something’ working where I can fire an event PropertyChanged where I have a class (UIManager) that subscribes to it and prints a message to console with the Sender’s PropertyName. It’s still a real headscratcher for me but im stuck with having to manually tell my UIManager to subscribe to each and every property. is there some way to facilitate automation of this?

Here is my code

using UnityEngine;
using System.Collections;
using System;

public class UIManager
{
    private static UIManager _instance;
    public static UIManager Instance
    {
        get
        {
            if (_instance == null)
            {
                _instance = new UIManager();
            }
            return _instance;
        }
        set
        {
            _instance = value;
        }
    }

    public UIManager()
    {
        SubscribeToEvents();
    }


    void SubscribeToEvents ()
    {
        GameObject.Find ("StartGame").GetComponent<StartGame>().PropertyChanged += HandlePropertyChanged;
        //GameObject.Find ("StartGame").GetComponent<StartGame>().CurrentPlayer.PropertyChanged += HandlePropertyChanged;
    }

    void HandlePropertyChanged(object sender, UnityPropertyChangedEventArgs e)
    {
        Debug.Log ("handled property change like a boss: " + e.PropertyName);
    }
}

///////Different Class StartGame.cs

using UnityEngine;
using System.Collections;
using System;

public class StartGame : MonoBehaviour
{
    public event EventHandler<UnityPropertyChangedEventArgs> PropertyChanged;

    private Player _currentPlayer;
    public Player CurrentPlayer
    {
        get
        {
            return _currentPlayer;
        }
        set
        {
            _currentPlayer = value;
            NotifyPropertyChanged("CurrentPlayer");
        }
    }

    void NotifyPropertyChanged (string propertyChanged)
    {
        if (PropertyChanged != null)
        {
            PropertyChanged(this, new UnityPropertyChangedEventArgs(propertyChanged));
        }
    }

    // Use this for initialization
    void Start ()
    {
        UIManager.Instance = new UIManager();
        CurrentPlayer = new Player();
        Debug.Log ("Done");
    }

    // Update is called once per frame
    void Update ()
    {

    }
}

Based on your idea, Oliver, of putting the value in a class, so it can be referenced- I came up with the following GlobalVariable class. Once your “GlobalVariable” is created, updating any instance of it, will update all the others with the same “name”.
To insert code into a post, first copy it, then click Insert->code: a windows should popup (sometimes this fails and I need to reload the page). Paste your code into this popup window.
FYI: the DataexchangeV2 I posted above has source code, so you can see how that does subscriptions too.

using UnityEngine;
using System;
using System.Collections;
using System.Collections.Generic;
//Author:  Glurth


/*
* The GlobalVariable<T> class is used as follows (sample is for T= float, but works for any type)
*  note: all instances of the globalVariable<T> with the same ValueName, will be updated automatically during value assignmen
* ...
* //in some processes' class
* ...
* init:
* GlobalVariable<float> playerscore= new GlobalVariable<float>("playerScore",0.0f);
* ...
* ...
* //get:
* local_float= float_value.VariableValue;
* //set:
* float_value.VariableValue = 23.4f
* ...
* ...
* //in UI class
* ...
* Start(){
* GlobalVariable<float> uiplayerscore= new GlobalVariable<float>("playerScore");}
* ...
* ...
* Update(){
* text = uiplayerscore.VariableValue.toString();}
*
* //or for assignment
* OnChange(){
* uiplayerscore.VariableValue=slider.value;}
*
*
*/
public class GlobalVariable<T>
{
    private T variableValue;
    public T VariableValue    {
        get    {
            return variableValue;
        }
        set    {
            variableValue=value;
            UpdateOtherGlobals();
        }
    }
    public T VariableValueNoUpdates    {
        get    {
            return variableValue;
        }
        set    {
            variableValue=value;
            //UpdateOtherGlobals();
        }
    }
    public string value_name;

    private static Dictionary<string,List<GlobalVariable<T>>> refenceValueLists=null;

    private void init(string _value_name)
    {
        value_name = _value_name;
        if (refenceValueLists == null)
            refenceValueLists = new Dictionary<string, List<GlobalVariable<T>>> ();//first global ever
        if (!refenceValueLists.ContainsKey (_value_name))
            refenceValueLists.Add (_value_name, new List<GlobalVariable<T>> ()); //first global with this name
        else
        {
            variableValue=(refenceValueLists[_value_name])[0].VariableValue;//grab the current value from a random global variable (randomly chose [0]) that already exists with this name
        }
        List<GlobalVariable<T>> refList = refenceValueLists[_value_name];
        refList.Add(this);
    }
    public GlobalVariable(string _value_name)
    {
        init (_value_name);

    }
    public GlobalVariable(string _value_name, T _value)
    {
        init(_value_name);
        VariableValue = _value;
    }
    public void UpdateOtherGlobals()
    {
        //assumes all lists and references exist already
        List<GlobalVariable<T>> refList=refenceValueLists[value_name];
        Debug.Log ("Updating " + refList.Count + " items in global var list: " + value_name);
        foreach (GlobalVariable<T> variable in refList)
        {
            variable.VariableValueNoUpdates=variableValue;
        }
    }
}

Update: Why do I make it so complex? There is no need for each instance of the global variable to keep it’s own version of the data- we are using a reference after all! note to self: KISS (keep it simple, stupid Glurth) Here is the simple version, usage is identical to complex version above.

Note: After creation, there are no look-ups performed, and there are no iterations to update all the references. They are not needed.

class KISSGlobalVariable<T>
{
    private class ReferenceableValue<P>
    {
        public P variableValue;
    }
    private ReferenceableValue<T> valueRef;
    private static Dictionary<string,ReferenceableValue<T>> refenceValueList=null;

    private void Init(string _value_name)  
    {
        if (refenceValueList == null)
            refenceValueList = new Dictionary<string, ReferenceableValue<T>> ();//first global ever
        if (!refenceValueList.ContainsKey (_value_name))
            refenceValueList.Add (_value_name, new ReferenceableValue<T>()); //first global with this name
        valueRef = refenceValueList[_value_name];
    }
    public KISSGlobalVariable(string _value_name){Init(_value_name);}

    public KISSGlobalVariable(string _value_name,T initial_value)
    {
        Init(_value_name);
        if (initial_value != null)
                VariableValue = initial_value;
    }
    public T VariableValue
    {
        get    {return valueRef.variableValue;    }
        set    {valueRef.variableValue=value;    }
    }

}

Hey, here is a follow up with an example of using Advanced C# Messenger to do very basic binding. I know there are other solutions, but this is just a really simple way to do things. The example code and scene should be self explanatory.

Basically you specify a message using a string in the inspector on any controls that will be bound. When the message is broadcast from anywhere else in the program, the binding components update their UI component’s respective values. Notice that since sliders are intractable, they can broadcast the same message that they receive. The MonoBehaviour that sends the message shouldn’t respond to messages it broadcasts, so the sending instance is sent in the signature of the message for checking.

Finally the text binding can listen to messages, but it expects a string. Since we have a float value coming through in the message, we need to tick the ‘Message Convert Float to Text’ check box and it will work correctly.

From this example, you should see how to expand this concept further by doing more component binding types and conversions etc. It avoids update and you don’t need to do much of anything to bind MonoBehaviours. Good luck

1913476–123438–UI Bind.unitypackage (15.5 KB)

I like it feaver! It looks like a far more generalized message system than simple data-exchange, though it would obviously work well for that too.

I think I’m going to end up going with the global-variable reference idea, at least for simple inter-scope data-exchange, as it does not require any look-ups, callbacks, or even loops to work (except during initialization). I’ll attach my test project of it.

I could see using the message system you posted for more complex operations, in particular when functions need to be called. A couple of questions about using it: I notice there are broadcast messages, I assume these don’t get “eaten-up” until all listeners have acted? Is there anyway to have a listener CHOOSE to “eat-up” the message, preventing any other listeners from acting?
.

1916221–123665–GlobalVariables.unitypackage (24.9 KB)

As the messenger class is now, the quickest approach would be to create a custom data class that stores the event value along with a bool for if it has been used. Listeners would need to check if the event was already used and ignore the message if it has been. Another option is to modify the Messenger class to only send certain marked messages to a single listener.

I don’t think I’ve ever used a messaging system that way though, so I’m curious as to why the message would need to be consumed that way?