Files
FlowScope/Packages/com.flowscope.gamecore/Runtime/Flow/GameFlow.cs
2026-06-04 14:54:49 +08:00

265 lines
7.3 KiB
C#

using System;
using System.Threading;
using System.Threading.Tasks;
using FlowScope.Config;
using FlowScope.Resources;
using R3;
using UnityEngine;
using ContainerType = FlowScope.Container.Container;
namespace FlowScope.Flow
{
public sealed class GameFlow
{
private ContainerType _root;
private IFeature _activeFeature;
private FeatureContext _activeContext;
public GameFlowState State { get; private set; } = GameFlowState.Idle;
public async Task StartupAsync<TInitialFeature>(
ContainerType root,
CancellationToken cancellationToken)
where TInitialFeature : IFeature
{
EnsureState(nameof(StartupAsync), GameFlowState.Idle);
_root = root ?? throw new ArgumentNullException(nameof(root));
State = GameFlowState.Starting;
IFeature feature = null;
FeatureContext context = null;
try
{
var config = _root.Resolve<IConfigProvider>();
await config.LoadAllAsync(cancellationToken);
(feature, context) = CreateFeature<TInitialFeature>(cancellationToken);
await feature.LoadAsync(context);
await feature.EnterAsync(context);
_activeFeature = feature;
_activeContext = context;
State = GameFlowState.Running;
}
catch
{
BestEffortDisposeContext(context);
_activeFeature = null;
_activeContext = null;
State = GameFlowState.Idle;
throw;
}
}
public async Task SwitchToAsync<TFeature>(CancellationToken cancellationToken)
where TFeature : IFeature
{
EnsureState(
nameof(SwitchToAsync),
GameFlowState.Running,
GameFlowState.NoActiveFeature);
State = GameFlowState.Switching;
await CleanupActiveFeatureAsync();
IFeature nextFeature = null;
FeatureContext nextContext = null;
try
{
(nextFeature, nextContext) = CreateFeature<TFeature>(cancellationToken);
await nextFeature.LoadAsync(nextContext);
await nextFeature.EnterAsync(nextContext);
_activeFeature = nextFeature;
_activeContext = nextContext;
State = GameFlowState.Running;
}
catch
{
BestEffortDisposeContext(nextContext);
_activeFeature = null;
_activeContext = null;
State = GameFlowState.NoActiveFeature;
throw;
}
}
public async Task ShutdownAsync(CancellationToken cancellationToken)
{
EnsureState(
nameof(ShutdownAsync),
GameFlowState.Running,
GameFlowState.NoActiveFeature);
State = GameFlowState.ShuttingDown;
Exception cancellationException = null;
try
{
cancellationToken.ThrowIfCancellationRequested();
}
catch (OperationCanceledException exception)
{
cancellationException = exception;
}
await CleanupActiveFeatureAsync();
BestEffortDisposeRoot();
State = GameFlowState.Disposed;
if (cancellationException != null)
{
throw cancellationException;
}
}
private (IFeature Feature, FeatureContext Context) CreateFeature<TFeature>(
CancellationToken cancellationToken)
where TFeature : IFeature
{
ContainerType scope = null;
IResourceGroup resources = null;
FeatureContext context = null;
try
{
scope = _root.CreateScope();
var resourceService = _root.Resolve<IResourceService>();
resources = resourceService.CreateGroup();
context = new FeatureContext(
scope,
resources,
new CompositeDisposable(),
cancellationToken);
var feature = scope.Resolve<TFeature>();
return (feature, context);
}
catch
{
if (context != null)
{
BestEffortDisposeContext(context);
}
else
{
BestEffortDisposePartialContext(resources, scope);
}
throw;
}
}
private async Task CleanupActiveFeatureAsync()
{
var feature = _activeFeature;
var context = _activeContext;
_activeFeature = null;
_activeContext = null;
if (feature != null)
{
try
{
await feature.ExitAsync(context);
}
catch (Exception exception)
{
Debug.LogError(exception);
}
}
BestEffortDisposeContext(context);
}
private void BestEffortDisposeContext(FeatureContext context)
{
if (context == null)
{
return;
}
try
{
context.Disposables.Clear();
}
catch (Exception exception)
{
Debug.LogError(exception);
}
try
{
context.Scope?.Dispose();
}
catch (Exception exception)
{
Debug.LogError(exception);
}
try
{
context.Resources?.Dispose();
}
catch (Exception exception)
{
Debug.LogError(exception);
}
}
private void BestEffortDisposePartialContext(
IResourceGroup resources,
ContainerType scope)
{
try
{
resources?.Dispose();
}
catch (Exception exception)
{
Debug.LogError(exception);
}
try
{
scope?.Dispose();
}
catch (Exception exception)
{
Debug.LogError(exception);
}
}
private void BestEffortDisposeRoot()
{
try
{
_root?.Dispose();
}
catch (Exception exception)
{
Debug.LogError(exception);
}
finally
{
_root = null;
}
}
private void EnsureState(string operation, params GameFlowState[] allowedStates)
{
foreach (var allowedState in allowedStates)
{
if (State == allowedState)
{
return;
}
}
throw new InvalidOperationException(
$"{operation} cannot run while GameFlow state is {State}.");
}
}
}