/// <summary> /// Updates the available pickups. /// </summary> /// <param name="fetch">The fetchables to update.</param> /// <param name="fetcher">The Duplicant gathering the items.</param> /// <param name="navigator">The navigator for that Duplicant.</param> /// <param name="cellCosts">A location to store the cell costs.</param> internal void UpdatePickups(FetchablesByPrefabId fetch, Navigator navigator, GameObject fetcher, IDictionary <int, int> cellCosts) { var pickups = fetch.finalPickups; if (pickups != null) { if (!outstanding.TryGetValue(fetch.prefabId, out FetchData data)) { data = null; } pickups.Clear(); GetFetchList(fetch, navigator, fetcher, cellCosts); if (pickups.Count > 1) { if (data != null) { pickups.Sort(data); } else { pickups.Sort(FetchData.Default); } // Condense down using the stock game logic CondensePickups(pickups); } if (data != null) { data.NeedsScan = false; } } }
/// <summary> /// Gets the list of fetchable pickups. /// </summary> /// <param name="fetch">The fetchables to update.</param> /// <param name="fetcher">The Duplicant gathering the items.</param> /// <param name="navigator">The navigator for that Duplicant.</param> /// <param name="cellCosts">A location to store the cell costs.</param> private void GetFetchList(FetchablesByPrefabId fetch, Navigator navigator, GameObject fetcher, IDictionary <int, int> cellCosts) { cellCosts.Clear(); var pickups = fetch.finalPickups; foreach (var fetchable in fetch.fetchables.GetDataList()) { var pickupable = fetchable.pickupable; if (pickupable.CouldBePickedUpByMinion(fetcher)) { // Optimize if many pickupables are in the same cell int cell = pickupable.cachedCell; if (!cellCosts.TryGetValue(cell, out int cost)) { cost = pickupable.GetNavigationCost(navigator, cell); cellCosts.Add(cell, cost); } if (cost >= 0) { // This pickup is reachable pickups.Add(new Pickup { pickupable = pickupable, tagBitsHash = fetchable.tagBitsHash, PathCost = (ushort)Math.Min(cost, ushort.MaxValue), masterPriority = fetchable.masterPriority, freshness = fetchable.freshness, foodQuality = fetchable.foodQuality }); } } } }
/// <summary> /// Applied before UpdatePickups runs. /// </summary> internal static bool Prefix(FetchManager.FetchablesByPrefabId __instance, Navigator worker_navigator, GameObject worker_go) { UpdatePickups(__instance, worker_navigator, worker_go); // Prevent the original method from running return(false); }
/// <summary> /// Applied before UpdatePickups runs. /// </summary> internal static bool Prefix(FetchManager.FetchablesByPrefabId __instance, Navigator worker_navigator, GameObject worker_go, Dictionary <int, int> ___cellCosts) { var inst = EfficientFetchManager.Instance; bool cont = true; if (inst != null && options.MinimumAmountPercent > 0) { try { inst.UpdatePickups(__instance, worker_navigator, worker_go, ___cellCosts); cont = false; } catch (Exception e) { // Crashing will bring down simdll with no stack trace PUtil.LogException(e); } } return(cont); }
/// <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); }