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.

About these ads

February 1, 2008 - Posted by | C#, GameComponent, XBOX360, XNA

12 Comments »

  1. Cool, works great for me. Wouldn’t you prefer to create a ThreadManager class as a single point for thread creation though? I haven’t done loads of multi-threading so I’m cautious about making debugging any more difficult than it needs to be.
    I guess it’s not really the point just yet… it would be easy to get bogged down in the details of a thread management system.
    What I’m getting out of this most is an understanding idea of how engine components need to fit together.

    Comment by jaff | February 3, 2008 | Reply

  2. Keep up the great work, can’t wait till the engine is complete, so to see how everything comes together, and finally be able to put “2+2″ together (figuratively speaking)

    Comment by KaBaL | February 4, 2008 | Reply

  3. Jaff,
    I always appreciate your comments. I understand your desire for a thread manager class. Multi-threading is no easy task to debug, but not so difficult that it requires specific managment classes just for threads. About the only thing I could gain would be to put the ThreadStarter and delgate into its own static method, definitely something to consider.

    Comment by roecode | February 4, 2008 | Reply

  4. KaBaL,
    Thank you, I am very happy this series has sparked interest.

    Comment by roecode | February 4, 2008 | Reply

  5. Hi, you are doing a greate job. I saw your implementation of a multi-thread loading, this not would be betther use an async pattern with ThreadPool?

    keep with the good job.

    thanks

    Comment by adriano | February 4, 2008 | Reply

  6. Not certain if using the ThreadPool could offer better results. If you like the implementation of ThreadPool or BackgroundWorker better, I am sure it would not be difficult to accomplish the changes.

    It has always been my understanding that under the covers, asynchronous threading is all handled the same way. Granted, I am not doing any thread safety here, but since I have the ShadersLoaded and Initialized properties I essentially have a safety net.

    Comment by roecode | February 5, 2008 | Reply

  7. [...] GameComponent Part VI: Input GameComponent Part VII: ScreenManager GameComponent (game state + GUI) Part VIII: Adding multithreading to GameComponentsPart IX: SceneGraphManager GameComponentPart X: CameraManager GameComponentPart XI: Static [...]

    Pingback by Getting up with XNA GS 2.0 [UPDATED] - Kartones Blog | February 19, 2008 | Reply

  8. [...] Part 8, Multi-Threading GameComponents [...]

    Pingback by UH COSC Interactive Game Development » XNA Game Engine Development | May 21, 2008 | Reply

  9. You have to be careful to make sure not to draw a texture before it’s loaded with this, because if something adds a texture to the texture manager and tries to draw it too soon, the texture will not be loaded yet and you’ll get an error. (This happens when using the menu from section 7) I don’t think it’s too big of a problem though.

    Comment by META_Cog | February 9, 2009 | Reply

  10. I agree whit numer 9, but I got better idia.
    Many games are used in load game screen, becouse it takes time until the game start. You should use in multi-threading for load all together(for multi core).
    And my offer is to use in a multi BackgroudWorkers for the loading percentages, and whit simple calculation know the sum of loading percentages.

    Comment by Tal | June 11, 2009 | Reply

  11. Hello !

    I’m new on this forum so I introduce me…

    My name is Jason I’m 24 years old, I’m French.

    I like: holdem poker and baseball…

    Nice to meet you

    Comment by Silapolxxzz | October 20, 2010 | Reply

  12. First, REALLY thanks for uploading this stuff.

    I want to tell something about threading, because i think the way you use it, it is not really performant.

    I learned that you should have about maximal 4 threads running. A thread moves the load to another processor/core if possible. But you normally have about maximum 4 cores today.

    If you make more threads, they will get processor time along other threads what means they have to wait until other threads have used “their” time. Its like a stack – on ONE core/processor. All you will get from “more-than-4-threads” is “another run” than a structured “from-above-to-below” run..but not “more” time because the threads ALSO have to wait.

    So, my opinion is to make some BIG threads:
    - a physics-thread for example.
    - a loading thread wich loads ALL content in one process (e.g. from file)
    - a network thread…

    other said: every thread wich is more than your core/processor-count does NOT really push your performance.

    Ok, its a little late but better late than never.

    Comment by ben0bi | December 8, 2010 | Reply


Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Follow

Get every new post delivered to your Inbox.

%d bloggers like this: