SimpleAudio/Plug-In/SA_Object.cpp

/*
     File: SA_Object.cpp 
 Abstract:  Part of SimpleAudioDriver Plug-In Example  
  Version: 1.0.1 
  
 Disclaimer: IMPORTANT:  This Apple software is supplied to you by Apple 
 Inc. ("Apple") in consideration of your agreement to the following 
 terms, and your use, installation, modification or redistribution of 
 this Apple software constitutes acceptance of these terms.  If you do 
 not agree with these terms, please do not use, install, modify or 
 redistribute this Apple software. 
  
 In consideration of your agreement to abide by the following terms, and 
 subject to these terms, Apple grants you a personal, non-exclusive 
 license, under Apple's copyrights in this original Apple software (the 
 "Apple Software"), to use, reproduce, modify and redistribute the Apple 
 Software, with or without modifications, in source and/or binary forms; 
 provided that if you redistribute the Apple Software in its entirety and 
 without modifications, you must retain this notice and the following 
 text and disclaimers in all such redistributions of the Apple Software. 
 Neither the name, trademarks, service marks or logos of Apple Inc. may 
 be used to endorse or promote products derived from the Apple Software 
 without specific prior written permission from Apple.  Except as 
 expressly stated in this notice, no other rights or licenses, express or 
 implied, are granted by Apple herein, including but not limited to any 
 patent rights that may be infringed by your derivative works or by other 
 works in which the Apple Software may be incorporated. 
  
 The Apple Software is provided by Apple on an "AS IS" basis.  APPLE 
 MAKES NO WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION 
 THE IMPLIED WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS 
 FOR A PARTICULAR PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND 
 OPERATION ALONE OR IN COMBINATION WITH YOUR PRODUCTS. 
  
 IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL 
 OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 
 SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 
 INTERRUPTION) ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION, 
 MODIFICATION AND/OR DISTRIBUTION OF THE APPLE SOFTWARE, HOWEVER CAUSED 
 AND WHETHER UNDER THEORY OF CONTRACT, TORT (INCLUDING NEGLIGENCE), 
 STRICT LIABILITY OR OTHERWISE, EVEN IF APPLE HAS BEEN ADVISED OF THE 
 POSSIBILITY OF SUCH DAMAGE. 
  
 Copyright (C) 2013 Apple Inc. All Rights Reserved. 
  
*/
/*==================================================================================================
    SA_ObjectMap.cpp
==================================================================================================*/
 
//==================================================================================================
//  Includes
//==================================================================================================
 
//  Self Include
#include "SA_Object.h"
 
//  PublicUtility Includes
#include "CADebugMacros.h"
#include "CADispatchQueue.h"
#include "CAException.h"
 
//==================================================================================================
#pragma mark -
#pragma mark SA_Object
//==================================================================================================
 
#pragma mark Construction/Destruction
 
SA_Object::SA_Object(AudioObjectID inObjectID, AudioClassID inClassID, AudioClassID inBaseClassID, AudioObjectID inOwnerObjectID)
:
    mObjectID(inObjectID),
    mClassID(inClassID),
    mBaseClassID(inBaseClassID),
    mOwnerObjectID(inOwnerObjectID),
    mIsActive(false)
{
}
 
void    SA_Object::Activate()
{
    mIsActive = true;
}
 
void    SA_Object::Deactivate()
{
    mIsActive = false;
}
 
SA_Object::~SA_Object()
{
}
 
#pragma mark Property Operations
 
bool    SA_Object::HasProperty(AudioObjectID inObjectID, pid_t inClientPID, const AudioObjectPropertyAddress& inAddress) const
{
    #pragma unused(inObjectID, inClientPID)
    
    bool theAnswer = false;
    switch(inAddress.mSelector)
    {
        case kAudioObjectPropertyBaseClass:
        case kAudioObjectPropertyClass:
        case kAudioObjectPropertyOwner:
        case kAudioObjectPropertyOwnedObjects:
            theAnswer = true;
            break;
    };
    return theAnswer;
}
 
