Jump to content
  • Advertisement
Sign in to follow this  

[.net] Resource management (Weak references, Dispose,...)

This topic is 4694 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Recommended Posts

I'm trying to design a resource manager in .NET Now I could write my own caching mechanism but then I thought of weak references. These would allow me to have some caching without configuring duration, memory size etc. But, as far as I can see, there is no way of automagically calling Dispose when the weak reference is being destroyed to get rid of the managed resources (during finalization you should only destroy UNmanaged resources) On top of that finalization could get costly... then again: my own caching might be as costly... Any thoughts, experience with this? BTW: for those who are wondering about Weak References: read this and perhaps this Cheers

Share this post


Link to post
Share on other sites
Advertisement
I just thought of two possible options:

1. Adding a Finalizer to the resource classes (that calls Dispose) to get rid of the unmanaged resources and just use WeakReferences
- Don't know when the finalizer runs
- Can't drop managed resources
+ Easy to code

2. Using the Cache object that's usually used in ASP.NET
- More options than I need (I don't want timeouts)
+ event gets fired when the resource is released

Any additional thoughts anyone?

Cheers

BTW: brainwave: create a mananger for each of the memory types (managed, unmanaged, video) let these do memory allocation and store references (and extra info (last used)) to objects that own the memory and then dispose these when a new memory allocation fails...

Share this post


Link to post
Share on other sites
I made a sort of pool, first a generic DataPool (takes object at the moment), then ResourcePool inherits that and holds ResourceItem[s]. This manages my resources and has its state the enumeration below:


/// <summary>Describes the state of a resource.</summary>
[System.Serializable]
public enum ResourceState
{
/// <summary>The device resource is loaded. Handles are held to the resource.</summary>
Active = 1,

/// <summary>The device resource is loaded. No handles are held to the resource. The device resource is kept as
/// an optimization. It will not be restored or reloaded, it will then be removed.</summary>
Redundant = 3,

/// <summary>The device resource has been released. Handles are held to the resource.</summary>
Invalidated = 2,

/// <summary>The device resource has not yet been loaded. Handles are held to the resource.</summary>
NotLoaded = 4
}



At the moment it's up to the application to set the state to redundant, but I could make all of my game resources implement an interface requring them to give their references however my game does not have too many objects so I'll do it manually when the user changes the theme...

When you use a finalizer make sure you use SuppressFinalize in your Dispose method!

I'll give you an example of my resource implementation invalidation:

/// <summary>
/// Invalidates the resource. The state should be set to <see cref="ResourceState.Invalidated"/>,
/// if the device resource was released.
/// The state will be <see cref="ResourceState.Active"/> when this function is called and the pool is guaranteed to
/// be <see cref="Direct3D.Pool.Default"/>.
/// </summary>
protected override void OnOnInvalidate()
{
if (m_Texture != null) {
m_Texture.Dispose();
m_Texture = null;
}
this.m_State = ResourceState.Invalidated;
}

/// <summary>
/// Destroys the resource. The state is set to <see cref="ResourceState.Invalidated"/> after this is called.
/// Must be callable multiple times.
/// The state will be <see cref="ResourceState.Active"/>, <see cref="ResourceState.Invalidated"/> or
/// <see cref="ResourceState.Redundant"/> when this function is called.</summary>
protected override void OnDestroy()
{
if (m_Texture != null) {
m_Texture.Dispose();
m_Texture = null;
}
}



Notable points: I don't need a finalizer in the resource implementations. You may be wondering why one function is OnOn and the other On. That's because I override OnOnInvalidate because OnInvalidate is implemented in the base class as it is bad practise to force the derived class to do everything.

Andrew <--- hoping he's right!

~Will rate for chocolate~

Share this post


Link to post
Share on other sites
Quick questing before I read it very carefully:

Why don't you call the base class's OnInvalidate in the OnInvalidate of the deriving class... That way you don't have to reprogram and do not have to use the OnOn construction. (Suppose I inherrit a couple of times: should I add OnOnOnOnOnInvalidate... [smile])

Thanks for sharing your code.

I'll have a better look at it this weekend!

Cheers

Share this post


Link to post
Share on other sites
OnOnInvalidate is an abstract function...I'll give you the base class too:

part of TextureBase

/// <summary>
/// Invalidates the resource. The state should be set to <see cref="ResourceState.Invalidated"/>,
/// if the device resource was released.
/// The state will be <see cref="ResourceState.Active"/> when this function is called.
/// </summary>
protected override void OnInvalidate()
{
//When OnOnInvalidate is called:
// The resource is in the default pool
if (this.Pool == Direct3D.Pool.Default)
OnOnInvalidate();
}


You see, when I make the resource, which could be anything, specific to a video resource it gets a pool (as in Default, Managed or SystemMem), and the resource only needs to be invalidated if the pool is Default. By putting more logic into the base classes I hope to remove the chance of forgetting to check the pool in base classes which would make resetting more inefficient.

whole of ResourceItemBase

namespace DirectXEngines
{
/// <summary>Describes the state of a resource.</summary>
[System.Serializable]
public enum ResourceState
{
/// <summary>The device resource is loaded. Handles are held to the resource.</summary>
Active = 1,

/// <summary>The device resource is loaded. No handles are held to the resource. The device resource is kept as
/// an optimization. It will not be restored or reloaded, it will then be removed.</summary>
Redundant = 3,

/// <summary>The device resource has been released. Handles are held to the resource.</summary>
Invalidated = 2,

/// <summary>The device resource has not yet been loaded. Handles are held to the resource.</summary>
NotLoaded = 4
}


/// <summary>
/// Describes a named resource item.</summary>
/// <remarks>
/// Loading and restoring functions vary greatly in their parameters, so these need to be added to the derived class.
/// </remarks>
//Not disposable because: OnDestroy can be used instead, checking in everything whether this has been disposed is a
//great burden, and the finalizer is not a great help as DX objects have a finalizer anyway.
[System.Serializable]
[System.CLSCompliant(true)]
public abstract class ResourceItemBase : INameable
{
/// <summary>The pool that the <see cref="ResourceItemBase"/> is kept in.</summary>
private ResourcePoolBase m_ResourcePool;
/// <summary>The name of the resource. Can be a null reference (Nothing in Visual Basic).</summary>
protected string m_Name;
/// <summary>The state of the resource.</summary>
protected ResourceState m_State = ResourceState.NotLoaded;


/// <summary>
/// Initializes a new instance of the <see cref="ResourceItemBase"/> class with the specified name, and
/// adds it to the specified resource pool.</summary>
/// <param name="resourcePool">The pool that the <see cref="ResourceItemBase"/> is to be kept in.</param>
/// <param name="name">The name of the resource item. Can be a null reference (Nothing in Visual Basic).</param>
/// <exception cref="System.ArgumentNullException">
/// resourcePool is a null reference (Nothing in Visual Basic).</exception>
protected ResourceItemBase(ResourcePoolBase resourcePool, string name)
{
if (resourcePool == null) throw new System.ArgumentNullException("resourcePool");

m_ResourcePool = resourcePool;
m_Name = name;
//Add this resource to the resource pool
m_ResourcePool.Add(this);
}

/// <summary>
/// Initializes a new instance of the <see cref="ResourceItemBase"/> class with the specified resource pool, name,
/// and adds it to the resource pool.</summary>
/// <param name="resourcePool">The pool that the <see cref="ResourceItemBase"/> is to be kept in.</param>
/// <param name="name">The name of the resource item. Can be a null reference (Nothing in Visual Basic).</param>
/// <param name="poolIndex">The index in the pool that the resource has been added to.</param>
/// <exception cref="System.ArgumentNullException">
/// resourcePool is a null reference (Nothing in Visual Basic).</exception>
protected ResourceItemBase(ResourcePoolBase resourcePool, string name, out int poolIndex)
{
if (resourcePool == null) throw new System.ArgumentNullException("resourcePool");

m_ResourcePool = resourcePool;
m_Name = name;
//Add this resource to the resource pool
poolIndex = m_ResourcePool.Add(this);
}

/// <summary>
/// Initializes a new instance of the <see cref="ResourceItemBase"/> class. FOR USE WITH SERIALIZATION ONLY.
/// Some resources will not be serializable, so will have to implement
/// <see cref="System.Runtime.Serialization.ISerializable"/> and be able to call base constructors with no
/// parameters to set the resource state correctly.
/// </summary>
protected ResourceItemBase()
{
}

/// <summary>
/// Gets the pool that the <see cref="ResourceItemBase"/> is kept in.</summary>
public ResourcePoolBase ResourcePool {
get {return m_ResourcePool;}
}

/// <summary>
/// Gets the name of the resource. Can be a null reference (Nothing in Visual Basic).</summary>
public string Name {
get {return m_Name;}
}

//TODO - convert state to Get..() Set..(), get public, set protected
/// <summary>
/// Gets or sets the state of the resource.</summary>
public ResourceState State {
get {return m_State;}
set {
switch (value) {
case ResourceState.Active:
//Call Restore to restore the resource if required
this.Restore();
break;
case ResourceState.Invalidated:
//Call Destroy to guarantee that the device resource has been released
this.Destroy();
break;
case ResourceState.NotLoaded:
//Destroy and set state to not loaded
this.Destroy();
this.m_State = ResourceState.NotLoaded;
break;
case ResourceState.Redundant:
switch (this.m_State) {
case ResourceState.Active:
//Set state to redundant
this.m_State = ResourceState.Redundant;
break;
case ResourceState.Invalidated:
case ResourceState.NotLoaded:
//Destroy, then remove from pool
this.Destroy(); //This will remove a redundant resource
break;
//Ignore this state being set again
}
break;
}
}
}

/// <summary>
/// Gets the contained resource.</summary>
public abstract object Resource {get;}


/// <summary>
/// Restores the resource if required. The state should be set to <see cref="ResourceState.Active"/> if
/// the restore is successful.
/// A resource is only restored when it has the state <see cref="ResourceState.Invalidated"/> or
/// <see cref="ResourceState.NotLoaded"/>.
/// Resources with a state of <see cref="ResourceState.Redundant"/> are removed from the pool.
/// </summary>
/// <remarks>Exceptions will likely be thrown from this function if the restore is not successful.</remarks>
public void Restore()
{
//IF THIS IS ALTERED, ALSO ALTER TEXTUREBASE AND MESHBASE AND EFFECT OVERRIDES
switch (this.State) {
case ResourceState.Active:
//This resource does not need to be restored, but might need to be reset
System.Diagnostics.Debug.Assert(this.Resource != null, "ResourceState is Active, but Resource is null.");
OnResetResource();
break;
case ResourceState.Invalidated:
case ResourceState.NotLoaded:
//Call abstract OnRestore to handle loading/restoring for not-loaded/invalidated resources
OnRestore();
break;
case ResourceState.Redundant:
//A redundant resource must not be restored, so destroy and remove
this.Destroy(); //This will remove a redundant resource
break;
}
}

/// <summary>
/// Restores the resource. The state should be set to <see cref="ResourceState.Active"/> if
/// the restore is successful.
/// The state will be <see cref="ResourceState.Invalidated"/> or
/// <see cref="ResourceState.NotLoaded"/> when this function is called.
/// </summary>
/// <remarks>Exceptions will likely be thrown from this function if the restore is not successful.</remarks>
protected abstract void OnRestore();

/// <summary>
/// Resets the resource if required.
/// A resource is only reset when it has the state <see cref="ResourceState.Active"/>.
/// </summary>
/// <remarks>Exceptions will likely be thrown from this function if the reset is not successful.</remarks>
public virtual void OnResetResource() {/*do nothing*/}

/// <summary>
/// Invalidates the resource if required. The state should be set to <see cref="ResourceState.Invalidated"/>,
/// if the resource was released.
/// A resource is only invalidated when it has the state <see cref="ResourceState.Active"/>.
/// Resources with a state of <see cref="ResourceState.Redundant"/> are removed from the pool.
/// </summary>
public void Invalidate()
{
switch (this.State) {
case ResourceState.Active:
//Invalidate
OnInvalidate();
break;
case ResourceState.Invalidated:
case ResourceState.NotLoaded:
//This resource does not need to be invalidated
break;
case ResourceState.Redundant:
//A redundant resource will not be restored, so destroy and remove
this.Destroy(); //This will remove a redundant resource
break;
}
}


/// <summary>
/// Invalidates the resource. The state should be set to <see cref="ResourceState.Invalidated"/>,
/// if the device resource was released.
/// The state will be <see cref="ResourceState.Active"/> when this function is called.
/// </summary>
protected abstract void OnInvalidate();


/// <summary>
/// Destroys the device resource if required. The state is set to <see cref="ResourceState.Invalidated"/>.
/// Resources with a state of <see cref="ResourceState.Redundant"/> are removed from the pool.
/// This must be callable without <see cref="Invalidate"/> having been previously called.</summary>
/// <remarks>
/// Like invalidation, but forced. For example, only textures in Direct3D.Pool.Default need to be invalidated.
/// </remarks>
public void Destroy()
{
switch (this.State) {
case ResourceState.Active:
//Destroy
OnDestroy();
m_State = ResourceState.Invalidated;
break;
case ResourceState.Invalidated: //Invalidation means that the device resource has been released
case ResourceState.NotLoaded: //No device resource has been loaded
break;
case ResourceState.Redundant:
//A redundant resource will not be restored, so destroy and remove
OnDestroy();
//Best to set state here in case this is still floating about after removal
m_State = ResourceState.Invalidated;
m_ResourcePool.Remove(this);
break;
}
}

/// <summary>
/// Destroys the resource. The state is set to <see cref="ResourceState.Invalidated"/> after this is called.
/// Must be callable multiple times.
/// The state will be <see cref="ResourceState.Active"/>, <see cref="ResourceState.Invalidated"/> or
/// <see cref="ResourceState.Redundant"/> when this function is called.</summary>
protected abstract void OnDestroy();

}
}



I'm very proud of my resource system because it seems to be so clean and simple... And that without doing any proper design at all... [grin]

I've learnt UML now though so I'm going to do proper design when I get back to it. Only 1 exam left!! And it's additional maths!!! I've finshed all my GCSEs now!!!
Oh, and I learnt how to do FSM diagrams in UML, so now I've realised I can make my game play (of a game of Rock Paper Scissors) much better by having events (such as hands flying toward each other) as transitions between states! At the moment I have some 'Environment Controllers' that switch to the next one when they finsh...ugh! I'm gonna be doing some refactoring!!

Share this post


Link to post
Share on other sites
Sign in to follow this  

  • Advertisement
×

Important Information

By using GameDev.net, you agree to our community Guidelines, Terms of Use, and Privacy Policy.

Participate in the game development conversation and more when you create an account on GameDev.net!

Sign me up!