Search Unity

Bug (Runtime)Custom element has no registered factory method

Discussion in 'UI Toolkit' started by nateblessing, May 22, 2020.

  1. nateblessing

    nateblessing

    Joined:
    Apr 17, 2020
    Posts:
    10
    What I am attempting to do is create a custom UIElements control for use during runtime via UIElements->Panel(PanelRenderer).

    In the Game window in editor, the custom control(CustomToolbar) renders without issue. However, once built to a player executable, I get the following error in the Player.log. "Element 'CustomToolbar' has no registered factory method." and in the game it shows "Unknown type:'CustomToolbar'".

    I have followed the documentation section "Defining new elements" at https://docs.unity3d.com/Manual/UIE-UXML.html

    I created a factory class nested within my CustomToolbar class.
    Code (CSharp):
    1. public new class UxmlFactory : UxmlFactory<CustomToolbar> { }
    I disabled managed code stripping by setting the managed stripping level to disabled, I also tried adding a preserve attribute to the factory class to ensure that it wasn't being optimized out on the built assemblies. However, none of these efforts have had an effect on the "Element 'CustomToolbar' has no registered factory method." error.

    I turned on full trace logging and was able to get the following error to the Player.log. Below is the full error.
    This is the line from my UXML that is attempting to load the custom control.
    Any help would be greatly appreciated.
    Has anyone been able to get custom UIElement controls to successfully load in a built player executable?

    Unity version: 2019.3.13f1
    Packages:
    com.unity.ui.builder: 0.11.2-preview
    com.unity.ui.runtime: 0.0.4-preview
     
    Last edited: May 22, 2020
  2. GuillaumeLeplang

    GuillaumeLeplang

    Joined:
    Sep 3, 2019
    Posts:
    8
  3. nateblessing

    nateblessing

    Joined:
    Apr 17, 2020
    Posts:
    10
    I added the preserve assembly attribute and it had no effect on the error, I have managed stripping level set to disabled, so I wasn't expecting to do anything. Below is the code with the added preserve attribute.
    Code (CSharp):
    1. [assembly: Preserve]
    2.  
    3.     public class CustomToolbar : VisualElement
    4.     {
    Thanks for the advice, but I think the issue is with the VisualElementFactoryRegistry within the UnityEngine.UIElementsModule.dll
     
  4. nateblessing

    nateblessing

    Joined:
    Apr 17, 2020
    Posts:
    10
    from what it looks like after investigating the following log line:
    I decompiled UnityEngine.UIElementsModule.dll to do some digging, the error is originating at the following line:
    Code (CSharp):
    1. bool flag = !VisualElementFactoryRegistry.TryGetValue(asset.fullTypeName, out list);
    After digging into VisualElementFactoryRegistry, it appears that the following are the only registered factory classes:
    Code (CSharp):
    1. private static void RegisterEngineFactories()
    2.         {
    3.             IUxmlFactory[] array = new IUxmlFactory[]
    4.             {
    5.                 new UxmlRootElementFactory(),
    6.                 new Button.UxmlFactory(),
    7.                 new VisualElement.UxmlFactory(),
    8.                 new IMGUIContainer.UxmlFactory(),
    9.                 new Image.UxmlFactory(),
    10.                 new Label.UxmlFactory(),
    11.                 new RepeatButton.UxmlFactory(),
    12.                 new ScrollView.UxmlFactory(),
    13.                 new Scroller.UxmlFactory(),
    14.                 new Slider.UxmlFactory(),
    15.                 new SliderInt.UxmlFactory(),
    16.                 new MinMaxSlider.UxmlFactory(),
    17.                 new Toggle.UxmlFactory(),
    18.                 new TextField.UxmlFactory(),
    19.                 new TemplateContainer.UxmlFactory(),
    20.                 new Box.UxmlFactory(),
    21.                 new PopupWindow.UxmlFactory(),
    22.                 new ListView.UxmlFactory(),
    23.                 new TreeView.UxmlFactory(),
    24.                 new Foldout.UxmlFactory(),
    25.                 new BindableElement.UxmlFactory()
    26.             };
    27.             IUxmlFactory[] array2 = array;
    28.             for (int i = 0; i < array2.Length; i++)
    29.             {
    30.                 IUxmlFactory factory = array2[i];
    31.                 VisualElementFactoryRegistry.RegisterFactory(factory);
    32.             }
    33.         }
    After analyzing the dll, VisualElementFactoryRegistry has an access level of internal, so all references to the class should only be found within the assembly. The only use of the RegisterFactory method is in RegisterEngineFactories(), not allowing any custom controls.


     
    Last edited: May 22, 2020
  5. uMathieu

    uMathieu

    Unity Technologies

    Joined:
    Jun 6, 2017
    Posts:
    398
    I remember seeing this issue a while back and it got fixed. We use reflection to auto-detect Factory types and instantiate them but obviously that failed in your case. That part of the code has changed a bit since 0.0.4 but I'll try to repro with your setup. In the meantime, you'll need to modify some code straight into our preview package to open up some internal apis, or call them via reflection:

    Call this with an instance of your type: UXMLRuntimeFactories.RegisterFactory(new CustomToolbar.UxmlFactory());

    This code is in UIElements Runtime\Runtime\UxmlRuntimeFactories.cs. If you want to modify it, copy the UIElements Runtime folder straight into your Packages folder and remove the line from your manifest.json
     
    nateblessing likes this.
  6. nateblessing

    nateblessing

    Joined:
    Apr 17, 2020
    Posts:
    10
    With your help pointing out that method, I had overlooked the reference to VisualElementFactoryRegistry within the UIElements.Runtime package, I have tracked down the issue. The problem appears to be with the order that methods execute within UIElements.Runtime package.

    Turns out that the factory methods were getting registered, but it was happening late.

    UXMLRuntimeFactories uses the RuntimeInitializeOnLoadMethod attribute.
    Code (CSharp):
    1. [RuntimeInitializeOnLoadMethod]
    2.         internal static void RegisterUserFactories()
    3.         {
    4.             HashSet<string> userAssemblies = new HashSet<string>(InternalBridge.GetAllUserAssemblies());
    5.             var assemblies = AppDomain.CurrentDomain.GetAssemblies();
    6.             foreach (Assembly assembly in assemblies)
    7.             {
    8.                 if (!(userAssemblies.Contains(assembly.GetName().Name + ".dll")))
    9.                     continue;
    10.                 var types = assembly.GetTypes();
    11.                 foreach (Type type in types)
    12.                 {
    13.                     if (typeof(IUxmlFactory).IsAssignableFrom(type) && !type.IsInterface && !type.IsAbstract)
    14.                     {
    15.                         var factory = (IUxmlFactory)Activator.CreateInstance(type);
    16.                         InternalBridge.RegisterFactory(factory);
    17.                     }
    18.                 }
    19.             }
    20.         }
    As referenced from the documentation https://docs.unity3d.com/ScriptReference/RuntimeInitializeOnLoadMethodAttribute.html
    The problem resides within the PanelRenderer initializing on Awake(), which happens before the factory classes are registered.
    This code is encapsulated within the com.unity.ui.runtime package. You mentioned above that the code had changed, is there a newer version of the package available for 2019.3.13f1 beyond 0.0.4-preview?
    I would prefer to keep my projects referencing the https://packages.unity.com registry to be able to get updates for new packages.

    As per
    The newest version available is 0.0.4-preview.
    Thanks for all you help UMathieu
     
  7. manuelgoellnitz

    manuelgoellnitz

    Joined:
    Feb 15, 2017
    Posts:
    397
    What did you do exactly to solve that problem?

    I run in the same issue. I created a custum UIElement, styled stuff, tried it play mode, works fine.
    Created a build and got the same errors.
    My custom UIElements are not inside of an assembly definition.
    When I put them into one -> same errors
    When I add [assemby: Preserve] -> same errors
    When I modified the package and added the RegisterFactory Line -> same errors

    I am using Unity 2020.1.0.b12
    and the latest runtime package.
     
  8. manuelgoellnitz

    manuelgoellnitz

    Joined:
    Feb 15, 2017
    Posts:
    397
    Lol I got a solution that does'nt need modifiing the package.
    Just add the code in a Awake() Method of a class that is constructed prior to the PanelRenderer()


    Code (CSharp):
    1. #if !UNITY_EDITOR
    2.         private void Awake()
    3.         {
    4.             //Register your UILements like this
    5.             InternalBridge.RegisterFactory(new ButtonTranslatable.UxmlFactory());
    6.         }
    7.         #endif
     
    pegorari likes this.
  9. pegorari

    pegorari

    Joined:
    Nov 19, 2009
    Posts:
    60
    Thanks, this solution worked! I also had to add the script to the Script Execution Order with a negative value.
     
  10. manuelgoellnitz

    manuelgoellnitz

    Joined:
    Feb 15, 2017
    Posts:
    397
    Unfortunatelly with the new version "1.0.0-preview.3" my fix does not work anymore:

    "InternalBrigde" is now "VisualElementFactoryRegistry", which is ok. But tthat class and its methods are not public anymore. So you noew have to modifiy the package in order for it to work :(
     
  11. pegorari

    pegorari

    Joined:
    Nov 19, 2009
    Posts:
    60
    It seems this bug is back again. If the team could share a working example of a custom element script that works for runtime would be very helpful.
     
  12. uDamian

    uDamian

    Unity Technologies

    Joined:
    Dec 11, 2017
    Posts:
    1,231
    Thanks for reporting. We can't repro this just yet but are aware of the problem now. We'll also look at opening up VisualElementFactoryRegistry's public API so the workaround...works around again.
     
    manuelgoellnitz likes this.
  13. nateblessing

    nateblessing

    Joined:
    Apr 17, 2020
    Posts:
    10
    For those interested in how I fixed this package for our internal distribution, I removed the RuntimeInitializeOnLoadMethod attribute from the RegisterUserFactories method and added a call to the RegisterUserFactories method in the awake of the PanelRenderer.

    Runtime/UXMLRuntimeFactories.cs
    Code (CSharp):
    1. #if !UNITY_EDITOR
    2.         //[RuntimeInitializeOnLoadMethod]
    3.         internal static void RegisterUserFactories()
    4.         {
    5.             HashSet<string> userAssemblies = new HashSet<string>(InternalBridge.GetAllUserAssemblies());
    6.             var assemblies = AppDomain.CurrentDomain.GetAssemblies();
    7.             foreach (Assembly assembly in assemblies)
    8.             {
    9.                 if (!(userAssemblies.Contains(assembly.GetName().Name + ".dll")))
    10.                     continue;
    11.                 var types = assembly.GetTypes();
    12.                 foreach (Type type in types)
    13.                 {
    14.                     if (typeof(IUxmlFactory).IsAssignableFrom(type) && !type.IsInterface && !type.IsAbstract)
    15.                     {
    16.                         var factory = (IUxmlFactory)Activator.CreateInstance(type);
    17.                         InternalBridge.RegisterFactory(factory);
    18.                     }
    19.                 }
    20.             }
    21.         }
    22.         #endif
    Runtime/PanelRenderer.cs
    Code (CSharp):
    1.         protected void Awake()
    2.         {
    3. #if UNITY_EDITOR
    4.             m_OldEnableLiveUpdate = m_EnableLiveUpdate;
    5. #endif
    6. #if !UNITY_EDITOR
    7.             UXMLRuntimeFactories.RegisterUserFactories();
    8. #endif
    9.             // We try to call Initialize as soon as possible.
    10.             Cleanup();
    11.             Initialize();
    12.         }
     
  14. pegorari

    pegorari

    Joined:
    Nov 19, 2009
    Posts:
    60
  15. manuelgoellnitz

    manuelgoellnitz

    Joined:
    Feb 15, 2017
    Posts:
    397
    Yes that problem is fixed now!