はじめに
ステートマシン実装の決定版ImtStateMachineについて語り尽くすという記事をたまたま見つけたので, 興味本位で自作のものと比較したくなりました.
できたもの>BoringSM
コンセプトは次のひとつです.
- switch文に毛の生えた程度で十分で, その方が使いやすいです.
- マルチスレッドには対応しない. オブジェクトの持っている状態をマルチスレッドで更新したい状況が設計ミスの匂いがします.
- 階層化など高度な機能はいらない. オブジェクトを階層化すればよろしいと思います.
- ランタイムにエラーを出さない. ただし, アサーションで頑張る.
パフォーマンス計測
元記事がUnityなので, 習って同じようにしてみました.
普通は, 親の変数を更新したりしたいと思うのですが, どうするのでしょう?いちいち参照を渡すのでしょうか.
ImtStateMachine
using UnityEngine;
using IceMilkTea.Core;
public class TestStateMachine : MonoBehaviour
{
private class IdleState : ImtStateMachine<TestStateMachine>.State
{
}
private class ProcessState : ImtStateMachine<TestStateMachine>.State
{
protected internal override void Enter()
{
}
protected internal override void Update()
{
//Debug.Log("ProcessState::Update");
}
protected internal override void Exit()
{
}
protected internal override bool Error(System.Exception exception)
{
return true;
}
protected internal override bool GuardEvent(int eventId)
{
if (!Context.isActiveAndEnabled)
{
return true;
}
return false;
}
protected internal override bool GuardPop()
{
if (!Context.isActiveAndEnabled)
{
return true;
}
return false;
}
}
public enum StateEventId
{
Finish,
Reset,
};
private ImtStateMachine<TestStateMachine> stateMachine_;
void Start()
{
stateMachine_ = new ImtStateMachine<TestStateMachine>(this);
stateMachine_.AddTransition<IdleState, ProcessState>((int)StateEventId.Finish);
stateMachine_.AddTransition<ProcessState, IdleState>((int)StateEventId.Reset);
stateMachine_.SetStartState<ProcessState>();
}
void Update()
{
for (int i = 0; i < 1000; ++i)
{
stateMachine_.Update();
}
}
}
enum名に接尾語, "_Init", "_Proc", "_Term"を付けたメソッドをコンストラクタで検索しています. なくてもいいです. state_.set(State.Process);
も不要です, 元記事がそうしていたからです.
BoringSM
using UnityEngine;
public class TestBoringSM : MonoBehaviour
{
private enum State
{
Idle,
Process,
};
private BoringSM.BoringSM<TestBoringSM, State> state_;
private void Start()
{
state_ = new BoringSM.BoringSM<TestBoringSM, State>(this);
state_.set(State.Process);
}
private void Update()
{
for (int i = 0; i < 1000; ++i)
{
state_.update();
}
}
private void Process_Proc()
{
//Debug.Log("Process_Proc");
}
}
結果
純粋にデリゲートと仮想関数の呼び出しを比較してないですし, 例外を補足する必要もなければ, よけいなスレッドIDのチェックもしなくていいので速いはずです.
まとめ
事前に遷移を決めておく理由て何なんでしょうか. 少なくともそれを理由に不具合の多寡が生まれた経験は私にはないです.
欠点はコンストラクタでstringの生成とリフレクションを使うので, 遅い&メモリ確保があることと, デリゲートのために呼び出し履歴が分かりにくいことです.