Skip to content

Commit

Permalink
Update ProjectWorkspaceState and HostProject at the same time (#11191)
Browse files Browse the repository at this point in the history
Preparation for ongoing work to hook up the Roslyn tokenizer and
#11182 I suppose.

There were three places that `UpdateProjectWorkspaceState` was called:

1. In `RazorProjectService`, just before calling
`UpdateProjectConfiguration`
2. In `ProjectWorkspaceStateGenerator`, where we will need to add a call
to `UpdateProjectConfiguration` in future, to wire up the tokenizer
3. In our LiveShare bits, in response to events from the above.

Previous attempts to plumb through more things for `RazorConfiguration`
resulted in RPS failures, that appeared to be simply more compilations
of closed files. This makes sense because we were adding another update,
which would have triggered another set of `ProjectChanged` events. I
thought it would make more sense to combine these two updates together,
so no matter which part of the project was being updated, there could be
a single `ProjectChanged` notification. This is that.
  • Loading branch information
davidwengier authored Nov 12, 2024
2 parents f3aa647 + b36e892 commit 95f862d
Show file tree
Hide file tree
Showing 25 changed files with 127 additions and 312 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ await projectManager.UpdateAsync(
updater.ProjectAdded(hostProject);
var tagHelpers = CommonResources.LegacyTagHelpers;
var projectWorkspaceState = ProjectWorkspaceState.Create(tagHelpers, CodeAnalysis.CSharp.LanguageVersion.CSharp11);
updater.ProjectWorkspaceStateChanged(hostProject.Key, projectWorkspaceState);
updater.ProjectChanged(hostProject, projectWorkspaceState);
updater.DocumentAdded(hostProject.Key, hostDocument, textLoader);
},
CancellationToken.None);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -369,34 +369,21 @@ private Task AddOrUpdateProjectCoreAsync(
_logger.LogInformation($"Updating project '{project.Key}' TagHelpers ({projectWorkspaceState.TagHelpers.Length}) and C# Language Version ({projectWorkspaceState.CSharpLanguageVersion}).");
}
updater.ProjectWorkspaceStateChanged(project.Key, projectWorkspaceState);
var currentConfiguration = project.Configuration;
var currentRootNamespace = project.RootNamespace;
if (currentConfiguration.ConfigurationName == configuration?.ConfigurationName &&
currentRootNamespace == rootNamespace)
{
_logger.LogTrace($"Updating project '{project.Key}'. The project is already using configuration '{configuration.ConfigurationName}' and root namespace '{rootNamespace}'.");
return;
}
if (configuration is null)
{
configuration = FallbackRazorConfiguration.Latest;
_logger.LogInformation($"Updating project '{project.Key}' to use the latest configuration ('{configuration.ConfigurationName}')'.");
}
else if (currentConfiguration.ConfigurationName != configuration.ConfigurationName)
{
_logger.LogInformation($"Updating project '{project.Key}' to Razor configuration '{configuration.ConfigurationName}' with language version '{configuration.LanguageVersion}'.");
}
if (currentRootNamespace != rootNamespace)
else if (currentConfiguration == configuration &&
currentRootNamespace == rootNamespace)
{
_logger.LogInformation($"Updating project '{project.Key}''s root namespace to '{rootNamespace}'.");
_logger.LogTrace($"Updating project '{project.Key}'. The project is already using configuration '{configuration.ConfigurationName}' and root namespace '{rootNamespace}'.");
}
var hostProject = new HostProject(project.FilePath, project.IntermediateOutputPath, configuration, rootNamespace, displayName);
updater.ProjectConfigurationChanged(hostProject);
updater.ProjectChanged(hostProject, projectWorkspaceState);
},
cancellationToken);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -150,15 +150,6 @@ public bool TryGetTextVersion(out VersionStamp result)
return false;
}

public virtual DocumentState WithConfigurationChange()
{
var state = new DocumentState(HostDocument, Version + 1, _textAndVersion, _textLoader);

// Do not cache computed state

return state;
}

public virtual DocumentState WithImportsChange()
{
var state = new DocumentState(HostDocument, Version + 1, _textAndVersion, _textLoader);
Expand All @@ -169,7 +160,7 @@ public virtual DocumentState WithImportsChange()
return state;
}

