GFramework/GFramework.Game/Scene/SceneRouterBase.cs
GeWuYou 4afa856fdc refactor(game): 重构路由系统并优化CI测试流程
- 将SceneRouterBase和UiRouterBase继承自新的RouterBase基类
- 移除原有的守卫管理相关代码,统一使用基类实现
- 更新路由栈操作使用基类提供的Stack属性
- 重写Current、Contains等方法以使用基类实现
- 在CI工作流中启用并发测试执行以提升性能
- 添加等待步骤确保并发测试完成
- 更新项目文件排除测试项目的编译
- 在解决方案文件中添加GFramework.Game.Tests项目引用
- 新增RouterBase基类提供通用路由管理功能
2026-03-17 15:01:55 +08:00

426 lines
12 KiB
C#
Raw Permalink 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.

// 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
}