Loading / Unloading a level's assets

Jun 9, 2009 at 6:36 AM

Hi, I'd like to know how to properly load and unload assets with Xen, how to load content associated with a level in a game.
I created a new project that mimic your tutorial 9. It creates an ImageDisplayer that loads the "skyline" texture, display it and when the game is shutdown, the ImageDisplayer along with it's texture and shader are unloaded.
In a more interresting game you might have a main menu that you'd want to unload to make room to the first level of the game (terrain, ennemies meshes, textures,...), play it, then at the end unload it, then load the second level,....
I understand that an IContentOwner created and added to the ContentRegister after the D3DDevice has been created and is valid, their LoadContent() method will be called immediately, so I should not have problems loading stuff whenever I want.
But how should I unload a level? Call all IContentOwner.UnloadContent(), destroy them, then call the ContentManager.Unload()?
I modified the tutorial to add the following at the end of the Application.Update() :
if ( state.PlayerInput[PlayerIndex.One].InputState.Buttons.A.OnPressed )
{
    drawToScreen.Remove( imageDisplayer );
    imageDisplayer.UnloadContent(...);
    imageDisplayer = null;

    ( (Game)this ).Content.Unload();

    imageDisplayer = new ImageDisplayer( this.Content );
    drawToScreen.Add( imageDisplayer );
}
It seams to work, but sometimes, after having "loaded multiple levels", I get 2 calls to the ImageDisplayer.UnloadContent(), while I quit the game. I should only have one instance at a time. Could this simply be the garbage collector finally kicking in.
Looks like calling GC.Collect(); right after ((Game)this).Content.Unload(); fixes it, but is my method good?

Coordinator
Jun 9, 2009 at 9:06 AM

Hello!

Thanks for getting contact. The answer to this is pretty simple, but usually requires some design work on your part.
Internally, all the content loading is through XNA content managers. IContentOwner, etc, are all just wrappers on the XNA internals in this case. The ContentRegister class is a wrapper on XNA's ContentManger (and a number of other things).

Because of this, you can't really unload a specific resource. You can, however, unload an entire XNA ContentManger.

The default ContentRegister simply wraps the XNA default XNA ContentManager, so for your design, you will want to have a ContentRegister object per level.

So, 'levelRegister = new ContentRegister(this.Application);'  (that this will create a new XNA ContentManager).

Then, when you are done with the level, simply call Dispose() on the ContentRegister, and everything it created will be cleaned up.
Does that make sense?

Jun 9, 2009 at 4:30 PM

Dispose(),... yeah,... I knew I needed a ContentRegister / level (+ one for global stuff), but yesterday when I did my small test, I didn't want to complicate stuff by creating too much test code, so I only used the default contentRegister, and when calling Dispose(), it tryed to kill the Application too, so I forgot about multiple content registers and looked else where.

thanks

Jun 10, 2009 at 3:12 AM
Edited Jun 10, 2009 at 3:13 AM

Ok, I tought I understood, but no.

If I call ContentRegister.Dispose(), it destroys it's service variable (remove the event handlers, then set it null).

Then if items is not null (it's only nulled after this IF statement, so it as to enter the if at least once) it calls CallUnload() wich uses the service variable that we just set to null

=> NullReferenceException !

repro steps:

- create a new ContentRegister in Initialise()

- load nothing, draw nothing

- later in the Update() Dispose it

- Boom!

 

Maybe service should be destroyed later?

Coordinator
Jun 11, 2009 at 3:20 AM

Hi. Sorry for my slow reply,

You are totally correct and it seems I'd screwed something up in the Dispose() ordering.
The block of code that clears out the items List (and calls unload) needs to be at the top of the method.

It doesn't actually destroy the service, it just detaches from it's events and nulls it's reference.
I'm putting together a minor update with a couple of other little features so I'll include this.

Thanks!