Code/State.cs
using System;
using System.Collections.Generic;
namespace Sandbox.States;
public sealed class State : IValid
{
private readonly List<Transition> _orderedTransitions = new();
private bool _transitionsDirty = false;
public StateMachineComponent StateMachine { get; }
/// <summary>
/// Unique ID of this state in its containing <see cref="StateMachineComponent"/>.
/// </summary>
public int Id { get; }
/// <summary>
/// Helpful name of this state.
/// </summary>
public string Name { get; set; } = "Unnamed";
public bool IsValid { get; internal set; }
public IReadOnlyList<Transition> Transitions
{
get
{
if ( _transitionsDirty ) UpdateTransitions();
return _orderedTransitions;
}
}
/// <summary>
/// Event dispatched on the owner when this state is entered.
/// </summary>
public Action? OnEnterState { get; set; }
/// <summary>
/// Event dispatched on the owner while this state is active.
/// </summary>
public Action? OnUpdateState { get; set; }
/// <summary>
/// Event dispatched on the owner when this state is exited.
/// </summary>
public Action? OnLeaveState { get; set; }
public Vector2 EditorPosition { get; set; }
internal State( StateMachineComponent stateMachine, int id )
{
StateMachine = stateMachine;
Id = id;
}
private void UpdateTransitions()
{
_transitionsDirty = false;
_orderedTransitions.Clear();
foreach ( var transition in StateMachine.Transitions )
{
if ( transition.Source == this )
{
_orderedTransitions.Add( transition );
}
}
_orderedTransitions.Sort();
}
internal Transition? GetNextTransition( string message )
{
foreach ( var transition in Transitions )
{
if ( transition.Target == this )
{
// TODO
continue;
}
if ( transition.Message != message ) continue;
var (minDelay, maxDelay) = transition.DelayRange;
if ( StateMachine.StateTime < minDelay || StateMachine.StateTime > maxDelay ) continue;
try
{
if ( transition.Condition?.Invoke() is not false )
{
return transition;
}
}
catch ( Exception e )
{
Log.Error( e );
}
}
return null;
}
private Transition? _defaultTransition;
private float _defaultTransitionDelay;
internal void Entered()
{
// For transitions with a delay range but no condition,
// pick a random delay in that range and find the
// smallest to be the default transition
_defaultTransition = null;
_defaultTransitionDelay = float.PositiveInfinity;
foreach ( var transition in Transitions )
{
if ( transition.Target == this )
{
// TODO
continue;
}
if ( transition.IsConditional ) continue;
var (minDelay, maxDelay) = transition.DelayRange;
if ( minDelay > _defaultTransitionDelay ) continue;
var delay = minDelay >= maxDelay ? minDelay : Random.Shared.Float( minDelay, maxDelay );
if ( delay > _defaultTransitionDelay ) continue;
_defaultTransition = transition;
_defaultTransitionDelay = delay;
}
}
internal Transition? GetNextTransition( float prevTime, float nextTime )
{
// Check conditional transitions within this time window
foreach ( var transition in Transitions )
{
if ( transition.Target == this )
{
// TODO
continue;
}
if ( transition.Condition is not { } condition ) continue;
if ( transition.Message is not null ) continue;
var (minDelay, maxDelay) = transition.DelayRange;
if ( nextTime < minDelay || prevTime > maxDelay )
{
continue;
}
try
{
if ( condition.Invoke() )
{
return transition;
}
}
catch ( Exception e )
{
Log.Error( e );
}
}
// Fall back to default transition
return nextTime >= _defaultTransitionDelay
? _defaultTransition
: null;
}
public Transition AddTransition( State target )
{
return StateMachine.AddTransition( this, target );
}
public void Remove()
{
if ( !IsValid ) return;
StateMachine.RemoveState( this );
}
internal void InvalidateTransitions()
{
_transitionsDirty = true;
}
internal record Model( int Id, string Name, Action? OnEnterState, Action? OnUpdateState, Action? OnLeaveState, Model.UserDataModel? UserData )
{
public record UserDataModel( Vector2 Position );
}
internal Model Serialize()
{
return new Model( Id, Name, OnEnterState, OnUpdateState, OnLeaveState, new Model.UserDataModel( EditorPosition ) );
}
internal void Deserialize( Model model )
{
Name = model.Name;
OnEnterState = model.OnEnterState;
OnUpdateState = model.OnUpdateState;
OnLeaveState = model.OnLeaveState;
EditorPosition = model.UserData?.Position ?? Vector2.Zero;
}
public override string ToString()
{
return $"{{ Id = {Id}, Name = {Name} }}";
}
}