美文网首页
Urho3D 1.7.1 源代码分析(一)

Urho3D 1.7.1 源代码分析(一)

作者: RonZheng2010 | 来源:发表于2019-11-30 18:23 被阅读0次

1. 概述

Urho3D依据功能划分成若干子系统,如:

  • Graphics 包装与显示编程的接口(OpenGL或者DirectX,这里只以OpenGL为例说明)。
  • UI 包括与用户交互的组件。
  • Renderer 包括与建模对象的组件。
  • Input 负责键盘、鼠标设备输入。
  • Audio 负责声音。
  • ResourceCache 负责Image、XML文件等资源加载。
  • FileSystem 包装文件系统接口。
  • Log 包装日志记录接口。

最重要的三个子系统是Graphics、UI和Renderer。

Context是各子系统运行的上下文环境。子系统都必须向它注册,通过这种方式,Context将各子系统粘合在一起,使它们能互相发现、互相发送通知(事件)、互相调用。这就如同设计模式中的中介者(Mediator)模式。

Engine也是一个子系统。没有将它列在上述的列表中,是因为它其实是其他子系统的组织者,由它负责创建和驱动其他子系统。Engine也要向Context注册,以便与其他子系统通信。

Application是应用程序类。 使用者应该从它派生自己的类。这里的HelloWorld是Urho3D的HelloWorld例子中定义的派生类。

Object类的成员context_是对Context的引用。实际上系统只有唯一的Context实例,构造Object对象时,将context_指向这个实例。需要与其他子系统的类都从Object派生,以便与其他子系统交互。

2. Object注册与创建

2.1 Object Factory

所有可以从Context创建的对象,除了从Object派生,还得有自己的ObjectFactory类,也就是ObjectFactoryImpl<T>。Context的成员factories_保存了对象类工厂的实例。

TypeInfo保存类的标识信息,也就是成员type_,这是一个根据类名称生成的全局唯一的Hash值。TypeInfo还保存指向父类的链接,也就是成员baseTypeInfo_。

每个类都要包括宏URHO3D_OBJECT(typename, baseTypeName)。 如下是宏展开后的部分代码。其中的函数GetTypeStatic()可以获得类的StringHash值,这个值是GetTypeInfoStatic()中定义的一个TypeInfo类型的静态变量typeInfoStatic。

static Urho3D::StringHash GetTypeStatic()
{
  return GetTypeInfoStatic()->GetType();
}

static const Urho3D::TypeInfo* GetTypeInfoStatic()
{
  static const Urho3D::TypeInfo typeInfoStatic(#typeName, BaseClassName::GetTypeInfoStatic()); 
  return &typeInfoStatic;
}

2.2 Object注册

Context可创建的对象类应该提供静态函数RegisterObject()。

RegisterObject()的工作如下:

  • 调用Context::RegisterFactory<T>创建对象工厂实例,并向Context注册,也就是保存在Context::factories_中。

  • 调用Context的一组属性设置函数,属性保存在Context的成员attributes_中,这是一个从类的StringHash值到其属性数组的映射。

    • AttributeInfo是属性类。成员type_是数据类型,name_是属性名称,offset_是属性在对象中的偏移,defaultValue_是缺省值,accessor_是属性访问函数。
  • 这些属性可能的来源是:

    • 调用URHO3D_COPY_BASE_ATTRIBUTES()从基类复制。
    • 调用URHO3D_UPDATE_ATTRIBUTE_DEFAULT_VALUE()更新已有的属性。
    • 调用URHO3D_ACCESSOR_ATTRIBUTE()注册新的属性。

2.3 Object创建

Context::CreateObject<T>()的工作如下:

  • 调用T::GetTypeStatic()得到类的StringHash值
  • 用该值调用Context::CreateObject()。它查找Context::factories_,找到对象工厂实例,然后调用ObjectFactoryImpl<T>::CreateObject()创建对象。

3. 事件处理机制

3.1 Event与EventHandler

每个event有自己唯一的StringHash值。EventNameRegistrar::RegiseterName()根据event的名字生成这个值。EventNameRegistrar的成员eventNames_保存了所有从event的StringHash值到名字的映射。

Object在成员eventHandlers_中保存自己的事件处理器EventHandler。

EventHandler抽象了事件处理器的接口。它的成员eventType_是event的StringHash值,成员receiver_是event的接收函数。

EventHandlerImpl<T>实现EventHandler。在EventHandleImpl<T>中,一般将receiver_设置成模板参数类T的成员函数,这样事件处理请求就被重定向了。

Context的成员eventReceivers_保存了从event的StringHash值到一组Object的映射,这组Object是该事件的接收者。

3.2 SubscribeToEvent()

SubscribeToEvent()负责订阅事件,如下的例子调用SubscribeToEvent()订阅E_SCREENMODE事件。

Object::SubscribeToEvent(E_SCREENMODE, URHO3D_HANDLER(Renderer, HandleScreenMode));

E_SCREENMODE是一个代表event type的StringHash值。它由URHO3D_EVENT()宏定义如下:

URHO3D_EVENT(E_SCREENMODE, ScreenMode)
{
  URHO3D_PARAM(P_WIDTH, Width);
}

以上宏定义展开如下:

static const Urho3D::StringHash
E_SCREENMODE(Urho3D::EventNameRegistrar::RegisterEventName(“ScreenMode”));

namespace ScreenMode
{
  static const Urho3D::StringHash P_WIDTH(“Width”);
}

RegisterEventName()从event名字”ScreenMode”计算出StringHash值。E_SCREENMODE被设置成该值。该事件还带参数P_WIDTH。

URHO3D_HANDLER()创建一个Event handler的实例。

#define URHO3D_HANDLER(className, function) \
(new Urho3D::EventHandlerImpl<className>(this, &className::function))

展开URHO3D_HANDLER(Renderer, HandleScreenMode) 的结果如下:

new Urho3D::EventHandlerImpl<Renderer>(this, &Renderer::HandleScreenMode)

EventHandlerImpl<Renderer>保存Renderer的实例,和event处理函数Render::HandlerScreenMode()。这样收到该event时,就调用EventHandlerImpl::Invoke()处理。

以上的步骤如下图所示。

  • 创建事件处理器,也就是EventHandleImpl<Renderer>。
  • 在Object::SubscriberToEvent()中,
    • 这个Object是接收者。调用Object::FindSpecificEvent()检查它的成员eventHandlers_是否已有该event的处理器,如果有则替换,否则新增一个。
    • 如果是新增,则还需要调用Context::AddEventReceiver()将调用者加入Context的成员eventReceiverGroup_中。

3.3 SendEvent()

SendEvent()负责发送事件。如下的例子发送E_SCREENMODE事件。

SendEvent(E_SCREENMODE, eventData);

如下是SendEvent()的工作。

  • 调用Context::GetEventReceiver(),在成员eventReceiverGroup_中,找到订阅该事件的Object实例。然后遍历所有Object,调用Object::OnEvent()。

  • Object::OnEvent()在eventHandlers_中找到该事件对应的处理函数,也就是EventHandler的实例。调用EventHandler::Invoke(),进而调用EventHandlerImpl<T>指定模板类T的成员函数。这里是模板类Renderer的成员函数HandleScreenMode()。

可以看出,这是一个从Context 到Object的两级派发机制。

Object的成员eventHanders_允许为同一个事件指定两个处理函数,一个绑定了特定的事件发送对象,一个不绑定。前一个优先级更高。在Object::OnEvent()中,会先查找前一个。

4. 工作队列

4.1 WorkThread 与 WorkQueue

Thread是封装线程的类。WorkThread是Thread的派生类,它专门配合工作队列WorkQueue使用。WorkQueue的成员threads_保存了一组WorkThread实例。

WorkItem是工作队列中的任务,成员workFunction_是任务的回调函数。WorkQueue的成员queue_保存了当前要执行的任务列表。

WorkQueue::CreateThreads()创建WorkThread实例,并调用Thread::Run()创建实际的线程,线程句柄保存在Thread的成员handle_中。

线程的执行函数是ThreadFunctionStatic()。它调用Thread的虚拟函数ThreadFunction(),Thread的派生类应实现这个函数。

WorkThread的成员owner_指向它所属的WorkQueue实例。WorkerThread的ThreadFunction()调用这个实例的ProcessItems()。后者持续从成员queue_取出任务并执行(也就是取出WorkItem,并调用WorkItem::workFunction_)。

WorkQueue的多个线程可以并行地执行queue_中的任务。从queue_中取出任务时,使用Mutex作互斥。

除了成员queue_之外,WorkQueue的成员poolItems_保存空闲的WorkItem实例,成员workItems_保存正在使用中的WorkItem实例。使用workItems_和queue_两个列表保存工作队列,是为了尽量减低互斥对访问效率的影响。

4.2 使用WorkQueue

WorkQueue的一般的使用过程如下。

这里使用OcclusionBuffer:DrawTrianles()作为例子,说明WorkQueue的一般使用过程。

Octree的成员batches_是一组OcclusionBatch实例。Octree需要在一组工作线程中对它们执行OcclusionBatch::DrawBatch(),并等待所有任务完成。

  • 遍历batches_,为每个OcclusionBatch实例增加WorkItem。

    • 调用WorkQueue::GetFreeItem()得到空闲的WorkItem实例。优先从成员poolItems_中取,如果没有,则创建一个新实例。
    • 设置WorkItem实例。这里设置成员workFunction_指向全局函数DrawOcclusionBatchWork(),这个函数将调用OcclusionBatch::DrawBatch()。OcclusionBatch实例作为成员start_传给全局函数。
    • 调用WorkQueue::AddWorkItem()加入新WorkItem实例。先加入成员workItems_,再加入queue_中。WorkItem有执行优先级,保存在成员priority_中。添加到queue_时比较优先级,保证优先级高的优先执行。
  • 将WorkItem加入queue_中,工作线程就从queue_中取出任务并执行。WorkItem的成员completed_表示任务状态,任务完成状态改成true。

  • 调用WorkQueue::Complete()等待指定优先级以上的任务完成,并做清理工作。

    • 首先调用IsCompleted(),遍历成员workItems_中的元素,检查指定优先级以上的任务是否完成。这里需要反复调用IsCompleted()确认,直到所有任务完成。
  • 调用PurgeComplete(),遍历workItems_,将状态为完成的元素挪出,调用ReturnToPool(),放回到poolItems_中。

  • 最后的问题是,任务少的时候如何回收WorkItem实例。 WorkQueue::HandleBeginFrame()中调用PurgePool()做这件事。PurgePool()记住每次调用时poolItems_的大小,如果前后两次相差值超过某一个阈值,则将多的WorkItem回收。

相关链接
Urho3D 1.7.1 源代码分析 (一)
Urho3D 1.7.1 源代码分析 (二)
Urho3D 1.7.1 源代码分析 (三)
Urho3D 1.7.1 源代码分析 (四)
Urho3D 1.7.1 源代码分析 (五)

相关文章

网友评论

      本文标题:Urho3D 1.7.1 源代码分析(一)

      本文链接:https://www.haomeiwen.com/subject/kowowctx.html