/// <summary> /// Finds the leaf quadrant which contains <paramref name="location"/> by /// traversing downward starting at <paramref name="initialQuadrant"/> using /// upgradeable locks. /// </summary> /// <returns>The leaf quadrant containing the location. The current thread /// will hold an upgradable lock on this quadrant.</returns> /// <param name="initialQuadrant"> /// The quadrant from which to start downward traversal. This must contain /// <paramref name="location"/> and the current thread should hold an /// upgradeable lock on it. This lock will be released unless this quadrant /// happens to be the return value. /// </param> /// <param name="location">The location for which to find a leaf quadrant.</param> Quadrant FindAndLockTargetLeafQuadrant(Quadrant initialQuadrant, GeoCoordinates location) { Debug.Assert(initialQuadrant.Contains(location)); Debug.Assert(initialQuadrant.Lock.ThreadOwnsUpgradeableLock); Quadrant targetQuadrant = initialQuadrant; while (!targetQuadrant.IsLeaf) { Quadrant next = targetQuadrant.GetChildContaining(location); next.Lock.EnterUpgradeableLock(); targetQuadrant.Lock.ExitUpgradeableLock(); targetQuadrant = next; } Debug.Assert(targetQuadrant.Contains(location)); return(targetQuadrant); }
/// <summary> /// Moves the element to a new position. This will return false and do /// nothing if another thread is currently adjusting this element. /// </summary> /// <returns><c>true</c>, if element was moved, <c>false</c> otherwise.</returns> /// <param name="elementRef">Element.</param> /// <param name="newCoordinates">New coordinates.</param> public bool MoveElement(IElement elementRef, GeoCoordinates newCoordinates) { Element element = (Element)elementRef; if (element.IsMarkedForRemoval) { return(false); } if (!element.TrySetBusy()) { return(false); } // This check has to happen after TrySetBusy() to avoid a race // condition with RemoveElement(). After TrySetBusy() succeeds, // no other move or remove operation can run on this element, // so we are guaranteed not to move the element after it has been // removed. If the element will be removed, we just abort the move. if (element.IsMarkedForRemoval) { element.SetNotBusy(); return(false); } while (true) { Quadrant originalQuadrant = GetAndLockQuadrantForElement(element, QuadrantLock.LockType.Shared); // Catch tricky issues early. Debug.Assert(originalQuadrant.Lock.ThreadOwnsSharedLock); Debug.Assert(!originalQuadrant.IsDisconnected); Debug.Assert(originalQuadrant.IsLeaf); if (originalQuadrant.Contains(newCoordinates)) { // Case (1): new position is inside the old quadrant. Just change x and y. element.Coordinates = newCoordinates; originalQuadrant.Lock.ExitSharedLock(); break; } // Case (2): new position is inside a new quadrant. Need to move // to a new quadrant. // The root node should contain all elements, so the above if statement // should prevent this assert. Debug.Assert(originalQuadrant != rootNode); // We have to lock the parent to avoid deadlocking with a join // operation if the new quadrant is also a child of the parent. if (!SwitchSharedLockToExclusiveLockAndLockParent(originalQuadrant)) { // If in between releasing the shared lock and acquiring the // exclusive lock the quadrant became disconnected (due to // a join on its parent) or became subdivided, we have to // restart the move. continue; } Debug.Assert(originalQuadrant.Lock.ThreadOwnsExlusiveLock); Debug.Assert(originalQuadrant.Parent.Lock.ThreadOwnsSharedLock); // If originalQuadrant was not subdivided or joined (or was // subdivided and joined an equal number of times), then element // could not have been moved because we set it to busy. Debug.Assert(originalQuadrant.Data.Contains(element)); // Traverse upward to search for the nearest ancestor that contains // the new position. No locking is necessary because ancestors // cannot be subdivided since they're not leaves, and they cannot // be joined because we hold a lock on a child node. Quadrant closestContainingAncestor = originalQuadrant.Parent; while (!closestContainingAncestor.Contains(newCoordinates)) { closestContainingAncestor = closestContainingAncestor.Parent; // This only happens if rootNode doesn't contain the new // position, but that cannot happen. Debug.Assert(closestContainingAncestor != null); } // Traverse downward to target quadrant. Use locking like in an insert operation. // It cannot be a leaf node because it is an ancestor node. Debug.Assert(!closestContainingAncestor.IsLeaf); // Find the leaf quadrant that contains the new coordinates. Quadrant targetQuadrant = closestContainingAncestor.GetChildContaining(newCoordinates); targetQuadrant.Lock.EnterUpgradeableLock(); targetQuadrant = FindAndLockTargetLeafQuadrant(targetQuadrant, newCoordinates); targetQuadrant.Lock.EnterExclusiveLock(); // Change the element's coordinates and move it to the new quadrant. element.Coordinates = newCoordinates; originalQuadrant.Data.Remove(element); targetQuadrant.Data.Add(element); elementsToNodes[element] = targetQuadrant; // If the target quadrant now contains too much data, mark it as a candidate // for subdivision. if (targetQuadrant.Data.Count > maxLeafCapacity && targetQuadrant.SubdivisionLevel < maxSubdivisionLevel) { candidatesForSubdivide.TryAdd(targetQuadrant); } // If the original quadrant is now nearly empty, mark its parent // as a candidate for a join operation. if (originalQuadrant.Parent != null && originalQuadrant.Data.Count < minLeafCapacity) { candidatesForJoin.TryAdd(originalQuadrant.Parent); } targetQuadrant.Lock.ExitExclusiveLock(); targetQuadrant.Lock.ExitUpgradeableLock(); // Release the locks on the original quadrant and its parent, // which were acquired earlier. originalQuadrant.Lock.ExitExclusiveLock(); originalQuadrant.Parent.Lock.ExitSharedLock(); break; } element.SetNotBusy(); return(true); }