Last active
May 22, 2022 17:44
-
-
Save t-tutiya/f2526a3d1f8caf638fb00e4f9cb8cb94 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// zlib/libpng License | |
// | |
// Copyright (c) 2021 TSUCHIYA Tsukasa | |
// Inspired By ImtStateMachine(Sinoa) https://github.com/Sinoa/IceMilkTea/blob/develop/Packages/IceMilkTea/Runtime/Core/UnitCode/PureCsharp/StateMachine.cs | |
// | |
// This software is provided 'as-is', without any express or implied warranty. | |
// In no event will the authors be held liable for any damages arising from the use of this software. | |
// Permission is granted to anyone to use this software for any purpose, | |
// including commercial applications, and to alter it and redistribute it freely, | |
// subject to the following restrictions: | |
// | |
// 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. | |
// If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. | |
// 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. | |
// 3. This notice may not be removed or altered from any source distribution. | |
using System; | |
using System.Collections.Generic; | |
using System.Runtime.ExceptionServices; | |
namespace Tsukasa.Core | |
{ | |
/// <summary> | |
/// ステートマシンの更新処理中に発生した、未処理の例外をどう振る舞うかを表現した列挙型です | |
/// </summary> | |
public enum StateMachineUnhandledExceptionMode | |
{ | |
/// <summary> | |
/// Update関数内で発生した例外をそのまま例外として発生させます。 | |
/// </summary> | |
ThrowException, | |
/// <summary> | |
/// OnUnhandledException ハンドラに転送されます。 | |
/// </summary> | |
CatchException, | |
} | |
/// <summary> | |
/// ステートマシン本体 | |
/// </summary> | |
/// <typeparam name="TContext">このステートマシンで制御するクラス</typeparam> | |
public class StateMachine<TContext> | |
{ | |
/// <summary> | |
/// ステートのスタック | |
/// </summary> | |
protected Stack<State> stateStack; | |
/// <summary> | |
/// サスペンドフラグ。Trueが設定されていると、スタックの巡回を中断して処理を終える。 | |
/// </summary> | |
private bool suspend; | |
/// <summary> | |
/// 未処理の例外が発生した際の振る舞いの設定取得をします | |
/// </summary> | |
public StateMachineUnhandledExceptionMode UnhandledExceptionMode { get; set; } | |
/// <summary> | |
/// ステートマシンが保持しているコンテキスト | |
/// </summary> | |
protected TContext Context { get; private set; } | |
/// <summary> | |
/// 現在のスタックしているステートの数 | |
/// TODO:このプロパティ不要か? | |
/// </summary> | |
public int StackCount => stateStack.Count; | |
/// <summary> | |
/// ステートマシンの Update() 中に未処理の例外が発生した時のイベントハンドラです。 | |
/// ただし UnhandledExceptionMode プロパティに CatchException が設定されている必要があります。 | |
/// false が返されると、例外が未処理と判断され Update() 関数が例外を送出します。 | |
/// </summary> | |
public event Func<Exception, bool> UnhandledException; | |
/// <summary> | |
/// 各ステートの規定クラス | |
/// </summary> | |
public abstract class State | |
{ | |
/// <summary> | |
/// このステートが所属するステートマシン | |
/// </summary> | |
protected internal StateMachine<TContext> StateMachine{private get; set;} | |
/// <summary> | |
/// ステートの更新を処理する | |
/// </summary> | |
protected internal abstract void Update(TContext context); | |
public void PushState(State state) | |
{ | |
StateMachine.PushState(state); | |
} | |
public void EnableSuspend() | |
{ | |
StateMachine.EnableSuspend(); | |
} | |
} | |
/// <summary> | |
/// コンストラクタ | |
/// </summary> | |
/// <param name="context">このステートマシンを所有しているオブジェクト</param> | |
public StateMachine(TContext context) | |
{ | |
// 渡されたコンテキストがnullの場合 | |
if (context is null) | |
{ | |
// nullは許されない | |
throw new ArgumentNullException(nameof(context)); | |
} | |
// メンバの初期化 | |
Context = context; | |
stateStack = new Stack<State>(); | |
UnhandledExceptionMode = StateMachineUnhandledExceptionMode.ThrowException; | |
} | |
/// <summary> | |
/// ステートをスタックする | |
/// </summary> | |
/// <param name="state">スタックするステート</param> | |
public void PushState(State state) | |
{ | |
//ステートマシンコンテキストを設定する | |
state.StateMachine = this; | |
//ステートをスタックにプッシュする | |
stateStack.Push(state); | |
} | |
public void EnableSuspend() | |
{ | |
suspend = true; | |
} | |
/// <summary> | |
/// スタックされたステート群を実行する | |
/// </summary> | |
public void Update() | |
{ | |
try | |
{ | |
//サスペンドフラグを初期化 | |
suspend = false; | |
//ステートがスタックされている限り巡回を続ける | |
while (stateStack.Count > 0) | |
{ | |
//スタックの先頭からステートを除去し、そのステートの処理を実行する。 | |
stateStack.Pop().Update(Context); | |
//サスペンドフラグが立っている(=Update処理を脱出する)なら巡回を終了 | |
if (suspend) break; | |
} | |
} | |
catch (Exception exception) | |
{ | |
//例外発生時のエラーハンドリング | |
DoHandleException(exception); | |
return; | |
} | |
} | |
/// <summary> | |
/// ステートスタックに積まれているすべてのステートを捨てます。 | |
/// TODO:動作未検証 | |
/// </summary> | |
public void ClearStack() | |
{ | |
// スタックを空にする | |
stateStack.Clear(); | |
} | |
#region 内部ロジック系 | |
/// <summary> | |
/// 発生した未処理の例外をハンドリングします | |
/// </summary> | |
/// <param name="exception">発生した未処理の例外</param> | |
/// <exception cref="ArgumentNullException">exception が null です</exception> | |
private void DoHandleException(Exception exception) | |
{ | |
// nullを渡されたら | |
if (exception == null) | |
{ | |
// 何をハンドリングすればよいのか | |
throw new ArgumentNullException(nameof(exception)); | |
} | |
// もし、例外を拾うモード かつ ハンドラが設定されているなら | |
if (UnhandledExceptionMode == StateMachineUnhandledExceptionMode.CatchException && UnhandledException != null) | |
{ | |
// イベントを呼び出して、正しくハンドリングされたのなら | |
if (UnhandledException(exception)) | |
{ | |
// そのまま終了 | |
return; | |
} | |
} | |
// 上記のモード以外(つまり ThrowException)か、例外がハンドリングされなかった(false を返された)のなら例外をキャプチャして発生させる | |
ExceptionDispatchInfo.Capture(exception).Throw(); | |
} | |
#endregion | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
State派生クラスのコンストラクタで未初期化のStatemachineやContextにアクセスできる(できてしまう)のがどうにも納得出来ないのでどちらも出来ないようにした。