bool    SA_Object::IsPropertySettable(AudioObjectID inObjectID, pid_t inClientPID, const AudioObjectPropertyAddress& inAddress) const
{
    #pragma unused(inObjectID, inClientPID)
    
    bool theAnswer = false;
    switch(inAddress.mSelector)
    {
        case kAudioObjectPropertyBaseClass:
        case kAudioObjectPropertyClass:
        case kAudioObjectPropertyOwner:
        case kAudioObjectPropertyOwnedObjects:
            theAnswer = false;
            break;
        
        default:
            Throw(CAException(kAudioHardwareUnknownPropertyError));
            break;
    };
    return theAnswer;
}
 
UInt32  SA_Object::GetPropertyDataSize(AudioObjectID inObjectID, pid_t inClientPID, const AudioObjectPropertyAddress& inAddress, UInt32 inQualifierDataSize, const void* inQualifierData) const
{
    #pragma unused(inObjectID, inClientPID, inQualifierDataSize, inQualifierData)
    
    UInt32 theAnswer = 0;
    switch(inAddress.mSelector)
    {
        case kAudioObjectPropertyBaseClass:
        case kAudioObjectPropertyClass:
            theAnswer = sizeof(AudioClassID);
            break;
            
        case kAudioObjectPropertyOwner:
            theAnswer = sizeof(AudioObjectID);
            break;
            
        case kAudioObjectPropertyOwnedObjects:
            theAnswer = 0;
            break;
        
        default:
            Throw(CAException(kAudioHardwareUnknownPropertyError));
            break;
    };
    return theAnswer;
}
 
void    SA_Object::GetPropertyData(AudioObjectID inObjectID, pid_t inClientPID, const AudioObjectPropertyAddress& inAddress, UInt32 inQualifierDataSize, const void* inQualifierData, UInt32 inDataSize, UInt32& outDataSize, void* outData) const
{
    #pragma unused(inObjectID, inClientPID, inQualifierDataSize, inQualifierData)
    
    switch(inAddress.mSelector)
    {
        case kAudioObjectPropertyBaseClass:
            //  This is the AudioClassID of the base class of this object. This is an invariant.
            ThrowIf(inDataSize < sizeof(AudioClassID), CAException(kAudioHardwareBadPropertySizeError), "SA_Object::GetPropertyData: not enough space for the return value of kAudioObjectPropertyBaseClass");
            *reinterpret_cast<AudioClassID*>(outData) = mBaseClassID;
            outDataSize = sizeof(AudioClassID);
            break;
            
        case kAudioObjectPropertyClass:
            //  This is the AudioClassID of the class of this object. This is an invariant.
            ThrowIf(inDataSize < sizeof(AudioClassID), CAException(kAudioHardwareBadPropertySizeError), "SA_Object::GetPropertyData: not enough space for the return value of kAudioObjectPropertyClass");
            *reinterpret_cast<AudioClassID*>(outData) = mClassID;
            outDataSize = sizeof(AudioClassID);
            break;
            
        case kAudioObjectPropertyOwner:
            //  The AudioObjectID of the object that owns this object. This is an invariant.
            ThrowIf(inDataSize < sizeof(AudioObjectID), CAException(kAudioHardwareBadPropertySizeError), "SA_Object::GetPropertyData: not enough space for the return value of kAudioObjectPropertyOwner");
            *reinterpret_cast<AudioClassID*>(outData) = mOwnerObjectID;
            outDataSize = sizeof(AudioObjectID);
            break;
            
        case kAudioObjectPropertyOwnedObjects:
            //  This is an array of AudioObjectIDs for the objects owned by this object. By default,
            //  objects don't own any other objects. This is an invariant by default, but an object
            //  that can contain other objects will likely need to do some synchronization to access
            //  this property.
            outDataSize = 0;
            break;
        
        default:
            Throw(CAException(kAudioHardwareUnknownPropertyError));
            break;
    };
}
 
