Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added selective ContentManager asset unloading #6663

Merged
merged 1 commit into from Apr 26, 2021

Conversation

kimimaru4000
Copy link
Contributor

This PR adds two new methods to ContentManager:

  1. UnloadAsset - Unloads a single asset and disposes it if it's disposable
  2. UnloadAssets - Takes in an IList<string> of asset names to unload and disposes the associated assets if they're disposable

Relevant issue: #6164

@nkast
Copy link
Contributor

nkast commented Feb 16, 2019

@tdeeb

-What would happen if I unload a Model ?
(will it free up the resources? VertexBuffers,Effects, and textures?)

-What would happen if I continuously unload and reload the same Model?
(when the player navigates back and forth the menus for example)

(the same applies to any custom assets that isn't disposable but contains native resources, not just Models)

@kimimaru4000
Copy link
Contributor Author

kimimaru4000 commented Feb 16, 2019

-I'm not the most knowledgeable about models, but I just went through the code and it doesn't look to unload any of those. The currently existing Unload method won't do so either. The user has to free these up manually.

-It would keep creating new Effects, etc. and likely cause memory leaks, should the user fail to manually free up the additional resources the model uses.

-If custom assets contain native resources, they should be implementing IDisposable. See here. Failure to do so gives them no standardized mechanism of releasing them, and they'll have to find another way to properly free up everything, either by manually doing it or overriding these methods to contain code specific to certain types of assets.

@Jjagg
Copy link
Contributor

Jjagg commented Feb 16, 2019

The currently existing Unload method won't do so either. The user has to free these up manually.

No, the content manager used to load the model will contain all disposable resources the model requires like effects and textures, so they're disposed when the content manager is unloaded.

If custom assets contain native resources, they should be implementing IDisposable

They should after this change, but just like Model, assets that can only be created through the content pipeline could depend on the unloading behavior instead of implementing IDisposable.

@mrhelmut
Copy link
Contributor

I think that unloading models should only unload the vertex buffer to give the control to the developer to handle the effect/texture on their own if need be (thinking about shared effect/texture). Which is what this PR does.

@kimimaru4000
Copy link
Contributor Author

What modifications need to be made to get this merged?

@BigBenEco
Copy link

As far as I understand the C# language, I don't think this code is safe to use.

While it will remove the item from memory, it does not remove any pointers to it, which could cause null point exceptions.

This is because the way the content manager works, any time a part of the code wants to load the asset, (say for example, grass in a game, an object containing location and texture) it checks to see if it already has the asset. If yes, it just returns the pointer to the same asset. (pointer, pass by reference, asset is a generic, generics are classes, classes are only ever handled by reference). So say, I have lots of grass objects, and I go into a house, so I unload a scene, we have to make sure at no point does the grass object act on or off of the texture after it gets loaded, every object owning a pointer to the asset must remove that connection or the parent object must be deleted (disconnected and forced garbage collected, maybe even defragged by a second forced collection), or by some other trick make sure the texture is not used ever again, so that you can then unload the asset safely knowing that no other objects have fantom memories of the asset. Generally, the idea is not idiot-proof, that doesn't make the code bad, but it does make it an issue.

Basically, you have to be smart and safe with the unloading.
You could modify the content manager such that it keeps track of all connections, and removes them when unloading, but that does not help with if an object wants to try to access the asset again for some reason in the code, unloading is generally not safe.

A typical workaround that is kinda safe, it seems many users make a unique content manager for each unique scene, which they tell to unload or rather, dispose, at the end of the scene. But that still requires users to design scene so that all the objects are destroyed as well. The only issue with this, however, is now you have to reload all assets, none carry over. A bit more load time at the risk of memory conservation. though I would also garbage-collect and defragment at this time as well.

@jnoyola
Copy link
Contributor

jnoyola commented Apr 24, 2020

Not sure if this has been on anyone’s mind lately, but I’m also curious about what this needs or what decisions need to be made to be able to merge.

While it will remove the item from memory, it does not remove any pointers to it, which could cause null point exceptions.

I don’t think this should be a concern, because unloading the entire ContentManager has the same problem. It’s up to the developer to manage either way.

@mrhelmut
Copy link
Contributor

It's up to the developer to manage the lifecycle of objects and the content manager is no substitute to this.

I believe that this PR is as good as it can get, though it would be nice to update the documentation to highlight the fact that unloading a Model will not unload its associated content (e.g. textures, shaders) because they might be shared.

@jnoyola
Copy link
Contributor

jnoyola commented Apr 24, 2020

unloading a Model will not unload its associated content (e.g. textures, shaders)

What are the options for manually unloading these? I haven't looked through how associated content is loaded, but would it be possible to unload these with an additional call to ContentManager.UnloadAsset("texture") despite it not having been loaded like that? Or is the only way to unload the entire ContentManager?

/// Unloads a set of assets.
/// </summary>
/// <param name="assetNames">The names of the assets to unload.</param>
public virtual void UnloadAssets(IList<string> assetNames)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For usability, can this be params string[] assetNames instead?

/// Unloads a single asset.
/// </summary>
/// <param name="assetName">The name of the asset to unload. This cannot be null.</param>
public virtual void UnloadAsset(string assetName)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does overloading Unload make more sense than UnloadAsset, since the load method is Load, not LoadAsset?

@mrhelmut
Copy link
Contributor

It depends on the setup, but basically yes. Either resources are internal to a model (and are therefore freed with the model) or they are external (and are not freed but can be unloaded through the content pipeline by naming them manually).

if (disposable != null)
{
disposable.Dispose();
disposableAssets.Remove(disposable);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure why disposableAssets is a list, since it seems like it's only used as a set. Should it be changed to a set for perf here?

@mrhelmut mrhelmut mentioned this pull request Apr 22, 2021
@mrhelmut
Copy link
Contributor

@Jjagg @harry-cpp May I request a review to move forward? It's good to go to me, and could be improved later on demand.

@Jjagg
Copy link
Contributor

Jjagg commented Apr 22, 2021

I'm fine with merging this as is.
@harry-cpp?

@mrhelmut mrhelmut merged commit de5703a into MonoGame:develop Apr 26, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

6 participants