Skip to content

Commit

Permalink
Repair projectwatcher (#2106)
Browse files Browse the repository at this point in the history
* use positive 77

* repair projectwatcher

* remove unused line

* undo -77 change

* undo -77

* order usings

* fix formatting

* remove unused solution

* remove unused async

* use previous cancelation method

* remove extra task

* add check to not throw assembly changes away

* rework distribution of assemblychanges

* remove unused using

* add broadcast back in

* remove assembly broadcast

* add cancelation

* replace cancelation location

* improve if nesting

* improve naming, fix reload on new references

* fix loading chain of assets

* refactor

* [Editor] Refactor initialization of CodeViewModel

---------

Co-authored-by: IXLLEGACYIXL <ixllegacy123@outlook.com>
Co-authored-by: Nicolas Musset <musset.nicolas@gmail.com>
  • Loading branch information
3 people committed Feb 1, 2024
1 parent ec74cd9 commit 26c3696
Show file tree
Hide file tree
Showing 4 changed files with 191 additions and 181 deletions.
85 changes: 43 additions & 42 deletions sources/assets/Stride.Core.Assets/PackageSession.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,12 @@
using Stride.Core.Packages;
using Stride.Core.Reflection;
using Stride.Core.Serialization;
using static Stride.Core.Assets.PackageSession;
using ILogger = Stride.Core.Diagnostics.ILogger;

namespace Stride.Core.Assets
{
record AssetLoadingInfo(PackageSession session, ILogger log, Package package, PackageLoadParameters loadParameters, List<PendingPackageUpgrade> pendingPackageUpgrades, PackageLoadParameters newLoadParameters);
public abstract class PackageContainer
{
public PackageContainer([NotNull] Package package)
Expand Down Expand Up @@ -955,7 +957,7 @@ public void LoadMissingAssets(ILogger log, IEnumerable<Package> packages, Packag
var loadParameters = loadParametersArg ?? PackageLoadParameters.Default();

var cancelToken = loadParameters.CancelToken;

List<AssetLoadingInfo> assetLoadInfos = [];
// Make a copy of Packages as it can be modified by PreLoadPackageDependencies
foreach (var package in packages)
{
Expand All @@ -964,8 +966,12 @@ public void LoadMissingAssets(ILogger log, IEnumerable<Package> packages, Packag
{
return;
}

TryLoadAssets(this, log, package, loadParameters);
if(TryLoadAssemblies(this, log, package, loadParameters,out var assetInfo))
assetLoadInfos.Add(assetInfo);
}
foreach (AssetLoadingInfo assetInfo in assetLoadInfos)
{
LoadAssets(assetInfo.session,assetInfo.log,assetInfo.package, assetInfo.loadParameters,assetInfo.pendingPackageUpgrades,assetInfo.newLoadParameters);
}
}

Expand Down Expand Up @@ -1389,11 +1395,12 @@ private Package PreLoadPackage(ILogger log, string filePath, PackageLoadParamete
return null;
}

private bool TryLoadAssets(PackageSession session, ILogger log, Package package, PackageLoadParameters loadParameters)
private bool TryLoadAssemblies(PackageSession session, ILogger log, Package package, PackageLoadParameters loadParameters, out AssetLoadingInfo info)
{
info = null;
// Already loaded
if (package.State >= PackageState.AssetsReady)
return true;
return false;

// Dependencies could not properly be loaded
if (package.State < PackageState.DependenciesReady)
Expand All @@ -1405,16 +1412,7 @@ private bool TryLoadAssets(PackageSession session, ILogger log, Package package,

try
{
// First, check that dependencies have their assets loaded
bool dependencyError = false;
foreach (var dependency in package.FindDependencies(false))
{
if (!TryLoadAssets(session, log, dependency, loadParameters))
dependencyError = true;
}

if (dependencyError)
return false;

// Get pending package upgrades
if (!pendingPackageUpgradesPerPackage.TryGetValue(package, out var pendingPackageUpgrades))
Expand Down Expand Up @@ -1502,44 +1500,47 @@ private bool TryLoadAssets(PackageSession session, ILogger log, Package package,
// Mark package as dirty
package.IsDirty = true;
}
info = new AssetLoadingInfo(session, log, package, loadParameters, pendingPackageUpgrades, newLoadParameters);
return true;
}
catch (Exception ex)
{
log.Error($"Error while pre-loading package [{package}]", ex);
return false;
}
}

// Load assets
package.LoadAssets(log, newLoadParameters);
private static void LoadAssets(PackageSession session, ILogger log, Package package, PackageLoadParameters loadParameters, List<PendingPackageUpgrade> pendingPackageUpgrades, PackageLoadParameters newLoadParameters)
{
// Load assets
package.LoadAssets(log, newLoadParameters);

// Validate assets from package
package.ValidateAssets(newLoadParameters.GenerateNewAssetIds, newLoadParameters.RemoveUnloadableObjects, log);
// Validate assets from package
package.ValidateAssets(newLoadParameters.GenerateNewAssetIds, newLoadParameters.RemoveUnloadableObjects, log);

if (pendingPackageUpgrades.Count > 0)
if (pendingPackageUpgrades.Count > 0)
{
// Perform post asset load upgrade
foreach (var pendingPackageUpgrade in pendingPackageUpgrades)
{
// Perform post asset load upgrade
foreach (var pendingPackageUpgrade in pendingPackageUpgrades)
var packageUpgrader = pendingPackageUpgrade.PackageUpgrader;
var dependencyPackage = pendingPackageUpgrade.DependencyPackage;
if (!packageUpgrader.UpgradeAfterAssetsLoaded(loadParameters, session, log, package, pendingPackageUpgrade.Dependency, dependencyPackage, pendingPackageUpgrade.DependencyVersionBeforeUpgrade))
{
var packageUpgrader = pendingPackageUpgrade.PackageUpgrader;
var dependencyPackage = pendingPackageUpgrade.DependencyPackage;
if (!packageUpgrader.UpgradeAfterAssetsLoaded(loadParameters, session, log, package, pendingPackageUpgrade.Dependency, dependencyPackage, pendingPackageUpgrade.DependencyVersionBeforeUpgrade))
{
log.Error($"Error while upgrading package [{package.Meta.Name}] for [{dependencyPackage.Meta.Name}] from version [{pendingPackageUpgrade.Dependency.Version}] to [{dependencyPackage.Meta.Version}]");
return false;
}
log.Error($"Error while upgrading package [{package.Meta.Name}] for [{dependencyPackage.Meta.Name}] from version [{pendingPackageUpgrade.Dependency.Version}] to [{dependencyPackage.Meta.Version}]");
return;
}

// Mark package as dirty
package.IsDirty = true;
}

// Mark package as ready
package.State = PackageState.AssetsReady;
// Mark package as dirty
package.IsDirty = true;
}

// Freeze the package after loading the assets
session.FreezePackage(package);
// Mark package as ready
package.State = PackageState.AssetsReady;

return true;
}
catch (Exception ex)
{
log.Error($"Error while pre-loading package [{package}]", ex);
return false;
}
// Freeze the package after loading the assets
session.FreezePackage(package);
}

private static PackageUpgrader CheckPackageUpgrade(ILogger log, Package dependentPackage, PackageDependency dependency, Package dependencyPackage)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,14 @@
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Collections.Specialized;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Threading.Tasks.Dataflow;
using Microsoft.Build.Construction;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.Text;
using Stride.Core.Assets;
using Stride.Core.Assets.Editor.ViewModel;
Expand All @@ -22,7 +21,6 @@
using Stride.Core.Extensions;
using Stride.Assets.Presentation.AssetEditors.ScriptEditor;
using Project = Microsoft.CodeAnalysis.Project;
using System.Diagnostics;

namespace Stride.Assets.Presentation.AssetEditors
{
Expand Down Expand Up @@ -51,7 +49,7 @@ public AssemblyChangedEvent(PackageLoadedAssembly assembly, AssemblyChangeType c

public Project Project { get; set; }
}

public class ProjectWatcher : IDisposable
{
private readonly TrackingCollection<TrackedAssembly> trackedAssemblies;
Expand All @@ -65,7 +63,7 @@ public class ProjectWatcher : IDisposable
private Project gameExecutable;

private CancellationTokenSource batchChangesCancellationTokenSource = new CancellationTokenSource();
private Task batchChangesTask;
public IAsyncEnumerable<List<AssemblyChangedEvent>> BatchChange;

private MSBuildWorkspace msbuildWorkspace;

Expand All @@ -86,25 +84,51 @@ public ProjectWatcher(SessionViewModel session, bool trackBinaries = true)
fileChangedLink1 = fileChanged.LinkTo(fileChangedTransform);
fileChangedLink2 = fileChangedTransform.LinkTo(AssemblyChangedBroadcast);

batchChangesTask = BatchChanges();
BatchChange = BatchChanges();
}

private async Task BatchChanges()
private async IAsyncEnumerable<List<AssemblyChangedEvent>> BatchChanges()
{
var buffer = new BufferBlock<AssemblyChangedEvent>();
using (AssemblyChangedBroadcast.LinkTo(buffer))
{
while (true)
while (!batchChangesCancellationTokenSource.IsCancellationRequested)
{
var hasChanged = false;
var assemblyChanges = new List<AssemblyChangedEvent>();
do
{
var assemblyChange = await buffer.ReceiveAsync(batchChangesCancellationTokenSource.Token);
if (assemblyChange != null)
assemblyChanges.Add(assemblyChange);

if (assemblyChange != null && !hasChanged)
if (assemblyChange == null)
continue;

assemblyChanges.Add(assemblyChange);
var project = assemblyChange.Project;
var referencedProjects = msbuildWorkspace.CurrentSolution.GetProjectDependencyGraph().GetProjectsThatTransitivelyDependOnThisProject(project.Id);
foreach (var referenceProject in referencedProjects)
{
var foundProject = msbuildWorkspace.CurrentSolution.GetProject(referenceProject);
if(foundProject is null)
continue;
var assemblyName = foundProject.AssemblyName;
var target = trackedAssemblies.FirstOrDefault(x => x.Project.AssemblyName == assemblyName);
if (target != null)
{
string file = assemblyChange.ChangedFile;
if (assemblyChange.ChangeType == AssemblyChangeType.Binary)
{
file = target.LoadedAssembly.Path;
}
else if (assemblyChange.ChangeType == AssemblyChangeType.Project)
{
file = target.Project.FilePath;
}
assemblyChanges.Add(new AssemblyChangedEvent(target.LoadedAssembly, assemblyChange.ChangeType, file, target.Project));
}
}

if (!hasChanged)
{
// After the first change, wait for more changes
await Task.Delay(TimeSpan.FromMilliseconds(500), batchChangesCancellationTokenSource.Token);
Expand All @@ -116,13 +140,11 @@ private async Task BatchChanges()
// Merge files that were modified multiple time
assemblyChanges = assemblyChanges.GroupBy(x => x.ChangedFile).Select(x => x.Last()).ToList();

AssembliesChangedBroadcast.Post(assemblyChanges);
yield return assemblyChanges;
}
}
}

public BroadcastBlock<AssemblyChangedEvent> AssemblyChangedBroadcast { get; } = new BroadcastBlock<AssemblyChangedEvent>(null);
public BroadcastBlock<List<AssemblyChangedEvent>> AssembliesChangedBroadcast { get; } = new BroadcastBlock<List<AssemblyChangedEvent>>(null);

public Project CurrentGameLibrary
{
Expand All @@ -145,7 +167,6 @@ public Project CurrentGameLibrary
public void Dispose()
{
batchChangesCancellationTokenSource.Cancel();
batchChangesTask.Wait();

directoryWatcher.Dispose();
fileChangedLink1.Dispose();
Expand Down Expand Up @@ -197,12 +218,12 @@ public async Task ReceiveAndDiscardChanges(TimeSpan batchInterval, CancellationT
}
}

private async Task<AssemblyChangedEvent> FileChangeTransformation(FileEvent e)
private async Task<AssemblyChangedEvent> FileChangeTransformation(FileEvent e)
{
string changedFile;
var renameEvent = e as FileRenameEvent;
changedFile = renameEvent?.OldFullPath ?? e.FullPath;

foreach (var trackedAssembly in trackedAssemblies)
{
// Report change of the assembly binary
Expand All @@ -214,9 +235,9 @@ private async Task<AssemblyChangedEvent> FileChangeTransformation(FileEvent e)
// Also check for .cs file changes (DefaultItems auto import *.cs, with some excludes such as obj subfolder)
// TODO: Check actual unevaluated .csproj to get the auto includes/excludes?
if (needProjectReload == false
&& ((e.ChangeType == FileEventChangeType.Deleted ||e.ChangeType == FileEventChangeType.Renamed || e.ChangeType == FileEventChangeType.Created)
&& ((e.ChangeType == FileEventChangeType.Deleted || e.ChangeType == FileEventChangeType.Renamed || e.ChangeType == FileEventChangeType.Created)
&& Path.GetExtension(changedFile)?.ToLowerInvariant() == ".cs"
&& changedFile.StartsWith(Path.GetDirectoryName(trackedAssembly.Project.FilePath),StringComparison.OrdinalIgnoreCase)))
&& changedFile.StartsWith(Path.GetDirectoryName(trackedAssembly.Project.FilePath), StringComparison.OrdinalIgnoreCase)))
{
needProjectReload = true;
}
Expand Down Expand Up @@ -312,7 +333,7 @@ private async Task TrackPackage(PackageViewModel package)
directoryWatcher.Track(loadedAssembly.ProjectReference.Location);

var trackedAssembly = new TrackedAssembly { Package = package, LoadedAssembly = loadedAssembly };

// Track project source code
if (await UpdateProject(trackedAssembly))
trackedAssemblies.Add(trackedAssembly);
Expand Down Expand Up @@ -358,17 +379,16 @@ private async Task<Project> OpenProject(UFile projectPath)
var host = await RoslynHost;
msbuildWorkspace = MSBuildWorkspace.Create(ImmutableDictionary<string, string>.Empty, host.HostServices);
}
await msbuildWorkspace.OpenSolutionAsync(session.SolutionPath.ToWindowsPath());

msbuildWorkspace.CloseSolution();

// Try up to 10 times (1 second)
const int retryCount = 10;
for (var i = retryCount - 1; i >= 0; --i)
{
try
{
var project = await msbuildWorkspace.OpenProjectAsync(projectPath.ToWindowsPath());

var project = msbuildWorkspace.CurrentSolution.Projects.FirstOrDefault(x => x.FilePath == projectPath.ToWindowsPath());
if (msbuildWorkspace.Diagnostics.Count > 0)
{
// There was an issue compiling the project
Expand Down

0 comments on commit 26c3696

Please sign in to comment.