// Copyright (c) 2011 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #ifndef CHROME_COMMON_DEPRECATED_EVENT_SYS_INL_H_ #define CHROME_COMMON_DEPRECATED_EVENT_SYS_INL_H_ #pragma once #include #include "base/basictypes.h" #include "base/logging.h" #include "base/message_loop.h" #include "base/port.h" #include "base/synchronization/condition_variable.h" #include "base/synchronization/lock.h" #include "chrome/common/deprecated/event_sys.h" // How to use Channels: // 0. Assume Bob is the name of the class from which you want to broadcast // events. // 1. Choose an EventType. This could be an Enum or something more complicated. // 2. Create an EventTraits class for your EventType. It must have // two members: // // typedef x EventType; // static bool IsChannelShutdownEvent(const EventType& event); // // 3. Add an EventChannel* instance and event_channel() const; // accessor to Bob. // Delete the channel ordinarily in Bob's destructor, or whenever you want. // 4. To broadcast events, call bob->event_channel()->NotifyListeners(event). // 5. Only call NotifyListeners from a single thread at a time. // How to use Listeners/Hookups: // 0. Assume you want a class called Lisa to listen to events from Bob. // 1. Create an event handler method in Lisa. Its single argument should be of // your event type. // 2. Add a EventListenerHookup* hookup_ member to Lisa. // 3. Initialize the hookup by calling: // // hookup_ = NewEventListenerHookup(bob->event_channel(), // this, // &Lisa::HandleEvent); // // 4. Delete hookup_ in Lisa's destructor, or anytime sooner to stop receiving // events. // An Event Channel is a source, or broadcaster of events. Listeners subscribe // by calling the AddListener() method. The owner of the channel calls the // NotifyListeners() method. // // Don't inherit from this class. Just make an event_channel member and an // event_channel() accessor. // No reason why CallbackWaiters has to be templatized. class CallbackWaiters { public: CallbackWaiters() : waiter_count_(0), callback_done_(false), condvar_(&mutex_) { } ~CallbackWaiters() { DCHECK_EQ(0, waiter_count_); } void WaitForCallbackToComplete(base::Lock* listeners_mutex) { { base::AutoLock lock(mutex_); waiter_count_ += 1; listeners_mutex->Release(); while (!callback_done_) condvar_.Wait(); waiter_count_ -= 1; if (0 != waiter_count_) return; } delete this; } void Signal() { base::AutoLock lock(mutex_); callback_done_ = true; condvar_.Broadcast(); } protected: int waiter_count_; bool callback_done_; base::Lock mutex_; base::ConditionVariable condvar_; }; template class EventChannel { public: typedef EventTraitsType EventTraits; typedef typename EventTraits::EventType EventType; typedef EventListener Listener; protected: typedef std::map Listeners; public: // The shutdown event gets send in the EventChannel's destructor. explicit EventChannel(const EventType& shutdown_event) : current_listener_callback_(NULL), current_listener_callback_message_loop_(NULL), callback_waiters_(NULL), shutdown_event_(shutdown_event) { } ~EventChannel() { // Tell all the listeners that the channel is being deleted. NotifyListeners(shutdown_event_); // Make sure all the listeners have been disconnected. Otherwise, they // will try to call RemoveListener() at a later date. #if defined(DEBUG) base::AutoLock lock(listeners_mutex_); for (typename Listeners::iterator i = listeners_.begin(); i != listeners_.end(); ++i) { DCHECK(i->second) << "Listener not disconnected"; } #endif } // Never call this twice for the same listener. // // Thread safe. void AddListener(Listener* listener) { base::AutoLock lock(listeners_mutex_); typename Listeners::iterator found = listeners_.find(listener); if (found == listeners_.end()) { listeners_.insert(std::make_pair(listener, false)); // Not dead yet. } else { DCHECK(found->second) << "Attempted to add the same listener twice."; found->second = false; // Not dead yet. } } // If listener's callback is currently executing, this method waits until the // callback completes before returning. // // Thread safe. void RemoveListener(Listener* listener) { bool wait = false; listeners_mutex_.Acquire(); typename Listeners::iterator found = listeners_.find(listener); if (found != listeners_.end()) { found->second = true; // Mark as dead. wait = (found->first == current_listener_callback_ && (MessageLoop::current() != current_listener_callback_message_loop_)); } if (!wait) { listeners_mutex_.Release(); return; } if (NULL == callback_waiters_) callback_waiters_ = new CallbackWaiters; callback_waiters_->WaitForCallbackToComplete(&listeners_mutex_); } // Blocks until all listeners have been notified. // // NOT thread safe. Must only be called by one thread at a time. void NotifyListeners(const EventType& event) { ScopedNotifyLocker lock_notify(notify_lock_); listeners_mutex_.Acquire(); DCHECK(NULL == current_listener_callback_); current_listener_callback_message_loop_ = MessageLoop::current(); typename Listeners::iterator i = listeners_.begin(); while (i != listeners_.end()) { if (i->second) { // Clean out dead listeners listeners_.erase(i++); continue; } current_listener_callback_ = i->first; listeners_mutex_.Release(); i->first->HandleEvent(event); listeners_mutex_.Acquire(); current_listener_callback_ = NULL; if (NULL != callback_waiters_) { callback_waiters_->Signal(); callback_waiters_ = NULL; } ++i; } listeners_mutex_.Release(); } // A map iterator remains valid until the element it points to gets removed // from the map, so a map is perfect for our needs. // // Map value is a bool, true means the Listener is dead. Listeners listeners_; // NULL means no callback is currently being called. Listener* current_listener_callback_; // Only valid when current_listener is not NULL. // The thread on which the callback is executing. MessageLoop* current_listener_callback_message_loop_; // Win32 Event that is usually NULL. Only created when another thread calls // Remove while in callback. Owned and closed by the thread calling Remove(). CallbackWaiters* callback_waiters_; base::Lock listeners_mutex_; // Protects all members above. const EventType shutdown_event_; NotifyLock notify_lock_; DISALLOW_COPY_AND_ASSIGN(EventChannel); }; // An EventListenerHookup hooks up a method in your class to an EventChannel. // Deleting the hookup disconnects from the EventChannel. // // Contains complexity of inheriting from Listener class and managing lifetimes. // // Create using NewEventListenerHookup() to avoid explicit template arguments. class EventListenerHookup { public: virtual ~EventListenerHookup() { } }; template class EventListenerHookupImpl : public EventListenerHookup, public EventListener { public: explicit EventListenerHookupImpl(EventChannel* channel) : channel_(channel), deleted_(NULL) { channel->AddListener(this); connected_ = true; } ~EventListenerHookupImpl() { if (NULL != deleted_) *deleted_ = true; if (connected_) channel_->RemoveListener(this); } typedef typename EventTraits::EventType EventType; virtual void HandleEvent(const EventType& event) { DCHECK(connected_); bool deleted = false; deleted_ = &deleted; static_cast(this)->Callback(event); if (deleted) // The callback (legally) deleted this. return; // The only safe thing to do. deleted_ = NULL; if (EventTraits::IsChannelShutdownEvent(event)) { channel_->RemoveListener(this); connected_ = false; } } protected: EventChannel* const channel_; bool connected_; bool* deleted_; // Allows the handler to delete the hookup. }; // SimpleHookup just passes the event to the callback message. template class SimpleHookup : public EventListenerHookupImpl > { public: SimpleHookup(EventChannel* channel, CallbackObject* cbobject, CallbackMethod cbmethod) : EventListenerHookupImpl(channel), cbobject_(cbobject), cbmethod_(cbmethod) { } typedef typename EventTraits::EventType EventType; void Callback(const EventType& event) { (cbobject_->*cbmethod_)(event); } CallbackObject* const cbobject_; CallbackMethod const cbmethod_; }; // ArgHookup also passes an additional arg to the callback method. template class ArgHookup : public EventListenerHookupImpl > { public: ArgHookup(EventChannel* channel, CallbackObject* cbobject, CallbackMethod cbmethod, CallbackArg0 arg0) : EventListenerHookupImpl(channel), cbobject_(cbobject), cbmethod_(cbmethod), arg0_(arg0) { } typedef typename EventTraits::EventType EventType; void Callback(const EventType& event) { (cbobject_->*cbmethod_)(arg0_, event); } CallbackObject* const cbobject_; CallbackMethod const cbmethod_; CallbackArg0 const arg0_; }; template EventListenerHookup* NewEventListenerHookup(EventChannel* channel, CallbackObject* cbobject, CallbackMethod cbmethod) { return new SimpleHookup(channel, cbobject, cbmethod); } template EventListenerHookup* NewEventListenerHookup(EventChannel* channel, CallbackObject* cbobject, CallbackMethod cbmethod, CallbackArg0 arg0) { return new ArgHookup(channel, cbobject, cbmethod, arg0); } #endif // CHROME_COMMON_DEPRECATED_EVENT_SYS_INL_H_