using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.AddressableAssets; using UnityEngine.ResourceManagement.AsyncOperations; using UnityEngine.ResourceManagement.ResourceProviders; using UnityEngine.SceneManagement; using BaseGames.Core.Assets; namespace BaseGames.World.Streaming { /// /// 单个房间的流式加载状态包装器。 /// /// 由 创建和管理,封装以下职责: /// /// Addressables 加载 / 卸载句柄 /// Dormantize:批量关闭 Renderer、暂停 IRoomLifecycle(AI、音效等) /// Activate:批量恢复渲染,分帧唤醒 IRoomLifecycle /// Deactivate:玩家离开后关闭本房间内容 /// /// /// public class RoomHandle { // ── 公开属性 ────────────────────────────────────────────────────────────── public string RoomId { get; } public RoomState State { get; private set; } = RoomState.Unloaded; public int EstimatedMemKB { get; set; } /// 上次处于 Active 状态的时间(Time.time)。用于 LRU 卸载优先级计算。 public float LastActiveTime { get; private set; } /// 加载完成后由 RoomController.Start() 通过 IRoomStreamingManager.RegisterRoomController 注入。 public RoomController RoomController { get; set; } // ── 私有字段 ────────────────────────────────────────────────────────────── private AsyncOperationHandle _sceneHandle; // 缓存的场景组件集合,Dormantize 后可快速 Activate private Renderer[] _renderers; private IRoomLifecycle[] _lifecycles; private AudioSource[] _audioSources; private Light[] _lights; private ParticleSystem[] _particleSystems; // 分帧激活的协程宿主(由 RoomStreamingManager 提供) private MonoBehaviour _coroutineRunner; private int _lifecycleActivatePerFrame; // ── 构造 ────────────────────────────────────────────────────────────────── public RoomHandle(string roomId, MonoBehaviour coroutineRunner, int lifecycleActivatePerFrame) { RoomId = roomId; _coroutineRunner = coroutineRunner; _lifecycleActivatePerFrame = lifecycleActivatePerFrame; } // ── 加载 / 卸载 ─────────────────────────────────────────────────────────── /// /// 开始后台异步加载(Additive)。 /// 加载完成后自动调用 将房间置于休眠状态。 /// public IEnumerator LoadAsync(string addressableKey) { if (State != RoomState.Unloaded) { Debug.LogWarning($"[RoomHandle] {RoomId} 非 Unloaded 状态,忽略重复加载请求。"); yield break; } State = RoomState.Loading; _sceneHandle = AssetLoader.LoadSceneAsync(addressableKey, LoadSceneMode.Additive); while (!_sceneHandle.IsDone) yield return null; if (_sceneHandle.Status != AsyncOperationStatus.Succeeded) { Debug.LogError($"[RoomHandle] {RoomId} 加载失败:{addressableKey}"); State = RoomState.Unloaded; yield break; } // 收集场景内组件引用,然后置于休眠 CollectSceneComponents(); Dormantize(); } /// 异步卸载本房间。完成后 State → Unloaded。 public IEnumerator UnloadAsync() { if (State == RoomState.Unloaded || State == RoomState.Unloading || !_sceneHandle.IsValid()) yield break; // 若正处于加载中,等待加载完成后再卸载(Addressables 不支持对未完成的句柄直接卸载) if (State == RoomState.Loading) { while (!_sceneHandle.IsDone) yield return null; } // Active 状态先走 Deactivate,再设置 Unloading bool needsDeactivate = State == RoomState.Active || State == RoomState.Activating || State == RoomState.Cooling; if (needsDeactivate) Deactivate(); State = RoomState.Unloading; var op = AssetLoader.UnloadSceneAsync(_sceneHandle); yield return op; _renderers = null; _lifecycles = null; _audioSources = null; _lights = null; _particleSystems = null; RoomController = null; State = RoomState.Unloaded; } // ── Dormantize ──────────────────────────────────────────────────────────── /// /// 将已加载的房间置于休眠状态。 /// 关闭所有 Renderer 和 AudioSource,通知所有 IRoomLifecycle 组件进入休眠。 /// 场景内 GameObject 保持激活(便于快速恢复),但不消耗渲染和 AI 开销。 /// public void Dormantize() { // 允许从 Loading(初次加载完成后)、Active、Activating、Cooling 进入 if (State != RoomState.Loading && State != RoomState.Active && State != RoomState.Activating && State != RoomState.Cooling) { Debug.LogWarning($"[RoomHandle] {RoomId} 在 {State} 状态下调用 Dormantize,忽略。"); return; } if (_renderers != null) foreach (var r in _renderers) if (r != null) r.enabled = false; if (_audioSources != null) foreach (var a in _audioSources) if (a != null) { a.Stop(); a.enabled = false; } if (_lights != null) foreach (var l in _lights) if (l != null) l.enabled = false; if (_particleSystems != null) foreach (var ps in _particleSystems) if (ps != null) ps.Pause(); if (_lifecycles != null) foreach (var l in _lifecycles) l?.OnRoomDormant(); State = RoomState.Dormant; } // ── Activate ────────────────────────────────────────────────────────────── /// /// 激活此房间(Dormant → Active)。 /// 恢复 Renderer、AudioSource,分帧唤醒 IRoomLifecycle,并设置玩家出生点。 /// 目标房间必须已处于 状态。 /// public IEnumerator Activate(SpawnContext context) { if (State != RoomState.Dormant) { Debug.LogError($"[RoomHandle] {RoomId} 不是 Dormant 状态(当前:{State}),无法激活。"); yield break; } State = RoomState.Activating; // 先恢复渲染(让玩家立刻能看到新房间) if (_renderers != null) foreach (var r in _renderers) if (r != null) r.enabled = true; if (_audioSources != null) foreach (var a in _audioSources) if (a != null) a.enabled = true; if (_lights != null) foreach (var l in _lights) if (l != null) l.enabled = true; if (_particleSystems != null) foreach (var ps in _particleSystems) if (ps != null) ps.Play(); // 相机 + 出生点(由 RoomController 处理) RoomController?.OnRoomActivate(context); // 分帧激活 IRoomLifecycle(AI、特效等) yield return _coroutineRunner.StartCoroutine(ActivateLifecyclesGradually(context)); State = RoomState.Active; LastActiveTime = Time.time; } /// 将 IRoomLifecycle 组件分帧激活,避免单帧 CPU 峰值。 private IEnumerator ActivateLifecyclesGradually(SpawnContext context) { if (_lifecycles == null) yield break; int activatedThisFrame = 0; foreach (var lc in _lifecycles) { if (lc == null) continue; lc.OnRoomActivate(context); activatedThisFrame++; if (activatedThisFrame >= _lifecycleActivatePerFrame) { activatedThisFrame = 0; yield return null; } } } // ── Deactivate ──────────────────────────────────────────────────────────── /// /// 玩家离开后关闭此房间(Active / Activating → Dormant)。 /// 与 相同,但语义上表示从 Active 退出。 /// public void Deactivate() { if (State != RoomState.Active && State != RoomState.Activating && State != RoomState.Cooling) { Debug.LogWarning($"[RoomHandle] {RoomId} 在 {State} 状态下调用 Deactivate,忽略。"); return; } Dormantize(); } // ── 冷却 ────────────────────────────────────────────────────────────────── /// /// 标记为冷却中,同时休眠渲染和 AI 开销。 /// 玩家离开后不立即卸载,等待冷却计时结束后再卸载。 /// public void BeginCooling() { if (State != RoomState.Active && State != RoomState.Activating) return; // 先休眠渲染和 AI(与 Dormantize 相同,但最终 State 设为 Cooling 而非 Dormant) Dormantize(); State = RoomState.Cooling; } /// /// 冷却期间玩家返回,将状态从 Cooling 重置为 Dormant。 /// 渲染已在 BeginCooling 时关闭,无需重复操作。 /// public void ResetToDormant() { if (State == RoomState.Cooling) State = RoomState.Dormant; } // ── 内部工具 ────────────────────────────────────────────────────────────── /// /// 收集加载完成的场景中所有需要被管理的组件引用。 /// 在 LoadAsync 完成后、Dormantize 之前调用一次。 /// private void CollectSceneComponents() { if (!_sceneHandle.IsValid()) return; Scene scene = _sceneHandle.Result.Scene; GameObject[] roots = scene.GetRootGameObjects(); var rendererList = new List(); var lifecycleList = new List(); var audioSourceList = new List(); var lightList = new List(); var particleList = new List(); foreach (var root in roots) { rendererList.AddRange(root.GetComponentsInChildren(true)); lifecycleList.AddRange(root.GetComponentsInChildren(true)); audioSourceList.AddRange(root.GetComponentsInChildren(true)); lightList.AddRange(root.GetComponentsInChildren(true)); particleList.AddRange(root.GetComponentsInChildren(true)); } _renderers = rendererList.ToArray(); _lifecycles = lifecycleList.ToArray(); _audioSources = audioSourceList.ToArray(); _lights = lightList.ToArray(); _particleSystems = particleList.ToArray(); } public override string ToString() => $"RoomHandle[{RoomId}, {State}]"; } }