What makes this code garbage collect?

According to my profiler calling InputManager.GetKey(Keycode) is generating garbage yet it seems like it’s a very simple code. Does someone know why that is?

public static class InputManager
{
    public static Dictionary<K, KeyCode[]> ButtonKeys { get; private set; }
    public static bool DisableListenToMouseClicks { get; private set; }

    static InputManager()
    {
        ButtonKeys = new Dictionary<K, KeyCode[]>();
        ButtonKeys[K.Glide] = new[] { KeyCode.LeftControl };
        ButtonKeys[K.Jump] = new[] { KeyCode.Space };
        ButtonKeys[K.Interact] = new[] { KeyCode.F };
        ButtonKeys[K.Attack] = new[] {/* KeyCode.C, KeyCode.LeftShift, */KeyCode.Mouse0 };
        ButtonKeys[K.AttackUp] = new[] { KeyCode.W };
        ButtonKeys[K.AttackDown] = new[] { KeyCode.S };
        ButtonKeys[K.AttackLeft] = new[] { KeyCode.A };
        ButtonKeys[K.AttackRight] = new[] { KeyCode.D };
        ButtonKeys[K.Dash] = new[] { KeyCode.Mouse1 };
        ButtonKeys[K.DodgeRoll] = new[] { KeyCode.LeftShift };

        // UI/Pauses..
        ButtonKeys[K.Pause] = new[] { KeyCode.P };
        ButtonKeys[K.Inventory] = new[] { KeyCode.I };
        ButtonKeys[K.Character] = new[] { KeyCode.H };
        ButtonKeys[K.HighlightItems] = new[] { KeyCode.LeftAlt };

        // Move
        ButtonKeys[K.Down] = new[] { KeyCode.S, KeyCode.DownArrow };
        ButtonKeys[K.Up] = new[] { KeyCode.W, KeyCode.UpArrow };
        ButtonKeys[K.Right] = new[] { KeyCode.D, KeyCode.RightArrow };
        ButtonKeys[K.Left] = new[] { KeyCode.A, KeyCode.LeftArrow };

        // Use skills
        ButtonKeys[K.StealSkill] = new[] { KeyCode.Q };
        ButtonKeys[K.EquipSkill1] = new[] { KeyCode.Alpha1 };
        ButtonKeys[K.EquipSkill2] = new[] { KeyCode.Alpha2 };
        ButtonKeys[K.EquipSkill3] = new[] { KeyCode.Alpha3 };
        ButtonKeys[K.EquipSkill4] = new[] { KeyCode.Alpha4 };

        // Misc
        ButtonKeys[K.TestKey] = new[] { KeyCode.J };
        ButtonKeys[K.Block] = new[] { KeyCode.R };
        ButtonKeys[K.Explosion] = new[] { KeyCode.L };
        ButtonKeys[K.PushPull] = new[] { KeyCode.V };
        ButtonKeys[K.Telekinesis] = new[] { KeyCode.B };

        ButtonKeys[K.SpellMode] = new[] { KeyCode.LeftShift };
        ButtonKeys[K.Teleport] = new[] { KeyCode.S };
        ButtonKeys[K.ThrowUp] = new[] { KeyCode.W };
        ButtonKeys[K.Meteor] = new[] { KeyCode.D };
        ButtonKeys[K.ChargeDash] = new[] { KeyCode.A };
        ButtonKeys[K.Arrow] = new[] { KeyCode.Q };
        ButtonKeys[K.Bungee] = new[] { KeyCode.F };
        ButtonKeys[K.DownwardSlash] = new[] { KeyCode.E };
        ButtonKeys[K.SunStrike] = new[] { KeyCode.Q };

        ButtonKeys[K.SwitchWeapon] = new[] { KeyCode.E };
        ButtonKeys[K.UseSkill] = new[] { KeyCode.Mouse1 };
        ButtonKeys[K.CancelSkill] = new[] { KeyCode.R };
        ButtonKeys[K.SlowTime] = new[] { KeyCode.C };
        ButtonKeys[K.CounterPush] = new[] { KeyCode.Mouse1 };
        ButtonKeys[K.CounterStun] = new[] { KeyCode.Mouse0 };
        ButtonKeys[K.Stalagmite] = new[] { KeyCode.Alpha1 };
    }

