Ogre源代码浅析——脚本及其解析(五)
经过以上三个阶段的处理,借由AbstractTreeBuilder类对象,Ogre终于“理解”了指定的脚本文件究竟包含了哪些脚本对象,以及这些脚本对象间的相互关系。需要强调的是,此时各脚本对象所包含的内部数据并未被加载。在Ogre中,脚本对象与脚本对象内部数据,这两个概念是严格区分的。脚本对象与这个脚本对象中的数据,对应着脚本文件中的“同一段”内容。但脚本对象是解析过程中必不可少的一个数据抽象,只有借助它,才能有一个完整的“语义分析”过程,或者说“语义分析”才有一个落脚点。而脚本对象中的数据,以材质(material)为例,是要参与到后期的渲流程中去的;material实际上是实体类(Entity)的一个属性,material数据一旦被解析出来,就完全脱离了“脚本”和脚本对象而独立存在了。因此,对于脚本所描述的材质对象,在Ogre中有两个对象类型与之对应,它们分别是ObjectAbstractNode和Material。
从以上的讨论可以看出Ogre实际上是——根据概念来组织代码,而非根据代码来组织概念。在很多情况下,我们往往会跳过AbstractNode这一层的抽象,而直接根据之前的解析结果,在代码中创建出相应的Material对象。如果不考虑对脚本中Import、set及派生关系的解析,则这种想法是可行的。可是,与多抽象出一个AbstractNode相比,这种做法会失掉很多的灵活性,同时也会使更多的代码纠缠在一起。如果待解析的脚本对象结构发生了变化,那么按照Ogre的做法,整个解析流程大的框架无需作任何修改,只要对AbstractNode的生成和脚本对象内部数据分析的过程作相应调整即可;而取消AbstractNode类,这种调整就会变得复杂的多。Ogre这种概念抽取,相互独立的方式体现了面向对象设计的精髓,根据概念来组织代码会使代码的组织结构更清晰,代码也更容易扩展和修改。不过事物总是有两面性,大量抽象出概念并依此构建代码也容易造成功能实现时,调用堆栈过深的情况。Ogre有时调用堆栈较深,与它的这种设计思想是有关系的。
在根据AbstractNode来创建相应的数据对象之前,还有一些工作要做:一是,有继承关系的脚本对象要将其基类对象的相关数据包含进来;二是,通过Import引入的脚本对象,在构建出相应的ImportAbstractNode对象后,还要做进一步的相应处理;三是,对于通过'$'定义的变量,要用实际的数据对象进行替换。
派生类对象获取其基类对象中的数据,由以下函数来完成:
1 void ScriptCompiler::processObjects(Ogre::AbstractNodeList *nodes, const Ogre::AbstractNodeListPtr &top) 2 { 3 for(AbstractNodeList::iterator i = nodes->begin(); i != nodes->end(); ++i) 4 { 5 if((*i)->type == ANT_OBJECT) 6 { 7 ObjectAbstractNode *obj = (ObjectAbstractNode*)(*i).get(); 8 9 // Overlay base classes in order. 10 for (std::vector<String>::const_iterator baseIt = obj->bases.begin(), end_it = obj->bases.end(); baseIt != end_it; ++baseIt) 11 { 12 const String& base = *baseIt; 13 // Check the top level first, then check the import table 14 AbstractNodeListPtr newNodes = locateTarget(top.get(), base); 15 if(newNodes->empty()) 16 newNodes = locateTarget(&mImportTable, base); 17 18 if (!newNodes->empty()) { 19 for(AbstractNodeList::iterator j = newNodes->begin(); j != newNodes->end(); ++j) { 20 overlayObject(*j, obj); 21 } 22 } else { 23 addError(CE_OBJECTBASENOTFOUND, obj->file, obj->line, 24 "base object named \"" + base + "\" not found in script definition"); 25 } 26 } 27 28 // Recurse into children 29 processObjects(&obj->children, top); 30 31 // Overrides now exist in obj's overrides list. These are non-object nodes which must now 32 // Be placed in the children section of the object node such that overriding from parents 33 // into children works properly. 34 obj->children.insert(obj->children.begin(), obj->overrides.begin(), obj->overrides.end()); 35 } 36 } 37 }
函数首先会逐一访问传入的AbstractNodeList链表中的各AbstractNode对象(3行),如果它存在基类对象的话,就将它们逐一取出(14-16行),然后将基类对象数据扩展到相应的派生类中(20行)。对每个AbstractNode对象的子结点链表中的每个子结点,也做同样的操作(29行)。最后将经过基类扩展后所得的ObjectAbstractNode对象的overrides(也是一个AbstractNodeList,注意不要与下面讨论的overlayObject()函数第16行定义的override混淆,ObjectAbstractNode::overrides中保存的AbstractNode派生类对象是非ObjectAbstractNode类型的),插入到原对象的子结点链表中(34行),而此时的子结点链表中的各结点都已经扩展完毕。(说明:processObjects()函数,是在processImports()函数之后被调用,此时import的相关数据已经被处理完毕,通过import导入的AbstractNode会被保存在ScriptCompiler的mImportTable中。)
再来看一下具体的扩展过程,由于AbstractNodeList表示的是相应脚本文件中各脚本对象的树形结构,所以将基类对象数据扩展到派生类对象中的过程,实际上就是把基类对象的子结点链表插入到派生类对象子结点链表中的过程。问题在于,对于基类与派生类中的同名且同类型的子结点(如果存在的话)应该怎么处理?仍以material为例,假设material B从material A派生,而两者都有一个名为 nt的technique子结点,在这种情况下进行扩展就不能简单地将基类的子结点链表插入到派生类的子结点链表中去了。进行扩展操作的overlayObject()函数展开如下:
1 void ScriptCompiler::overlayObject(const AbstractNodePtr &source, ObjectAbstractNode *dest) 2 { 3 if(source->type == ANT_OBJECT) 4 { 5 ObjectAbstractNode *src = reinterpret_cast<ObjectAbstractNode*>(source.get()); 6 7 // Overlay the environment of one on top the other first 8 for(map<String,String>::type::const_iterator i = src->getVariables().begin(); i != src->getVariables().end(); ++i) 9 { 10 std::pair<bool,String> var = dest->getVariable(i->first); 11 if(!var.first) 12 dest->setVariable(i->first, i->second); 13 } 14 15 // Create a vector storing each pairing of override between source and destination 16 vector<std::pair<AbstractNodePtr,AbstractNodeList::iterator> >::type overrides; 17 // A list of indices for each destination node tracks the minimum 18 // source node they can index-match against 19 map<ObjectAbstractNode*,size_t>::type indices; 20 // A map storing which nodes have overridden from the destination node 21 map<ObjectAbstractNode*,bool>::type overridden; 22 23 // Fill the vector with objects from the source node (base) 24 // And insert non-objects into the overrides list of the destination 25 AbstractNodeList::iterator insertPos = dest->children.begin(); 26 for(AbstractNodeList::const_iterator i = src->children.begin(); i != src->children.end(); ++i) 27 { 28 if((*i)->type == ANT_OBJECT) 29 { 30 overrides.push_back(std::make_pair(*i, dest->children.end())); 31 } 32 else 33 { 34 AbstractNodePtr newNode((*i)->clone()); 35 newNode->parent = dest; 36 dest->overrides.push_back(newNode); 37 } 38 } 39 40 // Track the running maximum override index in the name-matching phase 41 size_t maxOverrideIndex = 0; 42 43 // Loop through destination children searching for name-matching overrides 44 for(AbstractNodeList::iterator i = dest->children.begin(); i != dest->children.end(); ) 45 { 46 if((*i)->type == ANT_OBJECT) 47 { 48 // Start tracking the override index position for this object 49 size_t overrideIndex = 0; 50 51 ObjectAbstractNode *node = reinterpret_cast<ObjectAbstractNode*>((*i).get()); 52 indices[node] = maxOverrideIndex; 53 overridden[node] = false; 54 55 // special treatment for materials with * in their name 56 bool nodeHasWildcard=node->name.find('*') != String::npos; 57 58 // Find the matching name node 59 for(size_t j = 0; j < overrides.size(); ++j) 60 { 61 ObjectAbstractNode *temp = reinterpret_cast<ObjectAbstractNode*>(overrides[j].first.get()); 62 // Consider a match a node that has a wildcard and matches an input name 63 bool wildcardMatch = nodeHasWildcard && 64 (StringUtil::match(temp->name,node->name,true) || 65 (node->name.size() == 1 && temp->name.empty())); 66 if(temp->cls == node->cls && !node->name.empty() && (temp->name == node->name || wildcardMatch)) 67 { 68 // Pair these two together unless it's already paired 69 if(overrides[j].second == dest->children.end()) 70 { 71 AbstractNodeList::iterator currentIterator = i; 72 ObjectAbstractNode *currentNode = node; 73 if (wildcardMatch) 74 { 75 //If wildcard is matched, make a copy of current material and put it before the iterator, matching its name to the parent. Use same reinterpret cast as above when node is set 76 AbstractNodePtr newNode((*i)->clone()); 77 currentIterator = dest->children.insert(currentIterator, newNode); 78 currentNode = reinterpret_cast<ObjectAbstractNode*>((*currentIterator).get()); 79 currentNode->name = temp->name;//make the regex match its matcher 80 } 81 overrides[j] = std::make_pair(overrides[j].first, currentIterator); 82 // Store the max override index for this matched pair 83 overrideIndex = j; 84 overrideIndex = maxOverrideIndex = std::max(overrideIndex, maxOverrideIndex); 85 indices[currentNode] = overrideIndex; 86 overridden[currentNode] = true; 87 } 88 else 89 { 90 addError(CE_DUPLICATEOVERRIDE, node->file, node->line); 91 } 92 93 if(!wildcardMatch) 94 break; 95 } 96 } 97 98 if (nodeHasWildcard) 99 { 100 //if the node has a wildcard it will be deleted since it was duplicated for every match 101 AbstractNodeList::iterator deletable=i++; 102 dest->children.erase(deletable); 103 } 104 else 105 { 106 ++i; //Behavior in absence of regex, just increment iterator 107 } 108 } 109 else 110 { 111 ++i; //Behavior in absence of replaceable object, just increment iterator to find another 112 } 113 } 114 115 // Now make matches based on index 116 // Loop through destination children searching for name-matching overrides 117 for(AbstractNodeList::iterator i = dest->children.begin(); i != dest->children.end(); ++i) 118 { 119 if((*i)->type == ANT_OBJECT) 120 { 121 ObjectAbstractNode *node = reinterpret_cast<ObjectAbstractNode*>((*i).get()); 122 if(!overridden[node]) 123 { 124 // Retrieve the minimum override index from the map 125 size_t overrideIndex = indices[node]; 126 127 if(overrideIndex < overrides.size()) 128 { 129 // Search for minimum matching override 130 for(size_t j = overrideIndex; j < overrides.size(); ++j) 131 { 132 ObjectAbstractNode *temp = reinterpret_cast<ObjectAbstractNode*>(overrides[j].first.get()); 133 if(temp->name.empty() && temp->cls == node->cls && overrides[j].second == dest->children.end()) 134 { 135 overrides[j] = std::make_pair(overrides[j].first, i); 136 break; 137 } 138 } 139 } 140 } 141 } 142 } 143 144 // Loop through overrides, either inserting source nodes or overriding 145 insertPos = dest->children.begin(); 146 for(size_t i = 0; i < overrides.size(); ++i) 147 { 148 if(overrides[i].second != dest->children.end()) 149 { 150 // Override the destination with the source (base) object 151 overlayObject(overrides[i].first, 152 reinterpret_cast<ObjectAbstractNode*>((*overrides[i].second).get())); 153 insertPos = overrides[i].second; 154 insertPos++; 155 } 156 else 157 { 158 // No override was possible, so insert this node at the insert position 159 // into the destination (child) object 160 AbstractNodePtr newNode(overrides[i].first->clone()); 161 newNode->parent = dest; 162 if(insertPos != dest->children.end()) 163 { 164 dest->children.insert(insertPos, newNode); 165 } 166 else 167 { 168 dest->children.push_back(newNode); 169 } 170 } 171 } 172 } 173 }
此函数第一个参数sourc指向的是基类对象结点,第二个参数dest指向的是相应派生类对象结点。函数中定义的overrides容器变量很重要,它里面放了一个std::pair对象。函数先对overrides变量进行初始化(26-38行),它将基类对象的子结点链表中的每个子结点对象与派生类对象的子结点链表的结尾(end)标志对应起来;同时,clone基类中非ObjectAbstractNode类型的结点,并将其放入派生类的overrides链表中(33-37行)。初始化完成后,函数首先要对基类和派生类结点的子结点链表进行搜索,如果发现有重名且同类型的子结点就加以标记(66-87行),标记的方法就是通过overrides中的pair对象,pair的first指向基类子结点链表中的一个子结点,pair的second指向派生类子结点链表中与first指向的结点重名且同类型的结点的位置,如果基类中的某个子结点在派生类中没有重名的子结点,那么相应pair的second仍指向派生类子结点链表的end标志。
到了overlayObject()函数的后期,如果overrides中相应pair的second值仍指向end,说明在派生类子结点中没有与基类的此子结点同名且同类型者,故只需将此结点直接插入派生类子结点链表中即可(157-170);如果相应的pair的second值不再指向end,则说明派生类子结点中有与基类的某子结点同名且同类型的结点, 此时就需要对这两个同名子结点再进行overlayObject()操作了(148-155行)。
posted on 2013-01-13 14:16 yzwalkman 阅读(1589) 评论(0) 编辑 收藏 举报
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步