Element 61

Wednesday, August 10, 2005

Resource Management System -- Part 2

In Part 1 of this series, we looked at the basic structure of our resource management system. Now, we're going to implement the core class of our system, the Handle. You might want to go back and review the responsibilities behaviors of the handle class, which I discussed in part 1. Remember, the real power of this design is contained within the handle class. It's the object that is moved around between subsystems inside the engine, and it's the only thing clients should see. Let's jump right into the code for it:

template<typename Type> class ManagerBase;
template<typename Type> class Manager;

//defines a resource handle to work with
template<typename Type>
class Handle
{
private:
    friend ManagerBase<Type>;
    friend Manager<Type>;
    
    //internal id is just an integer (0 is reserved for invalid ids)
    typedef std::size_t resource_id;

    resource_id        m_Id;
    Manager<Type>*    m_Parent;

    //construct a valid handle

    Handle( resource_id Id, Manager<Type>* Parent ) : m_Id( Id ), m_Parent( Parent )
    {
        assert( m_Id != 0 );
        assert( m_Parent != NULL );
        m_Parent->AddRef( *this );
    }


public:
    //construct an invalid handle
    Handle()
    {
        m_Id = 0;
        m_Parent = NULL;
    }

    ~Handle()
    {
        if( m_Parent != NULL )
            m_Parent->Release( *this );
    }

    //copy a handle (works on valid and invalid handles)

    Handle( const Handle& Other ) : m_Id( Other.m_Id ), m_Parent( Other.m_Parent )
    {
        if( m_Parent != NULL )
        {
            m_Parent->AddRef( *this );
        }
    }

    //assign a handle (works on valid and invalid handles)

    const Handle& operator = ( const Handle& rhs )
    {
        if( m_Id && m_Parent )
            m_Parent->Release( *this );

        m_Id = rhs.m_Id;
        m_Parent = rhs.m_Parent;

        if( m_Parent != NULL )
            m_Parent->AddRef( *this );

        return *this;
    }

    bool operator < ( const Handle<Type>& rhs ) const { return m_Id < rhs.m_Id; }
    bool operator > ( const Handle<Type>& rhs ) const { return m_Id > rhs.m_Id; }
    bool operator == ( const Handle<Type>& rhs ) const { return m_Id == rhs.m_Id; }
    bool operator != ( const Handle<Type>& rhs ) const { return m_Id != rhs.m_Id; }
    
    bool Valid() const { return m_Id != 0; }
};
Ok, so let's take a look. We have two members. One is the array index of the resource this handle is attached to. The other is a pointer to the manager that we're part of. This pointer is mainly there because handles need to call home from time to time, and we wouldn't want to do anything nasty with globals or, dare I say it, singletons. Handles are a friend of the manager of the same type, because the manager will need to know our resource id later on. We don't want to reveal our resource id publicly; there's no reason anyone else should need it. You'll also notice that I've said that any handle with a resource id of 0 is invalid. This gives us a really easy way of checking whether a handle is invalid. (Strictly speaking the parent should always be NULL as well, but the redundancy can't hurt.)

You might notice that the comparison operators don't take the parent into account. I've assumed that two handles being compared always have the same parent. If this isn't the case in your code, you should make sure to alter the checks with '&& m_Parent == rhs.m_Parent'.
Now, behavior. First of all, you should notice that the only constructor to create a valid handle is private. Only a manager can create a real handle. Everyone else can create an invalid handle, copy an existing handle, or assign from an existing handle. Handles can be compared; the comparison uses their internal ids, without exposing the ids to the client. I haven't included a full set of comparison operators for brevity's sake. The main reason for equality and inequality operators is so that we can figure out whether or not two handles refer to the same object. The other operators, operator < in particular, are designed to help using handles in STL containers that rely on functors such as less<_T>. And that sums up all of the functionality of the handle. Clients can't do much of anything with it, or modify it in any way.

A word about managers. In the code above, there's a forward declare for a templated Manager and a templated ManagerBase. Both classes will be implemented in the next part. ManagerBase will carry functionality that is common to all managers. Manager will be specialized for each resource type, and will carry logic that is specific to that resource type. For the next part in this series, I'll be implementing a manager to work with images. It'll allow us to control when we load and unload images, as well as making sure that we never load an image more than once.

1 Comments:

  • Nice. Very interesting system. Any idea when the next chapter in the serise will be up?

    By Anonymous Anonymous, at 10/28/2005 5:22 AM  

Post a Comment

<< Home