Running on Empty

The few things I know, I like to share.

XNA Framework GameEngine Development. (Part 8, Multi-Threading GameComponents)

Introduction

Welcome to Part 8 of the XNA Framework GameEngine Development series.  In this article, I will be revisiting the TextureManager and the ShaderManager GameComponents to add in Multi-Threading.  Since we want to be able to load our game as quickly as possible it makes sense to use all the CPUs available to us.  For now I will focus on Background Loading, a future article will discuss Locking and Thread-Safety.

Background Loading template, just a sample

ThreadStart threadStarter = delegate
{
    // Do Work Here
};
Thread loadingThread = new Thread(threadStarter);
loadingThread.Start();

Yup, it is just that easy.

ShaderManager.cs – Multi-Threaded Loading

using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.Xna.Framework;
using System.Collections;
using RoeEngine2.Interfaces;
using Microsoft.Xna.Framework.Graphics;
using RoeEngine2.Shaders;
using System.Threading;

namespace RoeEngine2.Managers
{
    public class ShaderManager : GameComponent
    {
        private static Dictionary<string, IRoeShader> _shaders = new Dictionary<string, IRoeShader>();

        private static int _shadersLoaded = 0;
        /// <summary>
        /// The number of shaders that are currently loaded.
        /// Use this for user loading bar feedback.
        /// </summary>
        public static int ShadersLoaded
        {
            get { return _shadersLoaded; }
        }    

        private static bool _initialized = false;
        /// <summary>
        /// Is the ShaderManagers Initialized, used for test cases and setup of Effects.
        /// </summary>
        public static bool Initialized
        {
            get { return _initialized; }
        }

        /// <summary>
        /// Create the shader Managers.
        /// </summary>
        /// <param name="game"></param>
        public ShaderManager(Game game)
            : base(game)
        {
        }

        /// <summary>
        /// Add a shader of type IRoeShader.
        /// </summary>
        /// <param name="newShader"></param>
        /// <param name="shaderLabel"></param>
        public static void AddShader(IRoeShader newShader, string shaderLabel)
        {
            if (shaderLabel != null && !_shaders.ContainsKey(shaderLabel))
            {
                _shaders.Add(shaderLabel, newShader);

                if (_initialized)
                {
                    ThreadStart threadStarter = delegate
                    {
                        newShader.Initialize(EngineManager.Device);
                        _shadersLoaded++;
                    };
                    Thread loadingThread = new Thread(threadStarter);
                    loadingThread.Start();
                }
            }
        }

        /// <summary>
        /// Get a shader of type IRoeShader.
        /// </summary>
        /// <param name="shaderLabel"></param>
        /// <returns></returns>
        public static IRoeShader GetShader(string shaderLabel)
        {
            if (shaderLabel != null && _shaders.ContainsKey(shaderLabel))
            {
                return _shaders[shaderLabel];
            }
            return null;
        }

        /// <summary>
        /// Create the shaders.
        /// </summary>
        public override void Initialize()
        {
            base.Initialize();

            AddShader(new basicEffect(), "BasicEffect");

            ThreadStart threadStarter = delegate
            {
                foreach (IRoeShader shader in _shaders.Values)
                {
                    if (!shader.ReadyToRender)
                    {
                        shader.Initialize(EngineManager.Device);
                        _shadersLoaded++;
                    }
                }
            };
            Thread loadingThread = new Thread(threadStarter);
            loadingThread.Start();

            _initialized = true;
        }
    }
}

TextureManager.cs – Multi-Threaded Loading

using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using System.Collections;
using RoeEngine2.Interfaces;
using RoeEngine2.Texures;
using System.Threading;

namespace RoeEngine2.Managers
{
    public class TextureManager : GameComponent
    {
        private static Dictionary<string, IRoeTexture> _textures = new Dictionary<string, IRoeTexture>();

        private static bool _initialized = false;
        /// <summary>
        /// Is the TextureManagers Initialized, used for test cases and setup of Effects.
        /// </summary>
        public static bool Initialized
        {
            get { return _initialized; }
        }

        private static int _texturesLoaded = 0;
        /// <summary>
        /// The number of textures that are currently loaded.
        /// Use this for user loading bar feedback.
        /// </summary>
        public static int TexturesLoaded
        {
            get { return _texturesLoaded; }
        }

        /// <summary>
        /// Create the shader Managers.
        /// </summary>
        /// <param name="game"></param>
        public TextureManager(Game game)
            : base(game)
        {
        }

        /// <summary>
        /// Add a shader of type RoeTexture.
        /// </summary>
        /// <param name="newTexture"></param>
        /// <param name="textureName"></param>
        public static void AddTexture(IRoeTexture newTexture, string textureName)
        {
            if (textureName != null && !_textures.ContainsKey(textureName))
            {
                _textures.Add(textureName, newTexture);
                if (_initialized)
                {
                    ThreadStart threadStarter = delegate
                    {
                        newTexture.LoadContent();
                        _texturesLoaded++;
                    };
                    Thread loadingThread = new Thread(threadStarter);
                    loadingThread.Start();
                }
            }
        }

        /// <summary>
        /// Remove a texture from memory
        /// </summary>
        /// <param name="textureName"></param>
        public static void RemoveTexture(string textureName)
        {
            if (textureName != null && _textures.ContainsKey(textureName))
            {               
                if (_initialized)
                {
                    ThreadStart threadStarter = delegate
                    {
                        _textures[textureName].UnloadContent();
                        _textures.Remove(textureName);
                        _texturesLoaded--;
                    };
                    Thread loadingThread = new Thread(threadStarter);
                    loadingThread.Start();
                }               
            }
        }

        /// <summary>
        /// Get a texture
        /// </summary>
        /// <param name="textureId"></param>
        /// <returns></returns>
        public static IRoeTexture GetTexture(string textureName)
        {
            if (textureName != null && _textures.ContainsKey(textureName))
            {
                return _textures[textureName];
            }
            return null;
        }

        /// <summary>
        /// Create the shaders.
        /// </summary>
        public override void Initialize()
        {
            base.Initialize();

            ThreadStart threadStarter = delegate
            {
                foreach (IRoeTexture texture in _textures.Values)
                {
                    if (!texture.ReadyToRender)
                    {
                        texture.LoadContent();
                        _texturesLoaded++;
                    }
                }
            };
            Thread loadingThread = new Thread(threadStarter);
            loadingThread.Start();

            _initialized = true;
        }
    }
}

Conclusion

In this article, I introduced Multi-threading concepts into the engine.  Background Loading is the easiest threading template that the engine has to offer.  Granted, we have not put enough content into the game yet to really tell a difference between single and multithreading options.  If you use your system watch tool and have a multi core or processor system, you will see all CPUs sharing the “load” of loading your game.

I look forward to discussing your thoughts and requests for future articles.

February 1, 2008 Posted by roecode | C#, GameComponent, XBOX360, XNA | | 10 Comments