Chinaunix首页 | 论坛 | 博客
  • 博客访问: 588517
  • 博文数量: 208
  • 博客积分: 3286
  • 博客等级: 中校
  • 技术积分: 1780
  • 用 户 组: 普通用户
  • 注册时间: 2007-09-24 20:38
文章分类

全部博文(208)

文章存档

2012年(7)

2011年(28)

2010年(21)

2009年(76)

2008年(65)

2007年(11)

我的朋友

分类: C/C++

2008-02-20 13:05:22

事件处理机制

      

         Framework类是sample framework中最重要的类,完成了创建窗体,初始化设备,创建命令行,事件处理(render loop)以及调节各种参数的任务。Framework类包含在dxmut.cs文件中。其中,比较特别的就是事件处理模型(或render loop)。

         为了获得高性能的渲染以及事件处理机制,framework类在初始化的方法中使用Device.IsUsingEventHandlers = false;关闭了事件处理模型。我们先来看看为什么默认的事件处理机制会导致性能的损失。默认情况下,Managed DirectX中的类在每创建一个资源时,都会为device订阅一些必然的事件。在最简单的情况下,每个资源(比如纹理或顶点缓冲)将会订阅Disposing事件,以及其他一些诸如DeviceLostDeviceReset的事件。这个步骤在整个对象的生存期都会发生。但是为什么我们在程序中不需要这种行为呢?

         主要原因就是需要对这种行为付出一定代价,有些情况下,代价还会很大。我们举个例子来说明这一点。看看下面这段简单的伪代码:

SomeResource res = new SomeResource(device);

device.Render(res);

         这段代码看起来几乎是“无害”的。只是创建了一个资源,并且渲染它。当这个对象不再使用时,垃圾回收器应该会智能的清除它。但是,这个想法是完全错误的。当创建新资源时,至少需要对device订阅一个事件,允许device正确的清除它。这种订阅实际上是一把“双刃剑”。

首先,订阅事件时需要分配(allocationEventHander对象完成实际的订阅工作。虽然这种分配的代价很小,但是,我们稍候就会看到,就算是很小的分配也会迅速膨胀。第二,订阅事件之后,资源和设备之间就有了一个硬连接(hard link)。因此,在垃圾回收器的眼里,这个对象在device的生存期里仍然处于使用状态,并且没有取消事件的订约。设想一下,这代码如果在渲染每帧的时候都运行一次;再设想一下,你的程序每分钟需要进行上千次渲染,并且程序已经运行了两分钟。结果,你创建了120000个在device生存期间不会被回收的对象,以及120000个事件句柄。所创建的这些对象不但会迅速消耗内存,而且会导致额外的垃圾回收,严重影响程序性能。如果你的资源都位于显存中,那么很快就会耗尽显存。

         这里,我们还没有考虑当最后释放设备时可能发生的情况。在前面的例子中,当释放device时,首先触发Disposing事件,此时,有120000个监听者订约了这个事件。你是否已经考虑到,调用这个巨大的事件句柄列表会将花费很多时间?实际上这将会花去几分钟时间,并且让用户认为程序已经处于死锁状态。

         因此,最好只在最简单的Direct3D程序中使用Managed Direct3D内建的事件处理机制。在任何需要考虑内存容量和性能的应用中(比如游戏),都必须避免这些处理过程。

         接下来,我们来看看framework中是如何实现事件处理模型的。实际上,SDK中的事件处理模型也是几经修改,现在使用的方法最早由Tom Mille 在他的bolg上贴出了具体的实现:

public void MainLoop()

{

        // Hook the application's idle event

        System.Windows.Forms.Application.Idle += new EventHandler(OnApplicationIdle);

        System.Windows.Forms.Application.Run(myForm);

}

 

private void OnApplicationIdle(object sender, EventArgs e)

{

    while (AppStillIdle)

    {

         // Render a frame during idle time (no messages are waiting)

         UpdateEnvironment();

         Render3DEnvironment();

    }

}

 

private bool AppStillIdle

{

     get

    {

        NativeMethods.Message msg;

        return !NativeMethods.PeekMessage(out msg, IntPtr.Zero, 0, 0, 0);

     }

}

 

And the declarations for those two native methods members:

 

[StructLayout(LayoutKind.Sequential)]

public struct Message

{

    public IntPtr hWnd;

    public WindowMessage msg;

    public IntPtr wParam;

    public IntPtr lParam;

    public uint time;

    public System.Drawing.Point p;

}

 

[System.Security.SuppressUnmanagedCodeSecurity] // We won't use this maliciously

[DllImport("User32.dll", CharSet=CharSet.Auto)]

public static extern bool PeekMessage(out Message msg, IntPtr hWnd, uint messageFilterMin, uint messageFilterMax, uint flags);

 

         这里,通过平台调用,使用了一些Win32 API。首先,在main方法中订阅了Application.Idle事件。在程序处理完了所有消息(如果不熟悉消息,那么可以把消息理解为系统定义的一个32位的值,他唯一的定义了一个事件,向Windows发出一个通知,告诉应用程序某个事情发生了。例如,单击鼠标、改变窗口尺寸、按下键盘上的一个键都会使Windows发送一个消息给应用程序。)之后,将会触发Application.Idle事件。我们的目标是让程序尽可能快,尽可能多的处理消息,同时不打断wendows消息的输入。

         OnApplicationIdle事件处理程序中,使用的了简单的Win32 API PeekMessage来检查程序是否有任何未处理的消息。这里使用while循环的原因是保证在处理完所有消息,同时消息队列还为空时,只触发一次Application.Idle事件。所以,我没一直循环,直到有新的消息,然后,跳出循环。普通的.Net WinForm窗体消息句柄将会选择未处理的消息。

         接下来,我们将使用框架,来显示一些物体了(源码请参考SDK中的empty project)。由于框架已经为我们完成了以上工作。我们只需要选择订阅那些事件就可以了。Sample framework通过这些事件通知应用程序关于改变设备、用户输入以及各种窗口消息。这些事件是可选的,但是,如果你没有设置,那么框架就不会为你处理相应的事件。在main方法中,创建了GameEngine对象之后,添加代码:

sampleFramework.Disposing += new EventHandler(blockerEngine.OnDestroyDevice);

sampleFramework.DeviceLost += new EventHandler(blockerEngine.OnLoseDevice);

sampleFramework.DeviceCreated += new DeviceEventHandler(blockerEngine.CreateDevice);

sampleFramework.DeviceReset += new DeviceEventHandler(blockerEngine.OnResetDevice);

sampleFramework.SetWndProcCallback(new WndProcCallback(blockerEngine.OnMsgProc));

sampleFramework.SetCallbackInterface(blockerEngine);

(注意,虽然在SDK October 2005的文档中还可以查到framework对象的SetKeyboardCallback方法,但实际上这个方法已经被删除了,老版本的SDK示例中使用了整个方法。)

         这一段代码作了很多工作,首先,为四个事件订阅了事件处理程序,分别是创建设备,失去设备,重置设备,销毁设备。我们将在后面实现这些事件处理程序。SetWndProcCallback方法订阅了处理windows消息的方法。随后,使用当前game engine实例作为参数,调用SetCallbackInterface方法。之后,编写事件处理程序

private void OnCreateDevice(object sender, DeviceEventArgs e)

{

    SurfaceDescription desc = e.BackBufferDescription;

}

private void OnResetDevice(object sender, DeviceEventArgs e)

{

    SurfaceDescription desc = e.BackBufferDescription;

}

private void OnLostDevice(object sender, EventArgs e)

{

}

private void OnDestroyDevice(object sender, EventArgs e)

{

}

public IntPtr OnMsgProc(IntPtr hWnd, NativeMethods.WindowMessage msg, IntPtr wParam, IntPtr lParam, ref bool noFurtherProcessing)

{

}

 

         由于之前的SetCallbackInterface需要接收一个IframeworkCallback的变量作为参数,但是我们的game engine类并没有实现这个类,所以添加以下代码:

public class EmptyProject : IFrameworkCallback, IdeviceCreation

         实现这个接口所定义的方法

public void OnFrameMove(Device device, double appTime, float elapsedTime)

{

}

public void OnFrameRender(Device device, double appTime, float elapsedTime)

{

}

         哦,框架性的东西总算是弄的差不多了。在SetCallbackInterface之后加上以下代码

try

{

 

         sampleFramework.SetCursorSettings(true, true);

         sampleFramework.Initialize( false, false, true );

         sampleFramework.CreateWindow("haha");

         sampleFramework.Window.KeyDown += new System.Windows.Forms.KeyEventHandler(blockerEngine.OnKeyEvent);

         sampleFramework.CreateDevice( 0, true, Framework.DefaultSizeWidth, Framework.DefaultSizeHeight, blockerEngine);

         sampleFramework.MainLoop();

 

}

#if(DEBUG)

         catch (Exception e)

         {

                   sampleFramework.DisplayErrorMessage(e);

#else

     catch

     {

          // In release mode fail silently

#endif

                   // Ignore any exceptions here, they would have been handled by other areas

                   return (sampleFramework.ExitCode == 0) ? 1 : sampleFramework.ExitCode; // Return an error code here

                   }

 

                   return sampleFramework.ExitCode;

         }

}

         现在运行程序看看,虽然只是一个蓝色的窗口,但是我们背后所搭建的框架已经可以实际应用到一个游戏之中了。为了让程序开起来有一点点交互,我们还订阅了键盘事件,通过空格键可以改变程序的颜色。

 
阅读(884) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~