using System; using System.Collections.Generic; using Ichni.RhythmGame; namespace Ichni { /// /// 集中式元素更新调度器(ichni Official)。 /// 将所有 GameElement 的帧更新按 9 个阶段有序执行, /// 替代原先分散在 GameManager.Update/LateUpdate()、各子管理器、MonoBehaviour.Update() 中的零散更新。 /// /// TickEarly (GameManager.Update) /// Phase 0 TimeDuration — 判定元素激活/隐藏(Legacy timeDurations 列表) /// Phase 1 Animation — 更新动画值,设置脏标记(AnimationBase 通过 IScheduledElement 注册) /// Phase 2 Apply — 执行 Color + Transform(Legacy activeColorSubmodules / activeTransformSubmodules) /// /// --- Unity 执行其他脚本的 Update(SplineComputer 检测 transform.hasChanged,重新采样)--- /// /// TickLate (GameManager.LateUpdate) /// DirtyMark — ExecuteDeferredRefresh(Legacy,isStarting || isPlaying 条件) /// Phase 3 SplineRebuild — 占位(SplineComputer 已在 Update 中自行处理) /// Phase 4 TrackCore — 更新轨道时间、裁剪区间(Track 通过 IScheduledElement 注册) /// Phase 5 TrackFollower — CrossTrackPoint / HeadPoint / PercentPoint /// [TrackManager.ManualLateUpdate — 清除 refreshedThisFrame 标记] /// Phase 6 Note — NoteManager 内部驱动(激活 + 更新 + 重置) /// Phase 7 Effect — ParticleEmitter / TimeEffectsCollection / ParticleTracker / DTM 元素 / LookAt 旋转覆盖 /// Phase 8 Misc — SkyboxSubsetter / JudgeTrigger / LowPriorityActions /// /// 此分离设计消除了 Dreamteck Spline 的一帧延迟: /// Phase 2 (Update) 修改 Transform → SplineComputer.Update() 检测 hasChanged 并重采样 /// → Phase 4/5 (LateUpdate) 调用 SetPercent() 时获取最新采样数据。 /// public class ElementUpdateScheduler { #region [常量] Constants /// 阶段总数(TimeDuration..Misc,步长 10,共 9 个) private const int PhaseCount = 9; /// 阶段枚举值到数组索引的步长除数 private const int PhaseStep = 10; private static readonly UpdatePhase[] AllPhases = (UpdatePhase[])Enum.GetValues(typeof(UpdatePhase)); #endregion #region [阶段元素列表] Phase Element Lists /// /// 每个阶段的已注册 IScheduledElement 列表。 /// 使用数组替代 Dictionary<UpdatePhase, ...>, /// 索引通过 (int)phase / 10 直接映射,消除哈希查找开销。 /// private readonly List[] _phaseElements; /// 将 UpdatePhase 枚举值转换为数组索引。 private static int PhaseIndex(UpdatePhase phase) => (int)phase / PhaseStep; #endregion #region [轨道 Spline 列表] Track Spline List /// Phase 3 专用:需要手动重建 SplineComputer 的 Track 列表(保留供后续优化) private readonly List _trackSplines = new List(50); #endregion #region [缓存] Cached State /// TickEarly 中缓存的 songTime,供 TickLate 使用 private float _cachedSongTime; /// 当前帧是否处于更新状态(由 songPlayer.isUpdating 决定) private bool _isUpdating; #endregion #region [构造函数] Constructor public ElementUpdateScheduler() { _phaseElements = new List[PhaseCount]; for (int i = 0; i < PhaseCount; i++) { _phaseElements[i] = new List(); } } #endregion #region [注册与注销] Registration /// 将元素注册到指定的更新阶段。同一元素可注册到多个阶段。 public void Register(UpdatePhase phase, IScheduledElement element) { var list = _phaseElements[PhaseIndex(phase)]; if (!list.Contains(element)) { list.Add(element); } } /// 将元素从指定阶段注销。 public void Unregister(UpdatePhase phase, IScheduledElement element) { _phaseElements[PhaseIndex(phase)].Remove(element); } /// 注册 Track 的 SplineComputer 到 Phase 3(SplineRebuild,当前为占位)。 public void RegisterTrackSpline(Track track) { if (!_trackSplines.Contains(track)) { _trackSplines.Add(track); } } /// 从 Phase 3 注销 Track 的 SplineComputer。 public void UnregisterTrackSpline(Track track) { _trackSplines.Remove(track); } #endregion #region [主循环 - 早期阶段] TickEarly (Update) /// /// 早期阶段调度,由 GameManager.Update() 调用。 /// 执行 Phase 0(TimeDuration)→ Phase 1(Animation)→ Phase 2(Apply)。 /// Phase 2 完成后 Transform 已更新;SplineComputer 将在后续的 Update [order 0] 中检测变化。 /// public void TickEarly(float songTime) { _isUpdating = GameManager.Instance.songPlayer.isUpdating; if (!_isUpdating) return; _cachedSongTime = songTime; // ─── Phase 0: TimeDuration(Legacy timeDurations 列表)───────────────── TickTimeDurationLegacy(songTime); // ─── Phase 1: Animation ────────────────────────────────────────────── TickPhase(UpdatePhase.Animation, songTime); // ─── Phase 2: Apply(Color + Transform from active lists)──────────── TickApplyLegacy(); } #endregion #region [主循环 - 晚期阶段] TickLate (LateUpdate) /// /// 晚期阶段调度,由 GameManager.LateUpdate() 调用。 /// 此时所有 Update() 已完成,SplineComputer 已检测 transform.hasChanged 并重新采样。 /// public void TickLate() { // DirtyMark:保持原有 isStarting || isPlaying 条件,与 TickEarly 的 isUpdating 独立判断 if (GameManager.Instance.songPlayer.isStarting || GameManager.Instance.songPlayer.isPlaying) { TickDirtyMarkLegacy(); } if (!_isUpdating) return; float songTime = _cachedSongTime; // ─── Phase 3: SplineRebuild(占位)─────────────────────────────────── // SplineComputer 使用默认 UpdateMode.Update,已在 Update 中自行处理。 TickPhase(UpdatePhase.SplineRebuild, songTime); // ─── Phase 4: TrackCore ────────────────────────────────────────────── TickPhase(UpdatePhase.TrackCore, songTime); // ─── Phase 5: TrackFollower ────────────────────────────────────────── TickPhase(UpdatePhase.TrackFollower, songTime); // 清除轨道 refreshedThisFrame 标记(TrackManager 保留此单一职责) GameManager.Instance.trackManager.ManualLateUpdate(songTime); // ─── Phase 6: Note(由 NoteManager 内部驱动)───────────────────────── TickPhase(UpdatePhase.Note, songTime); GameManager.Instance.noteManager.ManualUpdate(songTime); GameManager.Instance.noteManager.ManualLateUpdate(songTime); // ─── Phase 7: Effect ───────────────────────────────────────────────── TickPhase(UpdatePhase.Effect, songTime); // ─── Phase 8: Misc ─────────────────────────────────────────────────── TickPhase(UpdatePhase.Misc, songTime); GameManager.Instance.beatmapContainer?.ExecuteLowPriorityActions(); } #endregion #region [阶段执行] Phase Execution /// /// 执行指定阶段的所有已注册 IScheduledElement。 /// 倒序遍历防止更新途中元素自行销毁导致越界。 /// private void TickPhase(UpdatePhase phase, float songTime) { var list = _phaseElements[PhaseIndex(phase)]; for (int i = list.Count - 1; i >= 0; i--) { var element = list[i]; if (element != null && element.IsScheduledActive) { element.ScheduledUpdate(phase, songTime); } } } #endregion #region [Legacy] 过渡期内联逻辑 /// /// [Legacy] Phase 0 内联逻辑:遍历 GameManager.timeDurations 执行激活/隐藏检测。 /// private void TickTimeDurationLegacy(float songTime) { var timeDurations = GameManager.Instance.timeDurations; for (int i = 0; i < timeDurations.Count; i++) { timeDurations[i]?.DetectAndSwitchActiveState(songTime); } } /// /// [Legacy] Phase 2 内联逻辑:从 GameManager 活跃列表执行 Color + Transform 更新。 /// 在 Update 阶段完成 Transform 修改,以便 SplineComputer 在后续 Update 中检测 hasChanged。 /// private void TickApplyLegacy() { var activeColorSubmodules = GameManager.Instance.activeColorSubmodules; var activeTransformSubmodules = GameManager.Instance.activeTransformSubmodules; for (int i = 0; i < activeColorSubmodules.Count; i++) { activeColorSubmodules[i].UpdateColor(true); } for (int i = 0; i < activeTransformSubmodules.Count; i++) { activeTransformSubmodules[i].UpdateTransform(true); } } /// /// [Legacy] TickLate DirtyMark 内联逻辑:保持原有 isStarting || isPlaying 条件。 /// private void TickDirtyMarkLegacy() { var activeDirtyMarkSubmodules = GameManager.Instance.activeDirtyMarkSubmodules; for (int i = 0; i < activeDirtyMarkSubmodules.Count; i++) { activeDirtyMarkSubmodules[i]?.dirtyMarkSubmodule?.ExecuteDeferredRefresh(); } } #endregion } }