Skip to content

Instantly share code, notes, and snippets.

@jbevain
Last active November 11, 2024 17:01
Show Gist options
  • Save jbevain/fc1eb91d633d8e569458f1e8db06a342 to your computer and use it in GitHub Desktop.
Save jbevain/fc1eb91d633d8e569458f1e8db06a342 to your computer and use it in GitHub Desktop.

Sebastian recently reported a scenario where an operation in Visual Studio took an unreasonable amount of time (15minutes) on a large Unity project (477 individual csproj in the solution).

Sebastian was kind enough to reach out and share a way to reproduce the issue.

What's happening

When all the projects of a Visual Studio solution are in an “unloaded” state.

Unloaded projects in the Solution Explorer

You can load all the projects at once by right clicking on the solution and invoking the command Load all projects.

Load all projects command in the Solution Exlorer

You can get in a state where all projects are considered unloaded if they are of project types that require a Visual Studio extension that is not currently installed. In Sebastian's case, he opened a Unity project without having the Visual Studio Tools for Unity installed.

The Tools for Unity offer a “Unity Project Explorer” (affectionately called UPE) Tool Window, that displays a view of the Unity project in a similar way Unity does, folder based instead of a solution/project based view like the “Solution Explorer” does.

To react to changes to the solution and adjust the content of the UPE, we were using EnvDTE.SolutionEvents to listen to differente events, including ProjectAdded.

The intent being, if a csproj is added to the solution, we rebuild the treeview of the UPE. In a day to day workflow, this is never done repeatedly, so this never came onto our radar.

When you click Load all projects however, the event ProjectAdded is going to be called for each project in the solution. In Sebastian's case, we were rebuilding the treeview of the UPE 477 times.

How we fixed it

When we first wrote the UPE, there wasn't a good way to be notified by Visual Studio that multiple projects were going to be loaded or unloaded.

Now we can use the interface IVsSolutionBatchProjectActionEvents.

Make sure to use IVsSolutionBatchProjectActionEvents and not the Batch methods of IVsSolutionLoadEvents because those have been marked as Obsolete and are not called.

The solution was as simple as replacing our usage of the EnvDTE.SolutionEvents by a solution listener implementing the family of IVsSolutionEvents interface and IVsSolutionBatchProjectActionEvents.

We're now able to distinguish between an individual project load, and a batched project load. So when you do Load all projects, we're going to be rebuilding the UPE only once.

So for Sebastian's solution, something that went from 15min for 477 projects should now take 15x60/477 => 1.88s (probably slightly more, I'm assuming rebuilding the treeview for the last project is going to take longer than for the first project simply because there will be more items). That's still too long for what it is, but it's now back in the pretty reasonable realm for as big of a project.

So we basically went from something that did this (at least in idea as we were not using exactly this interface):

public int OnAfterOpenProject(IVsHierarchy hierarchy, int added)
{
    _explorer.ReloadUnityTree();

    return HResult.S_OK;
}

To something that did this (code is not complete and just for illustration of the interface used):

public int OnBeforeBatchProjectLoad(IVsBatchProjectActionContext pContext)
{
    _batching = true;

    return HResult.S_OK;
}

public int OnAfterOpenProject(IVsHierarchy hierarchy, int added)
{
    if (!_batching)
    {
        _explorer.ReloadUnityTree();
    }

    return HResult.S_OK;
}

public int OnEndBatchProjectLoad(IVsBatchProjectActionContext pContext)
{
    _batching = false;
    _explorer.ReloadUnityTree();

    return HResult.S_OK;
}

In the future, we could definitely be smarter and not rebuild the entire UPE but compute the difference between the two states.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment