diff --git a/cocos/2d/CCNode.cpp b/cocos/2d/CCNode.cpp index 606595eefe..f54391c602 100644 --- a/cocos/2d/CCNode.cpp +++ b/cocos/2d/CCNode.cpp @@ -34,6 +34,7 @@ THE SOFTWARE. #include #include +#include "xxhash.h" #include "base/CCDirector.h" #include "base/CCScheduler.h" #include "base/CCEventDispatcher.h" @@ -53,6 +54,10 @@ THE SOFTWARE. #define RENDER_IN_SUBPIXEL(__ARGS__) (ceil(__ARGS__)) #endif +/* +* 4.5x faster than std::hash in release mode +*/ +#define CC_HASH_NODE_NAME(name) (!name.empty() ? XXH3_64bits(name.c_str(), name.length()) : 0) NS_CC_BEGIN @@ -84,13 +89,14 @@ Node::Node() , _additionalTransformDirty(false) , _transformUpdated(true) // children (lazy allocs) +, _childrenIndexer(nullptr) // lazy alloc , _localZOrder$Arrival(0LL) , _globalZOrder(0) , _parent(nullptr) // "whole screen" objects. like Scenes and Layers, should set _ignoreAnchorPointForPosition to true , _tag(Node::INVALID_TAG) -, _name("") +, _name() , _hashOfName(0) // userData is always inited as nil , _userData(nullptr) @@ -149,6 +155,8 @@ Node * Node::create() Node::~Node() { CCLOGINFO( "deallocing Node: %p - tag: %i", this, _tag ); + + CC_SAFE_DELETE(_childrenIndexer); #if CC_ENABLE_SCRIPT_BINDING if (_updateScriptHandler) @@ -680,7 +688,15 @@ int Node::getTag() const /// tag setter void Node::setTag(int tag) { - _tag = tag ; + auto parentChildrenIndexer = getParentChildrenIndexer(); + if (parentChildrenIndexer) + { + if (_tag != tag) + parentChildrenIndexer->erase(_tag); + (*parentChildrenIndexer)[tag] = this; + } + + _tag = tag; } const std::string& Node::getName() const @@ -690,9 +706,33 @@ const std::string& Node::getName() const void Node::setName(const std::string& name) { + uint64_t newHash = CC_HASH_NODE_NAME(name); + auto parentChildrenIndexer = getParentChildrenIndexer(); + if (parentChildrenIndexer) + { + auto oldHash = CC_HASH_NODE_NAME(_name); + if (oldHash != newHash) + parentChildrenIndexer->erase(oldHash); + (*parentChildrenIndexer)[newHash] = this; + } + _name = name; - std::hash h; - _hashOfName = h(name); + _hashOfName = newHash; +} + +NodeIndexerMap_t* Node::getParentChildrenIndexer() +{ + if (!_director->isChildrenIndexerEnabled()) + return nullptr; + auto parent = getParent(); + if (parent) + { + auto& indexer = parent->_childrenIndexer; + if (!indexer) + indexer = new NodeIndexerMap_t(); + return indexer; + } + return nullptr; } /// userData setter @@ -750,26 +790,41 @@ Node* Node::getChildByTag(int tag) const { CCASSERT(tag != Node::INVALID_TAG, "Invalid tag"); - for (const auto child : _children) + if (_childrenIndexer) { - if(child && child->_tag == tag) - return child; + auto it = _childrenIndexer->find(tag); + if (it != _childrenIndexer->end()) + return it->second; + } + else + { + for (const auto child : _children) + { + if (child && child->_tag == tag) + return child; + } } return nullptr; } Node* Node::getChildByName(const std::string& name) const { - CCASSERT(!name.empty(), "Invalid name"); - - std::hash h; - size_t hash = h(name); - - for (const auto& child : _children) + // CCASSERT(!name.empty(), "Invalid name"); + auto hash = CC_HASH_NODE_NAME(name); + if (_childrenIndexer) { - // Different strings may have the same hash code, but can use it to compare first for speed - if(child->_hashOfName == hash && child->_name.compare(name) == 0) - return child; + auto it = _childrenIndexer->find(hash); + if (it != _childrenIndexer->end()) + return it->second; + } + else + { + for (const auto& child : _children) + { + // Different strings may have the same hash code, but can use it to compare first for speed + if (child->_hashOfName == hash && child->_name.compare(name) == 0) + return child; + } } return nullptr; } @@ -937,14 +992,14 @@ void Node::addChildHelper(Node* child, int localZOrder, int tag, const std::stri } this->insertChild(child, localZOrder); - + + child->setParent(this); + if (setTag) child->setTag(tag); else child->setName(name); - child->setParent(this); - child->updateOrderOfArrival(); if( _running ) @@ -1056,6 +1111,7 @@ void Node::removeAllChildrenWithCleanup(bool cleanup) } _children.clear(); + CC_SAFE_DELETE(_childrenIndexer); } void Node::resetChild(Node* child, bool cleanup) diff --git a/cocos/2d/CCNode.h b/cocos/2d/CCNode.h index 63cef196c4..6c1fa7a5c0 100644 --- a/cocos/2d/CCNode.h +++ b/cocos/2d/CCNode.h @@ -82,6 +82,8 @@ enum { class EventListener; +typedef std::map NodeIndexerMap_t; + /** @class Node * @brief Node is the base element of the Scene Graph. Elements of the Scene Graph must be Node objects or subclasses of it. The most common Node objects are: Scene, Layer, Sprite, Menu, Label. @@ -1841,6 +1843,8 @@ protected: private: void addChildHelper(Node* child, int localZOrder, int tag, const std::string &name, bool setTag); + NodeIndexerMap_t* getParentChildrenIndexer(); + protected: float _rotationX; ///< rotation on the X-axis @@ -1897,12 +1901,13 @@ protected: static std::uint32_t s_globalOrderOfArrival; Vector _children; ///< array of children nodes + NodeIndexerMap_t* _childrenIndexer; ///< The children indexer for fast find child Node *_parent; ///< weak reference to parent node Director* _director; //cached director pointer to improve rendering performance int _tag; ///< a tag. Can be any number you assigned just to identify this node std::string _name; ///_field.byteVal; case Type::INTEGER: return v._field.intVal == this->_field.intVal; case Type::UNSIGNED: - return v._field.uintVal == this->_field.uintVal; + return v._field.unsignedVal == this->_field.unsignedVal; case Type::BOOLEAN: return v._field.boolVal == this->_field.boolVal; case Type::STRING: @@ -439,11 +447,13 @@ unsigned char Value::asByte(unsigned char defaultValue) const switch (_type) { + case Type::BYTE: + return _field.byteVal; case Type::INTEGER: return static_cast(_field.intVal); case Type::UNSIGNED: - return static_cast(_field.uintVal); + return static_cast(_field.unsignedVal); case Type::STRING: return static_cast(atoi(_field.strVal->c_str())); @@ -472,8 +482,11 @@ int Value::asInt(int defaultValue) const return _field.intVal; case Type::UNSIGNED: - CCASSERT(_field.uintVal < INT_MAX, "Can only convert values < INT_MAX"); - return (int)_field.uintVal; + CCASSERT(_field.unsignedVal < INT_MAX, "Can only convert values < INT_MAX"); + return (int)_field.unsignedVal; + + case Type::BYTE: + return _field.byteVal; case Type::STRING: return atoi(_field.strVal->c_str()); @@ -499,12 +512,15 @@ unsigned int Value::asUnsignedInt(unsigned int defaultValue) const switch (_type) { case Type::UNSIGNED: - return _field.uintVal; + return _field.unsignedVal; case Type::INTEGER: CCASSERT(_field.intVal >= 0, "Only values >= 0 can be converted to unsigned"); return static_cast(_field.intVal); + case Type::BYTE: + return static_cast(_field.byteVal); + case Type::STRING: // NOTE: strtoul is required (need to augment on unsupported platforms) return static_cast(strtoul(_field.strVal->c_str(), nullptr, 10)); @@ -532,6 +548,9 @@ float Value::asFloat(float defaultValue) const case Type::FLOAT: return _field.floatVal; + case Type::BYTE: + return static_cast(_field.byteVal); + case Type::STRING: return static_cast(utils::atof(_field.strVal->c_str())); @@ -539,7 +558,7 @@ float Value::asFloat(float defaultValue) const return static_cast(_field.intVal); case Type::UNSIGNED: - return static_cast(_field.uintVal); + return static_cast(_field.unsignedVal); case Type::DOUBLE: return static_cast(_field.doubleVal); @@ -561,6 +580,9 @@ double Value::asDouble(double defaultValue) const case Type::DOUBLE: return _field.doubleVal; + case Type::BYTE: + return static_cast(_field.byteVal); + case Type::STRING: return static_cast(utils::atof(_field.strVal->c_str())); @@ -568,7 +590,7 @@ double Value::asDouble(double defaultValue) const return static_cast(_field.intVal); case Type::UNSIGNED: - return static_cast(_field.uintVal); + return static_cast(_field.unsignedVal); case Type::FLOAT: return static_cast(_field.floatVal); @@ -590,6 +612,9 @@ bool Value::asBool(bool defaultValue) const case Type::BOOLEAN: return _field.boolVal; + case Type::BYTE: + return _field.byteVal == 0 ? false : true; + case Type::STRING: return (*_field.strVal == "0" || *_field.strVal == "false") ? false : true; @@ -597,7 +622,7 @@ bool Value::asBool(bool defaultValue) const return _field.intVal == 0 ? false : true; case Type::UNSIGNED: - return _field.uintVal == 0 ? false : true; + return _field.unsignedVal == 0 ? false : true; case Type::FLOAT: return _field.floatVal == 0.0f ? false : true; @@ -629,11 +654,14 @@ std::string Value::asString() const size_t n = 0; switch (_type) { + case Type::BYTE: + ret = std::to_string(_field.byteVal); + break; case Type::INTEGER: ret = std::to_string(_field.intVal); break; case Type::UNSIGNED: - ret = std::to_string(_field.uintVal); + ret = std::to_string(_field.unsignedVal); break; case Type::FLOAT: ret.resize(NUMBER_MAX_DIGITS); @@ -763,6 +791,7 @@ static std::string visit(const Value& v, int depth) switch (v.getType()) { case Value::Type::NONE: + case Value::Type::BYTE: case Value::Type::INTEGER: case Value::Type::UNSIGNED: case Value::Type::FLOAT: @@ -800,11 +829,14 @@ void Value::clear() // Free memory the old value allocated switch (_type) { + case Type::BYTE: + _field.byteVal = 0; + break; case Type::INTEGER: _field.intVal = 0; break; case Type::UNSIGNED: - _field.uintVal = 0u; + _field.unsignedVal = 0u; break; case Type::FLOAT: _field.floatVal = 0.0f; diff --git a/cocos/base/CCValue.h b/cocos/base/CCValue.h index 736d84d78e..87382f3622 100644 --- a/cocos/base/CCValue.h +++ b/cocos/base/CCValue.h @@ -203,6 +203,8 @@ public: { /// no value is wrapped, an empty Value NONE = 0, + /// wrap byte + BYTE, /// wrap integer INTEGER, /// wrap unsigned @@ -235,8 +237,9 @@ private: union { + unsigned char byteVal; int intVal; - unsigned int uintVal; + unsigned int unsignedVal; float floatVal; double doubleVal; bool boolVal;