mirror of
https://github.com/GeWuYou/GFramework.git
synced 2026-03-22 02:24:30 +08:00
- 将SceneRouterBase和UiRouterBase继承自新的RouterBase基类 - 移除原有的守卫管理相关代码,统一使用基类实现 - 更新路由栈操作使用基类提供的Stack属性 - 重写Current、Contains等方法以使用基类实现 - 在CI工作流中启用并发测试执行以提升性能 - 添加等待步骤确保并发测试完成 - 更新项目文件排除测试项目的编译 - 在解决方案文件中添加GFramework.Game.Tests项目引用 - 新增RouterBase基类提供通用路由管理功能
426 lines
12 KiB
C#
426 lines
12 KiB
C#
// Copyright (c) 2026 GeWuYou
|
||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||
// you may not use this file except in compliance with the License.
|
||
// You may obtain a copy of the License at
|
||
//
|
||
// http://www.apache.org/licenses/LICENSE-2.0
|
||
//
|
||
// Unless required by applicable law or agreed to in writing, software
|
||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||
// See the License for the specific language governing permissions and
|
||
// limitations under the License.
|
||
|
||
using GFramework.Core.Abstractions.Logging;
|
||
using GFramework.Core.Extensions;
|
||
using GFramework.Core.Logging;
|
||
using GFramework.Game.Abstractions.Enums;
|
||
using GFramework.Game.Abstractions.Scene;
|
||
using GFramework.Game.Routing;
|
||
|
||
namespace GFramework.Game.Scene;
|
||
|
||
/// <summary>
|
||
/// 场景路由基类,提供场景切换和卸载的基础功能。
|
||
/// 实现了 <see cref="ISceneRouter"/> 接口,用于管理场景的加载、替换和卸载操作。
|
||
/// </summary>
|
||
public abstract class SceneRouterBase
|
||
: RouterBase<ISceneBehavior, ISceneEnterParam>, ISceneRouter
|
||
{
|
||
private static readonly ILogger Log =
|
||
LoggerFactoryResolver.Provider.CreateLogger(nameof(SceneRouterBase));
|
||
|
||
private readonly SceneTransitionPipeline _pipeline = new();
|
||
|
||
private readonly SemaphoreSlim _transitionLock = new(1, 1);
|
||
private ISceneFactory _factory = null!;
|
||
|
||
/// <summary>
|
||
/// 场景根节点
|
||
/// </summary>
|
||
protected ISceneRoot? Root;
|
||
|
||
/// <summary>
|
||
/// 获取当前场景行为对象。
|
||
/// </summary>
|
||
public new ISceneBehavior? Current => Stack.Count > 0 ? Stack.Peek() : null;
|
||
|
||
/// <summary>
|
||
/// 获取当前场景的键名。
|
||
/// </summary>
|
||
public new string? CurrentKey => Current?.Key;
|
||
|
||
/// <summary>
|
||
/// 获取场景栈的只读视图,按压入顺序排列(从栈底到栈顶)。
|
||
/// </summary>
|
||
IEnumerable<ISceneBehavior> ISceneRouter.Stack => base.Stack.Reverse();
|
||
|
||
/// <summary>
|
||
/// 获取是否正在进行场景转换。
|
||
/// </summary>
|
||
public bool IsTransitioning { get; private set; }
|
||
|
||
/// <summary>
|
||
/// 绑定场景根节点。
|
||
/// </summary>
|
||
/// <param name="root">场景根节点实例。</param>
|
||
public void BindRoot(ISceneRoot root)
|
||
{
|
||
Root = root;
|
||
Log.Debug("Bind Scene Root: {0}", root.GetType().Name);
|
||
}
|
||
|
||
#region Replace
|
||
|
||
/// <summary>
|
||
/// 替换当前场景为指定场景。
|
||
/// </summary>
|
||
/// <param name="sceneKey">目标场景键名。</param>
|
||
/// <param name="param">场景进入参数。</param>
|
||
/// <returns>异步任务。</returns>
|
||
public async ValueTask ReplaceAsync(
|
||
string sceneKey,
|
||
ISceneEnterParam? param = null)
|
||
{
|
||
await _transitionLock.WaitAsync();
|
||
try
|
||
{
|
||
IsTransitioning = true;
|
||
|
||
var @event = CreateEvent(sceneKey, SceneTransitionType.Replace, param);
|
||
|
||
await _pipeline.ExecuteAroundAsync(@event, async () =>
|
||
{
|
||
await BeforeChangeAsync(@event);
|
||
await ClearInternalAsync();
|
||
await PushInternalAsync(sceneKey, param);
|
||
await AfterChangeAsync(@event);
|
||
});
|
||
}
|
||
finally
|
||
{
|
||
IsTransitioning = false;
|
||
_transitionLock.Release();
|
||
}
|
||
}
|
||
|
||
#endregion
|
||
|
||
#region Query
|
||
|
||
/// <summary>
|
||
/// 检查指定场景是否在栈中。
|
||
/// </summary>
|
||
/// <param name="sceneKey">场景键名。</param>
|
||
/// <returns>如果场景在栈中返回true,否则返回false。</returns>
|
||
public new bool Contains(string sceneKey)
|
||
{
|
||
return Stack.Any(s => s.Key == sceneKey);
|
||
}
|
||
|
||
#endregion
|
||
|
||
/// <summary>
|
||
/// 注册场景过渡处理器。
|
||
/// </summary>
|
||
/// <param name="handler">处理器实例。</param>
|
||
/// <param name="options">执行选项。</param>
|
||
public void RegisterHandler(ISceneTransitionHandler handler, SceneTransitionHandlerOptions? options = null)
|
||
{
|
||
_pipeline.RegisterHandler(handler, options);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 注销场景过渡处理器。
|
||
/// </summary>
|
||
/// <param name="handler">处理器实例。</param>
|
||
public void UnregisterHandler(ISceneTransitionHandler handler)
|
||
{
|
||
_pipeline.UnregisterHandler(handler);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 注册环绕场景过渡处理器。
|
||
/// </summary>
|
||
/// <param name="handler">环绕处理器实例。</param>
|
||
/// <param name="options">处理器选项配置。</param>
|
||
public void RegisterAroundHandler(
|
||
ISceneAroundTransitionHandler handler,
|
||
SceneTransitionHandlerOptions? options = null)
|
||
{
|
||
_pipeline.RegisterAroundHandler(handler, options);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 注销环绕场景过渡处理器。
|
||
/// </summary>
|
||
/// <param name="handler">环绕处理器实例。</param>
|
||
public void UnregisterAroundHandler(
|
||
ISceneAroundTransitionHandler handler)
|
||
{
|
||
_pipeline.UnregisterAroundHandler(handler);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 注册场景过渡处理器的抽象方法,由子类实现。
|
||
/// </summary>
|
||
protected override abstract void RegisterHandlers();
|
||
|
||
/// <summary>
|
||
/// 系统初始化方法,获取场景工厂并注册处理器。
|
||
/// </summary>
|
||
protected override void OnInit()
|
||
{
|
||
_factory = this.GetUtility<ISceneFactory>()!;
|
||
Log.Debug("SceneRouterBase initialized. Factory={0}", _factory.GetType().Name);
|
||
RegisterHandlers();
|
||
}
|
||
|
||
#region Push
|
||
|
||
/// <summary>
|
||
/// 将指定场景推入栈顶。
|
||
/// </summary>
|
||
/// <param name="sceneKey">目标场景键名。</param>
|
||
/// <param name="param">场景进入参数。</param>
|
||
/// <returns>异步任务。</returns>
|
||
public async ValueTask PushAsync(
|
||
string sceneKey,
|
||
ISceneEnterParam? param = null)
|
||
{
|
||
await _transitionLock.WaitAsync();
|
||
try
|
||
{
|
||
IsTransitioning = true;
|
||
|
||
var @event = CreateEvent(sceneKey, SceneTransitionType.Push, param);
|
||
|
||
await _pipeline.ExecuteAroundAsync(@event, async () =>
|
||
{
|
||
await BeforeChangeAsync(@event);
|
||
await PushInternalAsync(sceneKey, param);
|
||
await AfterChangeAsync(@event);
|
||
});
|
||
}
|
||
finally
|
||
{
|
||
IsTransitioning = false;
|
||
_transitionLock.Release();
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 内部推送场景实现方法。
|
||
/// 执行守卫检查、场景创建、添加到场景树、加载资源、暂停当前场景、压入栈等操作。
|
||
/// </summary>
|
||
/// <param name="sceneKey">场景键名。</param>
|
||
/// <param name="param">场景进入参数。</param>
|
||
/// <returns>异步任务。</returns>
|
||
private async ValueTask PushInternalAsync(
|
||
string sceneKey,
|
||
ISceneEnterParam? param)
|
||
{
|
||
if (Contains(sceneKey))
|
||
{
|
||
Log.Warn("Scene already in stack: {0}", sceneKey);
|
||
return;
|
||
}
|
||
|
||
// 守卫检查
|
||
if (!await ExecuteEnterGuardsAsync(sceneKey, param))
|
||
{
|
||
Log.Warn("Push blocked by guard: {0}", sceneKey);
|
||
return;
|
||
}
|
||
|
||
// 通过 Factory 创建场景实例
|
||
var scene = _factory.Create(sceneKey);
|
||
|
||
// 添加到场景树
|
||
Root!.AddScene(scene);
|
||
|
||
// 加载资源
|
||
await scene.OnLoadAsync(param);
|
||
|
||
// 暂停当前场景
|
||
if (Stack.Count > 0)
|
||
{
|
||
var current = Stack.Peek();
|
||
await current.OnPauseAsync();
|
||
}
|
||
|
||
// 压入栈
|
||
Stack.Push(scene);
|
||
|
||
// 进入场景
|
||
await scene.OnEnterAsync();
|
||
|
||
Log.Debug("Push Scene: {0}, stackCount={1}",
|
||
sceneKey, Stack.Count);
|
||
}
|
||
|
||
#endregion
|
||
|
||
#region Pop
|
||
|
||
/// <summary>
|
||
/// 弹出栈顶场景。
|
||
/// </summary>
|
||
/// <returns>异步任务。</returns>
|
||
public async ValueTask PopAsync()
|
||
{
|
||
await _transitionLock.WaitAsync();
|
||
try
|
||
{
|
||
IsTransitioning = true;
|
||
|
||
var @event = CreateEvent(null, SceneTransitionType.Pop);
|
||
|
||
await _pipeline.ExecuteAroundAsync(@event, async () =>
|
||
{
|
||
await BeforeChangeAsync(@event);
|
||
await PopInternalAsync();
|
||
await AfterChangeAsync(@event);
|
||
});
|
||
}
|
||
finally
|
||
{
|
||
IsTransitioning = false;
|
||
_transitionLock.Release();
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 内部弹出场景实现方法。
|
||
/// 执行守卫检查、退出场景、卸载资源、从场景树移除、恢复下一个场景等操作。
|
||
/// </summary>
|
||
/// <returns>异步任务。</returns>
|
||
private async ValueTask PopInternalAsync()
|
||
{
|
||
if (Stack.Count == 0)
|
||
return;
|
||
|
||
var top = Stack.Peek();
|
||
|
||
// 守卫检查
|
||
if (!await ExecuteLeaveGuardsAsync(top.Key))
|
||
{
|
||
Log.Warn("Pop blocked by guard: {0}", top.Key);
|
||
return;
|
||
}
|
||
|
||
Stack.Pop();
|
||
|
||
// 退出场景
|
||
await top.OnExitAsync();
|
||
|
||
// 卸载资源
|
||
await top.OnUnloadAsync();
|
||
|
||
// 从场景树移除
|
||
Root!.RemoveScene(top);
|
||
|
||
// 恢复下一个场景
|
||
if (Stack.Count > 0)
|
||
{
|
||
var next = Stack.Peek();
|
||
await next.OnResumeAsync();
|
||
}
|
||
|
||
Log.Debug("Pop Scene, stackCount={0}", Stack.Count);
|
||
}
|
||
|
||
#endregion
|
||
|
||
#region Clear
|
||
|
||
/// <summary>
|
||
/// 清空所有场景栈。
|
||
/// </summary>
|
||
/// <returns>异步任务。</returns>
|
||
public async ValueTask ClearAsync()
|
||
{
|
||
await _transitionLock.WaitAsync();
|
||
try
|
||
{
|
||
IsTransitioning = true;
|
||
|
||
var @event = CreateEvent(null, SceneTransitionType.Clear);
|
||
|
||
await _pipeline.ExecuteAroundAsync(@event, async () =>
|
||
{
|
||
await BeforeChangeAsync(@event);
|
||
await ClearInternalAsync();
|
||
await AfterChangeAsync(@event);
|
||
});
|
||
}
|
||
finally
|
||
{
|
||
IsTransitioning = false;
|
||
_transitionLock.Release();
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 内部清空场景栈实现方法。
|
||
/// 循环调用弹出操作直到栈为空。
|
||
/// </summary>
|
||
/// <returns>异步任务。</returns>
|
||
private async ValueTask ClearInternalAsync()
|
||
{
|
||
while (Stack.Count > 0)
|
||
{
|
||
await PopInternalAsync();
|
||
}
|
||
}
|
||
|
||
#endregion
|
||
|
||
#region Helper Methods
|
||
|
||
/// <summary>
|
||
/// 创建场景转换事件对象。
|
||
/// </summary>
|
||
/// <param name="toSceneKey">目标场景键名。</param>
|
||
/// <param name="type">转换类型。</param>
|
||
/// <param name="param">进入参数。</param>
|
||
/// <returns>场景转换事件实例。</returns>
|
||
private SceneTransitionEvent CreateEvent(
|
||
string? toSceneKey,
|
||
SceneTransitionType type,
|
||
ISceneEnterParam? param = null)
|
||
{
|
||
return new SceneTransitionEvent
|
||
{
|
||
FromSceneKey = CurrentKey,
|
||
ToSceneKey = toSceneKey,
|
||
TransitionType = type,
|
||
EnterParam = param
|
||
};
|
||
}
|
||
|
||
/// <summary>
|
||
/// 执行转换前阶段的处理逻辑。
|
||
/// </summary>
|
||
/// <param name="event">场景转换事件。</param>
|
||
/// <returns>异步任务。</returns>
|
||
private async Task BeforeChangeAsync(SceneTransitionEvent @event)
|
||
{
|
||
Log.Debug("BeforeChange phases started: {0}", @event.TransitionType);
|
||
await _pipeline.ExecuteAsync(@event, SceneTransitionPhases.BeforeChange);
|
||
Log.Debug("BeforeChange phases completed: {0}", @event.TransitionType);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 执行转换后阶段的处理逻辑。
|
||
/// </summary>
|
||
/// <param name="event">场景转换事件。</param>
|
||
private async Task AfterChangeAsync(SceneTransitionEvent @event)
|
||
{
|
||
Log.Debug("AfterChange phases started: {0}", @event.TransitionType);
|
||
await _pipeline.ExecuteAsync(@event, SceneTransitionPhases.AfterChange);
|
||
Log.Debug("AfterChange phases completed: {0}", @event.TransitionType);
|
||
}
|
||
|
||
#endregion
|
||
} |