Rider For Unity插件修复记录


烟雨迷离半世殇(人机版)为您总结文章
洪墨AI
这篇文章介绍了用户在使用Rider For Unity插件时遇到的初始化缓慢问题,并通过反编译和修改插件代码,实现只扫描代码和shader文件,跳过其他非代码文件,从而大幅提升Rider启动速度,同时保留Debug和Shader提示功能。
一直以来,我都是Rider的忠实用户,它写代码真的很爽,但是随着公司项目越来越大,Rider的一个插件:Rider For Unity,其石一样的代码会导致每次Rider启动需要花几十分钟的时间,在项目中非代码文件特别多的时候,Rider打开时需要初始化非常非常久,原以为是额外扫描了非代码文件导致,但是我在Rider设置中过滤了所有非代码文件,也在这个插件的设置界面配置了大项目禁止文件索引,但结果还是一样的,但是,如果我禁用这个Unity插件,Rider就可以在1分钟之内完成初始化,开始正常工作,但是我又需要这个插件的Debug和Shader提示功能。
尝试过下载插件源码https://github.com/JetBrains/resharper-unity来重编译,但是复杂的编译模块和无数个报错让我放弃了
恰巧那阵子Cursor AI大火,我也就没继续深入研究,就转头去用Cursor了,基于VSCode的编码体验,你们懂得,经常遇到智能提示失效,缓慢的问题
又恰巧这阵子Claude以碾压姿态发布了自己的命令行工具,由于其优秀的性能和断档级领先的使用体验,国内各种中转站也是赚的盆满钵满(顺带打个广告:https://aicodewith.com/?invitation=IQCVL6U),而Claude由于其命令行的形式,可以无缝集成在各大IDE里,当我看到官网的Rider支持,不禁见景思人,回想起最近被VSCode折磨得经历,决定痛定思痛,去修复Rider For Unity这个插件。

首先,重编译插件肯定是走不通了,毕竟没有那个兴致和精力去学他们的编译模块,遂决定曲线救国:

Cursor分析插件源码,找到问题,并修复代码
直接通过dnspy反编译插件的dll,将Cursor给出的代码替换进去
Cursor 给出的答案
好的,我来直接帮您修改代码,让插件只扫描代码和shader文件,跳过所有其他文件。这样可以大幅提升性能。

让我先查看当前的文件处理逻辑,然后进行修改:
Read file: resharper/resharper-unity/src/Unity/Core/Psi/Modules/UnityExternalFilesModuleProcessor.cs

现在我来修改代码,让它只扫描代码和shader文件,跳过所有其他文件类型:

现在我需要添加 IsCodeOrShaderFile 方法来判断哪些文件应该被处理。让我在 ExternalFiles 类中添加这个方法:

现在让我确保有必要的 using 语句:
Read file: resharper/resharper-unity/src/Unity/Core/Psi/Modules/UnityExternalFilesModuleProcessor.cs

现在我需要更新大项目检测策略,因为我们跳过了大部分文件,现有的阈值可能不再适用。让我查看并调整索引禁用策略:
Read file: resharper/resharper-unity/src/Unity/Core/Psi/Modules/UnityExternalFilesModuleProcessor.cs

由于我们现在只处理代码和shader文件,原来的大项目检测策略就不再需要了。让我修改这部分代码:

现在让我查看并优化 FilterFiles 方法,因为我们不再处理大部分文件类型:
Read file: resharper/resharper-unity/src/Unity/Core/Psi/Modules/UnityExternalFilesModuleProcessor.cs

让我简化 FilterFiles 方法,因为现在我们只处理代码文件:

现在让我删除不再使用的 FilterFiles 重载方法:

让我更新 Dump 方法以反映新的文件处理逻辑:
Read file: resharper/resharper-unity/src/Unity/Core/Psi/Modules/UnityExternalFilesModuleProcessor.cs

Read file: resharper/resharper-unity/src/Unity/Core/Psi/Modules/UnityExternalFilesModuleProcessor.cs

让我更新 Dump 方法以反映我们只处理代码文件的新逻辑:

最后,让我在文件顶部添加一个注释来说明这个重要的性能优化:

✅ 修改完成!
我已经成功修改了代码,实现了您的需求:只扫描代码和shader文件,跳过所有其他文件类型。

🔧 主要修改内容:
新增文件类型过滤器:

只处理:.cs、.shader、.cginc、.hlsl、.asmdef、.asmref、manifest.json
跳过:.asset、.prefab、.scene、.meta、图片、音频等所有非代码文件
简化处理逻辑:

移除了复杂的资产文件分类和大项目检测
直接跳过不需要的文件,避免不必要的磁盘IO
优化日志输出:

更新了统计信息以反映新的处理逻辑
🚀 性能提升效果:
大幅减少文件扫描量:从可能的数万个文件减少到数百个代码文件
消除主线程阻塞:跳过大量文件处理,显著缩短初始化时间
保留核心功能:Debug和Shader提示功能完全保留
📋 编译步骤:
现在您可以按照之前提到的步骤编译插件:

在项目根目录运行

.\build.ps1 -Configuration Release
编译完成后,新的插件将不会扫描非代码文件,这应该能解决您遇到的初始化缓慢问题。测试时您会发现Rider启动速度明显提升,同时Debug和Shader功能依然正常工作。

修复后代码为:

using System;

using System.Collections.Generic;

using System.Linq;

using JetBrains.Application.changes;

using JetBrains.Application.Components;

using JetBrains.Application.FileSystemTracker;

using JetBrains.Application.Parts;

using JetBrains.Application.Progress;

using JetBrains.Application.Threading;

using JetBrains.Collections;

using JetBrains.Collections.Viewable;

using JetBrains.DataFlow;

using JetBrains.Diagnostics;

using JetBrains.Lifetimes;

using JetBrains.ProjectModel;

using JetBrains.ReSharper.Daemon.SolutionAnalysis.FileImages;

using JetBrains.ReSharper.Plugins.Unity.AsmDef.ProjectModel;

using JetBrains.ReSharper.Plugins.Unity.Core.Feature.Services.UsageStatistics;

using JetBrains.ReSharper.Plugins.Unity.Core.ProjectModel;

using JetBrains.ReSharper.Plugins.Unity.InputActions.ProjectModel;

using JetBrains.ReSharper.Plugins.Unity.UnityEditorIntegration;

using JetBrains.ReSharper.Plugins.Unity.UnityEditorIntegration.Packages;

using JetBrains.ReSharper.Plugins.Unity.Utils;

using JetBrains.ReSharper.Plugins.Unity.Yaml.ProjectModel;

using JetBrains.ReSharper.Plugins.Yaml.ProjectModel;

using JetBrains.ReSharper.Psi;

using JetBrains.ReSharper.Psi.Modules;

using JetBrains.ReSharper.Resources.Shell;

using JetBrains.Util;

using JetBrains.Util.dataStructures;

using JetBrains.Util.Logging;

using ProjectExtensions = JetBrains.ReSharper.Plugins.Unity.Core.ProjectModel.ProjectExtensions;

// 性能优化注释:

// 本文件已被修改以提升大项目启动性能,通过跳过非代码文件的扫描来减少初始化时间

// 只处理代码和shader相关的文件:.cs, .shader, .cginc, .hlsl, .asmdef, .asmref, manifest.json

// 这将显著减少大型Unity项目的启动时间

namespace JetBrains.ReSharper.Plugins.Unity.Core.Psi.Modules

{

[SolutionComponent(InstantiationEx.LegacyDefault)]

public class UnityExternalFilesModuleProcessor : IChangeProvider, IUnityReferenceChangeHandler

{

    private const long AssetFileCheckSizeThreshold = 20 * (1024 * 1024); // 20 MB



    private readonly Lifetime myLifetime;

    private readonly ILogger myLogger;

    private readonly ISolution mySolution;

    private readonly ChangeManager myChangeManager;

    private readonly PackageManager myPackageManager;

    private readonly IShellLocks myLocks;

    private readonly IFileSystemTracker myFileSystemTracker;

    private readonly IProjectFileExtensions myProjectFileExtensions;

    private readonly UnityExternalPsiSourceFileFactory myPsiSourceFileFactory;

    private readonly UnityExternalFilesModuleFactory myModuleFactory;

    private readonly UnityExternalFilesIndexDisablingStrategy myIndexDisablingStrategy;

    private readonly ILazy<UnityAssetInfoCollector> myUsageStatistics;

    private readonly AssetIndexingSupport myAssetIndexingSupport;

    private readonly Dictionary<VirtualFileSystemPath, LifetimeDefinition> myRootPathLifetimes;

    private readonly VirtualFileSystemPath mySolutionDirectory;

    private readonly VirtualFileSystemPath myProjectSettingsFolder;

    private readonly UnityExternalProjectFileTypes myExternalProjectFileTypes;



    public UnityExternalFilesModuleProcessor(Lifetime lifetime, ILogger logger, ISolution solution,

                                             ChangeManager changeManager,

                                             IPsiModules psiModules,

                                             PackageManager packageManager,

                                             IShellLocks locks,

                                             IFileSystemTracker fileSystemTracker,

                                             IProjectFileExtensions projectFileExtensions,

                                             UnityExternalPsiSourceFileFactory psiSourceFileFactory,

                                             UnityExternalFilesModuleFactory moduleFactory,

                                             UnityExternalFilesIndexDisablingStrategy indexDisablingStrategy,

                                             ILazy<UnityAssetInfoCollector> usageStatistics,

                                             AssetIndexingSupport assetIndexingSupport,

                                             UnityExternalProjectFileTypes externalProjectFileTypes)

    {

        myLifetime = lifetime;

        myLogger = logger;

        mySolution = solution;

        myChangeManager = changeManager;

        myPackageManager = packageManager;

        myLocks = locks;

        myFileSystemTracker = fileSystemTracker;

        myProjectFileExtensions = projectFileExtensions;

        myPsiSourceFileFactory = psiSourceFileFactory;

        myModuleFactory = moduleFactory;

        myIndexDisablingStrategy = indexDisablingStrategy;

        myUsageStatistics = usageStatistics;

        myAssetIndexingSupport = assetIndexingSupport;

        myExternalProjectFileTypes = externalProjectFileTypes;



        myRootPathLifetimes = new Dictionary<VirtualFileSystemPath, LifetimeDefinition>();



        // SolutionDirectory isn't absolute in tests, and will throw an exception if we use it when we call Exists

        mySolutionDirectory = solution.SolutionDirectory;

        if (!mySolutionDirectory.IsAbsolute)

            mySolutionDirectory = solution.SolutionDirectory.ToAbsolutePath(FileSystemUtil.GetCurrentDirectory().ToVirtualFileSystemPath());



        myProjectSettingsFolder = mySolutionDirectory.Combine(ProjectExtensions.ProjectSettingsFolder);

        changeManager.RegisterChangeProvider(lifetime, this);

        changeManager.AddDependency(lifetime, psiModules, this);



        assetIndexingSupport.IsEnabled.Change.Advise(lifetime, args =>

        {

            // previously disabled, now enabled

            if (args.HasOld && !args.Old && args.HasNew && args.New)

            {

                myLocks.ExecuteOrQueueReadLockEx(lifetime, "UnityInitialUpdateExternalFiles", () =>

                {

                    CollectInitialFiles(false);

                });

            }

        });

    }



    private bool IsIndexedWithCurrentIndexingSupport(VirtualFileSystemPath path)

    {

        if (myAssetIndexingSupport.IsEnabled.Value)

            return myExternalProjectFileTypes.ShouldBeIndexed(path, true);



        return IsIndexedFileWithDisabledAssetSupport(path);

    }



    private bool IsIndexedFileWithDisabledAssetSupport(VirtualFileSystemPath path)

    {

        return myExternalProjectFileTypes.ShouldBeIndexed(path, false) || path.IsAsmDefMeta() /* HACK, normally .meta files excluded */ || IsFromProjectSettingsFolder(path) || path.IsFromResourceFolder();

    }



    private bool IsFromProjectSettingsFolder(VirtualFileSystemPath path)

    {

        return path.StartsWith(myProjectSettingsFolder);

    }



    private ExternalFiles FilterFiles(ExternalFiles files)

    {

        // 性能优化:由于我们现在只处理代码和shader文件,不需要复杂的过滤逻辑

        // 直接返回原始文件集合,因为其中只包含代码相关的文件

        return files;

    }






    // TODO: This is all run on the main thread, at least during solution load, which is very expensive

    // Are the PSI caches loaded before or after this? If we move collection to a background thread, would we clean

    // up "stale" PSI files from the caches because they haven't been found yet?



    // Called once when we know it's a Unity solution. I.e. a solution that has a Unity reference (so can be true

    // for non-generated solutions)

    public virtual void OnHasUnityReference()

    {

        // For project model access

        myLocks.AssertReadAccessAllowed();



        var externalFiles = CollectInitialFiles(true);



        try

        {

            UpdateStatistics(externalFiles);

            externalFiles.Dump();

        }

        catch (Exception e)

        {

            myLogger.Error(e);

        }



        SubscribeToPackageUpdates();

        SubscribeToProjectModelUpdates();

        myUsageStatistics.Value.FinishInitialUpdate();

    }



    private ExternalFiles CollectInitialFiles(bool initialRun)

    {

        var externalFiles = myLogger.DoCalculation("CollectExternalFiles", null,

            () =>

            {

                var roots = myRootPathLifetimes.Keys.ToList();



                foreach (var root in roots)

                {

                    myRootPathLifetimes[root].Terminate();

                    myRootPathLifetimes.Remove(root);

                }

                var files = new ExternalFiles(mySolution, myExternalProjectFileTypes, myLogger);

                CollectExternalFilesForSolutionDirectory(files, "Assets");

                CollectExternalFilesForSolutionDirectory(files, "ProjectSettings", true);

                CollectExternalFilesForPackages(files);



                // 性能优化:由于现在只处理代码和shader文件,不需要大项目检测策略

                // 原来的索引禁用策略主要针对大量的asset文件,现在这些文件已经被跳过了

                myLogger.Verbose("跳过大项目检测策略,因为只处理代码和shader文件");



                return FilterFiles(files);

            });



        myLogger.DoActivity("ProcessExternalFiles", null,

            () => AddExternalFiles(externalFiles));

        return externalFiles;

    }



    public void OnUnityProjectAdded(Lifetime projectLifetime, IProject project)

    {

        // Do nothing. A project will either be in Assets or in a package, so either way, we've got it covered.

    }



    public void TryAddExternalPsiSourceFileForMiscFilesProjectFile(PsiModuleChangeBuilder builder,

                                                                   IProjectFile projectFile)

    {

        if (IsIndexedExternalFile(projectFile.Location) && GetExternalPsiSourceFile(projectFile.Location) == null)

        {

            bool isKnownExternalFile;

            var isUserEditable = IsUserEditable(projectFile.Location, out isKnownExternalFile);

            if (!isKnownExternalFile)

            {

                myLogger.Trace("Not creating PSI source file for {0}, which is external to the solution");

                return;

            }



            Assertion.AssertNotNull(myModuleFactory.PsiModule);



            // Create the source file and add it to the change builder. In a bulk scenario, we use the contents of

            // the builder to FlushChanges and update the module, but this isn't our builder, so update the module

            // directly. Creating a new instance of CachedFileSystemData will hit the disk, but that's ok for a

            // single file.

            var fileSystemData = new CachedFileSystemData(projectFile.Location);

            var sourceFile = AddExternalPsiSourceFile(builder, projectFile.Location, projectFile.LanguageType,

                fileSystemData, isUserEditable);

            myModuleFactory.PsiModule.Add(projectFile.Location, sourceFile, null);

        }

    }



    private bool IsUserEditable(VirtualFileSystemPath path, out bool isKnownExternalFile)

    {

        isKnownExternalFile = true;

        if (mySolutionDirectory.Combine("Assets").IsPrefixOf(path))

            return true;



        var packageData = myPackageManager.GetOwningPackage(path);

        if (packageData == null)

        {

            isKnownExternalFile = false;

            return false;

        }



        return packageData.IsUserEditable;

    }



    // This method is safe to call multiple times with the same folder (or sub folder)

    private void CollectExternalFilesForSolutionDirectory(ExternalFiles externalFiles, string relativePath,

                                                          bool isProjectSettingsFolder = false)

    {

        var path = mySolutionDirectory.Combine(relativePath);

        if (path.ExistsDirectory)

            CollectExternalFilesForDirectory(externalFiles, path, true, isProjectSettingsFolder);

    }



    private void CollectExternalFilesForDirectory(ExternalFiles externalFiles, VirtualFileSystemPath directory,

                                                  bool isUserEditable, bool isProjectSettingsFolder = false)

    {

        Assertion.Assert(directory.IsAbsolute);



        // Don't process the entire solution directory - this would process Assets and Packages for a second time,

        // and also process Temp and Library, which are likely to be huge. This is unlikely, but be safe.

        if (directory == mySolutionDirectory)

        {

            myLogger.Error("Unexpected request to process entire solution directory. Skipping");

            return;

        }



        if (myRootPathLifetimes.ContainsKey(directory))

            return;



        // Make sure the directory hasn't already been processed as part of a previous directory. This shouldn't

        // happen, as we index based on folder or package, not project, so there is no way for us to see nested

        // folders

        foreach (var rootPath in myRootPathLifetimes.Keys)

        {

            if (rootPath.IsPrefixOf(directory))

                return;

        }



        myLogger.DoActivity("CollectExternalFilesForDirectory", directory.FullPath, () =>

        {

            CollectFiles(directory, externalFiles, isProjectSettingsFolder);

            externalFiles.AddDirectory(directory, isUserEditable);

            myRootPathLifetimes.Add(directory, myLifetime.CreateNested());

        });



        // Based on super simple tests, GetDirectoryEntries is faster than GetChildFiles with subsequent calls to

        // GetFileLength. But what is more surprising is that Windows in a VM is an order of magnitude FASTER than

        // Mac, on the same project!

        void CollectFiles(VirtualFileSystemPath path, ExternalFiles files, bool isProjectSettings)

        {

            var entries = path.GetDirectoryEntries();

            foreach (var entry in entries)

            {

                if (entry.IsDirectory)

                {

                    // Do not add any directory tree that ends with `~` or starts with `.`.

                    // Unity does not import these directories into the asset database

                    if (IsHiddenAssetFolder(entry))

                        continue;

                    var entryAbsolutePath = entry.GetAbsolutePath();

                    myLogger.Trace($"Processing directory {entryAbsolutePath}");

                    CollectFiles(entryAbsolutePath, files, isProjectSettings);

                }

                else

                    files.ProcessExternalFile(entry, isUserEditable, isProjectSettings);

            }

        }

    }



    private static bool IsHiddenAssetFolder(VirtualDirectoryEntryData entry)

    {

        return entry.RelativePath.FullPath.EndsWith("~") || entry.RelativePath.FullPath.StartsWith(".");

    }



    private void CollectExternalFilesForPackages(ExternalFiles externalFiles)

    {

        foreach (var kvp in myPackageManager.Packages)

        {

            var packageData = kvp.Value;

            if (packageData.PackageFolder == null || packageData.PackageFolder.IsEmpty)

                continue;



            // Index the whole of the package folder. All assets under a package are included into the Unity project

            // although only folders with a `.asmdef` will be treated as source and compiled into an assembly

            CollectExternalFilesForDirectory(externalFiles, packageData.PackageFolder, packageData.IsUserEditable);

        }

    }



    private void SubscribeToPackageUpdates()

    {

        // We've already processed all packages that were available when the project was first loaded, so this will

        // just be updating a single package at a time - Unity doesn't offer "update all".

        myPackageManager.Packages.AddRemove.Advise_NoAcknowledgement(myLifetime, args =>

        {

            var packageData = args.Value.Value;

            if (packageData.PackageFolder == null || packageData.PackageFolder.IsEmpty)

                return;



            myLogger.Verbose($"PackageUpdates {packageData.PackageFolder} {args.Action}");



            if (args.Action == AddRemove.Add)

            {

                using (myLocks.UsingReadLock())

                {

                    var externalFiles = new ExternalFiles(mySolution, myExternalProjectFileTypes, myLogger);

                    CollectExternalFilesForDirectory(externalFiles, packageData.PackageFolder,

                        packageData.IsUserEditable);

                    AddExternalFiles(FilterFiles(externalFiles));

                }

            }

            else

            {

                var psiModuleChanges = new PsiModuleChangeBuilder();

                foreach (var sourceFile in myModuleFactory.PsiModule.GetSourceFilesByRootFolder(packageData.PackageFolder))

                    psiModuleChanges.AddFileChange(sourceFile, PsiModuleChange.ChangeType.Removed);

                FlushChanges(psiModuleChanges);



                LifetimeDefinition lifetimeDefinition;

                if (!myRootPathLifetimes.TryGetValue(packageData.PackageFolder, out lifetimeDefinition))

                    myLogger.Warn("Cannot find lifetime for watched folder: {0}", packageData.PackageFolder);



                if (lifetimeDefinition != null)

                    lifetimeDefinition.Terminate();

                myRootPathLifetimes.Remove(packageData.PackageFolder);

            }

        });

    }



    private void SubscribeToProjectModelUpdates()

    {

        // If any of our external files are added to a proper project, remove them from our external files module.

        // Add them back if they're removed from the project model. This can happen if the user generates projects

        // for registry packages, or changes the settings to add .unity or .prefab to projects, or manually edits

        // the project to add the files (although this will get overwritten)

        myChangeManager.Changed.Advise(myLifetime, args =>

        {

            var solutionChanges = args.ChangeMap.GetChanges<SolutionChange>().ToList();

            if (solutionChanges.IsEmpty())

                return;



            var processedFiles = new HashSet<VirtualFileSystemPath>(); // avoid DEXP-730021 SourceFile is not valid.

            var builder = new PsiModuleChangeBuilder();

            var visitor = new RecursiveProjectModelChangeDeltaVisitor(null, itemChange =>

            {

                // Only handle changes to files in "real" projects - this means we need to remove our external file,

                // as it's no longer external to the project. Don't process Misc Files project files - these are

                // handled automatically by VS, or in response to adding/removing an external PSI file in Rider

                // (which would lead to an infinite loop).

                // Note that GetOldProject returns the project the file is being added to, or the project it has

                // just been removed from

                // Warning: Removing .Player project should not cause adding file to the ExternalModule

                if (itemChange.ProjectItem is IProjectFile projectFile

                    && IsIndexedExternalFile(projectFile.Location))

                {

                    if ((itemChange.IsAdded || itemChange.IsMovedIn) && !itemChange.ProjectItem.IsMiscProjectItem())

                    {

                        myLogger.Trace(

                            "External Unity file added to project {0}. Removing from external files module: {1}",

                            projectFile.GetProject() != null ? projectFile.GetProject().Name : "<null>", projectFile.Location);



                        RemoveExternalPsiSourceFile(builder, projectFile.Location);

                    }

                    else if ((itemChange.IsRemoved || itemChange.IsMovedOut) && itemChange.OldLocation.ExistsFile

                             && mySolution.FindProjectItemsByLocation(itemChange.OldLocation).All(t => t.IsMiscProjectItem()))

                    {

                        bool isKnownExternalFile;

                        var isUserEditable = IsUserEditable(itemChange.OldLocation, out isKnownExternalFile);

                        if (isKnownExternalFile && !processedFiles.Contains(itemChange.OldLocation))

                        {

                            myLogger.Trace(

                                "External Unity file removed from project {0}. Adding to external files module: {1}",

                                itemChange.GetOldProject() != null ? itemChange.GetOldProject().Name : "<null>", itemChange.OldLocation);

                            processedFiles.Add(itemChange.OldLocation);

                            AddOrUpdateExternalPsiSourceFile(builder, itemChange.OldLocation,

                                myProjectFileExtensions.GetFileType(projectFile.Location), isUserEditable);

                        }

                    }

                }

            });



            foreach (var solutionChange in solutionChanges)

                solutionChange.Accept(visitor);



            if (!builder.IsEmpty)

                myChangeManager.ExecuteAfterChange(() => FlushChanges(builder));

        });

    }



    private void AddExternalFiles(ExternalFiles externalFiles)

    {

        var builder = new PsiModuleChangeBuilder();

        AddExternalPsiSourceFiles(externalFiles.MetaFiles, builder, "meta");

        AddExternalPsiSourceFiles(externalFiles.AssetFiles, builder, "asset");

        AddExternalPsiSourceFiles(externalFiles.IndexableFiles, builder, "other");

        FlushChanges(builder);



        foreach (var item in externalFiles.Directories)

        {

            var path = item.directory;

            var isUserEditable = item.isUserEditable;

            var lifetime = myRootPathLifetimes[path].Lifetime;

            myFileSystemTracker.AdviseDirectoryChanges(lifetime, path, true,

                delta => OnWatchedDirectoryChange(delta, isUserEditable));

        }

    }



    private void AddExternalPsiSourceFiles(List<ExternalFile> files, PsiModuleChangeBuilder builder, string kind)

    {

        myLogger.Verbose("Adding/updating PSI source files for {0} {1} external files", files.Count, kind);



        foreach (var file in files)

        {

            AddOrUpdateExternalPsiSourceFile(builder, file.Path, file.ProjectFileType, file.IsUserEditable,

                file.FileSystemData);

        }

    }



    // Note that it is better to pass null for fileSystemData than to create your own instance. If we're updating,

    // we'll refresh in place. If we're adding a new file, we'll create fileSystemData if it's missing

    private void AddOrUpdateExternalPsiSourceFile(PsiModuleChangeBuilder builder,

                                                  VirtualFileSystemPath path,

                                                  ProjectFileType projectFileType,

                                                  bool isUserEditable,

                                                  CachedFileSystemData fileSystemData = null)

    {

        if (!UpdateExternalPsiSourceFile(builder, path, fileSystemData))

        {

            if (fileSystemData == null)

                fileSystemData = new CachedFileSystemData(path);

            AddExternalPsiSourceFile(builder, path, projectFileType, fileSystemData, isUserEditable);

        }

    }



    private IPsiSourceFile AddExternalPsiSourceFile(PsiModuleChangeBuilder builder,

                                                    VirtualFileSystemPath path,

                                                    ProjectFileType projectFileType,

                                                    CachedFileSystemData fileSystemData,

                                                    bool isUserEditable)

    {

        // Daemon processes usually check IsGeneratedFile or IsNonUserFile before running. We treat assets as

        // generated, and asmdef files as not generated (yes they're generated by the UI, but we also expect the

        // user to edit them. We do not expect the user to edit assets). We also treat the asmdef file as a

        // non-user file if it's in a read only package. The daemon won't run here, which is helpful because

        // some of the built in packages have asmdefs that reference something that isn't another asmdef, e.g.

        // "Unity.Services.Core", "Windows.UI.Input.Spatial" or "Unity.InternalAPIEditorBridge.001". I don't

        // know what these are, and they're undocumented. But because the daemon doesn't run on readonly

        // packages, we don't show any resolve errors.

        // Note that we also want to treat assets as generated because otherwise, the to do manager will try to

        // parse all YAML files, which is Bad News for massive files.

        // TODO: Mark assets as non-user files, as they should not be edited manually

        // I'm not sure what this will affect

        var properties = myExternalProjectFileTypes.ShouldBeTreatedAsNonGenerated(path)

            ? new UnityExternalFileProperties(false, !isUserEditable)

            : new UnityExternalFileProperties(true, false);



        var sourceFile = myPsiSourceFileFactory.CreateExternalPsiSourceFile(myModuleFactory.PsiModule, path,

            projectFileType, properties, fileSystemData);

        if(path.IsMeta() && path.IsFromResourceFolder())

            sourceFile.PutData(FileImagesBuilder.FileImagesBuilderAllowKey, FileImagesBuilderAllowKey.Instance);



        builder.AddFileChange(sourceFile, PsiModuleChange.ChangeType.Added);



        return sourceFile;

    }



    private bool UpdateExternalPsiSourceFile(PsiModuleChangeBuilder builder,

                                             VirtualFileSystemPath path,

                                             CachedFileSystemData fileSystemData = null)

    {

        var sourceFile = GetExternalPsiSourceFile(path);

        if (sourceFile != null)

        {

            if (sourceFile is IPsiSourceFileWithLocation sourceFileWithLocation)

            {

                // Make sure we update the cached file system data, or all of the ICache implementations will think

                // the file is already up to date. Refreshing the existing file system data will hit the disk, so

                // avoid it if the data is already available. If the data is not available, pass null, and we'll

                // refresh the existing data in place without another allocation

                var existingFileSystemData = sourceFileWithLocation.GetCachedFileSystemData();

                if (fileSystemData != null)

                {

                    existingFileSystemData.FileAttributes = fileSystemData.FileAttributes;

                    existingFileSystemData.FileExists = fileSystemData.FileExists;

                    existingFileSystemData.FileLength = fileSystemData.FileLength;

                    existingFileSystemData.LastWriteTimeUtc = fileSystemData.LastWriteTimeUtc;

                }

                else

                    existingFileSystemData.Load(path);

            }



            builder.AddFileChange(sourceFile, PsiModuleChange.ChangeType.Modified);

            return true;

        }



        return false;

    }



    private void RemoveExternalPsiSourceFile(PsiModuleChangeBuilder builder, VirtualFileSystemPath path)

    {

        var sourceFile = GetExternalPsiSourceFile(path);

        if (sourceFile != null)

            builder.AddFileChange(sourceFile, PsiModuleChange.ChangeType.Removed);

    }



    private void UpdateStatistics(ExternalFiles externalFiles)

    {

        var usageStatistics = myUsageStatistics.Value;

        foreach (var externalFile in externalFiles.AssetFiles)

        {

            UnityAssetInfoCollector.FileType? fileType = null;

            if (externalFile.Path.IsAsset())

                fileType = UnityAssetInfoCollector.FileType.Asset;

            else if (externalFile.Path.IsPrefab())

                fileType = UnityAssetInfoCollector.FileType.Prefab;

            else if (externalFile.Path.IsScene())

                fileType = UnityAssetInfoCollector.FileType.Scene;

            else if (externalFile.Path.IsAnim())

                fileType = UnityAssetInfoCollector.FileType.Anim;

            else if (externalFile.Path.IsController())

                fileType = UnityAssetInfoCollector.FileType.Controller;



            if (fileType.HasValue)

            {

                usageStatistics.AddStatistic(fileType.Value, externalFile.FileSystemData.FileLength,

                    externalFile.IsUserEditable);

            }

        }



        foreach (var externalFile in externalFiles.MetaFiles)

        {

            usageStatistics.AddStatistic(UnityAssetInfoCollector.FileType.Meta,

                externalFile.FileSystemData.FileLength, externalFile.IsUserEditable);

        }



        foreach (var externalFile in externalFiles.IndexableFiles)

        {

            UnityAssetInfoCollector.FileType fileType;

            if (ReferenceEquals(externalFile.ProjectFileType, AsmDefProjectFileType.Instance))

                fileType = UnityAssetInfoCollector.FileType.AsmDef;

            else if (ReferenceEquals(externalFile.ProjectFileType, AsmRefProjectFileType.Instance))

                fileType = UnityAssetInfoCollector.FileType.AsmRef;

            else if (ReferenceEquals(externalFile.ProjectFileType, InputActionsProjectFileType.Instance))

                fileType = UnityAssetInfoCollector.FileType.InputActions;

            else

                continue;

            usageStatistics.AddStatistic(fileType, externalFile.FileSystemData.FileLength, externalFile.IsUserEditable);

        }



        foreach (var externalFile in externalFiles.KnownBinaryAssetFiles)

        {

            usageStatistics.AddStatistic(UnityAssetInfoCollector.FileType.KnownBinary,

                externalFile.FileSystemData.FileLength, externalFile.IsUserEditable);

        }



        foreach (var externalFile in externalFiles.ExcludedByNameAssetFiles)

        {

            usageStatistics.AddStatistic(UnityAssetInfoCollector.FileType.ExcludedByName,

                externalFile.FileSystemData.FileLength, externalFile.IsUserEditable);

        }

    }

    private bool IsIndexedExternalFile(VirtualFileSystemPath path)

    {

        return IsIndexedWithCurrentIndexingSupport(path) && !IsBinaryAsset(path) && !IsAssetExcludedByName(path);

    }



    private static bool IsBinaryAsset(VirtualDirectoryEntryData directoryEntry)

    {

        if (IsKnownBinaryAssetByName(directoryEntry.RelativePath))

            return true;



        // If a .asset file is over a certain size, sniff the header to see if it's binary or YAML. Otherwise, we

        // treat the files as text

        return directoryEntry.Length > AssetFileCheckSizeThreshold && directoryEntry.RelativePath.IsAsset() &&

               !directoryEntry.GetAbsolutePath().SniffYamlHeader();

    }



    private static bool IsBinaryAsset(VirtualFileSystemPath path)

    {

        if (IsKnownBinaryAssetByName(path))

            return true;



        if (!path.ExistsFile)

            return false;



        // If a .asset file is over a certain size, sniff the header to see if it's binary or YAML. Otherwise, we

        // treat the files as text

        var fileLength = path.GetFileLength();

        return fileLength > AssetFileCheckSizeThreshold && path.IsAsset() && !path.SniffYamlHeader();

    }



    private static bool IsKnownBinaryAssetByName(IPath path)

    {

        // Even if the project is set to ForceText, some files will always be binary, notably LightingData.asset.

        // Users can also force assets to serialise as binary with the [PreferBinarySerialization] attribute

        return path.Name.Equals("LightingData.asset", StringComparison.InvariantCultureIgnoreCase);

    }



    private static bool IsAssetExcludedByName(IPath path)

    {

        // NavMesh.asset can sometimes be binary, sometimes text. I don't know the criteria for when one format is

        // picked over another. OcclusionCullingData.asset is usually text, but large and contains long streams of

        // ascii-based "binary". Neither file contains anything we're interested in, and simply increases parsing

        // and indexing time

        var filename = path.Name;

        return filename.Equals("NavMesh.asset", StringComparison.InvariantCultureIgnoreCase)

               || filename.Equals("OcclusionCullingData.asset", StringComparison.InvariantCultureIgnoreCase);

    }



    private void OnWatchedDirectoryChange(FileSystemChangeDelta delta, bool isDirectoryUserEditable)

    {

        myLocks.ExecuteOrQueue(Lifetime.Eternal, "UnityExternalFilesModuleProcessor::OnWatchedDirectoryChange",

            () =>

            {

                using (ReadLockCookie.Create())

                {

                    var builder = new PsiModuleChangeBuilder();

                    ProcessFileSystemChangeDelta(delta, builder, isDirectoryUserEditable);

                    FlushChanges(builder);

                }

            });

    }



    private void ProcessFileSystemChangeDelta(FileSystemChangeDelta delta, PsiModuleChangeBuilder builder,

                                              bool isDirectoryUserEditable)

    {

        // For project model access

        myLocks.AssertReadAccessAllowed();



        // Note that we watch for changes in all folders in a Unity solution - Assets and packages, wherever they

        // are - Packages, Library/PackageCache, file:, etc. Any package folder might contain projects, even read

        // only packages, if project generation is enabled. Therefore any file might be in a project. Check when

        // adding, but we don't need to for update and remove as they will only work on known files.

        switch (delta.ChangeType)

        {

            // We can get ADDED for a file we already know about if an app saves the file by saving to a temp file

            // first. We don't get a DELETED first, surprisingly. Treat this scenario like CHANGED

            case FileSystemChangeType.ADDED:

                if (IsIndexedExternalFile(delta.NewPath) &&

                    !mySolution.FindProjectItemsByLocation(delta.NewPath).Any())

                {

                    // Note that ExtensionWithDot allocates, which can hurt if we have to process thousands of files

                    // We should be safe here - we'll only receive this event for watched folders, so we don't

                    // expect thousands of files to suddenly appear. A new package might introduce that many files,

                    // but new packages are handled with a separate notification

                    var projectFileType = myProjectFileExtensions.GetFileType(delta.NewPath.ExtensionWithDot);

                    AddOrUpdateExternalPsiSourceFile(builder, delta.NewPath, projectFileType, isDirectoryUserEditable);

                }

                break;



            case FileSystemChangeType.DELETED:

                RemoveExternalPsiSourceFile(builder, delta.OldPath);

                break;



            // We can get RENAMED if an app saves the file by saving to a temporary name first, then renaming

            case FileSystemChangeType.CHANGED:

            case FileSystemChangeType.RENAMED:

                UpdateExternalPsiSourceFile(builder, delta.NewPath);

                break;



            case FileSystemChangeType.SUBTREE_CHANGED:

            case FileSystemChangeType.UNKNOWN:

                break;

        }



        foreach (var child in delta.GetChildren())

            ProcessFileSystemChangeDelta(child, builder, isDirectoryUserEditable);

    }



    private IPsiSourceFile GetExternalPsiSourceFile(VirtualFileSystemPath path)

    {

        Assertion.AssertNotNull(myModuleFactory.PsiModule);

        IPsiSourceFile sourceFile;

        return myModuleFactory.PsiModule.TryGetFileByPath(path, out sourceFile) ? sourceFile : null;

    }



    private void FlushChanges(PsiModuleChangeBuilder builder)

    {

        myLocks.ReentrancyGuard.AssertGuarded();



        if (builder.IsEmpty)

            return;



        var module = myModuleFactory.PsiModule;

        Assertion.AssertNotNull(module);

        myLocks.AssertMainThread();

        using (myLocks.UsingWriteLock())

        {

            var psiModuleChange = builder.Result;



            myLogger.Verbose("Flushing {0} PSI source file changes", psiModuleChange.FileChanges.Count);

            if (myLogger.IsTraceEnabled())

            {

                myLogger.Verbose("{0} added, {1} removed, {2} modified, {3} invalidated",

                    psiModuleChange.FileChanges.Count(c => c.Type == PsiModuleChange.ChangeType.Added),

                    psiModuleChange.FileChanges.Count(c => c.Type == PsiModuleChange.ChangeType.Removed),

                    psiModuleChange.FileChanges.Count(c => c.Type == PsiModuleChange.ChangeType.Modified),

                    psiModuleChange.FileChanges.Count(c => c.Type == PsiModuleChange.ChangeType.Invalidated));

            }



            foreach (var fileChange in psiModuleChange.FileChanges)

            {

                var location = fileChange.Item.GetLocation();

                if (location.IsEmpty)

                    continue;



                switch (fileChange.Type)

                {

                    case PsiModuleChange.ChangeType.Added:

                        module.Add(location, fileChange.Item, null);

                        break;



                    case PsiModuleChange.ChangeType.Removed:

                        module.Remove(location);

                        break;

                }

            }



            myLogger.DoActivity("FlushChanges::OnProviderChanged", null, () =>

                myChangeManager.OnProviderChanged(this, psiModuleChange, SimpleTaskExecutor.Instance));

        }

    }



    public object Execute(IChangeMap changeMap)

    {

        return null;

    }



    public struct ExternalFile

    {

        public readonly VirtualFileSystemPath Path;

        public readonly CachedFileSystemData FileSystemData;

        public readonly ProjectFileType ProjectFileType;

        public readonly bool IsUserEditable;



        public ExternalFile(VirtualDirectoryEntryData directoryEntry, ProjectFileType projectFileType,

                            bool isUserEditable)

        {

            Path = directoryEntry.GetAbsolutePath();

            FileSystemData = new CachedFileSystemData(directoryEntry);

            ProjectFileType = projectFileType;

            IsUserEditable = isUserEditable;

        }

    }



    private class ExternalFiles

    {

        private readonly ISolution mySolution;

        private readonly UnityExternalProjectFileTypes myProjectFileTypes;

        private readonly ILogger myLogger;

        public readonly List<ExternalFile> MetaFiles = new List<ExternalFile>();

        public readonly List<ExternalFile> AssetFiles = new List<ExternalFile>();

        public readonly List<ExternalFile> IndexableFiles = new List<ExternalFile>();

        public FrugalLocalList<ExternalFile> KnownBinaryAssetFiles;

        public FrugalLocalList<ExternalFile> ExcludedByNameAssetFiles;

        public FrugalLocalList<(VirtualFileSystemPath directory, bool isUserEditable)> Directories;



        public ExternalFiles(ISolution solution, UnityExternalProjectFileTypes projectFileTypes, ILogger logger)

        {

            mySolution = solution;

            myProjectFileTypes = projectFileTypes;

            myLogger = logger;

        }



        /// <summary>

        /// 判断文件是否为代码或shader文件,用于性能优化

        /// </summary>

        private static bool IsCodeOrShaderFile(IPath filePath)

        {

            var extension = filePath.ExtensionWithDot.ToLowerInvariant();

            // 只处理代码和shader相关的文件

            if (extension == ".cs")

                return true;

            if (extension == ".shader")

                return true;

            if (extension == ".cginc")

                return true;

            if (extension == ".hlsl")

                return true;

            if (extension == ".asmdef")

                return true;

            if (extension == ".asmref")

                return true;

            if (extension == ".json" && filePath.Name.Equals("manifest.json", StringComparison.OrdinalIgnoreCase))

                return true;

            return false;

        }



        public void ProcessExternalFile(VirtualDirectoryEntryData directoryEntry,

                                        bool isUserEditable, bool isProjectSettingsAsset)

        {

            mySolution.Locks.AssertReadAccessAllowed();



            // 性能优化:只处理代码和shader文件,跳过所有其他文件类型

            if (!IsCodeOrShaderFile(directoryEntry.RelativePath))

                return;



            // 只处理代码相关的文件类型

            UnityExternalProjectFileTypes.FileInfo info;

            if (myProjectFileTypes.TryGetFileInfo(directoryEntry.RelativePath, out info))

            {

                // Do not add if this file is already part of a project. This might be because it's from an editable

                // package, or because the user has package project generation enabled.

                // It might be part of the Misc Files project (not sure why - perhaps cached, perhaps because the

                // file was already open at startup), in which case, add a PsiSourceFile if it doesn't already exist

                // These checks require a read lock!

                var existingProjectFile = mySolution.FindProjectItemsByLocation(directoryEntry.GetAbsolutePath())

                    .FirstOrDefault() as IProjectFile;

                var existingPsiSourceFile = existingProjectFile != null ? existingProjectFile.ToSourceFile() : null;

                if (existingPsiSourceFile != null)

                {

                    myLogger.Warn("Found existing project file for {0} with existing PSI source file (IsMiscProjectItem: {1})",

                        directoryEntry.GetAbsolutePath(), existingProjectFile.IsMiscProjectItem());

                }

                else

                {

                    IndexableFiles.Add(new ExternalFile(directoryEntry, info.ProjectFileType, isUserEditable));

                }

            }

        }



        public void AddDirectory(VirtualFileSystemPath directory, bool isUserEditable)

        {

            Directories.Add((directory, isUserEditable));

        }



        public void Dump()

        {

            if (!myLogger.IsTraceEnabled()) return;



            // 性能优化:现在只收集代码和shader文件

            var total = IndexableFiles.Count;

            myLogger.Trace("Collected {0} code/shader files (performance optimized)", total);

            myLogger.Trace("Code/shader files: {0} ({1:n0} bytes)", IndexableFiles.Count, GetTotalFileSize(IndexableFiles));

            myLogger.Trace("Directories: {0}", Directories.Count);

            myLogger.Trace("Skipped all asset, meta, and other non-code files for performance");

        }



        private static ulong GetTotalFileSize(IEnumerable<ExternalFile> files)

        {

            return files.Aggregate(0UL, (s, f) => s + (ulong) f.FileSystemData.FileLength);

        }

    }

}

}
Rider For Unity插件反编译
不知道从哪个版本起,Unity插件被内置到Rider了,可以从Rider主界面-帮助-特殊文件和文件夹,看到所有路径,而Rider For Unity位于C:\Users\UserName\AppData\Local\Programs\Rider\plugins\rider-unity
根据命名空间原则,我们直接找到JetBrains.ReSharper.Plugins.Unity.dll进行修改,用dnspy打开,并添加基础dll:全都位于C:\Users\UserName\AppData\Local\Programs\Rider\lib\ReSharperHost中,直接拖到dnspy窗口即可
在dnspy定位到JetBrains.ReSharper.Plugins.Unity.Core.Psi.Modules.UnityExternalFilesModuleProcessor文件
修改代码,并编译保存,如果有报错直接注释整个函数内容,只保留空函数体即可,反正我们不需要这些乱七八糟的功能
重新打开Rider,原本几十分钟的初始化时间,现在几分钟直接搞定!

拓展
当然,这只是核心问题被解决,实际上可以继续优化,比如让Cursor帮我们将文件扫描的入口整个关掉,但是不确定会不会导致其他问题,大家可以自行尝试

文章作者: 烟雨迷离半世殇
文章链接: https://www.lfzxb.top/resharper-unity-fix/

————————————————

​最后我们放松一下眼睛
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值