Search Unity

  1. Megacity Metro Demo now available. Download now.
    Dismiss Notice
  2. Unity support for visionOS is now available. Learn more in our blog post.
    Dismiss Notice

Methods of Mapping Enums Together

Discussion in 'Scripting' started by TBernard, Feb 1, 2015.

  1. TBernard

    TBernard

    Joined:
    Jan 31, 2015
    Posts:
    1
    TLDR
    Using a singleton indexer you can very efficiently map one enum's values to anothers with a clean editable code block. If you have any other/better methods feel free to post them.
    /TLDR

    I'm making a card-type game. The player plays 8 sided tiles, and the values on their tiles are compared against the opposing sides of adjacent tiles.

    My tile's sides are stored in an array, but I use an enum to map the constants North, Northeast, East.... to the 0-7 index arrays.

    I come from a C++ background and also am vehemently opposed to using hardcoded values instead of constants. That said, while in practice my tiles are 8 sided, I want to keep the code flexible to fit whatever number of sides I want the tile to use down the road. With an 8 sided tile finding the "opposite" side is easily calculated by adding half and doing a modulus. So if I'm using this tiles SouthWest side (5) it's adjacent tile's side would be 5+4%8 = 1 (NorthEast). However what do you do with a 7sided tile since they don't fit together neatly? You'd need to define a custom mapping.

    I set out to find efficient methods of mapping one enum value to another. Here are the methods I came up with:
    1. Use the equation and don't overcomplicate things
    2. Use a second enum with it's value's "Shifted" to map with. For example EnumShift.South = Enum.North. Then use the Enum.GetName and Enum.Parse functions to find the matches.
    3. Use a function that hardcodes ( :mad: ) the mapping
    4. Use a custom indexer to take one enum value and output it's mapped value
    5. Use a singleton indexer to map one enum value to another
    Here's my average test results for each of the above methods. It was timed by running 1million iterations of each method in an Update function on the main camera in an otherwise empty game. I ran for 10 updates and averaged the results. Note the results are counted in Ticks not seconds.
    1. 41396 43725 43289 42488 42001 43448 42658 42370 43346 44244 42897
    2. 6744575 6785478 6698653 6861967 7168113 6615446 6855862 6800119 6818817 7042156 6839119
    3. 69732 61604 61748 63281 68100 61650 62717 63563 68403 63240 64404
    4. 47102 43556 43535 42935 46682 42463 42355 41991 46681 41863 43916
    5. 48420 43859 44028 44362 48164 42930 43326 46368 47892 44105 45345
    You'll notice using a custom indexer is ALMOST as fast as straight arithmetic, is easy to edit, requires no casting, and if you use the singleton is statically accessible. Trying to use the "shifted" enumeration to map values is a terrible idea. Enum.Parse and Enum.GetName are relatively intensive.

    Source Code: This was placed in the script Test.cs and attached to the main camera in an otherwise empty game.

    Code (csharp):
    1. using UnityEngine;
    2. using System.Collections;
    3. using System.Diagnostics;
    4.  
    5. public enum TileDirection {_FIRST, _LAST = _COUNT - 1, North = _FIRST, NorthEast, East, SouthEast, South, SouthWest, West, NorthWest, _COUNT }
    6. public enum TileOpposition {
    7.     North = TileDirection.South,
    8.     NorthEast = TileDirection.SouthWest,
    9.     East = TileDirection.West,
    10.     SouthEast = TileDirection.NorthWest,
    11.     South = TileDirection.North,
    12.     SouthWest = TileDirection.NorthEast,
    13.     West = TileDirection.East,
    14.     NorthWest = TileDirection.SouthEast
    15. }
    16.  
    17. public class OpposedSide
    18. {
    19.     private TileDirection[] arr = new TileDirection[(int)TileDirection._COUNT];
    20.  
    21.     public OpposedSide() {
    22.         this[TileDirection.North] = TileDirection.South;
    23.         this[TileDirection.NorthEast] = TileDirection.SouthWest;
    24.         this[TileDirection.East] = TileDirection.West;
    25.         this[TileDirection.SouthEast] = TileDirection.NorthWest;
    26.         this[TileDirection.South] = TileDirection.North;
    27.         this[TileDirection.SouthWest] = TileDirection.NorthEast;
    28.         this[TileDirection.West] = TileDirection.East;
    29.         this[TileDirection.NorthWest] = TileDirection.SouthEast;
    30.     }
    31.            
    32.     public TileDirection this[TileDirection i]
    33.     {
    34.         get
    35.         {
    36.             return arr[(int)i];
    37.         }
    38.         set
    39.         {
    40.             arr[(int)i] = value;
    41.         }
    42.     }
    43. }
    44.  
    45. public static class OpposedSideSingleton {
    46.     public static OppDir map = new OppDir();
    47.  
    48.     public class OppDir
    49.     {
    50.         private TileDirection[] arr = new TileDirection[(int)TileDirection._COUNT];
    51.  
    52.         public OppDir() {
    53.             this[TileDirection.North] = TileDirection.South;
    54.             this[TileDirection.NorthEast] = TileDirection.SouthWest;
    55.             this[TileDirection.East] = TileDirection.West;
    56.             this[TileDirection.SouthEast] = TileDirection.NorthWest;
    57.             this[TileDirection.South] = TileDirection.North;
    58.             this[TileDirection.SouthWest] = TileDirection.NorthEast;
    59.             this[TileDirection.West] = TileDirection.East;
    60.             this[TileDirection.NorthWest] = TileDirection.SouthEast;
    61.         }
    62.    
    63.         public TileDirection this[TileDirection i]
    64.         {
    65.             get
    66.             {
    67.                 return arr[(int)i];
    68.             }
    69.             set
    70.             {
    71.                 arr[(int)i] = value;
    72.             }
    73.         }
    74.     }
    75. }
    76.  
    77.  
    78. public class Test : MonoBehaviour {
    79.  
    80.     public int iterations = 1000000;
    81.  
    82.     // Update is called once per frame
    83.     void Update () {
    84.         // Time using integer and division
    85.         Stopwatch sw = new Stopwatch ();
    86.         int i = iterations;
    87.         int thisSide;
    88.         TileDirection opposes;
    89.         long intTime;
    90.         sw.Start ();
    91.         do {
    92.             thisSide = Random.Range ((int)TileDirection._FIRST, (int)TileDirection._LAST);
    93.             opposes = (TileDirection)((thisSide + ((int)TileDirection._COUNT / 2)) % (int)TileDirection._COUNT);
    94.         } while(--i >= 0);
    95.         sw.Stop ();
    96.         intTime = sw.ElapsedTicks;
    97.         sw.Reset ();
    98.  
    99.         // Time using transform enum
    100.         i = iterations;
    101.         TileDirection thisSideE, opposesE;
    102.         long enumTime;
    103.         sw.Start ();
    104.         do {
    105.             thisSideE = (TileDirection)Random.Range ((int)TileDirection._FIRST, (int)TileDirection._LAST);
    106.             opposesE = (TileDirection)System.Enum.Parse(typeof(TileOpposition),System.Enum.GetName (typeof(TileDirection), thisSideE), true);
    107.         } while(--i >= 0);
    108.         sw.Stop ();
    109.         enumTime = sw.ElapsedTicks;
    110.         sw.Reset ();
    111.  
    112.         // Time using transform function
    113.         i = iterations;
    114.         long funcTime;
    115.         sw.Start ();
    116.         do {
    117.             thisSideE = (TileDirection)Random.Range ((int)TileDirection._FIRST, (int)TileDirection._LAST);
    118.             opposesE = getOpposingSide(thisSideE);
    119.         } while(--i >= 0);
    120.         sw.Stop ();
    121.         funcTime = sw.ElapsedTicks;
    122.         sw.Reset ();
    123.  
    124.         // Time using a mapping Indexer
    125.         i = iterations;
    126.         long idxTime;
    127.         OpposedSide oppSide = new OpposedSide ();
    128.         sw.Start ();
    129.         do {
    130.             thisSideE = (TileDirection)Random.Range ((int)TileDirection._FIRST, (int)TileDirection._LAST);
    131.             opposesE = oppSide[thisSideE];
    132.         } while (--i >= 0);
    133.         sw.Stop ();
    134.         idxTime = sw.ElapsedTicks;
    135.         sw.Reset ();
    136.  
    137.         // Time using a singleton mapping Indexer
    138.         i = iterations;
    139.         long snglTime;
    140.         sw.Start ();
    141.         do {
    142.             thisSideE = (TileDirection)Random.Range ((int)TileDirection._FIRST, (int)TileDirection._LAST);
    143.             opposesE = OpposedSideSingleton.map[thisSideE];
    144.         } while (--i >= 0);
    145.         sw.Stop ();
    146.         snglTime = sw.ElapsedTicks;
    147.         sw.Reset ();
    148.  
    149.         // Output timing
    150.         UnityEngine.Debug.Log ("Int: " + intTime.ToString () + " Enum: " + enumTime.ToString () + " Func: " + funcTime.ToString () + " Idx: " + idxTime.ToString () + " Sing: " + snglTime.ToString ());
    151.     }
    152.  
    153.     public TileDirection getOpposingSide(TileDirection dir) {
    154.         switch (dir) {
    155.         case TileDirection.North: return TileDirection.South;
    156.         case TileDirection.NorthEast: return TileDirection.SouthWest;
    157.         case TileDirection.East: return TileDirection.West;
    158.         case TileDirection.SouthEast: return TileDirection.NorthWest;
    159.         case TileDirection.South: return TileDirection.North;
    160.         case TileDirection.SouthWest: return TileDirection.NorthEast;
    161.         case TileDirection.West: return TileDirection.East;
    162.         case TileDirection.NorthWest: return TileDirection.SouthEast;
    163.         default: return 0;
    164.         }
    165.     }
    166.  
    167. }
    168.  
     
    Last edited: Feb 1, 2015