I'm trying to make a reusable state machine in Unity, supposedly designed to work with both regular states and hierarchical states.
I have 3 main abstract classes, that being BaseState, StateMachine and StateFactory.
BaseState
public abstract class BaseState<EState>
where EState : Enum
{
protected StateFactory<EState, BaseState<EState>> _factory;
protected StateMachine<EState, BaseState<EState>> _machine;
protected EState _stateKey;
public BaseState(EState stateKey, StateMachine<EState, BaseState<EState>> machine, StateFactory<EState, BaseState<EState>> factory)
{
_stateKey = stateKey;
_machine = machine;
_factory = factory;
}
//Irrelevant logics
}
HierarchicalBaseState
public abstract class HBaseState<EState> : BaseState<EState> where EState : Enum
{
//whether this state is a primary state
protected bool _isRootState = false;
//references to the parent and child of this state;
protected HBaseState<EState> _parentState;
protected HBaseState<EState> _childState;
public HBaseState(EState stateKey, StateMachine<EState, HBaseState<EState>> machine, StateFactory<EState, HBaseState<EState>> factory, bool isRootState = false) : base(stateKey, *machine, factory*)
{
_stateKey = stateKey;
_isRootState = isRootState;
}
//Irrelevant logics
}
StateMachine
public abstract class StateMachine<EState, StateType> : MonoBehaviour
where EState : Enum
where StateType : BaseState<EState>
{
protected StateFactory<EState, StateType> _factory;
public StateType _currentState;
//Irrelevant logics
}
StateFactory
public abstract class StateFactory<EState, StateType> : MonoBehaviour
where EState : Enum
where StateType : BaseState<EState>
{
protected StateMachine<EState, StateType> _machine;
protected Dictionary<EState, StateType> _statesCached = new Dictionary<EState, StateType>();
public StateFactory(StateMachine<EState, StateType> machine, bool cacheStates = true)
{
_machine = machine;
InstantiateStates();
}
protected abstract void InstantiateStates();
//Irrelevant code
}
Problem arises when I try creating a constructor for the hierarchical state class that inherits BaseState.
Apparently C# can't convert StateMachine<EState, HBaseState<EState>>
to StateMachine<EState, BaseState<EState>>
(code example above), so I can't pass parameters from HBaseState to its base BaseState constructor. Tried typecasting but it doesn't work too.
Is my implementation of generics even correct in the first place? How can I refactor my code so that it works?
This is my first time using StackOverflow so thanks a lot for any help and suggestions :D
Apparently C# can't convert StateMachine<EState, HBaseState> to StateMachine<EState, BaseState>
That is correct, since these are two different types, without any variance that might allow conversion.
In some cases the Curiously recurring template pattern might help. This can help ensure multiple related type agree on what T
is. I.e something like:
public abstract class Base<T> where T : Base<T>
{
public StateMachine<T> Machine;
}
public class Derived1 : Base<Derived1>
{
public void Test()
{
Derived1 current = base.Machine.CurrentState;
}
}
public class Derived2 : Base<Derived2>
{
public void Test()
{
Derived2 current = base.Machine.CurrentState;
}
}
public class StateMachine<T> where T : Base<T>
{
public T CurrentState { get; }
}
Note that this will not help if you want multiple different state-types within the same state machine. If that is the case you need to use plain old runtime polymorphism:
public abstract class Base
{
public StateMachine Machine;
}
public class Derived1 : Base
{ }
public class StateMachine : Base
{
public Base CurrentState { get; }
}
Speaking from experience, it can be tempting to over design things like state machines to be overly generic and abstract. This usually ends badly, with compiler errors, or just unreadable code as a result. My recommendation would be to start as simple as possible, and move to more complicated models as needed. And not try to build something overly abstract and complex in the name of re-usability, since complexity can be a hindrance to re-usability itself.