Implementing generic type on a state machine

¡Hi! I’m working on a project with machine states and i have questions regarding implementing generic type T.

Ive a main class “MachineState” which implements T, because i want to pass the entity (ex: BoxEnemy).
MachineState has the currentState of the entity.
Problem is I need to acces currentState inside the state, but i can’t because T doesn’t has type MachineState.

//BoxEnemy it's an example of entity class.
 
    public class BoxEnemy : MachineState<BoxEnemy>
    {
        public string nombre = "Box";
         
        protected override void Update()
        {
            base.OnUpdate(this);
        }
    }
     
     
    public abstract class MachineState<T> : MonoBehaviour
    {
        public State<T> currentState;
         
        protected virtual void OnUpdate(T machine)
        {
            if (currentState != null)
            {
                currentState.OnUpdate(machine);
            }
        }
    }
 
 
    public class State<T>
    {
        public StateActions<T>[] onUpdate;
 
        public void OnUpdate(T states)
        {
            //Here I would like to acces currentState
            states.currentState //gives me error as currentState doesn't exists.
        }
    }

Maybe this is something obvious but ive never worked with generic types and it’s driving me crazy.

Thanks for your time <3

I’m a little confused by your code. Does State<T> represent the state machine state, or does MachineState<T> represent the state?

Also, where exactly are you trying to access the currentState variable from? If you’re talking about from inside BoxEnemy you should have no problem accessing the variable. If you’re talking about from within State, then the current object is the currentState variable, depending on context. So you could just access it by writing this.

Uh…based on the way you are calling OnUpdate from line 22, the way you would access currentState is with the keyword “this”, because the State that’s executing the OnUpdate function is the currentState. (Of course, you won’t be able to change the currentState, because you have access to the actual object but not the variable that refers to it.)

But I’m really not sure what these classes are for or how you imagine this system to work when you’re done.

To answer the more general question of how you access specific parts of a generic type like T, you would have to put a constraint on the generic parameter. Although when your constraint is something as specific as “has to be this specific class or a subtype”, then you should pause and consider whether you really need a generic at all, rather than just using that class’s type. You might also consider creating an interface instead.

1 Like

I’ve edited the post to clarify how I want to acces current state.

State is a class that makes the behaviour happen. It has an array of actionState which are behaviours.
MachineState is the class that executes the currentState and hold currentstate.

BoxEnemy is an entity class, which has attributes of the enemy, so then actionState can recieve T as boxEnemy and get acces to attributes of BoxEnemy entity class.

You need to provide some constraints for your generics – it gets a bit hairy when you’re doing recursive generics but it should look something like this:

public class BoxEnemy : StateMachine<BoxEnemy> {
    public string nombre = "Box";
}

public abstract class StateMachine<T> : MonoBehaviour where T : StateMachine<T> {
    public State<T> currentState;

    protected virtual void Update () {
        if( currentState != null ) {
            currentState.OnUpdate( (T)this );
        }
    }
}

public abstract class State<T> where T : StateMachine<T> {
    public StateActions<T>[] onUpdate;

    public virtual void OnUpdate ( T obj ) {
        obj.currentState // <-- currentState is accessible here
    }
}

public class ExampleBoxEnemyState : State<BoxEnemy> {
    public override void OnUpdate ( BoxEnemy obj ) {
        obj.nombre = "Example Box"; // <-- your BoxEnemy is accessible here
    }
}

I’m not sure that this is how you want to be designing a state machine regardless, but here you go. I remember getting myself into some very strange generic type situations when I first started using them.

EDIT: Modified the snippet to be working and not be a degenerate case.

Nevermind, thanks you guys for answering me. After trying it more times with different logics and combinations what I did is pass currentState as a parameter

  • public void OnUpdate(T states, State currentState)
  • {
  • states.currentState
  • }

Maybe i’m making a wrong structure (probably), but i’m just exploring different structures and trying to find the good one that fits my project.
I’ve spend last 2 hours trying a way to do this and when I post it I find it xD

That’s also a good answer, maybe i user your solution. As i said in last reply i know i’m probably doing wrong with this structure. Thanks!

But you already were passing the current state as a parameter! Now you’re passing it twice.

Because when you write
currentState.OnUpdate(machine);
…that really means that “currentState” is a special hidden parameter to the function (and the parameter’s name is “this”).

This gets really obvious when you start looking at the syntax for extension methods. If you had implemented OnUpdate as an extension method, its signature would look like

public static void OnUpdate(this State<T> currentState, T states)

and it would still be called with the syntax currentState.OnUpdate(states)

You just changed it to

public static void OnUpdate(this State<T> currentState, T states, State<T> currentState)
1 Like

Bu-whaaaaa? I don’t think you can do that. That’s fully recursive with no base case.

What’s an example of a type that you think would be valid to bind to the parameter T?

You can’t use, say, T = Object, because that obviously violates the constraint T : StateMachine.

