デリゲートパターンなStateMachine
コガネブログさんのStateMachineを一部付け加えた物です。
付け加えた所
現時点のStateのKeyを取得出来るようにした。
- たまに外部からStateMachineの状態を知りたくなる時があった。
ジェネリックに型制約を付けた。
- 過去のプロジェクトでStateMachineのキーにEnumしか使った事がなかったので、型制約を付けた。
- Enum以外をキーとして使うStateMachineを使うパターンってあるのかな・・?
Stateの切り替え時に発生するGCを無くした。
- EnumをキーとするDictionaryにアクセスすると、GCが発生するのでそれを無くすようにした。
Stateの切替時にコールバックを発火出来るようにした。
- たまにステートの切替時に、専用の処理が必要だった場合があった。
コード
StateMachine.cs
using System;
using System.Collections.Generic;
using System.Linq.Expressions;
/// <summary>
/// ステートマシン
/// ジェネリックのパラメーターはEnumを指定して下さい
/// </summary>
public class StateMachine<T> : IEqualityComparer<T> where T : struct
{
/// <summary>
/// ステート
/// </summary>
private class State
{
private readonly Action mEnterAct; // 開始時に呼び出されるデリゲート
private readonly Action mUpdateAct; // 更新時に呼び出されるデリゲート
private readonly Action mExitAct; // 終了時に呼び出されるデリゲート
/// <summary>
/// コンストラクタ
/// </summary>
public State(Action enterAct = null, Action updateAct = null, Action exitAct = null)
{
mEnterAct = enterAct ?? delegate { };
mUpdateAct = updateAct ?? delegate { };
mExitAct = exitAct ?? delegate { };
}
/// <summary>
/// 開始します
/// </summary>
public void Enter()
{
mEnterAct();
}
/// <summary>
/// 更新します
/// </summary>
public void Update()
{
mUpdateAct();
}
/// <summary>
/// 終了します
/// </summary>
public void Exit()
{
mExitAct();
}
}
private Dictionary<T, State> mStateTable; // ステートのテーブル
private Dictionary<T, Dictionary<T, Action>> mSwitchTable;
private State mCurrentState; // 現在のステート
public T currentState { get; private set; }
public StateMachine()
{
System.Type type = typeof(T);
UnityEngine.Debug.AssertFormat(type.IsEnum, "ステートのKeyにEnumではない型が指定されています");
mStateTable = new Dictionary<T, State>(this);
mSwitchTable = new Dictionary<T, Dictionary<T, Action>>(this);
}
/// <summary>
/// ステートを追加します
/// </summary>
public void Add(T key, Action enterAct = null, Action updateAct = null, Action exitAct = null)
{
mStateTable.Add(key, new State(enterAct, updateAct, exitAct));
}
public void AddSwitchCallBack(T prevState, T nextState, Action switchAct)
{
// すでにprevStateとnextStateの組み合わせでコールバックが仕込まれていたら
// prevStateとnextStateの組み合わせのコールバックは1つのみ
if (mSwitchTable.ContainsKey(prevState) && mSwitchTable[prevState].ContainsKey(nextState))
{
UnityEngine.Debug.LogError(prevState.ToString() + " - " + nextState.ToString() + "には既にSwitchコールバックが登録されています。");
return;
}
if (!mSwitchTable.ContainsKey(prevState))
{
mSwitchTable.Add(prevState, new Dictionary<T, Action>(this));
}
mSwitchTable[prevState].Add(nextState, switchAct);
}
/// <summary>
/// 現在のステートを設定します
/// </summary>
public void SetState(T key)
{
if (mCurrentState != null)
{
mCurrentState.Exit();
}
if (mSwitchTable.ContainsKey(currentState) && mSwitchTable[currentState].ContainsKey(key))
{
mSwitchTable[currentState][key]();
}
mCurrentState = mStateTable[key];
currentState = key;
mCurrentState.Enter();
}
/// <summary>
/// 現在のステートを更新します
/// </summary>
public void Update()
{
if (mCurrentState == null)
{
return;
}
mCurrentState.Update();
}
/// <summary>
/// すべてのステートを削除します
/// </summary>
public void Clear()
{
mStateTable.Clear();
mCurrentState = null;
}
public bool Equals(T x, T y)
{
return CastTo<int>.From(x) == CastTo<int>.From(y);
}
public int GetHashCode(T obj)
{
return CastTo<int>.From(obj);
}
}
public static class CastTo<T>
{
private static class Cache<S>
{
static Cache()
{
// 次のラムダ式を式木で構築
// (S s) => (T)s
var p = Expression.Parameter(typeof(S), "target");
var c = Expression.ConvertChecked(p, typeof(T));
Caster = Expression.Lambda<Func<S, T>>(c, p).Compile();
}
internal static readonly Func<S, T> Caster;
}
public static T From<S>(S source)
{
return Cache<S>.Caster(source);
}
}