I’ve had Bill Reiss’ Silverlight Games 101 blog in my Google reader for a while now, and recently he’s started a new series started on developing the Asteroids-like “Space Rocks” game under Silverlight 3. Between part 3 and part 4 was a post promoting caching dependency property values for performance. I’m not a WPF or Silverlight guru so I can’t really argue with the statement; but it does make sense in the same way that caching static data in a line of business application does.

In part 4 we see this in action, in properties on the Sprite class:

        public double X
        {
            get
            {
                return x;
            }
            set
            {
                if (x != value)
                {
                    x = value;
                    this.SetValue(Canvas.LeftProperty, x - dw);
                }
            }
        }

        public double Y
        {
            get
            {
                return y;
            }
            set
            {
                if (y != value)
                {
                    y = value;
                   this.SetValue(Canvas.TopProperty, y - dh);
                }
            }
        }

I don’t like the duplication of the caching inside of the class itself, especially when dependency properties are likely to be used quite heavily in a game, which is going to be doing things like adjusting position of visual game elements every rendered frame. Surely, this is unnecessary duplication and we can do better? Behold, the DependencyPropertyCache:

using System;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;

namespace SpaceRocks
{
    public class DependencyPropertyCache<T>
    {
        private DependencyProperty _dependencyProperty;
        private DependencyObject _parent;

        public DependencyPropertyCache(DependencyObject parent, DependencyProperty property)
        {
            if (parent == null)
                throw new ArgumentNullException("parent");
            if (property == null)
                throw new ArgumentNullException("property");

            _parent = parent;
            _dependencyProperty = property;
        }

        public T Value { get; private set; }

        public void SetValue(T value)
        {
            if (!Value.Equals(value))
            {
                _parent.SetValue(_dependencyProperty, value);
                this.Value = value;
            }
        }
    }
}

Pretty simple, really. Now we can have cached values exposed by the Value property and update the value using SetValue which does a comparison between old and new, and only updates the actual dependency property if the new value is different. Bill was using the inequality operator and this class is using the Equals method on System.Object, however it wouldn’t be very difficult to modify the cache to constrain T to implementing IComparable<T>, as System.Double does, and/or a constructor overload which might take a custom IComparable<T>. So far in the series, there are only System.Double properties, so I’ve not done that yet.

So now the modified code for the Sprite class from part 4 looks like this: 

using System.Windows.Controls;
using Microsoft.Xna.Framework;
using System.Windows;
using System.Windows.Media;

namespace SpaceRocks
{
    public class Sprite : Control
    {
        double dw = 0;
        double dh = 0; 
        static Vector2 gameSize = new Vector2(640, 480);
        private DependencyPropertyCache<double> _x;
        private DependencyPropertyCache<double> _y;
 
        public Sprite()
        {
            this.DefaultStyleKey = typeof(Sprite);
            this.SizeChanged += new SizeChangedEventHandler(Sprite_SizeChanged);
            _x = new DependencyPropertyCache<double>(this, Canvas.LeftProperty);
            _y = new DependencyPropertyCache<double>(this, Canvas.TopProperty);
        }

        void Sprite_SizeChanged(object sender, SizeChangedEventArgs e)
        {
            Vector2 pos = Position;           
            
            _x.SetValue(double.NaN);
            _y.SetValue(double.NaN);

            Position = pos;
        }

        public override void OnApplyTemplate()
        {
            base.OnApplyTemplate();
            FrameworkElement element = VisualTreeHelper.GetChild(this, 0) as FrameworkElement;
            dw = element.Width / 2;
            dh = element.Height / 2;
            Width = element.Width;
            Height = element.Height;
        }

        public double X
        {
            get
            {
                return _x.Value;
            }
            set
            {
                _x.SetValue(value - dw);                
            }
        }

        public double Y
        {
            get
            {
                return _y.Value;
            }
            set
            {
                _y.SetValue(value - dh);                
            }
        }

        public Vector2 Position
        {
            get
            {
                return new Vector2( (float)_x.Value, (float)_y.Value);
            }
            set
            {
                _x.SetValue(value.X);
                _y.SetValue(value.Y);
            }
        }

        public Vector2 Velocity { get; set; }

        public void Update(double elapsedSeconds)
        {
            Position += Velocity * (float)elapsedSeconds;
            if (Position.X > gameSize.X) Position -= new Vector2(gameSize.X, 0);
            if (Position.X < 0) Position += new Vector2(gameSize.X, 0);
            if (Position.Y > gameSize.Y) Position -= new Vector2(0, gameSize.Y);
            if (Position.Y < 0) Position += new Vector2(0, gameSize.Y);
        }
    }
}

Sweet.


 
Categories: Silverlight
Related posts:

Comments are closed.