新增 FlowScope Git 包雏形
This commit is contained in:
264
Packages/com.flowscope.gamecore/Runtime/Flow/GameFlow.cs
Normal file
264
Packages/com.flowscope.gamecore/Runtime/Flow/GameFlow.cs
Normal file
@@ -0,0 +1,264 @@
|
||||
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}.");
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user