public virtual DocumentState WithProjectWorkspaceStateChange()
public virtual DocumentState WithProjectChange()
{
var state = new DocumentState(HostDocument, Version + 1, _textAndVersion, _textLoader);

Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -50,11 +50,8 @@ public void ProjectAdded(HostProject project)
public void ProjectRemoved(ProjectKey projectKey)
=> instance.ProjectRemoved(projectKey);

public void ProjectConfigurationChanged(HostProject project)
=> instance.ProjectConfigurationChanged(project);

public void ProjectWorkspaceStateChanged(ProjectKey projectKey, ProjectWorkspaceState projectWorkspaceState)
=> instance.ProjectWorkspaceStateChanged(projectKey, projectWorkspaceState);
public void ProjectChanged(HostProject project, ProjectWorkspaceState projectWorkspaceState)
=> instance.ProjectChanged(project, projectWorkspaceState);

public void SolutionOpened()
=> instance.SolutionOpened();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -306,7 +306,7 @@ private void ProjectAdded(HostProject hostProject)
}
}

private void ProjectConfigurationChanged(HostProject hostProject)
private void ProjectChanged(HostProject hostProject, ProjectWorkspaceState projectWorkspaceState)
{
if (_initialized)
{
Expand All @@ -316,25 +316,7 @@ private void ProjectConfigurationChanged(HostProject hostProject)
if (TryUpdate(
hostProject.Key,
documentFilePath: null,
new HostProjectUpdatedAction(hostProject),
out var oldSnapshot,
out var newSnapshot))
{
NotifyListeners(oldSnapshot, newSnapshot, documentFilePath: null, ProjectChangeKind.ProjectChanged);
}
}

private void ProjectWorkspaceStateChanged(ProjectKey projectKey, ProjectWorkspaceState projectWorkspaceState)
{
if (_initialized)
{
_dispatcher.AssertRunningOnDispatcher();
}

if (TryUpdate(
projectKey,
documentFilePath: null,
new ProjectWorkspaceStateChangedAction(projectWorkspaceState),
new ProjectChangeAction(hostProject, projectWorkspaceState),
out var oldSnapshot,
out var newSnapshot))
{
Expand Down Expand Up @@ -591,11 +573,8 @@ private static Entry ComputeNewEntry(Entry originalEntry, IUpdateProjectAction a
}
}

case ProjectWorkspaceStateChangedAction(var workspaceState):
return new Entry(originalEntry.State.WithProjectWorkspaceState(workspaceState));

case HostProjectUpdatedAction(var hostProject):
return new Entry(originalEntry.State.WithHostProject(hostProject));
case ProjectChangeAction(var hostProject, var workspaceState):
return new Entry(originalEntry.State.WithHostProjectAndWorkspaceState(hostProject, workspaceState));

default:
throw new InvalidOperationException($"Unexpected action type {action.GetType()}");
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the MIT license. See License.txt in the project root for license information.

using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.IO;
Expand All @@ -21,17 +20,6 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem;
// Internal tracker for DefaultProjectSnapshot
internal class ProjectState
{
private const ProjectDifference ClearConfigurationVersionMask = ProjectDifference.ConfigurationChanged;

private const ProjectDifference ClearProjectWorkspaceStateVersionMask =
ProjectDifference.ConfigurationChanged |
ProjectDifference.ProjectWorkspaceStateChanged;

private const ProjectDifference ClearDocumentCollectionVersionMask =
ProjectDifference.ConfigurationChanged |
ProjectDifference.DocumentAdded |
ProjectDifference.DocumentRemoved;

private static readonly ImmutableDictionary<string, DocumentState> s_emptyDocuments = ImmutableDictionary.Create<string, DocumentState>(FilePathNormalizingComparer.Instance);
private static readonly ImmutableDictionary<string, ImmutableArray<string>> s_emptyImportsToRelatedDocuments = ImmutableDictionary.Create<string, ImmutableArray<string>>(FilePathNormalizingComparer.Instance);
private readonly object _lock;
Expand Down Expand Up @@ -70,7 +58,7 @@ private ProjectState(

private ProjectState(
ProjectState older,
ProjectDifference difference,
bool numberOfDocumentsMayHaveChanged,
HostProject hostProject,
ProjectWorkspaceState projectWorkspaceState,
ImmutableDictionary<string, DocumentState> documents,
Expand All @@ -87,17 +75,19 @@ private ProjectState(

_lock = new object();

if ((difference & ClearDocumentCollectionVersionMask) == 0)
if (numberOfDocumentsMayHaveChanged)
{
// Document collection hasn't changed
DocumentCollectionVersion = older.DocumentCollectionVersion;
DocumentCollectionVersion = Version;
}
else
{
DocumentCollectionVersion = Version;
// Document collection hasn't changed
DocumentCollectionVersion = older.DocumentCollectionVersion;
}

if ((difference & ClearConfigurationVersionMask) == 0 && older._projectEngine != null)
if (older._projectEngine != null &&
HostProject.Configuration == older.HostProject.Configuration &&
CSharpLanguageVersion == older.CSharpLanguageVersion)
{
// Optimistically cache the RazorProjectEngine.
_projectEngine = older.ProjectEngine;
Expand All @@ -108,24 +98,14 @@ private ProjectState(
ConfigurationVersion = Version;
}

if ((difference & ClearProjectWorkspaceStateVersionMask) == 0 ||
ProjectWorkspaceState == older.ProjectWorkspaceState ||
ProjectWorkspaceState.Equals(older.ProjectWorkspaceState))
if (ProjectWorkspaceState.Equals(older.ProjectWorkspaceState))
{
ProjectWorkspaceStateVersion = older.ProjectWorkspaceStateVersion;
}
else
{
ProjectWorkspaceStateVersion = Version;
}

if ((difference & ClearProjectWorkspaceStateVersionMask) != 0 &&
CSharpLanguageVersion != older.CSharpLanguageVersion)
{
// C# language version changed. This impacts the ProjectEngine, reset it.
_projectEngine = null;
ConfigurationVersion = Version;
}
}

// Internal set for testing.
Expand Down Expand Up @@ -196,16 +176,6 @@ RazorProjectEngine CreateProjectEngine()

public ProjectState WithAddedHostDocument(HostDocument hostDocument, TextLoader loader)
{
if (hostDocument is null)
{
throw new ArgumentNullException(nameof(hostDocument));
}

if (loader is null)
{
throw new ArgumentNullException(nameof(loader));
}

// Ignore attempts to 'add' a document with different data, we only
// care about one, so it might as well be the one we have.
if (Documents.ContainsKey(hostDocument.FilePath))
Expand All @@ -229,17 +199,12 @@ public ProjectState WithAddedHostDocument(HostDocument hostDocument, TextLoader
}
}

var state = new ProjectState(this, ProjectDifference.DocumentAdded, HostProject, ProjectWorkspaceState, documents, importsToRelatedDocuments);
var state = new ProjectState(this, numberOfDocumentsMayHaveChanged: true, HostProject, ProjectWorkspaceState, documents, importsToRelatedDocuments);
return state;
}

public ProjectState WithRemovedHostDocument(HostDocument hostDocument)
{
if (hostDocument is null)
{
throw new ArgumentNullException(nameof(hostDocument));
}

if (!Documents.ContainsKey(hostDocument.FilePath))
{
return this;
Expand All @@ -261,17 +226,12 @@ public ProjectState WithRemovedHostDocument(HostDocument hostDocument)
var importTargetPaths = GetImportDocumentTargetPaths(hostDocument);
var importsToRelatedDocuments = RemoveFromImportsToRelatedDocuments(ImportsToRelatedDocuments, hostDocument, importTargetPaths);

var state = new ProjectState(this, ProjectDifference.DocumentRemoved, HostProject, ProjectWorkspaceState, documents, importsToRelatedDocuments);
var state = new ProjectState(this, numberOfDocumentsMayHaveChanged: true, HostProject, ProjectWorkspaceState, documents, importsToRelatedDocuments);
return state;
}

public ProjectState WithChangedHostDocument(HostDocument hostDocument, SourceText sourceText, VersionStamp textVersion)
{
if (hostDocument is null)
{
throw new ArgumentNullException(nameof(hostDocument));
}

if (!Documents.TryGetValue(hostDocument.FilePath, out var document))
{
return this;
Expand All @@ -287,17 +247,12 @@ public ProjectState WithChangedHostDocument(HostDocument hostDocument, SourceTex
}
}

var state = new ProjectState(this, ProjectDifference.DocumentChanged, HostProject, ProjectWorkspaceState, documents, ImportsToRelatedDocuments);
var state = new ProjectState(this, numberOfDocumentsMayHaveChanged: false, HostProject, ProjectWorkspaceState, documents, ImportsToRelatedDocuments);
return state;
}

public ProjectState WithChangedHostDocument(HostDocument hostDocument, TextLoader loader)
{
if (hostDocument is null)
{
throw new ArgumentNullException(nameof(hostDocument));
}

if (!Documents.TryGetValue(hostDocument.FilePath, out var document))
{
return this;
Expand All @@ -313,24 +268,20 @@ public ProjectState WithChangedHostDocument(HostDocument hostDocument, TextLoade
}
}

var state = new ProjectState(this, ProjectDifference.DocumentChanged, HostProject, ProjectWorkspaceState, documents, ImportsToRelatedDocuments);
var state = new ProjectState(this, numberOfDocumentsMayHaveChanged: false, HostProject, ProjectWorkspaceState, documents, ImportsToRelatedDocuments);
return state;
}

public ProjectState WithHostProject(HostProject hostProject)
public ProjectState WithHostProjectAndWorkspaceState(HostProject hostProject, ProjectWorkspaceState projectWorkspaceState)
{
if (hostProject is null)
{
throw new ArgumentNullException(nameof(hostProject));
}

if (HostProject.Configuration.Equals(hostProject.Configuration) &&
HostProject.RootNamespace == hostProject.RootNamespace)
HostProject.RootNamespace == hostProject.RootNamespace &&
ProjectWorkspaceState.Equals(projectWorkspaceState))
{
return this;
}

var documents = Documents.ToImmutableDictionary(kvp => kvp.Key, kvp => kvp.Value.WithConfigurationChange(), FilePathNormalizingComparer.Instance);
var documents = Documents.ToImmutableDictionary(kvp => kvp.Key, kvp => kvp.Value.WithProjectChange(), FilePathNormalizingComparer.Instance);

// If the host project has changed then we need to recompute the imports map
var importsToRelatedDocuments = s_emptyImportsToRelatedDocuments;
Expand All @@ -341,25 +292,7 @@ public ProjectState WithHostProject(HostProject hostProject)
importsToRelatedDocuments = AddToImportsToRelatedDocuments(importsToRelatedDocuments, document.Value.HostDocument.FilePath, importTargetPaths);
}

var state = new ProjectState(this, ProjectDifference.ConfigurationChanged, hostProject, ProjectWorkspaceState, documents, importsToRelatedDocuments);
return state;
}

public ProjectState WithProjectWorkspaceState(ProjectWorkspaceState projectWorkspaceState)
{
if (ProjectWorkspaceState == projectWorkspaceState)
{
return this;
}

if (ProjectWorkspaceState.Equals(projectWorkspaceState))
{
return this;
}

var difference = ProjectDifference.ProjectWorkspaceStateChanged;
var documents = Documents.ToImmutableDictionary(kvp => kvp.Key, kvp => kvp.Value.WithProjectWorkspaceStateChange(), FilePathNormalizingComparer.Instance);
var state = new ProjectState(this, difference, HostProject, projectWorkspaceState, documents, ImportsToRelatedDocuments);
var state = new ProjectState(this, numberOfDocumentsMayHaveChanged: true, hostProject, projectWorkspaceState, documents, importsToRelatedDocuments);
return state;
}

Expand All @@ -372,7 +305,7 @@ internal static ImmutableDictionary<string, ImmutableArray<string>> AddToImports
{
if (!importsToRelatedDocuments.TryGetValue(importTargetPath, out var relatedDocuments))
{
relatedDocuments = ImmutableArray.Create<string>();
relatedDocuments = [];
}

relatedDocuments = relatedDocuments.Add(documentFilePath);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,4 @@ internal record ProjectAddedAction(HostProject HostProject) : IUpdateProjectActi

internal record ProjectRemovedAction(ProjectKey ProjectKey) : IUpdateProjectAction;

internal record HostProjectUpdatedAction(HostProject HostProject) : IUpdateProjectAction;

internal record ProjectWorkspaceStateChangedAction(ProjectWorkspaceState WorkspaceState) : IUpdateProjectAction;
internal record ProjectChangeAction(HostProject HostProject, ProjectWorkspaceState WorkspaceState) : IUpdateProjectAction;
Loading

0 comments on commit 95f862d

Please sign in to comment.