备份,准备做双端

This commit is contained in:
FloatGaming
2026-07-30 23:15:58 +08:00
parent add675e45d
commit ec4ec53545
88 changed files with 4050 additions and 872 deletions
+80 -7
View File
@@ -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());