Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.
  2. We have updated the language to the Editor Terms based on feedback from our employees and community. Learn more.
    Dismiss Notice
  3. Join us on November 16th, 2023, between 1 pm and 9 pm CET for Ask the Experts Online on Discord and on Unity Discussions.
    Dismiss Notice

Question EditorGuiLayout.MaskField issue with large enum error

Discussion in 'Scripting' started by z_yq, Jun 7, 2022.

  1. z_yq

    z_yq

    Joined:
    May 3, 2017
    Posts:
    15
    In a more than 32 options, when I select only one there is a problem of checking other options, how to solve it please
     

    Attached Files:

  2. MelvMay

    MelvMay

    Unity Technologies

    Joined:
    May 24, 2013
    Posts:
    10,620
    An enum using [Flags] produces a bitmask (int) and only has 32-bits where each bit is an option. Therefore it only has 32-bits.

    It does depend on the underlying type used by the enum in script but MaskField uses an int.
     
    z_yq likes this.
  3. z_yq

    z_yq

    Joined:
    May 3, 2017
    Posts:
    15
    I'm sorry I don't fully understand, can you provide a more detailed explanation, such as an example
     
  4. Bunny83

    Bunny83

    Joined:
    Oct 18, 2010
    Posts:
    3,571
    The MaskField works with "bit masks". Essentially a single 32 bit integer value. As you probably know data in a computer is stored in binary 1 and 0. A normal integer value has 32 bits and is composed of 4 bytes (a byte has 8 bits).

    So the decimal value
    42
    in binary would be
    00000000 00000000 00000000 00101010

    Just like in the decimal system each position in a number has a different base value. In the decimal system you have at the far right the units, going left you have the 10s, then the 100s, 1000s and so on.
    In binary everything is based on the base "2". So we again have the units at the far right and to the left the 2s, then the 4s, 8s, 16s, 32s, 64s, .... Each bit can only have a value of either 0 or 1. So either you have the value of a certain place or not.

    A bit mask now uses the individual bits in an integer to represent 32 boolean values that can be on or off. That's why the MaskField is limited to 32 values as there are only 32 bits to work with. That's also why Unity only has 32 layers for example since working with layermasks is extremely quick and efficient.

    So if you have more than 32 items you want to select, this can not be represented by a 32 bit integer and you can not use the MaskField. You may construct your own GenericMenu and handle the "selection" yourself. It's not completely clear what your goal is, so it's kinda hard to give you more detailed directions.

    Just in case you're looking for a solution to display a selection menu to select certain assemblies, you can do something like this:

    Code (CSharp):
    1. namespace B83.AssemblyListField
    2. {
    3.     using System.Collections.Generic;
    4.     using UnityEngine;
    5.     using System.Reflection;
    6.  
    7.     [System.Serializable]
    8.     public class AssemblyItem
    9.     {
    10.         [System.NonSerialized]
    11.         private Assembly m_Assembly;
    12.         public string displayName;
    13.         public string fullName;
    14.         public Assembly assembly => GetAssembly();
    15.         public AssemblyItem(Assembly aAssembly)
    16.         {
    17.             m_Assembly = aAssembly;
    18.             fullName = aAssembly.FullName;
    19.             displayName = aAssembly.GetName().Name;
    20.         }
    21.  
    22.         public Assembly GetAssembly()
    23.         {
    24.             if (m_Assembly == null)
    25.                 AssemblyCache.assemblies.TryGetValue(fullName, out m_Assembly);
    26.             return m_Assembly;
    27.         }
    28.         public override bool Equals(object obj)
    29.         {
    30.             if (obj is AssemblyItem other)
    31.                 return fullName == other.fullName;
    32.             return false;
    33.         }
    34.         public override int GetHashCode()
    35.         {
    36.             return fullName.GetHashCode();
    37.         }
    38.     }
    39.  
    40.     public class AssemblyCache
    41.     {
    42.         public static Dictionary<string, Assembly> assemblies;
    43.         public static List<AssemblyItem> assemblyList;
    44.         static AssemblyCache()
    45.         {
    46.             assemblies = new Dictionary<string, Assembly>();
    47.             assemblyList = new List<AssemblyItem>();
    48.             foreach (var a in System.AppDomain.CurrentDomain.GetAssemblies())
    49.             {
    50.                 assemblies.Add(a.FullName, a);
    51.                 assemblyList.Add(new AssemblyItem(a));
    52.             }
    53.             assemblyList.Sort((a, b) => a.fullName.CompareTo(b.fullName));
    54.         }
    55.     }
    56.  
    57.  
    58.     [System.Serializable]
    59.     public class AssemblyList
    60.     {
    61.         public List<AssemblyItem> list = new List<AssemblyItem>();
    62.     }
    63. #if UNITY_EDITOR
    64.     namespace Editor
    65.     {
    66.         using UnityEditor;
    67.         [CustomPropertyDrawer(typeof(AssemblyList))]
    68.         public class AssemblyListPropertyDrawer : PropertyDrawer
    69.         {
    70.             private class Item
    71.             {
    72.                 public AssemblyItem item;
    73.                 public int index;
    74.             }
    75.             SerializedProperty list;
    76.             public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
    77.             {
    78.                 list = property.FindPropertyRelative("list");
    79.  
    80.                 EditorGUI.BeginProperty(position, label, property);
    81.                 position = EditorGUI.PrefixLabel(position, label);              
    82.                 if (GUI.Button(position,"Select", EditorStyles.toolbarDropDown))
    83.                 {
    84.                     OpenMenu();
    85.                 }
    86.                 EditorGUI.EndProperty();
    87.             }
    88.             void OpenMenu()
    89.             {
    90.                 // purge outdated elements
    91.                 for (int i = list.arraySize - 1; i>=0; i--)
    92.                 {
    93.                     var e = list.GetArrayElementAtIndex(i);
    94.                     var fName = e.FindPropertyRelative("fullName").stringValue;
    95.                     if (!AssemblyCache.assemblies.ContainsKey(fName))
    96.                         list.DeleteArrayElementAtIndex(i);
    97.                 }
    98.                 list.serializedObject.ApplyModifiedPropertiesWithoutUndo();
    99.  
    100.                 // create drop down menu
    101.                 var menu = new GenericMenu();
    102.                 foreach (var a in AssemblyCache.assemblyList)
    103.                 {
    104.                     int index = -1;
    105.                     int count = list.arraySize;
    106.                     for (int i = 0; i < count; i++)
    107.                     {
    108.                         var e = list.GetArrayElementAtIndex(i);
    109.                         var fName = e.FindPropertyRelative("fullName").stringValue;
    110.                         if (fName == a.fullName)
    111.                         {
    112.                             index = i;
    113.                             break;
    114.                         }
    115.                     }
    116.                     string name = a.displayName.Replace(".", "/");
    117.                     menu.AddItem(new GUIContent(name), index >= 0, OnClick, new Item { index=index, item = a });
    118.                 }
    119.                 menu.ShowAsContext();
    120.             }
    121.  
    122.             private void OnClick(object aItem)
    123.             {
    124.                 Item item = (Item)aItem;
    125.                 if (item.index >= 0)
    126.                 {
    127.                     list.DeleteArrayElementAtIndex(item.index);
    128.                     list.serializedObject.ApplyModifiedProperties();
    129.                 }
    130.                 else
    131.                 {
    132.                     int index = list.arraySize;
    133.                     list.arraySize++;
    134.                     var element = list.GetArrayElementAtIndex(index);
    135.                     element.FindPropertyRelative("fullName").stringValue = item.item.fullName;
    136.                     element.FindPropertyRelative("displayName").stringValue = item.item.displayName;
    137.                     list.serializedObject.ApplyModifiedProperties();
    138.                 }
    139.             }
    140.         }
    141.     }
    142. #endif
    143. }

    With this in your project you can delcare a field like this

    Code (CSharp):
    1. public AssemblyList assemblies;
    and you get a similar drop down button that displays all loaded assemblies and allows you to select as many as you with. It actually stores "AssemblyItem"s inside a List. An AssemblyItem internally just stores the fullname and the displayname of the assembly. Though through the AssemblyCache you can access the actual assembly directly from the AssemblyItem.

    When you click an item in the dropdown menu, it will simply be added at the end of the list if it's not yet in the list. If it is already in the list it will be removed. Since projects could change, whenever you open the dropdown menu it first removes any elements from the list that could not be found in the assembly cache. This avoids having invalid lingering items in the list that do no longer exist (and therefore you could not deselect anymore).

    Note that assembly list is not sorted. So they appear in the order you have selected them. Feel free to sort the list if you need to.

    Finally since the list of assemblies is quite long, I decided to replace the dots in the assembly names with slashes. This would automatically create submenus which essentially groups assemblies which start with the same name like UnityEngine.XXXX or Unity.XXXX. This makes the dropdown a bit cleaner. Though the UnityEngine list is still quite long as there are many assemblies that start with UnityEngine.
     
    z_yq and MelvMay like this.
  5. z_yq

    z_yq

    Joined:
    May 3, 2017
    Posts:
    15
    Thank you very much for the explanation, I will try your suggestion, thanks again!
     
  6. FoodFish_

    FoodFish_

    Joined:
    May 3, 2018
    Posts:
    58
    I would love for MaskField to support 64 bit.