Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Interval tree refactorings. #73846

Merged
merged 20 commits into from
Jun 4, 2024
Merged

Conversation

CyrusNajmabadi
Copy link
Member

@CyrusNajmabadi CyrusNajmabadi commented Jun 4, 2024

These are some small-scale refactorings to the interval tree refactoring to prepare for a larger piece of work coming next.

The future change is that we want to provide a new impl of the interval tree that is much faster and more space efficient than the existing binary-tree form for clients (like tagging) that know all the intervals up front and which can produce the tree in one go. That tree will actually be stored directly in a flat, immutable, array, in a much more compact form as it doesn't have to ever change.

The changes in this pr are as follows:

  1. Many features were updated to compute their spans up front, and then create the interval tree once at the end. Not all features could do this though, as we have a small handful of features that genuinely make use of the mutable nature of our binary-interval-trees to build up state incrementally that they query while building.
  2. The core interval tree necessary functionality was extracted out into an interface.
  3. Much of the operation on that type were extracted out to run on that interface instead, so that we can have the same rich surface area with the different 'flat' impl.
  4. Our existing trees were renamed to have 'Binary' in the name to indicate to clients what form they are getting.

@dotnet-issue-labeler dotnet-issue-labeler bot added Area-IDE untriaged Issues and PRs which have not yet been triaged by a lead labels Jun 4, 2024
@@ -27,7 +27,7 @@ internal sealed partial class TagSpanIntervalTree<TTag>(SpanTrackingMode spanTra
public static readonly TagSpanIntervalTree<TTag> Empty = new(SpanTrackingMode.EdgeInclusive);

private readonly SpanTrackingMode _spanTrackingMode = spanTrackingMode;
private readonly IntervalTree<TagSpan<TTag>> _tree = IntervalTree<TagSpan<TTag>>.Empty;
private readonly BinaryIntervalTree<TagSpan<TTag>> _tree = BinaryIntervalTree<TagSpan<TTag>>.Empty;
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

renaming to add 'Binary' for clarity here. The upcoming interval tree will probably be called FlatArrayIntervalTree or something like that.

@@ -57,10 +57,10 @@ private static TagSpan<TTag> GetTranslatedTagSpan(TagSpan<TTag> originalTagSpan,
: new(GetTranslatedSpan(originalTagSpan, textSnapshot, trackingMode), originalTagSpan.Tag);

public bool HasSpanThatContains(SnapshotPoint point)
=> _tree.HasIntervalThatContains(point.Position, length: 0, new IntervalIntrospector(point.Snapshot, _spanTrackingMode));
=> _tree.Algorithms.HasIntervalThatContains(point.Position, length: 0, new IntervalIntrospector(point.Snapshot, _spanTrackingMode));
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

these helpers could not be extensions for two reasons:

  1. the types themselves have some existing intance methods that have hte same name. and we never look up extensions if we find a matching instance name (even if it fails).
  2. The extensions were on a TIntervalTree so that they can operate without calling through an interface once instantiated. but the inference required there is beyond what C# can do today. So i used the pattern of asking the instance for a wrapper struct that itself then exposes the operations. The property handles the initial part of generic inference, meaning that the only inference has to happen on the TIntrospector type, which works fine.

var interpolationInteriorSpans = new TextSpanIntervalTree();
var restrictedSpans = new TextSpanIntervalTree();
var interpolationInteriorSpans = new SegmentedList<TextSpan>();
var restrictedSpans = new SegmentedList<TextSpan>();
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

moving to tree construction at end, instead of as we go along.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

tree construction at end. this will move to the FlatArrayIntervalTree in next pr.

// models given any position in the file with any particular text snapshot.
using var _ = SegmentedListPool.GetPooledList<DocumentSymbolDataViewModel>(out var models);
AddAllModels(newViewModelItems, models);
var intervalTree = BinaryIntervalTree.Create(new IntervalIntrospector(), models);
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

tree construction at end. this will move to the FlatArrayIntervalTree in next pr.

/// <summary>
/// Mutable. Must only be accessed from the UI thread.
/// </summary>
private SimpleBinaryIntervalTree<GlyphData, GlyphDataIntrospector> _glyphDataTree;
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

tree construction at end. this will move to the FlatArrayIntervalTree in next pr.

finalParts.Add(part);
}
}

var semanticPartsTree = new SimpleBinaryIntervalTree<TClassifiedSpan, TClassifiedSpanIntervalIntrospector>(default, values: semanticSpans);
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

tree construction at end. this will move to the FlatArrayIntervalTree in next pr.

/// example checking if an interval 'contains', 'intersects', or 'overlaps' with a requested span.
/// </summary>
internal delegate bool TestInterval<T, TIntrospector>(T value, int start, int length, in TIntrospector introspector)
where TIntrospector : struct, IIntervalIntrospector<T>;
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

new core types taht power the BinaryIntervalTree, and will power the upcoming FlatArrayIntervalTree.

/// involved here is too complex for C# to handle (specifically using a <c>TIntervalTree</c> type), which would make
/// ergonomics extremely painful as the callsites would have to pass three type arguments along explicitly.
/// </summary>
internal readonly struct IntervalTreeAlgorithms<T, TIntervalTree>(TIntervalTree tree) where TIntervalTree : IIntervalTree<T>
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

all the algorithms that will work fine on any interval tree impl (binary, or flat array). extracted out from the Binary version.

/// <summary>
/// Provides access to lots of common algorithms on this interval tree.
/// </summary>
public IntervalTreeAlgorithms<T, BinaryIntervalTree<T>> Algorithms => new(this);
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is how we get type inference to work. a user just calls .Algorithms, and the T and TIntervalTree types are already solved for them on the Algorithms wrapper they get back.

@@ -101,102 +104,7 @@ public static IntervalTree<T> CreateFromSorted<TIntrospector>(in TIntrospector i
return node;
}

protected static bool Contains<TIntrospector>(T value, int start, int length, in TIntrospector introspector)
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

moved to the Algorithms type.

private int FillWithIntervalsThatMatch<TIntrospector>(
int start, int length, TestInterval<TIntrospector> testInterval,
int IIntervalTree<T>.FillWithIntervalsThatMatch<TIntrospector>(
int start, int length, TestInterval<T, TIntrospector> testInterval,
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

switched to explicit impl as nothing should call this directly, just the algorithms type.

int FillWithIntervalsThatMatch<TIntrospector>(
int start, int length, TestInterval<T, TIntrospector> testInterval,
ref TemporaryArray<T> builder, in TIntrospector introspector,
bool stopAfterFirst)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

bool stopAfterFirst

maybe use a max find count?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

keeping the same logic for now. will change after flag goes in.

@CyrusNajmabadi CyrusNajmabadi marked this pull request as ready for review June 4, 2024 21:54
@CyrusNajmabadi CyrusNajmabadi requested a review from a team as a code owner June 4, 2024 21:54
@@ -130,7 +134,7 @@ public void SetSnapshotAndUpdate(ITextSnapshot snapshot, IList<ITextViewLine> ne
{
// Go through all the existing visuals and invalidate or transform as appropriate.
var allGlyphData = _glyphDataTree.ToImmutableArray();
_glyphDataTree.ClearInPlace();
_glyphDataTree = new(new GlyphDataIntrospector(), values: null);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

new GlyphDataIntrospector(

Does this object hold state? If not, would it be worth only having one of these?

Copy link
Contributor

@ToddGrun ToddGrun left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

:shipit:

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Area-IDE untriaged Issues and PRs which have not yet been triaged by a lead
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants