Net State Machine
v0.4.0
Una biblioteca de creación de máquinas de estado para .NET.
El siguiente ejemplo muestra algunas de las funciones de la máquina de estados.
usando Enderlook.StateMachine; carácter de clase pública {StateMachineFactory estático privado <Estados, Eventos, Carácter>? factory;private readonly Random rnd = new();private readonly StateMachine<Estados, Eventos, Carácter> stateMachine;private int salud = 100;private int food = 100;enum privado Estados{Dormir, Jugar, Obtener comida, Cazar, Reunir,} Eventos de enumeración privada {HasFullHealth,LowHealth,IsHungry,IsNoLongerHungry,}public static async Task Main(){Carácter de carácter = new(); while (true){Console.Clear();// Ejecuta una llamada de actualización de la máquina de estado y le pasa un parámetro arbitrario.// El parámetro es genérico, por lo que no No asigna tipos de valor.// Este parámetro se pasa al delegado suscrito que acepta el tipo de argumento genérico en su firma.// Si no desea pasar un parámetro, puede eliminar la llamada al método .With()./ / Este sistema de parámetros también puede usarse con métodos de eventos de incendio.character.stateMachine.With(character.rnd.NextSingle()).Update();Console.WriteLine($"State: {character.stateMachine.CurrentState}.");Console.WriteLine($ "Salud: {carácter.salud}.");Console.WriteLine($"Comida: {carácter.comida}.");await Task.Delay(10).ConfigureAwait(false);}}public Character(){// Crea una instancia de la máquina de estado.stateMachine = GetStateMachineFactory().Create(this); // Alternativamente, si desea pasar parámetros para la inicialización de la máquina de estados, puede hacer: // stateMachine = GetStateMachineFactory().With(parameter).Create(this). // El método `.With(parámetro)` se puede concatenar tantas veces como sea necesario. // El patrón `stateMachine.With(p1).With(p2)...With(pn).SomeMethod(...)` también es válido para los métodos `Fire()`, `FireImmediately()` y `Update ()`.}StateMachineFactory estático privado<Estados, Eventos, Carácter> GetStateMachineFactory(){si (la fábrica no es nula)return factory;StateMachineFactory<Estados, Eventos, Personaje>? factory_ = StateMachine<Estados, Eventos, Carácter>// Las máquinas de estado se crean a partir de fábricas, lo que hace que la creación de múltiples instancias// sea más barata tanto en CPU como en memoria, ya que el cálculo se realiza una vez y se comparte entre las instancias creadas..CreateFactoryBuilder()// Determina el estado inicial de la máquina de estados.// El segundo parámetro determina cómo se deben ejecutar los delegados de OnEntry durante la inicialización de la máquina de estados,// InitializationPolicy.Ignore significa que no deberían ser ejecutado..SetInitialState(States.Sleep, InitializationPolicy.Ignore)// Configura un estado..In(States.Sleep)// Se ejecuta cada vez que ingresamos a este estado..OnEntry(() => Console.WriteLine(" Ir a la cama."))// Se ejecuta cada vez que salimos de este estado..OnExit(() => Console.WriteLine("Levantarse."))// Se ejecuta cada vez que se ejecuta el método de actualización (ya sea Update() o With<T>(T).Update()) y se encuentra en este estado. // Todos los eventos proporcionan una sobrecarga para pasar un destinatario, por lo que se puede parametrizar durante la compilación. de instancias concretas.// También proporciona una sobrecarga para pasar un parámetro de tipo arbitrario, por lo que se puede parametrizar durante la llamada de With<T>(T).Update().// También proporciona una sobrecarga para pasar tanto un destinatario como un parámetro de tipo arbitrario.// Esto sobrecarga también se aplica a los métodos OnEntry(...), OnExit(...), If(...) y Do(...)..OnUpdate(@this => @this.OnUpdateSleep()).On(Events .HasFullHealth)// Ejecutado cada vez que este evento se activa en este estado..Do(() => Console.WriteLine("Pick Toys."))// Nuevo estado para transitar..Goto(States.Play)// Alternativamente, puede configurar la política de ejecución de eventos. durante la transición.// La llamada al método anterior es equivalente a:// .OnEntryPolicy(TransitionPolicy.ChildFirstWithCulling).OnExitPolicy(TransitionPolicy.ParentFirstWithCulling).Goto(States.Play)..On(Events.IsHungry)// Ejecute solo el próxima llamada si la condición es verdadera..If(@this => @this.IsVeryWounded())// Nos quedamos en nuestro actual state sin ejecutar OnEntry ni OnExit delegados..StaySelf()// El método anterior es un atajo de:// .OnEntryPolicy(TransitionPolicy.Ignore).OnExitPolicy(TransitionPolicy.Ignore).Goto(States.Sleep).// Si Si queríamos ejecutar esos delegados, podemos usar:// .GotoSelf(false)// Cuál es el acceso directo de:// .OnEntryPolicy(TransitionPolicy.ChildFirstWithCullingInclusive).OnExitPolicy(TransitionPolicy.ParentFirstWithCullingInclusive).Goto(States.Sleep).// Si adicionalmente quisiéramos ejecutar delegados de transición desde sus estados padres (algo que no es útil en este ejemplo ya que State.Sleep no es un subestado) podemos hacer:// .GotoSelf(true)// ¿Cuál es el atajo? of:// .OnEntryPolicy(TransitionPolicy.ChildFirst).OnExitPolicy(TransitionPolicy.ParentFirst).Goto(States.Sleep).// De lo contrario, ejecute la siguiente llamada si la condición es verdadera..If(@this => @this.IsWounded ()).Goto(States.Gather)// De lo contrario, ejecutar incondicionalmente..Goto(States.Hunt)// Ignorar este evento en esta transición.// (Si no agregamos esto y activamos accidentalmente este evento, se genera una excepción)..Ignore(Events.LowHealth)// Que es el acceso directo 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("Ir por comida.")).OnExit(() => Console.WriteLine("Dejar de ir por food.")).In(States.Gather)// Determina que este estado es un subestado de otro.// Esto significa que los delegados de OnUpdate en el estado principal también se ejecutarán.// También dependiendo de la OnEntryPolicy y OnExitPolicy configuradas durante las transiciones, // los delegados OnEntry y OnExit suscritos en este estado pueden ejecutarse durante las transiciones en subestados..IsSubStateOf(States.GettingFood).OnUpdate((Carácter @this, parámetro flotante) => @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("Hacer una reverencia.")).OnExit(() => Console.WriteLine("Hacer una reverencia.")).OnUpdate((Carácter @this, parámetro flotante) => @this.OnUpdateHunt(parámetro)).On(Events.IsNoLongerHungry).Goto(States.Sleep).On(Events.LowHealth).Goto(States.Sleep).Finalize();/ / El entrelazado es útil para reducir el uso de memoria en situaciones de subprocesos múltiples.// Esto se debe a que la fábrica contiene datos comunes entre instancias, // por lo que si se crean dos instancias a partir de dos fábricas diferentes, consumirá más memoria // que dos instancias creadas desde la misma fábrica.Interlocked.CompareExchange(ref factory, factory_, null);return factory;}private bool IsVeryWounded() => salud <= 50;private bool IsWounded() => salud <= 75;private void OnUpdateHunt(suerte flotante){comida += (int)MathF.Round(rnd.Next(8) * suerte);if (comida >= 100){comida = 100;stateMachine.Fire(Events.IsNoLongerHungry); // Alternativamente, si deseas pasar parámetros a la inicialización de la máquina de estados, puedes hacer: // stateMachine.With(paramter).Fire(Events.IsNoLongerHungry);}health -= (int)MathF.Round(rnd.Next(6) * (1 - suerte));if (health <= 20)stateMachine. Fuego(Events.LowHealth);}vacío privado OnUpdateGather(suerte flotante){comida += (int)MathF.Round(rnd.Next(3) * suerte);if (comida >= 100){comida = 100;stateMachine.Fire(Events.IsNoLongerHungry);}if (rnd.Next(1) % 1 = = 0){salud++;si (salud >= 100){salud = 100;stateMachine.Fire(Events.HasFullHealth);}}}private void OnUpdatePlay(){food -= 3;if (food <= 0){food = 0;stateMachine.Fire(Events.IsHungry);}}private void OnUpdateSleep(){salud++;if (salud >= 100){salud = 100;stateMachine.Fire(Events.HasFullHealth);}comida -= 2;if (comida <= 0){comida = 0;stateMachine.Fire(Events.IsHungry);}}}
clase pública sellada StateMachine<TState, TEvent, TRecipient>donde TState: notnulldónde TEvent: notnull{/// Obtener el (sub)estado actual de esta máquina de estado.public TState CurrentState { get; }/// Obtener el (sub)estado actual y toda su jerarquía de estado principal.public ReadOnlySlice<TState> CurrentStateHierarchy { get; }/// Get acepta eventos por (sub)state.public actual ReadOnlySlice<TEvent> CurrentAcceptedEvents { get; }/// Crea una fábrica builder.public static StateMachineBuilder<TState, TEvent, TRecipient> CreateFactoryBuilder();/// Obtiene el estado principal del estado especificado./// Si el estado no es un subestado, devuelve false.public bool GetParentStateOf(TState state, [NotNullWhen(true)] out TState? parentState);/// Obtiene la jerarquía principal del estado especificado. Si el estado no es un subestado, devuelve vacío.public ReadOnlySlice<TState> GetParentHierarchyOf(TState state);/// Obtiene los eventos aceptados por el estado especificado.public ReadOnlySlice<TEvent> GetAcceptedEventsBy(TState state);/// Determina si el estado actual es el estado especificado o un subestado (anidado) de ese estado especificado.public bool IsInState(TState state);/// Activar un evento a la máquina de estado./// Si la máquina de estado ya está activando un estado, se pone en cola para ejecutarse después de completar el evento actual.public void Fire(TEvent @event);/// Activar un evento al estado machine./// El evento no se pondrá en cola, sino que se ejecutará, ignorando los eventos previamente puestos en cola./// Si los eventos posteriores se ponen en cola durante la ejecución de las devoluciones de llamada de este evento, también se ejecutarán después de completar this event.public void FireImmediately(TEvent @event);/// Ejecuta las devoluciones de llamada de actualización registradas en el estado actual.public void Update();/// Almacena uno o varios parámetros que se pueden pasar a los delegados suscritos.public ParametersBuilder With<T>(Tparameter);public readonly struct ParametersBuilder{/// Almacena un parámetro que se puede pasar a callbacks.public ParametersBuilder With<TParameter>(Parámetro TParameter);/// Igual que Fire(TEvent) en la clase principal pero incluye todo el valor almacenado que se puede pasar a los delegados suscritos.public void Fire(TEvent);/// Igual que FireImmediately(TEvent) ) en la clase principal pero incluye todo el valor almacenado que se puede pasar a los delegados suscritos.public void FireImmediately(TEvent);/// Igual que Update(TEvent) en la clase principal pero incluye todo el valor almacenado que se puede pasar a delegados suscritos.public void Update(TEvent);}estructura pública de solo lectura InitializeParametersBuilder{/// Almacena un parámetro que se puede pasar a callbacks.public InitializeParametersBuilder With<TParameter>(Parámetro TParameter);/// Crea la máquina de estado.public StateMachine <TState, TEvent, TRecipient> Create(TRecipient recipiente);}}clase pública sellada StateMachineFactory<TState, TEvent, TRecipient>where TState: notnullwhere TEvent: notnull{/// Crea una máquina de estado configurada e inicializada usando la configuración proporcionada por esta fábrica.public StateMachine<TState, TEvent, TRecipient> Create(TRecipient recipiente);/// Almacena un parámetro( s) que se pueden pasar a los delegados suscritos.public StateMachine<TState, TEvent, TRecipient>.InitializeParametersBuilder With<T>(T parámetro);}clase pública sellada StateMachineBuilder<TState, TEvent, TRecipient>: IFinalizablewhere TState: notnullwhere TEvent: notnull{/// Determina el estado inicial de la máquina de estado./// `initializationPolicy` determina cómo los delegados suscritos a los eventos OnEntry del estado especificado (y los estados principales) se ejecutarán durante la inicialización de la máquina de estados.public StateMachineBuilder<TState, TEvent, TRecipient> SetInitialState(TState state, ExecutionPolicy inicializationPolicy = ExecutionPolicy.ChildFirst);/// Agrega un nuevo estado o carga un state.public StateBuilder previamente agregado<TState, TEvent, TRecipient> In(TState state);/// Crea una fábrica utilice como configuración builder.public StateMachineFactory<TState, TEvent, TRecipient> Finalize();}clase pública sellada StateBuilder<TState, TEvent, TRecipient>: IFinalizablewhere TState: notnullwhere TEvent: notnull{/// Reenvía llamada a StateMachineBuilder<TState, TEvent, TRecipient>.In(TState state).public StateBuilder<TState , TEvent, TRecipient> In(TState state);/// Reenvía llamada a StateMachineBuilder<TState, TEvent, TRecipient>.Finalize();public StateMachineFactory<TState, TEvent, TRecipient> Finalize();/// Marca este estado como el subestado del estado especificado.public StateBuilder<TState, TEvent, TRecipient> IsSubStateOf (TState state);/// Determina una acción a ejecutar al ingresar a este estado.public StateBuilder<TState, TEvent, TRecipient> OnEntry(Action action);/// Igual que OnEntry(Action) pero pasa el destinatario como parámetro.public StateBuilder<TState, TEvent, TRecipient> OnEntry(Action<TRecipient> action);/// Igual que OnEntry (Acción) pero pasa al delegado cualquier parámetro pasado durante la llamada que coincida con el tipo de parámetro genérico. /// Si no se encuentra ningún parámetro pasado con el parámetro genérico especificado, se ignora.public StateBuilder<TState, TEvent, TRecipient> OnEntry<TParameter>(Action<TParameter> action);/// Versión combinada de OnEntry(Action<TRecipient>) y OnEntry(Action<TParameter>).public StateBuilder<TState, TEvent, TRecipient > OnEntry<TParameter>(Action<TRecipient, TParameter> action);/// Determina una acción a ejecutar al salir de pm este state.public StateBuilder<TState, TEvent, TRecipient> OnExit(Action action);/// Igual que OnExit(Action) pero pasa el destinatario como parámetro.public StateBuilder<TState, TEvent, TRecipient> OnExit(Action<TRecipient> action );/// Igual que OnExit(Action) pero pasa al delegado cualquier parámetro pasado durante la llamada que coincida con el tipo de parámetro genérico./// Si no se pasa ningún parámetro con el genérico especificado se encuentra el parámetro, se ignora.public StateBuilder<TState, TEvent, TRecipient> OnExit<TParameter>(Action<TParameter> action);/// Versión combinada de OnExit(Action<TRecipient>) y OnExit(Action<TParameter>). public StateBuilder<TState, TEvent, TRecipient> OnExit<TParameter>(Action<TRecipient, TParameter> acción);/// Determina una acción a ejecutar al actualizar a este state.public StateBuilder<TState, TEvent, TRecipient> OnUpdate(Action action);/// Igual que OnUpdate(Action) pero pasa el destinatario como parámetro.public StateBuilder<TState, TEvent, TRecipient > OnUpdate(Action<TRecipient> action);/// Igual que OnUpdate(Action) pero pasa al delegado cualquier parámetro pasado durante la llamada que coincida con el parámetro genérico type.public StateBuilder<TState, TEvent, TRecipient> OnUpdate<TParameter>(Action<TParameter> action);/// Versión combinada de OnUpdate(Action<TRecipient>) y OnUpdate(Action<TParameter>)./// Si no se pasa ningún parámetro con se encuentra el parámetro genérico especificado, se ignora.public StateBuilder<TState, TEvent, TRecipient> OnUpdate<TParameter>(Action<TRecipient, TParameter> action);/// Agrega un comportamiento que se ejecuta durante la activación del evento especificado.public TransitionBuilder<TState, TEvent, TRecipient, StateBuilder<TState, TEvent, TRecipient>> On (TEvent @event);/// Ignora el evento especificado./// Si no se agrega ningún comportamiento a un evento y se activa, se lanzará. Esto evita el lanzamiento ignorando la llamada en absoluto.public StateBuilder<TState, TEvent, TRecipient> Ignore(TEvent @event);}clase pública sellada TransitionBuilder<TState, TEvent, TRecipient, TParent>: IFinalizable, ITransitionBuilder<TState>donde TState: notnullwhere TEvent: notnull{/// Agrega una subtransición que se ejecuta cuando el delegado devuelve true.public TransitionBuilder<TState, TEvent, TRecipient, TransitionBuilder<TState, TEvent, TRecipient, TParent>> If(Func<bool> guard);/// Igual que If(Func<bool>) pero pasa el destinatario como parámetro.public TransitionBuilder< TState, TEvent, TRecipient, TransitionBuilder<TState, TEvent, TRecipient, TParent>> If(Func<TRecipient, bool> guard);/// Igual que If(Func<bool>) pero pasa al delegado cualquier parámetro pasado durante la llamada que coincida con el tipo de parámetro genérico.public TransitionBuilder<TState, TEvent, TRecipient, TransitionBuilder<TState, TEvent, TRecipient, TParent>> If<TParameter>(Func<TParameter, bool> guard);/// Versión combinada de If(Func<TRecipient, bool>) y If(Func<TParameter, bool>).public TransitionBuilder<TState, TEvent, TRecipient, TransitionBuilder<TState, TEvent, TRecipient, TParent>> If<TParameter>(Func<TParameter, bool>). > guard);/// Determina una acción a ejecutar cuando se genera el evento.public TransitionBuilder<TState, TEvent, TRecipient, TParent> Do(Action action);/// Igual que Do(Action) pero pasa el destinatario como parámetro.public TransitionBuilder<TState, TEvent, TRecipient, TParent> Do(Action<TRecipient> action);/// Igual como Do(Action) pero pasa al delegado cualquier parámetro pasado durante la llamada que coincida con el tipo de parámetro genérico./// Si no se encuentra ningún parámetro pasado con el parámetro genérico especificado, se ignora.public TransitionBuilder<TState, TEvent, TRecipient, TParent> Do<TParameter>(Action<TParameter> action);/// Versión combinada de Do(Action<TRecipient>) y Do(Action<TParameter>).public TransitionBuilder<TState, TEvent , TRecipient, TParent> Do<TParameter>(Action<TRecipient, TParameter> action);/// Configura la política de cómo suscribirse se deben ejecutar los delegados al enlace de entrada./// Si este método no se ejecuta, la política predeterminada es TransitionPolicy.ParentFirstWithCulling.public GotoBuilder<TState, TEvent, TRecipient, TParent> OnEntryPolicy(TransitionPolicy Policy);/// Configura la política de cómo se deben ejecutar los delegados suscritos al enlace de salida. /// Si este método no se ejecuta, la política predeterminada es TransitionPolicy.ChildFirstWithCulling.public GotoBuilder<TState, TEvent, TRecipient, TParent> OnExitPolicy(TransitionPolicy Policy);/// Determina a qué estado va esta transición./// Esto es equivalente a: OnEntryPolicy(TransitionPolicy.ChildFirstWithCulling).OnExitPolicy(TransitionPolicy .ParentFirstWithCulling).Goto(estado).public TParent Goto(TState state);/// Determina transitar al estado actual./// Si runParentsActions es verdadero: se ejecutarán las acciones OnExit y OnEntry del estado actual (pero no los estados principales en caso de que el estado actual sea un subestado) ./// Esto es equivalente a OnEntryPolicy(TransitionPolicy.ChildFirstWithCullingInclusive).OnExitPolicy(TransitionPolicy.ParentFirstWithCullingInclusive).Goto(currentState)./// Si runParentActions es falso: se ejecutarán las acciones OnExit y OEntry del estado actual (y los padres en caso de que el estado actual sea un subestado) ./// Esto es equivalente a OnEntryPolicy(TransitionPolicy.ChildFirst).OnExitPolicy(TransitionPolicy.ParentFirst).Goto(currentState).public TParent GotoSelf(bool runParentsActions = false);/// Determina que no habrá transición a ningún estado, por lo que no habrá ningún evento OnEntry ni OnExit levantado./// Esto es equivalente a OnEntryPolicy(TransitionPolicy.Ignore).OnExitPolicy(TransitionPolicy.Ignore).GotoSelf().public TParent StaySelf();}clase pública sellada GotoBuilder<TState, TEvent, TRecipient, TParent>: IGoto<TState>where TState: notnullwhere TEvent: notnull {/// Configura la política de cómo deben hacerlo los delegados suscritos al gancho de entrada. se ejecutará./// Si este método no se ejecuta, la política predeterminada es TransitionPolicy.ParentFirstWithCulling.public GotoBuilder<TState, TEvent, TRecipient, TParent> OnEntryPolicy(TransitionPolicy Policy);/// Configura la política de cómo los delegados suscritos a Se debe ejecutar el gancho de salida./// Si este método no se ejecuta, la política predeterminada es TransitionPolicy.ChildrenFirstWithCulling.public GotoBuilder<TState, TEvent, TRecipient, TParent> OnExitPolicy(TransitionPolicy Policy);/// Determina a qué estado va esta transición.public TParent Goto(TState state);/// Determina la transición al estado actual./// Esto es un acceso directo de Goto(currentState).public TParent GotoSelf();}/// Determina la política de transición entre dos estados./// Esto configura cómo los delegados suscritos en los estados se ejecutan durante la transición entre estados.public enum TransitionPolicy{/// Determina que los delegados suscritos no deben ejecutarse.Ignore = 0,/// Determina que los delegados suscritos en los padres se ejecutan primero.ParentFirst = 1,/// Determina que los delegados suscritos de los hijos se ejecutan primero. ChildFirst = 2,/// Determina que los delegados suscritos de los padres se ejecutan primero desde (excluyendo) el último padre común entre los dos. States.ParentFirstWithCulling = 3,/// Determina que los delegados suscritos en los niños se ejecutan primero hasta llegar (excluyendo) al último padre común entre los dos estados. ChildFirstWithCulling = 4,/// Determina que los delegados suscritos en los padres se ejecutan primero desde ( incluido) el último padre común entre los dos estados.ParentFirstWithCullingInclusive = 5,/// Determina que los delegados suscritos en los niños se ejecutan primero hasta llegar (incluido) el último padre común entre los dos estados. two States.ChildFirstWithCullingInclusive = 6,}/// Representa una porción de data.public readonly struct ReadOnlySlice<T> : IReadOnlyList<T>{/// Obtenga el elemento especificado en index.public T this[int index] { get ; }/// Obtener el recuento del segmento.public int Count { get; }/// Obtenga una <see cref="ReadOnlyMemory{T}"/> de este segmento.public ReadOnlyMemory<T> Memoria { get; }/// Obtenga un <see cref="ReadOnlySpan{T}"/> de este segmento.public ReadOnlySpan<T> Span { get; }/// Obtener el enumerador de slice.public Enumerator GetEnumerator();/// Enumerador de <see cref="ReadOnlySlice{T}"/>.public struct Enumerator: IEnumerator<T>{/// Obtener el elemento actual del enumerador.public T Actual { get; }/// Se mueve al siguiente elemento de enumeration.public bool MoveNext();/// Restablece enumeration.public void Reset();}}