   public static bool GetKey(K _buttonName)
    {
        //if (ButtonKeys.ContainsKey(_buttonName) == false)
        //{
        //    Debug.LogError("InputManager::GetButtonDown -- No button named: " + _buttonName);
        //    return false;
        //}
        var keycodes = ButtonKeys[_buttonName];
        foreach (var input in keycodes)
        {
            if (IsMouseClick(input) && DisableListenToMouseClicks) continue;
            if (Input.GetKey(input)) return true;
        }

        return false;
    }

    public static bool IsMouseClick(KeyCode _code)
    {
        return _code == KeyCode.Mouse0 || _code == KeyCode.Mouse1 || _code == KeyCode.Mouse2 ||
               _code == KeyCode.Mouse3 || _code == KeyCode.Mouse4 || _code == KeyCode.Mouse5 || _code == KeyCode.Mouse6;
    }
}

You mean beyond what your profiler tells you? It says it’s the dictionary getting and comparing items (Line 72 i would guess). I don’t think you can do anything about that.

But isn’t the dictionary the best structure to find an item at a specific position? And why would getting an item from a dictionary generate garbage?

The reason is that the defaultComparer is kicking in. That’s all kinds of inefficient, and happens when the dictionary doesn’t have a better way to compare your elements.

What’s K? If it’s an enum, I don’t think this shouldn’t be happening.

Yes K is an enum. Did you mean “I don’t think should be happening” instead of shouldn’t ?

public enum K
{
    Glide,
    Jump,
    Interact,
    Attack,
    AttackUp,
    AttackDown,
    AttackLeft,
    AttackRight,
    Dash,
    DodgeRoll,

    Pause,
    Inventory,
    Character,
    HighlightItems,

    Down,
    Up,
    Left,
    Right,

    StealSkill,
    EquipSkill1,
    EquipSkill2,
    EquipSkill3,
    EquipSkill4,
    UseSkill,
    CancelSkill,

    SwitchWeapon,

    Block,

    TestKey,
    SpellMode,
    Explosion,
    PushPull,
    Telekinesis,
    Teleport,
    ThrowUp,
    Meteor,
    ChargeDash,
    Arrow,
    Bungee,
    DownwardSlash,
    SunStrike,
    Stalagmite,

    SlowTime,
    CounterPush,
    CounterStun,
}

Yes, that’s what I meant! Writing is hard sometimes.

I looked it up, and turns out enums gets boxed when compared. Bummer! There’s some pretty straight forward fixes, see this StackOverflow question’s top answer. The first solution should be good enough; just make a comparer for K, and pass it to the constructor when you’re making ButtonKeys.

2 Likes

Thanks for the answer Baste! Much appreciated!

Can you not also cast the enum to int ? This will not be as pretty and I am not 100% sure if it is garbage free.

I.e. Dict<int,int> TestDict
TestDict[(int) K.Glide] = …

That’ll be garbage free, and might even be faster.

Honestly, as long as this is all hidden in a private static field, and there’s no custom assignment of what the int-values of K are, this can be solved with an array:

    public static KeyCode[][] ButtonKeys { get; private set; }

    static InputManager()
    {
        var numKeys = System.Enum.GetValues(typeof(K)).Length;
        ButtonKeys = new KeyCode[numKeys][];
        ButtonKeys[(int9 K.Glide] = new[] { KeyCode.LeftControl };
        ...
    }

Not much harder to read, and a pretty free speedup.

1 Like

DefaultComparer causes huge garbage when used with Enums.
Just create a custom static comparer for your Enum and use that instead.

class KComparer : IEqualityComparer<K>
{
    public static readonly KComparer Instance = new KComparer();

    public bool Equals(K x, K y)
    {
        return (x == y);
    }

    public int GetHashCode(K k)
    {
        return (int)k;
    }
 }

And Initialize the Dictionary as follows:

ButtonKeys = new Dictionary<K, KeyCode[]>(KComparer.Instance);