using UnityEngine;
|
using System.Collections.Generic;
|
using Spine.Unity;
|
|
public class BattleCacheManager
|
{
|
/// <summary>
|
/// 资源引用信息(战场 + 角色维度)
|
/// </summary>
|
private class ResourceReference
|
{
|
public BattleResCache.CachedResource CachedResource;
|
// 记录哪些战场的哪些角色在使用这个资源
|
// Key: battleGuid, Value: ownerIds
|
public Dictionary<string, HashSet<string>> BattlefieldOwners = new Dictionary<string, HashSet<string>>();
|
|
public int RefCount
|
{
|
get
|
{
|
int count = 0;
|
foreach (var owners in BattlefieldOwners.Values)
|
{
|
count += owners.Count;
|
}
|
return count;
|
}
|
}
|
|
/// <summary>
|
/// 添加战场+角色的引用
|
/// </summary>
|
public void AddBattlefieldOwner(string battleGuid, string ownerId)
|
{
|
if (!BattlefieldOwners.ContainsKey(battleGuid))
|
{
|
BattlefieldOwners[battleGuid] = new HashSet<string>();
|
}
|
BattlefieldOwners[battleGuid].Add(ownerId);
|
}
|
|
/// <summary>
|
/// 移除整个战场的所有引用
|
/// </summary>
|
public void RemoveBattlefield(string battleGuid)
|
{
|
BattlefieldOwners.Remove(battleGuid);
|
}
|
}
|
|
// ===== 全局统一资源池(不再区分红蓝队)=====
|
private static Dictionary<string, ResourceReference> globalSpineCache =
|
new Dictionary<string, ResourceReference>();
|
|
private static Dictionary<string, ResourceReference> globalAudioCache =
|
new Dictionary<string, ResourceReference>();
|
|
/// <summary>
|
/// 注册战场资源需求(红队+蓝队一起注册)
|
/// </summary>
|
public void RegisterBattlefieldResources(string battleGuid,
|
List<BattleResCache.ResourceIdentifier> spineResources,
|
List<BattleResCache.ResourceIdentifier> 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}");
|
}
|
|
/// <summary>
|
/// 注销战场(卸载该战场的所有资源引用)
|
/// </summary>
|
/// <param name="battleGuid">战场GUID</param>
|
/// <param name="forceUnloadPersistent">是否强制卸载常驻(红队)资源,默认为 false</param>
|
public void UnregisterBattlefield(string battleGuid, bool forceUnloadPersistent = false)
|
{
|
// 处理Spine资源
|
var spineKeysToRemove = new List<string>();
|
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<string>();
|
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}");
|
}
|
|
/// <summary>
|
/// 获取或加载Spine资源
|
/// </summary>
|
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<SkeletonDataAsset>(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;
|
}
|
|
/// <summary>
|
/// 获取或加载Audio资源
|
/// </summary>
|
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<AudioClip>(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;
|
}
|
|
/// <summary>
|
/// 更新已加载资源的引用(由加载器调用)
|
/// </summary>
|
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}";
|
}
|
|
/// <summary>
|
/// 获取已注册到某个战场的所有 OwnerId(可选择只返回常驻资源的 owner)
|
/// </summary>
|
public HashSet<string> GetRegisteredOwners(string battleGuid, bool onlyPersistent = false)
|
{
|
var owners = new HashSet<string>();
|
|
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;
|
}
|
|
/// <summary>
|
/// 仅注销战场中指定的 owner(按 owner granularity 移除),仅卸载因此变为无人引用的资源
|
/// </summary>
|
/// <param name="battleGuid">战场 GUID</param>
|
/// <param name="ownersToRemove">要移除的 OwnerId 集合</param>
|
public void UnregisterBattlefieldOwners(string battleGuid, HashSet<string> ownersToRemove)
|
{
|
if (ownersToRemove == null || ownersToRemove.Count == 0)
|
{
|
Debug.Log($"BattleCacheManager: UnregisterBattlefieldOwners called with empty owners for {battleGuid}");
|
return;
|
}
|
|
var spineKeysToRemove = new List<string>();
|
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<string>();
|
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}");
|
}
|
|
/// <summary>
|
/// 判断该战场是否已注册(有任意资源引用)
|
/// </summary>
|
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<string, HashSet<string>> 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<string, ResourceReferenceDebug> GetSpineCache()
|
{
|
var result = new Dictionary<string, ResourceReferenceDebug>();
|
foreach (var kvp in globalSpineCache)
|
{
|
// 直接在这里赋值,不通过构造函数传递私有类
|
result[kvp.Key] = new ResourceReferenceDebug
|
{
|
CachedResource = kvp.Value.CachedResource,
|
BattlefieldOwners = kvp.Value.BattlefieldOwners
|
};
|
}
|
return result;
|
}
|
|
public static Dictionary<string, ResourceReferenceDebug> GetAudioCache()
|
{
|
var result = new Dictionary<string, ResourceReferenceDebug>();
|
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<string> GetAllBattleGuids()
|
{
|
var guids = new HashSet<string>();
|
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<string, int> BattlefieldRefCounts = new Dictionary<string, int>();
|
|
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
|
}
|