using System.Collections;
using System.Collections.Generic;
using Ichni.RhythmGame;
using SLSUtilities.General;
using UnityEngine;
namespace Ichni.RhythmGame
{
///
/// 编辑器 NoteManager:集中管理场上所有 Note 的激活/隐藏与逐帧更新。
///
/// 与游戏本体的关键区别——时间可逆性:
/// 游戏本体时间单向流动,_nextNoteIndex 只递增。
/// 编辑器时间可随意跳转 / 倒退(拖动 Slider、按数字键),
/// 因此本管理器采用"每帧重新扫描激活窗口"的策略:
/// - 时间区间内的 Note → 激活并更新
/// - 时间区间外的 Note → 隐藏
/// 保证无论时间如何跳转,Note 的可见性和状态始终与 songTime 一致。
///
public class NoteManager : Singleton
{
#region [单例别名] Singleton Alias
public new static NoteManager instance => Instance;
#endregion
#region [数据结构] Note Record
/// Note 条目:存储 Note 本身及其激活/消失的时间阈值
private struct NoteRecord
{
public NoteBase note;
public float activationTime; // Note 应当进入可见状态的时间点
public float finishTime; // Note 应当退出可见状态的时间点
}
///
/// 所有已注册的 Note,按 activationTime 升序排列。
/// 排序后可用二分查找高效定位当前窗口。
///
private List _pendingNotes = new List(128);
/// 当前帧内激活的 Note(缓存,减少 GC)
private List _currentlyActive = new List(64);
private bool _isDirty = false; // 注册/更新后需要重排序
#endregion
#region [注册与管理] Registration
/// 注册一个新 Note(isNewOne = true)
public void RegisterNote(NoteBase note, float activationTime, float finishTime)
{
_pendingNotes.Add(new NoteRecord
{
note = note,
activationTime = activationTime,
finishTime = finishTime
});
_isDirty = true;
}
/// 更新已注册 Note 的时间信息(参数改变后重新计算窗口)
public void ChangeNoteInfo(NoteBase note, float activationTime, float finishTime)
{
int idx = _pendingNotes.FindIndex(r => r.note == note);
if (idx != -1)
{
_pendingNotes[idx] = new NoteRecord
{
note = note,
activationTime = activationTime,
finishTime = finishTime
};
_isDirty = true;
}
}
/// 手动触发排序(若需要在注册大批 Note 后一次性完成)
public void AllNotesRegistered()
{
SortIfDirty();
}
private void SortIfDirty()
{
if (!_isDirty) return;
// 移除已销毁的 Note
_pendingNotes.RemoveAll(r => r.note == null);
_pendingNotes.Sort((a, b) => a.activationTime.CompareTo(b.activationTime));
_isDirty = false;
}
#endregion
#region [中央集权主循环] Manager-Driven Tick
///
/// 由 EditorManager.Update 统一调度。
///
///
/// 编辑器时间可逆策略:每帧通过二分查找定位当前 songTime 覆盖的激活窗口,
/// 对窗口内的 Note 激活并调用 Update(),对窗口外的 Note 隐藏。
/// 无需维护 _nextNoteIndex 指针,天然支持时间任意跳转与倒退。
///
///
public void ManualTick(float songTime)
{
SortIfDirty();
// 1. 清空上一帧的激活记录
_currentlyActive.Clear();
// 2. 二分查找第一个 activationTime <= songTime 的 Note 起始位置
int count = _pendingNotes.Count;
if (count == 0) return;
int left = 0, right = count - 1, firstInWindow = count;
while (left <= right)
{
int mid = (left + right) >> 1;
if (_pendingNotes[mid].activationTime <= songTime)
{
firstInWindow = mid;
left = mid + 1;
}
else
{
right = mid - 1;
}
}
// firstInWindow 此时指向最后一个 activationTime <= songTime 的 Note,
// 从 0 向左扫描(也就是从头扫描到 firstInWindow)都可能在窗口内
// 实际需要:activationTime <= songTime && finishTime >= songTime
// 3. 遍历所有 Note,判断是否在当前窗口内
for (int i = 0; i < count; i++)
{
var record = _pendingNotes[i];
if (record.note == null) continue;
bool shouldBeActive = record.activationTime <= songTime && record.finishTime >= songTime;
bool isActive = record.note.gameObject.activeSelf;
if (shouldBeActive)
{
if (!isActive) record.note.gameObject.SetActive(true);
_currentlyActive.Add(record.note);
}
else
{
if (isActive) record.note.gameObject.SetActive(false);
}
}
// 4. 集中驱动当前帧内所有激活 Note 的逐帧更新
for (int i = _currentlyActive.Count - 1; i >= 0; i--)
{
_currentlyActive[i].ManualUpdate(songTime);
}
}
#endregion
}
}