Tim's Libraries/TGraphicCollection.cp

/*
    File:       TGraphicCollection.cp
 
    Contains:   A TGraphicCollection is a group of related TGraphic objects.  The API is
                very similar to that of TGraphic, but includes an index to determine which
                TGraphic is receiving the command.
 
                This class is tightly coupled to the TGraphic class, so it has all of the
                same limitations.  
 
                Note that this class isn't currently intended to be subclassed, so all of
                the methods are non-virtual.  This might change if at some point we
                determine we need to create a subclass.
    
    Written by: Timothy Carroll 
 
    Copyright:  Copyright © 1996-1999 by Apple Computer, Inc., All Rights Reserved.
 
                You may incorporate this Apple sample source code into your program(s) without
                restriction. This Apple sample source code has been provided "AS IS" and the
                responsibility for its operation is yours. You are not permitted to redistribute
                this Apple sample source code as "Apple sample source code" after having made
                changes. If you're going to re-distribute the source, we require that you make
                it clear in the source that the code was descended from Apple sample source
                code, but that you've made changes.
 
    Change History (most recent first):
                7/2/1999    Karl Groethe    Updated for Metrowerks Codewarror Pro 2.1
                
                11/5/97                     Cleaned up to work with 3.x Universal Headers 
                                            and CW Pro. Cleaned up some comments.  Made same
                                            changes to list management code as were made in
                                            TGraphic.  Should revisit making a generic list
                                            class, if performance isn't killed.
                                            
                4/2/97                      CreateCollection was calling ReleaseResource twice,
                                            because I forgot to remove the original call to 
                                            ReleaseResource when I reorganized some of the
                                            cleanup code.  Onyx's Spotlight spotted this one for 
                                            me.
 
                2/24/97                     Explicitly includes the error macros now.  We should 
                                            be able to build on other compilers than Metrowerks.
 
                
 
*/
 
 
#include <Memory.h>
#include <Resources.h>
 
#include "Error Macros.h"
#include "TGraphicCollection.h"
 
 
/*************************************************************************************
TGraphicCollections and List Management
 
TGraphicCollections are allocated and maintained in a list internally by this class.
Each object is reference counted, to minimize the need to load resources more than
once.
 
For optimal performance, pre-load the TGraphicCollection once outside the game loop.
This should mean that all searches hit inside the list.  Theoretically, you can also
just create one TGraphicCollection, and everybody who needs it gets a copy of the
handle.  Eliminates the need for reference counting, but then you need to make sure
any references to it are eliminated.
 
The list is sorted by the collections's resource ID, and uses standard C array indexing.
The list is searched using a binary search. 
*************************************************************************************/
 
static Handle gCollectionList = NULL;
static UInt32 gNumberCollections = 0;
 
static OSStatus InsertCollectionIntoList (TGraphicCollection *theCollection, UInt32 index);
static OSStatus DeleteCollectionFromList (UInt32 index);
static OSStatus SearchCollectionList (SInt16 resID, Boolean *found, UInt32 *index);
 
/*****************************************************************************
InsertCollectionIntoList
 
This routine creates the list if it hasn't been initialized.  Otherwise, it
just inserts the collection into the spot pointed to by the index.  If an error
occurs, the list is left in an indeterminate state.
*****************************************************************************/
 
