Search Unity

Feedback Rant: UIToolkit and USS are great, but the Binding system is a confusing, unintuitive black box

Discussion in 'UI Toolkit' started by tonycoculuzzi, Jan 29, 2021.

  1. tonycoculuzzi

    tonycoculuzzi

    Joined:
    Jun 2, 2011
    Posts:
    301
    I love the UI Toolkit and USS systems, they're really easy to work with and much easier to develop UI with. I see why Unity chose to go this direction, and I'm almost completely on board at this point after using it for roughly a year.

    That is, I'm on board except for the Binding system. I'm sorry if this post is stepping on any toes, but the Binding system is a black box of magical functionality, and it only confuses things. We have IBindable, IBinding, BindableElement, and none of these seem to even work together? None of them seem to offer straightforward binding handles? The documentation also seems very lacking.

    Just today I set the bindingPath of a sub-field of a sub-field of an custom Editor, nothing else. That field ended up being set and bound without any other setup, but also with no way to see when it was being updated by it's binding?

    This whole system is confusing, and I've gone ahead and just set up my own (albeit definitely worse) binding system which I just know is going to break with all the Bind/Unbind/BindProperty extension methods and confusion they cause.

    In my opinion, the binding system ruins an otherwise great set of systems. I wish it made more sense, but like a lot of Unity systems, it has devolved into being a black box of otherwise unknown and under-the-hood functionality.

    Sorry if this post seems grumpy, that isn't my intention! I've just been working with UI Toolkit for the past year and the binding system still frustrates the heck out of me, while everything else about UIT is a dream to work with.
     
    RKar likes this.
  2. broots

    broots

    Joined:
    Dec 20, 2019
    Posts:
    54
    I was actually thinking of writing something up on this topic as well, I've been working with UI toolkit for about a year as well and this is absolutely it's most problematic feature. I don't want to hate on the system though, because when it works it works extremely well. However, as I'll touch on here, it effectively is only useful for existing unity-built elements, and appears to be effectively too inflexible and clunky with too many encapsulated black box elements.

    Here's my case, I have completely written a graph view API for my own custom graph. It works great, fabulous, 100% UITK code and it's very fast, clean, and it feels good to write and use. The end result is much more professional and usable than I could of ever made using the old GUI system. I can agree 100% the system is good. Here's the problem, when I want to save the scriptable objects and widgets I create using UITK tools using the system binding I can't figure out how. Binding does not seem possible for runtime items (this is maybe a documentation issue) without resorting to reflection hacks that will break, even in editor the system is just confusing and feels exactly as you described -- I'm inserting my object into this black box and I hope it does what I want. If unity is going to encapsulate serialization to this extent (which makes sense) then their needs to be a guarentee that the system is usable.

    I'd like to touch on some technical aspects and my experience because I think anecdotes are not going to be as helpful.

    Here's an example of binding where it looks amazing, super usable and extremely intuitive:
    Code (CSharp):
    1. //...
    2. listViewer.bindItem = BindListItem<ScriptableObject>;
    3.  
    4. //..
    5.         private void BindListItem<T>(VisualElement e, int i) where T : Object
    6.         {
    7.             var targetObj = listViewer.itemsSource[i] as T;
    8.             var so = new SerializedObject(targetObj);
    9.             itemLabel.text = targetObj.name;
    10.             itemLabel.Bind(so);
    11. //...
    Fantastic, the encapsulation around the black box doesn't matter when when it does what you need.

    Okay, but how can I replicate list view's item runtime binding? Well, It doesn't seem possible without reflection. The list view binding call runs internal code I can't access, in addition serialized properties aren't exposed to me without reflection so even if the code wasn't internal I still couldn't replicate what it's doing. This is a big problem, the system fundamentally isin't accessible and this is a giant usability issue. The only avenue I can take is to write my own binding (which is the route I've taken, and it seems many others too) or use reflection to hack together a solution that will probably break. Not having access to runtime binding means that I have to resort to the old system of SerializedObject. If I'm working with generics, this becomes incredibly complex and extremely ugly. The list view binding is clever but it's several thousand lines of code, and some of the code is custom list-view only binding which should signal red flags that the binding system is flawed if the base binding code was insufficient to get the job done. Even if the systems were accessible, replicating the binding process list view is going through would be quite a headache.

    The root problem is that SerializedObject and SerializedProperty themselves are extremely unintuitive black boxes in the first place. Binding takes care of understanding this system for me, that's fantastic in places where it's doing the job as I don't want to deal with these constructs, but it's hard if not impossible to utilize in places where the voodoo isin't done for me.

    From my perspective, an ideal scenario would look like this:

    SerializedObject.Stream() where the data and all changes are written immediately (Undo is always a problem here I'm sure, but having the ability to have serialized objects actually written would be a massive improvement even without undo capabilities) This would cut out alot of ugly serialized object calls (And in places of more complexity, cut out reflection and hacks to work around SO). UI elements could then just easily call SerializedObject.Stream(). Undo history could be built using something like Binding.RecordState(). This is the dream scenario for my use-cases, but of course it's not so simple. However, I think functionality resembling this is absolutely neccesary for the binding system to really be usable.

    SerializedProperties being only accessible via string lookup is simply a poor solution, and in some UITK cases you cannot get access to the SP without reflection hacks. This one really must be addressed, not being able to serialize items because you cannot access them makes the system fundamentally unusable in some cases.

    To put things in perspective, in a year of working with UITK my principle gripes only stem from difficulties serializing (binding system, black box) and I have effectively the same complaints with the event system (rigid, the level of encapsulating makes some trivial things like manipulating click events actually impossible.) Ultimately these systems do not favour end users and make some problems incredibly difficult or impossible to solve using UITK. All of the other features of UITK have been fabulous, I'm already using it over the old GUI system as it's a massively improvement already. Having a more usable/intuitive binding systems and events would make this system a powerhouse for game design.

    Cheers,
    Z
     
    Last edited: Jan 30, 2021
  3. uMathieu

    uMathieu

    Unity Technologies

    Joined:
    Jun 6, 2017
    Posts:
    398
    The bindings interface was made and released at the same time UIElements went out of experimental in 2019.1. It was designed to be a stop gap solution to enable custom inspector creation in UI Toolkit. As such, it's really more value-binding than a real data binding solution.

    One of its goals was to have the smallest api surface as possible in order to allow for a complete refactoring later. Since then, some improvements were made for performance, ListView support, a better parity with IMGUI, but not much to address most the the issues you';ve encountered.
    Our focus being on providing a runtime solution for UITK and since SerializedObjects are not available at runtime, other priorities came in the way of improving this workflow.

    We plan on look back at the bindings feature during the 2022 release cycle.
     
    dsfgddsfgdsgd and tonycoculuzzi like this.
  4. manuelgoellnitz

    manuelgoellnitz

    Joined:
    Feb 15, 2017
    Posts:
    397
    I don't use the binding feature at all.
    For each Screen in the UI System I have a monobehavior with a reference to the UIDocument where I select the ui elements I need via the Name of the ui element with screenRoot.Q<Type>(elementName).