/// <summary> /// Gets/Sets a single character. /// Runs in O(lg N) for random access. Sequential read-only access benefits from a special optimization and runs in amortized O(1). /// </summary> /// <exception cref="ArgumentOutOfRangeException">Offset is outside the valid range (0 to Length-1).</exception> /// <remarks> /// The getter counts as a read access and may be called concurrently to other read accesses. /// </remarks> public T this[int index] { get { // use unsigned integers - this way negative values for index overflow and can be tested for with the same check if (unchecked ((uint)index >= (uint)this.Length)) { throw new ArgumentOutOfRangeException("index", index, "0 <= index < " + this.Length.ToString(CultureInfo.InvariantCulture)); } RopeCacheEntry entry = FindNodeUsingCache(index).PeekOrDefault(); return(entry.node.contents[index - entry.nodeStartIndex]); } set { if (index < 0 || index >= this.Length) { throw new ArgumentOutOfRangeException("index", index, "0 <= index < " + this.Length.ToString(CultureInfo.InvariantCulture)); } root = root.SetElement(index, value); OnChanged(); /* Here's a try at implementing the setter using the cached node stack (UNTESTED code!). * However I don't use the code because it's complicated and doesn't integrate correctly with change notifications. * Instead, I'll use the much easier to understand recursive solution. * Oh, and it also doesn't work correctly with function nodes. * ImmutableStack<RopeCacheEntry> nodeStack = FindNodeUsingCache(offset); * RopeCacheEntry entry = nodeStack.Peek(); * if (!entry.node.isShared) { * entry.node.contents[offset - entry.nodeStartOffset] = value; * // missing: clear the caches except for the node stack cache (e.g. ToString() cache?) * } else { * RopeNode oldNode = entry.node; * RopeNode newNode = oldNode.Clone(); * newNode.contents[offset - entry.nodeStartOffset] = value; * for (nodeStack = nodeStack.Pop(); !nodeStack.IsEmpty; nodeStack = nodeStack.Pop()) { * RopeNode parentNode = nodeStack.Peek().node; * RopeNode newParentNode = parentNode.CloneIfShared(); * if (newParentNode.left == oldNode) { * newParentNode.left = newNode; * } else { * Debug.Assert(newParentNode.right == oldNode); * newParentNode.right = newNode; * } * if (parentNode == newParentNode) { * // we were able to change the existing node (it was not shared); * // there's no reason to go further upwards * ClearCacheOnModification(); * return; * } else { * oldNode = parentNode; * newNode = newParentNode; * } * } * // we reached the root of the rope. * Debug.Assert(root == oldNode); * root = newNode; * ClearCacheOnModification(); * }*/ } }
internal ImmutableStack <RopeCacheEntry> FindNodeUsingCache(int index) { Debug.Assert(index >= 0 && index < Length); // thread safety: fetch stack into local variable var stack = lastUsedNodeStack; var oldStack = stack; if (stack == null) { stack = ImmutableStack <RopeCacheEntry> .Empty.Push(new RopeCacheEntry(root, 0)); } while (!stack.PeekOrDefault().IsInside(index)) { stack = stack.Pop(); } while (true) { var entry = stack.PeekOrDefault(); // check if we've reached a leaf or function node if (entry.node.height == 0) { if (entry.node.contents == null) { // this is a function node - go down into its subtree entry = new RopeCacheEntry(entry.node.GetContentNode(), entry.nodeStartIndex); // entry is now guaranteed NOT to be another function node } if (entry.node.contents != null) { // this is a node containing actual content, so we're done break; } } // go down towards leaves if (index - entry.nodeStartIndex >= entry.node.left.length) { stack = stack.Push(new RopeCacheEntry(entry.node.right, entry.nodeStartIndex + entry.node.left.length)); } else { stack = stack.Push(new RopeCacheEntry(entry.node.left, entry.nodeStartIndex)); } } // write back stack to volatile cache variable // (in multithreaded access, it doesn't matter which of the threads wins - it's just a cache) if (oldStack != stack) { // no need to write when we the cache variable didn't change lastUsedNodeStack = stack; } // this method guarantees that it finds a leaf node Debug.Assert(stack.Peek().node.contents != null); return(stack); }
/// <summary> /// Gets/Sets a single character. /// Runs in O(lg N) for random access. Sequential read-only access benefits from a special optimization and runs in amortized O(1). /// </summary> /// <exception cref="ArgumentOutOfRangeException">Offset is outside the valid range (0 to Length-1).</exception> /// <remarks> /// The getter counts as a read access and may be called concurrently to other read accesses. /// </remarks> public T this[int index] { get { // use unsigned integers - this way negative values for index overflow and can be tested for with the same check if (unchecked ((uint)index >= (uint)Length)) { throw new ArgumentOutOfRangeException("index", index, "0 <= index < " + Length.ToString(CultureInfo.InvariantCulture)); } RopeCacheEntry entry = FindNodeUsingCache(index).PeekOrDefault(); return(entry.node.contents[index - entry.nodeStartIndex]); } set { if (index < 0 || index >= Length) { throw new ArgumentOutOfRangeException("index", index, "0 <= index < " + Length.ToString(CultureInfo.InvariantCulture)); } root = root.SetElement(index, value); OnChanged(); } }