.NET Reflection, Boxing, and Properties in XNA games

jwatte's picture

System.Reflection is great! You can find all public properties on an object, and manipulate them programmatically, without knowing the exact type of the object. You can also treat properties generically, by passing a PropertyInfo and object instance around. This allows you to build generic animation systems (similar to how the Windows Presentation Foundation dynamic animatable properties work).

However, if you're animating value types, you will create a lot of garbage on the heap through boxing, because PropertyInfo.SetValue() goes through the object type, which causes things like Vector2 or float to get boxed.

To get around the boxing problem, you can declare container classes, and pre-allocate the container classes. It turns out that Nullable makes for a fine container class.

public class Foo {
  Vector2 myValueType_;
  Nullable<Vector2> myValueTypeWrapper_ = new Nullable<Vector2>();
  public Nullable<Vector2> PositionWrapper {
    set { myValueType_ = value.Value; }
    get { myValueTypeWrapper_.Value = myValueType_; return myValueTypeWrapper_; }  }
  public Vector2 Position { get { return myValueType_; } }
}

Now, on the setting side, you will need to keep a Nullable around as well, so you don't create a new one every time.
Pretty? No.

If you want to go one step further, you can define an IAnimatable interface, and expose that for each of your animatable properties. The interface will take care of shuffling values back and forth.

public interface IAnimatable<T> where T : struct{
  T GetValueAtTime(double time);  // you may or may not care about time
  void SetValueAtTime(T t, double time);
  string DisplayName { get; }
  Type Type { get; }
}

You can also create a concrete class that implements this interface, and takes an anonymous delegate for setters and getters:

public class Animatable<T> : IAnimatable<T> where T : struct
{
  public T GetValueAtTime(double time) { return getter_(time); }
  public void SetValueAtTime(T t, double time) { return setter_(time); }
  public string DisplayName { get { return name_; } }
  public Type Type { get { return typeof(T); } }
  public Animatable(GetterFunc<T> getter, SetterFund<T> setter, string name)
    {
      this.getter_ = getter;
      this.setter_ = setter;
      this.name_ = name;
    }
  GetterFunc<T> getter_;
  SetterFunc<T> setter_;
  string name_;
}
public delegate void SetterFunc<T>(T t, double time) where T : struct;
public delegate T GetterFunc<T>(double time) where T : struct;

Finally, you would use that in your class as such:

public class SomeEntity {
  Vector2 position_;
  Animatable<Vector2> positionProp_;
  public IAnimatable<Vector2> Position { get { return positionProp_; } }
  public SomeEntity() {
    positionProp_ = new Animatable<Vector2>(
        delegate (Vector2 v, double time) { position_ = v; },
        delegate (double time) { return position_; },
        "Position");
  }
}

Please excuse the generic delegate syntax, I may have gotten it slightly wrong, as this code is just typed in as an illustration.

Note that, at this point, you're building your own reflection system, without the generality of System.Reflection -- but then you have control, so that you can avoid the boxing.

Comments

This is pretty cool. It's a

This is pretty cool. It's a pity that you need to know ahead of usage which properties will be animateable and wrap them in Nullable<>.

On a different but related topic, I've been wondering lately about using Nullable to avoiding passing value types around. I find it a pain that I always want to pass Matrix or Vector3 structs using ref (to improve performance), but doing so means I can't pass properties to these methods. Argh. If my methods expected Nullable, I'd have to create a new Nullable each time I wanted to pass a property, or rewrite my properties (and internals) to use Nullable. Also painful. It really is a shame that the CLR has no way to pass properties by reference. Maybe you have some other insight on this?

I don't think we'll see any

I don't think we'll see any improvement until the XNA team gets some funding to add PPC/Xbox specific additions to the compact CLR.

The limitation is largely in the compact .NET framework/CLR implementation, which always passes value classes on the stack using copying. The desktop .NET framework can optimize in many ways (including, I believe, realizing that some argument is not modified in a given function, and passing by "const reference").