mirror of
https://github.com/GeWuYou/GFramework.git
synced 2026-03-22 02:24:30 +08:00
refactor(game): 重构路由系统并优化CI测试流程
- 将SceneRouterBase和UiRouterBase继承自新的RouterBase基类 - 移除原有的守卫管理相关代码,统一使用基类实现 - 更新路由栈操作使用基类提供的Stack属性 - 重写Current、Contains等方法以使用基类实现 - 在CI工作流中启用并发测试执行以提升性能 - 添加等待步骤确保并发测试完成 - 更新项目文件排除测试项目的编译 - 在解决方案文件中添加GFramework.Game.Tests项目引用 - 新增RouterBase基类提供通用路由管理功能
This commit is contained in:
parent
27858df94e
commit
4afa856fdc
25
.github/workflows/ci.yml
vendored
25
.github/workflows/ci.yml
vendored
@ -113,29 +113,42 @@ jobs:
|
||||
run: dotnet build -c Release --no-restore
|
||||
|
||||
# 运行单元测试,输出TRX格式结果到TestResults目录
|
||||
# 使用并发执行以加快测试速度
|
||||
- name: Test - Core
|
||||
run: |
|
||||
dotnet test GFramework.Core.Tests \
|
||||
-c Release \
|
||||
--no-build \
|
||||
--logger "trx;LogFileName=core-$RANDOM.trx" \
|
||||
--results-directory TestResults
|
||||
|
||||
--results-directory TestResults &
|
||||
|
||||
- name: Test - Game
|
||||
run: |
|
||||
dotnet test GFramework.Game.Tests \
|
||||
-c Release \
|
||||
--no-build \
|
||||
--logger "trx;LogFileName=game-$RANDOM.trx" \
|
||||
--results-directory TestResults &
|
||||
|
||||
- name: Test - SourceGenerators
|
||||
run: |
|
||||
dotnet test GFramework.SourceGenerators.Tests \
|
||||
-c Release \
|
||||
--no-build \
|
||||
--logger "trx;LogFileName=sg-$RANDOM.trx" \
|
||||
--results-directory TestResults
|
||||
|
||||
- name: Test - GFramework.Ecs.Arch.Tests
|
||||
--results-directory TestResults &
|
||||
|
||||
- name: Test - ECS Arch
|
||||
run: |
|
||||
dotnet test GFramework.Ecs.Arch.Tests \
|
||||
-c Release \
|
||||
--no-build \
|
||||
--logger "trx;LogFileName=ecs-arch-$RANDOM.trx" \
|
||||
--results-directory TestResults
|
||||
--results-directory TestResults &
|
||||
|
||||
# 等待所有并发测试完成
|
||||
- name: Wait for tests
|
||||
run: wait
|
||||
|
||||
- name: Generate CTRF report
|
||||
run: |
|
||||
|
||||
25
GFramework.Game.Abstractions/Routing/IRoute.cs
Normal file
25
GFramework.Game.Abstractions/Routing/IRoute.cs
Normal file
@ -0,0 +1,25 @@
|
||||
// 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.
|
||||
|
||||
namespace GFramework.Game.Abstractions.Routing;
|
||||
|
||||
/// <summary>
|
||||
/// 路由项接口,表示可路由的对象
|
||||
/// </summary>
|
||||
public interface IRoute
|
||||
{
|
||||
/// <summary>
|
||||
/// 路由键值,用于唯一标识路由项
|
||||
/// </summary>
|
||||
string Key { get; }
|
||||
}
|
||||
26
GFramework.Game.Abstractions/Routing/IRouteContext.cs
Normal file
26
GFramework.Game.Abstractions/Routing/IRouteContext.cs
Normal file
@ -0,0 +1,26 @@
|
||||
// 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.
|
||||
|
||||
namespace GFramework.Game.Abstractions.Routing;
|
||||
|
||||
/// <summary>
|
||||
/// 路由上下文接口,表示路由进入时的参数
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// 这是一个标记接口,用于类型约束。
|
||||
/// 具体的路由上下文类型应该实现此接口。
|
||||
/// </remarks>
|
||||
public interface IRouteContext
|
||||
{
|
||||
// 标记接口,用于类型约束
|
||||
}
|
||||
54
GFramework.Game.Abstractions/Routing/IRouteGuard.cs
Normal file
54
GFramework.Game.Abstractions/Routing/IRouteGuard.cs
Normal file
@ -0,0 +1,54 @@
|
||||
// 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.
|
||||
|
||||
namespace GFramework.Game.Abstractions.Routing;
|
||||
|
||||
/// <summary>
|
||||
/// 路由守卫接口,用于控制路由的进入和离开
|
||||
/// </summary>
|
||||
/// <typeparam name="TRoute">路由项类型</typeparam>
|
||||
public interface IRouteGuard<TRoute> where TRoute : IRoute
|
||||
{
|
||||
/// <summary>
|
||||
/// 守卫优先级,数值越小优先级越高
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// 守卫按优先级从小到大依次执行。
|
||||
/// 建议使用 0-100 的范围,默认为 50。
|
||||
/// </remarks>
|
||||
int Priority { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 是否可以中断后续守卫的执行
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// 如果为 true,当此守卫返回 true 或抛出异常时,将中断后续守卫的执行。
|
||||
/// 如果为 false,将继续执行后续守卫。
|
||||
/// </remarks>
|
||||
bool CanInterrupt { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 检查是否可以进入指定路由
|
||||
/// </summary>
|
||||
/// <param name="routeKey">路由键值</param>
|
||||
/// <param name="context">路由上下文</param>
|
||||
/// <returns>如果允许进入返回 true,否则返回 false</returns>
|
||||
ValueTask<bool> CanEnterAsync(string routeKey, IRouteContext? context);
|
||||
|
||||
/// <summary>
|
||||
/// 检查是否可以离开指定路由
|
||||
/// </summary>
|
||||
/// <param name="routeKey">路由键值</param>
|
||||
/// <returns>如果允许离开返回 true,否则返回 false</returns>
|
||||
ValueTask<bool> CanLeaveAsync(string routeKey);
|
||||
}
|
||||
@ -2,22 +2,24 @@
|
||||
// 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.Game.Abstractions.Routing;
|
||||
|
||||
namespace GFramework.Game.Abstractions.Scene;
|
||||
|
||||
/// <summary>
|
||||
/// 场景行为接口,定义了场景生命周期管理的标准方法。
|
||||
/// 实现此接口的类需要处理场景的加载、激活、暂停、恢复和卸载等核心操作。
|
||||
/// </summary>
|
||||
public interface ISceneBehavior
|
||||
public interface ISceneBehavior : IRoute
|
||||
{
|
||||
/// <summary>
|
||||
/// 获取场景的唯一标识符。
|
||||
|
||||
@ -2,19 +2,21 @@
|
||||
// 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.Game.Abstractions.Routing;
|
||||
|
||||
namespace GFramework.Game.Abstractions.Scene;
|
||||
|
||||
/// <summary>
|
||||
/// 场景进入参数接口
|
||||
/// 该接口用于定义场景跳转时传递的参数数据结构
|
||||
/// </summary>
|
||||
public interface ISceneEnterParam;
|
||||
public interface ISceneEnterParam : IRouteContext;
|
||||
@ -11,28 +11,16 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
using GFramework.Game.Abstractions.Routing;
|
||||
|
||||
namespace GFramework.Game.Abstractions.Scene;
|
||||
|
||||
/// <summary>
|
||||
/// 场景路由守卫接口,用于在场景切换前进行权限检查和条件验证。
|
||||
/// 实现此接口可以拦截场景的进入和离开操作。
|
||||
/// </summary>
|
||||
public interface ISceneRouteGuard
|
||||
public interface ISceneRouteGuard : IRouteGuard<ISceneBehavior>
|
||||
{
|
||||
/// <summary>
|
||||
/// 获取守卫的执行优先级。
|
||||
/// 数值越小优先级越高,越先执行。
|
||||
/// 建议范围:-1000 到 1000。
|
||||
/// </summary>
|
||||
int Priority { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 获取守卫是否可以中断后续守卫的执行。
|
||||
/// true 表示当前守卫通过后,可以跳过后续守卫直接允许操作。
|
||||
/// false 表示即使当前守卫通过,仍需执行所有后续守卫。
|
||||
/// </summary>
|
||||
bool CanInterrupt { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 异步检查是否允许进入指定场景。
|
||||
/// </summary>
|
||||
@ -46,5 +34,5 @@ public interface ISceneRouteGuard
|
||||
/// </summary>
|
||||
/// <param name="sceneKey">当前场景的唯一标识符。</param>
|
||||
/// <returns>如果允许离开则返回 true,否则返回 false。</returns>
|
||||
Task<bool> CanLeaveAsync(string sceneKey);
|
||||
new Task<bool> CanLeaveAsync(string sceneKey);
|
||||
}
|
||||
@ -1,11 +1,12 @@
|
||||
using GFramework.Game.Abstractions.Enums;
|
||||
using GFramework.Game.Abstractions.Routing;
|
||||
|
||||
namespace GFramework.Game.Abstractions.UI;
|
||||
|
||||
/// <summary>
|
||||
/// UI页面行为接口,定义了UI页面的生命周期方法和状态管理
|
||||
/// </summary>
|
||||
public interface IUiPageBehavior
|
||||
public interface IUiPageBehavior : IRoute
|
||||
{
|
||||
/// <summary>
|
||||
/// 获取或设置当前UI句柄。
|
||||
|
||||
@ -1,7 +1,9 @@
|
||||
namespace GFramework.Game.Abstractions.UI;
|
||||
using GFramework.Game.Abstractions.Routing;
|
||||
|
||||
namespace GFramework.Game.Abstractions.UI;
|
||||
|
||||
/// <summary>
|
||||
/// UI页面进入参数接口
|
||||
/// 该接口用于定义UI页面跳转时传递的参数数据结构
|
||||
/// </summary>
|
||||
public interface IUiPageEnterParam;
|
||||
public interface IUiPageEnterParam : IRouteContext;
|
||||
@ -1,22 +1,13 @@
|
||||
using GFramework.Game.Abstractions.Routing;
|
||||
|
||||
namespace GFramework.Game.Abstractions.UI;
|
||||
|
||||
/// <summary>
|
||||
/// UI路由守卫接口
|
||||
/// 用于拦截和处理UI路由切换,实现业务逻辑解耦
|
||||
/// </summary>
|
||||
public interface IUiRouteGuard
|
||||
public interface IUiRouteGuard : IRouteGuard<IUiPageBehavior>
|
||||
{
|
||||
/// <summary>
|
||||
/// 守卫优先级,数值越小越先执行
|
||||
/// </summary>
|
||||
int Priority { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 是否可中断后续守卫
|
||||
/// 如果返回 true,当该守卫返回 false 时,将停止执行后续守卫
|
||||
/// </summary>
|
||||
bool CanInterrupt { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 进入UI前的检查
|
||||
/// </summary>
|
||||
@ -30,5 +21,5 @@ public interface IUiRouteGuard
|
||||
/// </summary>
|
||||
/// <param name="uiKey">当前UI标识符</param>
|
||||
/// <returns>true表示允许离开,false表示拦截</returns>
|
||||
Task<bool> CanLeaveAsync(string uiKey);
|
||||
new Task<bool> CanLeaveAsync(string uiKey);
|
||||
}
|
||||
@ -113,28 +113,6 @@ public interface IUiRouter : ISystem
|
||||
/// </summary>
|
||||
bool Contains(string uiKey);
|
||||
|
||||
#region 路由守卫
|
||||
|
||||
/// <summary>
|
||||
/// 注册路由守卫
|
||||
/// </summary>
|
||||
/// <param name="guard">守卫实例</param>
|
||||
void AddGuard(IUiRouteGuard guard);
|
||||
|
||||
/// <summary>
|
||||
/// 注册路由守卫(泛型方法)
|
||||
/// </summary>
|
||||
/// <typeparam name="T">守卫类型,必须实现 IUiRouteGuard 且有无参构造函数</typeparam>
|
||||
void AddGuard<T>() where T : IUiRouteGuard, new();
|
||||
|
||||
/// <summary>
|
||||
/// 移除路由守卫
|
||||
/// </summary>
|
||||
/// <param name="guard">守卫实例</param>
|
||||
void RemoveGuard(IUiRouteGuard guard);
|
||||
|
||||
#endregion
|
||||
|
||||
#region Layer UI
|
||||
|
||||
/// <summary>
|
||||
|
||||
23
GFramework.Game.Tests/GFramework.Game.Tests.csproj
Normal file
23
GFramework.Game.Tests/GFramework.Game.Tests.csproj
Normal file
@ -0,0 +1,23 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>net8.0;net10.0</TargetFrameworks>
|
||||
<ImplicitUsings>disable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<IsPackable>false</IsPackable>
|
||||
<IsTestProject>true</IsTestProject>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="18.3.0"/>
|
||||
<PackageReference Include="Moq" Version="4.20.72"/>
|
||||
<PackageReference Include="NUnit" Version="4.5.1"/>
|
||||
<PackageReference Include="NUnit3TestAdapter" Version="6.1.0"/>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\GFramework.Game\GFramework.Game.csproj"/>
|
||||
<ProjectReference Include="..\GFramework.Core\GFramework.Core.csproj"/>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
19
GFramework.Game.Tests/GlobalUsings.cs
Normal file
19
GFramework.Game.Tests/GlobalUsings.cs
Normal file
@ -0,0 +1,19 @@
|
||||
// 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.
|
||||
|
||||
global using NUnit.Framework;
|
||||
global using Moq;
|
||||
global using System;
|
||||
global using System.Collections.Generic;
|
||||
global using System.Linq;
|
||||
global using System.Threading.Tasks;
|
||||
582
GFramework.Game.Tests/Routing/RouterBaseTests.cs
Normal file
582
GFramework.Game.Tests/Routing/RouterBaseTests.cs
Normal file
@ -0,0 +1,582 @@
|
||||
// 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.Game.Abstractions.Routing;
|
||||
using GFramework.Game.Routing;
|
||||
|
||||
namespace GFramework.Game.Tests.Routing;
|
||||
|
||||
/// <summary>
|
||||
/// RouterBase 单元测试
|
||||
/// </summary>
|
||||
[TestFixture]
|
||||
public class RouterBaseTests
|
||||
{
|
||||
/// <summary>
|
||||
/// 测试用路由项
|
||||
/// </summary>
|
||||
private class TestRoute : IRoute
|
||||
{
|
||||
public string Key { get; set; } = string.Empty;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 测试用路由上下文
|
||||
/// </summary>
|
||||
private class TestContext : IRouteContext
|
||||
{
|
||||
public string? Data { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 测试用路由守卫
|
||||
/// </summary>
|
||||
private class TestGuard : IRouteGuard<TestRoute>
|
||||
{
|
||||
public Func<string, IRouteContext?, ValueTask<bool>>? EnterFunc { get; set; }
|
||||
public Func<string, ValueTask<bool>>? LeaveFunc { get; set; }
|
||||
public int Priority { get; set; }
|
||||
public bool CanInterrupt { get; set; }
|
||||
|
||||
public ValueTask<bool> CanEnterAsync(string routeKey, IRouteContext? context)
|
||||
{
|
||||
return EnterFunc?.Invoke(routeKey, context) ?? ValueTask.FromResult(true);
|
||||
}
|
||||
|
||||
public ValueTask<bool> CanLeaveAsync(string routeKey)
|
||||
{
|
||||
return LeaveFunc?.Invoke(routeKey) ?? ValueTask.FromResult(true);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 测试用路由器实现
|
||||
/// </summary>
|
||||
private class TestRouter : RouterBase<TestRoute, TestContext>
|
||||
{
|
||||
public bool HandlersRegistered { get; private set; }
|
||||
|
||||
// 暴露 Stack 用于测试
|
||||
public new Stack<TestRoute> Stack => base.Stack;
|
||||
|
||||
protected override void OnInit()
|
||||
{
|
||||
// 测试用路由器不需要初始化逻辑
|
||||
}
|
||||
|
||||
protected override void RegisterHandlers()
|
||||
{
|
||||
HandlersRegistered = true;
|
||||
}
|
||||
|
||||
// 暴露 protected 方法用于测试
|
||||
public new Task<bool> ExecuteEnterGuardsAsync(string routeKey, TestContext? context)
|
||||
{
|
||||
return base.ExecuteEnterGuardsAsync(routeKey, context);
|
||||
}
|
||||
|
||||
public new Task<bool> ExecuteLeaveGuardsAsync(string routeKey)
|
||||
{
|
||||
return base.ExecuteLeaveGuardsAsync(routeKey);
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void AddGuard_ShouldAddGuardToList()
|
||||
{
|
||||
// Arrange
|
||||
var router = new TestRouter();
|
||||
var guard = new TestGuard { Priority = 10 };
|
||||
|
||||
// Act
|
||||
router.AddGuard(guard);
|
||||
|
||||
// Assert - 通过尝试添加相同守卫来验证
|
||||
Assert.DoesNotThrow(() => router.AddGuard(guard));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void AddGuard_ShouldSortByPriority()
|
||||
{
|
||||
// Arrange
|
||||
var router = new TestRouter();
|
||||
var guard1 = new TestGuard { Priority = 20 };
|
||||
var guard2 = new TestGuard { Priority = 10 };
|
||||
var guard3 = new TestGuard { Priority = 30 };
|
||||
|
||||
// Act
|
||||
router.AddGuard(guard1);
|
||||
router.AddGuard(guard2);
|
||||
router.AddGuard(guard3);
|
||||
|
||||
// Assert - 通过执行守卫来验证顺序
|
||||
var executionOrder = new List<int>();
|
||||
guard1.EnterFunc = (_, _) =>
|
||||
{
|
||||
executionOrder.Add(1);
|
||||
return ValueTask.FromResult(true);
|
||||
};
|
||||
guard2.EnterFunc = (_, _) =>
|
||||
{
|
||||
executionOrder.Add(2);
|
||||
return ValueTask.FromResult(true);
|
||||
};
|
||||
guard3.EnterFunc = (_, _) =>
|
||||
{
|
||||
executionOrder.Add(3);
|
||||
return ValueTask.FromResult(true);
|
||||
};
|
||||
|
||||
router.ExecuteEnterGuardsAsync("test", null).Wait();
|
||||
|
||||
Assert.That(executionOrder, Is.EqualTo(new[] { 2, 1, 3 }));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void AddGuard_WithGeneric_ShouldCreateAndAddGuard()
|
||||
{
|
||||
// Arrange
|
||||
var router = new TestRouter();
|
||||
|
||||
// Act & Assert
|
||||
Assert.DoesNotThrow(() => router.AddGuard<TestGuard>());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void AddGuard_WithNull_ShouldThrowArgumentNullException()
|
||||
{
|
||||
// Arrange
|
||||
var router = new TestRouter();
|
||||
|
||||
// Act & Assert
|
||||
Assert.Throws<ArgumentNullException>(() => router.AddGuard(null!));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void RemoveGuard_ShouldRemoveGuardFromList()
|
||||
{
|
||||
// Arrange
|
||||
var router = new TestRouter();
|
||||
var guard = new TestGuard { Priority = 10 };
|
||||
router.AddGuard(guard);
|
||||
|
||||
// Act
|
||||
router.RemoveGuard(guard);
|
||||
|
||||
// Assert - 守卫应该被移除,不会再执行
|
||||
var executed = false;
|
||||
guard.EnterFunc = (_, _) =>
|
||||
{
|
||||
executed = true;
|
||||
return ValueTask.FromResult(true);
|
||||
};
|
||||
|
||||
router.ExecuteEnterGuardsAsync("test", null).Wait();
|
||||
|
||||
Assert.That(executed, Is.False);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void RemoveGuard_WithNull_ShouldThrowArgumentNullException()
|
||||
{
|
||||
// Arrange
|
||||
var router = new TestRouter();
|
||||
|
||||
// Act & Assert
|
||||
Assert.Throws<ArgumentNullException>(() => router.RemoveGuard(null!));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task ExecuteEnterGuardsAsync_WithNoGuards_ShouldReturnTrue()
|
||||
{
|
||||
// Arrange
|
||||
var router = new TestRouter();
|
||||
|
||||
// Act
|
||||
var result = await router.ExecuteEnterGuardsAsync("test", null);
|
||||
|
||||
// Assert
|
||||
Assert.That(result, Is.True);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task ExecuteEnterGuardsAsync_WithAllowingGuard_ShouldReturnTrue()
|
||||
{
|
||||
// Arrange
|
||||
var router = new TestRouter();
|
||||
var guard = new TestGuard
|
||||
{
|
||||
Priority = 10,
|
||||
EnterFunc = (_, _) => ValueTask.FromResult(true)
|
||||
};
|
||||
router.AddGuard(guard);
|
||||
|
||||
// Act
|
||||
var result = await router.ExecuteEnterGuardsAsync("test", null);
|
||||
|
||||
// Assert
|
||||
Assert.That(result, Is.True);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task ExecuteEnterGuardsAsync_WithBlockingGuard_ShouldReturnFalse()
|
||||
{
|
||||
// Arrange
|
||||
var router = new TestRouter();
|
||||
var guard = new TestGuard
|
||||
{
|
||||
Priority = 10,
|
||||
EnterFunc = (_, _) => ValueTask.FromResult(false)
|
||||
};
|
||||
router.AddGuard(guard);
|
||||
|
||||
// Act
|
||||
var result = await router.ExecuteEnterGuardsAsync("test", null);
|
||||
|
||||
// Assert
|
||||
Assert.That(result, Is.False);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task ExecuteEnterGuardsAsync_WithInterruptingGuard_ShouldStopExecution()
|
||||
{
|
||||
// Arrange
|
||||
var router = new TestRouter();
|
||||
var guard1 = new TestGuard
|
||||
{
|
||||
Priority = 10,
|
||||
CanInterrupt = true,
|
||||
EnterFunc = (_, _) => ValueTask.FromResult(true)
|
||||
};
|
||||
var guard2Executed = false;
|
||||
var guard2 = new TestGuard
|
||||
{
|
||||
Priority = 20,
|
||||
EnterFunc = (_, _) =>
|
||||
{
|
||||
guard2Executed = true;
|
||||
return ValueTask.FromResult(true);
|
||||
}
|
||||
};
|
||||
router.AddGuard(guard1);
|
||||
router.AddGuard(guard2);
|
||||
|
||||
// Act
|
||||
var result = await router.ExecuteEnterGuardsAsync("test", null);
|
||||
|
||||
// Assert
|
||||
Assert.That(result, Is.True);
|
||||
Assert.That(guard2Executed, Is.False);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task ExecuteEnterGuardsAsync_WithThrowingGuard_ShouldContinueIfNotInterrupting()
|
||||
{
|
||||
// Arrange
|
||||
var router = new TestRouter();
|
||||
var guard1 = new TestGuard
|
||||
{
|
||||
Priority = 10,
|
||||
CanInterrupt = false,
|
||||
EnterFunc = (_, _) => throw new InvalidOperationException("Test exception")
|
||||
};
|
||||
var guard2Executed = false;
|
||||
var guard2 = new TestGuard
|
||||
{
|
||||
Priority = 20,
|
||||
EnterFunc = (_, _) =>
|
||||
{
|
||||
guard2Executed = true;
|
||||
return ValueTask.FromResult(true);
|
||||
}
|
||||
};
|
||||
router.AddGuard(guard1);
|
||||
router.AddGuard(guard2);
|
||||
|
||||
// Act
|
||||
var result = await router.ExecuteEnterGuardsAsync("test", null);
|
||||
|
||||
// Assert
|
||||
Assert.That(result, Is.True);
|
||||
Assert.That(guard2Executed, Is.True);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task ExecuteEnterGuardsAsync_WithThrowingInterruptingGuard_ShouldReturnFalse()
|
||||
{
|
||||
// Arrange
|
||||
var router = new TestRouter();
|
||||
var guard = new TestGuard
|
||||
{
|
||||
Priority = 10,
|
||||
CanInterrupt = true,
|
||||
EnterFunc = (_, _) => throw new InvalidOperationException("Test exception")
|
||||
};
|
||||
router.AddGuard(guard);
|
||||
|
||||
// Act
|
||||
var result = await router.ExecuteEnterGuardsAsync("test", null);
|
||||
|
||||
// Assert
|
||||
Assert.That(result, Is.False);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task ExecuteLeaveGuardsAsync_WithNoGuards_ShouldReturnTrue()
|
||||
{
|
||||
// Arrange
|
||||
var router = new TestRouter();
|
||||
|
||||
// Act
|
||||
var result = await router.ExecuteLeaveGuardsAsync("test");
|
||||
|
||||
// Assert
|
||||
Assert.That(result, Is.True);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task ExecuteLeaveGuardsAsync_WithAllowingGuard_ShouldReturnTrue()
|
||||
{
|
||||
// Arrange
|
||||
var router = new TestRouter();
|
||||
var guard = new TestGuard
|
||||
{
|
||||
Priority = 10,
|
||||
LeaveFunc = _ => ValueTask.FromResult(true)
|
||||
};
|
||||
router.AddGuard(guard);
|
||||
|
||||
// Act
|
||||
var result = await router.ExecuteLeaveGuardsAsync("test");
|
||||
|
||||
// Assert
|
||||
Assert.That(result, Is.True);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task ExecuteLeaveGuardsAsync_WithBlockingGuard_ShouldReturnFalse()
|
||||
{
|
||||
// Arrange
|
||||
var router = new TestRouter();
|
||||
var guard = new TestGuard
|
||||
{
|
||||
Priority = 10,
|
||||
LeaveFunc = _ => ValueTask.FromResult(false)
|
||||
};
|
||||
router.AddGuard(guard);
|
||||
|
||||
// Act
|
||||
var result = await router.ExecuteLeaveGuardsAsync("test");
|
||||
|
||||
// Assert
|
||||
Assert.That(result, Is.False);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Contains_WithEmptyStack_ShouldReturnFalse()
|
||||
{
|
||||
// Arrange
|
||||
var router = new TestRouter();
|
||||
|
||||
// Act
|
||||
var result = router.Contains("test");
|
||||
|
||||
// Assert
|
||||
Assert.That(result, Is.False);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Contains_WithMatchingRoute_ShouldReturnTrue()
|
||||
{
|
||||
// Arrange
|
||||
var router = new TestRouter();
|
||||
var route = new TestRoute { Key = "test" };
|
||||
router.Stack.Push(route);
|
||||
|
||||
// Act
|
||||
var result = router.Contains("test");
|
||||
|
||||
// Assert
|
||||
Assert.That(result, Is.True);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Contains_WithNonMatchingRoute_ShouldReturnFalse()
|
||||
{
|
||||
// Arrange
|
||||
var router = new TestRouter();
|
||||
var route = new TestRoute { Key = "test1" };
|
||||
router.Stack.Push(route);
|
||||
|
||||
// Act
|
||||
var result = router.Contains("test2");
|
||||
|
||||
// Assert
|
||||
Assert.That(result, Is.False);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void PeekKey_WithEmptyStack_ShouldReturnEmptyString()
|
||||
{
|
||||
// Arrange
|
||||
var router = new TestRouter();
|
||||
|
||||
// Act
|
||||
var result = router.PeekKey();
|
||||
|
||||
// Assert
|
||||
Assert.That(result, Is.EqualTo(string.Empty));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void PeekKey_WithRoute_ShouldReturnRouteKey()
|
||||
{
|
||||
// Arrange
|
||||
var router = new TestRouter();
|
||||
var route = new TestRoute { Key = "test" };
|
||||
router.Stack.Push(route);
|
||||
|
||||
// Act
|
||||
var result = router.PeekKey();
|
||||
|
||||
// Assert
|
||||
Assert.That(result, Is.EqualTo("test"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void IsTop_WithEmptyStack_ShouldReturnFalse()
|
||||
{
|
||||
// Arrange
|
||||
var router = new TestRouter();
|
||||
|
||||
// Act
|
||||
var result = router.IsTop("test");
|
||||
|
||||
// Assert
|
||||
Assert.That(result, Is.False);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void IsTop_WithMatchingRoute_ShouldReturnTrue()
|
||||
{
|
||||
// Arrange
|
||||
var router = new TestRouter();
|
||||
var route = new TestRoute { Key = "test" };
|
||||
router.Stack.Push(route);
|
||||
|
||||
// Act
|
||||
var result = router.IsTop("test");
|
||||
|
||||
// Assert
|
||||
Assert.That(result, Is.True);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void IsTop_WithNonMatchingRoute_ShouldReturnFalse()
|
||||
{
|
||||
// Arrange
|
||||
var router = new TestRouter();
|
||||
var route = new TestRoute { Key = "test1" };
|
||||
router.Stack.Push(route);
|
||||
|
||||
// Act
|
||||
var result = router.IsTop("test2");
|
||||
|
||||
// Assert
|
||||
Assert.That(result, Is.False);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Current_WithEmptyStack_ShouldReturnNull()
|
||||
{
|
||||
// Arrange
|
||||
var router = new TestRouter();
|
||||
|
||||
// Act
|
||||
var result = router.Current;
|
||||
|
||||
// Assert
|
||||
Assert.That(result, Is.Null);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Current_WithRoute_ShouldReturnTopRoute()
|
||||
{
|
||||
// Arrange
|
||||
var router = new TestRouter();
|
||||
var route = new TestRoute { Key = "test" };
|
||||
router.Stack.Push(route);
|
||||
|
||||
// Act
|
||||
var result = router.Current;
|
||||
|
||||
// Assert
|
||||
Assert.That(result, Is.EqualTo(route));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void CurrentKey_WithEmptyStack_ShouldReturnNull()
|
||||
{
|
||||
// Arrange
|
||||
var router = new TestRouter();
|
||||
|
||||
// Act
|
||||
var result = router.CurrentKey;
|
||||
|
||||
// Assert
|
||||
Assert.That(result, Is.Null);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void CurrentKey_WithRoute_ShouldReturnRouteKey()
|
||||
{
|
||||
// Arrange
|
||||
var router = new TestRouter();
|
||||
var route = new TestRoute { Key = "test" };
|
||||
router.Stack.Push(route);
|
||||
|
||||
// Act
|
||||
var result = router.CurrentKey;
|
||||
|
||||
// Assert
|
||||
Assert.That(result, Is.EqualTo("test"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Count_WithEmptyStack_ShouldReturnZero()
|
||||
{
|
||||
// Arrange
|
||||
var router = new TestRouter();
|
||||
|
||||
// Act
|
||||
var result = router.Count;
|
||||
|
||||
// Assert
|
||||
Assert.That(result, Is.EqualTo(0));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Count_WithRoutes_ShouldReturnCorrectCount()
|
||||
{
|
||||
// Arrange
|
||||
var router = new TestRouter();
|
||||
router.Stack.Push(new TestRoute { Key = "test1" });
|
||||
router.Stack.Push(new TestRoute { Key = "test2" });
|
||||
|
||||
// Act
|
||||
var result = router.Count;
|
||||
|
||||
// Assert
|
||||
Assert.That(result, Is.EqualTo(2));
|
||||
}
|
||||
}
|
||||
244
GFramework.Game/Routing/RouterBase.cs
Normal file
244
GFramework.Game/Routing/RouterBase.cs
Normal file
@ -0,0 +1,244 @@
|
||||
// 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.Logging;
|
||||
using GFramework.Core.Systems;
|
||||
using GFramework.Game.Abstractions.Routing;
|
||||
|
||||
namespace GFramework.Game.Routing;
|
||||
|
||||
/// <summary>
|
||||
/// 路由器基类,提供通用的路由管理功能
|
||||
/// </summary>
|
||||
/// <typeparam name="TRoute">路由项类型,必须实现 IRoute 接口</typeparam>
|
||||
/// <typeparam name="TContext">路由上下文类型,必须实现 IRouteContext 接口</typeparam>
|
||||
/// <remarks>
|
||||
/// 此基类提供了以下通用功能:
|
||||
/// - 路由守卫管理 (AddGuard/RemoveGuard)
|
||||
/// - 守卫执行逻辑 (ExecuteEnterGuardsAsync/ExecuteLeaveGuardsAsync)
|
||||
/// - 路由栈管理 (Stack/Current/CurrentKey)
|
||||
/// - 栈操作方法 (Contains/PeekKey/IsTop)
|
||||
/// </remarks>
|
||||
public abstract class RouterBase<TRoute, TContext> : AbstractSystem
|
||||
where TRoute : IRoute
|
||||
where TContext : IRouteContext
|
||||
{
|
||||
private static readonly ILogger Log =
|
||||
LoggerFactoryResolver.Provider.CreateLogger(nameof(RouterBase<TRoute, TContext>));
|
||||
|
||||
/// <summary>
|
||||
/// 路由守卫列表,按优先级排序
|
||||
/// </summary>
|
||||
private readonly List<IRouteGuard<TRoute>> _guards = new();
|
||||
|
||||
/// <summary>
|
||||
/// 路由栈,用于管理路由的显示顺序和导航历史
|
||||
/// </summary>
|
||||
protected readonly Stack<TRoute> Stack = new();
|
||||
|
||||
/// <summary>
|
||||
/// 获取当前路由 (栈顶元素)
|
||||
/// </summary>
|
||||
public TRoute? Current => Stack.Count > 0 ? Stack.Peek() : default;
|
||||
|
||||
/// <summary>
|
||||
/// 获取当前路由的键值
|
||||
/// </summary>
|
||||
public string? CurrentKey => Current?.Key;
|
||||
|
||||
/// <summary>
|
||||
/// 获取栈深度
|
||||
/// </summary>
|
||||
public int Count => Stack.Count;
|
||||
|
||||
#region Abstract Methods
|
||||
|
||||
/// <summary>
|
||||
/// 注册过渡处理器 (由子类实现)
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// 子类应该在此方法中注册所有需要的过渡处理器。
|
||||
/// 此方法在 OnInit 中被调用。
|
||||
/// </remarks>
|
||||
protected abstract void RegisterHandlers();
|
||||
|
||||
#endregion
|
||||
|
||||
#region Guard Management
|
||||
|
||||
/// <summary>
|
||||
/// 添加路由守卫
|
||||
/// </summary>
|
||||
/// <param name="guard">路由守卫实例</param>
|
||||
/// <exception cref="ArgumentNullException">当守卫实例为 null 时抛出</exception>
|
||||
public void AddGuard(IRouteGuard<TRoute> guard)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(guard);
|
||||
|
||||
if (_guards.Contains(guard))
|
||||
{
|
||||
Log.Debug("Guard already registered: {0}", guard.GetType().Name);
|
||||
return;
|
||||
}
|
||||
|
||||
_guards.Add(guard);
|
||||
_guards.Sort((a, b) => a.Priority.CompareTo(b.Priority));
|
||||
Log.Debug("Guard registered: {0}, Priority={1}", guard.GetType().Name, guard.Priority);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 添加路由守卫 (泛型版本)
|
||||
/// </summary>
|
||||
/// <typeparam name="T">守卫类型,必须实现 IRouteGuard 接口且有无参构造函数</typeparam>
|
||||
public void AddGuard<T>() where T : IRouteGuard<TRoute>, new()
|
||||
{
|
||||
AddGuard(new T());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 移除路由守卫
|
||||
/// </summary>
|
||||
/// <param name="guard">要移除的路由守卫实例</param>
|
||||
/// <exception cref="ArgumentNullException">当守卫实例为 null 时抛出</exception>
|
||||
public void RemoveGuard(IRouteGuard<TRoute> guard)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(guard);
|
||||
if (_guards.Remove(guard))
|
||||
Log.Debug("Guard removed: {0}", guard.GetType().Name);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Guard Execution
|
||||
|
||||
/// <summary>
|
||||
/// 执行进入守卫检查
|
||||
/// </summary>
|
||||
/// <param name="routeKey">路由键值</param>
|
||||
/// <param name="context">路由上下文</param>
|
||||
/// <returns>如果所有守卫都允许进入返回 true,否则返回 false</returns>
|
||||
/// <remarks>
|
||||
/// 守卫按优先级从小到大依次执行。
|
||||
/// 如果某个守卫返回 false 且 CanInterrupt 为 true,则中断后续守卫的执行。
|
||||
/// 如果某个守卫抛出异常且 CanInterrupt 为 true,则中断后续守卫的执行。
|
||||
/// </remarks>
|
||||
protected async Task<bool> ExecuteEnterGuardsAsync(string routeKey, TContext? context)
|
||||
{
|
||||
foreach (var guard in _guards)
|
||||
{
|
||||
try
|
||||
{
|
||||
Log.Debug("Executing enter guard: {0} for {1}", guard.GetType().Name, routeKey);
|
||||
var canEnter = await guard.CanEnterAsync(routeKey, context);
|
||||
|
||||
if (!canEnter)
|
||||
{
|
||||
Log.Debug("Enter guard blocked: {0}", guard.GetType().Name);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (guard.CanInterrupt)
|
||||
{
|
||||
Log.Debug("Enter guard {0} passed, can interrupt = true", guard.GetType().Name);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error("Enter guard {0} failed: {1}", guard.GetType().Name, ex.Message);
|
||||
if (guard.CanInterrupt)
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 执行离开守卫检查
|
||||
/// </summary>
|
||||
/// <param name="routeKey">路由键值</param>
|
||||
/// <returns>如果所有守卫都允许离开返回 true,否则返回 false</returns>
|
||||
/// <remarks>
|
||||
/// 守卫按优先级从小到大依次执行。
|
||||
/// 如果某个守卫返回 false 且 CanInterrupt 为 true,则中断后续守卫的执行。
|
||||
/// 如果某个守卫抛出异常且 CanInterrupt 为 true,则中断后续守卫的执行。
|
||||
/// </remarks>
|
||||
protected async Task<bool> ExecuteLeaveGuardsAsync(string routeKey)
|
||||
{
|
||||
foreach (var guard in _guards)
|
||||
{
|
||||
try
|
||||
{
|
||||
Log.Debug("Executing leave guard: {0} for {1}", guard.GetType().Name, routeKey);
|
||||
var canLeave = await guard.CanLeaveAsync(routeKey);
|
||||
|
||||
if (!canLeave)
|
||||
{
|
||||
Log.Debug("Leave guard blocked: {0}", guard.GetType().Name);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (guard.CanInterrupt)
|
||||
{
|
||||
Log.Debug("Leave guard {0} passed, can interrupt = true", guard.GetType().Name);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error("Leave guard {0} failed: {1}", guard.GetType().Name, ex.Message);
|
||||
if (guard.CanInterrupt)
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Stack Operations
|
||||
|
||||
/// <summary>
|
||||
/// 检查栈中是否包含指定路由
|
||||
/// </summary>
|
||||
/// <param name="routeKey">路由键值</param>
|
||||
/// <returns>如果栈中包含指定路由返回 true,否则返回 false</returns>
|
||||
public bool Contains(string routeKey)
|
||||
{
|
||||
return Stack.Any(r => r.Key == routeKey);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取栈顶路由的键值
|
||||
/// </summary>
|
||||
/// <returns>栈顶路由的键值,如果栈为空则返回空字符串</returns>
|
||||
public string PeekKey()
|
||||
{
|
||||
return Stack.Count == 0 ? string.Empty : Stack.Peek().Key;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 判断栈顶是否为指定路由
|
||||
/// </summary>
|
||||
/// <param name="routeKey">路由键值</param>
|
||||
/// <returns>如果栈顶是指定路由返回 true,否则返回 false</returns>
|
||||
public bool IsTop(string routeKey)
|
||||
{
|
||||
return Stack.Count != 0 && Stack.Peek().Key.Equals(routeKey);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
@ -14,9 +14,9 @@
|
||||
using GFramework.Core.Abstractions.Logging;
|
||||
using GFramework.Core.Extensions;
|
||||
using GFramework.Core.Logging;
|
||||
using GFramework.Core.Systems;
|
||||
using GFramework.Game.Abstractions.Enums;
|
||||
using GFramework.Game.Abstractions.Scene;
|
||||
using GFramework.Game.Routing;
|
||||
|
||||
namespace GFramework.Game.Scene;
|
||||
|
||||
@ -25,15 +25,13 @@ namespace GFramework.Game.Scene;
|
||||
/// 实现了 <see cref="ISceneRouter"/> 接口,用于管理场景的加载、替换和卸载操作。
|
||||
/// </summary>
|
||||
public abstract class SceneRouterBase
|
||||
: AbstractSystem, ISceneRouter
|
||||
: RouterBase<ISceneBehavior, ISceneEnterParam>, ISceneRouter
|
||||
{
|
||||
private static readonly ILogger Log =
|
||||
LoggerFactoryResolver.Provider.CreateLogger(nameof(SceneRouterBase));
|
||||
|
||||
private readonly List<ISceneRouteGuard> _guards = new();
|
||||
private readonly SceneTransitionPipeline _pipeline = new();
|
||||
|
||||
private readonly Stack<ISceneBehavior> _stack = new();
|
||||
private readonly SemaphoreSlim _transitionLock = new(1, 1);
|
||||
private ISceneFactory _factory = null!;
|
||||
|
||||
@ -45,17 +43,17 @@ public abstract class SceneRouterBase
|
||||
/// <summary>
|
||||
/// 获取当前场景行为对象。
|
||||
/// </summary>
|
||||
public ISceneBehavior? Current => _stack.Count > 0 ? _stack.Peek() : null;
|
||||
public new ISceneBehavior? Current => Stack.Count > 0 ? Stack.Peek() : null;
|
||||
|
||||
/// <summary>
|
||||
/// 获取当前场景的键名。
|
||||
/// </summary>
|
||||
public string? CurrentKey => Current?.Key;
|
||||
public new string? CurrentKey => Current?.Key;
|
||||
|
||||
/// <summary>
|
||||
/// 获取场景栈的只读视图,按压入顺序排列(从栈底到栈顶)。
|
||||
/// </summary>
|
||||
public IEnumerable<ISceneBehavior> Stack => _stack.Reverse();
|
||||
IEnumerable<ISceneBehavior> ISceneRouter.Stack => base.Stack.Reverse();
|
||||
|
||||
/// <summary>
|
||||
/// 获取是否正在进行场景转换。
|
||||
@ -115,9 +113,9 @@ public abstract class SceneRouterBase
|
||||
/// </summary>
|
||||
/// <param name="sceneKey">场景键名。</param>
|
||||
/// <returns>如果场景在栈中返回true,否则返回false。</returns>
|
||||
public bool Contains(string sceneKey)
|
||||
public new bool Contains(string sceneKey)
|
||||
{
|
||||
return _stack.Any(s => s.Key == sceneKey);
|
||||
return Stack.Any(s => s.Key == sceneKey);
|
||||
}
|
||||
|
||||
#endregion
|
||||
@ -163,46 +161,10 @@ public abstract class SceneRouterBase
|
||||
_pipeline.UnregisterAroundHandler(handler);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 添加场景路由守卫。
|
||||
/// </summary>
|
||||
/// <param name="guard">守卫实例。</param>
|
||||
public void AddGuard(ISceneRouteGuard guard)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(guard);
|
||||
if (!_guards.Contains(guard))
|
||||
{
|
||||
_guards.Add(guard);
|
||||
_guards.Sort((a, b) => a.Priority.CompareTo(b.Priority));
|
||||
Log.Debug("Guard added: {0}, Priority={1}", guard.GetType().Name, guard.Priority);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 添加场景路由守卫(泛型版本)。
|
||||
/// </summary>
|
||||
/// <typeparam name="T">守卫类型。</typeparam>
|
||||
public void AddGuard<T>() where T : ISceneRouteGuard, new()
|
||||
{
|
||||
AddGuard(new T());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 移除场景路由守卫。
|
||||
/// </summary>
|
||||
/// <param name="guard">守卫实例。</param>
|
||||
public void RemoveGuard(ISceneRouteGuard guard)
|
||||
{
|
||||
if (_guards.Remove(guard))
|
||||
{
|
||||
Log.Debug("Guard removed: {0}", guard.GetType().Name);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 注册场景过渡处理器的抽象方法,由子类实现。
|
||||
/// </summary>
|
||||
protected abstract void RegisterHandlers();
|
||||
protected override abstract void RegisterHandlers();
|
||||
|
||||
/// <summary>
|
||||
/// 系统初始化方法,获取场景工厂并注册处理器。
|
||||
@ -281,20 +243,20 @@ public abstract class SceneRouterBase
|
||||
await scene.OnLoadAsync(param);
|
||||
|
||||
// 暂停当前场景
|
||||
if (_stack.Count > 0)
|
||||
if (Stack.Count > 0)
|
||||
{
|
||||
var current = _stack.Peek();
|
||||
var current = Stack.Peek();
|
||||
await current.OnPauseAsync();
|
||||
}
|
||||
|
||||
// 压入栈
|
||||
_stack.Push(scene);
|
||||
Stack.Push(scene);
|
||||
|
||||
// 进入场景
|
||||
await scene.OnEnterAsync();
|
||||
|
||||
Log.Debug("Push Scene: {0}, stackCount={1}",
|
||||
sceneKey, _stack.Count);
|
||||
sceneKey, Stack.Count);
|
||||
}
|
||||
|
||||
#endregion
|
||||
@ -335,10 +297,10 @@ public abstract class SceneRouterBase
|
||||
/// <returns>异步任务。</returns>
|
||||
private async ValueTask PopInternalAsync()
|
||||
{
|
||||
if (_stack.Count == 0)
|
||||
if (Stack.Count == 0)
|
||||
return;
|
||||
|
||||
var top = _stack.Peek();
|
||||
var top = Stack.Peek();
|
||||
|
||||
// 守卫检查
|
||||
if (!await ExecuteLeaveGuardsAsync(top.Key))
|
||||
@ -347,7 +309,7 @@ public abstract class SceneRouterBase
|
||||
return;
|
||||
}
|
||||
|
||||
_stack.Pop();
|
||||
Stack.Pop();
|
||||
|
||||
// 退出场景
|
||||
await top.OnExitAsync();
|
||||
@ -359,13 +321,13 @@ public abstract class SceneRouterBase
|
||||
Root!.RemoveScene(top);
|
||||
|
||||
// 恢复下一个场景
|
||||
if (_stack.Count > 0)
|
||||
if (Stack.Count > 0)
|
||||
{
|
||||
var next = _stack.Peek();
|
||||
var next = Stack.Peek();
|
||||
await next.OnResumeAsync();
|
||||
}
|
||||
|
||||
Log.Debug("Pop Scene, stackCount={0}", _stack.Count);
|
||||
Log.Debug("Pop Scene, stackCount={0}", Stack.Count);
|
||||
}
|
||||
|
||||
#endregion
|
||||
@ -406,7 +368,7 @@ public abstract class SceneRouterBase
|
||||
/// <returns>异步任务。</returns>
|
||||
private async ValueTask ClearInternalAsync()
|
||||
{
|
||||
while (_stack.Count > 0)
|
||||
while (Stack.Count > 0)
|
||||
{
|
||||
await PopInternalAsync();
|
||||
}
|
||||
@ -460,82 +422,5 @@ public abstract class SceneRouterBase
|
||||
Log.Debug("AfterChange phases completed: {0}", @event.TransitionType);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 执行进入场景的守卫检查。
|
||||
/// 按优先级顺序执行所有守卫的CanEnterAsync方法。
|
||||
/// </summary>
|
||||
/// <param name="sceneKey">场景键名。</param>
|
||||
/// <param name="param">进入参数。</param>
|
||||
/// <returns>如果所有守卫都允许进入返回true,否则返回false。</returns>
|
||||
private async Task<bool> ExecuteEnterGuardsAsync(string sceneKey, ISceneEnterParam? param)
|
||||
{
|
||||
foreach (var guard in _guards)
|
||||
{
|
||||
try
|
||||
{
|
||||
Log.Debug("Executing enter guard: {0} for {1}", guard.GetType().Name, sceneKey);
|
||||
var canEnter = await guard.CanEnterAsync(sceneKey, param);
|
||||
|
||||
if (!canEnter)
|
||||
{
|
||||
Log.Debug("Enter guard blocked: {0}", guard.GetType().Name);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (guard.CanInterrupt)
|
||||
{
|
||||
Log.Debug("Enter guard {0} passed, can interrupt = true", guard.GetType().Name);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error("Enter guard {0} failed: {1}", guard.GetType().Name, ex.Message);
|
||||
if (guard.CanInterrupt)
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 执行离开场景的守卫检查。
|
||||
/// 按优先级顺序执行所有守卫的CanLeaveAsync方法。
|
||||
/// </summary>
|
||||
/// <param name="sceneKey">场景键名。</param>
|
||||
/// <returns>如果所有守卫都允许离开返回true,否则返回false。</returns>
|
||||
private async Task<bool> ExecuteLeaveGuardsAsync(string sceneKey)
|
||||
{
|
||||
foreach (var guard in _guards)
|
||||
{
|
||||
try
|
||||
{
|
||||
Log.Debug("Executing leave guard: {0} for {1}", guard.GetType().Name, sceneKey);
|
||||
var canLeave = await guard.CanLeaveAsync(sceneKey);
|
||||
|
||||
if (!canLeave)
|
||||
{
|
||||
Log.Debug("Leave guard blocked: {0}", guard.GetType().Name);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (guard.CanInterrupt)
|
||||
{
|
||||
Log.Debug("Leave guard {0} passed, can interrupt = true", guard.GetType().Name);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error("Leave guard {0} failed: {1}", guard.GetType().Name, ex.Message);
|
||||
if (guard.CanInterrupt)
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
@ -1,9 +1,9 @@
|
||||
using GFramework.Core.Abstractions.Logging;
|
||||
using GFramework.Core.Extensions;
|
||||
using GFramework.Core.Logging;
|
||||
using GFramework.Core.Systems;
|
||||
using GFramework.Game.Abstractions.Enums;
|
||||
using GFramework.Game.Abstractions.UI;
|
||||
using GFramework.Game.Routing;
|
||||
|
||||
namespace GFramework.Game.UI;
|
||||
|
||||
@ -11,15 +11,10 @@ namespace GFramework.Game.UI;
|
||||
/// UI路由基类,提供页面栈管理和层级UI管理功能
|
||||
/// 负责UI页面的导航、显示、隐藏以及生命周期管理
|
||||
/// </summary>
|
||||
public abstract class UiRouterBase : AbstractSystem, IUiRouter
|
||||
public abstract class UiRouterBase : RouterBase<IUiPageBehavior, IUiPageEnterParam>, IUiRouter
|
||||
{
|
||||
private static readonly ILogger Log = LoggerFactoryResolver.Provider.CreateLogger(nameof(UiRouterBase));
|
||||
|
||||
/// <summary>
|
||||
/// 路由守卫列表,用于控制UI页面的进入和离开
|
||||
/// </summary>
|
||||
private readonly List<IUiRouteGuard> _guards = new();
|
||||
|
||||
/// <summary>
|
||||
/// 层级管理字典(非栈层级),用于管理Overlay、Modal、Toast等浮层UI
|
||||
/// Key: UiLayer枚举值, Value: InstanceId到PageBehavior的映射字典
|
||||
@ -31,11 +26,6 @@ public abstract class UiRouterBase : AbstractSystem, IUiRouter
|
||||
/// </summary>
|
||||
private readonly UiTransitionPipeline _pipeline = new();
|
||||
|
||||
/// <summary>
|
||||
/// 页面栈,用于管理UI页面的显示顺序和导航历史
|
||||
/// </summary>
|
||||
private readonly Stack<IUiPageBehavior> _stack = new();
|
||||
|
||||
/// <summary>
|
||||
/// UI工厂实例,用于创建UI页面和相关对象
|
||||
/// </summary>
|
||||
@ -98,7 +88,7 @@ public abstract class UiRouterBase : AbstractSystem, IUiRouter
|
||||
}
|
||||
|
||||
var @event = CreateEvent(uiKey, UiTransitionType.Push, policy, param);
|
||||
Log.Debug("Push UI Page: key={0}, policy={1}, stackBefore={2}", uiKey, policy, _stack.Count);
|
||||
Log.Debug("Push UI Page: key={0}, policy={1}, stackBefore={2}", uiKey, policy, Stack.Count);
|
||||
|
||||
await _pipeline.ExecuteAroundAsync(@event, async () =>
|
||||
{
|
||||
@ -126,7 +116,7 @@ public abstract class UiRouterBase : AbstractSystem, IUiRouter
|
||||
}
|
||||
|
||||
var @event = CreateEvent(uiKey, UiTransitionType.Push, policy, param);
|
||||
Log.Debug("Push existing UI Page: key={0}, policy={1}, stackBefore={2}", uiKey, policy, _stack.Count);
|
||||
Log.Debug("Push existing UI Page: key={0}, policy={1}, stackBefore={2}", uiKey, policy, Stack.Count);
|
||||
|
||||
await _pipeline.ExecuteAroundAsync(@event, async () =>
|
||||
{
|
||||
@ -142,13 +132,13 @@ public abstract class UiRouterBase : AbstractSystem, IUiRouter
|
||||
/// <param name="policy">页面弹出策略</param>
|
||||
public async ValueTask PopAsync(UiPopPolicy policy = UiPopPolicy.Destroy)
|
||||
{
|
||||
if (_stack.Count == 0)
|
||||
if (Stack.Count == 0)
|
||||
{
|
||||
Log.Debug("Pop ignored: stack is empty");
|
||||
return;
|
||||
}
|
||||
|
||||
var leavingUiKey = _stack.Peek().Key;
|
||||
var leavingUiKey = Stack.Peek().Key;
|
||||
|
||||
if (!await ExecuteLeaveGuardsAsync(leavingUiKey))
|
||||
{
|
||||
@ -156,7 +146,7 @@ public abstract class UiRouterBase : AbstractSystem, IUiRouter
|
||||
return;
|
||||
}
|
||||
|
||||
var nextUiKey = _stack.Count > 1 ? _stack.ElementAt(1).Key : null;
|
||||
var nextUiKey = Stack.Count > 1 ? Stack.ElementAt(1).Key : null;
|
||||
var @event = CreateEvent(nextUiKey, UiTransitionType.Pop);
|
||||
|
||||
await _pipeline.ExecuteAroundAsync(@event, async () =>
|
||||
@ -226,7 +216,7 @@ public abstract class UiRouterBase : AbstractSystem, IUiRouter
|
||||
public async ValueTask ClearAsync()
|
||||
{
|
||||
var @event = CreateEvent(string.Empty, UiTransitionType.Clear);
|
||||
Log.Debug("Clear UI Stack, stackCount={0}", _stack.Count);
|
||||
Log.Debug("Clear UI Stack, stackCount={0}", Stack.Count);
|
||||
|
||||
await _pipeline.ExecuteAroundAsync(@event, async () =>
|
||||
{
|
||||
@ -240,9 +230,9 @@ public abstract class UiRouterBase : AbstractSystem, IUiRouter
|
||||
/// 获取栈顶元素的键值
|
||||
/// </summary>
|
||||
/// <returns>栈顶UI页面的键值,如果栈为空则返回空字符串</returns>
|
||||
public string PeekKey()
|
||||
public new string PeekKey()
|
||||
{
|
||||
return _stack.Count == 0 ? string.Empty : _stack.Peek().Key;
|
||||
return Stack.Count == 0 ? string.Empty : Stack.Peek().Key;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -251,7 +241,7 @@ public abstract class UiRouterBase : AbstractSystem, IUiRouter
|
||||
/// <returns>栈顶UI页面行为实例,如果栈为空则返回null</returns>
|
||||
public IUiPageBehavior? Peek()
|
||||
{
|
||||
return _stack.Count == 0 ? null : _stack.Peek();
|
||||
return Stack.Count == 0 ? null : Stack.Peek();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -259,9 +249,9 @@ public abstract class UiRouterBase : AbstractSystem, IUiRouter
|
||||
/// </summary>
|
||||
/// <param name="uiKey">要检查的UI页面键值</param>
|
||||
/// <returns>如果栈顶是指定UI则返回true,否则返回false</returns>
|
||||
public bool IsTop(string uiKey)
|
||||
public new bool IsTop(string uiKey)
|
||||
{
|
||||
return _stack.Count != 0 && _stack.Peek().Key.Equals(uiKey);
|
||||
return Stack.Count != 0 && Stack.Peek().Key.Equals(uiKey);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -269,15 +259,15 @@ public abstract class UiRouterBase : AbstractSystem, IUiRouter
|
||||
/// </summary>
|
||||
/// <param name="uiKey">要检查的UI页面键值</param>
|
||||
/// <returns>如果栈中包含指定UI则返回true,否则返回false</returns>
|
||||
public bool Contains(string uiKey)
|
||||
public new bool Contains(string uiKey)
|
||||
{
|
||||
return _stack.Any(p => p.Key.Equals(uiKey));
|
||||
return Stack.Any(p => p.Key.Equals(uiKey));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取栈深度
|
||||
/// </summary>
|
||||
public int Count => _stack.Count;
|
||||
public new int Count => Stack.Count;
|
||||
|
||||
#endregion
|
||||
|
||||
@ -458,51 +448,6 @@ public abstract class UiRouterBase : AbstractSystem, IUiRouter
|
||||
|
||||
#endregion
|
||||
|
||||
#region Route Guards
|
||||
|
||||
/// <summary>
|
||||
/// 注册路由守卫
|
||||
/// </summary>
|
||||
/// <param name="guard">路由守卫实例</param>
|
||||
/// <exception cref="ArgumentNullException">当守卫实例为null时抛出</exception>
|
||||
public void AddGuard(IUiRouteGuard guard)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(guard);
|
||||
|
||||
if (_guards.Contains(guard))
|
||||
{
|
||||
Log.Debug("Guard already registered: {0}", guard.GetType().Name);
|
||||
return;
|
||||
}
|
||||
|
||||
_guards.Add(guard);
|
||||
_guards.Sort((a, b) => a.Priority.CompareTo(b.Priority));
|
||||
Log.Debug("Guard registered: {0}, Priority={1}", guard.GetType().Name, guard.Priority);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 注册路由守卫(泛型)
|
||||
/// </summary>
|
||||
/// <typeparam name="T">路由守卫类型,必须实现IUiRouteGuard接口且有无参构造函数</typeparam>
|
||||
public void AddGuard<T>() where T : IUiRouteGuard, new()
|
||||
{
|
||||
AddGuard(new T());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 移除路由守卫
|
||||
/// </summary>
|
||||
/// <param name="guard">要移除的路由守卫实例</param>
|
||||
/// <exception cref="ArgumentNullException">当守卫实例为null时抛出</exception>
|
||||
public void RemoveGuard(IUiRouteGuard guard)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(guard);
|
||||
if (_guards.Remove(guard))
|
||||
Log.Debug("Guard removed: {0}", guard.GetType().Name);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Initialization
|
||||
|
||||
/// <summary>
|
||||
@ -525,7 +470,7 @@ public abstract class UiRouterBase : AbstractSystem, IUiRouter
|
||||
/// 抽象方法,用于注册具体的处理程序。
|
||||
/// 子类必须实现此方法以完成特定的处理逻辑注册。
|
||||
/// </summary>
|
||||
protected abstract void RegisterHandlers();
|
||||
protected override abstract void RegisterHandlers();
|
||||
|
||||
#endregion
|
||||
|
||||
@ -655,9 +600,9 @@ public abstract class UiRouterBase : AbstractSystem, IUiRouter
|
||||
/// <param name="policy">过渡策略</param>
|
||||
private void DoPushPageInternal(IUiPageBehavior page, IUiPageEnterParam? param, UiTransitionPolicy policy)
|
||||
{
|
||||
if (_stack.Count > 0)
|
||||
if (Stack.Count > 0)
|
||||
{
|
||||
var current = _stack.Peek();
|
||||
var current = Stack.Peek();
|
||||
Log.Debug("Pause current page: {0}", current.View.GetType().Name);
|
||||
current.OnPause();
|
||||
|
||||
@ -671,9 +616,9 @@ public abstract class UiRouterBase : AbstractSystem, IUiRouter
|
||||
Log.Debug("Add page to UiRoot: {0}", page.View.GetType().Name);
|
||||
_uiRoot.AddUiPage(page);
|
||||
|
||||
_stack.Push(page);
|
||||
Stack.Push(page);
|
||||
|
||||
Log.Debug("Enter & Show page: {0}, stackAfter={1}", page.View.GetType().Name, _stack.Count);
|
||||
Log.Debug("Enter & Show page: {0}, stackAfter={1}", page.View.GetType().Name, Stack.Count);
|
||||
page.OnEnter(param);
|
||||
page.OnShow();
|
||||
}
|
||||
@ -684,12 +629,12 @@ public abstract class UiRouterBase : AbstractSystem, IUiRouter
|
||||
/// <param name="policy">页面弹出策略</param>
|
||||
private void DoPopInternal(UiPopPolicy policy)
|
||||
{
|
||||
if (_stack.Count == 0)
|
||||
if (Stack.Count == 0)
|
||||
return;
|
||||
|
||||
var top = _stack.Pop();
|
||||
var top = Stack.Pop();
|
||||
Log.Debug("Pop UI Page internal: {0}, policy={1}, stackAfterPop={2}",
|
||||
top.GetType().Name, policy, _stack.Count);
|
||||
top.GetType().Name, policy, Stack.Count);
|
||||
|
||||
if (policy == UiPopPolicy.Destroy)
|
||||
{
|
||||
@ -701,9 +646,9 @@ public abstract class UiRouterBase : AbstractSystem, IUiRouter
|
||||
top.OnHide();
|
||||
}
|
||||
|
||||
if (_stack.Count > 0)
|
||||
if (Stack.Count > 0)
|
||||
{
|
||||
var next = _stack.Peek();
|
||||
var next = Stack.Peek();
|
||||
next.OnResume();
|
||||
next.OnShow();
|
||||
}
|
||||
@ -715,85 +660,10 @@ public abstract class UiRouterBase : AbstractSystem, IUiRouter
|
||||
/// <param name="policy">页面弹出策略</param>
|
||||
private void DoClearInternal(UiPopPolicy policy)
|
||||
{
|
||||
Log.Debug("Clear UI Stack internal, count={0}", _stack.Count);
|
||||
while (_stack.Count > 0)
|
||||
Log.Debug("Clear UI Stack internal, count={0}", Stack.Count);
|
||||
while (Stack.Count > 0)
|
||||
DoPopInternal(policy);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 执行进入守卫检查
|
||||
/// </summary>
|
||||
/// <param name="uiKey">UI页面键值</param>
|
||||
/// <param name="param">页面进入参数</param>
|
||||
/// <returns>如果允许进入则返回true,否则返回false</returns>
|
||||
private async Task<bool> ExecuteEnterGuardsAsync(string uiKey, IUiPageEnterParam? param)
|
||||
{
|
||||
foreach (var guard in _guards)
|
||||
{
|
||||
try
|
||||
{
|
||||
Log.Debug("Executing enter guard: {0} for {1}", guard.GetType().Name, uiKey);
|
||||
var canEnter = await guard.CanEnterAsync(uiKey, param);
|
||||
|
||||
if (!canEnter)
|
||||
{
|
||||
Log.Debug("Enter guard blocked: {0}", guard.GetType().Name);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (guard.CanInterrupt)
|
||||
{
|
||||
Log.Debug("Enter guard {0} passed, can interrupt = true", guard.GetType().Name);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error("Enter guard {0} failed: {1}", guard.GetType().Name, ex.Message);
|
||||
if (guard.CanInterrupt)
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 执行离开守卫检查
|
||||
/// </summary>
|
||||
/// <param name="uiKey">UI页面键值</param>
|
||||
/// <returns>如果允许离开则返回true,否则返回false</returns>
|
||||
private async Task<bool> ExecuteLeaveGuardsAsync(string uiKey)
|
||||
{
|
||||
foreach (var guard in _guards)
|
||||
{
|
||||
try
|
||||
{
|
||||
Log.Debug("Executing leave guard: {0} for {1}", guard.GetType().Name, uiKey);
|
||||
var canLeave = await guard.CanLeaveAsync(uiKey);
|
||||
|
||||
if (!canLeave)
|
||||
{
|
||||
Log.Debug("Leave guard blocked: {0}", guard.GetType().Name);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (guard.CanInterrupt)
|
||||
{
|
||||
Log.Debug("Leave guard {0} passed, can interrupt = true", guard.GetType().Name);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error("Leave guard {0} failed: {1}", guard.GetType().Name, ex.Message);
|
||||
if (guard.CanInterrupt)
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
@ -55,6 +55,7 @@
|
||||
<None Remove="GFramework.Godot.SourceGenerators.Attributes\**"/>
|
||||
<None Remove="GFramework.SourceGenerators.Attributes\**"/>
|
||||
<None Remove="Godot\**"/>
|
||||
<None Remove="GFramework.Game.Tests\**"/>
|
||||
</ItemGroup>
|
||||
<!-- 聚合核心模块 -->
|
||||
<ItemGroup>
|
||||
@ -93,6 +94,7 @@
|
||||
<Compile Remove="GFramework.Godot.SourceGenerators.Attributes\**"/>
|
||||
<Compile Remove="GFramework.SourceGenerators.Attributes\**"/>
|
||||
<Compile Remove="Godot\**"/>
|
||||
<Compile Remove="GFramework.Game.Tests\**"/>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<EmbeddedResource Remove="GFramework.Core\**"/>
|
||||
@ -117,6 +119,7 @@
|
||||
<EmbeddedResource Remove="GFramework.Godot.SourceGenerators.Attributes\**"/>
|
||||
<EmbeddedResource Remove="GFramework.SourceGenerators.Attributes\**"/>
|
||||
<EmbeddedResource Remove="Godot\**"/>
|
||||
<EmbeddedResource Remove="GFramework.Game.Tests\**"/>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<AdditionalFiles Remove="AnalyzerReleases.Shipped.md"/>
|
||||
|
||||
@ -32,6 +32,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GFramework.Ecs.Arch", "GFra
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GFramework.Ecs.Arch.Tests", "GFramework.Ecs.Arch.Tests\GFramework.Ecs.Arch.Tests.csproj", "{112CF413-4596-4AA3-B3FE-65532802FDD6}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GFramework.Game.Tests", "GFramework.Game.Tests\GFramework.Game.Tests.csproj", "{738DC58A-0387-4D75-AA96-1C1D8C29D350}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
@ -234,6 +236,18 @@ Global
|
||||
{112CF413-4596-4AA3-B3FE-65532802FDD6}.Release|x64.Build.0 = Release|Any CPU
|
||||
{112CF413-4596-4AA3-B3FE-65532802FDD6}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{112CF413-4596-4AA3-B3FE-65532802FDD6}.Release|x86.Build.0 = Release|Any CPU
|
||||
{738DC58A-0387-4D75-AA96-1C1D8C29D350}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{738DC58A-0387-4D75-AA96-1C1D8C29D350}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{738DC58A-0387-4D75-AA96-1C1D8C29D350}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||
{738DC58A-0387-4D75-AA96-1C1D8C29D350}.Debug|x64.Build.0 = Debug|Any CPU
|
||||
{738DC58A-0387-4D75-AA96-1C1D8C29D350}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||
{738DC58A-0387-4D75-AA96-1C1D8C29D350}.Debug|x86.Build.0 = Debug|Any CPU
|
||||
{738DC58A-0387-4D75-AA96-1C1D8C29D350}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{738DC58A-0387-4D75-AA96-1C1D8C29D350}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{738DC58A-0387-4D75-AA96-1C1D8C29D350}.Release|x64.ActiveCfg = Release|Any CPU
|
||||
{738DC58A-0387-4D75-AA96-1C1D8C29D350}.Release|x64.Build.0 = Release|Any CPU
|
||||
{738DC58A-0387-4D75-AA96-1C1D8C29D350}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{738DC58A-0387-4D75-AA96-1C1D8C29D350}.Release|x86.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user