备份,准备做双端
This commit is contained in:
@@ -224,8 +224,8 @@ public class AllyHero_SO : ScriptableObject
|
||||
equippedSkillGroupIDs = Array.Empty<int>();
|
||||
}
|
||||
|
||||
int currentLevelId = GetEffectiveLevelForCurrentEXP()?.levelID ?? 0;
|
||||
int maxSkillSlots = Mathf.Max(0, GetEffectiveSkillSlotLimit());
|
||||
int currentLevelId = AllyHeroLevelRuntimeResolver.GetCurrentLevelId(this);
|
||||
int maxSkillSlots = Mathf.Max(0, AllyHeroLevelRuntimeResolver.GetEffectiveSkillSlotLimit(this));
|
||||
List<int> validatedGroupIds = new List<int>(Mathf.Min(equippedSkillGroupIDs.Length, maxSkillSlots));
|
||||
HashSet<int> seenGroupIds = new HashSet<int>();
|
||||
|
||||
@@ -285,8 +285,45 @@ public class AllyHero_SO : ScriptableObject
|
||||
return true;
|
||||
}
|
||||
|
||||
// 性能:GetEffectiveEquippedSkillGroupIDs 在单次掉血/事件中会被多入口重复调用(每次都 new List+HashSet+ToArray)。
|
||||
// 用"输入快照比对"缓存返回数组:仅当影响返回值的可变输入变化时才重建。所有调用点均只读遍历返回数组,
|
||||
// 故返回共享缓存安全。EXP/tier 由外部 AllyHeroDeployLedger 直接写公有字段(无法用脏标记拦截),
|
||||
// 故此处按值/引用快照比对——这些比较都不分配,命中缓存时零 GC,且结果与每次重算完全一致(等效)。
|
||||
// 全局装备数据版本号。装备的换装/卸下(影响跨英雄幻象额外槽)以及对已装备装备
|
||||
// sa_skillID/ia_skillID 的“原地修改”(引用不变,单靠 _snapEquipment 引用比对无法察觉)
|
||||
// 都会自增此计数,从而让所有英雄的 GetEffectiveEquippedSkillGroupIDs 缓存失效重建。
|
||||
// 这些操作都发生在菜单/养成界面,战斗中不变,故战斗热路径仍是零 GC 命中缓存。
|
||||
private static int s_equipmentDataRevision;
|
||||
|
||||
// 装备数据发生“引用不变的原地变更”或“可能影响跨英雄派生结果”的变更时调用,
|
||||
// 使所有英雄的装备派生缓存(有效技能组 ID)在下次读取时重算。
|
||||
public static void InvalidateEquipmentDerivedCaches()
|
||||
{
|
||||
s_equipmentDataRevision++;
|
||||
}
|
||||
|
||||
[System.NonSerialized] private int[] _cachedEffectiveGroupIds;
|
||||
[System.NonSerialized] private bool _cachedGroupIdsValid;
|
||||
[System.NonSerialized] private int[] _snapEquippedGroupIdsRef; // equippedSkillGroupIDs 引用(整体替换才变)
|
||||
[System.NonSerialized] private int _snapCurrentEXP;
|
||||
[System.NonSerialized] private int _snapUnlockedTierIndex;
|
||||
[System.NonSerialized] private equipmentSO _snapEquipment;
|
||||
[System.NonSerialized] private int _snapEquipmentDataRevision = -1;
|
||||
|
||||
public int[] GetEffectiveEquippedSkillGroupIDs()
|
||||
{
|
||||
// 快照比对:任一影响返回值的输入变化则失效重建。
|
||||
equipmentSO currentEquipment = GetEquippedEquipmentResolved();
|
||||
if (_cachedGroupIdsValid
|
||||
&& _snapEquipmentDataRevision == s_equipmentDataRevision
|
||||
&& ReferenceEquals(_snapEquippedGroupIdsRef, equippedSkillGroupIDs)
|
||||
&& _snapCurrentEXP == ally_currentEXP
|
||||
&& _snapUnlockedTierIndex == ally_growthUnlockedTierIndex
|
||||
&& ReferenceEquals(_snapEquipment, currentEquipment))
|
||||
{
|
||||
return _cachedEffectiveGroupIds;
|
||||
}
|
||||
|
||||
var result = new List<int>();
|
||||
var dedupe = new HashSet<int>();
|
||||
int heroSkillSlotsRemaining = GetEffectiveSkillSlotLimit();
|
||||
@@ -311,14 +348,21 @@ public class AllyHero_SO : ScriptableObject
|
||||
}
|
||||
}
|
||||
|
||||
equipmentSO equipment = GetEquippedEquipmentResolved();
|
||||
if (equipment != null)
|
||||
if (currentEquipment != null)
|
||||
{
|
||||
TryAddEquipmentSkillGroupId(result, dedupe, equipment.sa_skillID);
|
||||
TryAddEquipmentSkillGroupId(result, dedupe, equipment.ia_skillID);
|
||||
TryAddEquipmentSkillGroupId(result, dedupe, currentEquipment.sa_skillID);
|
||||
TryAddEquipmentSkillGroupId(result, dedupe, currentEquipment.ia_skillID);
|
||||
}
|
||||
|
||||
return result.ToArray();
|
||||
// 写入缓存并记录本次输入快照(与上面失效判断的字段一一对应)。
|
||||
_cachedEffectiveGroupIds = result.ToArray();
|
||||
_snapEquippedGroupIdsRef = equippedSkillGroupIDs;
|
||||
_snapCurrentEXP = ally_currentEXP;
|
||||
_snapUnlockedTierIndex = ally_growthUnlockedTierIndex;
|
||||
_snapEquipment = currentEquipment;
|
||||
_snapEquipmentDataRevision = s_equipmentDataRevision;
|
||||
_cachedGroupIdsValid = true;
|
||||
return _cachedEffectiveGroupIds;
|
||||
}
|
||||
|
||||
public int GetEffectiveSkillSlotLimit()
|
||||
@@ -586,11 +630,31 @@ public class AllyHero_SO : ScriptableObject
|
||||
return sorted[effectiveIndex];
|
||||
}
|
||||
|
||||
// 性能:levelStats 是序列化配置,运行时不变,故排序结果可缓存,避免每次事件 new List + Sort(闭包+排序)。
|
||||
// 用 (引用, Count) 快照判失效,防止极少数运行时改配置的情况漏更新。返回缓存列表,调用方只读(仅索引读取)。
|
||||
[System.NonSerialized] private List<AllyLevelInfo> _cachedSortedLevelStats;
|
||||
[System.NonSerialized] private bool _cachedSortedLevelStatsIsNull;
|
||||
[System.NonSerialized] private object _snapLevelStatsRef;
|
||||
[System.NonSerialized] private int _snapLevelStatsCount = -1;
|
||||
|
||||
private List<AllyLevelInfo> BuildSortedLevelStats()
|
||||
{
|
||||
if (_cachedSortedLevelStats != null
|
||||
&& ReferenceEquals(_snapLevelStatsRef, levelStats)
|
||||
&& _snapLevelStatsCount == (levelStats != null ? levelStats.Count : 0))
|
||||
{
|
||||
return _cachedSortedLevelStatsIsNull ? null : _cachedSortedLevelStats;
|
||||
}
|
||||
|
||||
var sorted = new List<AllyLevelInfo>();
|
||||
_snapLevelStatsRef = levelStats;
|
||||
_snapLevelStatsCount = levelStats != null ? levelStats.Count : 0;
|
||||
|
||||
if (levelStats == null || levelStats.Count == 0)
|
||||
{
|
||||
// 空配置:原逻辑返回空列表(非 null)。缓存该空列表。
|
||||
_cachedSortedLevelStats = sorted;
|
||||
_cachedSortedLevelStatsIsNull = false;
|
||||
return sorted;
|
||||
}
|
||||
|
||||
@@ -604,10 +668,15 @@ public class AllyHero_SO : ScriptableObject
|
||||
|
||||
if (sorted.Count == 0)
|
||||
{
|
||||
// 原逻辑:全为 null 时返回 null。缓存该 null 语义(仍记录快照避免每次重建)。
|
||||
_cachedSortedLevelStats = sorted; // 存空列表占位,使缓存有效
|
||||
_cachedSortedLevelStatsIsNull = true;
|
||||
return null;
|
||||
}
|
||||
|
||||
sorted.Sort((left, right) => left.requiredEXP.CompareTo(right.requiredEXP));
|
||||
_cachedSortedLevelStats = sorted;
|
||||
_cachedSortedLevelStatsIsNull = false;
|
||||
return sorted;
|
||||
}
|
||||
|
||||
@@ -888,6 +957,8 @@ public class AllyHero_SO : ScriptableObject
|
||||
equippedEquipment = equipment;
|
||||
equippedEquipmentId = equipment != null ? equipment.name : string.Empty;
|
||||
equippedEquipmentHydrated = true;
|
||||
// 换装可能改变本英雄及其它英雄的跨英雄幻象额外槽归属,使全体派生缓存失效。
|
||||
InvalidateEquipmentDerivedCaches();
|
||||
|
||||
#if UNITY_EDITOR
|
||||
if (!Application.isPlaying)
|
||||
@@ -927,6 +998,8 @@ public class AllyHero_SO : ScriptableObject
|
||||
equippedEquipment = null;
|
||||
equippedEquipmentId = string.Empty;
|
||||
equippedEquipmentHydrated = true;
|
||||
// 卸下装备同样可能改变跨英雄幻象额外槽归属,使全体派生缓存失效。
|
||||
InvalidateEquipmentDerivedCaches();
|
||||
SecureSaveVault.Delete(EquippedEquipmentSaveCategory, GetEquippedEquipmentSaveKey());
|
||||
LocalRecoveryMirror.DeleteSlot(GetEquippedEquipmentRecoverySlotKey());
|
||||
PlayerPrefs.DeleteKey(GetEquippedEquipmentPrefsKey());
|
||||
|
||||
Reference in New Issue
Block a user