XNA Xbox 360 Content Manager Thread freezing Draw Thread
- by Alikar
I currently have a game that takes in large images, easily bigger than 1MB, to serve as backgrounds. I know exactly when this transition is supposed to take place, so I made a loader class to handle loading these large images in the background, but when I load the images it still freezes the main thread where the drawing takes place. Since this code runs on the 360 I move the thread to the 4th hardware thread, but that doesn't seem to help. Below is the class I am using. Any thoughts as to why my new content manager which should be in its own thread is interrupting the draw in my main thread would be appreciated.
namespace FileSystem
{
/// <summary>
/// This is used to reference how many objects reference this texture.
/// Everytime someone references a texture we increase the iNumberOfReferences.
/// When a class calls remove on a specific texture we check to see if anything
/// else is referencing the class, if it is we don't remove it. If there isn't
/// anything referencing the texture its safe to dispose of.
/// </summary>
class TextureContainer
{
public uint uiNumberOfReferences = 0;
public Texture2D texture;
}
/// <summary>
/// This class loads all the files from the Content.
/// </summary>
static class FileManager
{
static Microsoft.Xna.Framework.Content.ContentManager Content;
static EventWaitHandle wh = new AutoResetEvent(false);
static Dictionary<string, TextureContainer> Texture2DResourceDictionary;
static List<Texture2D> TexturesToDispose;
static List<String> TexturesToLoad;
static int iProcessor = 4;
private static object threadMutex = new object();
private static object Texture2DMutex = new object();
private static object loadingMutex = new object();
private static bool bLoadingTextures = false;
/// <summary>
/// Returns if we are loading textures or not.
/// </summary>
public static bool LoadingTexture
{
get {
lock (loadingMutex)
{
return bLoadingTextures;
}
}
}
/// <summary>
/// Since this is an static class. This is the constructor for the file loadeder. This is the version
/// for the Xbox 360.
/// </summary>
/// <param name="_Content"></param>
public static void Initalize(IServiceProvider serviceProvider, string rootDirectory, int _iProcessor )
{
Content = new Microsoft.Xna.Framework.Content.ContentManager(serviceProvider, rootDirectory);
Texture2DResourceDictionary = new Dictionary<string, TextureContainer>();
TexturesToDispose = new List<Texture2D>();
iProcessor = _iProcessor;
CreateThread();
}
/// <summary>
/// Since this is an static class. This is the constructor for the file loadeder.
/// </summary>
/// <param name="_Content"></param>
public static void Initalize(IServiceProvider serviceProvider, string rootDirectory)
{
Content = new Microsoft.Xna.Framework.Content.ContentManager(serviceProvider, rootDirectory);
Texture2DResourceDictionary = new Dictionary<string, TextureContainer>();
TexturesToDispose = new List<Texture2D>();
CreateThread();
}
/// <summary>
/// Creates the thread incase we wanted to set up some parameters
/// Outside of the constructor.
/// </summary>
static public void CreateThread()
{
Thread t = new Thread(new ThreadStart(StartThread));
t.Start();
}
// This is the function that we thread.
static public void StartThread()
{
//BBSThreadClass BBSTC = (BBSThreadClass)_oData;
FileManager.Execute();
}
/// <summary>
/// This thread shouldn't be called by the outside world.
/// It allows the File Manager to loop.
/// </summary>
static private void Execute()
{
// Make sure our thread is on the correct processor on the XBox 360.
#if WINDOWS
#else
Thread.CurrentThread.SetProcessorAffinity(new int[] { iProcessor });
Thread.CurrentThread.IsBackground = true;
#endif
// This loop will load textures into ram for us away from the main thread.
while (true)
{
wh.WaitOne();
// Locking down our data while we process it.
lock (threadMutex)
{
lock (loadingMutex)
{
bLoadingTextures = true;
}
bool bContainsKey = false;
for (int con = 0; con < TexturesToLoad.Count; con++)
{
// If we have already loaded the texture into memory reference
// the one in the dictionary.
lock (Texture2DMutex)
{
bContainsKey = Texture2DResourceDictionary.ContainsKey(TexturesToLoad[con]);
}
if (bContainsKey)
{
// Do nothing
}
// Otherwise load it into the dictionary and then reference the
// copy in the dictionary
else
{
TextureContainer TC = new TextureContainer();
TC.uiNumberOfReferences = 1; // We start out with 1 referece.
// Loading the texture into memory.
try
{
TC.texture = Content.Load<Texture2D>(TexturesToLoad[con]);
// This is passed into the dictionary, thus there is only one copy of
// the texture in memory.
// There is an issue with Sprite Batch and disposing textures.
// This will have to wait until its figured out.
lock (Texture2DMutex)
{
bContainsKey = Texture2DResourceDictionary.ContainsKey(TexturesToLoad[con]);
Texture2DResourceDictionary.Add(TexturesToLoad[con], TC);
}
// We don't have the find the reference to the container since we
// already have it.
}
// Occasionally our texture will already by loaded by another thread while
// this thread is operating. This mainly happens on the first level.
catch (Exception e)
{
// If this happens we don't worry about it since this thread only loads
// texture data and if its already there we don't need to load it.
}
}
Thread.Sleep(100);
}
}
lock (loadingMutex)
{
bLoadingTextures = false;
}
}
}
static public void LoadTextureList(List<string> _textureList)
{
// Ensuring that we can't creating threading problems.
lock (threadMutex)
{
TexturesToLoad = _textureList;
}
wh.Set();
}
/// <summary>
/// This loads a 2D texture which represents a 2D grid of Texels.
/// </summary>
/// <param name="_textureName">The name of the picture you wish to load.</param>
/// <returns>Holds the image data.</returns>
public static Texture2D LoadTexture2D( string _textureName )
{
TextureContainer temp;
lock (Texture2DMutex)
{
bool bContainsKey = false;
// If we have already loaded the texture into memory reference
// the one in the dictionary.
lock (Texture2DMutex)
{
bContainsKey = Texture2DResourceDictionary.ContainsKey(_textureName);
if (bContainsKey)
{
temp = Texture2DResourceDictionary[_textureName];
temp.uiNumberOfReferences++; // Incrementing the number of references
}
// Otherwise load it into the dictionary and then reference the
// copy in the dictionary
else
{
TextureContainer TC = new TextureContainer();
TC.uiNumberOfReferences = 1; // We start out with 1 referece.
// Loading the texture into memory.
try
{
TC.texture = Content.Load<Texture2D>(_textureName);
// This is passed into the dictionary, thus there is only one copy of
// the texture in memory.
}
// Occasionally our texture will already by loaded by another thread while
// this thread is operating. This mainly happens on the first level.
catch(Exception e)
{
temp = Texture2DResourceDictionary[_textureName];
temp.uiNumberOfReferences++; // Incrementing the number of references
}
// There is an issue with Sprite Batch and disposing textures.
// This will have to wait until its figured out.
Texture2DResourceDictionary.Add(_textureName, TC);
// We don't have the find the reference to the container since we
// already have it.
temp = TC;
}
}
}
// Return a reference to the texture
return temp.texture;
}
/// <summary>
/// Go through our dictionary and remove any references to the
/// texture passed in.
/// </summary>
/// <param name="texture">Texture to remove from texture dictionary.</param>
public static void RemoveTexture2D(Texture2D texture)
{
foreach (KeyValuePair<string, TextureContainer> pair in Texture2DResourceDictionary)
{
// Do our references match?
if (pair.Value.texture == texture)
{
// Only one object or less holds a reference to the
// texture. Logically it should be safe to remove.
if (pair.Value.uiNumberOfReferences <= 1)
{
// Grabing referenc to texture
TexturesToDispose.Add(pair.Value.texture);
// We are about to release the memory of the texture,
// thus we make sure no one else can call this member
// in the dictionary.
Texture2DResourceDictionary.Remove(pair.Key);
// Once we have removed the texture we don't want to create an exception.
// So we will stop looking in the list since it has changed.
break;
}
// More than one Object has a reference to this texture.
// So we will not be removing it from memory and instead
// simply marking down the number of references by 1.
else
{
pair.Value.uiNumberOfReferences--;
}
}
}
}
/*public static void DisposeTextures()
{
int Count = TexturesToDispose.Count;
// If there are any textures to dispose of.
if (Count > 0)
{
for (int con = 0; con < TexturesToDispose.Count; con++)
{
// =!THIS REMOVES THE TEXTURE FROM MEMORY!=
// This is not like a normal dispose. This will actually
// remove the object from memory. Texture2D is inherited
// from GraphicsResource which removes it self from
// memory on dispose. Very nice for game efficency,
// but "dangerous" in managed land.
Texture2D Temp = TexturesToDispose[con];
Temp.Dispose();
}
// Remove textures we've already disposed of.
TexturesToDispose.Clear();
}
}*/
/// <summary>
/// This loads a 2D texture which represnets a font.
/// </summary>
/// <param name="_textureName">The name of the font you wish to load.</param>
/// <returns>Holds the font data.</returns>
public static SpriteFont LoadFont( string _fontName )
{
SpriteFont temp = Content.Load<SpriteFont>( _fontName );
return temp;
}
/// <summary>
/// This loads an XML document.
/// </summary>
/// <param name="_textureName">The name of the XML document you wish to load.</param>
/// <returns>Holds the XML data.</returns>
public static XmlDocument LoadXML( string _fileName )
{
XmlDocument temp = Content.Load<XmlDocument>( _fileName );
return temp;
}
/// <summary>
/// This loads a sound file.
/// </summary>
/// <param name="_fileName"></param>
/// <returns></returns>
public static SoundEffect LoadSound( string _fileName )
{
SoundEffect temp = Content.Load<SoundEffect>(_fileName);
return temp;
}
}
}