Ogre源代码浅析——资源管理逻辑结构(三)
在完成了资源定位和资源初始化之后,Ogre就随时可以对资源进行加载了。现以mesh对象的加载过程为例,分析一下资源的加载过程。以下是Call Stack中,实现mesh对象数据加载的各被调函数的调用顺序:
1. MeshManager::load( const String& filename, const String& groupName, HardwareBuffer::Usage vertexBufferUsage, HardwareBuffer::Usage indexBufferUsage, bool vertexBufferShadowed, bool indexBufferShadowed) || \/ 2. MeshManager::createOrRetrieve( const String& name, const String& group, bool isManual, ManualResourceLoader* loader, const NameValuePairList* params, HardwareBuffer::Usage vertexBufferUsage, HardwareBuffer::Usage indexBufferUsage, bool vertexBufferShadowed, bool indexBufferShadowed) || \/ 3. ResourceManager::createOrRetrieve( const String& name, const String& group, bool isManual, ManualResourceLoader* loader, const NameValuePairList* params)
在结点“3”中,createOrRetrieve函数会先通过getByName(name, group)搜索name所指的资源对象,代码如下:
1 ResourcePtr ResourceManager::getByName(const String& name, const String& groupName /* = ResourceGroupManager::AUTODETECT_RESOURCE_GROUP_NAME */) 2 { 3 ResourcePtr res; 4 5 // if not in the global pool - get it from the grouped pool 6 if(!ResourceGroupManager::getSingleton().isResourceGroupInGlobalPool(groupName)) 7 { 8 OGRE_LOCK_AUTO_MUTEX 9 ResourceWithGroupMap::iterator itGroup = mResourcesWithGroup.find(groupName); 10 11 if( itGroup != mResourcesWithGroup.end()) 12 { 13 ResourceMap::iterator it = itGroup->second.find(name); 14 15 if( it != itGroup->second.end()) 16 { 17 res = it->second; 18 } 19 } 20 } 21 22 // if didn't find it the grouped pool - get it from the global pool 23 if (res.isNull()) 24 { 25 OGRE_LOCK_AUTO_MUTEX 26 27 ResourceMap::iterator it = mResources.find(name); 28 29 if( it != mResources.end()) 30 { 31 res = it->second; 32 } 33 else 34 { 35 // this is the case when we need to search also in the grouped hash 36 if (groupName == ResourceGroupManager::AUTODETECT_RESOURCE_GROUP_NAME) 37 { 38 ResourceWithGroupMap::iterator iter = mResourcesWithGroup.begin(); 39 ResourceWithGroupMap::iterator iterE = mResourcesWithGroup.end(); 40 for ( ; iter != iterE ; iter++ ) 41 { 42 ResourceMap::iterator resMapIt = iter->second.find(name); 43 44 if( resMapIt != iter->second.end()) 45 { 46 res = resMapIt->second; 47 break; 48 } 49 } 50 } 51 } 52 } 53 54 return res; 55 }
可以看到,Ogre会在MeshManager的"group pool"中搜索(6-20行、33-51行);也会在"global pool"中搜索(29-32行),并返回搜索结果。如果没找到(返回的资源指针为空),createOrRetrieve函数会调用ResourceManager::create()来创建一个mesh对象(同前)。以下为create的代码:
ResourcePtr ResourceManager::create(const String& name, const String& group, bool isManual, ManualResourceLoader* loader, const NameValuePairList* params) { // Call creation implementation ResourcePtr ret = ResourcePtr( createImpl(name, getNextHandle(), group, isManual, loader, params)); if (params) ret->setParameterList(*params); addImpl(ret); // Tell resource group manager ResourceGroupManager::getSingleton()._notifyResourceCreated(ret); return ret; }
create()函数会先创建一个mesh对象(并未加载数据),并将mesh对象指针根据实际情况保存到ResourceManager的相关容器中;并通知ResourceGroupManager修改相应ResrouceGroup中的数据。
通过以上过程,在返回到调用堆栈结点"1"的函数MeshManager::load()后,MeshManager和ResourceGroupManager中一定会存在相应的mesh对象。接下来load函数将调用mesh对象的load函数,开始真正的mesh数据加载操作。mesh数据的加载是通过调用Resource::load(bool background)函数来实现的。代码如下:
1 void Resource::load(bool background) 2 { 3 // Early-out without lock (mitigate perf cost of ensuring loaded) 4 // Don't load if: 5 // 1. We're already loaded 6 // 2. Another thread is loading right now 7 // 3. We're marked for background loading and this is not the background 8 // loading thread we're being called by 9 10 if (mIsBackgroundLo aded && !background) return; 11 12 // This next section is to deal with cases where 2 threads are fighting over 13 // who gets to prepare / load - this will only usually happen if loading is escalated 14 bool keepChecking = true; 15 LoadingState old = LOADSTATE_UNLOADED; 16 while (keepChecking) 17 { 18 // quick check that avoids any synchronisation 19 old = mLoadingState.get(); 20 21 if ( old == LOADSTATE_PREPARING ) 22 { 23 while( mLoadingState.get() == LOADSTATE_PREPARING ) 24 { 25 OGRE_LOCK_AUTO_MUTEX 26 } 27 old = mLoadingState.get(); 28 } 29 30 if (old!=LOADSTATE_UNLOADED && old!=LOADSTATE_PREPARED && old!=LOADSTATE_LOADING) return; 31 32 // atomically do slower check to make absolutely sure, 33 // and set the load state to LOADING 34 if (old==LOADSTATE_LOADING || !mLoadingState.cas(old,LOADSTATE_LOADING)) 35 { 36 while( mLoadingState.get() == LOADSTATE_LOADING ) 37 { 38 OGRE_LOCK_AUTO_MUTEX 39 } 40 41 LoadingState state = mLoadingState.get(); 42 if( state == LOADSTATE_PREPARED || state == LOADSTATE_PREPARING ) 43 { 44 // another thread is preparing, loop around 45 continue; 46 } 47 else if( state != LOADSTATE_LOADED ) 48 { 49 OGRE_EXCEPT(Exception::ERR_INVALIDPARAMS, "Another thread failed in resource operation", 50 "Resource::load"); 51 } 52 return; 53 } 54 keepChecking = false; 55 } 56 57 // Scope lock for actual loading 58 try 59 { 60 61 OGRE_LOCK_AUTO_MUTEX 62 63 64 if (mIsManual) 65 { 66 preLoadImpl(); 67 // Load from manual loader 68 if (mLoader) 69 { 70 mLoader->loadResource(this); 71 } 72 else 73 { 74 // Warn that this resource is not reloadable 75 LogManager::getSingleton().stream(LML_TRIVIAL) 76 << "WARNING: " << mCreator->getResourceType() 77 << " instance '" << mName << "' was defined as manually " 78 << "loaded, but no manual loader was provided. This Resource " 79 << "will be lost if it has to be reloaded."; 80 } 81 postLoadImpl(); 82 } 83 else 84 { 85 86 if (old==LOADSTATE_UNLOADED) 87 prepareImpl(); 88 89 preLoadImpl(); 90 91 if (mGroup == ResourceGroupManager::AUTODETECT_RESOURCE_GROUP_NAME) 92 { 93 // Derive resource group 94 changeGroupOwnership( 95 ResourceGroupManager::getSingleton() 96 .findGroupContainingResource(mName)); 97 } 98 99 loadImpl(); 100 101 postLoadImpl(); 102 } 103 104 // Calculate resource size 105 mSize = calculateSize(); 106 107 } 108 catch (...) 109 { 110 // Reset loading in-progress flag, in case failed for some reason. 111 // We reset it to UNLOADED because the only other case is when 112 // old == PREPARED in which case the loadImpl should wipe out 113 // any prepared data since it might be invalid. 114 mLoadingState.set(LOADSTATE_UNLOADED); 115 // Re-throw 116 throw; 117 } 118 119 mLoadingState.set(LOADSTATE_LOADED); 120 _dirtyState(); 121 122 // Notify manager 123 if(mCreator) 124 mCreator->_notifyResourceLoaded(this); 125 126 // Fire events, if not background 127 if (!background) 128 _fireLoadingComplete(false); 129 130 131 }
实际执行加载任务的是loadImpl()(99行),但之前的预加载处理prepareImpl()(87行)值得关注。Ogre是采用数据流的方式对外部数据进行读写的,为此专门引入DataStream类和FileStreamDataStream类来进行相关操作;而在用DataStream对象进行具体的数据操作之前,要先将DataStream对象与指定的外部数据文件关联起来,Ogre用ResourceGroupManager::openResource()函数来实现这个关联操作。ResourceGroupManager::openResource()是在prepareImpl()函数中被调用的。以下是ResourceGroupManager::openResource()函数展开:
1 DataStreamPtr ResourceGroupManager::openResource( 2 const String& resourceName, const String& groupName, 3 bool searchGroupsIfNotFound, Resource* resourceBeingLoaded) 4 { 5 OGRE_LOCK_AUTO_MUTEX 6 7 if(mLoadingListener) 8 { 9 DataStreamPtr stream = mLoadingListener->resourceLoading(resourceName, groupName, resourceBeingLoaded); 10 if(!stream.isNull()) 11 return stream; 12 } 13 14 // Try to find in resource index first 15 ResourceGroup* grp = getResourceGroup(groupName); 16 if (!grp) 17 { 18 OGRE_EXCEPT(Exception::ERR_ITEM_NOT_FOUND, 19 "Cannot locate a resource group called '" + groupName + 20 "' for resource '" + resourceName + "'" , 21 "ResourceGroupManager::openResource"); 22 } 23 24 OGRE_LOCK_MUTEX(grp->OGRE_AUTO_MUTEX_NAME) // lock group mutex 25 26 Archive* pArch = 0; 27 ResourceLocationIndex::iterator rit = grp->resourceIndexCaseSensitive.find(resourceName); 28 if (rit != grp->resourceIndexCaseSensitive.end()) 29 { 30 // Found in the index 31 pArch = rit->second; 32 DataStreamPtr stream = pArch->open(resourceName); 33 if (mLoadingListener) 34 mLoadingListener->resourceStreamOpened(resourceName, groupName, resourceBeingLoaded, stream); 35 return stream; 36 } 37 else 38 { 39 // try case insensitive 40 String lcResourceName = resourceName; 41 StringUtil::toLowerCase(lcResourceName); 42 rit = grp->resourceIndexCaseInsensitive.find(lcResourceName); 43 if (rit != grp->resourceIndexCaseInsensitive.end()) 44 { 45 // Found in the index 46 pArch = rit->second; 47 DataStreamPtr stream = pArch->open(resourceName); 48 if (mLoadingListener) 49 mLoadingListener->resourceStreamOpened(resourceName, groupName, resourceBeingLoaded, stream); 50 return stream; 51 } 52 else 53 { 54 // Search the hard way 55 LocationList::iterator li, liend; 56 liend = grp->locationList.end(); 57 for (li = grp->locationList.begin(); li != liend; ++li) 58 { 59 Archive* arch = (*li)->archive; 60 if (arch->exists(resourceName)) 61 { 62 DataStreamPtr ptr = arch->open(resourceName); 63 if (mLoadingListener) 64 mLoadingListener->resourceStreamOpened(resourceName, groupName, resourceBeingLoaded, ptr); 65 return ptr; 66 } 67 } 68 } 69 } 70 71 72 // Not found 73 if (searchGroupsIfNotFound) 74 { 75 ResourceGroup* foundGrp = findGroupContainingResourceImpl(resourceName); 76 if (foundGrp) 77 { 78 if (resourceBeingLoaded) 79 { 80 resourceBeingLoaded->changeGroupOwnership(foundGrp->name); 81 } 82 return openResource(resourceName, foundGrp->name, false); 83 } 84 else 85 { 86 OGRE_EXCEPT(Exception::ERR_FILE_NOT_FOUND, 87 "Cannot locate resource " + resourceName + 88 " in resource group " + groupName + " or any other group.", 89 "ResourceGroupManager::openResource"); 90 } 91 } 92 OGRE_EXCEPT(Exception::ERR_FILE_NOT_FOUND, "Cannot locate resource " + 93 resourceName + " in resource group " + groupName + ".", 94 "ResourceGroupManager::openResource"); 95 96 }
从上面代码可以看到,Ogre首先是在相应ResrouceGroup的resourceIndexCaseSensitive和resourceIndexCaseInsensitive中搜索与resourceName相匹配的Archive对象,如果找到了就在Archive指定的路径下打开resourceName所指定的文件。(27-51行)如果在以上两个容器中都未找到对应的Archive,那么Ogre会在locationList中再进行一轮搜索(55-67行)。如果还没找到,Ogre就会通过函数findGroupContainingResourceImpl()将搜索范围扩大到ResourceGroupManager中的所有ResouceGroup,搜索的方式与上面是相同的。当所有搜索结束而未得到相应的Archive时,Ogre将抛弃常。
从之前(一)、(二)的讨论可知,resourceIndexCaseSensitive和resourceIndexInsensitive中所有的key值是资源文件名,也就是说经过资源定位操作(见之前的讨论)之后,一般情况下只要是“定位”过的目录,其中的文件名都会存在于某个ResourceGroup的以上两个容器中,所以通常resouceName所指定的资源在前两轮对这两个容器的搜索中就可以成功得到相应的Archive;但还有一种特殊情况——Archive所指定的不是“普通目录”而是“压缩包”。如果Archive指向“压缩包”文件,那么在之前的资源定位操作中,Ogre并不会把压缩包中所包含的资源文件名提取出来,保存到resourceIndexCaseSensitive或resourceIndexInsensitive中去,因而如果被加载的资源文件处在某个压缩包中时,加载过程中对这两个容器的搜索就无法得到想要的结果。这样看来Ogre提供的对locationList就显得很有必要了。Ogre在遍历locationList的过程中会对其中的每个Archive对象所示的目录或压缩包中的所有文件进行逐一探查(通过bool exists(const String& filename)函数),看看其中是否有一个文件名与resourceName相同的文件存在。以下是FileSystemArchive与ZipArchive的exists()函数:
bool FileSystemArchive::exists(const String& filename) { String full_path = concatenate_path(mName, filename); struct stat tagStat; bool ret = (stat(full_path.c_str(), &tagStat) == 0); // stat will return true if the filename is absolute, but we need to check // the file is actually in this archive if (ret && is_absolute_path(filename.c_str())) { // only valid if full path starts with our base #if OGRE_PLATFORM == OGRE_PLATFORM_WIN32 // case insensitive on windows String lowerCaseName = mName; StringUtil::toLowerCase(lowerCaseName); ret = Ogre::StringUtil::startsWith(full_path, lowerCaseName, true); #else // case sensitive ret = Ogre::StringUtil::startsWith(full_path, mName, false); #endif } return ret; } bool ZipArchive::exists(const String& filename) { // zziplib is not threadsafe OGRE_LOCK_AUTO_MUTEX ZZIP_STAT zstat; int res = zzip_dir_stat(mZzipDir, filename.c_str(), &zstat, ZZIP_CASEINSENSITIVE); return (res == ZZIP_NO_ERROR); }
从上面的讨论可以看到,Ogre进行资源加载前的“资源定位”操作是何等重要。用户即便是准备好了资源,组织好了外部存储方式,如果之前未对其进行正确的“资源定位”,那么在后续的操作中此资源文件将始终无法被搜索到,从而无法被加载。这里要强调的是“资源定位”(locate)与“资源声明”(declare)是两个不同的概念。
posted on 2012-12-26 17:11 yzwalkman 阅读(1952) 评论(0) 编辑 收藏 举报