Files
ichni_Official/Assets/Scripts/Manager/ElementUpdateScheduler.cs

267 lines
11 KiB
C#
Raw Normal View History

2026-06-05 04:45:57 -04:00
using System;
using System.Collections.Generic;
using Ichni.RhythmGame;
namespace Ichni
{
/// <summary>
/// 集中式元素更新调度器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 + TransformLegacy activeColorSubmodules / activeTransformSubmodules
///
/// --- Unity 执行其他脚本的 UpdateSplineComputer 检测 transform.hasChanged重新采样---
///
/// TickLate (GameManager.LateUpdate)
/// DirtyMark — ExecuteDeferredRefreshLegacyisStarting || 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() 时获取最新采样数据。
/// </summary>
public class ElementUpdateScheduler
{
#region [] Constants
/// <summary>阶段总数TimeDuration..Misc步长 10共 9 个)</summary>
private const int PhaseCount = 9;
/// <summary>阶段枚举值到数组索引的步长除数</summary>
private const int PhaseStep = 10;
private static readonly UpdatePhase[] AllPhases = (UpdatePhase[])Enum.GetValues(typeof(UpdatePhase));
#endregion
#region [] Phase Element Lists
/// <summary>
/// 每个阶段的已注册 IScheduledElement 列表。
/// 使用数组替代 Dictionary&lt;UpdatePhase, ...&gt;
/// 索引通过 (int)phase / 10 直接映射,消除哈希查找开销。
/// </summary>
private readonly List<IScheduledElement>[] _phaseElements;
/// <summary>将 UpdatePhase 枚举值转换为数组索引。</summary>
private static int PhaseIndex(UpdatePhase phase) => (int)phase / PhaseStep;
#endregion
#region [ Spline ] Track Spline List
/// <summary>Phase 3 专用:需要手动重建 SplineComputer 的 Track 列表(保留供后续优化)</summary>
private readonly List<Track> _trackSplines = new List<Track>(50);
#endregion
#region [] Cached State
/// <summary>TickEarly 中缓存的 songTime供 TickLate 使用</summary>
private float _cachedSongTime;
/// <summary>当前帧是否处于更新状态(由 songPlayer.isUpdating 决定)</summary>
private bool _isUpdating;
#endregion
#region [] Constructor
public ElementUpdateScheduler()
{
_phaseElements = new List<IScheduledElement>[PhaseCount];
for (int i = 0; i < PhaseCount; i++)
{
_phaseElements[i] = new List<IScheduledElement>();
}
}
#endregion
#region [] Registration
/// <summary>将元素注册到指定的更新阶段。同一元素可注册到多个阶段。</summary>
public void Register(UpdatePhase phase, IScheduledElement element)
{
var list = _phaseElements[PhaseIndex(phase)];
if (!list.Contains(element))
{
list.Add(element);
}
}
/// <summary>将元素从指定阶段注销。</summary>
public void Unregister(UpdatePhase phase, IScheduledElement element)
{
_phaseElements[PhaseIndex(phase)].Remove(element);
}
/// <summary>注册 Track 的 SplineComputer 到 Phase 3SplineRebuild当前为占位。</summary>
public void RegisterTrackSpline(Track track)
{
if (!_trackSplines.Contains(track))
{
_trackSplines.Add(track);
}
}
/// <summary>从 Phase 3 注销 Track 的 SplineComputer。</summary>
public void UnregisterTrackSpline(Track track)
{
_trackSplines.Remove(track);
}
#endregion
#region [ - ] TickEarly (Update)
/// <summary>
/// 早期阶段调度,由 GameManager.Update() 调用。
/// 执行 Phase 0TimeDuration→ Phase 1Animation→ Phase 2Apply
/// Phase 2 完成后 Transform 已更新SplineComputer 将在后续的 Update [order 0] 中检测变化。
/// </summary>
public void TickEarly(float songTime)
{
_isUpdating = GameManager.Instance.songPlayer.isUpdating;
if (!_isUpdating) return;
_cachedSongTime = songTime;
// ─── Phase 0: TimeDurationLegacy timeDurations 列表)─────────────────
TickTimeDurationLegacy(songTime);
// ─── Phase 1: Animation ──────────────────────────────────────────────
TickPhase(UpdatePhase.Animation, songTime);
// ─── Phase 2: ApplyColor + Transform from active lists────────────
TickApplyLegacy();
}
#endregion
#region [ - ] TickLate (LateUpdate)
/// <summary>
/// 晚期阶段调度,由 GameManager.LateUpdate() 调用。
/// 此时所有 Update() 已完成SplineComputer 已检测 transform.hasChanged 并重新采样。
/// </summary>
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
/// <summary>
/// 执行指定阶段的所有已注册 IScheduledElement。
/// 倒序遍历防止更新途中元素自行销毁导致越界。
/// </summary>
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]
/// <summary>
/// [Legacy] Phase 0 内联逻辑:遍历 GameManager.timeDurations 执行激活/隐藏检测。
/// </summary>
private void TickTimeDurationLegacy(float songTime)
{
var timeDurations = GameManager.Instance.timeDurations;
for (int i = 0; i < timeDurations.Count; i++)
{
timeDurations[i]?.DetectAndSwitchActiveState(songTime);
}
}
/// <summary>
/// [Legacy] Phase 2 内联逻辑:从 GameManager 活跃列表执行 Color + Transform 更新。
/// 在 Update 阶段完成 Transform 修改,以便 SplineComputer 在后续 Update 中检测 hasChanged。
/// </summary>
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);
}
}
/// <summary>
/// [Legacy] TickLate DirtyMark 内联逻辑:保持原有 isStarting || isPlaying 条件。
/// </summary>
private void TickDirtyMarkLegacy()
{
var activeDirtyMarkSubmodules = GameManager.Instance.activeDirtyMarkSubmodules;
for (int i = 0; i < activeDirtyMarkSubmodules.Count; i++)
{
activeDirtyMarkSubmodules[i]?.dirtyMarkSubmodule?.ExecuteDeferredRefresh();
}
}
#endregion
}
}