XNA Creators Club Online
Page 1 of 1 (19 items)
Sort Posts: Previous Next

Removing Individual Items From The ContentManager

Last post 06-30-2008 5:45 PM by Roonda. 18 replies.
  • 05-11-2008 12:50 AM

    Removing Individual Items From The ContentManager

    This has been brought up and dismissed as design a lot, but I still think that hammering away will eventually get this implemented into the framework.

    As it stands you cannot unload a single piece of content from the ContentManager. You can unload everything or nothing. So the options you really have are either A) use lots of ContentManagers for your different items which can get cumbersome and annoying or B) make your own custom ContentManager derivative to implement this functionality.

    It has been stressed how simple it is to create the custom ContentManager to do this and I agree. It's pretty simple to make a new one. Shawn even showed how to get started in one blog post. But turning that around, if it is so simple, why has such a commonly requested feature not just been put into the framework? I understand there is testing involved and all that goes along with being Microsoft releasing a framework and so on, but this seems like such an obvious need for the framework.

    The issue is becoming more relevant because of the Zune where you have a decent size for game storage but a relatively small one for memory. Being able to unload specific assets is going to be very useful for developers to avoid hitting that 16MB limit. Granted this isn't the primary reason to add the methods, but a reason nonetheless.

    I do believe that the framework needs this functionality so that the average newbie doesn't have to start asking "why do I need bunches of ContentManagers just so I can unload this one texture?" or "why am I rewriting part of the framework to unload this one texture?" As such I've filed a Connect issue around this (probably the latest of many). I encourage others to take the time to vote on this as well if you find it valueable.

    https://connect.microsoft.com/feedback/ViewFeedback.aspx?FeedbackID=343259&SiteID=226

    Nick Gravelyn -- Microsoft XNA MVP
    Blog | The Best Game. Ever. | Next-Gen
  • 05-11-2008 12:55 AM In reply to

    Re: Removing Individual Items From The ContentManager

    I agree, this issue is extremely important for me and has been something I have continuously asked about. It seems to be one of those simple additions that would take no longer than half an hour of one of the programmer's time. The issue arises when a programmer disposes of a content item and sets the reference to null. This causes the content to be disposed, but not removed from the content manager's internal system. Thus the next time the content is loaded, a disposed reference is returned.

    John Sedlak Xna/DirectX MVP
    Focused Games | My Blog
  • 05-11-2008 1:56 AM In reply to

    Re: Removing Individual Items From The ContentManager

    Apparently, the main problem is that different assets are handled differently. To really do a good job of content unloading, you'd want to turn the content manager references into weak references, and have a way to re-load them when necessary. And/or you'd want to implement some form of reference counting, or implement assets as IDisposable, so that disposing the asset removes it from the content manager.

    Unfortunately, most of those approaches don't work well with the generic structure of the content manager. You can't (at least currently) require that each asset derives from IDisposable, or some reference counted interface.

    What I did in my XACT wrapper (which manages Cues) was to return an asset "handle" class, which holds a reference to the asset as well as the management record in the wrapper. The finalizer and dispose functions for the handle class then decrement a refcount inside the management record. When the refcount goes to 0, the asset is unloaded. Thus, you can dispose a handle to make it decrement the ref count, or you can just set it to NULL and let the garbage collector take care of it. I believe something like that could be added to the ContentManager without necessarily breaking APIs, if you wanted.

    Another, API transparent change would be to make the internal Dictionary from string to asset be a WeakReference, and let the GC take care of unloading the asset if needed. This would give less control to the developer, but would hopefully give "enough" control, because if resources are tight, GC would run and clean up the mess. When re-requesting a purged asset, the ContentManager would then know enough to re-load the asset if the reference went to null.
    Jon Watte, Direct3D MVP kW X-port 3ds Max .X exporter kW Animation source code
  • 05-11-2008 1:23 PM In reply to

    Re: Removing Individual Items From The ContentManager

    I gave the topic 5 stars.

    One of these days I'll run into the situation where my game's ContentManager becomes full and probably has to start juggling content.  I can imagine how that will kill preformance big time.  And of course there is the old addage, it works on my PC but someone elses...?

    My editor has multiple ContentManagers so it may take longer before I hit that wall with it. But in reality, I'm not too sure what's going on under the hood, so even with multiple CM's, I may see some very sluggish behavior there too.

  • 05-11-2008 1:30 PM In reply to

    Re: Removing Individual Items From The ContentManager

    Enchanted Age:
    Apparently, the main problem is that different assets are handled differently. To really do a good job of content unloading, you'd want to turn the content manager references into weak references, and have a way to re-load them when necessary. And/or you'd want to implement some form of reference counting, or implement assets as IDisposable, so that disposing the asset removes it from the content manager.

    Unfortunately, most of those approaches don't work well with the generic structure of the content manager. You can't (at least currently) require that each asset derives from IDisposable, or some reference counted interface.

    You can't require it be disposable, but it's easy enough to check:

    IDisposable disposableContent = content as IDisposable;

    if (disposableContent != null)
    disposableContent.Dispose();


    As for reference counting, I'd consider that not a priority. You are telling the ContentManager to unload a piece of content. To me that assumes you are done with it and don't want it anymore. There could also simply be a flag on the method as to whether you just want to remove the reference or call dispose on the item. Then you can keep your content and just force the ContentManager to get rid of its reference to it.

    Ultimately will I stop using XNA if the feature doesn't make it? Of course not. I could take the time today to fix it for me. I could even then start giving it out to other people to use it. But the problem still remains for that beginner XNA is trying to target where they download the files and start working only to find that to unload a single piece of content they have to write a bunch of code that, in the minds of lots of people, should simply be in the ContentManager to begin with. Those are the people I'm thinking about when I say this feature should go into the framework.

    Another reason why I consider the "writing my own" a silly option is that, in XNA GS 2.0, they moved the ContentManager from being something my custom Game1 class controlled to an actual field on the Game class. This means that I cannot replace that ContentManager. So now I have a bare minimum two ContentManagers (one from the Game class and a custom one in my Game1 class), one of which isn't even being used. This could be remedied by simply making the Content property have a public set, but as the framework is today that addition of the ContentManager to the Game class has made things more inflexible (or at least more annoying) when using a custom ContentManager.

    Nick Gravelyn -- Microsoft XNA MVP
    Blog | The Best Game. Ever. | Next-Gen
  • 05-11-2008 1:58 PM In reply to

    Re: Removing Individual Items From The ContentManager

    You can't require it be disposable, but it's easy enough to check:


    Yes; the ContentManager already checks that. The problem is that you don't get notified when something is disposed, so you need a richer interface -- else the ContentManager doesn't know to remove the disposed item from the cache.

    I agree that it's unfortunate that the Content field is not assignable, and is not virtual, and the "content" field is private, and there is no SetContentManager() function. However, the only thing that the Game uses the Content field for is to notify the content manager when the device is getting reset. You can arrange to do that yourself, and use your own contant manager, using your own field to access it, and putting it into the Services container yourself. It's a design deficiency in the Game class, but it can be worked around.

    Jon Watte, Direct3D MVP kW X-port 3ds Max .X exporter kW Animation source code
  • 05-11-2008 1:59 PM In reply to

    Re: Removing Individual Items From The ContentManager

    You can't require it be disposable, but it's easy enough to check:


    Yes; the ContentManager already checks that. The problem is that you don't get notified when something is disposed, so you need a richer interface -- else the ContentManager doesn't know to remove the disposed item from the cache.

    I agree that it's unfortunate that the Content field is not assignable, and is not virtual, and the "content" field is private, and there is no SetContentManager() function. However, the only thing that the Game uses the Content field for is to notify the content manager when the device is getting reset. You can arrange to do that yourself, and use your own contant manager, using your own field to access it, and putting it into the Services container yourself. It's a design deficiency in the Game class, but it can be worked around.

    Jon Watte, Direct3D MVP kW X-port 3ds Max .X exporter kW Animation source code
  • 05-11-2008 2:05 PM In reply to

    Re: Removing Individual Items From The ContentManager

    Enchanted Age:
    You can't require it be disposable, but it's easy enough to check:


    Yes; the ContentManager already checks that. The problem is that you don't get notified when something is disposed, so you need a richer interface -- else the ContentManager doesn't know to remove the disposed item from the cache.
    Indeed. That's why there should be a method that just drops the reference to the object and doesn't try to dispose it. Then you can just have the ContentManager forget all about something and handle calling Dispose on your own (or letting the finalizer get it).

    I agree that it's unfortunate that the Content field is not assignable, and is not virtual, and the "content" field is private, and there is no SetContentManager() function. However, the only thing that the Game uses the Content field for is to notify the content manager when the device is getting reset. You can arrange to do that yourself, and use your own contant manager, using your own field to access it, and putting it into the Services container yourself. It's a design deficiency in the Game class, but it can be worked around.
    Agreed. It's just unfortunate for beginners to have to do this workaround. Hopefully the team will find one way or another to satisfy this (perhaps a second ContentManager class? or at least making the Content property have a 'set' on it?) in the future.

    Nick Gravelyn -- Microsoft XNA MVP
    Blog | The Best Game. Ever. | Next-Gen
  • 05-11-2008 2:17 PM In reply to

    Re: Removing Individual Items From The ContentManager

    There are a number of suggestions filed on ContentManager on Connect, but none relating to the design problem in Game (which is really where the core design problem resides).
    I added one. Vote for it!
    https://connect.microsoft.com/feedback/ViewFeedback.aspx?FeedbackID=343348&SiteID=226


    Jon Watte, Direct3D MVP kW X-port 3ds Max .X exporter kW Animation source code
  • 05-11-2008 2:21 PM In reply to

    Re: Removing Individual Items From The ContentManager

    Voted. I especially like the repro steps:
    1) Create Game subclass using Wizard.
    2) Try to override the ContentManager.
    3) Realize that you can't.
    :p

    Nick Gravelyn -- Microsoft XNA MVP
    Blog | The Best Game. Ever. | Next-Gen
  • 05-12-2008 2:05 PM In reply to

    Re: Removing Individual Items From The ContentManager

    The Game.Content property not being settable is an oversight, and will be fixed in v3.

    As for more advanced unload functionality, I remain unconvinced. Let me explain some of the issues here...

    This is really a question of policy, rather than of technical implementation. Most (at least remotely sane) lifespan management policies can be implemented in relatively small amounts of code, so the big question is what is the right policy to enshrine in the default framework? This has big implications since any decision we make in the content manager is likely to spiral outward from there and also end up affecting all the types that are loaded through that manager.

    I blogged some of my early thinking about this here. Among the issues we need to consider are:

    Avoiding IDisposable becoming a viral mess that has to be implemented on all types you create. I'm not a big fan of IDisposable: it's a wart that is required wherever a managed object wraps a native resource, but this is really just a leaking of icky implementation details and I don't like having it on my .NET classes. Plus, this is a hard interface to get right: the recommended pattern with the public Dispose, finalizer, and virtual Dispose(bool) method is pretty subtle (corner case errors in implementing this are high on the list of problems we find during code reviews even with experienced .NET developers, for instance).

    One of the things I like least about IDisposable is that any time I make a class which has a field that is IDisposable, I must also make this new class IDisposable in order to dispose its member. That is just nasty. It means that because I have some low level data type which wraps a native resource, this native lifespan management policy must always be dragged along any time I aggregate that lower level type into a new higher level .NET class. I can never escape from everything having to be IDisposable, so effectively I've just abandoned .NET memory management and landed back in the native world of manually freeing all my objects.

    An important design goal for our built in content lifespan policy is that most XNA users should not be forced to implement IDisposable, or expected to understand how to get that right. Therefore it is important that the content manager be separated from the types it is managing. Individual types may implement IDisposable if they need to, but it is up to the manager to track these and dispose them at the right times. It should be possible to aggregate these disposable types without the container having to inhert any lifespan policy from its member types (thus, Model and SpriteFont are not IDisposable, even though they contain member data which is).

    Another important goal is to make shared resources (eg. two models using the same texture) work in a simple and consistent manner. How does that play with manual unloading? If I unload both models, clearly the texture should also be unloaded. But if I only unload one model, it must remain loaded. This requires reference counting. But what if the user also manually loads a copy of this texture? Does their attempt to Dispose this manually loaded copy then just turn into a dereference operation, such that they could Dispose the object, but still have it remain valid because another model was also referencing it? That would be an unexpected behavior in .NET land! Really, the only way I could see this working is if we exposed the idea of reference counting as a public API so people could properly understand and control what is going on. But do we really want to bring reference counting to .NET? Is that the right thing for the majority of our users?

    Jon's suggestion that the content manager should automatically stop tracking content when that content is disposed would be nice if there was a clean way to implement it, but unfortunately there isn't, for several reasons:
    • Such a policy requires all interesting types to be IDisposable, so we'd be back to requiring aggregate types such as Model to be disposable just because their member data is. I prefer the idea that native disposability is only a property of individual leaf node types, while any kind of higher level policy decision belongs to the manager, not the individual data types.
    • There is no standard way to be notified when an instance is disposed.
      • We could add a new IDisposedEvent interface, but that's pretty intrusive: we (and anyone else extending the pipeline) would then have to implement this on all relevant data types.
      • We could do a Python style duck typing thang and use reflection to see if types have a Disposed event, but, ick... not at all sure I want to go there :-)

    So, that's pretty much the stuff we thought about while designing this system.

    We plumped for our current "manager unloads everything in one swoop" policy for several reasons:
    • Keeps all the policy in the manager: data types are free to just be data types and don't need to care about lifespan policy decisions.
    • Doesn't require aggregate types to inherit IDisposable behavior.
    • No corner cases handling shared references.
    • Logically straightforward and easy to understand.

    I also think we have a pretty good extensibility story here:
    • For 50% of games, a simple "unload everything in one swoop" is perfectly adequate.
    • For 40% of games, using two or more manager instances does the trick. Most often I see people using one for global things loaded at startup, then another per level manager that is unloaded at the end of the level.
    • For the remaining 10%, the content manager is extensible enough that you can replace our simple default policy with pretty much any other policy you can imagine. In fact one of the biggest advantages of us being so careful to keep all policy in the manager rather than the individual data types is that it lets you override our policy just by customizing the content manager: our Model implementation won't care if you change this, and can still happily load shared textures using whatever refcounting or other mechanism you decide is appropriate.

    So that's where I'm at right now. I certainly agree that there are cases where our built in policy is not good enough, but I'm not convinced we are (yet, anyway) at the point where it is worth building any more policy directly into the framework. Partly, it's not clear to me exactly what that policy ought to be. Also, it's not clear what percentage of people actually need any more sophisticated policy (for instance, I've yet to see anyone care enough about this to start the "uber-extended reference counted content manager lifespan policy" project...)