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

made diagnostic tags to be removed faster and inserted slower. #8102

Merged
merged 3 commits into from
Jan 25, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ namespace Microsoft.CodeAnalysis.Editor.Implementation.Classification
{
internal partial class SyntacticClassificationTaggerProvider
{
private partial class Tagger : ITagger<IClassificationTag>, IDisposable
private class Tagger : ITagger<IClassificationTag>, IDisposable
{
private TagComputer _tagComputer;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,11 @@ event EventHandler ITaggerEventSource.UIUpdatesResumed { add { } remove { } }
void ITaggerEventSource.Connect() { }
void ITaggerEventSource.Disconnect() { }

// we will show new tags to users very slowly.
// don't confused this with data changed event which is for tag producer (which is set to NearImmediate).
// this delay is for letting editor know about newly added tags.
protected override TaggerDelay AddedTagNotificationDelay => TaggerDelay.OnIdle;

protected override ITaggerEventSource CreateEventSource(ITextView textViewOpt, ITextBuffer subjectBuffer)
{
// We act as a source of events ourselves. When the diagnostics service tells
Expand Down Expand Up @@ -138,7 +143,11 @@ internal void OnDiagnosticsUpdated(DiagnosticsUpdatedArgs e, SourceText sourceTe
_latestSourceText = sourceText;
_latestEditorSnapshot = editorSnapshot;
}
this.Changed?.Invoke(this, new TaggerEventArgs(TaggerDelay.Medium));

// unlike any other tagger, actual work to produce data is done by other service rather than tag provider itself.
// so we don't need to do any big delay for diagnostic tagger (producer) to reduce doing expensive work repeatedly. that is already
// taken cared by the external service (diagnostic service).
this.Changed?.Invoke(this, new TaggerEventArgs(TaggerDelay.NearImmediate));
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ private sealed partial class TagSource : ForegroundThreadAffinitizedObject

#endregion

public event Action<ICollection<KeyValuePair<ITextBuffer, NormalizedSnapshotSpanCollection>>> TagsChangedForBuffer;
public event Action<ICollection<KeyValuePair<ITextBuffer, DiffResult>>> TagsChangedForBuffer;

public event EventHandler Paused;
public event EventHandler Resumed;
Expand Down Expand Up @@ -112,6 +112,9 @@ public TagSource(
RecalculateTagsOnChanged(new TaggerEventArgs(TaggerDelay.Short));
}

public TaggerDelay AddedTagNotificationDelay => _dataSource.AddedTagNotificationDelay;
public TaggerDelay RemovedTagNotificationDelay => _dataSource.RemovedTagNotificationDelay;

private ITaggerEventSource CreateEventSource()
{
var eventSource = _dataSource.CreateEventSource(_textViewOpt, _subjectBuffer);
Expand Down Expand Up @@ -260,7 +263,7 @@ public void Disconnect()
_eventSource.Changed -= OnChanged;
}

private void RaiseTagsChanged(ITextBuffer buffer, NormalizedSnapshotSpanCollection difference)
private void RaiseTagsChanged(ITextBuffer buffer, DiffResult difference)
{
this.AssertIsForeground();
if (difference.Count == 0)
Expand All @@ -270,10 +273,10 @@ private void RaiseTagsChanged(ITextBuffer buffer, NormalizedSnapshotSpanCollecti
}

RaiseTagsChanged(SpecializedCollections.SingletonCollection(
new KeyValuePair<ITextBuffer, NormalizedSnapshotSpanCollection>(buffer, difference)));
new KeyValuePair<ITextBuffer, DiffResult>(buffer, difference)));
}

private void RaiseTagsChanged(ICollection<KeyValuePair<ITextBuffer, NormalizedSnapshotSpanCollection>> collection)
private void RaiseTagsChanged(ICollection<KeyValuePair<ITextBuffer, DiffResult>> collection)
{
TagsChangedForBuffer?.Invoke(collection);
}
Expand All @@ -296,13 +299,17 @@ private static T NextOrDefault<T>(IEnumerator<T> enumerator)
/// <summary>
/// Return all the spans that appear in only one of "latestSpans" or "previousSpans".
/// </summary>
private static IEnumerable<SnapshotSpan> Difference<T>(IEnumerable<ITagSpan<T>> latestSpans, IEnumerable<ITagSpan<T>> previousSpans, IEqualityComparer<T> comparer)
private static DiffResult Difference<T>(IEnumerable<ITagSpan<T>> latestSpans, IEnumerable<ITagSpan<T>> previousSpans, IEqualityComparer<T> comparer)
where T : ITag
{
var latestEnumerator = latestSpans.GetEnumerator();
var previousEnumerator = previousSpans.GetEnumerator();
try
using (var addedPool = SharedPools.Default<List<SnapshotSpan>>().GetPooledObject())
Copy link
Member

Choose a reason for hiding this comment

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

Does this intelligently free the list? If not, i'm sure there are methods for this. like AllocateAndFree and whatnot.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

ya, it calls those at dispose. and make sure pool object doesnt grow too big or left out data from previous usage.

using (var removedPool = SharedPools.Default<List<SnapshotSpan>>().GetPooledObject())
using (var latestEnumerator = latestSpans.GetEnumerator())
using (var previousEnumerator = previousSpans.GetEnumerator())
{
var added = addedPool.Object;
var removed = removedPool.Object;

var latest = NextOrDefault(latestEnumerator);
var previous = NextOrDefault(previousEnumerator);

Expand All @@ -313,12 +320,12 @@ private static IEnumerable<SnapshotSpan> Difference<T>(IEnumerable<ITagSpan<T>>

if (latestSpan.Start < previousSpan.Start)
{
yield return latestSpan;
added.Add(latestSpan);
latest = NextOrDefault(latestEnumerator);
}
else if (previousSpan.Start < latestSpan.Start)
{
yield return previousSpan;
removed.Add(previousSpan);
previous = NextOrDefault(previousEnumerator);
}
else
Expand All @@ -327,19 +334,19 @@ private static IEnumerable<SnapshotSpan> Difference<T>(IEnumerable<ITagSpan<T>>
// region to be conservative.
if (previousSpan.End > latestSpan.End)
{
yield return previousSpan;
removed.Add(previousSpan);
latest = NextOrDefault(latestEnumerator);
}
else if (latestSpan.End > previousSpan.End)
{
yield return latestSpan;
added.Add(latestSpan);
previous = NextOrDefault(previousEnumerator);
}
else
{
if (!comparer.Equals(latest.Tag, previous.Tag))
{
yield return latestSpan;
added.Add(latestSpan);
}

latest = NextOrDefault(latestEnumerator);
Expand All @@ -350,20 +357,17 @@ private static IEnumerable<SnapshotSpan> Difference<T>(IEnumerable<ITagSpan<T>>

while (latest != null)
{
yield return latest.Span;
added.Add(latest.Span);
latest = NextOrDefault(latestEnumerator);
}

while (previous != null)
{
yield return previous.Span;
removed.Add(previous.Span);
previous = NextOrDefault(previousEnumerator);
}
}
finally
{
latestEnumerator.Dispose();
previousEnumerator.Dispose();

return new DiffResult(added, removed);
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -90,10 +90,9 @@ private void RemoveAllTags()

var snapshot = _subjectBuffer.CurrentSnapshot;
var oldTagTree = GetTagTree(snapshot, oldTagTrees);
var newTagTree = GetTagTree(snapshot, this.CachedTagTrees);

var difference = ComputeDifference(snapshot, newTagTree, oldTagTree);
RaiseTagsChanged(snapshot.TextBuffer, difference);
// everything from old tree is removed.
RaiseTagsChanged(snapshot.TextBuffer, new DiffResult(added: null, removed: oldTagTree.GetSpans(snapshot).Select(s => s.Span)));
}

private void OnSubjectBufferChanged(object sender, TextContentChangedEventArgs e)
Expand Down Expand Up @@ -190,14 +189,15 @@ private void RemoveTagsThatIntersectEdit(TextContentChangedEventArgs e)

var snapshot = e.After;

var oldTagTrees = this.CachedTagTrees;
this.CachedTagTrees = oldTagTrees.SetItem(snapshot.TextBuffer, newTagTree);
this.CachedTagTrees = this.CachedTagTrees.SetItem(snapshot.TextBuffer, newTagTree);

// Grab our old tags. We might not have any, so in this case we'll just pretend it's
// empty
var oldTagTree = GetTagTree(snapshot, oldTagTrees);
// Not sure why we are diffing when we already have tagsToRemove. is it due to _tagSpanComparer might return
// different result than GetIntersectingSpans?
//
// treeForBuffer basically points to oldTagTrees. case where oldTagTrees not exist is already taken cared by
// CachedTagTrees.TryGetValue.
var difference = ComputeDifference(snapshot, newTagTree, treeForBuffer);

Copy link
Member

Choose a reason for hiding this comment

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

If you're not using oldTagTrees, then remove the local.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

oh. good catch.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

done

var difference = ComputeDifference(snapshot, newTagTree, oldTagTree);
RaiseTagsChanged(snapshot.TextBuffer, difference);
}

Expand Down Expand Up @@ -594,7 +594,7 @@ private void ProcessNewTagTrees(
object newState,
CancellationToken cancellationToken)
{
var bufferToChanges = new Dictionary<ITextBuffer, NormalizedSnapshotSpanCollection>();
var bufferToChanges = new Dictionary<ITextBuffer, DiffResult>();
using (Logger.LogBlock(FunctionId.Tagger_TagSource_ProcessNewTags, cancellationToken))
{
foreach (var latestBuffer in newTagTrees.Keys)
Expand All @@ -609,8 +609,7 @@ private void ProcessNewTagTrees(
else
{
// It's a new buffer, so report all spans are changed
var allSpans = new NormalizedSnapshotSpanCollection(newTagTrees[latestBuffer].GetSpans(snapshot).Select(t => t.Span));
bufferToChanges[latestBuffer] = allSpans;
bufferToChanges[latestBuffer] = new DiffResult(added: newTagTrees[latestBuffer].GetSpans(snapshot).Select(t => t.Span), removed: null);
}
}

Expand All @@ -619,8 +618,7 @@ private void ProcessNewTagTrees(
if (!newTagTrees.ContainsKey(oldBuffer))
{
// This buffer disappeared, so let's notify that the old tags are gone
var allSpans = new NormalizedSnapshotSpanCollection(oldTagTrees[oldBuffer].GetSpans(oldBuffer.CurrentSnapshot).Select(t => t.Span));
bufferToChanges[oldBuffer] = allSpans;
bufferToChanges[oldBuffer] = new DiffResult(added: null, removed: oldTagTrees[oldBuffer].GetSpans(oldBuffer.CurrentSnapshot).Select(t => t.Span));
}
}
}
Expand All @@ -640,7 +638,7 @@ private void ProcessNewTagTrees(

private void UpdateStateAndReportChanges(
ImmutableDictionary<ITextBuffer, TagSpanIntervalTree<TTag>> newTagTrees,
Dictionary<ITextBuffer, NormalizedSnapshotSpanCollection> bufferToChanges,
Dictionary<ITextBuffer, DiffResult> bufferToChanges,
object newState)
{
_workQueue.AssertIsForeground();
Expand Down Expand Up @@ -673,13 +671,12 @@ private void UpdateStateAndReportChanges(
RaiseTagsChanged(bufferToChanges);
}

private NormalizedSnapshotSpanCollection ComputeDifference(
private DiffResult ComputeDifference(
ITextSnapshot snapshot,
TagSpanIntervalTree<TTag> latestSpans,
TagSpanIntervalTree<TTag> previousSpans)
{
return new NormalizedSnapshotSpanCollection(
Difference(latestSpans.GetSpans(snapshot), previousSpans.GetSpans(snapshot), _dataSource.TagComparer));
return Difference(latestSpans.GetSpans(snapshot), previousSpans.GetSpans(snapshot), _dataSource.TagComparer);
}

/// <summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using Microsoft.CodeAnalysis.Editor.Shared.Tagging;
using Microsoft.CodeAnalysis.Shared.TestHooks;
using Microsoft.VisualStudio.Text;
using Microsoft.VisualStudio.Text.Tagging;
Expand Down Expand Up @@ -77,7 +78,7 @@ private void OnResumed(object sender, EventArgs e)
_batchChangeNotifier.Resume();
}

private void OnTagsChangedForBuffer(ICollection<KeyValuePair<ITextBuffer, NormalizedSnapshotSpanCollection>> changes)
private void OnTagsChangedForBuffer(ICollection<KeyValuePair<ITextBuffer, DiffResult>> changes)
{
_tagSource.AssertIsForeground();

Expand All @@ -94,8 +95,33 @@ private void OnTagsChangedForBuffer(ICollection<KeyValuePair<ITextBuffer, Normal
}

// Now report them back to the UI on the main thread.
_batchChangeNotifier.EnqueueChanges(change.Value);

// We ask to update UI immediately for removed tags
NotifyEditors(change.Value.Removed, _tagSource.RemovedTagNotificationDelay);
NotifyEditors(change.Value.Added, _tagSource.AddedTagNotificationDelay);
}
}

private void NotifyEditors(NormalizedSnapshotSpanCollection changes, TaggerDelay delay)
{
_tagSource.AssertIsForeground();

if (changes.Count == 0)
{
// nothing to do.
return;
}

if (delay == TaggerDelay.NearImmediate)
Copy link
Member

Choose a reason for hiding this comment

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

Bail immediately if changes is empty.

Copy link
Member

Choose a reason for hiding this comment

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

Add foreground assertion.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

done

{
// if delay is immediate, we let notifier knows about the change right away
_batchChangeNotifier.EnqueueChanges(changes);
return;
}

// if delay is anything more than that, we let notifier knows about the change after given delay
// event notification is not cancellable.
_tagSource.RegisterNotification(() => _batchChangeNotifier.EnqueueChanges(changes), (int)delay.ComputeTimeDelay(_subjectBuffer).TotalMilliseconds, CancellationToken.None);
}

public IEnumerable<ITagSpan<TTag>> GetTags(NormalizedSnapshotSpanCollection requestedSpans)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,16 @@ internal abstract partial class AbstractAsynchronousTaggerProvider<TTag> : Foreg
protected virtual IEnumerable<Option<bool>> Options => SpecializedCollections.EmptyEnumerable<Option<bool>>();
protected virtual IEnumerable<PerLanguageOption<bool>> PerLanguageOptions => SpecializedCollections.EmptyEnumerable<PerLanguageOption<bool>>();

/// <summary>
/// This controls what delay tagger will use to let editor know about newly inserted tags
/// </summary>
protected virtual TaggerDelay AddedTagNotificationDelay => TaggerDelay.NearImmediate;

/// <summary>
/// This controls what delay tagger will use to let editor know about just deleted tags.
/// </summary>
protected virtual TaggerDelay RemovedTagNotificationDelay => TaggerDelay.NearImmediate;

#if DEBUG
public readonly string StackTrace;
#endif
Expand Down Expand Up @@ -212,5 +222,24 @@ protected virtual Task ProduceTagsAsync(TaggerContext<TTag> context, DocumentSna
{
return SpecializedTasks.EmptyTask;
}

private struct DiffResult
{
public NormalizedSnapshotSpanCollection Added { get; }
public NormalizedSnapshotSpanCollection Removed { get; }

public DiffResult(List<SnapshotSpan> added, List<SnapshotSpan> removed) :
this(added?.Count == 0 ? null : (IEnumerable<SnapshotSpan>)added, removed?.Count == 0 ? null : (IEnumerable<SnapshotSpan>)removed)
{
}

public DiffResult(IEnumerable<SnapshotSpan> added, IEnumerable<SnapshotSpan> removed)
{
Added = added != null ? new NormalizedSnapshotSpanCollection(added) : NormalizedSnapshotSpanCollection.Empty;
Removed = removed != null ? new NormalizedSnapshotSpanCollection(removed) : NormalizedSnapshotSpanCollection.Empty;
}

public int Count => Added.Count + Removed.Count;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ internal static class InternalSolutionCrawlerOptions
public const string OptionName = "SolutionCrawler";

public static readonly Option<bool> SolutionCrawler = new Option<bool>("FeatureManager/Components", "Solution Crawler", defaultValue: true);
public static readonly Option<int> ActiveFileWorkerBackOffTimeSpanInMS = new Option<int>(OptionName, "Active file worker backoff timespan in ms", defaultValue: 800);
public static readonly Option<int> ActiveFileWorkerBackOffTimeSpanInMS = new Option<int>(OptionName, "Active file worker backoff timespan in ms", defaultValue: 400);
public static readonly Option<int> AllFilesWorkerBackOffTimeSpanInMS = new Option<int>(OptionName, "All files worker backoff timespan in ms", defaultValue: 1500);
public static readonly Option<int> EntireProjectWorkerBackOffTimeSpanInMS = new Option<int>(OptionName, "Entire project analysis worker backoff timespan in ms", defaultValue: 5000);
public static readonly Option<int> SemanticChangeBackOffTimeSpanInMS = new Option<int>(OptionName, "Semantic change backoff timespan in ms", defaultValue: 100);
Expand Down