Files
ichni_Official/Assets/Scripts/Manager/ElementUpdateScheduler.cs
SoulliesOfficial 7c60c40d6b 同步
2026-06-05 04:45:57 -04:00

267 lines
11 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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
}
}