I'm trying to create some state machines for the little game I'm making with Unity. I started with some enums, but as I want to do some multi layer state machines, it's not really a good way. Looking around the internet, I found some interesting ideas, and I came to this kind of implementation
public interface IStateMachine
{
public BaseState CurrentState { get; set; }
}
public abstract class BaseState
{
private IStateMachine _ctx;
// ... other state stuffs
}
That I'm trying to adapt to use a generic type for the context, like this
public abstract class BaseState<T> where T: IStateMachine
{
private T _ctx;
// ... other state stuffs
}
But that won't compile, saying that I forgot a <T>
to BaseState<T> CurrentState
in my first interface.
Using the generic type BaseState need 1 type arguments
But how could I tell him that T is the class that implements my interface ?
I tried some stuff, like adding a generic to my interface
public interface IStateMachine<T> where T : IStateMachine<T>
{
public BaseState<T> CurrentState { get; set; }
}
but it simply tell me
The type 'T' cannot be used as type parameter 'T' in the generic type or method 'BaseState'. There is no implicit reference conversion from 'T' to 'IStateMachine'.
I'm probably missing a simple fact about generics, and this question might be a duplicate of this question (state machine, unity, but from 7 years ago).
CodePudding user response:
The problem is probably that you do not declare the type when you make the actual state implementation:
public class StateMachine : IStateMachine
...
public class StateA : BaseState<StateMachine > // need to specify T
public class StateB<T>: BaseState<T> where T : IStateMachine // let caller specify T
But the problem you will run into is that you have to declare all the methods and properties you want to use on the common base type. States need to reference each other, so you will need to use the base type in these references. So you will not really gain anything by using generics.
A workaround for this is to use the Curiously recurring template pattern. i.e.
public abstract class BaseState<T> where T: BaseState<T>{
public T Next {get;}
This essentially gives a way to promise that the entire graph of states will have a common type. This can still allow for methods that do things like traversing the graph, without any need to know the actual type, for example:
public static T NextNext<T>(BaseState<T> start) where T : BaseState<T>{
return start.Next.Next;
}
public class MyState : BaseState<MyState>{
...
}
var myState = new MyState(...);
MyState nn = myState.NextNext(); // Keep the type when traversing the graph
CodePudding user response:
The CurrentState Property inside the IStateMachine probably references to the BaseState which needs a generic type. Either way your structure seem a bit tough because it kind of has a recursive link. IStateMachine needs a BaseState but a BaseState needs a IStateMachine?
I would suggest not trying to let the BaseState know what StateMachine it is attached to.
Another suggestion would be to create another Class "StateMachineContext" which the BaseState has a reference to and this StateMachineContext has a reference to IStateMachine.
Yet another approach would be to have it similiar to the following code:
public interface IStateMachine
{
public BaseState CurrentState { get; set; }
}
public abstract class BaseState
{
// Some functionality that IStateMachine needs access
// to.
}
public abstract class BaseState<T> : BaseState where T : IStateMachine
{
private T _ctx;
// ... other state stuffs
}