void    SA_Object::SetPropertyData(AudioObjectID inObjectID, pid_t inClientPID, const AudioObjectPropertyAddress& inAddress, UInt32 inQualifierDataSize, const void* inQualifierData, UInt32 inDataSize, const void* inData)
{
    #pragma unused(inObjectID, inClientPID, inQualifierDataSize, inQualifierData, inDataSize, inData)
    
    switch(inAddress.mSelector)
    {
        default:
            Throw(CAException(kAudioHardwareUnknownPropertyError));
            break;
    };
}
 
//==================================================================================================
#pragma mark -
#pragma mark SA_ObjectMap
//==================================================================================================
 
#pragma mark Construction/Destruction
 
SA_ObjectMap::SA_ObjectMap()
:
    mMutex("SA_ObjectMap Mutex"),
    mNextObjectID(32),
    mObjectInfoList()
{
    mObjectInfoList.reserve(256);
}
 
SA_ObjectMap::~SA_ObjectMap()
{
}
 
void    SA_ObjectMap::StaticInitializer()
{
    sInstance = new SA_ObjectMap;
    AssertNotNULL(sInstance, "SA_ObjectMap::StaticInitializer: failed to create the object map");
}
 
#pragma mark External Methods
 
AudioObjectID   SA_ObjectMap::GetNextObjectID()
{
    pthread_once(&sStaticInitializer, StaticInitializer);
    CAMutex::Locker theLocker(sInstance->mMutex);
    AudioObjectID theAnswer = sInstance->_GetNextObjectID();
    return theAnswer;
}
 
bool    SA_ObjectMap::MapObject(AudioObjectID inObjectID, SA_Object* inObject)
{
    pthread_once(&sStaticInitializer, StaticInitializer);
    bool theAnswer = false;
    if((inObjectID != 0) && (inObject != NULL))
    {
        CAMutex::Locker theLocker(sInstance->mMutex);
        theAnswer = sInstance->_MapObject(inObjectID, inObject);
    }
    return theAnswer;
}
 
void    SA_ObjectMap::UnmapObject(AudioObjectID inObjectID, SA_Object* inObject)
{
    pthread_once(&sStaticInitializer, StaticInitializer);
    if((inObjectID != 0) && (inObject != NULL))
    {
        CAMutex::Locker theLocker(sInstance->mMutex);
        sInstance->_UnmapObject(inObjectID, inObject);
    }
}
 
SA_Object*  SA_ObjectMap::CopyObjectByObjectID(AudioObjectID inObjectID)
{
    pthread_once(&sStaticInitializer, StaticInitializer);
    SA_Object* theAnswer = NULL;
    if(inObjectID != 0)
    {
        CAMutex::Locker theLocker(sInstance->mMutex);
        theAnswer = sInstance->_CopyObjectByObjectID(inObjectID);
    }
    return theAnswer;
}
 
UInt64  SA_ObjectMap::RetainObject(SA_Object* inObject)
{
    pthread_once(&sStaticInitializer, StaticInitializer);
    UInt64 theAnswer = 0;
    if(inObject != NULL)
    {
        CAMutex::Locker theLocker(sInstance->mMutex);
        theAnswer = sInstance->_RetainObject(inObject);
    }
    return theAnswer;
}
 
UInt64  SA_ObjectMap::ReleaseObject(SA_Object* inObject)
{
    pthread_once(&sStaticInitializer, StaticInitializer);
    UInt64 theAnswer = 0;
    if(inObject != NULL)
    {
        CAMutex::Locker theLocker(sInstance->mMutex);
        theAnswer = sInstance->_ReleaseObject(inObject);
    }
    return theAnswer;
}
 
void    SA_ObjectMap::Dump()
{
    pthread_once(&sStaticInitializer, StaticInitializer);
    CAMutex::Locker theLocker(sInstance->mMutex);
    sInstance->_Dump();
}
 
