2
\$\begingroup\$

I made a generic interpolator for Unity objects, but the way it functions makes me question if there's a better way to approach this problem.

I want the logic inside of the Tween<T> function to be the same for every type. I don't want to create many overrides for unique types because that would cause a lot of pointless code duplication.

The biggest "code smell" I can see is in the LerpFunction<T> where I am switching based on the type, and also casting things back and forth like a madman. Is there a better pattern which lets me accomplish what I am trying to do?

public class Tweener
{
    public static IEnumerator Tween<T>(T startValue, T endValue, float duration, Action<T> setter)
    {
        var startTime = Time.time;
        setter(startValue);
        while (Time.time - startTime < duration)
        {
            var t = (Time.time - startTime) / duration;
            t = Mathf.Clamp(t, 0f, 1f);

            var currentValue = LerpFunction(startValue, endValue, t);
            setter(currentValue);
            yield return null;
        }
        setter(endValue);
    }

    private static T LerpFunction<T>(T a, T b, float t)
    {
        return a switch
        {
            float => (T)(object)Mathf.Lerp((float)(object)a, (float)(object)b, t),
            Vector3 => (T)(object)Vector3.Lerp((Vector3)(object)a, (Vector3)(object)b, t),
            Quaternion => (T)(object)Quaternion.Slerp((Quaternion)(object)a, (Quaternion)(object)b, t),
            Color => (T)(object)Color.Lerp((Color)(object)a, (Color)(object)b, t),
            _ => throw new NotImplementedException($"Cannot interpolate function of type {typeof(T)}")
        };
    }
}
\$\endgroup\$

1 Answer 1

4
\$\begingroup\$

Let's refactor it in 3 simple steps

Step 1: Use type pattern matching and when

  • We can use the type pattern matching capability of switch expression
  • By specifying names (lhs and rhs) the casting will be done
    • automatically for lhs
    • and semi-automatically for rhs
private static T LerpFunction<T>(T a, T b, float t)
=> a switch
{
    float lhs when b is float rhs => (T)(object)Math.Lerp(lhs, rhs, t),
    Vector3 lhs when b is Vector3 rhs => (T)(object)Vector3.Lerp(lhs, rhs, t),
    Quaternion lhs when b is Quaternion rhs => (T)(object)Quaternion.Slerp(lhs, rhs, t),
    Color lhs when b is Color rhs => (T)(object)Color.Lerp(lhs, rhs, t),
    _ => throw new NotImplementedException($"Cannot interpolate function of type {typeof(T)}")
};

Step 2: Use ValueTuple

  • We can branch based on multiple variables by putting them into a ValueTuple
private static T LerpFunction<T>(T a, T b, float t)
=> (a, b) switch
{
    (float lhs, float rhs) => (T)(object)Math.Lerp(lhs, rhs, t),
    (Vector3 lhs, Vector3 rhs) => (T)(object)Vector3.Lerp(lhs, rhs, t),
    (Quaternion lhs, Quaternion rhs) => (T)(object)Quaternion.Slerp(lhs, rhs, t),
    (Color lhs, Color rhs) => (T)(object)Color.Lerp(lhs, rhs, t),
    _ => throw new NotImplementedException($"Cannot interpolate function of type {typeof(T)}")
};

Step 3: Restrict T to struct

  • If you could restrict T is a struct then you could get rid of the (T)(object) castings as well
private static T LerpFunction<T>(T a, T b, float t) where T: struct
=> (a, b) switch
{
    (float lhs, float rhs) => Math.Lerp(lhs, rhs, t),
    (Vector3 lhs, Vector3 rhs) => Vector3.Lerp(lhs, rhs, t),
    (Quaternion lhs, Quaternion rhs) => Quaternion.Slerp(lhs, rhs, t),
    (Color lhs, Color rhs) => Color.Lerp(lhs, rhs, t),
    _ => throw new NotImplementedException($"Cannot interpolate function of type {typeof(T)}")
};

Final though: I would suggest to prefer NotSupportedException rather than NotImplementedException, because the latter is misleading. If someone calls it with doubles at the first time, then (s)he won't call the method again since it it not implemented.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.