diff --git a/GFramework.Game.Abstractions/enums/SceneTransitionPhases.cs b/GFramework.Game.Abstractions/enums/SceneTransitionPhases.cs new file mode 100644 index 0000000..8745188 --- /dev/null +++ b/GFramework.Game.Abstractions/enums/SceneTransitionPhases.cs @@ -0,0 +1,42 @@ +// 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.enums; + +/// +/// 场景过渡阶段枚举,定义了场景切换过程中的不同执行阶段。 +/// 使用 Flags 特性支持组合多个阶段。 +/// +[Flags] +public enum SceneTransitionPhases +{ + /// + /// 场景切换前阶段(阻塞执行)。 + /// 用于执行需要在场景切换前完成的操作,如显示加载动画、弹出确认对话框等。 + /// 此阶段的处理器会阻塞场景切换流程,直到所有处理器执行完成。 + /// + BeforeChange = 1, + + /// + /// 场景切换后阶段(非阻塞执行)。 + /// 用于执行场景切换后的后续操作,如播放音效、记录日志、发送统计数据等。 + /// 此阶段的处理器异步执行,不会阻塞场景切换流程。 + /// + AfterChange = 2, + + /// + /// 所有阶段的组合标志。 + /// 表示处理器适用于场景切换的所有阶段。 + /// + All = BeforeChange | AfterChange +} \ No newline at end of file diff --git a/GFramework.Game.Abstractions/enums/SceneTransitionType.cs b/GFramework.Game.Abstractions/enums/SceneTransitionType.cs new file mode 100644 index 0000000..edd8599 --- /dev/null +++ b/GFramework.Game.Abstractions/enums/SceneTransitionType.cs @@ -0,0 +1,44 @@ +// 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.enums; + +/// +/// 场景过渡类型枚举,定义了场景切换的不同操作模式。 +/// +public enum SceneTransitionType +{ + /// + /// 压入新场景到场景栈顶部。 + /// 当前场景会被暂停,新场景成为活动场景。 + /// + Push, + + /// + /// 弹出当前场景并恢复下一个场景。 + /// 当前场景会被卸载,栈中的下一个场景变为活动场景。 + /// + Pop, + + /// + /// 替换所有场景,清空整个场景栈并加载新场景。 + /// 此操作会卸载所有现有场景,然后加载指定的新场景。 + /// + Replace, + + /// + /// 清空所有已加载的场景。 + /// 卸载场景栈中的所有场景,使系统回到无场景状态。 + /// + Clear +} \ No newline at end of file diff --git a/GFramework.Game.Abstractions/scene/ISceneLoadingProgress.cs b/GFramework.Game.Abstractions/scene/ISceneLoadingProgress.cs new file mode 100644 index 0000000..153325e --- /dev/null +++ b/GFramework.Game.Abstractions/scene/ISceneLoadingProgress.cs @@ -0,0 +1,45 @@ +// 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.scene; + +/// +/// 场景加载进度接口,用于跟踪和报告场景资源的加载状态。 +/// 实现此接口的类可以提供场景加载的实时进度信息。 +/// +public interface ISceneLoadingProgress +{ + /// + /// 获取正在加载的场景的唯一标识符。 + /// + string SceneKey { get; } + + /// + /// 获取当前加载进度,范围为 0.0 到 1.0。 + /// 0.0 表示刚开始加载,1.0 表示加载完成。 + /// + float Progress { get; } + + /// + /// 获取当前加载阶段的描述信息。 + /// 例如:"加载纹理资源"、"初始化场景对象"等。 + /// 如果没有具体信息则返回 null。 + /// + string? Message { get; } + + /// + /// 获取加载是否已完成的状态。 + /// true 表示场景资源已全部加载完成,false 表示仍在加载中。 + /// + bool IsCompleted { get; } +} \ No newline at end of file diff --git a/GFramework.Game.Abstractions/scene/ISceneRouteGuard.cs b/GFramework.Game.Abstractions/scene/ISceneRouteGuard.cs new file mode 100644 index 0000000..07ed138 --- /dev/null +++ b/GFramework.Game.Abstractions/scene/ISceneRouteGuard.cs @@ -0,0 +1,50 @@ +// 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.scene; + +/// +/// 场景路由守卫接口,用于在场景切换前进行权限检查和条件验证。 +/// 实现此接口可以拦截场景的进入和离开操作。 +/// +public interface ISceneRouteGuard +{ + /// + /// 获取守卫的执行优先级。 + /// 数值越小优先级越高,越先执行。 + /// 建议范围:-1000 到 1000。 + /// + int Priority { get; } + + /// + /// 获取守卫是否可以中断后续守卫的执行。 + /// true 表示当前守卫通过后,可以跳过后续守卫直接允许操作。 + /// false 表示即使当前守卫通过,仍需执行所有后续守卫。 + /// + bool CanInterrupt { get; } + + /// + /// 异步检查是否允许进入指定场景。 + /// + /// 目标场景的唯一标识符。 + /// 场景进入参数,可能包含初始化数据或上下文信息。 + /// 如果允许进入则返回 true,否则返回 false。 + Task CanEnterAsync(string sceneKey, ISceneEnterParam? param); + + /// + /// 异步检查是否允许离开指定场景。 + /// + /// 当前场景的唯一标识符。 + /// 如果允许离开则返回 true,否则返回 false。 + Task CanLeaveAsync(string sceneKey); +} \ No newline at end of file diff --git a/GFramework.Game.Abstractions/scene/ISceneTransitionHandler.cs b/GFramework.Game.Abstractions/scene/ISceneTransitionHandler.cs new file mode 100644 index 0000000..b1d31c7 --- /dev/null +++ b/GFramework.Game.Abstractions/scene/ISceneTransitionHandler.cs @@ -0,0 +1,52 @@ +// 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.enums; + +namespace GFramework.Game.Abstractions.scene; + +/// +/// 场景过渡处理器接口,定义了场景切换过程中的扩展点。 +/// 实现此接口可以在场景切换的不同阶段执行自定义逻辑。 +/// +public interface ISceneTransitionHandler +{ + /// + /// 获取处理器的执行优先级。 + /// 数值越小优先级越高,越先执行。 + /// 建议范围:-1000 到 1000。 + /// + int Priority { get; } + + /// + /// 获取处理器适用的场景过渡阶段。 + /// 可以使用 Flags 组合多个阶段(如 BeforeChange | AfterChange)。 + /// + SceneTransitionPhases Phases { get; } + + /// + /// 判断处理器是否应该处理当前场景过渡事件。 + /// + /// 场景过渡事件。 + /// 当前执行的阶段。 + /// 如果应该处理则返回 true,否则返回 false。 + bool ShouldHandle(SceneTransitionEvent @event, SceneTransitionPhases phases); + + /// + /// 异步处理场景过渡事件。 + /// + /// 场景过渡事件,包含切换的上下文信息。 + /// 取消令牌,用于支持操作取消。 + /// 表示处理操作完成的异步任务。 + Task HandleAsync(SceneTransitionEvent @event, CancellationToken cancellationToken); +} \ No newline at end of file diff --git a/GFramework.Game.Abstractions/scene/SceneTransitionEvent.cs b/GFramework.Game.Abstractions/scene/SceneTransitionEvent.cs new file mode 100644 index 0000000..348bb6d --- /dev/null +++ b/GFramework.Game.Abstractions/scene/SceneTransitionEvent.cs @@ -0,0 +1,115 @@ +// 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.enums; + +namespace GFramework.Game.Abstractions.scene; + +/// +/// 场景过渡事件,封装了场景切换过程中的上下文信息。 +/// 提供了场景切换的元数据和自定义上下文数据的存储能力。 +/// +public sealed class SceneTransitionEvent +{ + private readonly Dictionary _context = new(); + + /// + /// 获取或初始化源场景的唯一标识符。 + /// 表示切换前的场景,如果是首次加载场景则为 null。 + /// + public string? FromSceneKey { get; init; } + + /// + /// 获取或初始化目标场景的唯一标识符。 + /// 表示切换后的场景,如果是清空操作则为 null。 + /// + public string? ToSceneKey { get; init; } + + /// + /// 获取或初始化场景过渡类型。 + /// 指示当前执行的是哪种场景切换操作(Push/Pop/Replace/Clear)。 + /// + public SceneTransitionType TransitionType { get; init; } + + /// + /// 获取或初始化场景进入参数。 + /// 包含传递给新场景的初始化数据或上下文信息。 + /// + public ISceneEnterParam? EnterParam { get; init; } + + /// + /// 从上下文中获取指定键的值。 + /// + /// 值的类型。 + /// 上下文键。 + /// 如果键不存在时返回的默认值。 + /// 上下文中存储的值,如果键不存在则返回默认值。 + public T Get(string key, T defaultValue = default!) + { + if (_context.TryGetValue(key, out var value) && value is T typedValue) + return typedValue; + return defaultValue; + } + + /// + /// 设置上下文中指定键的值。 + /// + /// 值的类型。 + /// 上下文键。 + /// 要存储的值。 + public void Set(string key, T value) + { + if (key == null) + throw new ArgumentNullException(nameof(key)); + _context[key] = value!; + } + + /// + /// 尝试从上下文中获取指定键的值。 + /// + /// 值的类型。 + /// 上下文键。 + /// 输出参数,如果键存在则包含对应的值。 + /// 如果键存在且类型匹配则返回 true,否则返回 false。 + public bool TryGet(string key, out T value) + { + if (_context.TryGetValue(key, out var obj) && obj is T typedValue) + { + value = typedValue; + return true; + } + + value = default!; + return false; + } + + /// + /// 检查上下文中是否存在指定的键。 + /// + /// 上下文键。 + /// 如果键存在则返回 true,否则返回 false。 + public bool Has(string key) + { + return _context.ContainsKey(key); + } + + /// + /// 从上下文中移除指定的键。 + /// + /// 上下文键。 + /// 如果键存在并成功移除则返回 true,否则返回 false。 + public bool Remove(string key) + { + return _context.Remove(key); + } +} \ No newline at end of file diff --git a/GFramework.Game.Abstractions/scene/SceneTransitionHandlerOptions.cs b/GFramework.Game.Abstractions/scene/SceneTransitionHandlerOptions.cs new file mode 100644 index 0000000..d6c6fdd --- /dev/null +++ b/GFramework.Game.Abstractions/scene/SceneTransitionHandlerOptions.cs @@ -0,0 +1,32 @@ +// 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.scene; + +/// +/// 场景过渡处理器选项,定义了处理器执行时的配置参数。 +/// +/// +/// 处理器执行的超时时间(毫秒)。 +/// 设置为 0 表示无超时限制。 +/// 如果处理器执行超过此时间,将触发超时异常。 +/// +/// +/// 当处理器执行失败时是否继续执行后续处理器。 +/// true 表示即使当前处理器失败,管道仍会继续执行后续处理器。 +/// false 表示当前处理器失败时,管道会立即停止并抛出异常。 +/// +public record SceneTransitionHandlerOptions( + int TimeoutMs = 0, + bool ContinueOnError = true +); \ No newline at end of file diff --git a/GFramework.Game/scene/SceneRouterBase.cs b/GFramework.Game/scene/SceneRouterBase.cs index 9b884a5..04052a2 100644 --- a/GFramework.Game/scene/SceneRouterBase.cs +++ b/GFramework.Game/scene/SceneRouterBase.cs @@ -12,8 +12,10 @@ // limitations under the License. using GFramework.Core.Abstractions.logging; +using GFramework.Core.extensions; using GFramework.Core.logging; using GFramework.Core.system; +using GFramework.Game.Abstractions.enums; using GFramework.Game.Abstractions.scene; namespace GFramework.Game.scene; @@ -28,20 +30,43 @@ public abstract class SceneRouterBase private static readonly ILogger Log = LoggerFactoryResolver.Provider.CreateLogger(nameof(SceneRouterBase)); + private readonly List _guards = new(); + private readonly SceneTransitionPipeline _pipeline = new(); + private readonly Stack _stack = new(); private readonly SemaphoreSlim _transitionLock = new(1, 1); + private ISceneFactory _factory = null!; + /// + /// 场景根节点 + /// protected ISceneRoot? Root; + /// + /// 获取当前场景行为对象。 + /// public ISceneBehavior? Current => _stack.Count > 0 ? _stack.Peek() : null; + /// + /// 获取当前场景的键名。 + /// public string? CurrentKey => Current?.Key; + /// + /// 获取场景栈的只读列表,按压入顺序排列。 + /// public IReadOnlyList Stack => _stack.Reverse().ToList(); + /// + /// 获取是否正在进行场景转换。 + /// public bool IsTransitioning { get; private set; } + /// + /// 绑定场景根节点。 + /// + /// 场景根节点实例。 public void BindRoot(ISceneRoot root) { Root = root; @@ -50,6 +75,12 @@ public abstract class SceneRouterBase #region Replace + /// + /// 替换当前场景为指定场景。 + /// + /// 目标场景键名。 + /// 场景进入参数。 + /// 异步任务。 public async ValueTask ReplaceAsync( string sceneKey, ISceneEnterParam? param = null) @@ -59,10 +90,12 @@ public abstract class SceneRouterBase { IsTransitioning = true; - Log.Debug("Replace Scene: {0}", sceneKey); + var @event = CreateEvent(sceneKey, SceneTransitionType.Replace, param); + await BeforeChangeAsync(@event); await ClearInternalAsync(); await PushInternalAsync(sceneKey, param); + AfterChange(@event); } finally { @@ -75,6 +108,11 @@ public abstract class SceneRouterBase #region Query + /// + /// 检查指定场景是否在栈中。 + /// + /// 场景键名。 + /// 如果场景在栈中返回true,否则返回false。 public bool Contains(string sceneKey) { return _stack.Any(s => s.Key == sceneKey); @@ -82,8 +120,84 @@ public abstract class SceneRouterBase #endregion + /// + /// 注册场景过渡处理器。 + /// + /// 处理器实例。 + /// 执行选项。 + public void RegisterHandler(ISceneTransitionHandler handler, SceneTransitionHandlerOptions? options = null) + { + _pipeline.RegisterHandler(handler, options); + } + + /// + /// 注销场景过渡处理器。 + /// + /// 处理器实例。 + public void UnregisterHandler(ISceneTransitionHandler handler) + { + _pipeline.UnregisterHandler(handler); + } + + /// + /// 添加场景路由守卫。 + /// + /// 守卫实例。 + 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); + } + } + + /// + /// 添加场景路由守卫(泛型版本)。 + /// + /// 守卫类型。 + public void AddGuard() where T : ISceneRouteGuard, new() + { + AddGuard(new T()); + } + + /// + /// 移除场景路由守卫。 + /// + /// 守卫实例。 + public void RemoveGuard(ISceneRouteGuard guard) + { + if (_guards.Remove(guard)) + { + Log.Debug("Guard removed: {0}", guard.GetType().Name); + } + } + + /// + /// 注册场景过渡处理器的抽象方法,由子类实现。 + /// + protected abstract void RegisterHandlers(); + + /// + /// 系统初始化方法,获取场景工厂并注册处理器。 + /// + protected override void OnInit() + { + _factory = this.GetUtility()!; + Log.Debug("SceneRouterBase initialized. Factory={0}", _factory.GetType().Name); + RegisterHandlers(); + } + #region Push + /// + /// 将指定场景推入栈顶。 + /// + /// 目标场景键名。 + /// 场景进入参数。 + /// 异步任务。 public async ValueTask PushAsync( string sceneKey, ISceneEnterParam? param = null) @@ -92,7 +206,12 @@ public abstract class SceneRouterBase try { IsTransitioning = true; + + var @event = CreateEvent(sceneKey, SceneTransitionType.Push, param); + + await BeforeChangeAsync(@event); await PushInternalAsync(sceneKey, param); + AfterChange(@event); } finally { @@ -101,6 +220,13 @@ public abstract class SceneRouterBase } } + /// + /// 内部推送场景实现方法。 + /// 执行守卫检查、场景加载、暂停当前场景、压入栈等操作。 + /// + /// 场景键名。 + /// 场景进入参数。 + /// 异步任务。 private async ValueTask PushInternalAsync( string sceneKey, ISceneEnterParam? param) @@ -111,18 +237,31 @@ public abstract class SceneRouterBase return; } + // 守卫检查 + if (!await ExecuteEnterGuardsAsync(sceneKey, param)) + { + Log.Warn("Push blocked by guard: {0}", sceneKey); + return; + } + + // 通过 Root 加载场景(Root.LoadAsync 返回 ISceneBehavior) var scene = await Root!.LoadAsync(sceneKey); + // 加载资源 + await scene.OnLoadAsync(param); + + // 暂停当前场景 if (_stack.Count > 0) { var current = _stack.Peek(); await current.OnPauseAsync(); } + // 压入栈 _stack.Push(scene); - await scene.OnEnterAsync(param); - await scene.OnShowAsync(); + // 进入场景 + await scene.OnEnterAsync(); Log.Debug("Push Scene: {0}, stackCount={1}", sceneKey, _stack.Count); @@ -132,13 +271,22 @@ public abstract class SceneRouterBase #region Pop + /// + /// 弹出栈顶场景。 + /// + /// 异步任务。 public async ValueTask PopAsync() { await _transitionLock.WaitAsync(); try { IsTransitioning = true; + + var @event = CreateEvent(null, SceneTransitionType.Pop); + + await BeforeChangeAsync(@event); await PopInternalAsync(); + AfterChange(@event); } finally { @@ -147,21 +295,41 @@ public abstract class SceneRouterBase } } + /// + /// 内部弹出场景实现方法。 + /// 执行守卫检查、退出场景、卸载资源、恢复下一个场景等操作。 + /// + /// 异步任务。 private async ValueTask PopInternalAsync() { if (_stack.Count == 0) return; - var top = _stack.Pop(); + 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(); + + // 从场景树卸载 await Root!.UnloadAsync(top); + // 恢复下一个场景 if (_stack.Count > 0) { var next = _stack.Peek(); await next.OnResumeAsync(); - await next.OnShowAsync(); } Log.Debug("Pop Scene, stackCount={0}", _stack.Count); @@ -171,13 +339,22 @@ public abstract class SceneRouterBase #region Clear + /// + /// 清空所有场景栈。 + /// + /// 异步任务。 public async ValueTask ClearAsync() { await _transitionLock.WaitAsync(); try { IsTransitioning = true; + + var @event = CreateEvent(null, SceneTransitionType.Clear); + + await BeforeChangeAsync(@event); await ClearInternalAsync(); + AfterChange(@event); } finally { @@ -186,6 +363,11 @@ public abstract class SceneRouterBase } } + /// + /// 内部清空场景栈实现方法。 + /// 循环调用弹出操作直到栈为空。 + /// + /// 异步任务。 private async ValueTask ClearInternalAsync() { while (_stack.Count > 0) @@ -195,4 +377,141 @@ public abstract class SceneRouterBase } #endregion + + #region Helper Methods + + /// + /// 创建场景转换事件对象。 + /// + /// 目标场景键名。 + /// 转换类型。 + /// 进入参数。 + /// 场景转换事件实例。 + private SceneTransitionEvent CreateEvent( + string? toSceneKey, + SceneTransitionType type, + ISceneEnterParam? param = null) + { + return new SceneTransitionEvent + { + FromSceneKey = CurrentKey, + ToSceneKey = toSceneKey, + TransitionType = type, + EnterParam = param + }; + } + + /// + /// 执行转换前阶段的处理逻辑。 + /// + /// 场景转换事件。 + /// 异步任务。 + 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); + } + + /// + /// 执行转换后阶段的处理逻辑。 + /// 在后台线程中异步执行,避免阻塞主线程。 + /// + /// 场景转换事件。 + private void AfterChange(SceneTransitionEvent @event) + { + Log.Debug("AfterChange phases started: {0}", @event.TransitionType); + _ = Task.Run(async () => + { + try + { + await _pipeline.ExecuteAsync(@event, SceneTransitionPhases.AfterChange); + Log.Debug("AfterChange phases completed: {0}", @event.TransitionType); + } + catch (Exception ex) + { + Log.Error("AfterChange phases failed: {0}, Error: {1}", + @event.TransitionType, ex.Message); + } + }); + } + + /// + /// 执行进入场景的守卫检查。 + /// 按优先级顺序执行所有守卫的CanEnterAsync方法。 + /// + /// 场景键名。 + /// 进入参数。 + /// 如果所有守卫都允许进入返回true,否则返回false。 + private async Task 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; + } + + /// + /// 执行离开场景的守卫检查。 + /// 按优先级顺序执行所有守卫的CanLeaveAsync方法。 + /// + /// 场景键名。 + /// 如果所有守卫都允许离开返回true,否则返回false。 + private async Task 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 } \ No newline at end of file diff --git a/GFramework.Game/scene/SceneTransitionPipeline.cs b/GFramework.Game/scene/SceneTransitionPipeline.cs new file mode 100644 index 0000000..61a73c2 --- /dev/null +++ b/GFramework.Game/scene/SceneTransitionPipeline.cs @@ -0,0 +1,182 @@ +// 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.Game.Abstractions.enums; +using GFramework.Game.Abstractions.scene; + +namespace GFramework.Game.scene; + +/// +/// 场景过渡处理器管道,负责管理和执行场景切换扩展点。 +/// 提供了处理器的注册、注销和按优先级顺序执行的功能。 +/// +public class SceneTransitionPipeline +{ + private static readonly ILogger Log = LoggerFactoryResolver.Provider.CreateLogger("SceneTransitionPipeline"); + private readonly List _handlers = []; + private readonly Dictionary _options = new(); + + /// + /// 注册场景过渡处理器。 + /// + /// 处理器实例。 + /// 执行选项,如果为 null 则使用默认选项。 + public void RegisterHandler(ISceneTransitionHandler handler, SceneTransitionHandlerOptions? options = null) + { + ArgumentNullException.ThrowIfNull(handler); + + if (_handlers.Contains(handler)) + { + Log.Debug("Handler already registered: {0}", handler.GetType().Name); + return; + } + + _handlers.Add(handler); + _options[handler] = options ?? new SceneTransitionHandlerOptions(); + Log.Debug( + "Handler registered: {0}, Priority={1}, Phases={2}, TimeoutMs={3}", + handler.GetType().Name, + handler.Priority, + handler.Phases, + _options[handler].TimeoutMs + ); + } + + /// + /// 注销场景过渡处理器。 + /// + /// 处理器实例。 + public void UnregisterHandler(ISceneTransitionHandler handler) + { + ArgumentNullException.ThrowIfNull(handler); + + if (!_handlers.Remove(handler)) return; + _options.Remove(handler); + Log.Debug("Handler unregistered: {0}", handler.GetType().Name); + } + + /// + /// 执行指定阶段的所有处理器。 + /// + /// 场景过渡事件。 + /// 执行阶段。 + /// 取消令牌。 + /// 异步任务。 + public async Task ExecuteAsync( + SceneTransitionEvent @event, + SceneTransitionPhases phases, + CancellationToken cancellationToken = default + ) + { + @event.Set("Phases", phases.ToString()); + + Log.Debug( + "Execute pipeline: Phases={0}, From={1}, To={2}, Type={3}, HandlerCount={4}", + phases, + @event.FromSceneKey, + @event.ToSceneKey ?? "None", + @event.TransitionType, + _handlers.Count + ); + + var sortedHandlers = FilterAndSortHandlers(@event, phases); + + if (sortedHandlers.Count == 0) + { + Log.Debug("No handlers to execute for phases: {0}", phases); + return; + } + + Log.Debug( + "Executing {0} handlers for phases {1}", + sortedHandlers.Count, + phases + ); + + foreach (var handler in sortedHandlers) + { + var options = _options[handler]; + await ExecuteSingleHandlerAsync(handler, options, @event, cancellationToken); + } + + Log.Debug("Pipeline execution completed for phases: {0}", phases); + } + + private List FilterAndSortHandlers( + SceneTransitionEvent @event, + SceneTransitionPhases phases) + { + return _handlers + .Where(h => h.Phases.HasFlag(phases) && h.ShouldHandle(@event, phases)) + .OrderBy(h => h.Priority) + .ToList(); + } + + private static async Task ExecuteSingleHandlerAsync( + ISceneTransitionHandler handler, + SceneTransitionHandlerOptions options, + SceneTransitionEvent @event, + CancellationToken cancellationToken) + { + Log.Debug( + "Executing handler: {0}, Priority={1}", + handler.GetType().Name, + handler.Priority + ); + + try + { + using var timeoutCts = options.TimeoutMs > 0 + ? new CancellationTokenSource(options.TimeoutMs) + : null; + + using var linkedCts = timeoutCts != null && cancellationToken.CanBeCanceled + ? CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, timeoutCts.Token) + : null; + + await handler.HandleAsync( + @event, + linkedCts?.Token ?? cancellationToken + ).ConfigureAwait(false); + + Log.Debug("Handler completed: {0}", handler.GetType().Name); + } + catch (OperationCanceledException) when (!cancellationToken.IsCancellationRequested) + { + Log.Error( + "Handler timeout: {0}, TimeoutMs={1}", + handler.GetType().Name, + options.TimeoutMs + ); + + if (options.ContinueOnError) return; + Log.Error("Stopping pipeline due to timeout and ContinueOnError=false"); + throw; + } + catch (OperationCanceledException) + { + Log.Debug("Handler cancelled: {0}", handler.GetType().Name); + throw; + } + catch (Exception ex) + { + Log.Error("Handler failed: {0}, Error: {1}", handler.GetType().Name, ex.Message); + + if (options.ContinueOnError) return; + Log.Error("Stopping pipeline due to error and ContinueOnError=false"); + throw; + } + } +} \ No newline at end of file diff --git a/GFramework.Game/scene/handler/LoadingProgressHandler.cs b/GFramework.Game/scene/handler/LoadingProgressHandler.cs new file mode 100644 index 0000000..95b33d8 --- /dev/null +++ b/GFramework.Game/scene/handler/LoadingProgressHandler.cs @@ -0,0 +1,61 @@ +// 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.Game.Abstractions.enums; +using GFramework.Game.Abstractions.scene; + +namespace GFramework.Game.scene.handler; + +/// +/// 加载进度处理器,用于在场景加载前显示加载界面和进度反馈。 +/// +public sealed class LoadingProgressHandler : SceneTransitionHandlerBase +{ + private static readonly ILogger Log = LoggerFactoryResolver.Provider.CreateLogger(nameof(LoadingProgressHandler)); + + /// + /// 获取处理器优先级,数值越小优先级越高(优先执行)。 + /// + public override int Priority => -100; + + /// + /// 获取处理器处理的场景切换阶段,只处理 BeforeChange 阶段。 + /// + public override SceneTransitionPhases Phases => SceneTransitionPhases.BeforeChange; + + /// + /// 处理场景切换事件的异步方法。 + /// + /// 场景切换事件对象,包含切换的相关信息。 + /// 取消令牌,用于控制异步操作的取消。 + /// 表示异步操作的任务。 + public override async Task HandleAsync(SceneTransitionEvent @event, CancellationToken cancellationToken) + { + // 只处理 Push 和 Replace 操作(需要加载场景) + if (@event.TransitionType != SceneTransitionType.Push && + @event.TransitionType != SceneTransitionType.Replace) + return; + + // 显示加载界面 + // TODO: 调用 UI 系统显示加载界面 + Log.Info("Loading scene: {0}", @event.ToSceneKey); + + // 监听加载进度(如果需要) + // 这里可以通过事件上下文传递进度信息 + // 例如:@event.Set("LoadingProgress", progressValue); + + await Task.CompletedTask; + } +} \ No newline at end of file diff --git a/GFramework.Game/scene/handler/LoggingTransitionHandler.cs b/GFramework.Game/scene/handler/LoggingTransitionHandler.cs new file mode 100644 index 0000000..d8afbc6 --- /dev/null +++ b/GFramework.Game/scene/handler/LoggingTransitionHandler.cs @@ -0,0 +1,56 @@ +// 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.Game.Abstractions.enums; +using GFramework.Game.Abstractions.scene; + +namespace GFramework.Game.scene.handler; + +/// +/// 日志场景切换处理器,用于记录场景切换的详细信息。 +/// +public sealed class LoggingTransitionHandler : SceneTransitionHandlerBase +{ + private static readonly ILogger Log = LoggerFactoryResolver.Provider.CreateLogger(nameof(LoggingTransitionHandler)); + + /// + /// 获取处理器优先级,数值越大优先级越低(最后执行)。 + /// + public override int Priority => 999; + + /// + /// 获取处理器处理的场景切换阶段,处理所有阶段。 + /// + public override SceneTransitionPhases Phases => SceneTransitionPhases.All; + + /// + /// 处理场景切换事件的异步方法。 + /// + /// 场景切换事件对象,包含切换的相关信息。 + /// 取消令牌,用于控制异步操作的取消。 + /// 表示异步操作的任务。 + public override Task HandleAsync(SceneTransitionEvent @event, CancellationToken cancellationToken) + { + Log.Info( + "Scene Transition: Phases={0}, Type={1}, From={2}, To={3}", + @event.Get("Phases", "Unknown"), + @event.TransitionType, + @event.FromSceneKey ?? "None", + @event.ToSceneKey ?? "None" + ); + + return Task.CompletedTask; + } +} \ No newline at end of file diff --git a/GFramework.Game/scene/handler/SceneTransitionHandlerBase.cs b/GFramework.Game/scene/handler/SceneTransitionHandlerBase.cs new file mode 100644 index 0000000..d83f814 --- /dev/null +++ b/GFramework.Game/scene/handler/SceneTransitionHandlerBase.cs @@ -0,0 +1,55 @@ +// 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.enums; +using GFramework.Game.Abstractions.scene; + +namespace GFramework.Game.scene.handler; + +/// +/// 场景过渡处理器抽象基类,提供了 ISceneTransitionHandler 接口的默认实现。 +/// 派生类只需重写必要的方法即可快速实现自定义处理器。 +/// +public abstract class SceneTransitionHandlerBase : ISceneTransitionHandler +{ + /// + /// 获取处理器适用的场景过渡阶段。 + /// 默认为所有阶段(BeforeChange 和 AfterChange)。 + /// + public virtual SceneTransitionPhases Phases => SceneTransitionPhases.All; + + /// + /// 获取处理器的执行优先级。 + /// 派生类必须实现此属性以指定优先级。 + /// + public abstract int Priority { get; } + + /// + /// 判断处理器是否应该处理当前场景过渡事件。 + /// 默认实现总是返回 true,派生类可以重写此方法以添加自定义过滤逻辑。 + /// + /// 场景过渡事件。 + /// 当前执行的阶段。 + /// 默认返回 true,表示总是处理。 + public virtual bool ShouldHandle(SceneTransitionEvent @event, SceneTransitionPhases phases) + => true; + + /// + /// 异步处理场景过渡事件。 + /// 派生类必须实现此方法以定义具体的处理逻辑。 + /// + /// 场景过渡事件,包含切换的上下文信息。 + /// 取消令牌,用于支持操作取消。 + /// 表示处理操作完成的异步任务。 + public abstract Task HandleAsync(SceneTransitionEvent @event, CancellationToken cancellationToken); +} \ No newline at end of file