feat: add state machine

This commit is contained in:
Bragin Stepan
2026-03-02 19:00:06 +05:00
parent 99c88c071f
commit 7737ee3158
50 changed files with 828 additions and 128 deletions

View File

@@ -3,7 +3,7 @@ using System.Collections.Generic;
namespace _Project.Develop.Runtime.Utils.ReactiveManagement.Event
{
public class ReactiveEvent<T> : IReadOnlyEvent<T>
public class ReactiveEvent<T> : IReadOnlyEvent<T>
{
private readonly List<Subscriber<T>> _subscribers = new();
private readonly List<Subscriber<T>> _toAdd = new();
@@ -41,9 +41,9 @@ namespace _Project.Develop.Runtime.Utils.ReactiveManagement.Event
public class ReactiveEvent : IReadOnlyEvent
{
private readonly List<Subscriber> _subscribers = new();
private readonly List<Subscriber> _toAdd = new();
private readonly List<Subscriber> _toRemove = new();
private readonly List<Subscriber> _subscribers = new List<Subscriber>();
private readonly List<Subscriber> _toAdd = new List<Subscriber>();
private readonly List<Subscriber> _toRemove = new List<Subscriber>();
public IDisposable Subscribe(Action action)
{

View File

@@ -5,16 +5,16 @@ namespace _Project.Develop.Runtime.Utils.ReactiveManagement
{
public class ReactiveVariable<T> : IReadOnlyVariable<T> where T : IEquatable<T>
{
private readonly List<Subscriber<T, T>> _subscribers = new ();
private readonly List<Subscriber<T, T>> _toAddList = new ();
private readonly List<Subscriber<T, T>> _toRemoveList = new ();
private readonly List<Subscriber<T, T>> _subscribers = new List<Subscriber<T, T>>();
private readonly List<Subscriber<T, T>> _toAddList = new List<Subscriber<T, T>>();
private readonly List<Subscriber<T, T>> _toRemoveList = new List<Subscriber<T, T>>();
public ReactiveVariable() => _value = default(T);
public ReactiveVariable(T value) => _value = value;
private T _value;
public T Value
{
get => _value;
@@ -27,33 +27,33 @@ namespace _Project.Develop.Runtime.Utils.ReactiveManagement
Invoke(oldValue, _value);
}
}
public IDisposable Subscribe(Action<T, T> action)
{
Subscriber<T, T> subscriber = new (action, RemoveSubscriber);
_toAddList.Add(subscriber);
return subscriber;
}
public IDisposable Subscribe(Action<T, T> action)
{
Subscriber<T, T> subscriber = new Subscriber<T, T>(action, RemoveSubscriber);
_toAddList.Add(subscriber);
return subscriber;
}
private void RemoveSubscriber(Subscriber<T, T> subscriber) => _toRemoveList.Add(subscriber);
private void Invoke(T oldValue, T newValue)
{
if(_toAddList.Count > 0)
if (_toAddList.Count > 0)
{
_subscribers.AddRange(_toAddList);
_toAddList.Clear();
}
if(_toRemoveList.Count > 0)
if (_toRemoveList.Count > 0)
{
foreach (Subscriber<T, T> subscriber in _toRemoveList)
_subscribers.Remove(subscriber);
_toRemoveList.Clear();
}
foreach (Subscriber<T, T> subscriber in _subscribers)
subscriber.Invoke(oldValue, newValue);
}

View File

@@ -0,0 +1,14 @@

using _Project.Develop.Runtime.Utils.ReactiveManagement.Event;
namespace Assets._Project.Develop.Runtime.Utilities.StateMachineCore
{
public interface IState
{
IReadOnlyEvent Entered { get; }
IReadOnlyEvent Exited { get; }
void Enter();
void Exit();
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: aed062833c374a6c8f4f784616dad179
timeCreated: 1772453453

View File

@@ -0,0 +1,7 @@
namespace Assets._Project.Develop.Runtime.Utilities.StateMachineCore
{
public interface IUpdatableState : IState
{
void Update(float deltaTime);
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: c1ea582554484972bd37276974ad4434
timeCreated: 1772453453

View File

@@ -0,0 +1,32 @@
using System.Collections.Generic;
namespace Assets._Project.Develop.Runtime.Utilities.StateMachineCore
{
public abstract class ParallelState<TState> : State where TState : class, IState
{
private List<TState> _states;
public ParallelState(params TState[] states)
{
_states = new List<TState>(states);
}
protected IReadOnlyList<TState> States => _states;
public override void Enter()
{
base.Enter();
foreach (TState state in _states)
state.Enter();
}
public override void Exit()
{
base.Exit();
foreach (TState state in _states)
state.Exit();
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 3084d8be941e4eb89f019da66b818702
timeCreated: 1772453453

View File

@@ -0,0 +1,18 @@
using _Project.Develop.Runtime.Utils.ReactiveManagement.Event;
namespace Assets._Project.Develop.Runtime.Utilities.StateMachineCore
{
public abstract class State : IState
{
private ReactiveEvent _entered = new();
private ReactiveEvent _exited = new();
public IReadOnlyEvent Entered => _entered;
public IReadOnlyEvent Exited => _exited;
public virtual void Enter() => _entered.Invoke();
public virtual void Exit() => _exited.Invoke();
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 975fa4387d1e4f37afb584f32071e4bc
timeCreated: 1772453453

View File

@@ -0,0 +1,96 @@
using System;
using System.Collections.Generic;
using System.Linq;
using _Project.Develop.Runtime.Utilities.Conditions;
namespace Assets._Project.Develop.Runtime.Utilities.StateMachineCore
{
public abstract class StateMachine<TState> : State, IDisposable, IUpdatableState where TState : class, IState
{
private List<StateNode<TState>> _states = new();
private StateNode<TState> _currentState;
private bool _isRunning;
private List<IDisposable> _disposables;
protected StateMachine(List<IDisposable> disposables)
{
_disposables = new List<IDisposable>(disposables);
}
protected TState CurrentState => _currentState.State;
public void AddState(TState state) => _states.Add(new StateNode<TState>(state));
public void AddTransition(TState fromState, TState toState, ICondition condition)
{
StateNode<TState> from = _states.First(stateNode => stateNode.State == fromState);
StateNode<TState> to = _states.First(stateNode => stateNode.State == toState);
from.AddTransition(new StateTransition<TState>(to, condition));
}
public void Update(float deltaTime)
{
if (_isRunning == false)
return;
foreach (StateTransition<TState> transition in _currentState.Transitions)
{
if (transition.Condition.Evaluate())
{
SwitchState(transition.ToState);
break;
}
}
UpdateLogic(deltaTime);
}
protected virtual void UpdateLogic(float deltaTime) { }
public void Dispose()
{
_isRunning = false;
foreach (StateNode<TState> stateNode in _states)
if (stateNode.State is IDisposable disposableState)
disposableState.Dispose();
_states.Clear();
foreach (IDisposable disposable in _disposables)
disposable.Dispose();
_disposables.Clear();
}
public override void Enter()
{
base.Enter();
if (_currentState == null)
SwitchState(_states[0]);
_isRunning = true;
}
public override void Exit()
{
base.Exit();
_currentState?.State.Exit();
_isRunning = false;
}
private void SwitchState(StateNode<TState> nextState)
{
_currentState?.State.Exit();
_currentState = nextState;
_currentState.State.Enter();
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 421f6f14fa064baaa737bea3684d473f
timeCreated: 1772453453

View File

@@ -0,0 +1,20 @@
using System.Collections.Generic;
namespace Assets._Project.Develop.Runtime.Utilities.StateMachineCore
{
public class StateNode<TState> where TState : class, IState
{
private List<StateTransition<TState>> _transitions = new();
public StateNode(TState state)
{
State = state;
}
public TState State { get; }
public IReadOnlyList<StateTransition<TState>> Transitions => _transitions;
public void AddTransition(StateTransition<TState> transition) => _transitions.Add(transition);
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: dce697e6ac3b45b497e8e89c5ca258cc
timeCreated: 1772453453

View File

@@ -0,0 +1,16 @@
using _Project.Develop.Runtime.Utilities.Conditions;
namespace Assets._Project.Develop.Runtime.Utilities.StateMachineCore
{
public class StateTransition<TState> where TState : class, IState
{
public StateTransition(StateNode<TState> toState, ICondition condition)
{
ToState = toState;
Condition = condition;
}
public StateNode<TState> ToState { get; }
public ICondition Condition { get; }
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 07aef2e29c2643419ff17e32556da944
timeCreated: 1772453453

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 831af1f46fb24b5c89e15c287f7bdfad
timeCreated: 1772456814

View File

@@ -0,0 +1,67 @@
using System;
using System.Collections;
using _Project.Develop.Runtime.Utils.ReactiveManagement;
using _Project.Develop.Runtime.Utils.ReactiveManagement.Event;
using Assets._Project.Develop.Runtime.Utilities.CoroutinesManagement;
using UnityEngine;
namespace Assets._Project.Develop.Runtime.Utilities.Timer
{
public class TimerService : IDisposable
{
private float _cooldown;
private ReactiveEvent _cooldownEnded;
private ReactiveVariable<float> _currentTime;
private ICoroutinesPerformer _coroutinePerformer;
private Coroutine _cooldownProcess;
public TimerService(
float cooldown,
ICoroutinesPerformer coroutinePerformer)
{
_cooldown = cooldown;
_coroutinePerformer = coroutinePerformer;
_cooldownEnded = new ReactiveEvent();
_currentTime = new ReactiveVariable<float>();
}
public IReadOnlyEvent CooldownEnded => _cooldownEnded;
public IReadOnlyVariable<float> CurrentTime => _currentTime;
public bool IsOver => _currentTime.Value <= 0;
public void Dispose()
{
Stop();
}
public void Stop()
{
if (_cooldownProcess != null)
_coroutinePerformer.StopPerform(_cooldownProcess);
}
public void Restart()
{
Stop();
_cooldownProcess = _coroutinePerformer.StartPerform(CooldownProcess());
}
private IEnumerator CooldownProcess()
{
_currentTime.Value = _cooldown;
while (IsOver == false)
{
_currentTime.Value -= Time.deltaTime;
yield return null;
}
_cooldownEnded.Invoke();
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: e5636415f9a840ab8ff88d96719179ec
timeCreated: 1772456820

View File

@@ -0,0 +1,18 @@
using Assets._Project.Develop.Runtime.Infrastructure.DI;
using Assets._Project.Develop.Runtime.Utilities.CoroutinesManagement;
namespace Assets._Project.Develop.Runtime.Utilities.Timer
{
public class TimerServiceFactory
{
private readonly DIContainer _container;
public TimerServiceFactory(DIContainer container)
{
_container = container;
}
public TimerService Create(float cooldown)
=> new TimerService(cooldown, _container.Resolve<ICoroutinesPerformer>());
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 6fbab9cab89b4ad094fc636facb8597a
timeCreated: 1772456820