/************************************************************************************ PublicHeader: Kernel Filename : OVR_CallbacksInternal.h Content : Callback library Created : Nov 11, 2014 Author : Chris Taylor Copyright : Copyright 2014 Oculus VR, LLC All Rights reserved. Licensed under the Oculus VR Rift SDK License Version 3.2 (the "License"); you may not use the Oculus VR Rift SDK except in compliance with the License, which is provided at the time of installation or download, or which otherwise accompanies this software in either electronic or hard copy form. You may obtain a copy of the License at http://www.oculusvr.com/licenses/LICENSE-3.2 Unless required by applicable law or agreed to in writing, the Oculus VR SDK distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. ************************************************************************************/ #ifndef OVR_CallbacksInternal_h #define OVR_CallbacksInternal_h #include "OVR_Atomic.h" #include "OVR_RefCount.h" #include "OVR_Delegates.h" #include "OVR_Array.h" namespace OVR { template class FloatingCallbackEmitter; // Floating emitter object template class CallbackEmitter; template class FloatingCallbackListener; // Floating listener object template class CallbackListener; //----------------------------------------------------------------------------- // FloatingCallbackEmitter // // The Call() function is not thread-safe. // TBD: Should we add a thread-safe Call() option to constructor? class CallbackEmitterBase { protected: static Lock EmitterLock; }; template class FloatingCallbackEmitter : public CallbackEmitterBase, public RefCountBase< FloatingCallbackEmitter > { friend class CallbackEmitter; FloatingCallbackEmitter() : IsShutdown(false), DirtyListenersCache(0) { } public: typedef Array< Ptr< FloatingCallbackListener > > ListenerPtrArray; ~FloatingCallbackEmitter() { OVR_ASSERT(Listeners.GetSizeI() == 0); // ListenersCache will be emptied here. } bool AddListener(FloatingCallbackListener* listener); void Shutdown(); // Called from the listener object as it is transitioning to canceled state. // The listener's mutex is not held during this call. void OnListenerCancel(FloatingCallbackListener* listener); public: void Call(); template void Call(Param1* p1); template void Call(Param1& p1); template void Call(Param1* p1, Param2* p2); template void Call(Param1& p1, Param2& p2); template void Call(Param1* p1, Param2* p2, Param3* p3); template void Call(Param1& p1, Param2& p2, Param3& p3); protected: // Is the emitter shut down? This prevents more listeners from being added during shutdown. bool IsShutdown; // Array of added listeners. ListenerPtrArray Listeners; // Is the cache dirty? This avoids locking and memory allocation in steady state. AtomicInt DirtyListenersCache; // Cache of listeners used by the Call() function. ListenerPtrArray ListenersCacheForCalls; // Update the ListenersCache array in response to an insertion or removal. // This is how AddListener() insertions get rolled into the listeners array. // This is how RemoveListener() removals get purged from the cache. void updateListenersCache() { if (DirtyListenersCache != 0) { Lock::Locker locker(&EmitterLock); // TBD: Should memory allocation be further reduced here? ListenersCacheForCalls = Listeners; DirtyListenersCache = 0; } } // Without holding a lock, find and remove the given listener from the array of listeners. void noLockFindAndRemoveListener(FloatingCallbackListener* listener) { const int count = Listeners.GetSizeI(); for (int i = 0; i < count; ++i) { if (Listeners[i] == listener) { Listeners.RemoveAt(i); // After removing it from the array, set the dirty flag. // Note: Because the flag is atomic, a portable memory fence is implied. DirtyListenersCache = 1; break; } } } }; //----------------------------------------------------------------------------- // FloatingCallbackListener // // Internal implementation class for the CallbackListener. // This can only be associated with one CallbackListener object for its lifetime. template class FloatingCallbackListener : public RefCountBase< FloatingCallbackListener > { public: FloatingCallbackListener(DelegateT handler); ~FloatingCallbackListener(); void EnterCancelState(); bool IsValid() const { return Handler.IsValid(); } // TBD: Should these be binned to reduce the lock count? // Boost does not do that. And I am worried about deadlocks when misused. mutable Lock ListenerLock; // Handler function DelegateT Handler; }; //----------------------------------------------------------------------------- // Template Implementation: FloatingCallbackEmitter template bool FloatingCallbackEmitter::AddListener(FloatingCallbackListener* listener) { Lock::Locker locker(&EmitterLock); if (IsShutdown) { return false; } // Add the listener to our list Listeners.PushBack(listener); // After adding it to the array, set the dirty flag. // Note: Because the flag is atomic, a portable memory fence is implied. DirtyListenersCache = 1; return true; } // Called from the listener object as it is transitioning to canceled state. // The listener's mutex is not held during this call. template void FloatingCallbackEmitter::OnListenerCancel(FloatingCallbackListener* listener) { Lock::Locker emitterLocker(&EmitterLock); // If not shut down, // Note that if it is shut down then there will be no listeners in the array. if (!IsShutdown) { // Remove it. noLockFindAndRemoveListener(listener); } } template void FloatingCallbackEmitter::Shutdown() { Lock::Locker locker(&EmitterLock); IsShutdown = true; Listeners.ClearAndRelease(); // Note: Because the flag is atomic, a portable memory fence is implied. DirtyListenersCache = 1; } //----------------------------------------------------------------------------- // Call function // // (1) Update the cache of listener references, if it has changed. // (2) For each listener, // (a) Hold ListenerLock. // (b) If listener handler is valid, call the handler. #define OVR_EMITTER_CALL_BODY(params) \ updateListenersCache(); \ if (IsShutdown) return; /* Pure optimization. It is fine if this races. */ \ const int count = ListenersCacheForCalls.GetSizeI(); \ for (int i = 0; i < count; ++i) \ { \ Lock::Locker locker(&ListenersCacheForCalls[i]->ListenerLock); \ if (ListenersCacheForCalls[i]->Handler.IsValid()) \ { \ ListenersCacheForCalls[i]->Handler params; /* Using a macro for this line. */ \ } \ } template void FloatingCallbackEmitter::Call() { OVR_EMITTER_CALL_BODY(()) } template template void FloatingCallbackEmitter::Call(Param1* p1) { OVR_EMITTER_CALL_BODY((p1)) } template template void FloatingCallbackEmitter::Call(Param1& p1) { OVR_EMITTER_CALL_BODY((p1)) } template template void FloatingCallbackEmitter::Call(Param1* p1, Param2* p2) { OVR_EMITTER_CALL_BODY((p1, p2)) } template template void FloatingCallbackEmitter::Call(Param1& p1, Param2& p2) { OVR_EMITTER_CALL_BODY((p1, p2)) } template template void FloatingCallbackEmitter::Call(Param1* p1, Param2* p2, Param3* p3) { OVR_EMITTER_CALL_BODY((p1, p2, p3)) } template template void FloatingCallbackEmitter::Call(Param1& p1, Param2& p2, Param3& p3) { OVR_EMITTER_CALL_BODY((p1, p2, p3)) } #undef OVR_EMITTER_CALL_BODY //----------------------------------------------------------------------------- // Template Implementation: FloatingCallbackListener template FloatingCallbackListener::FloatingCallbackListener(DelegateT handler) : Handler(handler) { OVR_ASSERT(Handler.IsValid()); } template FloatingCallbackListener::~FloatingCallbackListener() { OVR_ASSERT(!Handler.IsValid()); } template void FloatingCallbackListener::EnterCancelState() { ListenerLock.DoLock(); Handler.Invalidate(); ListenerLock.Unlock(); } } // namespace OVR #endif // OVR_CallbacksInternal_h