///////////////////////////////////////////////////////////////////////////// // AGCGlobal.cpp : Implementation of the CAGCGlobal class. // #include "pch.h" #include "AGCGlobal.h" #include "AGCEvent.h" #include "AGCEventDef.h" #include "AGCEventData.h" #include "AGCEventThread.h" #include "AGCEventIDRange.h" #include "AGCEventIDRanges.h" #include "AGCVector.h" #include "AGCOrientation.h" #include "AGCWinApp.h" ///////////////////////////////////////////////////////////////////////////// // CAGCGlobal TC_OBJECT_EXTERN_IMPL(CAGCGlobal) ///////////////////////////////////////////////////////////////////////////// // Construction / Destruction void CAGCGlobal::FinalRelease() { // Ensure that we're terminated Terminate(); } ///////////////////////////////////////////////////////////////////////////// // Implementation const IID* CAGCGlobal::InterpretIID(const void* pvIgc, REFIID iid) { // Only interpret if pvIgc is non-NULL if (pvIgc) { // Determine if iid is IID_IAGCModel if (IID_IAGCModel == iid) { // Determine the corresponding IID from the model's type const ImodelIGC* pModel = reinterpret_cast(pvIgc); switch (pModel->GetObjectType()) { case OT_ship: return &IID_IAGCShip ; case OT_station: return &IID_IAGCStation ; case OT_missile: return &IID_IAGCModel ; // TODO: Implement specific object type in AGC case OT_mine: return &IID_IAGCModel ; // TODO: Implement specific object type in AGC case OT_probe: return &IID_IAGCProbe ; case OT_asteroid: return &IID_IAGCAsteroid; case OT_projectile: return &IID_IAGCModel ; // TODO: Implement specific object type in AGC case OT_warp: return &IID_IAGCAleph ; case OT_treasure: return &IID_IAGCModel ; // TODO: Implement specific object type in AGC case OT_buoy: return &IID_IAGCModel ; // TODO: Implement specific object type in AGC case OT_chaff: return &IID_IAGCModel ; // TODO: Implement specific object type in AGC default: ZError("CAGCGlobal::InterpretIID(): non-model type specified\n"); } } } // No interpretation return &iid; } ///////////////////////////////////////////////////////////////////////////// // IAGCGlobal Interface Methods STDMETHODIMP_(void) CAGCGlobal::RegisterObjectCreator(REFIID riid, PFNAGCCreator pfnCreator) { assert(pfnCreator); // Lock the object for the duration of this method XLock lock(this); // Insert the association into the map std::pair result = m_mapCreator.insert(std::make_pair(riid, pfnCreator)); if (!result.second) result.first->second = pfnCreator; } STDMETHODIMP_(void) CAGCGlobal::RevokeObjectCreator(REFIID riid) { assert(!"NOT IMPLEMENTED!"); } STDMETHODIMP CAGCGlobal::GetAGCObject(const void* pvIgc, REFIID riid, void** ppUnk) { // Lock the object for the duration of this method XLock lock(this); // Interpret the specified IID const IID* piid = InterpretIID(pvIgc, riid); // Find the specified Igc pointer in the map XIgcToAGCMapIt it = m_map.find(const_cast(pvIgc)); if (m_map.end() != it) { // Simply QueryInterface the associated AGC pointer return it->second->QueryInterface(*piid, ppUnk); } // Specified object does not exist in the map, create a wrapper for it // Find the specified IID in the AGC object creator map XAGCCreatorMapIt itCreator = m_mapCreator.find(*piid); if (m_mapCreator.end() == itCreator) { assert(false); return E_UNEXPECTED; } // Call the creator function to create an AGC wrapper for the Igc object PFNAGCCreator pfn = itCreator->second; assert(pfn); RETURN_FAILED((*pfn)(const_cast(pvIgc), *piid, ppUnk)); // Add the AGC wrapper to the map AddAGCObject(const_cast(pvIgc), IUnknownPtr((IUnknown*)*ppUnk)); // Indicate success return S_OK; } STDMETHODIMP_(void) CAGCGlobal::AddAGCObject(const void* pvIgc, IUnknown* pUnkAGC) { // Lock the object for the duration of this method XLock lock(this); // Ensure that the specified Igc pointer is not already in the map assert(m_map.end() == m_map.find(const_cast(pvIgc))); // Ensure that the specified AGC pointer supports IAGCPrivate assert(NULL != IAGCPrivatePtr(pUnkAGC)); // Insert the pair into the map #ifdef _DEBUG std::pair result = #endif m_map.insert(std::make_pair(const_cast(pvIgc), pUnkAGC)); assert(result.second); } STDMETHODIMP_(void) CAGCGlobal::RemoveAGCObject(const void* pvIgc, boolean bForceDestruct) { // Lock the object for the duration of this method XLock lock(this); // Find the specified Igc pointer in the map XIgcToAGCMapIt it = m_map.find(pvIgc); // Remove the association from the map if (m_map.end() != it) { // Store and AddRef the associated AGC interface IUnknownPtr spUnk(it->second); // Remove the association from the map m_map.erase(it); // Disconnect the object's proxy, if specified if (bForceDestruct) ::CoDisconnectObject(spUnk, 0); // spUnk Release'd upon leaving scope } } ///////////////////////////////////////////////////////////////////////////// // STDMETHODIMP_(void) CAGCGlobal::SetEventSinksAreGITCookies( boolean bEventSinksAreGITCookies) { XLock lock(this); // Can't change mode when event sinks are already registered #ifdef _DEBUG if (m_RegEvents.size()) assert(!m_bEventSinksAreGITCookies == !bEventSinksAreGITCookies); #endif // _DEBUG m_bEventSinksAreGITCookies = !!bEventSinksAreGITCookies; } ///////////////////////////////////////////////////////////////////////////// // STDMETHODIMP_(boolean) CAGCGlobal::GetEventSinksAreGITCookies() { XLock lock(this); return m_bEventSinksAreGITCookies; } ///////////////////////////////////////////////////////////////////////////// // Description: Registers an event sink for the specified event. // // Remarks: // If or when the specified event is fired, the specified event sink will be // notified. // // A uniqueID of causes global firing of an event, that is, an event will // fire without being limited to a single IGC object. // STDMETHODIMP_(void) CAGCGlobal::RegisterEvent(AGCEventID eventID, AGCUniqueID uniqueID, IAGCEventSink* pEventSink) { // Verify that the specified event sink is as required #ifdef _DEBUG { if (GetEventSinksAreGITCookies()) { // This will assert if pEventSink is not a cookie in the GIT IAGCEventSinkPtr spEventSink; GetInterfaceFromGlobal(reinterpret_cast(pEventSink), IID_IAGCEventSink, (void**)&spEventSink); } } #endif // _DEBUG // Find the array for the specified id XLock lock(this); TCINT64 nKey = MakeKey(eventID, uniqueID); XRegEventsIt itID = m_RegEvents.find(nKey); if (m_RegEvents.end() != itID) { // Find the specified event sink in the array XEventSinks& vecEvents = itID->second; for (XEventSinksIt it = vecEvents.begin(); it != vecEvents.end(); ++it) if (*it == pEventSink) return; } else { // Insert a new array into the map itID = m_RegEvents.insert(std::make_pair(nKey, XEventSinks())).first; } // Add the event sink to the array for this event XEventSinks& vecEvents = itID->second; vecEvents.push_back(pEventSink); } ///////////////////////////////////////////////////////////////////////////// // Description: Revokes an event sink for the specified event. // // Return Value: // *true* if the specified event sink was registered for the specified event // prior to the method being called. Otherwise, *false*. // STDMETHODIMP_(boolean) CAGCGlobal::RevokeEvent(AGCEventID eventID, AGCUniqueID uniqueID, IAGCEventSink* pEventSink) { // Verify that the specified event sink is as required #ifdef _DEBUG { if (GetEventSinksAreGITCookies()) { // This will assert if pEventSink is not a cookie in the GIT IAGCEventSinkPtr spEventSink; GetInterfaceFromGlobal(reinterpret_cast(pEventSink), IID_IAGCEventSink, (void**)&spEventSink); } } #endif // _DEBUG // Find the array for the specified id XLock lock(this); XRegEventsIt itID = m_RegEvents.find(MakeKey(eventID, uniqueID)); if (m_RegEvents.end() != itID) { // Find the specified object pointer in the array XEventSinks& vecEvents = itID->second; for (XEventSinksIt it = vecEvents.begin(); it != vecEvents.end(); ++it) { if (*it == pEventSink) { vecEvents.erase(it); if (vecEvents.empty()) m_RegEvents.erase(itID); return true; } } } // Specified object was not previously registered return false; } ///////////////////////////////////////////////////////////////////////////// // STDMETHODIMP_(void) CAGCGlobal::RegisterEventRanges( IAGCEventIDRanges* pRanges, AGCUniqueID uniqueID, IAGCEventSink* pEventSink) { assert(pRanges); // Register all event ID's that are not group ID's XLock lock(this); for (const CAGCEventDef::XEventDef* it = CAGCEventDef::begin(); CAGCEventDef::end() != it; ++it) { if (0 == it->m_nIndent) { range theRange(it->m_id, it->m_id); if (m_EventIDRanges.find(theRange) != m_EventIDRanges.end()) { VARIANT_BOOL bIntersects; ZSucceeded(pRanges->get_IntersectsWithValue(it->m_id, &bIntersects)); if (bIntersects) RegisterEvent(it->m_id, uniqueID, pEventSink); } } } } ///////////////////////////////////////////////////////////////////////////// // STDMETHODIMP_(void) CAGCGlobal::RevokeEventRanges(IAGCEventIDRanges* pRanges, AGCUniqueID uniqueID, IAGCEventSink* pEventSink) { assert(pRanges); // Revoke all event ID's that are not group ID's XLock lock(this); for (const CAGCEventDef::XEventDef* it = CAGCEventDef::begin(); CAGCEventDef::end() != it; ++it) { if (0 == it->m_nIndent) { range theRange(it->m_id, it->m_id); if (m_EventIDRanges.find(theRange) != m_EventIDRanges.end()) { VARIANT_BOOL bIntersects; ZSucceeded(pRanges->get_IntersectsWithValue(it->m_id, &bIntersects)); if (bIntersects) RevokeEvent(it->m_id, uniqueID, pEventSink); } } } } ///////////////////////////////////////////////////////////////////////////// // Description: Revokes an event sink for all its events. // // Return Value: The count of events unregistered for the specified event // sink. // STDMETHODIMP_(int) CAGCGlobal::RevokeAllEvents(IAGCEventSink* pEventSink) { // Verify that the specified event sink is as required #ifdef _DEBUG { if (GetEventSinksAreGITCookies()) { // This will assert if pEventSink is not a cookie in the GIT IAGCEventSinkPtr spEventSink; GetInterfaceFromGlobal(reinterpret_cast(pEventSink), IID_IAGCEventSink, (void**)&spEventSink); } } #endif // _DEBUG int cRemoved = 0; XLock lock(this); for (XRegEventsIt itID = m_RegEvents.begin(); itID != m_RegEvents.end(); ++itID) { XEventSinks& vecEvents = itID->second; for (XEventSinksIt it = vecEvents.begin(); it != vecEvents.end(); ++it) { if (*it == pEventSink) { vecEvents.erase(it); ++cRemoved; break; } } } return cRemoved; } ///////////////////////////////////////////////////////////////////////////// // Description: Registers an event sink for all its events. // // Return Value: The count of events registered for the specified event // sink. // STDMETHODIMP_(int) CAGCGlobal::RegisterAllEvents(IAGCEventSink* pEventSink) { // Verify that the specified event sink is as required #ifdef _DEBUG { if (GetEventSinksAreGITCookies()) { // This will assert if pEventSink is not a cookie in the GIT IAGCEventSinkPtr spEventSink; GetInterfaceFromGlobal(reinterpret_cast(pEventSink), IID_IAGCEventSink, (void**)&spEventSink); } } #endif // _DEBUG // Register all event ID's that are not group ID's XLock lock(this); int cEventIDs = 0; for (const CAGCEventDef::XEventDef* it = CAGCEventDef::begin(); CAGCEventDef::end() != it; ++it) { if (0 == it->m_nIndent) { range theRange(it->m_id, it->m_id); if (m_EventIDRanges.find(theRange) != m_EventIDRanges.end()) { RegisterEvent(it->m_id, AGC_Any_Objects, pEventSink); ++cEventIDs; } } } // Return the number of event ID's registered return cEventIDs; } ///////////////////////////////////////////////////////////////////////////// // Description: Returns true if someone wants to know about an event // Use this to see if you should fire an event. // // Note: Calling this method is optional since the TriggerEvent method will // make this check if NULL is specified as its hListeners parameter. This // method is used for cases when other processing may be involved with // sending an event. // // Parameters: // idEvent - The event id for which to lookup the list of listeners. // idSubject - The subject for which the event represents an action being // taken. This is optional and should be set to -1 if no such object is // appropriate. // idObject1 - An additional object which is affected by the event. This is // optional and should be set to -1 if not such object is appropriate. // idObject2 - An additional object which is affected by the event. This is // optional and should be set to -1 if not such object is appropriate. // // Return Value: NULL if no event sinks are registered for the specified // event and subject/objects. Otherwise a non-NULL handle that contains an // internal list of event sinks registerd for the specified event and // subject/objects. This handle should be passed to TriggerEvent to fire the // event to those sinks. If the caller decides to not fire the events, it // should pass this handle to FreeListeners. // STDMETHODIMP_(HAGCLISTENERS) CAGCGlobal::EventListeners(AGCEventID idEvent, AGCUniqueID idSubject, AGCUniqueID idObject1, AGCUniqueID idObject2) { XLock lock(this); XEventSinks* pSinks = NULL; // // A key with the uniqueID component as AGC_Any_Objects causes stands for // global firing of an event, that is, an event will fire without being // limited to a single IGC object. So first, we see if any global event // firing keys are in the map. // XRegEventsIt itID_AllObjects = m_RegEvents.find(MakeKey(idEvent, AGC_Any_Objects)); if (itID_AllObjects != m_RegEvents.end()) { XEventSinksIt itBegin = itID_AllObjects->second.begin(); XEventSinksIt itEnd = itID_AllObjects->second.end(); if (itEnd != itBegin) { pSinks = new XEventSinks(itBegin, itEnd); } } if (-1 != idSubject) { XRegEventsIt itID1 = m_RegEvents.find(MakeKey(idEvent, idSubject)); if (itID1 != m_RegEvents.end()) { XEventSinksIt itBegin = itID_AllObjects->second.begin(); XEventSinksIt itEnd = itID_AllObjects->second.end(); if (itEnd != itBegin) { if (!pSinks) pSinks = new XEventSinks(itBegin, itEnd); else pSinks->insert(pSinks->end(), itBegin, itEnd); } } } if (-1 != idObject1) { XRegEventsIt itID1 = m_RegEvents.find(MakeKey(idEvent, idObject1)); if (itID1 != m_RegEvents.end()) { XEventSinksIt itBegin = itID_AllObjects->second.begin(); XEventSinksIt itEnd = itID_AllObjects->second.end(); if (itEnd != itBegin) { if (!pSinks) pSinks = new XEventSinks(itBegin, itEnd); else pSinks->insert(pSinks->end(), itBegin, itEnd); } } } if (-1 != idObject2) { XRegEventsIt itID1 = m_RegEvents.find(MakeKey(idEvent, idObject2)); if (itID1 != m_RegEvents.end()) { XEventSinksIt itBegin = itID_AllObjects->second.begin(); XEventSinksIt itEnd = itID_AllObjects->second.end(); if (itEnd != itBegin) { if (!pSinks) pSinks = new XEventSinks(itBegin, itEnd); else pSinks->insert(pSinks->end(), itBegin, itEnd); } } } return pSinks; } ///////////////////////////////////////////////////////////////////////////// // Description: Notifies registered event sinks that the specified event // occurred // STDMETHODIMP_(void) CAGCGlobal::TriggerEvent(HAGCLISTENERS hListeners, AGCEventID idEvent, LPCSTR pszContext, LPCOLESTR pszSubject, AGCUniqueID idSubject, AGCUniqueID idObject1, AGCUniqueID idObject2, long cArgTriplets, void* pvArgs) { XLock lock(this); // Create the event thread, if not already created // Note: I've put this outside of the timer, since it only happens once. if (!m_pthEvents) m_pthEvents = new CAGCEventThread(this); assert(m_pthEvents); // Initialize the timer static CTempTimer timerTriggerEvent("in TriggerEvent", .01f); // Start the timer timerTriggerEvent.Start(); // Get the event sinks for this event/subject/objects, if not specified if (!hListeners) { hListeners = EventListeners(idEvent, idSubject, idObject1, idObject2); if (!hListeners) goto stopTimer; } // Create an AGCEventData object { CAGCEventData data(idEvent, pszContext, pszSubject, idSubject, cArgTriplets, reinterpret_cast(pvArgs)); // Queue the event to the event thread m_pthEvents->QueueEvent(hListeners, data); } // Stop the timer stopTimer: timerTriggerEvent.Stop(); } ///////////////////////////////////////////////////////////////////////////// // Description: Notifies registered event sinks that the specified event // occurred // STDMETHODIMP_(void) CAGCGlobal::TriggerEventSynchronous(HAGCLISTENERS hListeners, AGCEventID idEvent, LPCSTR pszContext, LPCOLESTR pszSubject, AGCUniqueID idSubject, AGCUniqueID idObject1, AGCUniqueID idObject2, long cArgTriplets, void* pvArgs) { XLock lock(this); // Create the event thread, if not already created if (!m_pthEvents) m_pthEvents = new CAGCEventThread(this); assert(m_pthEvents); // Get the event sinks for this event/subject/objects, if not specified if (!hListeners) { hListeners = EventListeners(idEvent, idSubject, idObject1, idObject2); if (!hListeners) return; } lock.Unlock(); // Create an AGCEventData object CAGCEventData data(idEvent, pszContext, pszSubject, idSubject, cArgTriplets, reinterpret_cast(pvArgs)); // Queue the event to the event thread m_pthEvents->QueueEventSynchronous(hListeners, data); } STDMETHODIMP_(void) CAGCGlobal::FreeListeners(HAGCLISTENERS hListeners) { // Delegate to static method FreeListenersImpl(hListeners); } ///////////////////////////////////////////////////////////////////////////// // Description: Determines if the specified object has a callback interface // registered for the specified event. // // Return Value: // *true* if the specified object was registered for the specified event. // Otherwise, *false*. // STDMETHODIMP_(boolean) CAGCGlobal::IsRegistered(AGCEventID eventID, AGCUniqueID uniqueID, IAGCEventSink* pEventSink) { // Find the array for the specified id XLock lock(this); XRegEventsIt itID = m_RegEvents.find(MakeKey(eventID, uniqueID)); if (m_RegEvents.end() != itID) { // Loop thru each array element until pEventSink is found XEventSinks& vecEvents = itID->second; for (XEventSinksIt it = vecEvents.begin(); it != vecEvents.end(); ++it) if (*it == pEventSink) return true; } return false; } ///////////////////////////////////////////////////////////////////////////// // Description: Creates an event object. // STDMETHODIMP CAGCGlobal::MakeAGCEvent(AGCEventID idEvent, LPCSTR pszContext, LPCOLESTR pszSubject, AGCUniqueID idSubject, long cArgTriplets, void* pvArgs, IAGCEvent** ppEvent) { // Create an event object CComObject* pEvent = NULL; RETURN_FAILED(pEvent->CreateInstance(&pEvent)); IAGCEventPtr spEvent(pEvent); RETURN_FAILED(pEvent->Init(idEvent, pszContext, pszSubject, idSubject, cArgTriplets, (va_list)pvArgs)); // Detach the object to the [out] parameter *ppEvent = spEvent.Detach(); return S_OK; } STDMETHODIMP_(void) CAGCGlobal::SetAvailableEventIDRanges( IAGCEventIDRanges* pRanges) { // Copy the specified rangeset assert(pRanges); XLock lock(this); IAGCRangesPrivatePtr spPrivate(pRanges); ZSucceeded(spPrivate->CopyRangesTo(&m_EventIDRanges)); } STDMETHODIMP_(void) CAGCGlobal::GetAvailableEventIDRanges( IAGCEventIDRanges** ppRanges) { // Create an AGC.EventIDRanges object CComObject* pRanges = NULL; ZSucceeded(pRanges->CreateInstance(&pRanges)); IAGCRangesPrivatePtr spPrivate(pRanges); assert(NULL != spPrivate); // Copy the rangeset into the new object { XLock lock(this); ZSucceeded(spPrivate->InitFromRanges(&m_EventIDRanges)); } // AddRef the object to the [out] parameter (*ppRanges = pRanges)->AddRef(); } ///////////////////////////////////////////////////////////////////////////// // STDMETHODIMP_(WORD) CAGCGlobal::GetEventSeverity(AGCEventID idEvent) { const CAGCEventDef::XEventDef* it = CAGCEventDef::find(idEvent); return (it != CAGCEventDef::end()) ? it->m_wSeverity : 0; } ///////////////////////////////////////////////////////////////////////////// // STDMETHODIMP_(HINSTANCE) CAGCGlobal::GetResourceInstance() { return _Module.GetResourceInstance(); } ///////////////////////////////////////////////////////////////////////////// // STDMETHODIMP CAGCGlobal::MakeAGCVector(const void* pVectorRaw, IAGCVector** ppVector) { CComObject* pVector = NULL; RETURN_FAILED(pVector->CreateInstance(&pVector)); IAGCVectorPtr spVector(pVector); RETURN_FAILED(pVector->InitFromVector(pVectorRaw)); CLEAROUT(ppVector, (IAGCVector*)spVector); spVector.Detach(); return S_OK; } ///////////////////////////////////////////////////////////////////////////// // STDMETHODIMP CAGCGlobal::MakeAGCOrientation(const void* pOrientationRaw, IAGCOrientation** ppOrientation) { CComObject* pOrientation = NULL; RETURN_FAILED(pOrientation->CreateInstance(&pOrientation)); IAGCOrientationPtr spOrientation(pOrientation); RETURN_FAILED(pOrientation->InitFromOrientation(pOrientationRaw)); CLEAROUT(ppOrientation, (IAGCOrientation*)spOrientation); spOrientation.Detach(); return S_OK; } ///////////////////////////////////////////////////////////////////////////// // STDMETHODIMP CAGCGlobal::MakeAGCEventIDRange(AGCEventID lower, AGCEventID upper, IAGCEventIDRange** ppRange) { CComObject* pRange = NULL; RETURN_FAILED(pRange->CreateInstance(&pRange)); IAGCEventIDRangePtr spRange(pRange); RETURN_FAILED(pRange->InitFromRange(&make_range(lower, upper))); CLEAROUT(ppRange, (IAGCEventIDRange*)spRange); spRange.Detach(); return S_OK; } ///////////////////////////////////////////////////////////////////////////// // STDMETHODIMP CAGCGlobal::RegisterInterfaceInGlobal(IUnknown* pUnk, REFIID riid, DWORD* pdwCookie) { HRESULT hr = GetGIT()->RegisterInterfaceInGlobal(pUnk, riid, pdwCookie); if (FAILED(hr)) { assert(!"CAGCGlobal::RegisterInterfaceInGlobal()"); } return hr; } ///////////////////////////////////////////////////////////////////////////// // STDMETHODIMP CAGCGlobal::RevokeInterfaceFromGlobal(DWORD dwCookie) { HRESULT hr = GetGIT()->RevokeInterfaceFromGlobal(dwCookie); if (FAILED(hr)) { assert(!"CAGCGlobal::RevokeInterfaceFromGlobal()"); } return hr; } ///////////////////////////////////////////////////////////////////////////// // STDMETHODIMP CAGCGlobal::GetInterfaceFromGlobal(DWORD dwCookie, REFIID riid, void** ppv) { HRESULT hr = GetGIT()->GetInterfaceFromGlobal(dwCookie, riid, ppv); if (FAILED(hr)) { assert(!"CAGCGlobal::GetInterfaceFromGlobal()"); } return hr; } ///////////////////////////////////////////////////////////////////////////// // STDMETHODIMP_(void) CAGCGlobal::Initialize() { // Initialize the CAGCEventDef static class CAGCEventDef::Initialize(); } ///////////////////////////////////////////////////////////////////////////// // STDMETHODIMP_(void) CAGCGlobal::Terminate() { // Shutdown the AGCEventThread, if it is running CAGCEventThread* pthEvents; { XLock lock(this); pthEvents = m_pthEvents; } if (pthEvents) { delete pthEvents; XLock lock(this); m_pthEvents = NULL; } // Terminate the CAGCEventDef static class CAGCEventDef::Terminate(); } ///////////////////////////////////////////////////////////////////////////// // STDMETHODIMP_(void) CAGCGlobal::SetDebugHook(IAGCDebugHook* pdh) { g_app.SetDebugHook(pdh); }