新增 FlowScope Git 包雏形
This commit is contained in:
31
Packages/com.flowscope.gamecore/Runtime/Flow/FeatureBase.cs
Normal file
31
Packages/com.flowscope.gamecore/Runtime/Flow/FeatureBase.cs
Normal file
@@ -0,0 +1,31 @@
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace FlowScope.Flow
|
||||
{
|
||||
public abstract class FeatureBase : IFeature
|
||||
{
|
||||
protected FeatureContext Context { get; private set; }
|
||||
|
||||
public virtual Task LoadAsync(FeatureContext context)
|
||||
{
|
||||
Context = context;
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public virtual Task EnterAsync(FeatureContext context)
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public virtual Task ExitAsync(FeatureContext context)
|
||||
{
|
||||
context?.Disposables.Clear();
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public virtual void Dispose()
|
||||
{
|
||||
Context = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a5f4e3fb56394e719bff30e8edd4abaa
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,27 @@
|
||||
using System.Threading;
|
||||
using FlowScope.Resources;
|
||||
using R3;
|
||||
using ContainerType = FlowScope.Container.Container;
|
||||
|
||||
namespace FlowScope.Flow
|
||||
{
|
||||
public sealed class FeatureContext
|
||||
{
|
||||
public FeatureContext(
|
||||
ContainerType scope,
|
||||
IResourceGroup resources,
|
||||
CompositeDisposable disposables,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
Scope = scope;
|
||||
Resources = resources;
|
||||
Disposables = disposables;
|
||||
CancellationToken = cancellationToken;
|
||||
}
|
||||
|
||||
public ContainerType Scope { get; }
|
||||
public IResourceGroup Resources { get; }
|
||||
public CompositeDisposable Disposables { get; }
|
||||
public CancellationToken CancellationToken { get; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4196008ec1434af48d1307f18d14b655
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
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}.");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a533908611b643259dbfcb681369d012
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,13 @@
|
||||
namespace FlowScope.Flow
|
||||
{
|
||||
public enum GameFlowState
|
||||
{
|
||||
Idle,
|
||||
Starting,
|
||||
Running,
|
||||
Switching,
|
||||
NoActiveFeature,
|
||||
ShuttingDown,
|
||||
Disposed
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 1f73ffad83dc461db1dde0b4bece5eda
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
12
Packages/com.flowscope.gamecore/Runtime/Flow/IFeature.cs
Normal file
12
Packages/com.flowscope.gamecore/Runtime/Flow/IFeature.cs
Normal file
@@ -0,0 +1,12 @@
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace FlowScope.Flow
|
||||
{
|
||||
public interface IFeature : IDisposable
|
||||
{
|
||||
Task LoadAsync(FeatureContext context);
|
||||
Task EnterAsync(FeatureContext context);
|
||||
Task ExitAsync(FeatureContext context);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 749ce778d6814cf9a9fe89889025bccb
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Reference in New Issue
Block a user