【Unity】特定の型のアセットの依存情報をJSONで保存する

特定の型の依存情報のみを高速で取得したい案件があったので,
JSONで依存情報を保存しておき, 必要な時にデシリアライズして使用します.

ソースコード

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using UnityEditor;
using UnityEngine;

namespace DependenceData
{
    public class DependenceData : AssetPostprocessor
    {
        private const string ROOT_DIR_NAME = "DependenceData";
        private static List<Type> ObserveType = new List<Type>() { typeof(MonoScript), typeof(GameObject), };

        [MenuItem("Func/DependenceData")]
        private static void Execute()
        {
            foreach (var type in ObserveType)
            {
                Initialize(type);
            }
        }

        public static void Initialize(Type type)
        {
            var guids = AssetDatabase.FindAssets($"t: {type.Name}").ToList();
            UpdateGuids(guids);
        }

        private static void AddGuid(string addedGuid)
        {
            // 依存先の被依存リストに追加.
            var guidList = GetDependenciesAsGuidByGuid(addedGuid);
            foreach (var guid in guidList)
            {
                TryGetSavedGuids(guid, out var savedGuids);
                savedGuids ??= new Guids(); // ファイルが無ければ作成.
                savedGuids.AddFromDependents(addedGuid);

                Save(guid, savedGuids);
            }

            // 依存先情報を保存.
            var addedGuids = new Guids();
            addedGuids.UpdateToDependencies(guidList);
            Save(addedGuid, addedGuids);
        }

        private static void RemoveGuids(List<string> removedGuidList)
        {
            if (removedGuidList == null)
            {
                return;
            }

            foreach (var removedGuid in removedGuidList)
            {
                // 依存先の被依存リストから削除.
                TryGetSavedGuids(removedGuid, out var guids);
                guids ??= new Guids();
                foreach (var guid in guids.m_ToDependencies)
                {
                    if (TryGetSavedGuids(guid, out var savedGuids))
                    {
                        savedGuids.RemoveFromDependents(removedGuid);
                        Save(guid, savedGuids);
                    }
                }

                DeleteSaveData(removedGuid);
            }
        }

        private static void UpdateGuids(List<string> guidList)
        {
            if (guidList == null)
            {
                return;
            }

            foreach (var guid in guidList)
            {
                var dependencies = GetDependenciesAsGuidByGuid(guid);
                // 既に管理対象の guid であれば, 関連ファイルを更新する.
                if (TryGetSavedGuids(guid, out var savedGuids))
                {
                    foreach (var added in dependencies)
                    {
                        // 依存先の被依存リストに書き込む.
                        TryGetSavedGuids(added, out var guids);
                        guids ??= new Guids();
                        guids.AddFromDependents(guid);
                        Save(added, guids);
                    }

                    foreach (var removed in savedGuids.GetExceptedToDependencies(dependencies))
                    {
                        // 依存先の被依存から消す.
                        if (TryGetSavedGuids(removed, out var guids))
                        {
                            guids.RemoveFromDependents(guid);
                            Save(removed, guids);
                        }
                    }

                    savedGuids.UpdateToDependencies(dependencies);
                    Save(guid, savedGuids);
                }
                else
                {
                    // 新規追加の guid.
                    AddGuid(guid);
                }
            }
        }

        private static IEnumerable<string> GetDependenciesAsGuidByGuid(string guid)
        {
            var path = AssetDatabase.GUIDToAssetPath(guid);
            return AssetDatabase.GetDependencies(path, false)
                .Select(path => AssetDatabase.AssetPathToGUID(path));
        }

        public static List<string> GetFromDependentsByPath(string path)
        {
            return GetFromDepenets(AssetDatabase.AssetPathToGUID(path));
        }

        public static List<string> GetFromDepenets(string guid)
        {
            TryGetSavedGuids(guid, out var guids);
            return guids.m_FromDependents ?? new List<string>();
        }

        private static bool TryGetSavedGuids(string guid, out Guids guids)
        {
            guids = null;
            var filePath = GetGuidPath(guid);
            if (File.Exists(filePath))
            {
                var json = File.ReadAllText(filePath);
                guids = JsonUtility.FromJson<Guids>(json);
                return true;
            }

            return false;
        }

        private static void Save(string fileGuid, Guids guids)
        {
            var filePath = GetGuidPath(fileGuid);
            CreateDirectory(filePath);
            var json = JsonUtility.ToJson(guids, true);
            File.WriteAllText(filePath, json);
        }

        private static void DeleteSaveData(string guid)
        {
            var path = GetGuidPath(guid);
            if (!File.Exists(path))
            {
                return;
            }

            File.Delete(path);
        }

        private static string GetRootPath()
        {
            var projectPath = Directory.GetParent(Application.dataPath).FullName.Replace("\\", "/");
            return $"{projectPath}/{ROOT_DIR_NAME}";
        }

        private static string GetGuidPath(string guid)
        {
            return $"{GetRootPath()}/{guid.Substring(0, 2)}/{guid}.json";
        }

        private static void CreateDirectory(string filePath)
        {
            var dir = Directory.GetParent(filePath).FullName.Replace("\\", "/");
            if (Directory.Exists(dir))
            {
                return;
            }

            Directory.CreateDirectory(dir);
        }

        private static void OnPostprocessAllAssets(string[] importedAssets, string[] deletedAssets, string[] movedAssets, string[] movedFromAssetPaths)
        {
            var importedAssetsGuids = importedAssets
                .Where(path => ObserveType.Contains(AssetDatabase.GetMainAssetTypeAtPath(path)))
                .Select(path => AssetDatabase.AssetPathToGUID(path))
                .ToList();
            UpdateGuids(importedAssetsGuids);

            var deletedAssetsGuids = deletedAssets
                .Where(path => ObserveType.Contains(AssetDatabase.GetMainAssetTypeAtPath(path)))
                .Select(path => AssetDatabase.AssetPathToGUID(path))
                .ToList();
            RemoveGuids(deletedAssetsGuids);
        }

        [Serializable]
        private class Guids
        {
            // 被依存
            public List<string> m_FromDependents = new();
            // 依存先
            public List<string> m_ToDependencies = new();

            public Guids() { }

            // 非依存を追加
            public void AddFromDependents(string guid)
            {
                if (m_FromDependents.Contains(guid))
                {
                    return;
                }

                m_FromDependents.Add(guid);
            }

            // 被依存を削除
            public void RemoveFromDependents(string guid)
            {
                m_FromDependents.Remove(guid);
            }

            // 被依存を削除
            public void RemoveFromDependents(Guids guids)
            {
                if (guids == null)
                {
                    return;
                }

                foreach (var guid in guids.m_FromDependents)
                {
                    m_FromDependents.Remove(guid);
                }
            }

            // 依存先を更新.
            public void UpdateToDependencies(IEnumerable<string> guidList)
            {
                if (guidList == null)
                {
                    return;
                }

                m_ToDependencies.Clear();
                m_ToDependencies.AddRange(guidList);
            }

            // 非依存・依存先を追加
            public void Add(Guids guids)
            {
                if (guids == null)
                {
                    return;
                }

                foreach (var guid in guids.m_FromDependents)
                {
                    AddFromDependents(guid);
                }

                UpdateToDependencies(guids.m_ToDependencies);
            }

            // guidList にのみ存在する guid リストを返す
            public IEnumerable<string> GetExceptedToDependencies(IEnumerable<string> guidList)
            {
                if (guidList == null)
                {
                    return new List<string>();
                }

                return m_ToDependencies.Except(guidList);
            }
        }
    }
}