401 lines
16 KiB
C#
401 lines
16 KiB
C#
using System;
|
||
using System.Collections.Generic;
|
||
using Ichni.RhythmGame;
|
||
using Unity.Profiling;
|
||
|
||
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 + 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() 时获取最新采样数据。
|
||
/// </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));
|
||
private static readonly ProfilerMarker[] PhaseMarkers =
|
||
{
|
||
new ProfilerMarker("Ichni.UpdateScheduler.TimeDuration"),
|
||
new ProfilerMarker("Ichni.UpdateScheduler.Animation"),
|
||
new ProfilerMarker("Ichni.UpdateScheduler.Apply"),
|
||
new ProfilerMarker("Ichni.UpdateScheduler.SplineRebuild"),
|
||
new ProfilerMarker("Ichni.UpdateScheduler.TrackCore"),
|
||
new ProfilerMarker("Ichni.UpdateScheduler.TrackFollower"),
|
||
new ProfilerMarker("Ichni.UpdateScheduler.Note"),
|
||
new ProfilerMarker("Ichni.UpdateScheduler.Effect"),
|
||
new ProfilerMarker("Ichni.UpdateScheduler.Misc")
|
||
};
|
||
private static readonly ProfilerMarker DirtyMarkMarker =
|
||
new ProfilerMarker("Ichni.UpdateScheduler.DirtyMark");
|
||
|
||
#endregion
|
||
|
||
#region [阶段元素列表] Phase Element Lists
|
||
|
||
/// <summary>
|
||
/// 每个阶段的已注册 IScheduledElement 列表。
|
||
/// 使用数组替代 Dictionary<UpdatePhase, ...>,
|
||
/// 索引通过 (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 [Apply Dirty Queues]
|
||
|
||
private readonly List<IHaveColorSubmodule> _dirtyColorSubmodules = new List<IHaveColorSubmodule>(256);
|
||
private readonly List<IHaveTransformSubmodule> _dirtyTransformSubmodules = new List<IHaveTransformSubmodule>(256);
|
||
|
||
#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 3(SplineRebuild,当前为占位)。</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);
|
||
}
|
||
|
||
public void RegisterDirtyColor(IHaveColorSubmodule target)
|
||
{
|
||
if (target == null || target.colorSubmodule == null || target.colorSubmodule.queuedForApply)
|
||
{
|
||
return;
|
||
}
|
||
|
||
target.colorSubmodule.queuedForApply = true;
|
||
_dirtyColorSubmodules.Add(target);
|
||
}
|
||
|
||
public void RegisterDirtyTransform(IHaveTransformSubmodule target)
|
||
{
|
||
if (target == null || target.transformSubmodule == null || target.transformSubmodule.queuedForApply)
|
||
{
|
||
return;
|
||
}
|
||
|
||
target.transformSubmodule.queuedForApply = true;
|
||
_dirtyTransformSubmodules.Add(target);
|
||
}
|
||
|
||
public void UnregisterDirtyColor(IHaveColorSubmodule target)
|
||
{
|
||
if (target?.colorSubmodule != null)
|
||
{
|
||
target.colorSubmodule.queuedForApply = false;
|
||
}
|
||
|
||
_dirtyColorSubmodules.Remove(target);
|
||
}
|
||
|
||
public void UnregisterDirtyTransform(IHaveTransformSubmodule target)
|
||
{
|
||
if (target?.transformSubmodule != null)
|
||
{
|
||
target.transformSubmodule.queuedForApply = false;
|
||
}
|
||
|
||
_dirtyTransformSubmodules.Remove(target);
|
||
}
|
||
|
||
#endregion
|
||
|
||
#region [主循环 - 早期阶段] TickEarly (Update)
|
||
|
||
/// <summary>
|
||
/// 早期阶段调度,由 GameManager.Update() 调用。
|
||
/// 执行 Phase 0(TimeDuration)→ Phase 1(Animation)→ Phase 2(Apply)。
|
||
/// 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: TimeDuration(Legacy timeDurations 列表)─────────────────
|
||
using (PhaseMarkers[PhaseIndex(UpdatePhase.TimeDuration)].Auto())
|
||
{
|
||
TickTimeDurationLegacy(songTime);
|
||
}
|
||
|
||
// ─── Phase 1: Animation ──────────────────────────────────────────────
|
||
TickPhase(UpdatePhase.Animation, songTime);
|
||
|
||
// ─── Phase 2: Apply(Color + Transform from active lists)────────────
|
||
using (PhaseMarkers[PhaseIndex(UpdatePhase.Apply)].Auto())
|
||
{
|
||
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)
|
||
{
|
||
using (DirtyMarkMarker.Auto())
|
||
{
|
||
TickDirtyMarkLegacy();
|
||
}
|
||
}
|
||
|
||
if (!_isUpdating) return;
|
||
float songTime = _cachedSongTime;
|
||
|
||
// ─── Phase 3: SplineRebuild ──────────────────────────────────────────
|
||
using (PhaseMarkers[PhaseIndex(UpdatePhase.SplineRebuild)].Auto())
|
||
{
|
||
TickTrackSplines();
|
||
TickPhaseElements(UpdatePhase.SplineRebuild, songTime);
|
||
}
|
||
|
||
// ─── Phase 4: TrackCore ──────────────────────────────────────────────
|
||
TickPhase(UpdatePhase.TrackCore, songTime);
|
||
|
||
// ─── Phase 5: TrackFollower ──────────────────────────────────────────
|
||
using (PhaseMarkers[PhaseIndex(UpdatePhase.TrackFollower)].Auto())
|
||
{
|
||
TickPhaseElements(UpdatePhase.TrackFollower, songTime);
|
||
GameManager.Instance.trackManager.ManualLateUpdate(songTime);
|
||
}
|
||
|
||
// ─── Phase 6: Note(由 NoteManager 内部驱动)─────────────────────────
|
||
using (PhaseMarkers[PhaseIndex(UpdatePhase.Note)].Auto())
|
||
{
|
||
TickPhaseElements(UpdatePhase.Note, songTime);
|
||
GameManager.Instance.noteManager.ManualUpdate(songTime);
|
||
GameManager.Instance.noteManager.ManualLateUpdate(songTime);
|
||
}
|
||
|
||
// ─── Phase 7: Effect ─────────────────────────────────────────────────
|
||
TickPhase(UpdatePhase.Effect, songTime);
|
||
|
||
// ─── Phase 8: Misc ───────────────────────────────────────────────────
|
||
using (PhaseMarkers[PhaseIndex(UpdatePhase.Misc)].Auto())
|
||
{
|
||
TickPhaseElements(UpdatePhase.Misc, songTime);
|
||
GameManager.Instance.beatmapContainer?.ExecuteLowPriorityActions();
|
||
}
|
||
}
|
||
|
||
#endregion
|
||
|
||
#region [阶段执行] Phase Execution
|
||
|
||
/// <summary>
|
||
/// 执行指定阶段的所有已注册 IScheduledElement。
|
||
/// 倒序遍历防止更新途中元素自行销毁导致越界。
|
||
/// </summary>
|
||
private void TickPhase(UpdatePhase phase, float songTime)
|
||
{
|
||
using (PhaseMarkers[PhaseIndex(phase)].Auto())
|
||
{
|
||
TickPhaseElements(phase, songTime);
|
||
}
|
||
}
|
||
|
||
private void TickPhaseElements(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);
|
||
}
|
||
}
|
||
}
|
||
|
||
private void TickTrackSplines()
|
||
{
|
||
for (int i = _trackSplines.Count - 1; i >= 0; i--)
|
||
{
|
||
Track track = _trackSplines[i];
|
||
if (track == null)
|
||
{
|
||
_trackSplines.RemoveAt(i);
|
||
continue;
|
||
}
|
||
|
||
if (!track.isActiveAndEnabled)
|
||
{
|
||
continue;
|
||
}
|
||
|
||
track.trackPathSubmodule?.path?.RunManualUpdate();
|
||
track.trackRendererSubmodule?.meshGenerator?.RunManualUpdate();
|
||
}
|
||
}
|
||
|
||
#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()
|
||
{
|
||
int colorCount = _dirtyColorSubmodules.Count;
|
||
for (int i = 0; i < colorCount; i++)
|
||
{
|
||
IHaveColorSubmodule target = _dirtyColorSubmodules[i];
|
||
if (target?.colorSubmodule == null)
|
||
{
|
||
continue;
|
||
}
|
||
|
||
target.colorSubmodule.queuedForApply = false;
|
||
target.UpdateColor(true);
|
||
}
|
||
if (colorCount > 0)
|
||
{
|
||
_dirtyColorSubmodules.RemoveRange(0, Math.Min(colorCount, _dirtyColorSubmodules.Count));
|
||
}
|
||
|
||
int transformCount = _dirtyTransformSubmodules.Count;
|
||
for (int i = 0; i < transformCount; i++)
|
||
{
|
||
IHaveTransformSubmodule target = _dirtyTransformSubmodules[i];
|
||
if (target?.transformSubmodule == null)
|
||
{
|
||
continue;
|
||
}
|
||
|
||
target.transformSubmodule.queuedForApply = false;
|
||
target.UpdateTransform(true);
|
||
}
|
||
if (transformCount > 0)
|
||
{
|
||
_dirtyTransformSubmodules.RemoveRange(0, Math.Min(transformCount, _dirtyTransformSubmodules.Count));
|
||
}
|
||
}
|
||
|
||
/// <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
|
||
}
|
||
}
|