public void Refresh(bool wantClobber = true)
        {
            LogDebug(string.Format("[LimitItems] Refresh: {0} {1} {2} {3}", index, filteredInventory.Count, itemLimit, new Traverse(inventoryWidget).Field("scrollbarArea").GetValue <UnityEngine.UI.ScrollRect>().verticalNormalizedPosition));
            if (index > filteredInventory.Count - itemsOnScreen)
            {
                index = filteredInventory.Count - itemsOnScreen;
            }
            if (filteredInventory.Count < itemsOnScreen)
            {
                index = 0;
            }
            if (index < 0)
            {
                index = 0;
            }
            if (Spam)
            {
                LogSpam(string.Format("[LimitItems] Refresh(F): {0} {1} {2} {3}", index, filteredInventory.Count, itemLimit, new Traverse(inventoryWidget).Field("scrollbarArea").GetValue <UnityEngine.UI.ScrollRect>().verticalNormalizedPosition));
            }

            Func <ListElementController_BASE_NotListView, string> pp = lec => {
                return(string.Format("[id:{0},damage:{1},quantity:{2},id:{3}]"
                                     , GetRef(lec).ComponentDefID
                                     , GetRef(lec).DamageLevel
                                     , lec.quantity
                                     , lec.GetId()));
            };

            var iw_corrupted_add = inventoryWidget.localInventory.Where(x => !ielCache.Contains(x)).ToList();

            if (iw_corrupted_add.Count > 0)
            {
                LogError("inventoryWidget has been corrupted, items were added directly: " + string.Join(", ", iw_corrupted_add.Select(c => c.controller).Select(pp).ToArray()));
            }
            var iw_corrupted_remove = ielCache.Where(x => !inventoryWidget.localInventory.Contains(x)).ToList();

            if (iw_corrupted_remove.Count > 0)
            {
                LogError("inventoryWidget has been corrupted, iel elements were removed.");
            }

            if (iw_corrupted_add.Any() || iw_corrupted_remove.Any())
            {
                LogWarning("Restoring to last good state. Duplication or item loss may occur.");
                inventoryWidget.localInventory = ielCache.ToArray().ToList();
            }

            var toShow = filteredInventory.Skip(index).Take(itemLimit).ToList();

            var icc = ielCache.ToList();



            if (Spam)
            {
                LogSpam("[LimitItems] Showing: " + string.Join(", ", toShow.Select(pp).ToArray()));
            }

            var details = new List <string>();

            toShow.ForEach(lec => {
                var iw   = icc[0]; icc.RemoveAt(0);
                var cref = GetRef(lec);
                iw.ClearEverything();
                iw.ComponentRef = cref;
                lec.ItemWidget  = iw;
                iw.SetData(lec, inventoryWidget, lec.quantity, false, null);
                lec.SetupLook(iw);
                iw.gameObject.SetActive(true);
                details.Insert(0, string.Format("enabled {0} {1}", iw.ComponentRef.ComponentDefID, iw.GetComponent <UnityEngine.RectTransform>().anchoredPosition));
            });
            icc.ForEach(unused => unused.gameObject.SetActive(false));

            var listElemSize = 64.0f;
            var spacerTotal  = 16.0f; // IEL elements are 64 tall, but have a total of 80 pixels between each when considering spacing.
            var spacerHalf   = spacerTotal * .5f;
            var tsize        = listElemSize + spacerTotal;

            var virtualStartSize = tsize * index - spacerHalf;

            DummyStart.gameObject.SetActive(index > 0); //If nothing prefixing, must disable to prevent halfspacer offset.
            DummyStart.sizeDelta = new UnityEngine.Vector2(100, virtualStartSize);
            DummyStart.SetAsFirstSibling();

            var itemsHanging = filteredInventory.Count - (index + ielCache.Count(ii => ii.gameObject.activeSelf));

            var ap1 = ielCache[0].GetComponent <UnityEngine.RectTransform>().anchoredPosition;
            var ap2 = ielCache[1].GetComponent <UnityEngine.RectTransform>().anchoredPosition;

            LogDebug(string.Format("[LimitItems] Items prefixing {0} hanging {1} total {2} {3}/{4}", index, itemsHanging, filteredInventory.Count, ap1, ap2));



            var virtualEndSize = tsize * itemsHanging - spacerHalf;

            DummyEnd.gameObject.SetActive(itemsHanging > 0); //If nothing postfixing, must disable to prevent halfspacer offset.
            DummyEnd.sizeDelta = new UnityEngine.Vector2(100, virtualEndSize);
            DummyEnd.SetAsLastSibling();

            new Traverse(instance).Method("RefreshInventorySelectability").GetValue();
            if (Spam)
            {
                var sr = new Traverse(inventoryWidget).Field("scrollbarArea").GetValue <UnityEngine.UI.ScrollRect>();
                LogSpam(string.Format("[LimitItems] RefreshDone dummystart {0} dummyend {1} vnp {2} lli {3}"
                                      , DummyStart.anchoredPosition.y
                                      , DummyEnd.anchoredPosition.y
                                      , sr.verticalNormalizedPosition
                                      , "(" + string.Join(", ", details.ToArray()) + ")"
                                      ));
            }
        }
        public PatchMechlabLimitItems(MechLabPanel instance)
        {
            try {
                var sw = new Stopwatch();
                sw.Start();
                this.instance        = instance;
                this.inventoryWidget = new Traverse(instance).Field("inventoryWidget")
                                       .GetValue <MechLabInventoryWidget>()
                                       .LogIfNull("inventoryWidget is null");

                LogDebug($"StorageInventory contains {instance.storageInventory.Count}");

                if (instance.IsSimGame)
                {
                    new Traverse(instance).Field("originalStorageInventory").SetValue(instance.storageInventory.LogIfNull("storageInventory is null"));
                }

                LogDebug($"Mechbay Patch initialized :simGame? {instance.IsSimGame}");

                List <ListElementController_BASE_NotListView> BuildRawInventory()
                => instance.storageInventory.Select <MechComponentRef, ListElementController_BASE_NotListView>(componentRef => {
                    componentRef.LogIfNull("componentRef is null");
                    componentRef.DataManager = instance.dataManager.LogIfNull("(MechLabPanel instance).dataManager is null");
                    componentRef.RefreshComponentDef();
                    componentRef.Def.LogIfNull("componentRef.Def is null");
                    var count = (!instance.IsSimGame
                                          ? int.MinValue
                                          : instance.sim.GetItemCount(componentRef.Def.Description, componentRef.Def.GetType(), instance.sim.GetItemCountDamageType(componentRef)));

                    if (componentRef.ComponentDefType == ComponentType.Weapon)
                    {
                        ListElementController_InventoryWeapon_NotListView controller = new ListElementController_InventoryWeapon_NotListView();
                        controller.InitAndFillInSpecificWidget(componentRef, null, instance.dataManager, null, count, false);
                        return(controller);
                    }
                    else
                    {
                        ListElementController_InventoryGear_NotListView controller = new ListElementController_InventoryGear_NotListView();
                        controller.InitAndFillInSpecificWidget(componentRef, null, instance.dataManager, null, count, false);
                        return(controller);
                    }
                }).ToList();

                /* Build a list of data only for all components. */
                rawInventory = Sort(BuildRawInventory());

                InventoryItemElement_NotListView mkiie(bool nonexistant)
                {
                    var nlv = instance.dataManager.PooledInstantiate(ListElementController_BASE_NotListView.INVENTORY_ELEMENT_PREFAB_NotListView
                                                                     , BattleTechResourceType.UIModulePrefabs, null, null, null)
                              .LogIfNull("Unable to instantiate INVENTORY_ELEMENT_PREFAB_NotListView")
                              .GetComponent <InventoryItemElement_NotListView>()
                              .LogIfNull("Inventory_Element_prefab does not contain a NLV");

                    nlv.gameObject.IsDestroyedError("NLV gameObject has been destroyed");
                    nlv.gameObject.LogIfNull("NLV gameObject has been destroyed");
                    if (!nonexistant)
                    {
                        nlv.SetRadioParent(new Traverse(inventoryWidget).Field("inventoryRadioSet").GetValue <HBSRadioSet>().LogIfNull("inventoryRadioSet is null"));
                        nlv.gameObject.transform.SetParent(new Traverse(inventoryWidget).Field("listParent").GetValue <UnityEngine.Transform>().LogIfNull("listParent is null"), false);
                        nlv.gameObject.transform.localScale = UnityEngine.Vector3.one;
                    }
                    return(nlv);
                };

                iieTmp = mkiie(true);

                /* Allocate very few visual elements, as this is extremely slow for both allocation and deallocation.
                 * It's the difference between a couple of milliseconds and several seconds for many unique items in inventory
                 * This is the core of the fix, the rest is just to make it work within HBS's existing code.
                 */
                List <InventoryItemElement_NotListView> make_ielCache()
                => Enumerable.Repeat <Func <InventoryItemElement_NotListView> >(() => mkiie(false), itemLimit)
                .Select(thunk => thunk())
                .ToList();

                ielCache = make_ielCache();

                var li = new Traverse(inventoryWidget).Field("localInventory").GetValue <List <InventoryItemElement_NotListView> >();
                ielCache.ForEach(iw => li.Add(iw));
                // End

                var lp = new Traverse(inventoryWidget).Field("listParent").GetValue <UnityEngine.Transform>();

                // DummyStart&End are blank rects stored at the beginning and end of the list so that unity knows how big the scrollrect should be
                // "placeholders"
                if (DummyStart == null)
                {
                    DummyStart = new UnityEngine.GameObject().AddComponent <UnityEngine.RectTransform>();
                }
                if (DummyEnd == null)
                {
                    DummyEnd = new UnityEngine.GameObject().AddComponent <UnityEngine.RectTransform>();
                }

                DummyStart.SetParent(lp, false);
                DummyEnd.SetParent(lp, false);
                LogDebug(string.Format("[LimitItems] inventory cached in {0} ms", sw.Elapsed.TotalMilliseconds));

                FilterChanged();
            } catch (Exception e) {
                LogException(e);
            }
        }