OSStatus InsertCollectionIntoList (TGraphicCollection *theCollection, UInt32 index)
{
    OSStatus theErr = noErr;
 
#if qDebugging
    if (theCollection == NULL)
        SIGNAL_ERROR ("\pAttempting to insert a null collection into the list") 
    if ((index > gNumberCollections) || (index < 0))
        SIGNAL_ERROR ("\pAttempting to insert collection at invalid index")
#endif
    
    // if we don't have a list allocate one
    if (gCollectionList == NULL)
    {
        gNumberCollections = 1;
        
        gCollectionList = NewHandleClear (sizeof (TGraphicCollection *));
        theErr = MemError();
        FAIL_OSERR (theErr, "\pCouldn't allocate a new handle of information")
        FAIL_NIL (gCollectionList, "\pCouldn't allocate a new handle of information")
    }
    else
    // if we do have a list, expand it by one node.
    {
        gNumberCollections++;
        SetHandleSize(gCollectionList,gNumberCollections*sizeof(TGraphicCollection *));
        
        theErr = MemError();
        FAIL_OSERR (theErr, "\pCouldn't resize the list handle")
        FAIL_NIL (gCollectionList, "\pCouldn't resize the list handle")
        
        // Shift the data to make room
        BlockMoveData(  (*(TGraphicCollection ***)gCollectionList)+index,
                        (*(TGraphicCollection ***)gCollectionList)+index+1,
                        (gNumberCollections - index-1) * sizeof(TGraphicCollection *));
    }
    
    // finally, set the new object in place
            *((*(TGraphicCollection ***) gCollectionList)+index)     = theCollection;
 
    goto cleanup;
    
error:
    if (theErr == noErr)
        theErr = paramErr;
        
cleanup:
    return theErr;
}
 
 
/*****************************************************************************
DeleteCollectionFromList
    
This routine removes the collection from the list.  If the list is then empty,
it disposes of the handle.  If an error occurs, the list is left in an 
indeterminate state.
*****************************************************************************/
OSStatus DeleteCollectionFromList (UInt32 index)
{
    OSStatus theErr = noErr;
    
#if qDebugging
    if ((index < 0) || (index > gNumberCollections-1))
        SIGNAL_ERROR ("\pError: Attempted to delete outside of the list.")
#endif
    
    gNumberCollections--;
    
    //  slide remaining elements up
    
    BlockMoveData(  (*(TGraphicCollection ***)gCollectionList)+index+1,
                    (*(TGraphicCollection ***)gCollectionList)+index,
                    (gNumberCollections - index) * sizeof(TGraphicCollection *));
 
    //  Shrink the handle   
    if (gNumberCollections> 0)
    {
        SetHandleSize((Handle) gCollectionList,gNumberCollections*sizeof(TGraphicCollection *));
        theErr = MemError();
        FAIL_OSERR (theErr, "\pCouldn't resize the list handle")
        FAIL_NIL (gCollectionList, "\pCouldn't resize the list handle")
    }
    else
    {
        DisposeHandle (gCollectionList);
        gCollectionList = NULL;
    }
    
    // We succeeded, cleanup and exit.
    goto cleanup;
 
error:
    if (theErr == noErr)
        theErr = paramErr;
        
cleanup:
    return theErr;
}
 
 
/*****************************************************************************
SearchCollectionList
    
This routine searches the list for an existing collection with that resID.
If it doesn't find one, the index points to where that resID should be
inserted into the list.
*****************************************************************************/
OSStatus SearchCollectionList (SInt16 resID, Boolean *found, UInt32 *index)
{   
    OSStatus    theErr = noErr;
    UInt32      low = 0;
    UInt32      high = gNumberCollections;
    UInt32      tempIndex;
    SInt16      tempID;
    TGraphicCollection *theItem;
        
    while (low < high)
    {
        tempIndex = (low+high) >> 1;
        theItem = (*(TGraphicCollection ***)gCollectionList)[tempIndex];
#if qDebugging
        FAIL_NIL (theItem, "\pBad TGraphic object")
#endif
        tempID = theItem->GetResID();
        if (resID == tempID)
        {
            *found = true;
            *index = tempIndex;
            goto cleanup;
        }
        else if (resID < tempID)
        // element must be below the current index
            high = tempIndex;                   
        else
        // element must be above the current index.
            low = tempIndex+1;
    }
    
    // We fell through, so we didn't find the item in the list.
    
    *found = false;
    *index = (low+high) >> 1;
    
    goto cleanup;
    
error:
    if (theErr == noErr)
        theErr = paramErr;
cleanup:
    return theErr;
 
 
}
/*****************************************************************************
TGraphicCollection::NewCollection
 
This uses the list management routines to create and load a TGraphicCollection.
*****************************************************************************/
TGraphicCollection
*TGraphicCollection::NewCollection (SInt16 resID)
{
    OSStatus            theErr = noErr;
    UInt32              listIndex;
    Boolean             collectionExists;
    TGraphicCollection  *newCollection = NULL;
    
    theErr = SearchCollectionList (resID, &collectionExists, &listIndex);
    FAIL_OSERR(theErr,"\pError: Failed to search the collection list")
    
    if (collectionExists)
    {
        // Bump the reference count on the existing collection.
        newCollection = (*(TGraphicCollection ***)gCollectionList)[listIndex];
        newCollection->AddReference();
    }
    else
    {
        // create a new collection
        newCollection = new TGraphicCollection(resID);
        theErr = newCollection->CreateCollection ();
        FAIL_OSERR(theErr,"\pCouldn't create new TGraphicCollection")
        theErr = InsertCollectionIntoList(newCollection, listIndex);
        FAIL_OSERR(theErr, "\pCouldn't add new TGraphicCollection to list")
        newCollection->AddReference();
    }
    
    // We succeeded, cleanup and return the collection.
    goto cleanup;
    
error:
    if (newCollection != NULL)
    {
        delete newCollection;
        newCollection = NULL;
    }
    
cleanup:    
    return newCollection;
}
 
 
/*****************************************************************************
TGraphicCollection::AddReference
*****************************************************************************/
 
