using System.Collections.Generic; using UnityEngine; #if UNITY_EDITOR using UnityEditor; #endif public static class EquipmentSkillGroupLibrary { private static readonly string[] SpecialResourcesPaths = { "so/skill_equipSkills/mmrEffectSkills", "so/skill_equipSkills/\u8bb0\u5fc6\u7279\u6548\u6280\u80fd" }; private static readonly string[] IllusionResourcesPaths = { "so/skill_equipSkills/illusionSkills", "so/skill_equipSkills/\u5e7b\u5316\u6280\u80fd" }; #if UNITY_EDITOR private static readonly string[] SpecialAssetFolders = { "Assets/Resources/so/skill_equipSkills/mmrEffectSkills", "Assets/Resources/so/skill_equipSkills/\u8bb0\u5fc6\u7279\u6548\u6280\u80fd" }; private static readonly string[] IllusionAssetFolders = { "Assets/Resources/so/skill_equipSkills/illusionSkills", "Assets/Resources/so/skill_equipSkills/\u5e7b\u5316\u6280\u80fd" }; #endif public static SkillGroup ResolveSpecialSkillGroup(int skillGroupId) { return ResolveSkillGroup(skillGroupId, EquipmentSkillGroupSO.EquipmentSkillCategory.Special); } public static SkillGroup ResolveIllusionSkillGroup(int skillGroupId) { return ResolveSkillGroup(skillGroupId, EquipmentSkillGroupSO.EquipmentSkillCategory.Illusion); } public static SkillGroup ResolveSkillGroup(int skillGroupId) { SkillGroup group = ResolveSpecialSkillGroup(skillGroupId); if (group != null) { return group; } return ResolveIllusionSkillGroup(skillGroupId); } public static SkillGroup ResolveSkillGroup(int skillGroupId, EquipmentSkillGroupSO.EquipmentSkillCategory category) { if (skillGroupId <= 0) { return null; } EquipmentSkillGroupSO asset = FindAsset(skillGroupId, category); return asset != null ? asset.skillGroup : null; } // --- Runtime cache ------------------------------------------------------- // Equipment skill-group assets are static config loaded from Resources and never // mutated at runtime, so both the per-category asset list and the id->asset resolution // are memoized during play mode. This turns a per-note/per-trigger Resources.LoadAll + // collection-allocation storm into O(1) dictionary lookups. The editor (!isPlaying) // path stays uncached so asset edits are always reflected; the cache is cleared on // play-mode start via RuntimeInitializeOnLoadMethod so a fresh session never sees stale // entries from a prior play session (domain-reload-disabled projects). private static readonly Dictionary> s_assetsByCategory = new Dictionary>(); // Keyed by (category, skillGroupId); caches both hits and misses (null value = confirmed miss). private static readonly Dictionary s_findAssetCache = new Dictionary(); [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.SubsystemRegistration)] private static void ResetRuntimeCache() { s_assetsByCategory.Clear(); s_findAssetCache.Clear(); } private static long MakeFindKey(int skillGroupId, EquipmentSkillGroupSO.EquipmentSkillCategory category) { return ((long)category << 32) | (uint)skillGroupId; } public static EquipmentSkillGroupSO FindAsset(int skillGroupId, EquipmentSkillGroupSO.EquipmentSkillCategory category) { if (skillGroupId <= 0) { return null; } bool useCache = Application.isPlaying; long findKey = 0; if (useCache) { findKey = MakeFindKey(skillGroupId, category); if (s_findAssetCache.TryGetValue(findKey, out EquipmentSkillGroupSO cached)) { return cached; } } var assets = LoadAssets(category); EquipmentSkillGroupSO resolved = null; for (int i = 0; i < assets.Count; i++) { EquipmentSkillGroupSO asset = assets[i]; if (asset == null || asset.skillGroup == null) { continue; } if (asset.skillGroup.skillGroupID == skillGroupId) { resolved = asset; break; } } if (useCache) { // Cache hits AND misses (null) so repeated unmatched ids don't re-scan. s_findAssetCache[findKey] = resolved; } return resolved; } public static List LoadAssets(EquipmentSkillGroupSO.EquipmentSkillCategory category) { // Play mode: build the per-category list once (from Resources) and reuse it. if (Application.isPlaying && s_assetsByCategory.TryGetValue(category, out var cachedList)) { return cachedList; } var results = new List(); #if UNITY_EDITOR if (!Application.isPlaying) { string[] folders = category == EquipmentSkillGroupSO.EquipmentSkillCategory.Special ? SpecialAssetFolders : IllusionAssetFolders; var dedupe = new HashSet(); for (int folderIndex = 0; folderIndex < folders.Length; folderIndex++) { if (!AssetDatabase.IsValidFolder(folders[folderIndex])) { continue; } string[] guids = AssetDatabase.FindAssets("t:EquipmentSkillGroupSO", new[] { folders[folderIndex] }); for (int i = 0; i < guids.Length; i++) { string path = AssetDatabase.GUIDToAssetPath(guids[i]); if (!dedupe.Add(path)) { continue; } EquipmentSkillGroupSO asset = AssetDatabase.LoadAssetAtPath(path); if (asset != null) { results.Add(asset); } } } return results; } #endif string[] resourcePaths = category == EquipmentSkillGroupSO.EquipmentSkillCategory.Special ? SpecialResourcesPaths : IllusionResourcesPaths; var seen = new HashSet(); for (int i = 0; i < resourcePaths.Length; i++) { EquipmentSkillGroupSO[] loaded = Resources.LoadAll(resourcePaths[i]); if (loaded == null || loaded.Length == 0) { continue; } for (int j = 0; j < loaded.Length; j++) { if (loaded[j] != null && seen.Add(loaded[j])) { results.Add(loaded[j]); } } } if (Application.isPlaying) { s_assetsByCategory[category] = results; } return results; } }