private void FindSmallestNodeData( HashSet <TNode> keys, Dictionary <TNode, DynamicGraphNodeData <TNode, TCost, TEdge> > lookUp, out TNode smallestNode, out DynamicGraphNodeData <TNode, TCost, TEdge> smallestEdge ) { Contract.Requires(keys != null); Contract.Requires(keys.Count > 0); Contract.Requires(lookUp != null); Contract.Requires(lookUp.Count > 0); Contract.Requires(lookUp.Count >= keys.Count); Contract.Requires(Contract.ForAll(lookUp.Values, x => x != null)); Contract.Requires(Contract.ForAll(keys, key => key != null && lookUp.ContainsKey(key))); Contract.Ensures(Contract.ValueAtReturn(out smallestNode) != null); Contract.Ensures(Contract.ValueAtReturn(out smallestEdge) != null); using (var enumerator = keys.GetEnumerator()) { if (!enumerator.MoveNext()) { throw new ArgumentException("keys is empty", "keys"); } var currentNode = enumerator.Current; //var smallest = new KeyValuePair<TNode, DynamicGraphNodeData<TNode, TCost, TEdge>>(currentNode, lookUp[currentNode]); smallestNode = currentNode; smallestEdge = lookUp[currentNode]; Contract.Assume(smallestEdge != null); while (enumerator.MoveNext()) { currentNode = enumerator.Current; var currentData = lookUp[currentNode]; Contract.Assume(currentData != null); if (CostComparer.Compare(smallestEdge.Cost, currentData.Cost) > 0) { smallestNode = currentNode; smallestEdge = currentData; } } } if (null == smallestNode) { throw new ArgumentException("null keys are not allowed", "keys"); } if (null == smallestEdge) { throw new ArgumentException("null edge contained in edge look-up", "lookUp"); } }
/// <summary> /// Finds a graph path from the <paramref name="start"/> node to the <paramref name="target"/> node. /// </summary> /// <param name="start">The node to begin the path search from.</param> /// <param name="target">The target node of the search.</param> /// <returns>The shortest path from the start node to the target node if one exists.</returns> /// <exception cref="System.ArgumentException">Thrown if a node or edge encountered within the graph is <c>null</c>.</exception> public ReadOnlyCollection <DynamicGraphNodeData <TNode, TCost, TEdge> > FindPath(TNode start, TNode target) { if (null == start) { throw new ArgumentNullException("start"); } if (null == target) { throw new ArgumentNullException("target"); } Contract.EndContractBlock(); // initialize the look-ups var nodeDataCache = new Dictionary <TNode, DynamicGraphNodeData <TNode, TCost, TEdge> >(NodeComparer) { { start, new DynamicGraphNodeData <TNode, TCost, TEdge>(start, default(TCost), default(TEdge)) } }; var visitRequired = new HashSet <TNode>(NodeComparer) { start }; // NOTE: in order for a node to be in this collection it must have a corresponding key in the pathData dictionary. DynamicGraphNodeData <TNode, TCost, TEdge> nodeData; TNode currentNode; var bestCompleteCost = default(TCost); var completeRouteFound = false; // generate the dynamic path information and find the shortest path while (visitRequired.Count != 0) { DynamicGraphNodeData <TNode, TCost, TEdge> currentNodeData; Contract.Assume(nodeDataCache.Count >= visitRequired.Count); Contract.Assume(Contract.ForAll(visitRequired, k => k != null && nodeDataCache.ContainsKey(k))); FindSmallestNodeData(visitRequired, nodeDataCache, out currentNode, out currentNodeData); visitRequired.Remove(currentNode); // logic to see if we can short out of checking this node due to it being too long if (completeRouteFound) { if (CostComparer.Compare(bestCompleteCost, currentNodeData.Cost) <= 0) { continue; // this path is larger than or equal to the best found complete path } if (NodeComparer.Equals(currentNode, target)) { bestCompleteCost = currentNodeData.Cost; } } else if (NodeComparer.Equals(currentNode, target)) { bestCompleteCost = currentNodeData.Cost; completeRouteFound = true; } foreach (var neighborInfo in GetNeighborInfo(currentNode, currentNodeData.Cost)) { if (ReferenceEquals(null, neighborInfo) || ReferenceEquals(null, neighborInfo.Node)) { continue; } if (nodeDataCache.TryGetValue(neighborInfo.Node, out nodeData)) { Contract.Assume(nodeData != null); if (CostComparer.Compare(neighborInfo.Cost, nodeData.Cost) < 0) { nodeData.Node = currentNode; nodeData.Cost = neighborInfo.Cost; nodeData.Edge = neighborInfo.Edge; visitRequired.Add(neighborInfo.Node); } } else { nodeDataCache.Add( neighborInfo.Node, new DynamicGraphNodeData <TNode, TCost, TEdge>(currentNode, neighborInfo.Cost, neighborInfo.Edge)); visitRequired.Add(neighborInfo.Node); } } } // build the final result path if (nodeDataCache.TryGetValue(target, out nodeData)) { Contract.Assume(nodeData != null); var pathResult = new List <DynamicGraphNodeData <TNode, TCost, TEdge> > { new DynamicGraphNodeData <TNode, TCost, TEdge>(target, nodeData.Cost, nodeData.Edge) }; currentNode = nodeData.Node; if (null == currentNode) { return(null); } while (nodeDataCache.TryGetValue(currentNode, out nodeData)) { Contract.Assume(nodeData != null); pathResult.Add(new DynamicGraphNodeData <TNode, TCost, TEdge>(currentNode, nodeData.Cost, nodeData.Edge)); if (Equals(currentNode, start)) { break; } currentNode = nodeData.Node; if (null == currentNode) { return(null); } } pathResult.Reverse(); return(new ReadOnlyCollection <DynamicGraphNodeData <TNode, TCost, TEdge> >(pathResult)); } return(null); // no path was found }
public static void AssignTasksGreedy(List <Task> newGoals, List <CreatureAI> creatures, int maxPerGoal) { string trackingString = "AssignTasksGreedy(" + newGoals.Count + ")"; GamePerformance.Instance.StartTrackPerformance(trackingString); // We are going to keep track of the unassigned goal count // to avoid having to parse the list at the end of the loop. int goalsUnassigned = newGoals.Count; List <int> counts = new List <int>(goalsUnassigned); for (int i = 0; i < goalsUnassigned; i++) { counts.Add(0); } // Randomized list changed from the CreatureAI objects themselves to an index into the // List passed in. This is to avoid having to shift the masterCosts list around to match // each time we randomize. List <int> randomIndex = new List <int>(creatures.Count); for (int i = 0; i < creatures.Count; i++) { randomIndex.Add(i); } // We create the comparer outside of the loop. It gets reused for each sort. CostComparer toCompare = new CostComparer(); // One of the biggest issues with the old function was that it was recalculating the whole task list for // the creature each time through the loop, using one item and then throwing it away. Nothing changed // in how the calculation happened between each time so we will instead make a costs list for each creature // and keep them all. This not only avoids rebuilding the list but the sheer KeyValuePair object churn there already was. List <List <KeyValuePair <int, float> > > masterCosts = new List <List <KeyValuePair <int, float> > >(creatures.Count); // We will set this up in the next loop rather than make it's own loop. List <int> costsPositions = new List <int>(creatures.Count); for (int costIndex = 0; costIndex < creatures.Count; costIndex++) { List <KeyValuePair <int, float> > costs = new List <KeyValuePair <int, float> >(); CreatureAI creature = creatures[costIndex]; // We already were doing an index count to be able to make the KeyValuePair for costs // and foreach uses Enumeration which is slower. for (int i = 0; i < newGoals.Count; i++) { Task task = newGoals[i]; // We are checking for tasks the creature is already assigned up here to avoid having to check // every task in the newGoals list against every task in the newGoals list. The newGoals list // should reasonably not contain any task duplicates. if (creature.Tasks.Contains(task)) { continue; } float cost = 0; // We've swapped the order of the two checks to take advantage of a new ComputeCost that can act different // if we say we've already called IsFeasible first. This allows us to skip any calculations that are repeated in both. if (!task.IsFeasible(creature.Creature)) { cost += 1e10f; } cost += task.ComputeCost(creature.Creature, true); costs.Add(new KeyValuePair <int, float>(i, cost)); } // The sort lambda function has been replaced by an IComparer class. // This is faster but I mainly did it because VS can not Edit & Continue // any function with a Lambda function in it which was slowing down dev time. costs.Sort(toCompare); masterCosts.Add(costs); costsPositions.Add(0); } // We are going to precalculate the maximum iterations and count down // instead of up. int iters = goalsUnassigned * creatures.Count; while (goalsUnassigned > 0 && iters > 0) { randomIndex.Shuffle(); iters--; for (int creatureIndex = 0; creatureIndex < randomIndex.Count; creatureIndex++) { int randomCreature = randomIndex[creatureIndex]; CreatureAI creature = creatures[randomCreature]; List <KeyValuePair <int, float> > costs = masterCosts[randomCreature]; int costPosition = costsPositions[randomCreature]; // This loop starts with the previous spot we stopped. This avoids us having to constantly run a task we // know we have processed. for (int i = costPosition; i < costs.Count; i++) { // Incremented at the start in case we find a task and break. costPosition++; KeyValuePair <int, float> taskCost = costs[i]; // We've swapped the checks here. Tasks.Contains is far more expensive so being able to skip // if it's going to fail the maxPerGoal check anyways is very good. if (counts[taskCost.Key] < maxPerGoal)// && !creature.Tasks.Contains(newGoals[taskCost.Key])) { // We have to check to see if the task we are assigning is fully unassigned. If so // we reduce the goalsUnassigned count. If it's already assigned we skip it. if (counts[taskCost.Key] == 0) { goalsUnassigned--; } counts[taskCost.Key]++; creature.Tasks.Add(newGoals[taskCost.Key].Clone()); break; } } // We have to set the position we'll start the loop at the next time based on where we found // our task. costsPositions[randomCreature] = costPosition; // The loop at the end to see if all are unassigned is gone now, replaced by a countdown // variable: goalsUnassigned. } } GamePerformance.Instance.EndTrackPerformance(trackingString); }