介绍

这是一个用来查找资源引用和依赖的插件,通过缓存来保存资源间的引用信息,通过树状结构直观的展示。

在试用后发现,这个插件不光可以查找反向引用,也可以查找正向引用,通过模式按钮可以一键切换。

这里有一篇功能介绍很完整的文章:Unity引用查找工具开源库 - UWA Blog

这个插件功能非常强大,直接替换掉了 Unity 查找资源引用 - 狂飙Unity 资源引用 - 狂飙

项目的问题

项目的功能很好,但是有一些问题没有处理。

支持的 Unity 版本

项目本身未明确标明使用的 Unity 版本,导致用户在遇到错误时无法确定是否是版本问题。

实际测试显示,项目应该是在 Unity 2018 以上使用,在 Unity 2017 上使用会有 Hash128 无法序列化的错误。

开源协议

项目没有标明使用的开源协议,建议使用 MIT。

用户的问题

在 UWA 曝光后从评论数据可以看到这个库很受欢迎,大家在使用的时候遇到了一些问题,但是提出来的问题包含的内容却不够全面,导致无法查找问题原因。

  • 未说明使用的 Unity 版本
  • 未提供完整的出错堆栈
  • 没有提供最小可重现工程
  • 在评论里提问题而没有去正确的 GitHub Issue 中提问题
  • 错误堆栈没有使用代码标记包起来导致显示格式错误

建议提问题前先去阅读 提问的智慧 - GitHub,一个烂问题会永远烂在那里,而一个好的问题是会吸引他人去解决的。

正确的贡献方式

有人回答了问题如何解决,但是更好的做法是提供一个 Pull Request 来修复这个问题,这样其他人都可以直接享用成果。

程序员即是用户,也是开发者。

  • 如果不可以自行修复问题,那么就可以按照提问的智慧提出一个清晰的问题,让他人修复。
  • 如果可以自行修复问题,那么就将问题修复,并提交 Pull Request 来反馈项目。

增强与改进

循环依赖

似乎是从 Unity 2018 开始,场景依赖的 LightingData.asset 资源保存了对所在场景的引用,造成了循环依赖,在 Unity 2018.4.0f1 实测如此。

即使除了场景与光照贴图信息之间的循环依赖,项目在制作过程中也可能会存在其他循环依赖,如自定义的资源之间、字体与其 Fallback 字体之间等等,因此最好能兼容循环依赖这个问题。

查看代码发现只在 CreateTree 有递归调用,因此这里使用一个栈检查是否存在循环依赖。

兼容性问题

Unity 2017 中无法使用,是由于 UnityEngine.Hash128 无法序列化

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
System.Runtime.Serialization.SerializationException: Type UnityEngine.Hash128 is not marked as Serializable.
  at System.Runtime.Serialization.Formatters.Binary.BinaryCommon.CheckSerializable (System.Type type, ISurrogateSelector selector, StreamingContext context) [0x0002c] in /Users/builduser/buildslave/mono/build/mcs/class/corlib/System.Runtime.Serialization.Formatters.Binary/BinaryCommon.cs:119 
  at System.Runtime.Serialization.Formatters.Binary.ObjectWriter.GetObjectData (System.Object obj, System.Runtime.Serialization.Formatters.Binary.TypeMetadata& metadata, System.Object& data) [0x00054] in /Users/builduser/buildslave/mono/build/mcs/class/corlib/System.Runtime.Serialization.Formatters.Binary/ObjectWriter.cs:386 
  at System.Runtime.Serialization.Formatters.Binary.ObjectWriter.WriteObject (System.IO.BinaryWriter writer, Int64 id, System.Object obj) [0x00000] in /Users/builduser/buildslave/mono/build/mcs/class/corlib/System.Runtime.Serialization.Formatters.Binary/ObjectWriter.cs:306 
  at System.Runtime.Serialization.Formatters.Binary.ObjectWriter.WriteObjectInstance (System.IO.BinaryWriter writer, System.Object obj, Boolean isValueObject) [0x00062] in /Users/builduser/buildslave/mono/build/mcs/class/corlib/System.Runtime.Serialization.Formatters.Binary/ObjectWriter.cs:293 
  at System.Runtime.Serialization.Formatters.Binary.ObjectWriter.WriteValue (System.IO.BinaryWriter writer, System.Type valueType, System.Object val) [0x0006b] in /Users/builduser/buildslave/mono/build/mcs/class/corlib/System.Runtime.Serialization.Formatters.Binary/ObjectWriter.cs:748 
  at System.Runtime.Serialization.Formatters.Binary.ObjectWriter.WriteGenericArray (System.IO.BinaryWriter writer, Int64 id, System.Array array) [0x000e3] in /Users/builduser/buildslave/mono/build/mcs/class/corlib/System.Runtime.Serialization.Formatters.Binary/ObjectWriter.cs:519 
  at System.Runtime.Serialization.Formatters.Binary.ObjectWriter.WriteArray (System.IO.BinaryWriter writer, Int64 id, System.Array array) [0x00085] in /Users/builduser/buildslave/mono/build/mcs/class/corlib/System.Runtime.Serialization.Formatters.Binary/ObjectWriter.cs:471 
  at System.Runtime.Serialization.Formatters.Binary.ObjectWriter.WriteObjectInstance (System.IO.BinaryWriter writer, System.Object obj, Boolean isValueObject) [0x0004f] in /Users/builduser/buildslave/mono/build/mcs/class/corlib/System.Runtime.Serialization.Formatters.Binary/ObjectWriter.cs:290 
  at System.Runtime.Serialization.Formatters.Binary.ObjectWriter.WriteQueuedObjects (System.IO.BinaryWriter writer) [0x00005] in /Users/builduser/buildslave/mono/build/mcs/class/corlib/System.Runtime.Serialization.Formatters.Binary/ObjectWriter.cs:271 
  at System.Runtime.Serialization.Formatters.Binary.ObjectWriter.WriteObjectGraph (System.IO.BinaryWriter writer, System.Object obj, System.Runtime.Remoting.Messaging.Header[] headers) [0x0001f] in /Users/builduser/buildslave/mono/build/mcs/class/corlib/System.Runtime.Serialization.Formatters.Binary/ObjectWriter.cs:256 
  at System.Runtime.Serialization.Formatters.Binary.BinaryFormatter.Serialize (System.IO.Stream serializationStream, System.Object graph, System.Runtime.Remoting.Messaging.Header[] headers) [0x000a4] in /Users/builduser/buildslave/mono/build/mcs/class/corlib/System.Runtime.Serialization.Formatters.Binary/BinaryFormatter.cs:232 
  at System.Runtime.Serialization.Formatters.Binary.BinaryFormatter.Serialize (System.IO.Stream serializationStream, System.Object graph) [0x00000] in /Users/builduser/buildslave/mono/build/mcs/class/corlib/System.Runtime.Serialization.Formatters.Binary/BinaryFormatter.cs:211 
  at ReferenceFinderData.WriteToChache () [0x00154] in Assets/Editor/ReferenceFinder/ReferenceFinder/ReferenceFinderData.cs:179 
  at ReferenceFinderData.CollectDependenciesInfo () [0x0009e] in Assets/Editor/ReferenceFinder/ReferenceFinder/ReferenceFinderData.cs:39 
