4
6

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

デリゲートで組まれたStateMachine

Last updated at Posted at 2018-06-19

デリゲートパターンな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);
	}
}

引用元

コンパイラをごまかすキャスト
【C#】デリゲートを使用したステートパターン

4
6
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
6

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?