void    SA_ObjectMap::DestroyObject(SA_Object* inObject)
{
    //  The method is called when an object has been fully released and removed from the map. There
    //  is no need to synchronize against the object map's mutex. All it needs to do is dispose of
    //  the object in whatever manner is most suitable.
    delete inObject;
}
 
#pragma mark Internal Methods
 
AudioObjectID   SA_ObjectMap::_GetNextObjectID()
{
    return mNextObjectID++;
}
 
bool    SA_ObjectMap::_MapObject(AudioObjectID inObjectID, SA_Object* inObject)
{
    bool theAnswer = false;
    
    //  we don't do mappings for IDs of 0 or NULL object pointers
    if((inObjectID != 0) && (inObject != NULL))
    {
        //  look to see if the ID is already attached to an object
        ObjectInfoList::iterator theByIDIterator = std::find(mObjectInfoList.begin(), mObjectInfoList.end(), inObjectID);
        if(theByIDIterator == mObjectInfoList.end())
        {
            //  it is not, so we're going to do a mapping
            theAnswer = true;
            
            //  look to see if the object is already in the list
            ObjectInfoList::iterator theByPtrIterator = std::find(mObjectInfoList.begin(), mObjectInfoList.end(), inObject);
            if(theByPtrIterator != mObjectInfoList.end())
            {
                //  it is, so just add the new ID to it's ID list
                theByPtrIterator->mObjectIDList.push_back(inObjectID);
            }
            else
            {
                //  this is the first time this object has been mapped, so add a new entry to the list
                mObjectInfoList.push_back(ObjectInfo(inObjectID, inObject));
            }
        }
        else
        {
            //  the given ID is already attached to an object, this is a programming error
            DebugMsg("HALB_ObjectMap::_MapObject: %d cannot be mapped to object %p because it is already mapped to %p", (int)inObjectID, inObject, theByIDIterator->mObject);
        }
    }
    
    return theAnswer;
}
 
void    SA_ObjectMap::_UnmapObject(AudioObjectID inObjectID, SA_Object* inObject)
{
    //  we don't do mappings for IDs of 0 or NULL object pointers
    if((inObjectID != 0) && (inObject != NULL))
    {
        //  find the object this ID is attached to
        ObjectInfoList::iterator theIterator = std::find(mObjectInfoList.begin(), mObjectInfoList.end(), inObjectID);
        if(theIterator != mObjectInfoList.end())
        {
            //  make sure that it is the object we expect to be unmapping
            if(theIterator->mObject == inObject)
            {
                //  find the ID in the ID list
                ObjectIDList::iterator theIDIterator = std::find(theIterator->mObjectIDList.begin(), theIterator->mObjectIDList.end(), inObjectID);
                if(theIDIterator != theIterator->mObjectIDList.end())
                {
                    //  get rid of it
                    theIterator->mObjectIDList.erase(theIDIterator);
                    
                    //  get rid of the object if there are no more IDs
                    if(theIterator->mObjectIDList.empty())
                    {
                        //  get rid of the info in the list
                        mObjectInfoList.erase(theIterator);
                        
                        //  and destroy the object
                        CADispatchQueue::GetGlobalSerialQueue().Dispatch(false, ^{ DestroyObject(inObject); });
                    }
                }
            }
        }
    }
}
 
SA_Object*  SA_ObjectMap::_CopyObjectByObjectID(AudioObjectID inObjectID)
{
    SA_Object* theAnswer = NULL;
    
    //  find the object this ID is attached to
    ObjectInfoList::iterator theIterator = std::find(mObjectInfoList.begin(), mObjectInfoList.end(), inObjectID);
    if(theIterator != mObjectInfoList.end())
    {
        //  don't overflow the reference count
        if(theIterator->mReferenceCount < UINT64_MAX)
        {
            //  increment the reference count
            theIterator->mReferenceCount += 1;
            
            //  return the object pointer
            theAnswer = theIterator->mObject;
        }
        else
        {
            DebugMsg("SA_ObjectMap::_CopyObjectByObjectID: not copying because the reference count is at maximum");
        }
    }
    
    return theAnswer;
}
 
