Game Component Manager

From XNAWiki
Jump to: navigation, search

A game component manager handles game components in a similar way as the XNA Framework's Game class does. This particular version was written for XNA 3.1, but changing it for XNA 4.0 is trivial, requiring switching the event types and invocations. This comes in two flavors, one universal game component manager, and one with type restriction.

Just keep in mind while using this that game components are not game objects and thus should not be treated as such. This is more designed to segregate the framework so that things remain neat and orderly.

/// <summary>
    /// A comparer designed to assist with sorting IDrawable interfaces.
    /// </summary>
    public sealed class IUpdateableComparer : IComparer<IUpdateable>
    {
        /// <summary>
        /// A static copy of the comparer to circumvent the GC.
        /// </summary>
        public static readonly IUpdateableComparer Default;
 
        static IUpdateableComparer() { Default = new IUpdateableComparer(); }
        public IUpdateableComparer() { }
 
        #region IComparer<IUpdateable> Members
 
        public int Compare(IUpdateable x, IUpdateable y)
        {
            // -1 if x < y
            //  0 if x = y
            // +1 if x > y
 
            if (x == null && y == null)
                return 0;
            if (x == null)
                return -1;
            if (y == null || x.UpdateOrder > y.UpdateOrder)
                return 1;
            if (x.UpdateOrder < y.UpdateOrder)
                return -1;
 
            return 0;
        }
 
        #endregion
    }
 
    /// <summary>
    /// A comparer designed to assist with sorting IDrawable interfaces.
    /// </summary>
    public sealed class IDrawableComparer : IComparer<IDrawable>
    {
        /// <summary>
        /// A static copy of the comparer to circumvent the GC.
        /// </summary>
        public static readonly IDrawableComparer Default;
 
        static IDrawableComparer() { Default = new IDrawableComparer(); }
        public IDrawableComparer() { }
 
        #region IComparer<IDrawable> Members
 
        public int Compare(IDrawable x, IDrawable y)
        {
            // -1 if x < y
            //  0 if x = y
            // +1 if x > y
 
            if (x == null && y == null)
                return 0;
            if (x == null)
                return -1;
            if (y == null || x.DrawOrder > y.DrawOrder)
                return 1;
            if (x.DrawOrder < y.DrawOrder)
                return -1;
 
            return 0;
        }
 
        #endregion
    }
 
    /// <summary>
    /// This class will handle calling Initialize(), Update(GameTime) on IUpdateable components, Draw(GameTime) on IDrawable components, and
    /// Dispose() on IDisposable components when appropriate to do so.
    /// </summary>
    public class GameComponentManager : GameComponentManager<IGameComponent>
    {
        /// <summary>
        /// Create a new GameComponentManager.
        /// </summary>
        /// <param name="game">The game that this game component manager belongs to.</param>
        public GameComponentManager(Game game) : base(game) { }
    }
 
    /// <summary>
    /// This class will handle calling Initialize(), Update(GameTime) on IUpdateable components, Draw(GameTime) on IDrawable components, and
    /// Dispose() on IDisposable components when appropriate to do so.
    /// </summary>
    /// <typeparam name="T">The base type of game components in this manager, this type must implement IGameComponent.</typeparam>
    public class GameComponentManager<T> : DrawableGameComponent, ICollection<T>
        where T : IGameComponent
    {
        #region Fields
 
        Collection<T> m_components;
        List<IGameComponent> m_uninitialized;
        List<IUpdateable> m_updateable;
        List<IDrawable> m_drawable;
        List<IDisposable> m_disposable;
 
        #endregion
 
        #region Properties
 
        /// <summary>
        /// A list of IUpdateable components which are enabled, please do not modify this list.
        /// </summary>
        protected List<IUpdateable> UpdateableComponents { get { return m_updateable; } }
        /// <summary>
        /// A list of IDrawable components which are visible, please do not modify this list.
        /// </summary>
        protected List<IDrawable> DrawableComponents { get { return m_drawable; } }
 
        #endregion
 
        #region Events
 
        /// <summary>
        /// Raised when a game component is added to this manager.
        /// </summary>
        public event Action<T> ComponentAdded = delegate { };
        /// <summary>
        /// Raised when a game component is removed from this manager.
        /// </summary>
        public event Action<T> ComponentRemoved = delegate { };
 
        #endregion
 
        #region Initialization
 
        /// <summary>
        /// Create a new GameComponentManager.
        /// </summary>
        /// <param name="game">The game that this game component manager belongs to.</param>
        public GameComponentManager(Game game)
            : base(game)
        {
            m_components = new Collection<T>();
            m_uninitialized = new List<IGameComponent>();
            m_updateable = new List<IUpdateable>();
            m_drawable = new List<IDrawable>();
            m_disposable = new List<IDisposable>();
        }
 
        /// <summary>
        /// When a component is added it'll be probed to figure out just what interfaces it implements
        /// and appropriate actions will be taken.
        /// </summary>
        protected virtual void OnComponentAdded(T component)
        {
            if (m_initialized)
                component.Initialize();
            else
                m_uninitialized.Add(component);
 
            // If the new component impliments IUpdateable find a spot for it on the updateable list 
            //  and hook it's UpdateOrderChanged event
            IUpdateable uComponent = component as IUpdateable;
            if (uComponent != null)
            {
                if (BinaryInsert(uComponent))
                {
                    uComponent.UpdateOrderChanged += new EventHandler(ChildUpdateOrderChanged);
                    uComponent.EnabledChanged += new EventHandler(ChildEnabledChanged);
                }
            }
 
            // If the new component impliments IDrawable find a spot for it on the drawable list 
            //  and hook it's DrawOrderChanged event
            IDrawable dComponent = component as IDrawable;
            if (dComponent != null)
            {
                if (BinaryInsert(dComponent))
                {
                    dComponent.DrawOrderChanged += new EventHandler(ChildDrawOrderChanged);
                    dComponent.VisibleChanged += new EventHandler(ChildVisibleChanged);
                }
            }
 
            IDisposable kComponent = component as IDisposable;
            if (kComponent != null)
                m_disposable.Add(kComponent);
 
            ComponentAdded(component);
        }
        /// <summary>
        /// When a component is removed it'll be probed to figure out just what interfaces it implements
        /// and appropriate actions will be taken.
        /// </summary>
        protected virtual void OnComponentRemoved(T component)
        {
            if (!m_initialized)
                m_uninitialized.Remove(component);
 
            IUpdateable uComponent = component as IUpdateable;
            if (uComponent != null)
            {
                m_updateable.Remove(uComponent);
                uComponent.UpdateOrderChanged -= new EventHandler(ChildUpdateOrderChanged);
                uComponent.EnabledChanged -= new EventHandler(ChildEnabledChanged);
            }
 
            IDrawable dComponent = component as IDrawable;
            if (dComponent != null)
            {
                m_drawable.Remove(dComponent);
                dComponent.DrawOrderChanged -= new EventHandler(ChildDrawOrderChanged);
                dComponent.VisibleChanged -= new EventHandler(ChildVisibleChanged);
            }
 
            IDisposable kComponent = component as IDisposable;
            if (kComponent != null)
                m_disposable.Remove(kComponent);
 
            ComponentRemoved(component);
        }
 
        #endregion
 
        #region ICollection<T> Members
 
        /// <summary>
        /// Get the number of game components in this game component manager.
        /// </summary>
        public int Count { get { return m_components.Count; } }
 
        bool ICollection<T>.IsReadOnly { get { return false; } }
 
        /// <summary>
        /// Get a component from this manager by it's index, this will return null if the component
        /// wasn't found in the manager.
        /// </summary>
        public T this[int index] { get { try { return m_components[index]; } catch { return default(T); } } }
 
        /// <summary>
        /// Add a game component to this game component manager.
        /// </summary>
        public void Add(T item)
        {
            if (!m_components.Contains(item))
            {
                m_components.Add(item);
                OnComponentAdded(item);
            }
        }
 
        /// <summary>
        /// Removes all game components from this game component manager.
        /// </summary>
        public void Clear()
        {
            for (int i = 0; i < m_components.Count; i++)
                OnComponentRemoved(m_components[i]);
            m_components.Clear();
        }
 
        /// <summary>
        /// Determines wheather a game component exist in this game component manager.
        /// </summary>
        public bool Contains(T item) { return m_components.Contains(item); }
 
        /// <summary>
        /// Copy all of the game components in this game component manager to the specified array.
        /// </summary>
        public void CopyTo(T[] array, int arrayIndex) { m_components.CopyTo(array, arrayIndex); }
 
        /// <summary>
        /// Remove a game component from this game component manager.
        /// </summary>
        public bool Remove(T item)
        {
            if (m_components.Contains(item))
                OnComponentRemoved(item);
            return m_components.Remove(item);
        }
 
        #endregion
 
        #region IEnumerable<T> Members
 
        /// <summary>
        /// Returns an enumerator that iterates through the game components in this game component manager.
        /// </summary>
        public IEnumerator<T> GetEnumerator() { return m_components.GetEnumerator(); }
 
        #endregion
 
        #region IEnumerable Members
 
        /// <summary>
        /// Returns an enumerator that iterates through the game components in this game component manager.
        /// </summary>
        System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() { return m_components.GetEnumerator(); }
 
        #endregion
 
        #region IGameComponent Members
 
        bool m_initialized;
 
        /// <summary>
        /// Initialize the GameComponentManager and attach the event handlers required to allow content loading and unload,
        /// afterward initialize any IGameComponents that haven't been initialized yet.
        /// </summary>
        public override void Initialize()
        {
            base.Initialize();
 
            // at this point it's initialized if the base level didn't throw exceptions
            m_initialized = true;
 
            // Initialize any un-initialized game components
            while (m_uninitialized.Count > 0)
            {
                m_uninitialized[0].Initialize();
                m_uninitialized.RemoveAt(0);
            }
        }
 
        #endregion
 
        #region IUpdateable Members
 
        public override void Update(GameTime gameTime)
        {
            base.Update(gameTime);
 
            List<IUpdateable> updateable = m_updateable;
            for (int i = 0; i < updateable.Count; i++)
                updateable[i].Update(gameTime);
        }
 
        // these two are just dummy method, the overloads will reroute calls into the appropriate type match,
        //  the method call is just for ensurance
        void ChildUpdateOrderChanged(object sender, EventArgs args) { ChildUpdateOrderChanged(sender as IUpdateable, args); }
        void ChildEnabledChanged(object sender, EventArgs args) { ChildEnabledChanged(sender as IUpdateable, args); }
 
        /// <summary>
        /// When the update order of a component in this manager changes, will need to find a new place for it
        /// on the list of updateable components.
        /// </summary>
        void ChildUpdateOrderChanged(IUpdateable sender, EventArgs args)
        {
            m_updateable.Remove(sender);
            BinaryInsert(sender);
        }
        /// <summary>
        /// When the enabled of a component in this manager changes, will need to remove or add it to the
        /// updateable component listing.
        /// </summary>
        void ChildEnabledChanged(IUpdateable sender, EventArgs args)
        {
            try
            {
                if (!m_updateable.Remove(sender))
                    BinaryInsert(sender);
            }
            catch { }
        }
 
        /// <summary>
        /// Use a binary insertion algorithm to find a spot appropriate for an updateable component.
        /// </summary>
        /// <returns>True if the component was inserted or could be inserted but wasn't due to 
        /// enabled being false, false if it wasn't inserted due to a duplicate existing in the
        /// list already.</returns>
        bool BinaryInsert(IUpdateable uComponent)
        {
            int index = m_updateable.BinarySearch(uComponent, IUpdateableComparer.Default);
            if (index < 0)
            {
                index = ~index;
                while (index < m_updateable.Count && m_updateable[index].UpdateOrder == uComponent.UpdateOrder)
                    index++;
 
                // only insert the item if it's enabled
                if (uComponent.Enabled)
                    m_updateable.Insert(index, uComponent);
 
                // even though it's not always inserted, it does have a spot in the list
                return true;
            }
            return false;
        }
 
        #endregion
 
        #region IDrawable Members
 
        public override void Draw(GameTime gameTime)
        {
            base.Draw(gameTime);
 
            List<IDrawable> drawable = m_drawable;
            for (int i = 0; i < drawable.Count; i++)
                drawable[i].Draw(gameTime);
        }
 
        // these two are just dummy method, the overloads will reroute calls into the appropriate type match,
        //  the method call is just for ensurance
        void ChildDrawOrderChanged(object sender, EventArgs args) { ChildDrawOrderChanged(sender as IDrawable, args); }
        void ChildVisibleChanged(object sender, EventArgs args) { ChildVisibleChanged(sender as IDrawable, args); }
 
        /// <summary>
        /// When the draw order of a component in this manager changes, will need to find a new place for it
        /// on the list of drawable components.
        /// </summary>
        void ChildDrawOrderChanged(IDrawable sender, EventArgs args)
        {
            m_drawable.Remove(sender);
            BinaryInsert(sender);
        }
        /// <summary>
        /// When the visibility of a component in this manager changes, will need to remove or add it to the
        /// drawable component listing.
        /// </summary>
        void ChildVisibleChanged(IDrawable sender, EventArgs args)
        {
            try
            {
                if (!m_drawable.Remove(sender))
                    BinaryInsert(sender);
            }
            catch { }
        }
 
        /// <summary>
        /// Use a binary insertion algorithm to find a spot appropriate for an drawable component.
        /// </summary>
        /// <returns>True if the component was inserted or could be inserted but wasn't due to 
        /// visible being false, false if it wasn't inserted due to a duplicate existing in the 
        /// list already.</returns>
        bool BinaryInsert(IDrawable dComponent)
        {
            int index = m_drawable.BinarySearch(dComponent, IDrawableComparer.Default);
            if (index < 0)
            {
                index = ~index;
                while (index < m_drawable.Count && m_drawable[index].DrawOrder == dComponent.DrawOrder)
                    index++;
 
                // only insert the item if it's visible
                if (dComponent.Visible)
                    m_drawable.Insert(index, dComponent);
 
                // even though it's not always inserted it does have a spot on the list
                return true;
            }
            return false;
        }
 
        #endregion
 
        #region IDisposable Members
 
        /// <summary>
        /// Called when this game component manager is being disposed of.
        /// </summary>
        protected override void Dispose(bool disposing)
        {
            if (disposing)
            {
                for (int i = 0; i < m_disposable.Count; i++)
                    m_disposable[i].Dispose();
            }
            base.Dispose(disposing);
        }
 
        #endregion
    }