目标:(十六)中问题38
瓦片请求的大概过程是这样的:
1、osgEarth::Drivers::RexTerrainEngine::TileNode在渲染遍历时产生LoadTileData请求,将请求传递给DatabsePager改造成DatabaseRequest请求后,将此请求放入了DatabasePager的_fileRequestQueue队列中
2、DatabasePager的运行线程DatabaseThread又将DatabaseRequest请求部分属性进行设置后,放入DatabasePager的_dataToMergeList的队列中
3、在更新遍历DatabasePager时,将其_dataToMergeList列表中的DatabaseRequest请求经过转换成LoadTileData请求后,放入瓦片分页加载器的_mergeQueue队列中
4、在更新遍历时瓦片分页加载器处理_mergeQueue中的请求
下面围绕着每个过程中的子过程有哪些,请求是如何在其中运转腾挪的这么一个思路来详细展开。
1、osgEarth::Drivers::RexTerrainEngine::TileNode在渲染遍历时产生请求
(1)在渲染遍历时,TileNode通过PagerLoader加载请求
osgEarthDrivers/engine_rex/TileNode.cpp
void
TileNode::load(TerrainCuller* culler)
{
_context->getLoader()->load( _loadRequest.get(), priority, *culler );
}
这里的_context为rex引擎RexTerrainEngineNode中设置的(见(十二)),在TileNode执行create的时候赋值。getLoader就是获取分页瓦片加载器PagerLoader,然后由PagerLoader执行load方法加载请求。
这里的_loadRequest为LoadTileData对象,是TileNode的成员变量,在TileNode执行create的时候赋值。从代码可以看出,创建瓦片节点时,相应的高程和影像数据并没有同时加载进来,而是通过多线程处理请求的方式来加载。
osgEarthDrivers/engine_rex/TileNode.cpp
void
TileNode::create(const TileKey& key, TileNode* parent, EngineContext* context)
{
_context = context;
_loadRequest = new LoadTileData( this, context );
_loadRequest->setName( _key.str() );
_loadRequest->setTileKey( _key );
}
这里的priority意思为加载瓦片的优先级
这里的culler为裁剪遍历器
(2)PagerLoader再通过DatabasePager处理请求
osgEarthDrivers/engine_rex/Loader.cpp
bool
PagerLoader::load(Loader::Request* request, float priority, osg::NodeVisitor& nv)
{
request->setState(Request::RUNNING);
// remember the last tick at which this request was submitted
request->_lastTick = osg::Timer::instance()->tick();
// update the priority, scale and bias it, and then normalize it to [0..1] range.
unsigned lod = request->getTileKey().getLOD();
float p = priority * _priorityScales[lod] + _priorityOffsets[lod];
request->_priority = p / (float)(_numLODs+1);
request->setFrameNumber( fn );
request->_loadCount++;
char filename[64];
sprintf(filename, "%u.osgearth_rex_loader", request->_uid);
nv.getDatabaseRequestHandler()->requestNodeFile(
filename,
_myNodePath,
request->_priority,
nv.getFrameStamp(),
request->_internalHandle,
_dboptions.get() );
_requests[request->getUID()] = request;
}
PagerLoader先对请求的相关属性进行设置,然后在转交给DatabasePager做进一步处理,包括设置请求的状态(有空闲、运行、合并、完成四种状态)、请求提交的时间、请求的优先级、请求的帧号、。
这里的filename,request的_uid为请求LoadTileData构造时,由其父类Loader::Request在构造函数时通过osgEarth的Registry获得的。filename如何一步步传递变现,需要在下文持续跟踪。
osgEarth/Registry.cpp
UID
Registry::createUID()
{
//todo: use OpenThreads::Atomic for this
ScopedLock<Mutex> exclusive( _uidGenMutex );
return (UID)( _uidGen++ );
}
_myNodePath存放的是节点路径,其实这里面只有一个节点,就是分页瓦片加载器PagerLoader,PagerLoader在构造函数时将自身指针放入了该路径里。
request的_priority为请求的优先级,这个优先级在PagerLoader的load函数做过处理,但是为什么要如此处理,需关注问题40的跟踪(十九)。
request的_internalHandle,需关注问题41的跟踪,第一次这个指针内容是空的,在DatabasePager的requestNodeFile函数中会对这个指针赋值,最终该指针指向的是DatabaseRequest请求。
_dboptions设置数据库相关选项,包括文件位置回调函数和分页瓦片加载器(后面的处理过程中,会从这个选项中取出这个PagerLoader),在PagerLoader的构造函数中设置。
PagerLoader在对请求处理完后,会将其存放到自己的请求列表_requests中,以备以后使用。
(3)DatabasePager的requestNodeFile对请求做进一步处理
主要分三种情况:
一是第一次处理这种请求,构造DatabaseRequest的成员信息,包括合法性、文件名、第一次请求的帧号、第一次请求的帧戳、第一次请求的优先级、最后一次请求的帧号、最后一次请求的帧戳、最后一次请求的优先级、组节点(其实应该是分页加载器节点PagerLoader)、地形节点(容纳所有瓦片的节点)、加载选项(见上面的_dboptions)、对象缓存_objectCache。
二是第二次以上处理这种请求,更新设置DatabaseRequest的部分成员信息,如合法性、最后一次请求的帧号、最后一次请求的帧戳、最后一次请求的优先级、请求数量等。
三是第一次处理过然后又删除过(猜测的,待确认),重新设置DatabaseRequest的成员信息,如最后一次请求的帧号、最后一次请求的帧戳、最后一次请求的优先级、组节点(其实应该是分页加载器节点PagerLoader)、地形节点(容纳所有瓦片的节点)、加载选项(见上面的_dboptions)、对象缓存_objectCache。
请求处理完成后,会将请求DatabaseRequest放入到_fileRequestQueue队列中,在DatabaseThread线程中进一步处理。
值得注意的是,在这个函数中会判断DatabaseThread是否运行,如果没有的话则建立线程并允许。
经过这一步处理后,原始的LoadTileData请求转变成了DatabaseRequest请求,这两个请求不存在继承关系,DatabaseRequest是LoadTileData中的一个成员指针_internalHandle。这样对DatabaseRequest的处理就变成了对LoadTileData的处理。
2、DatabaseThread线程对_fileRequestQueue队列中的请求进行处理
线程的主要处理结果是设置DatabaseRequest的_loadedModel成员变量。然后将请求放入DatabasePager的_dataToMergeList的队列中。
每次从队列中取出第一个请求,按如下流程处理:
(1)请求数据在缓存中存在,从缓冲中读,专列文章分析
(2)缓存中不存在,从文件中读
osgEarth通过插件加载的方式(详见文章(三)),最后由osgEarth::Drivers::RexTerrainEngine::PagerLoaderAngent的readNode方法负责对请求进一步处理。readNode具体是由PagerLoader的invokeAndRelease对请求处理完后返回请求,然后PagerLoaderAngent将请求封装到RequestResultNode(继承自osg::Node,里面有包含请求信息的成员变量_request,瓦片模型信息在请求里)中返回到ReadResult中供使用,而invokeAndRelease具体是由请求(本质上是LoadTileData)调用自己的invoke方法完善自己的_dataModel成员变量。_dataModel是通过rex引擎的createTileModel来创建的,具体创建方法如下:
osgEarth/TerrainEngineNode.cpp
TerrainTileModel*
TerrainEngineNode::createTileModel(const MapFrame& frame,
const TileKey& key,
const CreateTileModelFilter& filter,
ProgressCallback* progress
)
{
TerrainEngineRequirements* requirements = this;
// Ask the factory to create a new tile model:
osg::ref_ptr<TerrainTileModel> model = _tileModelFactory->createTileModel(
frame,
key,
filter,
requirements,
progress);
}
由于TerrainEngineNode是多继承的,通过TerrainEngineRequirements* requirements = this;进行类型明确。
这里的_tileModelFactory为rex引擎在setMap时创建的地形瓦片模型工厂osgEarth::TerrainTileModelFactory,用于创建地形瓦片模型等。具体创建过程为:
osgEarth/TerrainTileModelFactory.cpp
TerrainTileModel*
TerrainTileModelFactory::createTileModel(const MapFrame& frame,
const TileKey& key,
const CreateTileModelFilter& filter,
const TerrainEngineRequirements* requirements,
ProgressCallback* progress)
{
// Make a new model:
osg::ref_ptr<TerrainTileModel> model = new TerrainTileModel(
key,
frame.getRevision() );
// assemble all the components:
//addImageLayers(model.get(), frame, requirements, key, filter, progress);
addColorLayers(model.get(), frame, requirements, key, filter, progress);
addPatchLayers(model.get(), frame, key, filter, progress);
if ( requirements == 0L || requirements->elevationTexturesRequired() )
{
unsigned border = requirements->elevationBorderRequired() ? 1u : 0u;
addElevation( model.get(), frame, key, filter, border, progress );
}
#if 0
if ( requirements == 0L || requirements->normalTexturesRequired() )
{
addNormalMap( model.get(), frame, key, progress );
}
#endif
// done.
return model.release();
}
生成好DatabaseRequest请求中的_loadedModel后,请求会被放入到DatabasePager的_dataToMergeList中。
3、在更新遍历DatabasePager时
将其_dataToMergeList列表中的DatabaseRequest请求传给PagerLoader(是由DatabasePager的addLoadedDataToSceneGraph函数来完成的),经过PagerLoader转换成原先的LoadTileData请求后,放入PagerLoader的_mergeQueue队列中(是由PagerLoader的addChild完成的)。
4、在更新遍历时瓦片分页加载器处理_mergeQueue中的请求
从合并请求队列中取出第一个请求,总共处理的请求数不超过设定的每帧最多处理的数目(在PagerLoader创建时会设置),对请求进行处理。
总结,请求是在TileNode节点中创建的,但是是在PagerLoader中进行管理,在DatabasePager线程中由PagerLoader进行转手(最终交由LoadTileData从读取真正的瓦片数据)。
待继续分析列表:
9、earth文件中都有哪些options((九)中问题)
10、如何根据earth文件options创建不同的地理信息引擎节点((九)中问题)
11、rex地理信息引擎的四梁八柱((九)中问题)
12、osgEarth::TerrainEngineNode中setMap方法作用((十二)中问题)
13、RexTerrainEngineNode中_mapFrame的作用((十二)中问题)
14、地形变形(Terrain morphing)((十二)中问题)
15、地球瓦片过期门限的含义((十二)中问题)
16、高分辨率优先的含义((十二)中问题)
17、OSGEARTH_DEBUG_NORMALS环境变量的作用((十二)中问题)
18、活跃瓦片寄存器的作用((十二)中问题)
19、资源释放器子节点的作用((十二)中问题)
20、共享几何图形池子节点的作用((十二)中问题)
21、分页瓦片加载器子节点的作用((十二)中问题)
22、分页瓦片卸载器子节点的作用((十二)中问题)
23、栅格化器子节点的作用((十二)中问题)
24、地形子节点的作用((十二)中问题)
25、绑定渲染器的作用((十二)中问题)
26、地图回调函数的作用((十二)中问题)
27、如何将地图图层添加到rex引擎中((十二)中问题)
28、选择信息的作用((十二)中问题)
29、瓦片包围盒修改回调函数的作用((十二)中问题)
30、刷新rex引擎((十二)中问题)
31、刷新边界作用((十二)中问题)
32、osgEarth::Metrics类的意义((十四)中问题)
33、请求合并队列_mergeQueue((十四)中问题)
34、分页瓦片加载器在更新遍历时对请求处理过程((十四)中问题)
35、分页瓦片加载器在更新遍历时对已处理请求裁剪过程((十四)中问题)
36、已处理的请求队列_requests((十四)中问题)
37、DatabasePager中的_fileRequestQueue和_httpRequestQueue((十六)中问题)
38、瓦片请求的生成到处理过程详解((十六)中问题)
39、瓦片节点TileNode的创建过程((十七)中问题)
40、request请求加载瓦片优先级的含义((十七)中问题)
41、request的_internalHandle的作用((十七)中问题)
42、DatabaseRequest中_objectCache含义((十七)中问题)
42、osgEarth的多线程分析((十七)中问题)
43、osgEarth的缓存及其结构((十七)中问题)
44、DatabaseThread从缓存加载数据过程((十七)中问题)
45、DatabaseThread从文件加载数据过程((十七)中问题)
46、决定创建TileNode的时机条件((十七)中问题)
47、TerrainEngineNode的createTileModel过程详解((十七)中问题)
48、DatabaseThread中CompileSet的含义((十七)中问题)
48、PagerLoader的traverse过程详解((十七)中问题)
49、DatabaseThread的run过程详解((十七)中问题)
50、LoadTileData的invoke过程详解((十七)中问题)
51、TileNode的cull过程详解((十七)中问题)