void
TGraphicCollection::AddReference (void)
   {
    fReferenceCount++;
   }
   
   
/*****************************************************************************
TGraphicCollection::DisposeReference
*****************************************************************************/
 
void 
TGraphicCollection::DisposeReference (void)
{
    fReferenceCount--;
    if (fReferenceCount == 0)
    {
        UInt32      listIndex;
        Boolean     tableEntry;
        OSStatus    theErr;
 
        theErr = SearchCollectionList (fResID, &tableEntry, &listIndex);
                
        FAIL_OSERR (theErr, "\pFailed to search the CollectionList")
        FAIL_FALSE (tableEntry, "\pFailed to find an existing TGraphicCollection in list")
                
        theErr = DeleteCollectionFromList(listIndex);
        FAIL_OSERR (theErr, "\pFailed to delete Collection from the list")
                
        delete this;
    }
        
    error:
    return;
}
   
 
/*****************************************************************************
TGraphicCollection::TGraphicCollection
 
The constructor just sets a few clean values -- the real work is done later.
*****************************************************************************/
 
TGraphicCollection::TGraphicCollection (SInt16 resID)
{
    fResID = resID;
    fReferenceCount = 0;
    fGraphics = NULL;
}
 
 
/*****************************************************************************
TGraphicCollection::~TGraphicCollection
    
The destructor just disposes of any data and returns.
*****************************************************************************/
 
TGraphicCollection::~TGraphicCollection (void)
{
    (void) DestroyCollection();
}
 
 
/*****************************************************************************
TGraphicCollection::CreateCollection
 
We load the 'SptA' resource, and extract the resource IDs of our TGraphic
objects we want to load.  Assumes those resources are in the current
resource fork.
 
The format of a 'SptA' resource is a 2 byte unsigned integer for the number
of elements in the list, followed by the resource IDs.  In the future, this
should probably have a version number, so we can change the format.
*****************************************************************************/
 
OSStatus TGraphicCollection::CreateCollection(void)
{
    Handle      collectionResource = Get1Resource ('SptA', fResID);
    OSStatus    theErr = ResError();
    UInt32      loop;
    
    FAIL_OSERR (theErr, "\pResource manager couldn't load the sprite resource")
    FAIL_NIL (collectionResource, "\pResource manager returned a null handle")
    
    // Find out how many TGraphcs we need to load, and allocate a handle
    // to hold the TGraphic pointers.
    
    fNumberOfGraphics = *((SInt16 *) (*collectionResource));
    fGraphics = NewHandleClear (fNumberOfGraphics * sizeof (TGraphic *));
    FAIL_NIL (fGraphics, "\pCouldn't allocate collection handle")
 
    // Load each of the TGraphics.
    
    HLock (fGraphics);
    
    for (loop = 1; loop <= fNumberOfGraphics; loop++)
    {
        SInt16  graphicResID = *((SInt16 *) ((*collectionResource)+sizeof (SInt16)*loop));
        TGraphic *theGraphic = TGraphic::NewGraphic (graphicResID);
        FAIL_NIL (theGraphic, "\pFailed to load graphic for collection")
        
        *((*(TGraphic ***)fGraphics)+loop-1) = theGraphic;
    }
    HUnlock (fGraphics);
    
    goto cleanup;
    
error:
    if (theErr == noErr)
        theErr = paramErr;
    
    // We didn't explictly destroy the collection if an error occured, because
    // the object will be deleted anyway.
cleanup:
    if (collectionResource != NULL)
        ReleaseResource (collectionResource);
    
    return theErr;
}
 
 
/*****************************************************************************
TGraphicCollection::DestroyCollection
    
Throw away all of the objects that we're created.  We do check for null TGraphic objects here,
and properly skip null objects that might not have been finished from the create calls.
*****************************************************************************/
 
OSStatus
TGraphicCollection::DestroyCollection (void)
{
    UInt32 loop;
    
    if (fGraphics)
    {
        HLock (fGraphics);
        for (loop = 0; loop < fNumberOfGraphics ; loop++)
        {       
            TGraphic *theGraphic = *((*(TGraphic ***)fGraphics)+loop);
            if (theGraphic != NULL)
                theGraphic->DisposeReference();
        }
        DisposeHandle (fGraphics);
        fGraphics = NULL;
    }
    return noErr;
}
 
 
 
 
 
/*****************************************************************************
TGraphicCollection::LockCollection
*****************************************************************************/
 
