public static async Task <Dictionary <Vector2Ushort, ZoneChunkFilledCellsCounter> > CalculateZoneChunks( IQuadTreeNode quadTree, int chunkSize, Func <Task> callbackYieldIfOutOfTime) { // this is a heavy method so we will try to yield every few nodes to reduce the load const int defaultCounterToYieldValue = 100; var counterToYield = defaultCounterToYieldValue; var dict = new Dictionary <Vector2Ushort, ZoneChunkFilledCellsCounter>(); if (false) { // slow algorithm (each tile added separately into the dictionary) ZoneChunkFilledCellsCounter lastCounter = new ZoneChunkFilledCellsCounter(Vector2Ushort.Max); foreach (var filledNode in quadTree.EnumerateFilledNodes()) { var size = filledNode.Size; var startPosition = filledNode.Position; var endPosition = startPosition + new Vector2Ushort(size, size); for (var y = startPosition.Y; y < endPosition.Y; y++) { for (var x = startPosition.X; x < endPosition.X; x++) { var chunkPosition = new Vector2Ushort((ushort)(x / chunkSize), (ushort)(y / chunkSize)); if (lastCounter.ChunkOffset == chunkPosition || dict.TryGetValue(chunkPosition, out lastCounter)) { lastCounter.Count++; } else { lastCounter = new ZoneChunkFilledCellsCounter(chunkPosition) { Count = 1 }; dict[chunkPosition] = lastCounter; } } } } } // fast algorithm (each quad tree node can instantly fill each chunk) foreach (var node in quadTree.EnumerateFilledNodes()) { await YieldIfOutOfTime(); var nodeStartPosition = node.Position; var startChunksOffset = new Vector2Ushort( (ushort)(chunkSize * (nodeStartPosition.X / chunkSize)), (ushort)(chunkSize * (nodeStartPosition.Y / chunkSize))); var nodeSize = node.Size; if (nodeSize == 1) { // short path var chunkPosition = new Vector2Ushort(startChunksOffset.X, startChunksOffset.Y); if (!dict.TryGetValue(chunkPosition, out var counter)) { counter = new ZoneChunkFilledCellsCounter(chunkPosition); dict[chunkPosition] = counter; } counter.Count++; continue; } // long path (for nodes > 1*1 size) var nodeEndPosition = nodeStartPosition.AddAndClamp(new Vector2Ushort(node.Size, node.Size)); var endChunksOffset = new Vector2Ushort( (ushort)(chunkSize * Math.Ceiling(nodeEndPosition.X / (double)chunkSize)), (ushort)(chunkSize * Math.Ceiling(nodeEndPosition.Y / (double)chunkSize))); for (var chunkY = startChunksOffset.Y; chunkY < endChunksOffset.Y; chunkY = (ushort)(chunkY + chunkSize)) { for (var chunkX = startChunksOffset.X; chunkX < endChunksOffset.X; chunkX = (ushort)(chunkX + chunkSize)) { var chunkPosition = new Vector2Ushort(chunkX, chunkY); var cellsCount = CalculateCellsCount(nodeStartPosition, nodeEndPosition, chunkX, chunkY, chunkSize); if (!dict.TryGetValue(chunkPosition, out var counter)) { counter = new ZoneChunkFilledCellsCounter(chunkPosition); dict[chunkPosition] = counter; } counter.Count += cellsCount; } } } return(dict); Task YieldIfOutOfTime() { if (--counterToYield > 0) { return(Task.CompletedTask); } counterToYield = defaultCounterToYieldValue; return(callbackYieldIfOutOfTime()); } }