// 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.system;
using GFramework.Game.Abstractions.scene;
namespace GFramework.Game.scene;
///
/// 场景路由基类,提供场景切换和卸载的基础功能。
/// 实现了 接口,用于管理场景的加载、替换和卸载操作。
///
public abstract class SceneRouterBase
: AbstractSystem, ISceneRouter
{
private static readonly ILogger Log =
LoggerFactoryResolver.Provider.CreateLogger(nameof(SceneRouterBase));
private readonly Stack _stack = new();
private readonly SemaphoreSlim _transitionLock = new(1, 1);
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;
Log.Debug("Bind Scene Root: {0}", root.GetType().Name);
}
#region Replace
public async ValueTask ReplaceAsync(
string sceneKey,
ISceneEnterParam? param = null)
{
await _transitionLock.WaitAsync();
try
{
IsTransitioning = true;
Log.Debug("Replace Scene: {0}", sceneKey);
await ClearInternalAsync();
await PushInternalAsync(sceneKey, param);
}
finally
{
IsTransitioning = false;
_transitionLock.Release();
}
}
#endregion
#region Query
public bool Contains(string sceneKey)
{
return _stack.Any(s => s.Key == sceneKey);
}
#endregion
#region Push
public async ValueTask PushAsync(
string sceneKey,
ISceneEnterParam? param = null)
{
await _transitionLock.WaitAsync();
try
{
IsTransitioning = true;
await PushInternalAsync(sceneKey, param);
}
finally
{
IsTransitioning = false;
_transitionLock.Release();
}
}
private async ValueTask PushInternalAsync(
string sceneKey,
ISceneEnterParam? param)
{
if (Contains(sceneKey))
{
Log.Warn("Scene already in stack: {0}", sceneKey);
return;
}
var scene = await Root!.LoadAsync(sceneKey);
if (_stack.Count > 0)
{
var current = _stack.Peek();
await current.OnPauseAsync();
}
_stack.Push(scene);
await scene.OnEnterAsync(param);
await scene.OnShowAsync();
Log.Debug("Push Scene: {0}, stackCount={1}",
sceneKey, _stack.Count);
}
#endregion
#region Pop
public async ValueTask PopAsync()
{
await _transitionLock.WaitAsync();
try
{
IsTransitioning = true;
await PopInternalAsync();
}
finally
{
IsTransitioning = false;
_transitionLock.Release();
}
}
private async ValueTask PopInternalAsync()
{
if (_stack.Count == 0)
return;
var top = _stack.Pop();
await top.OnExitAsync();
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);
}
#endregion
#region Clear
public async ValueTask ClearAsync()
{
await _transitionLock.WaitAsync();
try
{
IsTransitioning = true;
await ClearInternalAsync();
}
finally
{
IsTransitioning = false;
_transitionLock.Release();
}
}
private async ValueTask ClearInternalAsync()
{
while (_stack.Count > 0)
{
await PopInternalAsync();
}
}
#endregion
}