UnityEngine.Debug:LogError(Object)
ReferenceFinderData:CollectDependenciesInfo() (at Assets/Editor/ReferenceFinder/ReferenceFinder/ReferenceFinderData.cs:47)
ReferenceFinderWindow:InitDataIfNeeded() (at Assets/Editor/ReferenceFinder/ReferenceFinder/ReferenceFinderWindow.cs:63)
ReferenceFinderWindow:FindRef() (at Assets/Editor/ReferenceFinder/ReferenceFinder/ReferenceFinderWindow.cs:38)

项目使用了 BinaryFormatter 来序列化缓存文件,BinaryFormatter 要求序列化的类必须使用 System.Serializable 属性修饰; 缓存文件的内容中包含了 Unity 自带的类 Hash128,此类在 Unity 2017 中并未使用 System.Serializable 属性修饰,而在 Unity 2018 中使用了 System.Serializable 属性修饰。

首先想到的是自定义一个 SerializableHash128 类,使用 System.Serializable 属性修饰,然后在比较时使用自定义的 operator == 来处理。这样的好处是序列化后体积小,只有 128 位。 但是实际查看代码的时候才发现,所有用到的 4 个 uint 都是私有字段,无法编写 operator == 进行比较,而且此类中的其他方法(如 GetHashCode)也不能保证值是唯一的。

1
2
3
4
public override int GetHashCode()
{
  return this.m_u32_0.GetHashCode() ^ this.m_u32_1.GetHashCode() ^ this.m_u32_2.GetHashCode() ^ this.m_u32_3.GetHashCode();
}

由于此类还提供了与字符串转换的两个方法

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
/// <summary>
///   <para>Convert Hash128 to string.</para>
/// </summary>
public override string ToString()
{
  return Hash128.Internal_Hash128ToString(this);
}

/// <summary>
///   <para>Convert the input string to Hash128.</para>
/// </summary>
/// <param name="hashString"></param>
[FreeFunction("StringToHash128")]
public static Hash128 Parse(string hashString)
{
  Hash128 ret;
  Hash128.Parse_Injected(hashString, out ret);
  return ret;
}

因此最终使用 string 保存此值。Hash128 最终保存成 32 个字符,合计 256 位,因此缓存会增大不少。

项目大概 20000 个资源文件,缓存文件大小约为 2MiB。

1
2
3
4
5
6
$ find Assets -type f -not -name '*.meta' | wc -l
   20534

$ ls -l Library
-rw-r--r--    1 mac  staff   1946145 Apr 22 14:19 ReferenceFinderCacheBinary
-rw-r--r--    1 mac  staff   1903294 Apr 22 14:23 ReferenceFinderCacheText

结果令人意外,居然使用 string 保存 Hash128 体积更小一点儿。

另外在实际编写代码时又遇到了无法加载旧版本缓存的问题,因此增加缓存版本字段,读取缓存版本字段发现与当前代码缓存版本不一致或者由于不存在此字段出现异常时直接忽略缓存,这样就可以兼容旧版本。

改完后在 Unity 2017.4.35f1 5.6.6f2 中测试功能完全正常。

忽略非项目引用

资源有可能会引用 Unity 安装目录下的脚本或其他资源,因此在记录引用信息时应该将其过滤掉,直接将所有路径不是 Assets/ 开头的过滤掉即可。

忽略目录引用

在获取依赖时有可能会取到目录,但是前一步在加入所有资源时特意忽略了目录,因此在获取依赖时需要过滤掉目录,使用一个 Where 就可以做到:

1
2
3
int[] indexes = assetDict[guid].dependencies.
    Where(s => guidIndex.ContainsKey(s)).
    Select(s => guidIndex[s]).ToArray();

右键展开收起

默认是双击选中资源,但是展开与收起需要点击三角按钮,实在是太小不方便点击,因此直接覆写 TreeView 的方法就可以实现右键展开收起:

1
2
3
4
protected override void ContextClickedItem(int id)
{
    SetExpanded(id, !IsExpanded(id));
}

菜单与快捷键

修改菜单名称为 Assets/Find References In Project,同时将其放在 Assets/Find References In Scene 下面,并增加快捷键 Ctrl+Alt+Shift+F

代码

以上所有改动都已提交: