OSG — 事件处理模型

来源:互联网 发布:梦龙网络计划流水 编辑:程序博客网 时间:2024/04/30 05:23
纵览多数面向对象的库,在事件处理上都希望把事件源和事件处理分开。在说OSG的事件处理之前我想先看一个通用的事件处理模式,Reactor模式。
Reactor的是用于事件驱动的应用程序中的,将一个或多个客户提交的请求分发给相应的事件处理器处理。这里有两个设计需求:
Q1.客户提交的请求时不能阻塞客户代码,我们可以待会处理你的请求,但是不能看到你的请求时理都不理你。
解决: 设计一个Synchronously Event Demultiplexer.使用不阻塞客户代码的API来检测到达的事件源请求,怎么把事件源和事件处理器关联起来呢,我们需要给Reactor提供事件注册能力,告诉Reactor哪些事件你是有兴趣的,什么样的事件源和什么样的事件处理器关联。
2.程序要求方便扩展,当我要添加一类事件处理能力时不能对现有代码造成大的冲击。
解决:Reactor中为了实现事件派发一定有一张映射表 (Event Source –> Event Handler),我们可以定义一个Event Handler的接口,所有具体的事件处理类都从该接口继承,这样,添加一类新的事件处理时,只需要定义一个新类,并注册给Reactor就可以了。
那OSG呢,OSG是桌面系统渲染库,我需要相应鼠标操作,键盘操作,以Windows为例是由windows的消息循环处理的,我怎么能用面向对象的发式去包装它使得客户使用的时候也只要简单的定义继承类,注册事件处理类从而只要在事件继承类中填写处理代码就可以了呢。
看看OSG事件处理的客户代码是怎么写的:
先定义具体的事件处理类,这里是picking,必须从OSG预先定义好的事件处理接口继承
class PickHandler : public osgGA::GUIEventHandler
{
 public:
      //实现这个接口,在这里填写处理代码 };
      bool handler(const osgGA::GUIEventAdapter& ea,osgGA::GUIActionAdapter& aa){ …};
};
再看看主程序:
int main(int argc, char** argv)
{
     //创建场景的视图
     osgViewer::Viewer viewer;
     viewer.serSceneData( createScene().get());
    
     //添加pick的事件处理类
     viewer.addEventHandler(new PickHandler);
    
     // create the windows and run the threads.
    viewer.realize();
    while( !viewer.done() )
    {
        viewer.sync();// wait for all cull and draw threads to complete.
        /*update the scene by traversing it with the the update visitor
           which will call all node update callbacks and animations.*/
        viewer.update();
        
        // fire off the cull and draw traversals of the scene.
        viewer.frame();
    }
    
     viewer.sync();               // wait for all cull and draw threads to complete.
     viewer.cleanup_frame();  // run a clean up frame to delete all OpenGL objects.
     viewer.sync();               // wait for all the clean up frame to complete.

}
我这里套用候捷老师的一句话:“让我们拿起手术刀,看看背后到底发生了什么”

先看viewer.realize做了些什么,首先viewer.realize调用了OsgCamaraGroup的realize(),为什么是一个camera的group呢,因为OSG的Viewer可以让我们同时从多个角度观察场景,这个时候场景数据是同一份,多个角度观察意味着多个Camera。每个Camera对应一个RenderSurface。说到绘图,总要有个画布吧。通常这里的画布就是一个window资源。我想现在我们也知道OSG的设计者设计realize函数的意图了,Realize就是实现,就是为这个实例分配所需的资源,并且对该实例和他所辖的资源进行初始化,Viewer是这样,CameraGroup是这样,RenderSurface也是这样。于是CamaraGroup迭代他的所有的Camara,得到每个Camara的RenderSurface,调用RenderSurface.realize()。RenderSurface.realize最终会调用_init()对自己和所辖资源进行初始化,在看_init()的代码之前我先要解释一下:
OSG的设计者希望给RenderSurface一定的灵活性,他既可以自己拥有绘图窗口也可以接管别人的窗口,对于一个库的设计,这一点是很重要的。如果OSG是单独使用,那RenderSurface应该创建自己的窗口并在上面绘图,同时管理在该窗口上发生的鼠标,键盘事件。但是很多情况用户希望利用OSG提供的场景渲染能力在用户原有的程序中工作,比如用户的程序是MFC的单文档视图架构,用户希望使用OSG在他的视图类CView上绘图,我们知道OSG中已经提供了使用鼠标进行旋转,缩放,Picking等事件响应,用户代码当然希望能重用这样的功能,而用户的CView类本身也有鼠标,键盘响应函数(比如单击鼠标右键,弹出一个Context Menu等)。
我设想RenderSurface应该有一个HWND m_hwnd,如果m_hwnd为NULL,就自己创建窗口和管理窗口上的事件。如果m_hwnd不是NULL,就是说RenderSurface应该接管m_hwnd所指向的窗口,所以应该使用SetWindowLong函数将原来的窗口事件处理函数替换为RenderSurface的静态成员函数。但是这样的话,原来的CView类的事件处理函数就都失效了,所以RenderSurface中的事件处理函数应该是个钩子,发生在窗口上的事件先到达RenderSurface,让OSG处理,然后OSG再转发该事件到原来的事件处理函数(比如CView的鼠标键盘函数)。
好,看代码(为了解释方便,我做了简化)
bool RenderSurface::realize()
{
    if(_ownWindow) // 如果RenderSurface自己创建窗口,就需要有自己的时间循环–>自己的消息泵
    {
         startThread(); //创建自己的线程和消息泵来分发事件
   //_ThreadReady是个barrier,Decription: Block until numThreads threads have entered the barrier.
         _threadReady->block(); //阻止当前线程,保证刚用startThread创建的线程能够先运行!
    }
    else //如果接管别人,那自己只是一个钩子,使用别人的消息泵
    {
        if(!_init()) //调用RenderSurface的_init()进行初始化
        {
            return false;
        }
     } 
}
startThread是OpenThread库中的函数(关于OSG线程管理,等我学习好了再贴出来~~),这里我们只要知道它创建了一个新的线程,线程函数如下:
static unsigned long __stdcall ThreadPrivateActions::StartThread(void *data)
{
 //这里的data就是RenderSurface的this指针(RenderSurface继承自Thread)
      Thread *thread = static_cast<Thread *>(data);
      …
      thread->run(); //进入RenderSurface(Thread)自己的消息泵
      …
}
好,看一下自己的消息泵,逻辑和标准的Windows一样~~
void RenderSurface::run()
{
    _init(); //还是要初始化资源的
    if( _threadReady != 0L)
        _threadReady->block(); //既然_init已经做完了,激活刚才在realize中被阻止的线程并返回
    //哈哈,我们自己的消息泵开始运转拉 
    MSG msg;
    while( GetMessage( &msg, NULL, 0, 0) > 0)
    {
        TranslateMessage( &msg );
        DispatchMessage( &msg );
        testCancel();
    }
}
我们来看一下_init()函数(为了解释方便,我做了简化)
bool RenderSurface::_init()
{
    if(_ownWindow) //如果RenderSurface需要自己创建窗口
    {
        //配置一些信息然后创建窗口
        WNDCLASS wndclass;
        wndclass.lpfnWndProc = (WNDPROC)s_proc; //设定自己创建窗口的消息处理函数是s_proc
        m_hwnd = CreateWindowEx(…);
     }
     else  //如果m_hwnd指向其他窗口,表示RenderSurface是接管资源
     {
         //用RenderSurface的静态成员函数(事件处理)来替换原来的 事件处理函数
         m_oldWndProc = (WNDPROC)SetWindowLongPtr(m_hwnd,GWLP_WNDPROC,(LONG_PTR)((LONG)s_proc));
     }
     //其他初始化工作,比如OpenGL的初始化(SetPixelFormat,wglCreateContext)
}
到现在,不管是自己创建窗口还是接管别人的窗口,所有的消息都回到达这里:
LONG WINAPI RenderSurface::s_proc( Window hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam )
{
     ref_ptr<Event> ev;
     switch(uMsg)
     {
   case WM_MOUSEMOVE :
             _mx = ((int)(short)LOWORD(lParam)); //保存鼠标位置
             _my = ((int)(short)HIWORD(lParam));
             ev = new MouseMotionEvent( hWnd, _mx, _my ); //创建一个OSG的事件对象
             dispatch( ev ); //发送这个事件,使得OSG事件模型能够捕获并调用相应的EventHandler
             if (!_ownWindow) //如果我们是接管其他的窗口,给那个窗口处理自己消息的机会
                lRet = CallWindowProc(_oldWndProc, hWnd, uMsg, wParam, lParam);
              break;
        case WM_LBUTTONDOWN:
             //和WM_MOUSEMOVE的代码是类似的
        …
        case WM_CLOSE:
         if (!_ownWindow)
         {
                 SetWindowLongPtr(_win, GWLP_WNDPROC, (LONG_PTR)_oldWndProc); //把被接管窗口恢复原来的情况
                 lRet = CallWindowProc(_oldWndProc, hWnd, uMsg, wParam, lParam);
                 ev = new ShutdownEvent(hWnd);
                 dispatch(ev);
             }
             else
                 DestroyWindow (hWnd);               
             break;
     }
}
void RenderSurface::dispatch( ref_ptr<Event> ev )
{
    for(遍历所有对事件响应有兴趣的客户)
    {
        Client[i].EventQueue.push_back(ev);
    }
}
现在我们知道OSG把Windows原始的事件包装成事件对象塞到了一个叫EventQueue的对象里面,现在我们来研究谁来从这个Queue里面取事件并调用OSG中的事件处理器!
我们回到一开始,看看OSG是怎么注册事件处理器的,viewer::addEventHandler(new PickHandler)把PickHandler实例加到_eventHandlerList的头部(push_front).
哈哈,接着看外层循环里面的viewer.update()
void Viewer::update() [我只抽去了和事件处理相关的部分并作了简化了代码]
{
   // 按照事件到达的顺序遍历Queue中的事件
    for(osgGA::EventQueue::Events::iterator event_itr=events.begin(); event_itr!=events.end(); ++event_itr)
    {
        bool handled = false;
        //遍历所有注册过的事件处理器
        for(EventHandlerList::iterator handler_itr=_eventHandlerList.begin();
            handler_itr!=_eventHandlerList.end() && !handled;
            ++handler_itr)
        {  
            handled = (*handler_itr)->handle(*(*event_itr),*this,0,0); //调用事件处理器的处理函数
        }
       
    }
}
可以看得出,OSG的事件模型中,所有的事件处理器都回得到事件通知
bool PickHandler::handle(const osgGA::GUIEventAdapter& ea,osgGA::GUIActionAdapter&)
{
    switch(ea.getEventType())
    {
        case(osgGA::GUIEventAdapter::FRAME)://由具体的事件处理器自己负责事件的Filter
        {
            pick(ea);
        }
        return false;
       
        default:
            return false;
    }
}
这里面共享了EventQueue。并且注意一下,往EventQueue里面push事件对象的代码是在RenderSurface所在的线程中,而取事件对象是在main函数所在线程中,这里有个同步,我们在将来的OSG线程管理的时候再说。

原创粉丝点击