OSStatus
TGraphicCollection::LockCollection (void)
{
    UInt32      loop;
    OSStatus    theErr = noErr;
    
    MoveHHi (fGraphics);
    theErr = MemError();
    FAIL_OSERR (theErr, "\pCouldn't move fGraphics handle high")
    HLock (fGraphics);
    theErr = MemError();
    FAIL_OSERR (theErr, "\pCouldn't lock fGraphics handle")
    
    TGraphic **ptrToGraphics = (TGraphic **) *fGraphics;
    
    for (loop = 0; loop < fNumberOfGraphics ; loop++)
    {
        TGraphic *theGraphic = *ptrToGraphics++;
        theGraphic->LockGraphic();
    }
    
    return noErr;
    
    error:
    return theErr;
}
 
 
/*****************************************************************************
TGraphicCollection::UnlockCollection
*****************************************************************************/
 
OSStatus
TGraphicCollection::UnlockCollection (void)
{
    UInt32 loop;
    OSStatus theErr = noErr;
    
    // Should still be locked from the lock collection call.  We should do some testing
    // to make sure we aren't locking and unlocking things multiple times here.
    TGraphic **ptrToGraphics = (TGraphic **) *fGraphics;
    
    for (loop = 0; loop < fNumberOfGraphics ; loop++)
    {
        TGraphic *theGraphic = *ptrToGraphics++;
        theGraphic->UnlockGraphic();
    }
    
    HUnlock (fGraphics);
    theErr = MemError();
    FAIL_OSERR (theErr, "\pCouldn't unlock fGraphics handle")
    
    return noErr;
    
    error:
    return theErr;
}
 
 
/*****************************************************************************
TGraphicCollection::GetBounds
*****************************************************************************/
 
Rect
TGraphicCollection::GetBounds (UInt32 index)
{
    TGraphic *theGraphic;
#if qDebugging
    if  (index >= fNumberOfGraphics)
        // We're outside of the bounds of the graphics table here.  Debugger time! :-)
            SIGNAL_ERROR("\ptried to index a TGraphic that was out of bounds");
#endif
 
    theGraphic = *((*(TGraphic ***)fGraphics)+index);
 
#if qDebugging
    if (theGraphic == NULL)
            SIGNAL_ERROR("\pRetrieved a null TGRAPHIC object")
#endif
    
    return (theGraphic->GetBounds ());
 
#if qDebugging
    error:
    Rect theRect = {0,0,0,0};
    return theRect;
#endif
}
 
 
/*****************************************************************************
TGraphicCollection::CopyImage
*****************************************************************************/
 
void
TGraphicCollection::CopyImage (UInt32 index, SInt32 top, SInt32 left, Boolean useBackground)
{
    TGraphic *theGraphic;
#if qDebugging
    if  (index >= fNumberOfGraphics)
        // We're outside of the bounds of the graphics table here.  Debugger time! :-)
            SIGNAL_ERROR("\ptried to index a TGraphic that was out of bounds");
#endif
 
    theGraphic = *((*(TGraphic ***)fGraphics)+index);
 
#if qDebugging
    if (theGraphic == NULL)
            SIGNAL_ERROR("\pRetrieved a null TGRAPHIC object")
#endif
    
    theGraphic->CopyImage(top, left, useBackground);
 
    error:
    return;
}
 
 
/*****************************************************************************
    TGraphicCollection::HitTest
 
*****************************************************************************/
 
Boolean
TGraphicCollection::HitTest (UInt32 index, SInt32 v, SInt32 h)
{
    TGraphic *theGraphic;
#if qDebugging
    if  (index >= fNumberOfGraphics)
        // We're outside of the bounds of the graphics table here.  Debugger time! :-)
            SIGNAL_ERROR("\ptried to index a TGraphic that was out of bounds");
#endif
 
    theGraphic = *((*(TGraphic ***)fGraphics)+index);
 
#if qDebugging
    if (theGraphic == NULL)
            SIGNAL_ERROR("\pRetrieved a null TGRAPHIC object")
#endif
    
    return theGraphic->HitTest (v, h);
 
    error:
    return false;
}
 
 
/*****************************************************************************
TGraphicCollection::GetGraphicObject
*****************************************************************************/
 
TGraphic 
*TGraphicCollection::GetTGraphic (UInt32 index)
{
    TGraphic *theGraphic;
#if qDebugging
    if  (index >= fNumberOfGraphics)
        // We're outside of the bounds of the graphics table here.  Debugger time! :-)
            SIGNAL_ERROR("\ptried to index a TGraphic that was out of bounds");
#endif
 
    theGraphic = *((*(TGraphic ***)fGraphics)+index);
#if qDebugging
    if (theGraphic == NULL)
            SIGNAL_ERROR("\pRetrieved a null TGRAPHIC object")
#endif
    
    return theGraphic;
 
    error:
    
    return NULL;
}