Skip to content

Instantly share code, notes, and snippets.

@t-tutiya

t-tutiya/StateMachine.cs

Last active Apr 15, 2021
Embed
What would you like to do?
// 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>
public 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
{
// メンバ変数定義
private StateMachine<TContext> stateMachine;
/// <summary>
/// このステートが所属するステートマシン
/// ※Stateのコンストラクタ実行時には初期化されていないので注意
/// </summary>
protected internal StateMachine<TContext> StateMachine
{
get
{
return stateMachine ?? throw new NullReferenceException("ステートがステートマシンに登録されるより前にステートマシンあるいはコンテキストが参照されました");
}
set { stateMachine = value; }
}
/// <summary>
/// このステートが所属するステートマシンを所持しているオブジェクト
/// ※Stateのコンストラクタ実行時には初期化されていないので注意
/// </summary>
protected internal TContext Context => StateMachine.Context;
/// <summary>
/// ステートの更新を処理する
/// </summary>
protected internal abstract void Update();
}
/// <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);
}
/// <summary>
/// 複数のステートをスタックする
/// </summary>
/// <param name="stateList">ステートリスト</param>
public void PushStateArray(State[] stateArray)
{
for (int i = stateArray.Length - 1; i >= 0; i--)
{
Console.Write(stateArray[i]);
}
}
public void EnableSuspend()
{
suspend = true;
}
/// <summary>
/// スタックされたステート群を実行する
/// </summary>
public void Update()
{
try
{
//サスペンドフラグを初期化
suspend = false;
//ステートがスタックされている限り巡回を続ける
while (stateStack.Count > 0)
{
//スタックの先頭からステートを除去し、そのステートの処理を実行する。
stateStack.Pop().Update();
//サスペンドフラグが立っている(=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