If you say T = StateMachine, then we’ve satisfied the (hypothetical) constraint T : StateMachine, but we still haven’t satisfied the rule T : StateMachine<T>. We can easily see this if we just replace each T with the binding for T, yielding the false statement that
StateMachine<Object> : StateMachine<StateMachine<Object>>
which is just as untrue as our original try when T = Object.

As soon as we turned T into a StateMachine<>, your constraint demanded a StateMachine<StateMachine<>>. If we continue down this path by turning T into a StateMachine<StateMachine<>>, then your constraint will immediately demand a StateMachine<StateMachine<StateMachine<>>>. And so on.

2 Likes

Hate to say that, but your mind fooled you. :stuck_out_tongue:

That pattern does actually exist and it works, and it’s useful in some cases. In Unity, it’s one of the keys for MonoBehaviour based singletons (which are not only singletons by some lose “agreement”, “rules” or “scene setup”, but instead are enforced singletons due to their technical details, constraints and the implied characteristics). Still, many publicly available implementations don’t use it.

Whether that’s the correct pattern for the question at hand, I’m not sure. Probably not.

You’re totally correct that the snippet as posted is incorrect & doesn’t compile, since the declaration of currentState is infinitely recursive – there’s no conversion between StateMachine<T> and StateMachine<StateMachine<T>>, and so on. Well reasoned – teach me for coding C# in between meetings!

However, this pattern isn’t totally impossible with a slight modification. I don’t recommend this to be used in any production scenario as it is horribly confusing*, but it does work with the compiler:

public class BoxEnemy : StateMachine<BoxEnemy> {
    public string nombre = "Box";

   private void Update () {
       OnUpdate( this );
   }
}

public abstract class StateMachine<T> : MonoBehaviour where T : StateMachine<T> {
    public State<T> currentState;

   protected void OnUpdate ( T obj ) {
       currentState.Update( obj );
   }

}

public abstract class State<T> where T : StateMachine<T> {
    public virtual void Update ( T obj ) {
       Debug.Log( obj.currentState ); // <-- StateMachine props are accessible here
   }
}

public class ExampleBoxEnemyState : State<BoxEnemy> {
   public override void Update ( BoxEnemy obj ) {
       obj.nombre = "Example Box"; // <-- your BoxEnemy is accessible here
   }
}

Here, we’re skating around the infinite case by only dealing with known types. Instead of defining the currentState as State<StateMachine<T>>, we’re defining it as just T. And instead of passing this to OnUpdate, which can’t be converted from StateMachine<T> to T, we’re passing in a T obj which doesn’t need to be converted at all.

*But for @joan_stark specifically (or anyone else playing around with data structures), go for it! Exploring the eccentricities of the type system will deepen your understanding of the language & programming as a whole & I highly recommend it.

2 Likes

I suspect you are confusing this with CRTP, where a subclass uses itself as the parameter for a parent class, like this:

class Base<T>

class Derived : Base<Derived>

If you are not confusing this with CRTP, then please point me to an actual working example of recursive generic constraints.

I don’t see how any of this helps. The one exact line of code that I had a problem with has not changed in any way. There is still no legal way for you to fill in the blank in the following variable declaration:

StateMachine<_____> myVariable;

And yet, C# has no complaints if you’re not trying to convert between types. Here’s the exact code I used:

using UnityEngine;

// RecursionTest.cs
public class RecursionTest : MonoBehaviour {
    public void Awake () {
        Debug.Log( "Begin recursion test!" );
        var boxEnemyGo = new GameObject( "box" );
        var enemy = boxEnemyGo.AddComponent<BoxEnemy>();
        enemy.currentState = new ExampleBoxEnemyState();
    }
}

// StateMachine.cs
public abstract class StateMachine<T> : MonoBehaviour where T : StateMachine<T> {
    public State<T> currentState;

    public StateMachine<T> myLocalStateMachine;

    private void Awake () {
        Debug.Log( "The state machine is alive! My type is: " + typeof( StateMachine<T> ) );
    }

    protected void OnUpdate ( T obj ) {
        Debug.Log( "Updating state with object: " + obj.GetType() );
        if( currentState != null ) {
            currentState.Update( obj );
        }
    }

}

public abstract class State<T> where T : StateMachine<T> {
    public virtual void Update ( T obj ) {
        Debug.Log( "I'm in the base state! " + obj.currentState ); // <-- StateMachine props are accessible here
    }
}

// BoxEnemy.cs
public class BoxEnemy : StateMachine<BoxEnemy> {
    public string nombre = "Box";

    private void Update () {
        OnUpdate( this );
    }
}


public class ExampleBoxEnemyState : State<BoxEnemy> {
    public override void Update ( BoxEnemy obj ) {
        base.Update( obj );
        obj.nombre = "Example Box"; // <-- your BoxEnemy is accessible here
        Debug.Log( "I'm in the derived state! " + obj.nombre );
    }
}

And here’s that code running in the UnityEditor:
6638659--757603--2020-12-18_15-27-38.png

Think about it this way: Generics aren’t “real” in the sense that they get compiled down to non-generic types by the compiler. There is no such thing as StateMachine in the runtime, barring some reflection metadata.

