/// <summary>Async method for moving the graph</summary> IEnumerator UpdateGraphCoroutine() { // Find the direction that we want to move the graph in. // Calcuculate this in graph space (where a distance of one is the size of one node) Vector3 dir = PointToGraphSpace(target.position) - PointToGraphSpace(graph.center); // Snap to a whole number of nodes dir.x = Mathf.Round(dir.x); dir.z = Mathf.Round(dir.z); dir.y = 0; // Nothing do to if (dir == Vector3.zero) { yield break; } // Number of nodes to offset in each direction Int2 offset = new Int2(-Mathf.RoundToInt(dir.x), -Mathf.RoundToInt(dir.z)); // Move the center (this is in world units, so we need to convert it back from graph space) graph.center += graph.transform.TransformVector(dir); graph.UpdateTransform(); // Cache some variables for easier access int width = graph.width; int depth = graph.depth; GridNodeBase[] nodes = graph.nodes; // Layers are required when handling LayeredGridGraphs int layers = graph.LayerCount; // Create a temporary buffer required for the calculations Memory.Realloc(ref buffer, width * depth); // Check if we have moved less than a whole graph width all directions // If we have moved more than this we can just as well recalculate the whole graph if (Mathf.Abs(offset.x) <= width && Mathf.Abs(offset.y) <= depth) { // Note: the upper limits are treated as exclusive limits (xmax, ymax) IntRect recalculateRect = new IntRect(0, 0, offset.x, offset.y); // If offset.x < 0, adjust the rect if (recalculateRect.xmin > recalculateRect.xmax) { int tmp2 = recalculateRect.xmax; recalculateRect.xmax = width + recalculateRect.xmin; recalculateRect.xmin = width + tmp2; } // If offset.y < 0, adjust the rect if (recalculateRect.ymin > recalculateRect.ymax) { int tmp2 = recalculateRect.ymax; recalculateRect.ymax = depth + recalculateRect.ymin; recalculateRect.ymin = depth + tmp2; } // Offset each node by the #offset variable // nodes which would end up outside the graph // will wrap around to the other side of it for (int l = 0; l < layers; l++) { int layerOffset = l * width * depth; for (int z = 0; z < depth; z++) { int pz = z * width; int tz = ((z + offset.y + depth) % depth) * width; for (int x = 0; x < width; x++) { buffer[tz + ((x + offset.x + width) % width)] = nodes[layerOffset + pz + x]; } } yield return(null); // Copy the nodes back to the graph // and set the correct indices for (int z = 0; z < depth; z++) { int pz = z * width; for (int x = 0; x < width; x++) { int newIndex = pz + x; var node = buffer[newIndex]; if (node != null) { node.NodeInGridIndex = newIndex; } nodes[layerOffset + newIndex] = node; } // Calculate the limits for the region that has been wrapped // to the other side of the graph. // This part is made up of a couple of rows and a couple of columns // So depending on the row we are in (z) we should either iterate over the whole row // or only the relevant columns. int xmin, xmax; if (z >= recalculateRect.ymin && z < recalculateRect.ymax) { xmin = 0; xmax = width; } else { xmin = recalculateRect.xmin; xmax = recalculateRect.xmax; } for (int x = xmin; x < xmax; x++) { var node = buffer[pz + x]; if (node != null) { // Clear connections on all nodes that are wrapped and placed on the other side of the graph. // This is both to clear any custom connections (which do not really make sense after moving the node) // and to prevent possible exceptions when the node will later (possibly) be destroyed because it was // not needed anymore (only for layered grid graphs). node.ClearConnections(false); } } } yield return(null); } // Recalculate the nodes // Take a look at the image in the docs for the UpdateGraph method // to see which nodes are being recalculated. var dependencyTracker = ObjectPool <Jobs.JobDependencyTracker> .Claim(); var updateNodesJob1 = graph.UpdateRegion(new IntRect(0, recalculateRect.ymin, width, recalculateRect.ymax), dependencyTracker, Unity.Collections.Allocator.Persistent); // Note: It is important the the main thread work is completed before the second update begins. // Otherwise if we give updateNodesJob1 as a dependency the code might call .Complete on a the handle and block indefinitely // since it requires work to complete in the main thread. foreach (var _ in updateNodesJob1.CompleteTimeSliced(MaxMillisPerFrame)) { yield return(null); } IntRect job2rect; if (recalculateRect.ymin == 0) { job2rect = new IntRect(recalculateRect.xmin, recalculateRect.ymax, recalculateRect.xmax, depth); } else if (recalculateRect.ymax == depth) { job2rect = new IntRect(recalculateRect.xmin, 0, recalculateRect.xmax, recalculateRect.ymin - 1); } else { throw new System.Exception("Should not happen"); } var updateNodesJob2 = graph.UpdateRegion(job2rect, dependencyTracker, Unity.Collections.Allocator.Persistent); foreach (var _ in updateNodesJob2.CompleteTimeSliced(MaxMillisPerFrame)) { yield return(null); } ObjectPool <Jobs.JobDependencyTracker> .Release(ref dependencyTracker); // Calculate all connections for the nodes along the boundary // of the graph, these always need to be updated /// <summary>TODO: Optimize to not traverse all nodes in the graph, only those at the edges</summary> for (int z = 0; z < depth; z++) { for (int x = 0; x < width; x++) { if (x == 0 || z == 0 || x == width - 1 || z == depth - 1) { graph.CalculateConnections(x, z); } } } yield return(null); } else { var dependencyTracker = ObjectPool <Jobs.JobDependencyTracker> .Claim(); var updateNodesJob = graph.UpdateRegion(new IntRect(0, 0, width, depth), dependencyTracker, Unity.Collections.Allocator.Persistent); foreach (var _ in updateNodesJob.CompleteTimeSliced(MaxMillisPerFrame)) { yield return(null); } ObjectPool <Jobs.JobDependencyTracker> .Release(ref dependencyTracker); } }