/// <summary> /// Condenses the pickups. /// </summary> /// <param name="pickups">The pickups to condense down.</param> private void CondensePickups(List <Pickup> pickups) { int n = pickups.Count; Pickup prevPickup = pickups[0]; var tagBits = new TagBits(ref FetchManager.disallowedTagMask); prevPickup.pickupable.KPrefabID.AndTagBits(ref tagBits); int hash = prevPickup.tagBitsHash, last = n, next = 0; for (int i = 1; i < n; i++) { bool del = false; var pickup = pickups[i]; var newTagBits = default(TagBits); if (prevPickup.masterPriority == pickup.masterPriority) { newTagBits = new TagBits(ref FetchManager.disallowedTagMask); pickup.pickupable.KPrefabID.AndTagBits(ref newTagBits); if (pickup.tagBitsHash == hash && newTagBits.AreEqual(ref tagBits)) { // Identical to the previous item del = true; } } if (del) { // Skip last--; } else { // Keep and move down next++; prevPickup = pickup; tagBits = newTagBits; hash = pickup.tagBitsHash; if (i > next) { pickups[next] = pickup; } } } pickups.RemoveRange(last, n - last); }
/// <summary> /// The better and more optimized UpdatePickups. Aggregate runtime on a test world /// dropped from ~60 ms/1000 ms to ~45 ms/1000 ms. /// </summary> private static void UpdatePickups(FetchManager.FetchablesByPrefabId targets, Navigator navigator, GameObject worker) { var canBePickedUp = DictionaryPool <PickupTagKey, FetchManager.Pickup, FetchManager> .Allocate(); var pathCosts = DictionaryPool <int, int, FetchManager> .Allocate(); var finalPickups = targets.finalPickups; // Will reflect the changes from Waste Not, Want Not and No Manual Delivery var comparer = PICKUP_COMPARER.Get(null); foreach (var fetchable in targets.fetchables.GetDataList()) { var target = fetchable.pickupable; int cell = target.cachedCell; if (target.CouldBePickedUpByMinion(worker)) { // Look for cell cost, share costs across multiple queries to a cell if (!pathCosts.TryGetValue(cell, out int cost)) { pathCosts.Add(cell, cost = target.GetNavigationCost(navigator, cell)); } // Exclude unreachable items if (cost >= 0) { int hash = fetchable.tagBitsHash; var key = new PickupTagKey(hash, target.KPrefabID); var candidate = new FetchManager.Pickup { pickupable = target, tagBitsHash = hash, PathCost = (ushort)cost, masterPriority = fetchable.masterPriority, freshness = fetchable. freshness, foodQuality = fetchable.foodQuality }; if (canBePickedUp.TryGetValue(key, out FetchManager.Pickup current)) { // Is the new one better? int result = comparer.Compare(current, candidate); if (result > 0 || (result == 0 && candidate.pickupable. UnreservedAmount > current.pickupable.UnreservedAmount)) { canBePickedUp[key] = candidate; } } else { canBePickedUp.Add(key, candidate); } } } } // Copy the remaining pickups to the list, there are now way fewer because only // one was kept per possible tag bits (with the highest priority, best path cost, // etc) finalPickups.Clear(); foreach (var pair in canBePickedUp) { finalPickups.Add(pair.Value); } pathCosts.Recycle(); canBePickedUp.Recycle(); }
/// <summary> /// Applied before UpdatePickups runs. A more optimized UpdatePickups whose aggregate /// runtime on a test world dropped from ~60 ms/1000 ms to ~45 ms/1000 ms. /// </summary> internal static bool BeforeUpdatePickups(FetchManager.FetchablesByPrefabId __instance, Navigator worker_navigator, GameObject worker_go) { var canBePickedUp = Allocate(); var pathCosts = DictionaryPool <int, int, FetchManager> .Allocate(); var finalPickups = __instance.finalPickups; // Will reflect the changes from Waste Not, Want Not and No Manual Delivery var comparer = FetchManager.ComparerIncludingPriority; bool needThreadSafe = FastTrackOptions.Instance.PickupOpts; var fetchables = __instance.fetchables.GetDataList(); int n = fetchables.Count; for (int i = 0; i < n; i++) { var fetchable = fetchables[i]; var target = fetchable.pickupable; int cell = target.cachedCell; if (target.CouldBePickedUpByMinion(worker_go)) { // Look for cell cost, share costs across multiple queries to a cell // If this is being run synchronous, no issue, otherwise the GSP patch will // avoid races on the scene partitioner if (!pathCosts.TryGetValue(cell, out int cost)) { if (needThreadSafe) { worker_navigator.GetNavigationCostNU(target, cell, out cost); } else { cost = worker_navigator.GetNavigationCost(target); } pathCosts.Add(cell, cost); } // Exclude unreachable items if (cost >= 0) { int hash = fetchable.tagBitsHash; var key = new PickupTagKey(hash, target.KPrefabID); var candidate = new FetchManager.Pickup { pickupable = target, tagBitsHash = hash, PathCost = (ushort)cost, masterPriority = fetchable.masterPriority, freshness = fetchable. freshness, foodQuality = fetchable.foodQuality }; if (canBePickedUp.TryGetValue(key, out FetchManager.Pickup current)) { // Is the new one better? int result = comparer.Compare(candidate, current); if (result < 0 || (result == 0 && candidate.pickupable. UnreservedAmount > current.pickupable.UnreservedAmount)) { canBePickedUp[key] = candidate; } } else { canBePickedUp.Add(key, candidate); } } } } // Copy the remaining pickups to the list, there are now way fewer because only // one was kept per possible tag bits (with the highest priority, best path cost, // etc) finalPickups.Clear(); foreach (var pickup in canBePickedUp.Values) { finalPickups.Add(pickup); } pathCosts.Recycle(); Recycle(canBePickedUp); // Prevent the original method from running return(false); }