The generic classes in the above snippet are essentially become this when compiled (obviously the compiler doesn’t literally work this way, but for demonstration purposes it’s close enough):

public class BoxEnemy : StateMachine_BoxEnemy {
   public string nombre = "Box";

   private void Update () {
       OnUpdate( this );
   }
}

public class StateMachine_BoxEnemy : MonoBehaviour {
    State_BoxEnemy currentState;

    protected void OnUpdate ( BoxEnemy obj ) {
        if( currentState != null ) {
            currentState.Update( obj );
        }
    }
}

public class State_BoxEnemy {
    public virtual void Update ( BoxEnemy obj ) {

    }
}

public class ExampleBoxEnemyState : State_BoxEnemy {
    public override void Update ( BoxEnemy obj ) {

    }
}

There’s no infinite regression of nested types here – everything can be resolved to a known type. Actually, in my other example that doesn’t work, I was very close to having it working:

public class BoxEnemy : StateMachine<BoxEnemy> {
    public string nombre = "Box";
}

public abstract class StateMachine<T> : MonoBehaviour where T : StateMachine<T> {
    public State<T> currentState;

    protected virtual void Update () {
        if( currentState != null ) {
            currentState.OnUpdate( (T)this );
        }
    }
}

public abstract class State<T> where T : StateMachine<T> {

    public virtual void OnUpdate ( T obj ) {
        obj.currentState // <-- currentState is accessible here
    }
}

public class ExampleBoxEnemyState : State<BoxEnemy> {
    public override void OnUpdate ( BoxEnemy obj ) {
        obj.nombre = "Example Box"; // <-- your BoxEnemy is accessible here
    }
}

All I needed to do is remember to cast this to the known type. This can get resolved as follows without a degenerate case:

public class BoxEnemy : StateMachine_BoxEnemy {
    public string nombre = "Box";
}

public class StateMachine_BoxEnemy : MonoBehaviour {
    public State_BoxEnemy currentState;

    protected virtual void Update () {
        if( currentState != null ) {
            currentState.OnUpdate( (BoxEnemy)this );
        }
    }
}

public abstract class State_BoxEnemy {

    public virtual void OnUpdate ( BoxEnemy obj ) {
        obj.currentState // <-- currentState is accessible here
    }
}

public class ExampleBoxEnemyState : State_BoxEnemy {
    public override void OnUpdate ( BoxEnemy obj ) {
        obj.nombre = "Example Box"; // <-- your BoxEnemy is accessible here
    }
}

It’s just the line of State<StateMachine<T>> currentState that becomes an unresolvable infinite tree of generics. Once you clean up that line, it becomes very possible!

No, I’m not confusing it.

If I were referring to what you described (without the constraint), the example I’ve provided would have been a really bad one. The CRTP without constraint does not satisfy the more specialized characteristics of the same pattern with a constraint to the base type itself.

…huh. I guess I stand corrected.

And the answer to my explicit question was apparently “you can fill in that blank using a subclass that you are defining while you fill it in”. So…it can only be used in combination with CRTP?

Basically, yes.

Thanks for the awswers, i’m learning a lot with them about generics. It’s quite difficult to understand them, but as always, using them, reading and mistaking, helps.

No,no,no :slight_smile: A lot people seem to confuse C# generics with C++ templates. They have nothing in common besides their similar syntax. The above mentioned pattern does work just fine:

public class StateMachine<T> : MonoBehaviour where T : StateMachine<T>{

Keep in mind that a generic constraint does not define inheritance but only a type constraint. This pattern, as mentioned above, is commonly used for monobehaviour singletons.

Yes, you can not use „Object„ as type parameter, that‘s the point. The only valid Type for T is the derived type itself. So this does work:

public class Example : StateMachine<Example>

However you can not build longer inheritance chains that way. The inheritance is limited / locked to exactly one layer. So the usage of the State class does not work.

A lot people also confuse generics with base classes. Though generics are kinda the opposite of a baseclass. Two types derived from the same generic class but with different generic type arguments have nothing in common. Just like a List<int> and a List<string> have nothing in common and are completely incompatible with each other.

1 Like

@Bunny83 , I’ve been pretty impressed with many of your recent posts, but here I suspect you are just reciting a nice-sounding quip that you heard once and haven’t carefully thought about:

Well, their similar syntax, plus the fact that they solve a similar set of programming problems, using a similar set of programming patterns, to the extent that many (though not all) C++ templated classes can be ported one-for-one into C# generic classes (and vice versa) while retaining the same practical functionality…oh, and the fact that C# generics were specifically modeled on C++ templates…

This seems kind of like complaining that people “confuse” electric cars with combustion-engine cars despite the fact that batteries and combustion engines operate nothing alike.

In any event, if you somehow made use of the differences between templates and generics in your following explanation, I’m afraid I can’t quite figure out how. You’re obviously not claiming that CRTP can’t be used in C#, because you used it in your own C# example code.