Net State Machine
v0.4.0
Uma biblioteca de construção de máquinas de estado para .NET.
O exemplo a seguir mostra algumas funções da máquina de estados.
usando Enderlook.StateMachine;classe pública Character{private static StateMachineFactory<Estados, Eventos, Personagem>? fábrica;privado somente leitura Random rnd = new();privado somente leitura StateMachine<Estados, Eventos, Personagem> stateMachine;private int health = 100;private int food = 100;private enum States{Sleep,Play,GettingFood,Hunt,Gather,} enumeração privada Events{HasFullHealth,LowHealth,IsHungry,IsNoLongerHungry,}public static async Task Main(){Character character = new();while (true){Console.Clear();// Executa uma chamada de atualização da máquina de estado e passa um parâmetro arbitrário para ele. // O parâmetro é genérico, portanto não é alocado em tipos de valor. // Este parâmetro é passado para o delegado inscrito que aceita o tipo de argumento genérico em é assinatura.// Se você não quiser passar um parâmetro, você pode remover a chamada do método .With(). // Este sistema de parâmetros também pode ser usado com eventos de incêndio métodos.character.stateMachine.With(character.rnd. NextSingle()).Update();Console.WriteLine($"Estado: {character.stateMachine.CurrentState}.");Console.WriteLine($"Saúde: {character.health}.");Console.WriteLine($"Food: {character.food}.");await Task.Delay(10).ConfigureAwait(false);}}public Character(){// Cria um instância do estado machine.stateMachine = GetStateMachineFactory().Create(this); // Alternativamente, se você deseja passar parâmetros para a inicialização da máquina de estados, você pode fazer: // stateMachine = GetStateMachineFactory().With(parameter).Create(this).
// O método `.With(parameter)` pode ser concatenado quantas vezes você precisar.
// O padrão `stateMachine.With(p1).With(p2)...With(pn).SomeMethod(...)` também é válido para os métodos `Fire()`, `FireImmediately()` e `Update ()`.}private static StateMachineFactory<Estados, Eventos, Personagem> GetStateMachineFactory(){if (fábrica não é nula)return factory;StateMachineFactory<Estados, Eventos, Personagem>? factory_ = StateMachine<States, Events, Character>// Máquinas de estado são criadas a partir de fábricas, o que torna a criação de múltiplas instâncias // mais barata tanto na CPU quanto na memória, já que a computação é feita uma vez e compartilhada entre as instâncias criadas..CreateFactoryBuilder()// Determina o estado inicial da máquina de estado.// O segundo parâmetro determina como os delegados OnEntry devem ser executados durante a inicialização da máquina de estado, // InitializationPolicy.Ignore significa que eles não devem be run..SetInitialState(States.Sleep, InitializationPolicy.Ignore)// Configura um estado..In(States.Sleep)// Executado toda vez que entramos neste estado..OnEntry(() => Console.WriteLine(" Indo para a cama."))// Executado sempre que saímos deste estado..OnExit(() => Console.WriteLine("Getting up."))// Executado sempre O método update (Update() ou With<T>(T).Update()) é executado e está neste estado. // Todos os eventos fornecem uma sobrecarga para passar um destinatário, para que possa ser parametrizado durante a construção de instâncias concretas .// Também fornece uma sobrecarga para passar um parâmetro de tipo arbitrário, para que possa ser parametrizado durante a chamada de With<T>(T).Update(). // Também fornece uma sobrecarga para passar um destinatário e um parâmetro de tipo arbitrário.// Essas sobrecargas também se aplicam a Métodos OnEntry(...), OnExit(...), If(...) e Do(...)..OnUpdate(@this => @this.OnUpdateSleep()).On(Events.HasFullHealth) // Executado toda vez que este evento é disparado neste estado..Do(() => Console.WriteLine("Pick Toys."))// Novo estado para transite..Goto(States.Play)// Alternativamente, você pode configurar a política de execução de eventos durante a transição.// A chamada do método acima é equivalente a:// .OnEntryPolicy(TransitionPolicy.ChildFirstWithCulling).OnExitPolicy(TransitionPolicy.ParentFirstWithCulling).Goto(States.Play)..On(Events.IsHungry)// Execute apenas o próxima chamada se a condição for verdadeira..If(@this => @this.IsVeryWounded())// Ficamos em nosso estado atual sem executar delegados OnEntry nem OnExit..StaySelf()// O método acima é um atalho de:// .OnEntryPolicy(TransitionPolicy.Ignore).OnExitPolicy(TransitionPolicy.Ignore).Goto(States.Sleep).// Se quisermos executar esses delegados, podemos usar:// .GotoSelf(false)// Qual é o atalho de:// .OnEntryPolicy(TransitionPolicy.ChildFirstWithCullingInclusive).OnExitPolicy(TransitionPolicy.ParentFirstWithCullingInclusive).Goto(States.Sleep).// Se, adicionalmente, quiséssemos executar delegados de transição de seus estados pais (algo que não é útil neste exemplo, pois State.Sleep não é um subestado) podemos fazer:// .GotoSelf(true)// Qual é o atalho of:// .OnEntryPolicy(TransitionPolicy.ChildFirst).OnExitPolicy(TransitionPolicy.ParentFirst).Goto(States.Sleep).// Caso contrário, execute a próxima chamada se a condição for verdadeira..If(@this => @this.IsWounded ()).Goto(States.Gather)// Caso contrário, execute incondicionalmente..Goto(States.Hunt)// Ignore este evento em esta transição.// (Se não adicionarmos isso e dispararmos acidentalmente este evento, uma exceção será lançada)..Ignore(Events.LowHealth)// Qual é o atalho de: // .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("Indo buscar comida.")).OnExit( () => Console.WriteLine("Pare de ir buscar comida.")).In(States.Gather)// Determina que este estado é um subestado de outro.// Isso significa que OnUpdate delega no estado pai também será executado.// Também dependendo do OnEntryPolicy e OnExitPolicy configurados durante as transições, // os delegados OnEntry e OnExit inscritos neste estado podem ser executados durante transições em subestados..IsSubStateOf(States.GettingFood).OnUpdate((Character @ isso, parâmetro float) => @this.OnUpdateGather(parâmetro)).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("Pegue o arco.")).OnExit(() => Console.WriteLine("Solte o arco.")).OnUpdate((Caracter @this, parâmetro float) => @this.OnUpdateHunt(parâmetro)) .On(Events.IsNoLongerHungry).Goto(States.Sleep).On(Events.LowHealth).Goto(States.Sleep).Finalize();// O intertravado é útil para reduzir o uso de memória em situações de multithreading. // Isso ocorre porque a fábrica contém dados comuns entre instâncias, // portanto, se duas instâncias forem criadas a partir de duas fábricas diferentes, consumirá mais memória // do que duas instâncias criadas a partir da mesma factory.Interlocked.CompareExchange(ref factory, factory_, null);return factory;}private bool IsVeryWounded() => saúde <= 50;private bool IsWounded() => saúde <= 75;private void OnUpdateHunt(float sorte){comida += (int)MathF.Round(rnd.Next(8) * sorte);if (comida >= 100){comida = 100;stateMachine.Fire(Events. Não tem mais fome); // Alternativamente se você quiser passar parâmetros para a inicialização da máquina de estados você pode fazer:
// stateMachine.With(paramter).Fire(Events.IsNoLongerHungry);}health -= (int)MathF.Round(rnd.Next(6) * (1 - sorte));if (health <= 20)stateMachine. Fire(Events.LowHealth);}private void OnUpdateGather(float lucky){comida += (int)MathF.Round(rnd.Next(3) * sorte);if (comida >= 100){comida = 100;stateMachine.Fire(Events.IsNoLongerHungry);}if (rnd.Next(1) % 1 = = 0){saúde++;if (saúde >= 100){saúde = 100;stateMachine.Fire(Events.HasFullHealth);}}}private void OnUpdatePlay(){food -= 3;if (food <= 0){food = 0;stateMachine.Fire(Events.IsHungry);}}private void OnUpdateSleep(){saúde++;if (saúde >= 100){saúde = 100;stateMachine.Fire(Events.HasFullHealth);}comida -= 2;if (comida <= 0){comida = 0;stateMachine.Fire(Events.IsHungry);}}} public sealed class StateMachine<TState, TEvent, TRecipient>where TState : notnullwhere TEvent : notnull{/// Obtém (sub)estado atual deste estado machine.public TState CurrentState { get; }/// Obtém o (sub)estado atual e todos os seus estados pai hierarquia.public ReadOnlySlice<TState> CurrentStateHierarchy { get; }/// Get aceita eventos por (sub)state atual.public ReadOnlySlice<TEvent> CurrentAcceptedEvents { get; }/// Cria um construtor de fábrica.public static StateMachineBuilder<TState, TEvent, TRecipient> CreateFactoryBuilder();/// Obtém o estado pai do estado especificado. /// Se o estado não for um subestado, retorna false.public bool GetParentStateOf(TState state, [NotNullWhen(true)] out TState? parentState);/// Obtém a hierarquia pai do estado especificado. Se o estado não for um subestado, retorna vazio.public ReadOnlySlice<TState> GetParentHierarchyOf(TState state);/// Obtém os eventos que são aceitos pelo estado especificado.public ReadOnlySlice<TEvent> GetAcceptedEventsBy(TState state);/// Determina se o estado atual for o estado especificado ou um subestado (aninhado) desse estado especificado.public bool IsInState(TState state);/// Dispara um evento para a máquina de estado. /// Se a máquina de estado já estiver disparando um estado, ela será colocada na fila para ser executada após a conclusão do evento atual.public void Fire(TEvent @event);/// Dispare um evento para a máquina de estado. /// O evento não será enfileirado, mas realmente executado, ignorando os eventos enfileirados anteriormente. /// Se eventos subsequentes forem enfileirados durante a execução dos retornos de chamada deste evento, eles também serão executado após a conclusão deste evento.public void FireImmediately(TEvent @event);/// Executa os retornos de chamada de atualização registrados no estado atual.public void Update();/// Armazena um(s) parâmetro(s) que podem ser passados para subscribed delegados.public ParametersBuilder With<T>(T parâmetro);public readonly struct ParametersBuilder{/// Armazena um parâmetro que pode ser passado para callbacks.public ParametersBuilder With<TParameter>(TParameter parâmetro);/// O mesmo que Fire(TEvent) na classe pai, mas inclui todo o valor armazenado que pode ser passado para delegados inscritos.public void Fire(TEvent);/// O mesmo que FireImmediately(TEvent ) na classe pai, mas inclui todo o valor armazenado que pode ser passado para delegados inscritos.public void FireImmediately(TEvent);/// O mesmo que Update(TEvent) na classe pai, mas inclui todo o valor armazenado que pode ser passado para subscribed delegados.public void Update(TEvent);}public readonly struct InitializeParametersBuilder{/// Armazena um parâmetro que pode ser passado para callbacks.public InitializeParametersBuilder With<TParameter>(TParameter parâmetro);/// Cria o estado machine.public StateMachine <TState, TEvent, TRecipient> Create(TRecipient destinatário);}}classe pública selada StateMachineFactory<TState, TEvent, TRecipient>where TState : notnullwhere TEvent : notnull{/// Cria uma máquina de estado configurada e inicializada usando a configuração fornecida por este factory.public StateMachine<TState, TEvent, TRecipient> Create(TRecipient destinatário);/// Armazena um parâmetro( s) que pode ser passado para delegados inscritos.public StateMachine<TState, TEvent, TRecipient>.InitializeParametersBuilder With<T>(T parâmetro);}classe selada pública StateMachineBuilder<TState, TEvent, TRecipient> : IFinalizablewhere TState : notnullwhere TEvent : notnull{/// Determina o estado inicial da máquina de estado./// `initializationPolicy` determina como os delegados inscritos nos eventos OnEntry do estado especificado (e estados pai) será executado durante a inicialização do estado machine.public StateMachineBuilder<TState, TEvent, TRecipient> SetInitialState(TState state, ExecutionPolicy inicializationPolicy = ExecutionPolicy.ChildFirst);/// Adiciona um novo estado ou carrega um estado adicionado anteriormente.public StateBuilder<TState, TEvent, TRecipient> In(TState state); /// Cria uma fábrica usando como configuração o construtor.public StateMachineFactory<TState, TEvent, TRecipient> Finalize();}classe pública selada StateBuilder<TState, TEvent, TRecipient> : IFinalizablewhere TState : notnullwhere TEvent : notnull{/// Encaminha a chamada para StateMachineBuilder<TState, TEvent, TRecipient>.In( Estado TState).public StateBuilder<TState, TEvent, TRecipient> In(TState state);/// Encaminha a chamada para StateMachineBuilder<TState, TEvent, TRecipient>.Finalize();public StateMachineFactory<TState, TEvent, TRecipient> Finalize();/// Marca este estado como o subestado de o estado especificado.public StateBuilder<TState, TEvent, TRecipient> IsSubStateOf(TState state);/// Determina uma ação a ser executada na entrada deste estado.public StateBuilder<TState, TEvent, TRecipient> OnEntry(Action action);/// O mesmo que OnEntry(Action) mas passa o destinatário como parâmetro.public StateBuilder<TState, TEvent, TRecipient > OnEntry(Action<TRecipient> action);/// O mesmo que OnEntry(Action) mas passa para o delegado qualquer parâmetro passado durante a chamada que corresponda ao parâmetro genérico type./// Se nenhum parâmetro passado com o parâmetro genérico especificado for encontrado, ele será ignorado.public StateBuilder<TState, TEvent, TRecipient> OnEntry<TParameter>(Action<TParameter> action);/// Versão combinada de OnEntry(Action <TRecipient>) e OnEntry(Action<TParameter>).public StateBuilder<TState, TEvent, TRecipient> OnEntry<TParameter>(Action<TRecipient, TParameter> action);/// Determina uma ação a ser executada ao sair deste state.public StateBuilder<TState, TEvent, TRecipient> OnExit(Action action);/// O mesmo que OnExit(Action) mas passa o destinatário como parâmetro. public StateBuilder<TState, TEvent, TRecipient> OnExit(Action<TRecipient> action);/// O mesmo que OnExit(Action) mas passa para o delegado qualquer parâmetro passado durante o chamada que corresponde ao tipo de parâmetro genérico./// Se nenhum parâmetro passado com o parâmetro genérico especificado for encontrado, ele será ignorado.public StateBuilder<TState, TEvent, TRecipient> OnExit<TParameter>(Action<TParameter> action);/// Versão combinada de OnExit(Action<TRecipient>) e OnExit(Action<TParameter>).public StateBuilder<TState, TEvent, TRecipient> OnExit<TParameter>(Action<TRecipient, TParameter> action);/// Determina uma ação a ser executada na atualização para este state.public StateBuilder<TState, TEvent, TRecipient> OnUpdate(Action action);/// Igual a OnUpdate( Action), mas passe o destinatário como parâmetro.public StateBuilder<TState, TEvent, TRecipient> OnUpdate(Action<TRecipient> action);/// Igual a OnUpdate(Action) mas passe para o delegado qualquer parâmetro passado durante a chamada que corresponda ao parâmetro genérico type.public StateBuilder<TState, TEvent, TRecipient> OnUpdate<TParameter>(Action<TParameter> action);/// Versão combinada de OnUpdate (Action<TRecipient>) e OnUpdate(Action<TParameter>)./// Se nenhum parâmetro passado com o parâmetro genérico especificado for encontrado, ele será ignorado.public StateBuilder<TState, TEvent, TRecipient> OnUpdate<TParameter>(Action<TRecipient, TParameter> action);/// Adiciona um comportamento que é executado durante o disparo do evento especificado.public TransitionBuilder<TState, TEvent, TRecipient, StateBuilder< TState, TEvent, TRecipient>> On(TEvent @event);/// Ignora o evento especificado. /// Se nenhum comportamento for adicionado a um evento e é disparado, ele vai lançar. Isso evita o lançamento, ignorando a chamada. notnullwhere TEvent : notnull{/// Adiciona uma subtransição que é executada quando o delegado retorna true.public TransitionBuilder<TState, TEvent, TRecipient, TransitionBuilder<TState, TEvent, TRecipient, TParent>> If(Func<bool> guard);/// O mesmo que If(Func<bool>) mas passa o destinatário como parâmetro. public TransitionBuilder<TState, TEvent, TRecipient, TransitionBuilder<TState, TEvent, TRecipient, TParent>> If(Func<TRecipient, bool> guard);/// O mesmo que If(Func<bool>) mas passa para o delegado qualquer parâmetro passado durante a chamada que corresponda ao parâmetro genérico type.public TransitionBuilder<TState, TEvent, TRecipient, TransitionBuilder<TState, TEvent, TRecipient, TParent>> If<TParameter>(Func<TParameter, bool> guard);/// Versão combinada de If(Func<TRecipient, bool>) e If(Func<TParameter, bool>).public TransitionBuilder<TState, TEvent, TRecipient, TransitionBuilder<TState, TEvent, TRecipient, TParent>> If<TParameter>(Func<TParameter, bool > guard);/// Determina uma ação a ser executada quando o evento for gerado.public TransitionBuilder<TState, TEvent, TRecipient, TParent> Do(Action action);/// O mesmo que Do(Action) mas passa o destinatário como parâmetro.public TransitionBuilder<TState, TEvent, TRecipient, TParent> Do(Action<TRecipient> action);/// O mesmo que Do (Ação), mas passe para o delegado qualquer parâmetro passado durante a chamada que corresponda ao tipo de parâmetro genérico. /// Se nenhum parâmetro passado com o parâmetro genérico especificado for encontrado, ele será ignorado.public TransitionBuilder<TState, TEvent, TRecipient, TParent> Do<TParameter>(Action<TParameter> action);/// Versão combinada de Do(Action<TRecipient>) e Do(Action<TParameter>).public TransitionBuilder<TState, TEvent , TRecipient, TParent> Do<TParameter>(Action<TRecipient, TParameter> action);/// Configura a política de como os delegados inscritos serão recebidos na entrada hook deve ser executado./// Se este método não for executado, a política padrão é TransitionPolicy.ParentFirstWithCulling.public GotoBuilder<TState, TEvent, TRecipient, TParent> OnEntryPolicy(TransitionPolicy policy);/// Configura a política de como os delegados inscritos to on exit hook deve ser executado. /// Se este método não for executado, a política padrão é TransitionPolicy.ChildFirstWithCulling.public GotoBuilder<TState, TEvent, TRecipient, TParent> OnExitPolicy(TransitionPolicy policy); /// Determina para qual estado essa transição vai. /// Isso é equivalente a: OnEntryPolicy(TransitionPolicy.ChildFirstWithCulling).OnExitPolicy(TransitionPolicy.ParentFirstWithCulling).Goto(state).public TParent Goto(TState state); /// Determina transitar para o estado atual. /// Se runParentsActions for verdadeiro: ações OnExit e OnEntry do estado atual (mas não dos estados pais no caso do estado atual ser um subestado) será executado./// Isso é equivalente a OnEntryPolicy(TransitionPolicy.ChildFirstWithCullingInclusive).OnExitPolicy(TransitionPolicy.ParentFirstWithCullingInclusive).Goto(currentState)./// Se runParentActions for falso: ações OnExit e OEntry do estado atual (e pais no caso de estado atual sendo um subestado) será executado. /// Isso é equivalente a OnEntryPolicy(TransitionPolicy.ChildFirst).OnExitPolicy(TransitionPolicy.ParentFirst).Goto(currentState).public TParent GotoSelf(bool runParentsActions = false); /// Determina que não terá transição para nenhum estado, portanto nenhum evento OnEntry e OnExit será levantado. /// Isso é equivalente a OnEntryPolicy(TransitionPolicy.Ignore).OnExitPolicy(TransitionPolicy.Ignore).GotoSelf().public TParent StaySelf();}classe pública selada GotoBuilder<TState, TEvent, TRecipient, TParent> : IGoto<TState>where TState : notnullwhere TEvent : notnull {/// Configura a política de como os delegados inscritos em o gancho de entrada deve ser executado./// Se este método não for executado, a política padrão é TransitionPolicy.ParentFirstWithCulling.public GotoBuilder<TState, TEvent, TRecipient, TParent> OnEntryPolicy(TransitionPolicy policy);/// Configura a política de como subscrito delegados para o gancho de saída devem ser executados. /// Se este método não for executado, a política padrão é TransitionPolicy.ChildrenFirstWithCulling.public GotoBuilder<TState, TEvent, TRecipient, TParent> OnExitPolicy(TransitionPolicy policy);/// Determina para qual estado esta transição vai.public TParent Goto(TState state);/// Determina transitar para o estado atual ./// Este é um atalho de Goto(currentState).public TParent GotoSelf();}/// Determina a política de transição entre dois estados. /// Isso configura como os delegados inscritos nos estados são executados durante a transição entre estados.public enum TransitionPolicy{/// Determina que os delegados inscritos não devem ser executados. Ignore = 0, /// Determina que os delegados inscritos não devem ser executados. delegados nos pais são executados primeiro.ParentFirst = 1, /// Determina que os delegados inscritos nos filhos são executados primeiro.ChildFirst = 2, /// Determina que os delegados inscritos nos pais são executados primeiro de (excluindo) o último pai comum entre os dois estados.ParentFirstWithCulling = 3,/// Determina que os delegados inscritos em filhos são executados primeiro até atingir (excluindo) o último pai comum entre os dois estados.ChildFirstWithCulling = 4,// / Determina que os delegados inscritos nos pais são executados primeiro (incluindo) o último pai comum entre os dois estados.ParentFirstWithCullingInclusive = 5,/// Determina que os delegados inscritos nos filhos são executados primeiro até atingir (incluindo) o último pai comum entre os dois estados.ChildFirstWithCullingInclusive = 6,}/// Representa uma fatia de data.public readonly struct ReadOnlySlice<T> : IReadOnlyList<T>{// Get o elemento especificado em index.public T this[int index] { get; }/// Obtém a contagem do slice.public int Count { get; }/// Obtenha uma <see cref="ReadOnlyMemory{T}"/> desta fatia.public ReadOnlyMemory<T> Memory { get; }/// Obtenha um <see cref="ReadOnlySpan{T}"/> desta fatia.public ReadOnlySpan<T> Span { get; } /// Obtenha o enumerador do slice.public Enumerator GetEnumerator(); /// Enumerador de <see cref="ReadOnlySlice{T}"/>.public struct Enumerator : IEnumerator<T>{/// Obtenha o elemento atual do enumerador.public T Current { get; } /// Move para o próximo elemento do enumeration.public bool MoveNext(); /// Reinicia o enumeration.public void Reset();}}