Net State Machine
v0.4.0
.NET 的状态机构建器库。
以下示例显示了状态机的一些功能。
使用 Enderlook.StateMachine;public class Character{private static StateMachineFactory<States, Events, Character>?工厂;私有只读随机 rnd = new();私有只读 StateMachine<状态、事件、字符> stateMachine;私有 int health = 100;私有 int food = 100;私有枚举 States{Sleep,Play,GettingFood,Hunt,Gather,}私有枚举事件{HasFullHealth,LowHealth,IsHungry,IsNoLongerHungry,}公共静态异步任务Main(){Character character = new();while (true){Console.Clear();//执行状态机的更新调用并向其传递任意参数。//参数是通用的,因此它不是在值类型上分配。 // 该参数被传递给订阅的委托,该委托在其签名中接受通用参数类型。 // 如果您不想传递参数,您可以删除 .With() 方法调用。 // 这也可以使用参数系统带有火灾事件的methods.character.stateMachine.With(character.rnd.NextSingle()).Update();Console.WriteLine($"State: {character.stateMachine.CurrentState}.");Console.WriteLine($"Health : {character.health}.");Console.WriteLine($"食物: {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 (factory is not null)return factory;StateMachineFactory<States, Events,性格>? factory_ = StateMachine<States, Events, Character>// 状态机是从工厂创建的,这使得创建多个实例 // 在 CPU 和内存方面都更便宜,因为计算只完成一次并在创建的实例之间共享..CreateFactoryBuilder()//确定状态机的初始状态。 // 第二个参数确定 OnEntry 委托在状态机初始化期间应该如何执行, // InitializationPolicy.Ignore 表示不应该执行它们run..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("Picktoys."))// 要转换的新状态..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 ,这在本例中没有用)不是子状态)我们可以这样做:// .GotoSelf(true)// 这是快捷方式of:// .OnEntryPolicy(TransitionPolicy.ChildFirst).OnExitPolicy(TransitionPolicy.ParentFirst).Goto(States.Sleep).// 如果条件为 true,则执行下一个调用..If(@this => @this.IsWounded ()).Goto(States.Gather)// 否则无条件执行..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("Stop go for food.")).In(States.Gather)// 确定此状态是另一个状态的子状态。 // 这意味着父状态中的 OnUpdate 委托也将被运行。// 同样取决于转换期间配置的 OnEntryPolicy 和 OnExitPolicy,// 该状态下订阅的 OnEntry 和 OnExit 委托可能会在子状态转换期间运行。.IsSubStateOf(States.GettingFood) .OnUpdate((字符@this,浮点参数)=> @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((字符@this,浮动参数) => @this.OnUpdateHunt(parameter)).On(Events.IsNoLongerHungry).Goto(States.Sleep).On(Events.LowHealth).Goto(States.Sleep).Finalize();// 互锁对于减少内存很有用在多线程情况下使用。 // 这是因为工厂包含实例之间的公共数据, // 因此如果从两个不同的工厂创建两个实例,它将比两个实例消耗更多的内存 //从同一工厂创建的实例.Interlocked.CompareExchange(reffactory,factory_,null);returnfactory;}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 - 运气));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 (health >= 100){ health = 100;stateMachine.Fire(Events.HasFullHealth);}}}private void OnUpdatePlay(){food -= 3;if (food <= 0){食物 = 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 seal class StateMachine<TState, TEvent, TRecipient>where TState : notnullwhere TEvent : notnull{// 获取此状态机的当前(子)状态。 public TState CurrentState { get; }/// 获取当前(子)状态及其所有父状态层次结构。 public ReadOnlySlice<TState> CurrentStateHierarchy { get; }/// Get 按当前(子)状态接受事件。public ReadOnlySlice<TEvent> CurrentAcceptedEvents { get; }/// 创建工厂 builder.public static StateMachineBuilder<TState, TEvent, TRecipient> CreateFactoryBuilder();/// 获取指定状态的父状态。/// 如果 state 不是子状态,则返回 false.public bool GetParentStateOf(TState state, [NotNullWhen(true)] out TState?parentState);/// 获取指定状态的父层次结构。如果state不是子状态,则返回空。 public ReadOnlySlice<TState> GetParentHierarchyOf(TState state);/// 获取指定state接受的事件。 public ReadOnlySlice<TEvent> GetAcceptedEventsBy(TState state);/// 确定如果当前状态是指定状态或指定状态的(嵌套)子状态。public bool IsInState(TState state);/// 触发/// 如果状态机已经在触发一个状态,则它会排队等待当前事件完成后运行。public void Fire(TEvent @event);/// 向状态机触发一个事件。 /// 该事件不会入队,但实际运行,忽略之前入队的事件。 /// 如果后续事件在执行该事件的回调期间入队,则它们也将在本次执行完成后运行event.public void FireImmediately(TEvent @event);/// 执行当前状态中注册的更新回调。public void Update();/// 存储可以传递给订阅委托的参数。publicParametersBuilderWith <T>(Tparameter);public readonly structParametersBuilder{//存储可以传递给callbacks的参数。publicParametersBuilderWith<TParameter>(TParameterparameter);///与Fire(TEvent)相同位于父类中,但包含所有可传递给订阅委托的存储值。 public void Fire(TEvent);/// 与父类中的 FireImmediately(TEvent) 相同,但包含所有可传递给订阅委托的存储值。 public void FireImmediately(TEvent);/// 与父类中的 Update(TEvent) 相同,但包含可以传递给订阅委托的所有存储值。public void Update(TEvent);}public readonly struct InitializeParametersBuilder{/// 存储可以传递给回调的参数。public InitializeParametersBuilder With<TParameter>(TParameterparameter);/// 创建状态机。public StateMachine<TState, TEvent, TRecipient> Create(TRecipientrecipient);} }public seal class StateMachineFactory<TState, TEvent, TRecipient>where TState : notnullwhere TEvent : notnull{// 创建一个已配置的并使用此工厂提供的配置初始化状态机。public StateMachine<TState, TEvent, TRecipient> Create(TRecipientrecipient);/// 存储可以传递给订阅委托的参数。public StateMachine<TState, TEvent , TRecipient>.InitializeParametersBuilder With<T>(T 参数);}公共密封类 StateMachineBuilder<TState, TEvent, TRecipient> : IFinalizedwhere TState : notnullwhere TEvent : notnull{// 确定状态机的初始状态。/// `initializationPolicy` 确定在初始化期间如何运行指定状态(和父状态)的 OnEntry 烤箱的订阅委托状态机.public StateMachineBuilder<TState, TEvent, TRecipient> SetInitialState(TState state, ExecutionPolicy初始化策略= ExecutionPolicy.ChildFirst);/// 添加新状态或加载之前添加的 state.public StateBuilder<TState, TEvent, TRecipient> In(TState state);/// 使用 builder.public StateMachineFactory< 的配置创建一个工厂TState, TEvent, TRecipient> Finalize();}公共密封类 StateBuilder<TState, TEvent, TRecipient> : IFinalizedwhere 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();/// 将此状态标记为指定状态的子状态。public StateBuilder<TState, TEvent, TRecipient> IsSubStateOf(TState state);/// 确定进入此状态时要执行的操作。public StateBuilder< TState, TEvent, TRecipient> OnEntry(Action action);/// 与 OnEntry(Action) 相同,但将接收者作为参数传递。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) 相同但传递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);// / 确定更新此状态时要执行的操作。public StateBuilder<TState, TEvent, TRecipient> OnUpdate(Action action);/// 与OnUpdate(Action) 但将接收者作为参数传递。public StateBuilder<TState, TEvent, TRecipient> OnUpdate(Action<TRecipient> action);/// 与 OnUpdate(Action) 相同,但将调用期间传递的任何参数传递给委托,其中匹配泛型参数类型。public StateBuilder<TState, TEvent, TRecipient> OnUpdate<TParameter>(Action<TParameter> action);/// 的组合版本OnUpdate(Action<TRecipient>) 和 OnUpdate(Action<TParameter>)./// 如果没有找到指定泛型参数传递的参数,则忽略该参数。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 seal class TransitionBuilder<TState, TEvent, TRecipient, TParent> : IFinalized, 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>) 但将调用期间传递的与泛型参数类型匹配的任何参数传递给委托。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) 相同,但将接收者作为参数传递。public TransitionBuilder<TState, TEvent, TRecipient, TParent> Do(Action<TRecipient> action);/// 与 Do(Action) 相同,但将调用期间传递的与通用参数类型匹配的任何参数传递给委托。/// If找不到使用指定泛型参数传递的参数,它被忽略。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> action);/// 配置策略应该如何执行入口钩子上的订阅委托。/// 如果不执行此方法,则默认策略为 TransitionPolicy.ParentFirstWithCulling.public GotoBuilder<TState, TEvent, TRecipient, TParent> OnEntryPolicy(TransitionPolicy policy);/// 配置退出钩子上订阅的委托应如何执行的策略。/// 如果不执行此方法,则默认策略为 TransitionPolicy。 ChildFirstWithCulling.public GotoBuilder<TState, TEvent, TRecipient, TParent> OnExitPolicy(TransitionPolicy) policy);/// 确定此转换进入哪个状态。/// 这相当于: 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();}公共密封类 GotoBuilder<TState, TEvent, TRecipient, TParent> : IGoto<TState>where TState : notnullwhere TEvent : notnull {// 配置入口钩子上订阅委托的策略/// 如果不执行该方法,则默认策略为 TransitionPolicy.ParentFirstWithCulling.public GotoBuilder<TState, TEvent, TRecipient, TParent> OnEntryPolicy(TransitionPolicy policy);/// 配置退出时订阅委托的策略hook应该被执行。///如果不执行该方法,则默认策略为TransitionPolicy.ChildrenFirstWithCulling.public GotoBuilder<TState, TEvent, TRecipient, TParent> OnExitPolicy(TransitionPolicy policy);/// 确定此转换进入哪个状态。public TParent Goto(TState state);/// 确定转换到当前状态。/// This是 Goto(currentState).public TParent GotoSelf();}/// 确定两个状态之间的转换策略。/// 这配置了如何状态上的订阅委托在状态之间的转换期间运行。public enum TransitionPolicy{/// 确定订阅的委托不应运行。Ignore = 0,/// 确定父级上的订阅委托首先运行。ParentFirst = 1,///确定首先运行子级上的订阅委托。ChildFirst = 2,/// 确定父级上的订阅委托首先从(不包括)两者之间的最后一个公共父级运行states.ParentFirstWithCulling = 3,/// 确定首先运行子级上的订阅委托,直到到达(排除)两个状态之间的最后一个公共父级。ChildFirstWithCulling = 4,/// 确定首先运行父级上的订阅委托 from (包括)两个状态之间的最后一个公共父级。ParentFirstWithCullingInclusive = 5,/// 确定首先运行子级上的订阅委托,直到到达(包括)最后一个公共父级介于两种状态之间。ChildFirstWithCullingInclusive = 6,}/// 表示一个数据切片。public readonly struct ReadOnlySlice<T> : IReadOnlyList<T>{// 获取索引处指定的元素。public T this[int index] { 得到; }/// 获取切片的计数。 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>{/// 获取当前元素枚举器的 public T Current { get; }/// 移动到枚举的下一个元素.public bool MoveNext();/// 重置枚举.public void Reset();}}