Net State Machine
v0.4.0
.NET 用のステート マシン ビルダー ライブラリ。
次の例は、ステート マシンの機能の一部を示しています。
using Enderlook.StateMachine;public class Character{private static StateMachineFactory<States, Events, Character>? Factory;private readonly Random rnd = new();private readonly StateMachine<States, Events, Character> stateMachine;private int health = 100;private int food = 100;private enum States{Sleep,Play,GettingFood,Hunt,Gather,} private enum Events{HasFullHealth,LowHealth,IsHungry,IsNoLongerHungry,}public static async Task Main(){Character Character = new();while (true){Console.Clear();// ステート マシンの更新呼び出しを実行し、それに任意のパラメーターを渡します。// パラメーターは汎用であるため、値の型には割り当てられません。//このパラメータは、シグネチャ内のジェネリック引数型を受け入れるサブスクライブされたデリゲートに渡されます。// パラメータを渡したくない場合は、 .With() メソッド呼び出しを削除できます。// このパラメータ システムは、火イベントメソッド.character.stateMachine.With(character.rnd.NextSingle()).Update();Console.WriteLine($"State: {character.stateMachine.CurrentState}.");Console.WriteLine($"Health: { Character.health}.");Console.WriteLine($"Food: {character.food}.");await Task.Delay(10).ConfigureAwait(false);}}public Character(){// ステート マシンのインスタンスを作成します。stateMachine = GetStateMachineFactory().Create(this); // ステート マシンの初期化にパラメータを渡したい場合は、次のようにすることもできます: // stateMachine = GetStateMachineFactory().With(parameter).Create(this)。 // メソッド `.With(parameter)` は必要なだけ連結できます。 // パターン `stateMachine.With(p1).With(p2)...With(pn).SomeMethod(...)` はメソッド `Fire()`、`FireImmediately()`、および `Update にも有効です()`.}private static StateMachineFactory<States, Events, Character> GetStateMachineFactory(){if (ファクトリが null ではない)returnファクトリー;StateMachineFactory<状態、イベント、文字>? Factory_ = StateMachine<States, Events, Character>// ステート マシンはファクトリから作成され、複数のインスタンスの作成が可能になります。// 計算は 1 回で完了し、作成されたインスタンス間で共有されるため、CPU とメモリの両方でコストがかかります。.CreateFactoryBuilder()//ステート マシンの初期状態を決定します。// 2 番目のパラメーターは、ステート マシンの初期化中に OnEntry デリゲートを実行する方法を決定します。// InitializationPolicy.Ignore SetInitialState(States.Sleep, InitializationPolicy.Ignore)// 状態を設定します。In(States.Sleep)// この状態に入るたびに実行されます。.OnEntry(() => Console .WriteLine("Going to bed."))// この状態を終了するたびに実行されます。.OnExit(() => Console.WriteLine("Getting up."))// 更新メソッド (Update() または With<T>(T).Update()) が実行され、この状態になるたびに実行されます。// すべてのイベントは、受信者を渡すためのオーバーロードを提供します。そのため、具体的なインスタンスの構築中にパラメータ化できます。// 任意の型のパラメータを渡すためのオーバーロードも提供するため、With<T>(T).Update() の呼び出し中にパラメータ化できます。// また、過負荷に受信者と任意の型のパラメータの両方を渡します。// このオーバーロードは、OnEntry(...)、OnExit(...)、If(...)、および Do(...) メソッドにも適用されます。.OnUpdate( @this => @this.OnUpdateSleep()).On(Events.HasFullHealth)// この状態でこのイベントが発生するたびに実行されます..Do(() => Console.WriteLine("Pick Toy."))/ / 新しい状態to transite..Goto(States.Play)// あるいは、遷移中にイベント実行ポリシーを構成することもできます。// 上記のメソッド呼び出しは、:// .OnEntryPolicy(TransitionPolicy.ChildFirstWithCulling).OnExitPolicy(TransitionPolicy.ParentFirstWithCulling) と同等です。 ).Goto(States.Play)..On(Events.IsHungry)// 条件が次の場合にのみ次の呼び出しを実行します。 true..If(@this => @this.IsVeryWounded())// OnEntry も OnExit デリゲートも実行せずに現在の状態に留まります。.StaySelf()// 上記のメソッドは、:// .OnEntryPolicy( TransitionPolicy.Ignore).OnExitPolicy(TransitionPolicy.Ignore).Goto(States.Sleep).// これらのデリゲートを実行したい場合:// .GotoSelf(false)// これは、:// .OnEntryPolicy(TransitionPolicy.ChildFirstWithCullingInclusive).OnExitPolicy(TransitionPolicy.ParentFirstWithCullingInclusive).Goto(States.Sleep).// のショートカットです。// さらに必要な場合は、親状態から遷移デリゲートを実行するには (State.Sleep はサブ状態ではないため、この例では役に立ちません)、次のことができます。 do:// .GotoSelf(true)// これは:// .OnEntryPolicy(TransitionPolicy.ChildFirst).OnExitPolicy(TransitionPolicy.ParentFirst).Goto(States.Sleep)のショートカットです。// それ以外の場合は、次の呼び出しを実行します。条件が true..If(@this => @this.IsWounded()).Goto(States.Gather)// Else を実行無条件に..Goto(States.Hunt)// この遷移ではこのイベントを無視します。// (これを追加せず、誤ってこのイベントを起動すると例外がスローされます)..Ignore(Events.LowHealth)// どちらは://のショートカットです.On(Events.LowHealth).OnEntryPolicy(TransitionPolicy.Ignore).OnExitPolicy(TransitionPolicy.Ignore).Goto(States.Sleep)..In(States.Play).OnUpdate(@this => @this.OnUpdatePlay()) .On(Events.IsHungry).If(@this => @this.IsWounded()).Goto(States.Gather).Goto(States.Hunt).In(States.GettingFood).OnEntry(() => Console.WriteLine("食べ物を探しに行きます。")).OnExit( () => Console.WriteLine("食べ物を買いに行くのはやめてください。")).In(States.Gather)// この状態が別の状態のサブ状態であることを決定します。//これは、親状態の OnUpdate デリゲートも実行されることを意味します。// また、遷移中に構成された OnEntryPolicy および OnExitPolicy によっては、// この状態でサブスクライブされた OnEntry および OnExit デリゲートは、サブ状態の遷移中に実行される可能性があります。..IsSubStateOf(States .GettingFood).OnUpdate((Character @this, float パラメータ) => @this.OnUpdateGather(パラメータ)).On(Events.IsNoLongerHungry).If(@this => @this.IsWounded()).Goto(States.Sleep).Goto(States.Play).On(Events.HasFullHealth) .Goto(States.Hunt).In(States.Hunt).IsSubStateOf(States.GettingFood).OnEntry(() => Console.WriteLine("弓を取る。")).OnExit(() => Console.WriteLine("弓を落とす。")).OnUpdate((Character @this, float パラメータ) => @this.OnUpdateHunt(パラメータ)) .On(Events.IsNoLongerHungry).Goto(States.Sleep).On(Events.LowHealth).Goto(States.Sleep).Finalize();// 連動するのはマルチスレッド状況でのメモリ使用量を減らすのに役立ちます。// これは、ファクトリにはインスタンス間で共通のデータが含まれているためです。// そのため、2 つのインスタンスが 2 つの異なるファクトリから作成される場合、同じファクトリから作成される 2 つのインスタンスよりも多くのメモリを消費します。// Interlocked.CompareExchange(ref ファクトリー、factory_、null);return ファクトリー;}private bool IsVeryWounded() => health <= 50;private bool IsWounded() => health <= 75;private void OnUpdateHunt(float lucky){food += (int)MathF.Round(rnd.Next(8) * lucky);if (food >= 100){food = 100;stateMachine.Fire(Events.IsNoLongerHungry) ; // あるいは、ステート マシンの初期化にパラメータを渡したい場合は、次のようにすることもできます。 // stateMachine.With(paramter).Fire(Events.IsNoLongerHungry);}health -= (int)MathF.Round(rnd.Next(6) * (1 - lucky));if (health <= 20)stateMachine. Fire(Events.LowHealth);}private void OnUpdateGather(float lucky){food += (int)MathF.Round(rnd.Next(3) * 運);if (食べ物 >= 100){食べ物 = 100;stateMachine.Fire(Events.IsNoLongerHungry);}if (rnd.Next(1) % 1 == 0){health++;if (健康 >= 100) {health = 100;stateMachine.Fire(Events.HasFullHealth);}}}private void OnUpdatePlay(){food -= 3;if (food <= 0){food = 0;stateMachine.Fire(Events.IsHungry);}}private void OnUpdateSleep(){health++;if (health >= 100){health = 100;stateMachine.Fire(Events.HasFullHealth) ;}食べ物 -= 2;if (食べ物 <= 0){食べ物 = 0;stateMachine.Fire(Events.IsHungry);}}}
public sealed class StateMachine<TState, TEvent, TRecipient>where TState : notnullwhere TEvent : notnull{/// このステート マシンの現在の (サブ) 状態を取得します。public TState CurrentState { get; }/// 現在の (サブ) 状態とそのすべての親状態階層を取得します。public ReadOnlySlice<TState> CurrentStateHierarchy { get; }/// Get は現在の (サブ)state によるイベントを受け入れます。public ReadOnlySlice<TEvent> CurrentAcceptedEvents { get; }/// ファクトリを作成します builder.public static StateMachineBuilder<TState, TEvent, TRecipient> CreateFactoryBuilder();/// 指定された状態の親状態を取得します。/// 状態がサブ状態でない場合は、false.public bool を返します。 GetParentStateOf(TState state, [NotNullWhen(true)] out TState?parentState);/// 指定された階層の親階層を取得します 州。 state がサブステートでない場合は、empty.public ReadOnlySlice<TState> GetParentHierarchyOf(TState state);/// 指定された state によって受け入れられるイベントを取得します。public ReadOnlySlice<TEvent> GetAcceptedEventsBy(TState state);/// 決定します現在の状態が指定された状態、またはその指定された状態の (ネストされた) サブ状態である場合。public bool IsInState(TState state);/// ステート マシンにイベントを起動します。/// ステート マシンがすでにステートを起動している場合、現在のイベントの完了後に実行するためにキューに入れられます。public void Fire(TEvent @event);///ステート マシンにイベントを発生させます。/// イベントはキューに入れられませんが、以前にキューに入れられたイベントを無視して実際に実行されます。/// このイベントのコールバックの実行中に後続のイベントがキューに入れられた場合、それらのイベントもキューに入れられます。このイベントの完了後に実行される。public void FireImmediately(TEvent @event);/// 現在の状態に登録されている更新コールバックを実行する。public void Update();/// 渡すことができるパラメータを格納するサブスクライブされた delegates.public ParametersBuilder With<T>(T パラメータ);public readonly struct ParametersBuilder{/// callbacks.public ParametersBuilder に渡すことができるパラメータを格納しますWith<TParameter>(TParameter パラメータ);/// 親クラスの Fire(TEvent) と同じですが、サブスクライブされたデリゲートに渡すことができるすべての保存された値が含まれます。public void Fire(TEvent);/// FireImmediately(TEvent) と同じ) 親クラスにありますが、サブスクライブされたデリゲートに渡すことができるすべての格納された値が含まれます。public void FireImmediately(TEvent);/// 親クラスの Update(TEvent) と同じですが、サブスクライブされたデリゲートに渡すことができるすべての格納された値が含まれますサブスクライブされた delegates.public void Update(TEvent);}public readonly struct InitializeParametersBuilder{/// callbacks.public InitializeParametersBuilder に渡すことができるパラメータを格納します With<TParameter>(TParameter パラメータ);/// ステート マシンを作成します.public StateMachine<TState, TEvent, TRecipient> Create(TRecipient 受信者);}}パブリック シール クラスStateMachineFactory<TState, TEvent, TRecipient>where TState : notnullwhere TEvent : notnull{/// この Factory によって提供される構成を使用して、構成および初期化されたステート マシンを作成します。public StateMachine<TState, TEvent, TRecipient> Create(TRecipient 受信者);/ // サブスクライブされたデリゲートに渡すことができるパラメータを格納します。public StateMachine<TState, TEvent, TRecipient>.InitializeParametersBuilder With<T>(T パラメーター);}public sealed class StateMachineBuilder<TState, TEvent, TRecipient> : IFinalizablewhere TState : notnullwhere TEvent : notnull{/// ステート マシンの初期状態を決定します。// / `initializationPolicy` は、デリゲートを OnEntry オーブンにどのようにサブスクライブするかを決定します。指定された状態 (および親状態) は、状態マシンの初期化中に実行されます。public StateMachineBuilder<TState, TEvent, TRecipient> SetInitialState(TState state, ExecutionPolicy initializationPolicy = ExecutionPolicy.ChildFirst);/// 新しい状態を追加するか、以前に追加された state.public StateBuilder<TState, TEvent, TRecipient> In(TState state);/// builder.public StateMachineFactory<TState, TEvent, TRecipient> Finalize();}public sealed class StateBuilder<TState, TEvent, TRecipient> : IFinalizablewhere TState : notnullwhere TEvent : notnull{/// 呼び出しを設定として使用してファクトリを作成します。 StateMachineBuilder<TState、TEvent、 TRecipient>.In(TState state).public StateBuilder<TState, TEvent, TRecipient> In(TState state);/// 呼び出しを StateMachineBuilder<TState, TEvent, TRecipient>.Finalize();public StateMachineFactory<TState, TEvent, に転送します。 TRecipient> Finalize();/// この状態を指定された state.public のサブ状態としてマークします。 StateBuilder<TState, TEvent, TRecipient> IsSubStateOf(TState state);/// この状態へのエントリ時に実行するアクションを決定します。public StateBuilder<TState, TEvent, TRecipient> OnEntry(Action action);/// OnEntry(と同じ)アクション)ただし、受信者をparameter.public StateBuilder<TState, TEvent, TRecipient>として渡します。 OnEntry(Action<TRecipient> action);/// OnEntry(Action) と同じですが、呼び出し中に渡されたジェネリック パラメータの型と一致するパラメータをデリゲートに渡します。/// 指定されたジェネリック パラメータで渡されたパラメータが見つからない場合、無視されます。public StateBuilder<TState, TEvent, TRecipient> OnEntry<TParameter>(Action<TParameter> action);/// の結合バージョンOnEntry(Action<TRecipient>) および OnEntry(Action<TParameter>).public StateBuilder<TState, TEvent, TRecipient> OnEntry<TParameter>(Action<TRecipient, TParameter> action);/// 終了時に実行するアクションを決定します。 this state.public StateBuilder<TState, TEvent, TRecipient> OnExit(Action action);/// と同じOnExit(Action) ただし、受信者をparameter.public StateBuilder<TState, TEvent, TRecipient> OnExit(Action<TRecipient> action);/// OnExit(Action)と同じですが、呼び出し中に渡されたパラメータをデリゲートに渡します。ジェネリック パラメーターのタイプと一致します。/// 指定されたジェネリック パラメーターとともに渡されたパラメーターが見つからない場合は、無視されます。public StateBuilder<TState, TEvent, TRecipient> OnExit<TParameter>(Action<TParameter> action);/// OnExit(Action<TRecipient>) と OnExit(Action<TParameter>) を組み合わせたバージョン.public StateBuilder<TState, TEvent, TRecipient> OnExit<TParameter>(Action< TRecipient, TParameter> action);/// この state.public StateBuilder<TState, の更新時に実行するアクションを決定します。 TEvent, TRecipient> OnUpdate(Action action);/// OnUpdate(Action) と同じですが、受信者をparameter.public StateBuilder<TState, TEvent, TRecipient> OnUpdate(Action<TRecipient> action);/// OnUpdate と同じ(アクション) ただし、汎用パラメーター type.public StateBuilder<TState, TEvent, TRecipient> に一致する、呼び出し中に渡されたパラメーターをデリゲートに渡します。 OnUpdate<TParameter>(Action<TParameter> action);/// OnUpdate(Action<TRecipient>) と OnUpdate(Action<TParameter>) を組み合わせたバージョン。/// 指定されたジェネリック パラメーターとともに渡されたパラメーターが見つからない場合は、 ignored.public StateBuilder<TState, TEvent, TRecipient> OnUpdate<TParameter>(Action<TRecipient, TParameter> action);/// 実行中に実行される動作を追加します。指定されたイベントの発生.public TransitionBuilder<TState, TEvent, TRecipient, StateBuilder<TState, TEvent, TRecipient>> On(TEvent @event);/// 指定されたイベントを無視します。/// イベントに動作が追加されない場合そして発射されると投げます。これにより、呼び出しをすべて無視することによってスローが防止されます。public StateBuilder<TState, TEvent, TRecipient> Ignore(TEvent @event);}public sealed class TransitionBuilder<TState, TEvent, TRecipient, TParent> : IFinalizable, ITransitionBuilder<TState>where TState : notnullwhere TEvent : notnull{/// デリゲートが戻ったときに実行されるサブトランジションを追加しますtrue.public TransitionBuilder<TState, TEvent, TRecipient, TransitionBuilder<TState, TEvent, TRecipient, TParent>> If(Func<bool> Guard);/// If(Func<bool>) と同じですが、受信者をパラメーターとして渡します。 public TransitionBuilder<TState, TEvent, TRecipient, TransitionBuilder<TState, TEvent, TRecipient, TParent>> If(Func<TRecipient, bool> Guard);/// If(Func<bool>) と同じですが、汎用パラメーター type.public TransitionBuilder<TState, TEvent, TRecipient, と一致する、呼び出し中に渡されたパラメーターをデリゲートに渡します。 TransitionBuilder<TState, TEvent, TRecipient, TParent>> If<TParameter>(Func<TParameter, bool> Guard);/// の結合バージョンIf(Func<TRecipient, bool>) および If(Func<TParameter, bool>).public TransitionBuilder<TState, TEvent, TRecipient, TransitionBuilder<TState, TEvent, TRecipient, TParent>> If<TParameter>(Func<TParameter, bool> > Guard);/// イベントが発生したときに実行するアクションを決定します。public TransitionBuilder<TState, TEvent, TRecipient, TParent> Do(Action action);/// Do(Action) と同じですが、受信者をparameter.public TransitionBuilder<TState, TEvent, TRecipient, TParent> Do(Action<TRecipient> action);/// 同じですDo(Action) として実行しますが、呼び出し中に渡されたパラメータのうち、ジェネリック パラメータの型と一致するパラメータをデリゲートに渡します。/// 指定されたジェネリック パラメータで渡されたパラメータが見つからない場合は、それが返されます。 ignored.public TransitionBuilder<TState, TEvent, TRecipient, TParent> Do<TParameter>(Action<TParameter> action);/// Do(Action<TRecipient>) と Do(Action<TParameter>) を組み合わせたバージョン.public TransitionBuilder< TState, TEvent, TRecipient, TParent> Do<TParameter>(Action<TRecipient, TParameter> アクション);///エントリフックでサブスクライブされたデリゲートをどのように実行するかについてのポリシーを構成します。 /// このメソッドが実行されない場合、デフォルトのポリシーは TransitionPolicy.ParentFirstWithCulling.public GotoBuilder<TState, TEvent, TRecipient, TParent> OnEntryPolicy(TransitionPolicypolicy); です。 /// 終了フックでサブスクライブされたデリゲートがどのように実行されるかに関するポリシーを構成します。/// このメソッドがそうでない場合実行された場合、デフォルトのポリシーは TransitionPolicy.ChildFirstWithCulling.public GotoBuilder<TState, TEvent, TRecipient, TParent> OnExitPolicy(TransitionPolicypolicy);/// この遷移がどの状態に移行するかを決定します。/// これは次と同等です。 OnEntryPolicy(TransitionPolicy.ChildFirstWithCulling).OnExitPolicy(TransitionPolicy.ParentFirstWithCulling).Goto(state).public TParent Goto(TState state);/// 現在の状態に遷移することを決定します。 /// runParentsActions が true の場合: OnExit アクションと OnEntry アクション現在の状態 (ただし、現在の状態がサブ状態の場合は親状態ではありません) は次のようになります。実行されました。/// これは、OnEntryPolicy(TransitionPolicy.ChildFirstWithCullingInclusive).OnExitPolicy(TransitionPolicy.ParentFirstWithCullingInclusive).Goto(currentState) と同等です。/// runParentActions が false の場合: 現在の状態の OnExit および OEntry アクション (および、次の場合は親)現在の状態はサブ状態です) が実行されます。/// これは次と同等です。 OnEntryPolicy(TransitionPolicy.ChildFirst).OnExitPolicy(TransitionPolicy.ParentFirst).Goto(currentState).public TParent GotoSelf(bool runParentsActions = false);/// どの状態にも遷移しないことを決定するため、OnEntry イベントも OnExit イベントも発生しません。発生しました。/// これは次と同等です。 OnEntryPolicy(TransitionPolicy.Ignore).OnExitPolicy(TransitionPolicy.Ignore).GotoSelf().public TParent StaySelf();} public sealed class GotoBuilder<TState, TEvent, TRecipient, TParent> : IGoto<TState>where TState : notnullwhere TEvent : notnull {/// 方法のポリシーを構成しますエントリ フックでサブスクライブされたデリゲートが実行される必要があります。/// このメソッドが実行されない場合、デフォルトのポリシーは TransitionPolicy.ParentFirstWithCulling.public GotoBuilder<TState, TEvent, TRecipient, TParent> OnEntryPolicy(TransitionPolicy ポリシー);///出口フックでサブスクライブされたデリゲートがどのように実行されるべきかのポリシー。/// このメソッドが実行されない場合、デフォルトのポリシーは次のとおりです。 TransitionPolicy.ChildrenFirstWithCulling.public GotoBuilder<TState, TEvent, TRecipient, TParent> OnExitPolicy(TransitionPolicypolicy);/// この遷移がどの状態に移行するかを決定します。public TParent Goto(TState state);/// 現在の状態に遷移することを決定します./// これは Goto(currentState).public TParent のショートカットですGotoSelf();}/// 2 つの状態間の遷移ポリシーを決定します。/// これは、状態間の遷移中に状態でサブスクライブされたデリゲートがどのように実行されるかを構成します。public enum TransitionPolicy{/// サブスクライブされたデリゲートが実行されないことを決定します。Ignore = 0,/// 親のサブスクライブされたデリゲートが最初に実行されることを決定します。ParentFirst = 1,/// 子のサブスクライブされたデリゲートが実行されることを決定しますfirst.ChildFirst = 2,/// 親のサブスクライブされたデリゲートが、2 つの状態間の最後の共通の親から (除外されて) 最初に実行されることを決定します。ParentFirstWithCulling = 3,/// 子のサブスクライブされたデリゲートが、到達するまで最初に実行されることを決定します ( 2 つの状態間の最後の共通の親を除く).ChildFirstWithCulling = 4,/// 親のサブスクライブされたデリゲートが、最後の共通の親 (を含む) から最初に実行されることを決定します。 between the two states.ParentFirstWithCullingInclusive = 5,/// 2 つの状態間の最後の共通の親 (を含む) に到達するまで、子のサブスクライブされたデリゲートが最初に実行されることを決定します。ChildFirstWithCullingInclusive = 6,}/// data.public のスライスを表します。 readonly struct ReadOnlySlice<T> : IReadOnlyList<T>{/// Index で指定された要素を取得します。public T this[int index] { get; }/// スライスのカウントを取得します。public int Count { get; }/// このスライスの <see cref="ReadOnlyMemory{T}"/> を取得します。public ReadOnlyMemory<T> Memory { get; }/// このスライスの <see cref="ReadOnlySpan{T}"/> を取得します。public ReadOnlySpan<T> Span { get; }/// スライスの列挙子を取得します。public Enumerator GetEnumerator();/// <see cref="ReadOnlySlice{T}"/>.public struct の列挙子 Enumerator : IEnumerator<T>{/// 現在の要素を取得しますenumerator の.public T Current { get; }/// enumeration.public bool の次の要素に移動します MoveNext();/// enumeration.public をリセットします void Reset();}}