Net State Machine
v0.4.0
Библиотека построителя конечных автоматов для .NET.
В следующем примере показаны некоторые функции конечного автомата.
используя Enderlook.StateMachine; общедоступный класс символов {частный статический StateMachineFactory <Состояния, События, Символ>? фабрика;частное только для чтения Random rnd = new();частное только для чтения StateMachine<Состояния, События, Персонаж> stateMachine;частное int health = 100;частное int food = 100;частное перечисление States{Sleep,Play,GettingFood,Hunt,Gather,} частное перечисление Events{HasFullHealth,LowHealth,IsHungry,IsNoLongerHungry,}public static async Task Main(){Charactercharacter = 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<Состояния, События, Символ> GetStateMachineFactory(){if (фабрика не равна нулю)return фабрика;StateMachineFactory<Состояния, События, Персонаж>? Factory_ = StateMachine<States, Events, Character>// Конечные автоматы создаются на фабриках, что делает создание нескольких экземпляров // дешевле как в процессоре, так и в памяти, поскольку вычисления выполняются один раз и распределяются между созданными экземплярами. CreateFactoryBuilder()// Определяет начальное состояние конечного автомата. // Второй параметр определяет, как должны выполняться делегаты OnEntry во время инициализации конечного автомата, // InitializationPolicy.Ignore означает, что их не следует выполнять. run..SetInitialState(States.Sleep, InitializationPolicy.Ignore)// Настраивает состояние..In(States.Sleep)// Выполняется каждый раз, когда мы входим в это состояние..OnEntry(() => Console.WriteLine("Going спать."))// Выполняется каждый раз, когда мы выходим из этого состояния..OnExit(() => Console.WriteLine("Вставание."))// Выполняется каждый раз метод обновления (либо Update(), либо With<T>(T).Update()) выполняется и находится в этом состоянии. // Все события обеспечивают перегрузку для передачи получателю, поэтому его можно параметризовать во время сборки конкретных экземпляров. .// Также предоставляет перегрузку для передачи параметра произвольного типа, поэтому его можно параметризовать во время вызова With<T>(T).Update().// Также предоставляет перегрузку для передачи как получателя, так и параметра произвольный тип. // Эта перегрузка также применима к Методы OnEntry(...), OnExit(...), If(...) и Do(...)..OnUpdate(@this => @this.OnUpdateSleep()).On(Events.HasFullHealth) // Выполняется каждый раз, когда это событие запускается в этом состоянии..Do(() => Console.WriteLine("Pick Toys."))// Новое состояние для перехода..Goto(States.Play)// Альтернативно, вы могу настроить событие политика выполнения во время перехода.// Вышеупомянутый вызов метода эквивалентен::// .OnEntryPolicy(TransitionPolicy.ChildFirstWithCulling).OnExitPolicy(TransitionPolicy.ParentFirstWithCulling).Goto(States.Play)..On(Events.IsHungry)// Только выполнить следующий вызов, если условие истинно..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)// Это ярлык::// .OnEntryPolicy(TransitionPolicy.ChildFirst).OnExitPolicy(TransitionPolicy.ParentFirst).Goto(States.Sleep).// В противном случае выполните следующий вызов, если условие истинно..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("Хватит ходить за едой.")).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(параметр)).On(Events.IsNoLongerHungry).Goto(States.Sleep).On(Events.LowHealth).Goto( States.Sleep).Finalize();// Блокировка полезна для уменьшения использования памяти в многопоточных ситуациях.// Это потому, что фабрика содержит общие данные между экземпляры,// поэтому, если два экземпляра созданы из двух разных фабрик, они будут потреблять больше памяти//, чем два экземпляра, созданные из одной и той же фабрики.Interlocked.CompareExchange(ref Factory, Factory_, null);return Factory;}private bool IsVeryWounded( ) => health <= 50;private bool IsWounded() => health <= 75;private void OnUpdateHunt(float удача){food += (int)MathF.Round(rnd.Next(8) * удача);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 удача){еда += (int)MathF.Round(rnd.Next(3) * удача);if (food >= 100){food = 100;stateMachine.Fire(Events.IsNoLongerHungry);}if (rnd.Next(1) % 1 = = 0){здоровье++;if (здоровье >= 100){здоровье = 100;stateMachine.Fire(Events.HasFullHealth);}}}private void OnUpdatePlay(){food -= 3;if (food <= 0){food = 0;stateMachine.Fire(Events.IsHungry);}}private void OnUpdateSleep(){здоровье++;if (здоровье >= 100){здоровье = 100;stateMachine.Fire(Events.HasFullHealth);}food -= 2;if (food <= 0){food = 0;stateMachine.Fire(Events.IsHungry);}}}
общедоступный запечатанный класс StateMachine<TState, TEvent, TRecipient>where TState: notnullwhere TEvent: notnull{/// Получить текущее (под)состояние этого конечного автомата.public TState CurrentState { get; }/// Получаем текущее (под)состояние и все его родительские состояния иерархии.public ReadOnlySlice<TState> CurrentStateHierarchy { get; }/// Get принимает события по текущему (sub)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);/// Получаем родительскую иерархию указанного состояние. Если состояние не является подсостоянием, возвращается пустое.public ReadOnlySlice<TState> GetParentHierarchyOf(TStatestate);/// Получаем события, которые принимаются указанным состоянием.public ReadOnlySlice<TEvent> GetAcceptedEventsBy(TStatestate);/// Определяет если текущее состояние является указанным состоянием или (вложенным) подсостоянием этого указанного состояния.public bool IsInState(TState state);/// Выпускаем событие в конечный автомат./// Если конечный автомат уже запускает состояние, он ставится в очередь для запуска после завершения текущего события.public void Fire(TEvent @event);/// Запустите событие в конечный автомат./// Событие не будет поставлено в очередь, а фактически запустится, игнорируя ранее поставленные в очередь события./// Если последующие события будут поставлены в очередь во время выполнения обратных вызовов этого события, они также будут быть запущен после завершения этого события. public void FireImmediately(TEvent @event);/// Выполняет обратные вызовы обновления, зарегистрированные в текущем состоянии. public void Update();/// Сохраняет параметр(ы), которые можно передать для подписанных делегатов.public ParametersBuilder With<T>(T параметр);публичная структура только для чтения. With<TParameter>(параметр TParameter);/// То же, что Fire(TEvent) в родительском классе, но включает все сохраненные значения, которые могут быть переданы подписанным делегатам. public void Fire(TEvent);/// То же, что FireImmediately(TEvent) ) в родительском классе, но включает все сохраненные значения, которые могут быть переданы подписанным делегатам. public void FireImmediately(TEvent);/// То же, что и Update(TEvent) в родительском классе, но включает все сохраненные значения, которые могут быть передается подписанным делегатам.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{/// Создает настроенный и инициализированный конечный автомат, используя конфигурацию, предоставленную этой фабрикой.public StateMachine<TState, TEvent, TRecipient> Create(TRecipient получатель);/ // Сохраняет параметр(ы), которые можно передать подписанным делегатам.public StateMachine<TState, TEvent, TRecipient>.InitializeParametersBuilder With<T>(T параметр);}public запечатанный класс StateMachineBuilder<TState, TEvent, TRecipient> : IFinalizablewhere TState : notnullwhere TEvent : notnull{/// Определяет начальное состояние конечного автомата.// / `initializationPolicy` определяет, как будут подписываться делегаты на функции OnEntry указанное состояние (и родительские состояния) будут запущены во время инициализации конечного автомата. ранее добавленный state.public StateBuilder<TState, TEvent, TRecipient> In(TState state);/// Создает фабрику, используя в качестве конфигурации builder.public StateMachineFactory<TState, TEvent, TRecipient> Finalize();}public запечатанный класс StateBuilder<TState, TEvent, TRecipient> : IFinalizablewhere TState : notnullwhere TEvent : notnull{ /// Направляет вызов StateMachineBuilder<TState, TEvent, TRecipient>.In(TStatestate).public StateBuilder<TState, TEvent, TRecipient> In(TStatestate);/// Направляет вызов 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);/// Определяет действие, которое необходимо выполнить при выходе из fropm 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);/// Игнорирует указанное событие./// Если к событию не добавлено никакого поведения и оно запущено, оно будет выдано. Это предотвращает выдачу, игнорируя вызов вообще. 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), но передает делегату любой параметр, переданный во время вызова, который соответствует общему типу параметра./// Если ни один параметр не передан с помощью указанный общий параметр найден, он игнорируется. 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 истинно: действия 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 {/// Настраивает политику подписки делегатов to при входном перехвате должен быть выполнен./// Если этот метод не выполняется, политикой по умолчанию является TransitionPolicy.ParentFirstWithCulling.public GotoBuilder<TState, TEvent, TRecipient, TParent> OnEntryPolicy(TransitionPolicy policy);/// Настраивает политику как должны выполняться подписанные делегаты на перехватчик выхода. /// Если этот метод не выполняется, политика по умолчанию TransitionPolicy.ChildrenFirstWithCulling.public GotoBuilder<TState, TEvent, TRecipient, TParent> OnExitPolicy(TransitionPolicy policy);/// Определяет, в какое состояние переходит этот переход.public TParent Goto(TState state);/// Определяет переход в текущее состояние ./// Это ярлык Goto(currentState).public TParent GotoSelf();}/// Определяет политику перехода между двумя состояниями./// Определяет, как подписанные делегаты в состояниях выполняются во время перехода между состояниями. public enum TransitionPolicy{/// Определяет, что подписанные делегаты не должны запускаться. Ignore = 0,/// Определяет, что подписанные делегаты на родительских элементах запускаются первыми.ParentFirst = 1,/// Определяет, что подписанные делегаты на дочерних элементах запускаются первыми.ChildFirst = 2,/// Определяет, что подписанные делегаты на родительских элементах запускаются первыми (исключая) последнего общего родителя между двумя состояниями. общий родительский элемент между двумя состояниями.ChildFirstWithCulling = 4, /// Определяет, что подписанные делегаты родительских элементов запускаются первыми (включая) последнего общего родительского элемента между двумя состояниями. states.ParentFirstWithCullingInclusive = 5,/// Определяет, что подписанные делегаты на дочерних элементах выполняются первыми до тех пор, пока не будет достигнут (включая) последний общий родительский элемент между двумя состояниями.ChildFirstWithCullingInclusive = 6,}/// Представляет фрагмент data.public readonly struct ReadOnlySlice <T> : IReadOnlyList<T>{/// Получить элемент, указанный в index.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>{/// Получаем текущий элемент из enumerator.public T Current {get; }/// Переходит к следующему элементу enumeration.public bool MoveNext();/// Сбрасываем enumeration.public void Reset();}}