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
}