Search Unity

LayerMask variables with error "Can only be called from the main thread" still work?

Discussion in 'Scripting' started by HiddenMonk, Aug 28, 2015.

  1. HiddenMonk

    HiddenMonk

    Joined:
    Dec 19, 2014
    Posts:
    987
    If you have a monobehaviour with a Layermask variable setup like this
    "LayerMask mask = LayerMask.GetMask("SomeLayerName");"
    When you press play, it will cause an error and not work correctly.
    However, if you build and run the game, then it would seemingly work properly.

    Now, if you made that "LayerMask mask" a static variable, then in both build and editor it seems to work fine, except you will get an error, only once, stating you it "Can only be called from the main thread" when you open your project and go to the scene.

    Error.png

    I am a bit confused about all this. Would it be safe for me to use the static variable version and ignore that initial error?
    Why would I get the errors in the first place if it is still allowing me to use the method outside the main thread? Or maybe I am not really outside the main thread?

    I have other static layermasks that use GetMask(), however, they are only used by methods that are inside Update or what not, so I guess thats why they didnt give me any errors? That is leading me to believe that what I am doing with the static variable is fine, but the error is a bug, or more so just a safety net?

    Any info is appreciated =)

    Edit - For now I will just use this extension class to avoid any possible issues.
    The downside is I will have to keep track of any layer changes I make.
    Code (CSharp):
    1. using System;
    2. using UnityEngine;
    3. using System.Collections.Generic;
    4.  
    5. public static class ExtLayerMask
    6. {
    7.     static Dictionary<string, int> layers = new Dictionary<string, int>()
    8.     {
    9.         {"Default", 0},
    10.         {"TransparentFX", 1},
    11.         {"Ignore Raycast", 2},
    12.         //{"", 3},
    13.         {"Water", 4},
    14.         {"UI", 5},
    15.         //{"", 6},
    16.         //{"", 7},
    17.         {"MyPlayer", 8},
    18.         //{"", 9},
    19.         //{"", 10},
    20.         //{"", 11},
    21.         //{"", 12},
    22.         //{"", 13},
    23.         //{"", 14},
    24.         //{"", 15},
    25.         //{"", 16},
    26.         //{"", 17},
    27.         //{"", 18},
    28.         //{"", 19},
    29.         //{"", 20},
    30.         //{"", 21},
    31.         //{"", 22},
    32.         //{"", 23},
    33.         //{"", 24},
    34.         //{"", 25},
    35.         //{"", 26},
    36.         //{"", 27},
    37.         //{"", 28},
    38.         //{"", 29},
    39.         //{"", 30},
    40.         //{"", 31}
    41.     };
    42.  
    43.     public static int NameToLayer(string name)
    44.     {
    45.         if(layers.ContainsKey(name))
    46.         {
    47.             return layers[name];
    48.         }else{
    49.             Debug.LogError("ExtLayerMask NameToLayer could not find the layer for the provided layer name. Returning default layer");
    50.         }
    51.         return 0;
    52.     }
    53.  
    54.     public static int GetMask(params string[] layerNames)
    55.     {
    56.         int mask = 0;
    57.         for(int i = 0; i < layerNames.Length; i++)
    58.         {
    59.             mask |= 1 << NameToLayer(layerNames[i]);
    60.         }
    61.         return mask;
    62.     }
    63. }
     
    Last edited: Aug 28, 2015
  2. lordofduct

    lordofduct

    Joined:
    Oct 3, 2011
    Posts:
    8,528
    This has to do with how initialization of a field works.

    Static fields are initialized the first time the class is accessed in some manner.

    Non-static fields are initialized when an object of that type of class is created. They're initialized just before the constructor function occurs.


    As long as you're not multi-threading, the first time the class is accessed, is almost always on the main thread (there may be weird times that unity might access a class outside of the main thread... and this will actually have issues). This is why you seldom see this exception with the static fields.

    Where as when a component is constructed for placement on a GameObject when a scene is loaded is during the deserialization process. This process is on a different thread, that way the game can proceed doing stuff during load, like display a load screen. This is why unity advises you to not do anything in the constructor function of a script... thing is you actually CAN do stuff in the constructor, it's just that you have to be very careful. And because initializing fields occurs during this time, the same set of rules applies there as well.



    As for your specific problem, you could just avoid using LayerMask.GetMask.

    For example, I don't bother with that at all. Instead I have a static class with all my constants defined on it.

    example:
    Code (csharp):
    1.  
    2. using UnityEngine;
    3.  
    4. using com.spacepuppy;
    5.  
    6. namespace com.apoc
    7. {
    8.    
    9.    public static class Constants
    10.   {
    11.  
    12.   #region Tags
    13.  
    14.   //inherited from com.spacepuppy
    15.   public const string TAG_MULTITAG = SPConstants.TAG_MULTITAG;
    16.   public const string TAG_ROOT = SPConstants.TAG_ROOT;
    17.   public const string TAG_PLAYER = SPConstants.TAG_PLAYER;
    18.   //custom to this game
    19.   public const string TAG_NPC = "NPC";
    20.   public const string TAG_DEBRIS = "Debris";
    21.  
    22.   public const string TAG_ENTITYEVENTRIGGER = "EntityEventTrigger";
    23.  
    24.   #endregion
    25.  
    26.   #region Layers
    27.  
    28.   public const int LAYER_DEFAULT = 0;
    29.   public const int LAYER_WATER = 4;
    30.    
    31.   //mobile layers
    32.      public const int LAYER_PLAYER = 8;
    33.   public const int LAYER_NPC = 9;
    34.   public const int LAYER_DYNAMIC = 10;
    35.   public const int LAYER_PROJECTILE = 14;
    36.  
    37.   public const int LAYER_CAMERA = 11;
    38.  
    39.   //terrain layers
    40.   public const int LAYER_BGRUN = 13;
    41.   public const int LAYER_NONCLIMBABLE = 18;
    42.  
    43.   public const int LAYER_EFFECT = 15;
    44.  
    45.   //hitbox layers
    46.   public const int LAYER_HITBOX = 16;
    47.   public const int LAYER_STRIKEBOX = 17;
    48.  
    49.   //event trigger layers
    50.   public const int LAYER_ENTITYEVENTRIGGER = 19;
    51.  
    52.   public const int LAYER_RAGDOLLSELFCOLLISION = 30;
    53.  
    54.   public const int MASK_DEFAULT = 1 << LAYER_DEFAULT;
    55.  
    56.      public const int MASK_PLAYER = 1 << LAYER_PLAYER;
    57.   public const int MASK_NPC = 1 << LAYER_NPC;
    58.   public const int MASK_DYNAMIC = 1 << LAYER_DYNAMIC;
    59.   public const int MASK_PROJECTILE = 1 << LAYER_PROJECTILE;
    60.   public const int MASK_MOBILELAYERS = MASK_PLAYER | MASK_NPC | MASK_DYNAMIC | MASK_PROJECTILE;
    61.   public const int MASK_PUSHABLEMOBILELAYERS = MASK_NPC | MASK_DYNAMIC | MASK_PROJECTILE;
    62.  
    63.   public const int MASK_CAMERA = 1 << LAYER_CAMERA;
    64.  
    65.   public const int MASK_BGRUN = 1 << LAYER_BGRUN;
    66.   public const int MASK_NONCLIMBABLE = 1 << LAYER_NONCLIMBABLE;
    67.   public const int MASK_SURFACE = MASK_DEFAULT | MASK_NONCLIMBABLE;
    68.   public const int MASK_GRABBABLE = MASK_DEFAULT;
    69.  
    70.   public const int MASK_ENTITYEVENTTRIGGER = 1 << LAYER_ENTITYEVENTRIGGER;
    71.  
    72.   /// <summary>
    73.   /// This pertains the surfaces that can be considered the 'ground' and walked on via 'GroundingResolver' and 'Platforming' movement styles
    74.   /// </summary>
    75.   public const int MASK_CLIMBABLE = MASK_DEFAULT;
    76.  
    77.   public const int MASK_HITBOX = 1 << LAYER_HITBOX;
    78.   public const int MASK_STRIKEBOX = 1 << LAYER_STRIKEBOX;
    79.  
    80.   public const int MASK_EFFECT = 1 << LAYER_EFFECT;
    81.  
    82.   public const int MASK_RAGDOLLSELFCOLLISION = 1 << LAYER_RAGDOLLSELFCOLLISION;
    83.  
    84.   #endregion
    85.  
    86.   #region Anim Layers
    87.  
    88.   public const int ANIM_LAYER_PRIMARY = 0;
    89.   public const int ANIM_LAYER_JUMP = 1;
    90.   public const int ANIM_LAYER_CUSTOMACTION = 10;
    91.  
    92.   #endregion
    93.  
    94.   #region Messages
    95.  
    96.   public const string MSG_ONLEVELPAUSED = "OnLevelPaused";
    97.   public const string MSG_ONLEVELUNPAUSED = "OnLevelUnPaused";
    98.  
    99.   #endregion
    100.  
    101.   #region Methods
    102.  
    103.   /// <summary>
    104.   /// Returns the primary layer for some mob type.
    105.   /// </summary>
    106.   /// <param name="tp"></param>
    107.   /// <returns></returns>
    108.   public static int GetPrimaryLayerForMobType(EntityType tp)
    109.   {
    110.   switch (tp)
    111.   {
    112.   case EntityType.Player:
    113.   return LAYER_PLAYER;
    114.   case EntityType.NPC:
    115.   return LAYER_NPC;
    116.   case EntityType.DynamicObject:
    117.   return LAYER_DYNAMIC;
    118.   default:
    119.   return LAYER_DEFAULT;
    120.   }
    121.   }
    122.  
    123.   /// <summary>
    124.   /// Gets the mob type for a layer.
    125.   /// </summary>
    126.   /// <param name="layer"></param>
    127.   /// <returns></returns>
    128.   public static EntityType GetMobTypeForLayer(int layer)
    129.   {
    130.   if (layer == LAYER_PLAYER)
    131.   {
    132.   return EntityType.Player;
    133.   }
    134.   else if (layer == LAYER_NPC)
    135.   {
    136.   return EntityType.NPC;
    137.   }
    138.   else if (layer == LAYER_DYNAMIC)
    139.   {
    140.   return EntityType.DynamicObject;
    141.   }
    142.   else
    143.   {
    144.   return EntityType.None;
    145.   }
    146.   }
    147.  
    148.   #endregion
    149.  
    150.   }
    151.  
    152. }
    153.  
    LayerMask really is just a struct that wraps around an int. And it implicity converts int to LayerMask. So you can just define a variable the type LayerMask and pull the int from a constants class like the above.

    Example:
    Code (csharp):
    1.  
    2. public class SomeScript : MonoBehaviour
    3. {
    4.     public LayerMask Mask = Constants.MASK_SURFACE;
    5. }
    6.  
     
    kypronite and HiddenMonk like this.
  3. hpjohn

    hpjohn

    Joined:
    Aug 14, 2012
    Posts:
    2,190
    Or just... put it in start....?
    Code (CSharp):
    1.     LayerMask l;
    2.     void Start () {
    3.         l = LayerMask.GetMask( "Player" );
    4.     }
     
  4. HiddenMonk

    HiddenMonk

    Joined:
    Dec 19, 2014
    Posts:
    987
    Ye, I always assumed they were initialized in the beginning of the program.

    I was doing something similar to that, which I edited in earlier to my original post.

    That can work for a monobehaviour script, but if you have a non monobehaviour script that is being initialized inside a monobehaviour script like so "SomeScript someScript = new SomeScript();" and if inside that script you have "LayerMask mask = LayerMask.GetMask("SomeLayerName");" then it will still give the error.

    I guess you can then initialize that "SomeScript" inside start, but I prefer not.