diff --git a/cocos/2d/CCNode.cpp b/cocos/2d/CCNode.cpp index 99c2c85888..5956007311 100644 --- a/cocos/2d/CCNode.cpp +++ b/cocos/2d/CCNode.cpp @@ -30,6 +30,7 @@ THE SOFTWARE. #include #include +#include #include "base/CCDirector.h" #include "base/CCScheduler.h" @@ -692,7 +693,7 @@ GLProgram * Node::getGLProgram() const return _glProgramState ? _glProgramState->getGLProgram() : nullptr; } -Scene* Node::getScene() +Scene* Node::getScene() const { if(!_parent) return nullptr; @@ -767,7 +768,7 @@ Node* Node::getChildByTag(int tag) const Node* Node::getChildByName(const std::string& name) const { - CCASSERT(name.size() != 0, "Invalid name"); + CCASSERT(name.length() != 0, "Invalid name"); std::hash h; size_t hash = h(name); @@ -781,23 +782,141 @@ Node* Node::getChildByName(const std::string& name) const return nullptr; } -void Node::enumerateChildren(const std::string &name, std::function callback) const +void Node::enumerateChildren(const std::string &name, std::function callback) const { - CCASSERT(name.size() != 0, "Invalid name"); + CCASSERT(name.length() != 0, "Invalid name"); + CCASSERT(callback != nullptr, "Invalid callback function"); - std::hash h; - size_t hash = h(name); + size_t length = name.length(); - for (const auto& child : _children) + size_t subStrStartPos = 0; // sub string start index + size_t subStrlength = length; // sub string length + + // Starts with '/' or '//'? + bool searchFromRoot = false; + bool searchFromRootRecursive = false; + if (name[0] == '/') { - // 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) + if (length > 2 && name[1] == '/') { - // terminate enumeration if callback return true - if (callback(child)) - break; + searchFromRootRecursive = true; + subStrStartPos = 2; + subStrlength -= 2; + } + else + { + searchFromRoot = true; + subStrStartPos = 1; + subStrlength -= 1; } } + + // End with '/..'? + bool searchFromParent = false; + if (length > 3 && + name[length-3] == '/' && + name[length-2] == '.' && + name[length-1] == '.') + { + searchFromParent = true; + subStrlength -= 3; + } + + // Remove '/', '//' and '/..' if exist + std::string newName = name.substr(subStrStartPos, subStrlength); + // If search from parent, then add * at first to make it match its children, which will do make + if (searchFromParent) + { + newName.insert(0, "[[:alnum:]]+/"); + } + + if (searchFromRoot) + { + // name is '/xxx' + auto root = getScene(); + if (root) + { + root->doEnumerate(newName, callback); + } + } + else if (searchFromRootRecursive) + { + // name is '//xxx' + auto root = getScene(); + if (root) + { + doEnumerateRecursive(root, newName, callback); + } + } + else + { + // name is xxx + doEnumerate(newName, callback); + } +} + +bool Node::doEnumerateRecursive(const Node* node, const std::string &name, std::function callback) const +{ + bool ret =false; + + if (node->doEnumerate(name, callback)) + { + // search itself + ret = true; + } + else + { + // search its children + for (const auto& child : node->getChildren()) + { + if (doEnumerateRecursive(child, name, callback)) + { + ret = true; + break; + } + } + } + + return ret; +} + +bool Node::doEnumerate(std::string name, std::function callback) const +{ + // name may be xxx/yyy, should find its parent + size_t pos = name.find('/'); + std::string searchName = name; + bool needRecursive = false; + if (pos != name.npos) + { + searchName = name.substr(0, pos); + name.erase(0, pos+1); + needRecursive = true; + } + + bool ret = false; + for (const auto& child : _children) + { + if(std::regex_match(child->_name, std::regex(searchName))) + { + if (!needRecursive) + { + // terminate enumeration if callback return true + if (callback(child)) + { + ret = true; + break; + } + } + else + { + ret = child->doEnumerate(name, callback); + if (ret) + break; + } + } + } + + return ret; } /* "add" logic MUST only be on this method diff --git a/cocos/2d/CCNode.h b/cocos/2d/CCNode.h index dffb0841f1..4b0b3e58ed 100644 --- a/cocos/2d/CCNode.h +++ b/cocos/2d/CCNode.h @@ -679,7 +679,28 @@ public: virtual Node* getChildByName(const std::string& name) const; /** Search the children of the receiving node to perform processing for nodes which share a name. * - * @param name The name to search for + * @param name The name to search for, support c++11 regular expression + * Search syntax options: + * `/` : When placed at the start of the search string, this indicates that the search should be performed on the tree's node. + * `//`: Can only be placed at the begin of the search string. This indicates that the search should be performed on the tree's node + * and be performed recursively across the entire node tree. + * `..`: The search should move up to the node's parent. Can only be placed at the end of string + * `/` : When placed anywhere but the start of the search string, this indicates that the search should move to the node's children + * + * @code + * enumerateChildren("/MyName", ...): This searches the root's children and matches any node with the name `MyName`. + * enumerateChildren("//MyName", ...): This searches the root's children recursively and matches any node with the name `MyName`. + * enumerateChildren("[[:alnum:]]+", ...): This search string matches every node of its children. + * enumerateChildren("/MyName", ...): This searches the node tree and matches the parent node of every node named `MyName`. + * enumerateChildren("A([:digit:])", ...): This searches the node's children and returns any child named `A0`, `A1`, ..., `A9` + * enumerateChildren("Abby/Normal", ...): This searches the node's grandchildren and returns any node whose name is `Normal` + * and whose parent is named `Abby`. + * enumerateChildren("//Abby/Normal", ...): This searches the node tree and returns any node whose name is `Normal` and whose + * parent is named `Abby`. + * @endcode + * + * @warning Only support alpha or number for name, and not support unicode + * * @param callback A callback function to execute on nodes that match the `name` parameter. The function takes the following arguments: * `node` * A node that matches the name @@ -687,7 +708,7 @@ public: * * @since v3.2 */ - virtual void enumerateChildren(const std::string &name, std::function callback) const; + virtual void enumerateChildren(const std::string &name, std::function callback) const; /** * Returns the array of the node's children * @@ -994,7 +1015,7 @@ public: It returns `nullptr` if the node doesn't belong to any Scene. This function recursively calls parent->getScene() until parent is a Scene object. The results are not cached. It is that the user caches the results in case this functions is being used inside a loop. */ - virtual Scene* getScene(); + virtual Scene* getScene() const; /** * Returns an AABB (axis-aligned bounding-box) in its parent's coordinate system. @@ -1427,6 +1448,9 @@ protected: virtual void disableCascadeColor(); virtual void updateColor() {} + bool doEnumerate(std::string name, std::function callback) const; + bool doEnumerateRecursive(const Node* node, const std::string &name, std::function callback) const; + #if CC_USE_PHYSICS virtual void updatePhysicsBodyPosition(Scene* layer); virtual void updatePhysicsBodyRotation(Scene* layer); diff --git a/cocos/2d/CCScene.cpp b/cocos/2d/CCScene.cpp index c59d8439f6..834a83ffca 100644 --- a/cocos/2d/CCScene.cpp +++ b/cocos/2d/CCScene.cpp @@ -98,9 +98,10 @@ std::string Scene::getDescription() const return StringUtils::format("", _tag); } -Scene* Scene::getScene() +Scene* Scene::getScene() const { - return this; + // FIX ME: should use const_case<> to fix compiling error + return const_cast(this); } #if CC_USE_PHYSICS diff --git a/cocos/2d/CCScene.h b/cocos/2d/CCScene.h index 4d30cbd76e..1174d1a9a8 100644 --- a/cocos/2d/CCScene.h +++ b/cocos/2d/CCScene.h @@ -58,7 +58,7 @@ public: static Scene *createWithSize(const Size& size); // Overrides - virtual Scene *getScene() override; + virtual Scene *getScene() const override; using Node::addChild; virtual std::string getDescription() const override; diff --git a/tests/cpp-tests/Classes/NodeTest/NodeTest.cpp b/tests/cpp-tests/Classes/NodeTest/NodeTest.cpp index f45d62e38c..14e494be28 100644 --- a/tests/cpp-tests/Classes/NodeTest/NodeTest.cpp +++ b/tests/cpp-tests/Classes/NodeTest/NodeTest.cpp @@ -1247,6 +1247,8 @@ void NodeNameTest::onEnter() { TestCocosNodeDemo::BaseTest::onEnter(); + auto parent = Node::create(); + // setName(), getName() and getChildByName() char name[20]; for (int i = 0; i < 10; ++i) @@ -1254,42 +1256,172 @@ void NodeNameTest::onEnter() sprintf(name, "node%d", i); auto node = Node::create(); node->setName(name); - addChild(node); + parent->addChild(node); } - - for (int j = 0; j < 10; ++j) + + for (int i = 0; i < 10; ++i) { - sprintf(name, "node%d", j); - auto node = getChildByName(name); + sprintf(name, "node%d", i); + auto node = parent->getChildByName(name); log("find child: %s", node->getName().c_str()); } - // node with same name - auto node = Node::create(); - log("node with name test: %p", node); - node->setName("test"); - addChild(node); - - node = Node::create(); - log("node with name test: %p", node); - node->setName("test"); - addChild(node); - - node = getChildByName("test"); - log("find node with name: %p", node); - // enumerateChildren() - log("will terminate when find node with name 'test'"); - enumerateChildren("test", [](const Node* node) -> bool { - log("find node with name 'test'"); + int i = 0; + parent->enumerateChildren("test", [&i](Node* node) -> bool { + ++i; return true; }); + CCAssert(i == 1, ""); - log("will find all nodes with name 'test' twice"); - enumerateChildren("test", [](const Node* node) -> bool { - log("find node with name 'test'"); + i = 0; + parent->enumerateChildren("test", [&i](Node* node) -> bool { + ++i; return false; }); + CCAssert(i == 2, ""); + + // enumerateChildren() + // name = regular expression + parent = Node::create(); + for (int i = 0; i < 100; ++i) + { + auto node = Node::create(); + sprintf(name, "node%d", i); + node->setName(name); + parent->addChild(node); + } + + i = 0; + parent->enumerateChildren("node[[:digit:]]+", [&i](Node* node) -> bool { + ++i; + return false; + }); + CCAssert(i == 100, ""); + + i = 0; + parent->enumerateChildren("node[[:digit:]]+", [&i](Node* node) -> bool { + ++i; + return true; + }); + CCAssert(i == 1, ""); + + + // enumerateChildren + // name = node[[digit]]+/node + + parent = Node::create(); + for (int i = 0; i < 100; ++i) + { + auto node = Node::create(); + sprintf(name, "node%d", i); + node->setName(name); + parent->addChild(node); + + for (int j = 0; j < 100; ++j) + { + auto child = Node::create(); + child->setName("node"); + node->addChild(child); + } + } + + i = 0; + parent->enumerateChildren("node1/node", [&i](Node* node) -> bool { + ++i; + return false; + }); + CCAssert(i == 100, ""); + + i = 0; + parent->enumerateChildren("node1/node", [&i](Node* node) -> bool { + ++i; + return true; + }); + CCAssert(i == 1, ""); + + i = 0; + parent->enumerateChildren("node[[:digit:]]+/node", [&i](Node* node) -> bool { + ++i; + return false; + }); + CCAssert(i == 10000, ""); + + i = 0; + parent->enumerateChildren("node[[:digit:]]+/node", [&i](Node* node) -> bool { + ++i; + return true; + }); + CCAssert(i == 1, ""); + + // search from parent + // name is xxx/.. + i = 0; + parent->enumerateChildren("node/..", [&i](Node* node) -> bool { + ++i; + return true; + }); + CCAssert(i == 1, ""); + + i = 0; + parent->enumerateChildren("node/..", [&i](Node* node) -> bool { + ++i; + return false; + }); + CCAssert(i == 10000, ""); + + // name = /xxx : search from root + parent = getScene(); + for (int j = 0; j < 100; j++) + { + auto node = Node::create(); + sprintf(name, "node%d", j); + node->setName(name); + parent->addChild(node); + + for (int k = 0; k < 100; ++k) + { + auto child = Node::create(); + sprintf(name, "node%d", k); + child->setName(name); + node->addChild(child); + } + } + + i = 0; + enumerateChildren("/node[[:digit:]]+", [&i](Node* node) -> bool { + ++i; + return false; + }); + CCAssert(i == 100, ""); + + i = 0; + enumerateChildren("/node[[:digit:]]+", [&i](Node* node) -> bool { + ++i; + return true; + }); + CCAssert(i == 1, ""); + + i = 0; + enumerateChildren("//node[[:digit:]]+", [&i](Node* node) -> bool { + ++i; + return false; + }); + CCAssert(i == 10100, ""); // 10000(children) + 100(parent) + + i = 0; + enumerateChildren("//node[[:digit:]]+", [&i](Node* node) -> bool { + ++i; + return true; + }); + CCAssert(i == 1, ""); + + i = 0; + enumerateChildren("//node[[:digit:]]+/..", [&i](Node* node) -> bool { + ++i; + return false; + }); + CCAssert(i == 10000, ""); } ///