axmol/cocos/2d/CCEventDispatcher.cpp

1398 lines
42 KiB
C++
Raw Normal View History

2013-09-20 22:23:13 +08:00
/****************************************************************************
Copyright (c) 2013-2014 Chukong Technologies Inc.
2013-09-20 22:23:13 +08:00
http://www.cocos2d-x.org
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
****************************************************************************/
#include "CCEventDispatcher.h"
#include "CCEvent.h"
2013-09-21 14:40:54 +08:00
#include "CCEventTouch.h"
#include "CCEventCustom.h"
2013-09-21 14:40:54 +08:00
#include "CCEventListenerTouch.h"
#include "CCEventListenerAcceleration.h"
#include "CCEventListenerMouse.h"
#include "CCEventListenerKeyboard.h"
2013-12-21 16:56:28 +08:00
#include "CCEventListenerCustom.h"
#include "CCScene.h"
2013-09-20 22:23:13 +08:00
#include "CCDirector.h"
#include "CCEventType.h"
2013-09-20 22:23:13 +08:00
#include <algorithm>
#define DUMP_LISTENER_ITEM_PRIORITY_INFO 0
2013-09-20 22:23:13 +08:00
namespace
{
class DispatchGuard
{
public:
DispatchGuard(int& count):
_count(count)
{
++_count;
}
~DispatchGuard()
{
--_count;
}
private:
int& _count;
};
}
NS_CC_BEGIN
static EventListener::ListenerID __getListenerID(Event* event)
{
EventListener::ListenerID ret;
switch (event->getType())
{
case Event::Type::ACCELERATION:
ret = EventListenerAcceleration::LISTENER_ID;
break;
case Event::Type::CUSTOM:
{
auto customEvent = static_cast<EventCustom*>(event);
ret = customEvent->getEventName();
}
break;
case Event::Type::KEYBOARD:
ret = EventListenerKeyboard::LISTENER_ID;
break;
2013-10-31 14:19:36 +08:00
case Event::Type::MOUSE:
ret = EventListenerMouse::LISTENER_ID;
2013-10-31 14:19:36 +08:00
break;
case Event::Type::TOUCH:
// Touch listener is very special, it contains two kinds of listeners, EventListenerTouchOneByOne and EventListenerTouchAllAtOnce.
// return UNKNOWN instead.
CCASSERT(false, "Don't call this method if the event is for touch.");
break;
default:
CCASSERT(false, "Invalid type!");
break;
}
return ret;
}
2013-12-27 15:06:16 +08:00
EventDispatcher::EventListenerVector::EventListenerVector() :
_fixedListeners(nullptr),
_sceneGraphListeners(nullptr),
_gt0Index(0)
{
}
EventDispatcher::EventListenerVector::~EventListenerVector()
{
CC_SAFE_DELETE(_sceneGraphListeners);
CC_SAFE_DELETE(_fixedListeners);
}
size_t EventDispatcher::EventListenerVector::size() const
{
size_t ret = 0;
if (_sceneGraphListeners)
ret += _sceneGraphListeners->size();
if (_fixedListeners)
ret += _fixedListeners->size();
return ret;
}
bool EventDispatcher::EventListenerVector::empty() const
{
return (_sceneGraphListeners == nullptr || _sceneGraphListeners->empty())
&& (_fixedListeners == nullptr || _fixedListeners->empty());
}
void EventDispatcher::EventListenerVector::push_back(EventListener* listener)
{
#if CC_NODE_DEBUG_VERIFY_EVENT_LISTENERS
Fix crashes in event dispatch other related safety checks/fixes 1: Add a fix to prevent events from being sent to scene nodes which are destroyed. Previously when a node was being destroyed it would try to unregister any event listeners it would have. This process would fail however in the case where event dispatching was already underway because we can't modify the listeners list while we are iterating through it. This is a pretty common occurrence since we often tear down & switch scenes in response to button clicks etc. Fix the problem by nulling out the slot for the listener we are removing in this case, so that it's node no longer receives any events after it is destroyed. The event dispatching code will cleanup this unused/nulled slot once the event processing loop has terminated. 2: When removing an event listener in cocos2d::EventDispatcher, ensure immediately it gets removed from the node priority map and dirty nodes set in order to ensure we don't inadvertently access any dangling pointers to nodes. 3: Add debug checks to ensure nodes have no associated event listeners after they are destroyed. This check will catch the problem fixed by (1) if it is still present. 4: Add some extra debug sanity checks in the event dispatcher to ensure nodes are not added to the lists twice and that they are being properly removed from the lists under certain conditions. 5: In cocos2d::Node's destructor NULL out members after releasing them in case they somehow happen to be accessed during the destructor. Move the release of the event dispatcher to the very end, since we need to check against it in debug.
2014-03-27 04:12:44 +08:00
CCASSERT(_sceneGraphListeners == nullptr ||
std::count(_sceneGraphListeners->begin(), _sceneGraphListeners->end(), listener) == 0,
"Listener should not be added twice!");
CCASSERT(_fixedListeners == nullptr ||
std::count(_fixedListeners->begin(), _fixedListeners->end(), listener) == 0,
"Listener should not be added twice!");
#endif
Fix crashes in event dispatch other related safety checks/fixes 1: Add a fix to prevent events from being sent to scene nodes which are destroyed. Previously when a node was being destroyed it would try to unregister any event listeners it would have. This process would fail however in the case where event dispatching was already underway because we can't modify the listeners list while we are iterating through it. This is a pretty common occurrence since we often tear down & switch scenes in response to button clicks etc. Fix the problem by nulling out the slot for the listener we are removing in this case, so that it's node no longer receives any events after it is destroyed. The event dispatching code will cleanup this unused/nulled slot once the event processing loop has terminated. 2: When removing an event listener in cocos2d::EventDispatcher, ensure immediately it gets removed from the node priority map and dirty nodes set in order to ensure we don't inadvertently access any dangling pointers to nodes. 3: Add debug checks to ensure nodes have no associated event listeners after they are destroyed. This check will catch the problem fixed by (1) if it is still present. 4: Add some extra debug sanity checks in the event dispatcher to ensure nodes are not added to the lists twice and that they are being properly removed from the lists under certain conditions. 5: In cocos2d::Node's destructor NULL out members after releasing them in case they somehow happen to be accessed during the destructor. Move the release of the event dispatcher to the very end, since we need to check against it in debug.
2014-03-27 04:12:44 +08:00
if (listener->getFixedPriority() == 0)
{
if (_sceneGraphListeners == nullptr)
{
_sceneGraphListeners = new std::vector<EventListener*>();
_sceneGraphListeners->reserve(100);
}
_sceneGraphListeners->push_back(listener);
}
else
{
if (_fixedListeners == nullptr)
{
_fixedListeners = new std::vector<EventListener*>();
_fixedListeners->reserve(100);
}
_fixedListeners->push_back(listener);
}
}
2013-10-24 17:27:22 +08:00
void EventDispatcher::EventListenerVector::clearSceneGraphListeners()
{
if (_sceneGraphListeners)
{
_sceneGraphListeners->clear();
delete _sceneGraphListeners;
_sceneGraphListeners = nullptr;
}
2013-10-24 17:27:22 +08:00
}
void EventDispatcher::EventListenerVector::clearFixedListeners()
{
if (_fixedListeners)
{
_fixedListeners->clear();
delete _fixedListeners;
_fixedListeners = nullptr;
}
2013-09-20 22:23:13 +08:00
}
2013-10-24 17:27:22 +08:00
void EventDispatcher::EventListenerVector::clear()
{
clearSceneGraphListeners();
clearFixedListeners();
}
2013-09-20 22:23:13 +08:00
EventDispatcher::EventDispatcher()
: _inDispatch(0)
, _isEnabled(false)
, _nodePriorityIndex(0)
2013-09-20 22:23:13 +08:00
{
_toAddedListeners.reserve(50);
// fixed #4129: Mark the following listener IDs for internal use.
// Therefore, internal listeners would not be cleaned when removeAllEventListeners is invoked.
_internalCustomListenerIDs.insert(EVENT_COME_TO_FOREGROUND);
_internalCustomListenerIDs.insert(EVENT_COME_TO_BACKGROUND);
2013-09-20 22:23:13 +08:00
}
EventDispatcher::~EventDispatcher()
{
// Clear internal custom listener IDs from set,
// so removeAllEventListeners would clean internal custom listeners.
_internalCustomListenerIDs.clear();
removeAllEventListeners();
2013-09-20 22:23:13 +08:00
}
void EventDispatcher::visitTarget(Node* node, bool isRootNode)
2013-10-23 17:26:14 +08:00
{
2013-12-05 17:19:01 +08:00
int i = 0;
auto& children = node->getChildren();
2013-12-05 17:19:01 +08:00
auto childrenCount = children.size();
if(childrenCount > 0)
{
Node* child = nullptr;
// visit children zOrder < 0
for( ; i < childrenCount; i++ )
{
child = children.at(i);
if ( child && child->getLocalZOrder() < 0 )
visitTarget(child, false);
else
break;
}
if (_nodeListenersMap.find(node) != _nodeListenersMap.end())
{
_globalZOrderNodeMap[node->getGlobalZOrder()].push_back(node);
}
for( ; i < childrenCount; i++ )
{
child = children.at(i);
if (child)
visitTarget(child, false);
}
}
else
{
if (_nodeListenersMap.find(node) != _nodeListenersMap.end())
{
_globalZOrderNodeMap[node->getGlobalZOrder()].push_back(node);
}
}
if (isRootNode)
{
2014-01-22 08:36:19 +08:00
std::vector<float> globalZOrders;
globalZOrders.reserve(_globalZOrderNodeMap.size());
for (const auto& e : _globalZOrderNodeMap)
{
globalZOrders.push_back(e.first);
}
2014-01-22 08:36:19 +08:00
std::sort(globalZOrders.begin(), globalZOrders.end(), [](const float a, const float b){
return a < b;
});
for (const auto& globalZ : globalZOrders)
{
for (const auto& n : _globalZOrderNodeMap[globalZ])
{
_nodePriorityMap[n] = ++_nodePriorityIndex;
}
}
_globalZOrderNodeMap.clear();
}
}
void EventDispatcher::pauseEventListenersForTarget(Node* target, bool recursive/* = false */)
{
auto listenerIter = _nodeListenersMap.find(target);
if (listenerIter != _nodeListenersMap.end())
{
auto listeners = listenerIter->second;
for (auto& l : *listeners)
{
l->setPaused(true);
}
}
if (recursive)
{
const auto& children = target->getChildren();
for (const auto& child : children)
{
pauseEventListenersForTarget(child, true);
}
}
}
void EventDispatcher::resumeEventListenersForTarget(Node* target, bool recursive/* = false */)
{
auto listenerIter = _nodeListenersMap.find(target);
if (listenerIter != _nodeListenersMap.end())
{
auto listeners = listenerIter->second;
for (auto& l : *listeners)
{
l->setPaused(false);
}
}
setDirtyForNode(target);
if (recursive)
{
const auto& children = target->getChildren();
for (const auto& child : children)
{
resumeEventListenersForTarget(child, true);
}
}
}
void EventDispatcher::removeEventListenersForTarget(Node* target, bool recursive/* = false */)
{
Fix crashes in event dispatch other related safety checks/fixes 1: Add a fix to prevent events from being sent to scene nodes which are destroyed. Previously when a node was being destroyed it would try to unregister any event listeners it would have. This process would fail however in the case where event dispatching was already underway because we can't modify the listeners list while we are iterating through it. This is a pretty common occurrence since we often tear down & switch scenes in response to button clicks etc. Fix the problem by nulling out the slot for the listener we are removing in this case, so that it's node no longer receives any events after it is destroyed. The event dispatching code will cleanup this unused/nulled slot once the event processing loop has terminated. 2: When removing an event listener in cocos2d::EventDispatcher, ensure immediately it gets removed from the node priority map and dirty nodes set in order to ensure we don't inadvertently access any dangling pointers to nodes. 3: Add debug checks to ensure nodes have no associated event listeners after they are destroyed. This check will catch the problem fixed by (1) if it is still present. 4: Add some extra debug sanity checks in the event dispatcher to ensure nodes are not added to the lists twice and that they are being properly removed from the lists under certain conditions. 5: In cocos2d::Node's destructor NULL out members after releasing them in case they somehow happen to be accessed during the destructor. Move the release of the event dispatcher to the very end, since we need to check against it in debug.
2014-03-27 04:12:44 +08:00
// Ensure the node is removed from these immediately also.
// Don't want any dangling pointers or the possibility of dealing with deleted objects..
_nodePriorityMap.erase(target);
_dirtyNodes.erase(target);
auto listenerIter = _nodeListenersMap.find(target);
if (listenerIter != _nodeListenersMap.end())
{
auto listeners = listenerIter->second;
2013-10-23 17:13:03 +08:00
auto listenersCopy = *listeners;
for (auto& l : listenersCopy)
{
removeEventListener(l);
}
}
// Bug fix: ensure there are no references to the node in the list of listeners to be added.
// If we find any listeners associated with the destroyed node in this list then remove them.
// This is to catch the scenario where the node gets destroyed before it's listener
// is added into the event dispatcher fully. This could happen if a node registers a listener
// and gets destroyed while we are dispatching an event (touch etc.)
for (auto iter = _toAddedListeners.begin(); iter != _toAddedListeners.end(); )
{
EventListener * listener = *iter;
if (listener->getAssociatedNode() == target)
{
listener->setAssociatedNode(nullptr); // Ensure no dangling ptr to the target node.
listener->setRegistered(false);
listener->release();
iter = _toAddedListeners.erase(iter);
}
else
{
++iter;
}
}
if (recursive)
{
const auto& children = target->getChildren();
for (const auto& child : children)
{
removeEventListenersForTarget(child, true);
}
}
}
void EventDispatcher::associateNodeAndEventListener(Node* node, EventListener* listener)
{
std::vector<EventListener*>* listeners = nullptr;
auto found = _nodeListenersMap.find(node);
if (found != _nodeListenersMap.end())
{
listeners = found->second;
}
else
{
listeners = new std::vector<EventListener*>();
_nodeListenersMap.insert(std::make_pair(node, listeners));
}
listeners->push_back(listener);
}
void EventDispatcher::dissociateNodeAndEventListener(Node* node, EventListener* listener)
{
std::vector<EventListener*>* listeners = nullptr;
auto found = _nodeListenersMap.find(node);
if (found != _nodeListenersMap.end())
{
listeners = found->second;
auto iter = std::find(listeners->begin(), listeners->end(), listener);
if (iter != listeners->end())
{
listeners->erase(iter);
}
if (listeners->empty())
{
_nodeListenersMap.erase(found);
2013-10-24 17:27:22 +08:00
delete listeners;
}
}
}
void EventDispatcher::addEventListener(EventListener* listener)
2013-09-20 22:23:13 +08:00
{
if (_inDispatch == 0)
{
forceAddEventListener(listener);
}
else
{
_toAddedListeners.push_back(listener);
}
2013-09-20 22:23:13 +08:00
listener->retain();
}
void EventDispatcher::forceAddEventListener(EventListener* listener)
{
EventListenerVector* listeners = nullptr;
EventListener::ListenerID listenerID = listener->getListenerID();
auto itr = _listenerMap.find(listenerID);
if (itr == _listenerMap.end())
{
listeners = new EventListenerVector();
_listenerMap.insert(std::make_pair(listenerID, listeners));
}
else
{
listeners = itr->second;
}
listeners->push_back(listener);
if (listener->getFixedPriority() == 0)
{
setDirty(listenerID, DirtyFlag::SCENE_GRAPH_PRIORITY);
auto node = listener->getAssociatedNode();
CCASSERT(node != nullptr, "Invalid scene graph priority!");
associateNodeAndEventListener(node, listener);
if (node->isRunning())
{
resumeEventListenersForTarget(node);
}
2013-09-20 22:23:13 +08:00
}
else
{
setDirty(listenerID, DirtyFlag::FIXED_PRIORITY);
2013-09-20 22:23:13 +08:00
}
}
void EventDispatcher::addEventListenerWithSceneGraphPriority(EventListener* listener, Node* node)
{
CCASSERT(listener && node, "Invalid parameters.");
CCASSERT(!listener->isRegistered(), "The listener has been registered.");
2013-09-20 22:23:13 +08:00
if (!listener->checkAvailable())
2013-09-20 22:23:13 +08:00
return;
listener->setAssociatedNode(node);
listener->setFixedPriority(0);
listener->setRegistered(true);
addEventListener(listener);
2013-09-20 22:23:13 +08:00
}
#if CC_NODE_DEBUG_VERIFY_EVENT_LISTENERS && COCOS2D_DEBUG > 0
Fix crashes in event dispatch other related safety checks/fixes 1: Add a fix to prevent events from being sent to scene nodes which are destroyed. Previously when a node was being destroyed it would try to unregister any event listeners it would have. This process would fail however in the case where event dispatching was already underway because we can't modify the listeners list while we are iterating through it. This is a pretty common occurrence since we often tear down & switch scenes in response to button clicks etc. Fix the problem by nulling out the slot for the listener we are removing in this case, so that it's node no longer receives any events after it is destroyed. The event dispatching code will cleanup this unused/nulled slot once the event processing loop has terminated. 2: When removing an event listener in cocos2d::EventDispatcher, ensure immediately it gets removed from the node priority map and dirty nodes set in order to ensure we don't inadvertently access any dangling pointers to nodes. 3: Add debug checks to ensure nodes have no associated event listeners after they are destroyed. This check will catch the problem fixed by (1) if it is still present. 4: Add some extra debug sanity checks in the event dispatcher to ensure nodes are not added to the lists twice and that they are being properly removed from the lists under certain conditions. 5: In cocos2d::Node's destructor NULL out members after releasing them in case they somehow happen to be accessed during the destructor. Move the release of the event dispatcher to the very end, since we need to check against it in debug.
2014-03-27 04:12:44 +08:00
void EventDispatcher::debugCheckNodeHasNoEventListenersOnDestruction(Node* node)
{
// Check the listeners map
for (const auto & keyValuePair : _listenerMap)
{
const EventListenerVector * eventListenerVector = keyValuePair.second;
if (eventListenerVector)
{
if (eventListenerVector->getSceneGraphPriorityListeners())
{
for (EventListener * listener : *eventListenerVector->getSceneGraphPriorityListeners())
{
CCASSERT(!listener ||
listener->getAssociatedNode() != node,
Fix crashes in event dispatch other related safety checks/fixes 1: Add a fix to prevent events from being sent to scene nodes which are destroyed. Previously when a node was being destroyed it would try to unregister any event listeners it would have. This process would fail however in the case where event dispatching was already underway because we can't modify the listeners list while we are iterating through it. This is a pretty common occurrence since we often tear down & switch scenes in response to button clicks etc. Fix the problem by nulling out the slot for the listener we are removing in this case, so that it's node no longer receives any events after it is destroyed. The event dispatching code will cleanup this unused/nulled slot once the event processing loop has terminated. 2: When removing an event listener in cocos2d::EventDispatcher, ensure immediately it gets removed from the node priority map and dirty nodes set in order to ensure we don't inadvertently access any dangling pointers to nodes. 3: Add debug checks to ensure nodes have no associated event listeners after they are destroyed. This check will catch the problem fixed by (1) if it is still present. 4: Add some extra debug sanity checks in the event dispatcher to ensure nodes are not added to the lists twice and that they are being properly removed from the lists under certain conditions. 5: In cocos2d::Node's destructor NULL out members after releasing them in case they somehow happen to be accessed during the destructor. Move the release of the event dispatcher to the very end, since we need to check against it in debug.
2014-03-27 04:12:44 +08:00
"Node should have no event listeners registered for it upon destruction!");
}
}
}
}
// Check the node listeners map
for (const auto & keyValuePair : _nodeListenersMap)
{
CCASSERT(keyValuePair.first != node, "Node should have no event listeners registered for it upon destruction!");
if (keyValuePair.second)
{
for (EventListener * listener : *keyValuePair.second)
{
CCASSERT(listener->getAssociatedNode() != node,
Fix crashes in event dispatch other related safety checks/fixes 1: Add a fix to prevent events from being sent to scene nodes which are destroyed. Previously when a node was being destroyed it would try to unregister any event listeners it would have. This process would fail however in the case where event dispatching was already underway because we can't modify the listeners list while we are iterating through it. This is a pretty common occurrence since we often tear down & switch scenes in response to button clicks etc. Fix the problem by nulling out the slot for the listener we are removing in this case, so that it's node no longer receives any events after it is destroyed. The event dispatching code will cleanup this unused/nulled slot once the event processing loop has terminated. 2: When removing an event listener in cocos2d::EventDispatcher, ensure immediately it gets removed from the node priority map and dirty nodes set in order to ensure we don't inadvertently access any dangling pointers to nodes. 3: Add debug checks to ensure nodes have no associated event listeners after they are destroyed. This check will catch the problem fixed by (1) if it is still present. 4: Add some extra debug sanity checks in the event dispatcher to ensure nodes are not added to the lists twice and that they are being properly removed from the lists under certain conditions. 5: In cocos2d::Node's destructor NULL out members after releasing them in case they somehow happen to be accessed during the destructor. Move the release of the event dispatcher to the very end, since we need to check against it in debug.
2014-03-27 04:12:44 +08:00
"Node should have no event listeners registered for it upon destruction!");
}
}
}
// Check the node priority map
for (const auto & keyValuePair : _nodePriorityMap)
{
CCASSERT(keyValuePair.first != node,
"Node should have no event listeners registered for it upon destruction!");
}
// Check the to be added list
for (EventListener * listener : _toAddedListeners)
{
CCASSERT(listener->getAssociatedNode() != node,
Fix crashes in event dispatch other related safety checks/fixes 1: Add a fix to prevent events from being sent to scene nodes which are destroyed. Previously when a node was being destroyed it would try to unregister any event listeners it would have. This process would fail however in the case where event dispatching was already underway because we can't modify the listeners list while we are iterating through it. This is a pretty common occurrence since we often tear down & switch scenes in response to button clicks etc. Fix the problem by nulling out the slot for the listener we are removing in this case, so that it's node no longer receives any events after it is destroyed. The event dispatching code will cleanup this unused/nulled slot once the event processing loop has terminated. 2: When removing an event listener in cocos2d::EventDispatcher, ensure immediately it gets removed from the node priority map and dirty nodes set in order to ensure we don't inadvertently access any dangling pointers to nodes. 3: Add debug checks to ensure nodes have no associated event listeners after they are destroyed. This check will catch the problem fixed by (1) if it is still present. 4: Add some extra debug sanity checks in the event dispatcher to ensure nodes are not added to the lists twice and that they are being properly removed from the lists under certain conditions. 5: In cocos2d::Node's destructor NULL out members after releasing them in case they somehow happen to be accessed during the destructor. Move the release of the event dispatcher to the very end, since we need to check against it in debug.
2014-03-27 04:12:44 +08:00
"Node should have no event listeners registered for it upon destruction!");
}
// Check the dirty nodes set
for (Node * dirtyNode : _dirtyNodes)
{
CCASSERT(dirtyNode != node,
"Node should have no event listeners registered for it upon destruction!");
}
}
#endif // #if CC_NODE_DEBUG_VERIFY_EVENT_LISTENERS && COCOS2D_DEBUG > 0
Fix crashes in event dispatch other related safety checks/fixes 1: Add a fix to prevent events from being sent to scene nodes which are destroyed. Previously when a node was being destroyed it would try to unregister any event listeners it would have. This process would fail however in the case where event dispatching was already underway because we can't modify the listeners list while we are iterating through it. This is a pretty common occurrence since we often tear down & switch scenes in response to button clicks etc. Fix the problem by nulling out the slot for the listener we are removing in this case, so that it's node no longer receives any events after it is destroyed. The event dispatching code will cleanup this unused/nulled slot once the event processing loop has terminated. 2: When removing an event listener in cocos2d::EventDispatcher, ensure immediately it gets removed from the node priority map and dirty nodes set in order to ensure we don't inadvertently access any dangling pointers to nodes. 3: Add debug checks to ensure nodes have no associated event listeners after they are destroyed. This check will catch the problem fixed by (1) if it is still present. 4: Add some extra debug sanity checks in the event dispatcher to ensure nodes are not added to the lists twice and that they are being properly removed from the lists under certain conditions. 5: In cocos2d::Node's destructor NULL out members after releasing them in case they somehow happen to be accessed during the destructor. Move the release of the event dispatcher to the very end, since we need to check against it in debug.
2014-03-27 04:12:44 +08:00
2013-09-20 22:23:13 +08:00
void EventDispatcher::addEventListenerWithFixedPriority(EventListener* listener, int fixedPriority)
{
CCASSERT(listener, "Invalid parameters.");
CCASSERT(!listener->isRegistered(), "The listener has been registered.");
2013-09-20 22:23:13 +08:00
CCASSERT(fixedPriority != 0, "0 priority is forbidden for fixed priority since it's used for scene graph based priority.");
if (!listener->checkAvailable())
2013-09-20 22:23:13 +08:00
return;
listener->setAssociatedNode(nullptr);
listener->setFixedPriority(fixedPriority);
listener->setRegistered(true);
listener->setPaused(false);
2013-09-20 22:23:13 +08:00
addEventListener(listener);
2013-09-20 22:23:13 +08:00
}
EventListenerCustom* EventDispatcher::addCustomEventListener(const std::string &eventName, const std::function<void(EventCustom*)>& callback)
2013-12-21 16:56:28 +08:00
{
EventListenerCustom *listener = EventListenerCustom::create(eventName, callback);
addEventListenerWithFixedPriority(listener, 1);
return listener;
}
2013-09-20 22:23:13 +08:00
void EventDispatcher::removeEventListener(EventListener* listener)
{
if (listener == nullptr)
return;
bool isFound = false;
auto removeListenerInVector = [&](std::vector<EventListener*>* listeners){
if (listeners == nullptr)
return;
for (auto iter = listeners->begin(); iter != listeners->end(); ++iter)
{
auto l = *iter;
if (l == listener)
2013-09-20 22:23:13 +08:00
{
2013-10-24 11:17:29 +08:00
CC_SAFE_RETAIN(l);
l->setRegistered(false);
if (l->getAssociatedNode() != nullptr)
2013-09-20 22:23:13 +08:00
{
dissociateNodeAndEventListener(l->getAssociatedNode(), l);
l->setAssociatedNode(nullptr); // NULL out the node pointer so we don't have any dangling pointers to destroyed nodes.
2013-09-20 22:23:13 +08:00
}
if (_inDispatch == 0)
{
listeners->erase(iter);
CC_SAFE_RELEASE(l);
2013-09-20 22:23:13 +08:00
}
2013-09-20 22:23:13 +08:00
isFound = true;
break;
2013-09-20 22:23:13 +08:00
}
}
};
for (auto iter = _listenerMap.begin(); iter != _listenerMap.end();)
{
auto listeners = iter->second;
auto fixedPriorityListeners = listeners->getFixedPriorityListeners();
auto sceneGraphPriorityListeners = listeners->getSceneGraphPriorityListeners();
2013-10-24 11:17:29 +08:00
removeListenerInVector(sceneGraphPriorityListeners);
if (isFound)
{
// fixed #4160: Dirty flag need to be updated after listeners were removed.
setDirty(listener->getListenerID(), DirtyFlag::SCENE_GRAPH_PRIORITY);
}
else
{
removeListenerInVector(fixedPriorityListeners);
if (isFound)
{
setDirty(listener->getListenerID(), DirtyFlag::FIXED_PRIORITY);
}
}
Fix crashes in event dispatch other related safety checks/fixes 1: Add a fix to prevent events from being sent to scene nodes which are destroyed. Previously when a node was being destroyed it would try to unregister any event listeners it would have. This process would fail however in the case where event dispatching was already underway because we can't modify the listeners list while we are iterating through it. This is a pretty common occurrence since we often tear down & switch scenes in response to button clicks etc. Fix the problem by nulling out the slot for the listener we are removing in this case, so that it's node no longer receives any events after it is destroyed. The event dispatching code will cleanup this unused/nulled slot once the event processing loop has terminated. 2: When removing an event listener in cocos2d::EventDispatcher, ensure immediately it gets removed from the node priority map and dirty nodes set in order to ensure we don't inadvertently access any dangling pointers to nodes. 3: Add debug checks to ensure nodes have no associated event listeners after they are destroyed. This check will catch the problem fixed by (1) if it is still present. 4: Add some extra debug sanity checks in the event dispatcher to ensure nodes are not added to the lists twice and that they are being properly removed from the lists under certain conditions. 5: In cocos2d::Node's destructor NULL out members after releasing them in case they somehow happen to be accessed during the destructor. Move the release of the event dispatcher to the very end, since we need to check against it in debug.
2014-03-27 04:12:44 +08:00
#if CC_NODE_DEBUG_VERIFY_EVENT_LISTENERS
Fix crashes in event dispatch other related safety checks/fixes 1: Add a fix to prevent events from being sent to scene nodes which are destroyed. Previously when a node was being destroyed it would try to unregister any event listeners it would have. This process would fail however in the case where event dispatching was already underway because we can't modify the listeners list while we are iterating through it. This is a pretty common occurrence since we often tear down & switch scenes in response to button clicks etc. Fix the problem by nulling out the slot for the listener we are removing in this case, so that it's node no longer receives any events after it is destroyed. The event dispatching code will cleanup this unused/nulled slot once the event processing loop has terminated. 2: When removing an event listener in cocos2d::EventDispatcher, ensure immediately it gets removed from the node priority map and dirty nodes set in order to ensure we don't inadvertently access any dangling pointers to nodes. 3: Add debug checks to ensure nodes have no associated event listeners after they are destroyed. This check will catch the problem fixed by (1) if it is still present. 4: Add some extra debug sanity checks in the event dispatcher to ensure nodes are not added to the lists twice and that they are being properly removed from the lists under certain conditions. 5: In cocos2d::Node's destructor NULL out members after releasing them in case they somehow happen to be accessed during the destructor. Move the release of the event dispatcher to the very end, since we need to check against it in debug.
2014-03-27 04:12:44 +08:00
CCASSERT(_inDispatch != 0 ||
!sceneGraphPriorityListeners ||
std::count(sceneGraphPriorityListeners->begin(), sceneGraphPriorityListeners->end(), listener) == 0,
"Listener should be in no lists after this is done if we're not currently in dispatch mode.");
CCASSERT(_inDispatch != 0 ||
!fixedPriorityListeners ||
std::count(fixedPriorityListeners->begin(), fixedPriorityListeners->end(), listener) == 0,
"Listener should be in no lists after this is done if we're not currently in dispatch mode.");
#endif
2013-09-20 22:23:13 +08:00
if (iter->second->empty())
{
_priorityDirtyFlagMap.erase(listener->getListenerID());
2013-09-20 22:23:13 +08:00
auto list = iter->second;
iter = _listenerMap.erase(iter);
2013-09-20 22:23:13 +08:00
CC_SAFE_DELETE(list);
}
else
{
++iter;
}
if (isFound)
break;
}
if (isFound)
{
2013-09-20 22:23:13 +08:00
CC_SAFE_RELEASE(listener);
}
else
{
for(auto iter = _toAddedListeners.begin(); iter != _toAddedListeners.end(); ++iter)
{
if (*iter == listener)
2013-11-02 15:03:54 +08:00
{
listener->setRegistered(false);
listener->release();
2013-11-02 15:03:54 +08:00
_toAddedListeners.erase(iter);
break;
}
}
2013-09-20 22:23:13 +08:00
}
}
void EventDispatcher::setPriority(EventListener* listener, int fixedPriority)
{
if (listener == nullptr)
return;
for (auto iter = _listenerMap.begin(); iter != _listenerMap.end(); ++iter)
2013-09-20 22:23:13 +08:00
{
auto fixedPriorityListeners = iter->second->getFixedPriorityListeners();
if (fixedPriorityListeners)
2013-09-20 22:23:13 +08:00
{
auto found = std::find(fixedPriorityListeners->begin(), fixedPriorityListeners->end(), listener);
if (found != fixedPriorityListeners->end())
2013-09-20 22:23:13 +08:00
{
CCASSERT(listener->getAssociatedNode() == nullptr, "Can't set fixed priority with scene graph based listener.");
2013-09-20 22:23:13 +08:00
if (listener->getFixedPriority() != fixedPriority)
2013-09-20 22:23:13 +08:00
{
listener->setFixedPriority(fixedPriority);
setDirty(listener->getListenerID(), DirtyFlag::FIXED_PRIORITY);
2013-09-20 22:23:13 +08:00
}
return;
}
}
}
}
void EventDispatcher::dispatchEventToListeners(EventListenerVector* listeners, const std::function<bool(EventListener*)>& onEvent)
{
bool shouldStopPropagation = false;
auto fixedPriorityListeners = listeners->getFixedPriorityListeners();
auto sceneGraphPriorityListeners = listeners->getSceneGraphPriorityListeners();
2013-12-27 15:06:16 +08:00
ssize_t i = 0;
// priority < 0
if (fixedPriorityListeners)
{
2014-03-24 10:12:40 +08:00
CCASSERT(listeners->getGt0Index() <= static_cast<ssize_t>(fixedPriorityListeners->size()), "Out of range exception!");
if (!fixedPriorityListeners->empty())
{
for (; i < listeners->getGt0Index(); ++i)
{
auto l = fixedPriorityListeners->at(i);
if (l->isEnabled() && !l->isPaused() && l->isRegistered() && onEvent(l))
{
shouldStopPropagation = true;
break;
}
}
}
}
if (sceneGraphPriorityListeners)
{
if (!shouldStopPropagation)
{
// priority == 0, scene graph priority
2013-10-24 11:17:29 +08:00
for (auto& l : *sceneGraphPriorityListeners)
{
if (l->isEnabled() && !l->isPaused() && l->isRegistered() && onEvent(l))
{
shouldStopPropagation = true;
break;
}
}
}
}
if (fixedPriorityListeners)
{
if (!shouldStopPropagation)
{
// priority > 0
2013-12-27 15:06:16 +08:00
ssize_t size = fixedPriorityListeners->size();
for (; i < size; ++i)
{
auto l = fixedPriorityListeners->at(i);
if (l->isEnabled() && !l->isPaused() && l->isRegistered() && onEvent(l))
{
shouldStopPropagation = true;
break;
}
}
}
}
}
void EventDispatcher::dispatchEvent(Event* event)
2013-09-20 22:23:13 +08:00
{
if (!_isEnabled)
return;
updateDirtyFlagForSceneGraph();
DispatchGuard guard(_inDispatch);
2013-09-20 22:23:13 +08:00
if (event->getType() == Event::Type::TOUCH)
2013-09-20 22:23:13 +08:00
{
dispatchTouchEvent(static_cast<EventTouch*>(event));
return;
2013-09-20 22:23:13 +08:00
}
auto listenerID = __getListenerID(event);
sortEventListeners(listenerID);
2013-09-20 22:23:13 +08:00
auto iter = _listenerMap.find(listenerID);
if (iter != _listenerMap.end())
2013-09-20 22:23:13 +08:00
{
auto listeners = iter->second;
auto onEvent = [&event](EventListener* listener) -> bool{
event->setCurrentTarget(listener->getAssociatedNode());
listener->_onEvent(event);
return event->isStopped();
};
dispatchEventToListeners(listeners, onEvent);
2013-09-20 22:23:13 +08:00
}
updateListeners(event);
2013-09-20 22:23:13 +08:00
}
void EventDispatcher::dispatchCustomEvent(const std::string &eventName, void *optionalUserData)
{
EventCustom ev(eventName);
ev.setUserData(optionalUserData);
dispatchEvent(&ev);
}
2013-09-20 22:23:13 +08:00
void EventDispatcher::dispatchTouchEvent(EventTouch* event)
{
sortEventListeners(EventListenerTouchOneByOne::LISTENER_ID);
sortEventListeners(EventListenerTouchAllAtOnce::LISTENER_ID);
auto oneByOneListeners = getListeners(EventListenerTouchOneByOne::LISTENER_ID);
auto allAtOnceListeners = getListeners(EventListenerTouchAllAtOnce::LISTENER_ID);
2013-09-20 22:23:13 +08:00
// If there aren't any touch listeners, return directly.
if (nullptr == oneByOneListeners && nullptr == allAtOnceListeners)
return;
2013-09-20 22:23:13 +08:00
bool isNeedsMutableSet = (oneByOneListeners && allAtOnceListeners);
2013-09-20 22:23:13 +08:00
const std::vector<Touch*>& originalTouches = event->getTouches();
std::vector<Touch*> mutableTouches(originalTouches.size());
std::copy(originalTouches.begin(), originalTouches.end(), mutableTouches.begin());
2013-09-20 22:23:13 +08:00
//
// process the target handlers 1st
//
if (oneByOneListeners)
2013-09-20 22:23:13 +08:00
{
auto mutableTouchesIter = mutableTouches.begin();
auto touchesIter = originalTouches.begin();
2013-09-20 22:23:13 +08:00
for (; touchesIter != originalTouches.end(); ++touchesIter)
2013-09-20 22:23:13 +08:00
{
bool isSwallowed = false;
auto onTouchEvent = [&](EventListener* l) -> bool { // Return true to break
EventListenerTouchOneByOne* listener = static_cast<EventListenerTouchOneByOne*>(l);
2013-09-20 22:23:13 +08:00
// Skip if the listener was removed.
if (!listener->_isRegistered)
return false;
2013-09-20 22:23:13 +08:00
event->setCurrentTarget(listener->_node);
2013-09-20 22:23:13 +08:00
bool isClaimed = false;
std::vector<Touch*>::iterator removedIter;
EventTouch::EventCode eventCode = event->getEventCode();
if (eventCode == EventTouch::EventCode::BEGAN)
{
if (listener->onTouchBegan)
2013-09-20 22:23:13 +08:00
{
isClaimed = listener->onTouchBegan(*touchesIter, event);
if (isClaimed && listener->_isRegistered)
2013-09-20 22:23:13 +08:00
{
listener->_claimedTouches.push_back(*touchesIter);
2013-09-20 22:23:13 +08:00
}
}
}
else if (listener->_claimedTouches.size() > 0
&& ((removedIter = std::find(listener->_claimedTouches.begin(), listener->_claimedTouches.end(), *touchesIter)) != listener->_claimedTouches.end()))
2013-09-20 22:23:13 +08:00
{
isClaimed = true;
switch (eventCode)
{
case EventTouch::EventCode::MOVED:
if (listener->onTouchMoved)
2013-09-20 22:23:13 +08:00
{
listener->onTouchMoved(*touchesIter, event);
2013-09-20 22:23:13 +08:00
}
break;
case EventTouch::EventCode::ENDED:
if (listener->onTouchEnded)
2013-09-20 22:23:13 +08:00
{
listener->onTouchEnded(*touchesIter, event);
2013-09-20 22:23:13 +08:00
}
if (listener->_isRegistered)
2013-09-20 22:23:13 +08:00
{
listener->_claimedTouches.erase(removedIter);
2013-09-20 22:23:13 +08:00
}
break;
case EventTouch::EventCode::CANCELLED:
if (listener->onTouchCancelled)
2013-09-20 22:23:13 +08:00
{
listener->onTouchCancelled(*touchesIter, event);
2013-09-20 22:23:13 +08:00
}
if (listener->_isRegistered)
2013-09-20 22:23:13 +08:00
{
listener->_claimedTouches.erase(removedIter);
2013-09-20 22:23:13 +08:00
}
break;
default:
CCASSERT(false, "The eventcode is invalid.");
break;
}
}
// If the event was stopped, return directly.
if (event->isStopped())
{
updateListeners(event);
return true;
2013-09-20 22:23:13 +08:00
}
CCASSERT((*touchesIter)->getID() == (*mutableTouchesIter)->getID(), "");
if (isClaimed && listener->_isRegistered && listener->_needSwallow)
2013-09-20 22:23:13 +08:00
{
if (isNeedsMutableSet)
{
mutableTouchesIter = mutableTouches.erase(mutableTouchesIter);
isSwallowed = true;
}
return true;
2013-09-20 22:23:13 +08:00
}
return false;
};
//
dispatchEventToListeners(oneByOneListeners, onTouchEvent);
if (event->isStopped())
{
return;
2013-09-20 22:23:13 +08:00
}
if (!isSwallowed)
++mutableTouchesIter;
}
}
//
// process standard handlers 2nd
//
if (allAtOnceListeners && mutableTouches.size() > 0)
2013-09-20 22:23:13 +08:00
{
auto onTouchesEvent = [&](EventListener* l) -> bool{
EventListenerTouchAllAtOnce* listener = static_cast<EventListenerTouchAllAtOnce*>(l);
2013-09-20 22:23:13 +08:00
// Skip if the listener was removed.
if (!listener->_isRegistered)
return false;
2013-09-20 22:23:13 +08:00
event->setCurrentTarget(listener->_node);
2013-09-20 22:23:13 +08:00
switch (event->getEventCode())
{
case EventTouch::EventCode::BEGAN:
if (listener->onTouchesBegan)
2013-09-20 22:23:13 +08:00
{
listener->onTouchesBegan(mutableTouches, event);
2013-09-20 22:23:13 +08:00
}
break;
case EventTouch::EventCode::MOVED:
if (listener->onTouchesMoved)
2013-09-20 22:23:13 +08:00
{
listener->onTouchesMoved(mutableTouches, event);
2013-09-20 22:23:13 +08:00
}
break;
case EventTouch::EventCode::ENDED:
if (listener->onTouchesEnded)
2013-09-20 22:23:13 +08:00
{
listener->onTouchesEnded(mutableTouches, event);
2013-09-20 22:23:13 +08:00
}
break;
case EventTouch::EventCode::CANCELLED:
if (listener->onTouchesCancelled)
2013-09-20 22:23:13 +08:00
{
listener->onTouchesCancelled(mutableTouches, event);
2013-09-20 22:23:13 +08:00
}
break;
default:
CCASSERT(false, "The eventcode is invalid.");
break;
}
// If the event was stopped, return directly.
if (event->isStopped())
{
updateListeners(event);
return true;
2013-09-20 22:23:13 +08:00
}
return false;
};
dispatchEventToListeners(allAtOnceListeners, onTouchesEvent);
if (event->isStopped())
{
return;
2013-09-20 22:23:13 +08:00
}
}
updateListeners(event);
2013-09-20 22:23:13 +08:00
}
void EventDispatcher::updateListeners(Event* event)
2013-09-20 22:23:13 +08:00
{
CCASSERT(_inDispatch > 0, "If program goes here, there should be event in dispatch.");
auto onUpdateListeners = [this](const EventListener::ListenerID& listenerID)
2013-09-20 22:23:13 +08:00
{
auto listenersIter = _listenerMap.find(listenerID);
if (listenersIter == _listenerMap.end())
return;
auto listeners = listenersIter->second;
auto fixedPriorityListeners = listeners->getFixedPriorityListeners();
auto sceneGraphPriorityListeners = listeners->getSceneGraphPriorityListeners();
if (sceneGraphPriorityListeners)
2013-09-20 22:23:13 +08:00
{
for (auto iter = sceneGraphPriorityListeners->begin(); iter != sceneGraphPriorityListeners->end();)
2013-09-20 22:23:13 +08:00
{
auto l = *iter;
if (!l->isRegistered())
{
iter = sceneGraphPriorityListeners->erase(iter);
2013-10-24 11:17:29 +08:00
l->release();
}
else
{
++iter;
}
2013-09-20 22:23:13 +08:00
}
}
if (fixedPriorityListeners)
{
for (auto iter = fixedPriorityListeners->begin(); iter != fixedPriorityListeners->end();)
2013-09-20 22:23:13 +08:00
{
auto l = *iter;
if (!l->isRegistered())
{
iter = fixedPriorityListeners->erase(iter);
2013-10-24 11:17:29 +08:00
l->release();
}
else
{
++iter;
}
2013-09-20 22:23:13 +08:00
}
}
2013-10-24 17:27:22 +08:00
if (sceneGraphPriorityListeners && sceneGraphPriorityListeners->empty())
{
listeners->clearSceneGraphListeners();
}
if (fixedPriorityListeners && fixedPriorityListeners->empty())
{
listeners->clearFixedListeners();
}
};
if (event->getType() == Event::Type::TOUCH)
{
onUpdateListeners(EventListenerTouchOneByOne::LISTENER_ID);
onUpdateListeners(EventListenerTouchAllAtOnce::LISTENER_ID);
}
else
{
onUpdateListeners(__getListenerID(event));
2013-09-20 22:23:13 +08:00
}
if (_inDispatch > 1)
return;
CCASSERT(_inDispatch == 1, "_inDispatch should be 1 here.");
for (auto iter = _listenerMap.begin(); iter != _listenerMap.end();)
{
if (iter->second->empty())
{
_priorityDirtyFlagMap.erase(iter->first);
delete iter->second;
iter = _listenerMap.erase(iter);
}
else
{
++iter;
}
}
2013-09-20 22:23:13 +08:00
if (!_toAddedListeners.empty())
{
for (auto& listener : _toAddedListeners)
2013-09-20 22:23:13 +08:00
{
forceAddEventListener(listener);
2013-09-20 22:23:13 +08:00
}
_toAddedListeners.clear();
}
}
void EventDispatcher::updateDirtyFlagForSceneGraph()
{
if (!_dirtyNodes.empty())
{
for (auto& node : _dirtyNodes)
{
auto iter = _nodeListenersMap.find(node);
if (iter != _nodeListenersMap.end())
{
for (auto& l : *iter->second)
{
setDirty(l->getListenerID(), DirtyFlag::SCENE_GRAPH_PRIORITY);
}
}
}
_dirtyNodes.clear();
}
}
void EventDispatcher::sortEventListeners(const EventListener::ListenerID& listenerID)
{
DirtyFlag dirtyFlag = DirtyFlag::NONE;
auto dirtyIter = _priorityDirtyFlagMap.find(listenerID);
if (dirtyIter != _priorityDirtyFlagMap.end())
{
dirtyFlag = dirtyIter->second;
}
if (dirtyFlag != DirtyFlag::NONE)
{
// Clear the dirty flag first, if `rootNode` is nullptr, then set its dirty flag of scene graph priority
dirtyIter->second = DirtyFlag::NONE;
if ((int)dirtyFlag & (int)DirtyFlag::FIXED_PRIORITY)
{
sortEventListenersOfFixedPriority(listenerID);
}
if ((int)dirtyFlag & (int)DirtyFlag::SCENE_GRAPH_PRIORITY)
{
auto rootNode = Director::getInstance()->getRunningScene();
if (rootNode)
{
sortEventListenersOfSceneGraphPriority(listenerID, rootNode);
}
else
{
dirtyIter->second = DirtyFlag::SCENE_GRAPH_PRIORITY;
}
}
}
}
void EventDispatcher::sortEventListenersOfSceneGraphPriority(const EventListener::ListenerID& listenerID, Node* rootNode)
{
auto listeners = getListeners(listenerID);
if (listeners == nullptr)
return;
auto sceneGraphListeners = listeners->getSceneGraphPriorityListeners();
if (sceneGraphListeners == nullptr)
return;
2013-10-23 17:26:14 +08:00
// Reset priority index
_nodePriorityIndex = 0;
2013-10-23 17:26:14 +08:00
_nodePriorityMap.clear();
visitTarget(rootNode, true);
// After sort: priority < 0, > 0
std::sort(sceneGraphListeners->begin(), sceneGraphListeners->end(), [this](const EventListener* l1, const EventListener* l2) {
return _nodePriorityMap[l1->getAssociatedNode()] > _nodePriorityMap[l2->getAssociatedNode()];
});
#if DUMP_LISTENER_ITEM_PRIORITY_INFO
log("-----------------------------------");
for (auto& l : *sceneGraphListeners)
{
2013-10-23 17:26:14 +08:00
log("listener priority: node ([%s]%p), priority (%d)", typeid(*l->_node).name(), l->_node, _nodePriorityMap[l->_node]);
}
#endif
}
void EventDispatcher::sortEventListenersOfFixedPriority(const EventListener::ListenerID& listenerID)
2013-09-20 22:23:13 +08:00
{
auto listeners = getListeners(listenerID);
2013-09-20 22:23:13 +08:00
if (listeners == nullptr)
2013-09-20 22:23:13 +08:00
return;
auto fixedListeners = listeners->getFixedPriorityListeners();
if (fixedListeners == nullptr)
return;
// After sort: priority < 0, > 0
std::sort(fixedListeners->begin(), fixedListeners->end(), [](const EventListener* l1, const EventListener* l2) {
return l1->getFixedPriority() < l2->getFixedPriority();
2013-09-20 22:23:13 +08:00
});
// FIXME: Should use binary search
2013-12-05 17:19:01 +08:00
int index = 0;
for (auto& listener : *fixedListeners)
{
if (listener->getFixedPriority() >= 0)
break;
++index;
}
listeners->setGt0Index(index);
2013-09-20 22:23:13 +08:00
#if DUMP_LISTENER_ITEM_PRIORITY_INFO
log("-----------------------------------");
for (auto& l : *fixedListeners)
2013-09-20 22:23:13 +08:00
{
log("listener priority: node (%p), fixed (%d)", l->_node, l->_fixedPriority);
2013-09-20 22:23:13 +08:00
}
#endif
}
EventDispatcher::EventListenerVector* EventDispatcher::getListeners(const EventListener::ListenerID& listenerID)
2013-09-20 22:23:13 +08:00
{
auto iter = _listenerMap.find(listenerID);
if (iter != _listenerMap.end())
2013-09-20 22:23:13 +08:00
{
return iter->second;
}
return nullptr;
}
void EventDispatcher::removeEventListenersForListenerID(const EventListener::ListenerID& listenerID)
2013-09-20 22:23:13 +08:00
{
auto listenerItemIter = _listenerMap.find(listenerID);
if (listenerItemIter != _listenerMap.end())
2013-09-20 22:23:13 +08:00
{
auto listeners = listenerItemIter->second;
auto fixedPriorityListeners = listeners->getFixedPriorityListeners();
auto sceneGraphPriorityListeners = listeners->getSceneGraphPriorityListeners();
auto removeAllListenersInVector = [&](std::vector<EventListener*>* listenerVector){
if (listenerVector == nullptr)
return;
2013-09-20 22:23:13 +08:00
for (auto iter = listenerVector->begin(); iter != listenerVector->end();)
2013-09-20 22:23:13 +08:00
{
auto l = *iter;
l->setRegistered(false);
if (l->getAssociatedNode() != nullptr)
{
dissociateNodeAndEventListener(l->getAssociatedNode(), l);
l->setAssociatedNode(nullptr); // NULL out the node pointer so we don't have any dangling pointers to destroyed nodes.
}
if (_inDispatch == 0)
{
iter = listenerVector->erase(iter);
CC_SAFE_RELEASE(l);
}
else
{
++iter;
}
2013-09-20 22:23:13 +08:00
}
};
removeAllListenersInVector(sceneGraphPriorityListeners);
removeAllListenersInVector(fixedPriorityListeners);
2013-09-20 22:23:13 +08:00
// Remove the dirty flag according the 'listenerID'.
// No need to check whether the dispatcher is dispatching event.
_priorityDirtyFlagMap.erase(listenerID);
2013-09-20 22:23:13 +08:00
if (!_inDispatch)
{
listeners->clear();
delete listeners;
_listenerMap.erase(listenerItemIter);
2013-09-20 22:23:13 +08:00
}
}
for (auto iter = _toAddedListeners.begin(); iter != _toAddedListeners.end();)
{
if ((*iter)->getListenerID() == listenerID)
{
(*iter)->setRegistered(false);
(*iter)->release();
iter = _toAddedListeners.erase(iter);
}
else
{
++iter;
}
}
2013-09-20 22:23:13 +08:00
}
void EventDispatcher::removeEventListenersForType(EventListener::Type listenerType)
{
if (listenerType == EventListener::Type::TOUCH_ONE_BY_ONE)
{
removeEventListenersForListenerID(EventListenerTouchOneByOne::LISTENER_ID);
}
else if (listenerType == EventListener::Type::TOUCH_ALL_AT_ONCE)
{
removeEventListenersForListenerID(EventListenerTouchAllAtOnce::LISTENER_ID);
}
else if (listenerType == EventListener::Type::MOUSE)
{
removeEventListenersForListenerID(EventListenerMouse::LISTENER_ID);
}
else if (listenerType == EventListener::Type::ACCELERATION)
{
removeEventListenersForListenerID(EventListenerAcceleration::LISTENER_ID);
}
else if (listenerType == EventListener::Type::KEYBOARD)
{
removeEventListenersForListenerID(EventListenerKeyboard::LISTENER_ID);
}
else
{
CCASSERT(false, "Invalid listener type!");
}
}
void EventDispatcher::removeCustomEventListeners(const std::string& customEventName)
{
removeEventListenersForListenerID(customEventName);
}
void EventDispatcher::removeAllEventListeners()
2013-09-20 22:23:13 +08:00
{
bool cleanMap = true;
std::vector<EventListener::ListenerID> types(_listenerMap.size());
for (const auto& e : _listenerMap)
2013-09-20 22:23:13 +08:00
{
if (_internalCustomListenerIDs.find(e.first) != _internalCustomListenerIDs.end())
{
cleanMap = false;
}
else
{
types.push_back(e.first);
}
2013-09-20 22:23:13 +08:00
}
for (const auto& type : types)
{
removeEventListenersForListenerID(type);
}
if (!_inDispatch && cleanMap)
2013-09-20 22:23:13 +08:00
{
_listenerMap.clear();
2013-09-20 22:23:13 +08:00
}
}
void EventDispatcher::setEnabled(bool isEnabled)
{
_isEnabled = isEnabled;
}
bool EventDispatcher::isEnabled() const
{
return _isEnabled;
}
void EventDispatcher::setDirtyForNode(Node* node)
{
// Mark the node dirty only when there is an eventlistener associated with it.
2013-10-24 17:27:22 +08:00
if (_nodeListenersMap.find(node) != _nodeListenersMap.end())
{
_dirtyNodes.insert(node);
}
// Also set the dirty flag for node's children
const auto& children = node->getChildren();
for (const auto& child : children)
{
setDirtyForNode(child);
}
}
void EventDispatcher::setDirty(const EventListener::ListenerID& listenerID, DirtyFlag flag)
{
auto iter = _priorityDirtyFlagMap.find(listenerID);
2013-09-20 22:23:13 +08:00
if (iter == _priorityDirtyFlagMap.end())
{
_priorityDirtyFlagMap.insert(std::make_pair(listenerID, flag));
2013-09-20 22:23:13 +08:00
}
else
{
int ret = (int)flag | (int)iter->second;
iter->second = (DirtyFlag) ret;
2013-09-20 22:23:13 +08:00
}
}
NS_CC_END