using UnityEngine; using System.Collections.Generic; using Spine.Unity; public class BattleCacheManager { /// /// 资源引用信息(战场 + 角色维度) /// private class ResourceReference { public BattleResCache.CachedResource CachedResource; // 记录哪些战场的哪些角色在使用这个资源 // Key: battleGuid, Value: ownerIds public Dictionary> BattlefieldOwners = new Dictionary>(); public int RefCount { get { int count = 0; foreach (var owners in BattlefieldOwners.Values) { count += owners.Count; } return count; } } /// /// 添加战场+角色的引用 /// public void AddBattlefieldOwner(string battleGuid, string ownerId) { if (!BattlefieldOwners.ContainsKey(battleGuid)) { BattlefieldOwners[battleGuid] = new HashSet(); } BattlefieldOwners[battleGuid].Add(ownerId); } /// /// 移除整个战场的所有引用 /// public void RemoveBattlefield(string battleGuid) { BattlefieldOwners.Remove(battleGuid); } } // ===== 全局统一资源池(不再区分红蓝队)===== private static Dictionary globalSpineCache = new Dictionary(); private static Dictionary globalAudioCache = new Dictionary(); /// /// 注册战场资源需求(红队+蓝队一起注册) /// public void RegisterBattlefieldResources(string battleGuid, List spineResources, List audioResources) { // 注册Spine资源 foreach (var res in spineResources) { if (string.IsNullOrEmpty(res.OwnerId)) continue; string key = res.GetKey(); if (!globalSpineCache.ContainsKey(key)) { // 把 ResourceIdentifier 的 IsPersistent 传递给 CachedResource globalSpineCache[key] = new ResourceReference { CachedResource = new BattleResCache.CachedResource(res, null, res.IsPersistent) }; } globalSpineCache[key].AddBattlefieldOwner(battleGuid, res.OwnerId); } // 注册Audio资源 foreach (var res in audioResources) { if (string.IsNullOrEmpty(res.OwnerId)) continue; string key = res.GetKey(); if (!globalAudioCache.ContainsKey(key)) { // 把 ResourceIdentifier 的 IsPersistent 传递给 CachedResource globalAudioCache[key] = new ResourceReference { CachedResource = new BattleResCache.CachedResource(res, null, res.IsPersistent) }; } globalAudioCache[key].AddBattlefieldOwner(battleGuid, res.OwnerId); } Debug.Log($"BattleCacheManager: Registered battlefield {battleGuid} - Spine: {spineResources.Count}, Audio: {audioResources.Count}"); } /// /// 注销战场(卸载该战场的所有资源引用) /// /// 战场GUID /// 是否强制卸载常驻(红队)资源,默认为 false public void UnregisterBattlefield(string battleGuid, bool forceUnloadPersistent = false) { // 处理Spine资源 var spineKeysToRemove = new List(); foreach (var kvp in globalSpineCache) { kvp.Value.RemoveBattlefield(battleGuid); if (kvp.Value.RefCount == 0) { // 仅在非常驻或被强制卸载时才真正移除资源 if (forceUnloadPersistent || kvp.Value.CachedResource.CanUnload()) { spineKeysToRemove.Add(kvp.Key); } else { Debug.Log($"BattleCacheManager: Skipped unloading persistent spine resource: {kvp.Key}"); } } } foreach (var key in spineKeysToRemove) { var res = globalSpineCache[key]; if (res.CachedResource.Asset != null) { ResManager.Instance.UnloadAsset( res.CachedResource.Identifier.Directory, res.CachedResource.Identifier.AssetName ); } globalSpineCache.Remove(key); Debug.Log($"BattleCacheManager: Unloaded spine (refCount=0): {key}"); } // 处理Audio资源 var audioKeysToRemove = new List(); foreach (var kvp in globalAudioCache) { kvp.Value.RemoveBattlefield(battleGuid); if (kvp.Value.RefCount == 0) { // 仅在非常驻或被强制卸载时才真正移除资源 if (forceUnloadPersistent || kvp.Value.CachedResource.CanUnload()) { audioKeysToRemove.Add(kvp.Key); } else { Debug.Log($"BattleCacheManager: Skipped unloading persistent audio resource: {kvp.Key}"); } } } foreach (var key in audioKeysToRemove) { var res = globalAudioCache[key]; if (res.CachedResource.Asset != null) { ResManager.Instance.UnloadAsset( res.CachedResource.Identifier.Directory, res.CachedResource.Identifier.AssetName ); } globalAudioCache.Remove(key); Debug.Log($"BattleCacheManager: Unloaded audio (refCount=0): {key}"); } Debug.Log($"BattleCacheManager: Unregistered battlefield {battleGuid}"); } /// /// 获取或加载Spine资源 /// public SkeletonDataAsset GetSpineResource(string directory, string assetName, string battleGuid = "") { string key = $"{directory}/{assetName}"; if (globalSpineCache.TryGetValue(key, out var refInfo)) { return refInfo.CachedResource.Asset as SkeletonDataAsset; } // 自动加载 Debug.LogWarning($"BattleCacheManager: Spine cache miss for {key}, loading on-demand..."); var asset = ResManager.Instance.LoadAsset(directory, assetName); if (asset != null) { var identifier = new BattleResCache.ResourceIdentifier { Directory = directory, AssetName = assetName, Type = BattleResCache.ResourceType.Spine, IsPersistent = false // on-demand 加载的默认非常驻 }; globalSpineCache[key] = new ResourceReference { CachedResource = new BattleResCache.CachedResource(identifier, asset, identifier.IsPersistent) }; } return asset; } /// /// 获取或加载Audio资源 /// public AudioClip GetAudioResource(string directory, string assetName, string battleGuid = "") { string key = $"{directory}/{assetName}"; if (globalAudioCache.TryGetValue(key, out var refInfo)) { return refInfo.CachedResource.Asset as AudioClip; } // 自动加载 Debug.LogWarning($"BattleCacheManager: Audio cache miss for {key}, loading on-demand..."); var asset = ResManager.Instance.LoadAsset(directory, assetName, false); if (asset != null) { var identifier = new BattleResCache.ResourceIdentifier { Directory = directory, AssetName = assetName, Type = BattleResCache.ResourceType.Audio, IsPersistent = false // on-demand 加载的默认非常驻 }; globalAudioCache[key] = new ResourceReference { CachedResource = new BattleResCache.CachedResource(identifier, asset, identifier.IsPersistent) }; } return asset; } /// /// 更新已加载资源的引用(由加载器调用) /// public void UpdateResourceReference(string key, BattleResCache.CachedResource resource, string battleGuid, string ownerId) { // 确保 CachedResource 的 IsPersistent 与 Identifier 一致 resource.IsPersistent = resource.Identifier.IsPersistent; if (resource.Identifier.Type == BattleResCache.ResourceType.Spine) { if (globalSpineCache.ContainsKey(key)) { globalSpineCache[key].CachedResource = resource; } } else if (resource.Identifier.Type == BattleResCache.ResourceType.Audio) { if (globalAudioCache.ContainsKey(key)) { globalAudioCache[key].CachedResource = resource; } } } public string GetCacheStats(string battleGuid = "") { int spineTotal = globalSpineCache.Count; int audioTotal = globalAudioCache.Count; return $"Spine: {spineTotal}, Audio: {audioTotal}"; } /// /// 获取已注册到某个战场的所有 OwnerId(可选择只返回常驻资源的 owner) /// public HashSet GetRegisteredOwners(string battleGuid, bool onlyPersistent = false) { var owners = new HashSet(); foreach (var kvp in globalSpineCache) { if (!kvp.Value.BattlefieldOwners.ContainsKey(battleGuid)) continue; if (onlyPersistent && kvp.Value.CachedResource.IsPersistent == false) continue; foreach (var owner in kvp.Value.BattlefieldOwners[battleGuid]) owners.Add(owner); } foreach (var kvp in globalAudioCache) { if (!kvp.Value.BattlefieldOwners.ContainsKey(battleGuid)) continue; if (onlyPersistent && kvp.Value.CachedResource.IsPersistent == false) continue; foreach (var owner in kvp.Value.BattlefieldOwners[battleGuid]) owners.Add(owner); } return owners; } /// /// 仅注销战场中指定的 owner(按 owner granularity 移除),仅卸载因此变为无人引用的资源 /// /// 战场 GUID /// 要移除的 OwnerId 集合 public void UnregisterBattlefieldOwners(string battleGuid, HashSet ownersToRemove) { if (ownersToRemove == null || ownersToRemove.Count == 0) { Debug.Log($"BattleCacheManager: UnregisterBattlefieldOwners called with empty owners for {battleGuid}"); return; } var spineKeysToRemove = new List(); foreach (var kvp in globalSpineCache) { if (!kvp.Value.BattlefieldOwners.ContainsKey(battleGuid)) continue; var owners = kvp.Value.BattlefieldOwners[battleGuid]; bool changed = false; foreach (var owner in ownersToRemove) { if (owners.Contains(owner)) { owners.Remove(owner); changed = true; } } if (owners.Count == 0) kvp.Value.BattlefieldOwners.Remove(battleGuid); // 如果全局引用数为0,说明没有任何战场/角色在使用这个资源,安全卸载 if (kvp.Value.RefCount == 0) { spineKeysToRemove.Add(kvp.Key); } else if (changed) { Debug.Log($"BattleCacheManager: Updated spine owners for {kvp.Key} after removing owners for {battleGuid}"); } } foreach (var key in spineKeysToRemove) { var res = globalSpineCache[key]; if (res.CachedResource.Asset != null) { ResManager.Instance.UnloadAsset( res.CachedResource.Identifier.Directory, res.CachedResource.Identifier.AssetName ); } globalSpineCache.Remove(key); Debug.Log($"BattleCacheManager: Unloaded spine (no owners left): {key}"); } var audioKeysToRemove = new List(); foreach (var kvp in globalAudioCache) { if (!kvp.Value.BattlefieldOwners.ContainsKey(battleGuid)) continue; var owners = kvp.Value.BattlefieldOwners[battleGuid]; bool changed = false; foreach (var owner in ownersToRemove) { if (owners.Contains(owner)) { owners.Remove(owner); changed = true; } } if (owners.Count == 0) kvp.Value.BattlefieldOwners.Remove(battleGuid); if (kvp.Value.RefCount == 0) { audioKeysToRemove.Add(kvp.Key); } else if (changed) { Debug.Log($"BattleCacheManager: Updated audio owners for {kvp.Key} after removing owners for {battleGuid}"); } } foreach (var key in audioKeysToRemove) { var res = globalAudioCache[key]; if (res.CachedResource.Asset != null) { ResManager.Instance.UnloadAsset( res.CachedResource.Identifier.Directory, res.CachedResource.Identifier.AssetName ); } globalAudioCache.Remove(key); Debug.Log($"BattleCacheManager: Unloaded audio (no owners left): {key}"); } Debug.Log($"BattleCacheManager: Unregistered specific owners for battlefield {battleGuid}. Removed owners: {ownersToRemove.Count}"); } /// /// 判断该战场是否已注册(有任意资源引用) /// public bool HasBattleRegistered(string battleGuid) { foreach (var kvp in globalSpineCache) { if (kvp.Value.BattlefieldOwners.ContainsKey(battleGuid)) return true; } foreach (var kvp in globalAudioCache) { if (kvp.Value.BattlefieldOwners.ContainsKey(battleGuid)) return true; } return false; } // ===== 编辑器调试接口 ===== #if UNITY_EDITOR // 编辑器模式下的资源引用调试类 public class ResourceReferenceDebug { public BattleResCache.CachedResource CachedResource; public Dictionary> BattlefieldOwners; public int RefCount { get { int count = 0; foreach (var owners in BattlefieldOwners.Values) { count += owners.Count; } return count; } } // 私有构造函数,只能由 DebugAPI 调用 internal ResourceReferenceDebug() { } } public static class DebugAPI { public static Dictionary GetSpineCache() { var result = new Dictionary(); foreach (var kvp in globalSpineCache) { // 直接在这里赋值,不通过构造函数传递私有类 result[kvp.Key] = new ResourceReferenceDebug { CachedResource = kvp.Value.CachedResource, BattlefieldOwners = kvp.Value.BattlefieldOwners }; } return result; } public static Dictionary GetAudioCache() { var result = new Dictionary(); foreach (var kvp in globalAudioCache) { result[kvp.Key] = new ResourceReferenceDebug { CachedResource = kvp.Value.CachedResource, BattlefieldOwners = kvp.Value.BattlefieldOwners }; } return result; } public static int GetTotalSpineCount() => globalSpineCache.Count; public static int GetTotalAudioCount() => globalAudioCache.Count; public static HashSet GetAllBattleGuids() { var guids = new HashSet(); foreach (var refInfo in globalSpineCache.Values) { foreach (var guid in refInfo.BattlefieldOwners.Keys) { guids.Add(guid); } } foreach (var refInfo in globalAudioCache.Values) { foreach (var guid in refInfo.BattlefieldOwners.Keys) { guids.Add(guid); } } return guids; } public static void ClearAllCache() { globalSpineCache.Clear(); globalAudioCache.Clear(); Debug.Log("BattleCacheManager: All cache cleared (Editor only)"); } } // 资源引用视图类(供编辑器使用) public class ResourceReferenceView { public string ResourceKey; public string ResourcePath; public bool IsLoaded; public int TotalRefCount; public Dictionary BattlefieldRefCounts = new Dictionary(); public ResourceReferenceView(string key, ResourceReferenceDebug refInfo) { ResourceKey = key; ResourcePath = refInfo.CachedResource.Identifier.Directory + "/" + refInfo.CachedResource.Identifier.AssetName; IsLoaded = refInfo.CachedResource.Asset != null; TotalRefCount = refInfo.RefCount; foreach (var kvp in refInfo.BattlefieldOwners) { BattlefieldRefCounts[kvp.Key] = kvp.Value.Count; } } } #endif }