UInt64  SA_ObjectMap::_RetainObject(SA_Object* inObject)
{
    UInt64 theAnswer = 0;
 
    //  find the info for this object
    ObjectInfoList::iterator theIterator = std::find(mObjectInfoList.begin(), mObjectInfoList.end(), inObject);
    if(theIterator != mObjectInfoList.end())
    {
        //  don't overflow the reference count
        if(theIterator->mReferenceCount < UINT64_MAX)
        {
            //  increment the reference count
            theIterator->mReferenceCount += 1;
        }
        else
        {
            DebugMsg("SA_ObjectMap::_RetainObject: not retaining because the reference count is at maximum");
        }
        
        theAnswer = theIterator->mReferenceCount;
    }
    
    return theAnswer;
}
 
UInt64  SA_ObjectMap::_ReleaseObject(SA_Object* inObject)
{
    UInt64 theAnswer = 0;
 
    //  find the info for this object
    ObjectInfoList::iterator theIterator = std::find(mObjectInfoList.begin(), mObjectInfoList.end(), inObject);
    if(theIterator != mObjectInfoList.end())
    {
        //  don't underflow the reference count
        if(theIterator->mReferenceCount > 0)
        {
            //  decrement the reference count
            theIterator->mReferenceCount -= 1;
            
            //  destroy the object if the count reaches 0
            if(theIterator->mReferenceCount == 0)
            {
                //  get rid of the info in the list
                mObjectInfoList.erase(theIterator);
                
                //  and destroy the object
                CADispatchQueue::GetGlobalSerialQueue().Dispatch(false, ^{ DestroyObject(inObject); });
            }
        }
        else
        {
            DebugMsg("SA_ObjectMap::_ReleaseObject: not releasing because the reference count is already at 0");
        }
        
        theAnswer = theIterator->mReferenceCount;
    }
    
    return theAnswer;
}
 
void    SA_ObjectMap::_Dump()
{
 
    //  iterate through the objects in the map and dump their id, class, and ref count
    DebugMsg("HALB_ObjectMap::_Dump:");
    AudioClassID    theBaseClassID;
    char            theBaseClassIDString[5];
    AudioClassID    theClassID;
    char            theClassIDString[5];
    UInt64          theReferenceCount;
    
    if(!mObjectInfoList.empty())
    {
        for(ObjectInfoList::iterator theIterator = mObjectInfoList.begin(); theIterator != mObjectInfoList.end(); ++theIterator)
        {
            theBaseClassID = theIterator->mObject->GetBaseClassID();
            theClassID = theIterator->mObject->GetClassID();
            theReferenceCount = theIterator->mReferenceCount;
            CACopy4CCToCString(theBaseClassIDString, theBaseClassID);
            CACopy4CCToCString(theClassIDString, theClassID);
            
            if(theIterator->mObjectIDList.size() == 1)
            {
                DebugMsg("  Object: %p | Class: '%s' | Base Class: '%s' | Ref: %4qd | ID: %d", theIterator->mObject, theClassIDString, theBaseClassIDString, theReferenceCount, (int)theIterator->mObjectIDList.front());
            }
            else
            {
                DebugMsg("  Object: %p | Class: '%s' | Base Class: '%s' | Ref: %4qd | Number IDs: %d", theIterator->mObject, theClassIDString, theBaseClassIDString, theReferenceCount, (int)theIterator->mObjectIDList.size());
            
                for(size_t theIndex = 0; theIndex < theIterator->mObjectIDList.size(); ++theIndex)
                {
                    DebugMsg("    ID %3d: %d", (int)theIndex, (int)theIterator->mObjectIDList.at(theIndex));
                }
            }
        }
    }
    else
    {
        DebugMsg("  No Objects");
    }
}
 
pthread_once_t  SA_ObjectMap::sStaticInitializer = PTHREAD_ONCE_INIT;
SA_ObjectMap*   SA_ObjectMap::sInstance = NULL;