gpt4 book ai didi

c# - 游戏逻辑的最快迭代方法 : thread, 空闲处理程序、winproc 或其他我不知道的东西?

转载 作者:太空宇宙 更新时间:2023-11-03 13:17:14 25 4
gpt4 key购买 nike

我正在创建一个新的 Windows C# 窗体应用程序,它使用各种硬件系统(Kinect V2 和 3D RFID 定位系统)。这是一个科学应用程序,而不是游戏,但具有绝对类似于游戏的逻辑(需要收集和管理 3d env 对象的物理数据,渲染图形 3D 环境等)。在大多数原生 Windows 环境游戏中,您可能有一个基本的主循环,它在监听来自 mse 和 kbd 的 DirectInput 时调用逻辑更新和渲染函数。但由于这是 C#,它更像是一个事件驱动的环境,我相信我需要依赖 3 个选项之一:

(1) 创建一个新线程并将其用作逻辑更新和 3d 渲染的主循环(当然要监听转义逻辑),或者
(2) 创建一个应用程序空闲处理函数,检查应用程序是否空闲,然后处理逻辑,或者
(3) 覆盖 winproc 并在那里执行我的类似游戏的逻辑。

所以我创建了一个测试应用程序:它有一个时钟显示,并使用刚才提到的所有三种方法为每种方法独立计算 FPS 计数器。这衡量每种方法每秒迭代的频率,并每秒显示一次在时钟旁边。

但是结果不是我所期望的。我认为新线程将是最快的方法,类似于主循环。但事实并非如此。 WinProc 被调用最多(即使将线程的优先级设置为最高)。此外,应用程序空闲处理程序是最慢的(正如预期的那样,它只在空闲时调用!) - 但我已经读到空闲处理是处理窗口 C# 窗体游戏类型应用程序的规范方法。这三个都使用完全相同的逻辑来计算它们的 FPS(只是一个 int,每次调用它们的方法时都会得到一个++)。

以下是应用运行几秒钟并稳定后的结果:

基于线程的 FPS:3,200
应用程序空闲处理程序 FPS:600
WinProc 覆盖 FPS:10,000

我知道为什么空闲处理程序是最慢的。但为什么线程的迭代频率低于 winproc?我在那里错过了什么?此外,在与窗体窗口交互时,winproc 发现每秒迭代次数大幅增加(正如预期的那样),但线程也是如此!为什么仅仅因为与 GUI 交互,独立线程就会被更频繁地调用?我显然不理解这里的一些基本知识 - 有人可以给我一些线索吗?

更新 1: 这是 self 的问题的第一个版本被搁置以来的一次编辑 - 显然人们希望看到一些实际的代码!这不是“我不知道为什么我的代码不起作用”——我试图梳理性能……所有这些都有效。所以这是基本代码 - 希望这会有所帮助!

谢谢!

代码:ClockText 变量只是 3 个文本框,在每个方法中用时间戳更新,而 UPS 变量是用每个方法计算的 FPS 更新的文本框:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Runtime.InteropServices; //for DllImport of c++ functionality for win msg processing

namespace Clock1
{
public partial class ClockForm : Form
{
//General app variables
System.Threading.Thread t;
bool KeepLooping = true;
DateTime StartTime = DateTime.Now;
DateTime StopTime = DateTime.Now;
TimeSpan TimePassed = new TimeSpan();
int FrameTick1 = 0;
int FrameTick2 = 0;
int FrameTick3 = 0;

//Imported for use of c++ win msg variables

//For message handler version
[StructLayout(LayoutKind.Sequential)]
public struct NativeMessage
{
public IntPtr Handle;
public uint Message;
public IntPtr WParameter;
public IntPtr LParameter;
public uint Time;
public Point Location;
}
[DllImport("user32.dll")]
public static extern int PeekMessage(out NativeMessage message, IntPtr window, uint filterMin, uint filterMax, uint remove);

//Imported for winproc loop version processing on WM_PAINT (0x000F) messages (lowest allocation overhead)
[DllImport("user32.dll")]
public static extern int SendNotifyMessage(IntPtr hWnd, int Msg, IntPtr wParam, IntPtr lParam);

//Primary form loop
public ClockForm()
{
//Loaded on program start

//Init components for C# environment
InitializeComponent();

//For winproc method, override painting (double check the functionality of this):
//SetStyle(ControlStyles.UserPaint | ControlStyles.AllPaintingInWmPaint, true);

//Multithreaded method initialization:
t = new System.Threading.Thread(MainLoop);
t.Priority = System.Threading.ThreadPriority.Highest;
t.Start();

//Application idle event handling allocation:
Application.Idle += HandleApplicationIdle;
}

//Multithreading method primary func
public void MainLoop()
{
while (KeepLooping == true)
{
//Count Frames
FrameTick1++;

//Use Invoke because the new thread can't access UI elements directly
MethodInvoker MI = delegate()
{
//Update time:
ClockText.Text = DateTime.Now.ToString("hh:mm:ss.fff tt");
};
ClockText.Invoke(MI);
}

//When KeepLooping is no longer true, exit:
Application.Exit();
Environment.Exit(1);
}

//Application idle event handling method primary func
void HandleApplicationIdle(object sender, EventArgs e)
{
while (IsApplicationIdle())
{
//Count Frames
FrameTick2++;

//Call frame update:
UpdatesPerSecond();

//Update time text
ClockText2.Text = DateTime.Now.ToString("hh:mm:ss.fff tt");
}
}

//Application idle event handling method secondary func
bool IsApplicationIdle()
{
NativeMessage result;
return PeekMessage(out result, IntPtr.Zero, (uint)0, (uint)0, (uint)0) == 0;
}

//Winproc method (override for the WndProc handler):
protected override void WndProc(ref Message m)
{
//Updates with full FPS capacity (may be a CPU hit though, so consider doing this only on a certain event - maybe not WM_PAINT, but something else, a faster more regular heartbeat
//NOte: All messages are sent to the WndProc method after getting filtered through the PreProcessMessage method. Recursive infinite loop risk when working with windows control variables, due to updates and such

//Count Frames
FrameTick3++;

//Update clock text:
ClockText3.Text = DateTime.Now.ToString("hh:mm:ss.fff tt");

//Proceed with regular message handling
base.WndProc(ref m);
}

private void UpdatesPerSecond()
{
//Time Calc:
StopTime = DateTime.Now;
TimePassed = StopTime - StartTime;
if (TimePassed.Seconds >= 1)
{
//Update UPS/FPS count displays:
UPS1.Text = FrameTick1.ToString("D");
UPS2.Text = FrameTick2.ToString("D");
UPS3.Text = FrameTick3.ToString("D");

//Reset UPS count:
FrameTick1 = 0;
FrameTick2 = 0;
FrameTick3 = 0;
StartTime = DateTime.Now;
TimePassed = TimeSpan.Zero;
}
}

//Event handeler for exit button
private void btnExit_Click(object sender, EventArgs e)
{
//Detach event handler for idle event processing
Application.Idle -= HandleApplicationIdle;

//Stop the multi-threaded method
KeepLooping = false;
}
}
}

更新 2: 我想我会在采纳下面 Zer0 的建议(这是正确的!)后添加更新,以防有一天它可能对其他人有所帮助。正如他所提到的,嵌入在每个处理方法中的用于 GUI 更新的 Invoke 方法导致了瓶颈和奇怪的结果。结果好多了!!!

我将所有 GUI 更新重新定位到计时器功能,应用程序的结果如下所示:

enter image description here

最后的代码,以防其他人想自己测试:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Runtime.InteropServices; //for DllImport of c++ functionality for win msg processing
using System.Globalization;

namespace Clock1
{
public partial class VosClockForm : Form
{
//Timer for GUI updates:
System.Windows.Forms.Timer MainTimer = new System.Windows.Forms.Timer();

//General app variables
System.Threading.Thread t;
bool KeepLooping = true;
bool ExitApplicationNow = false;
DateTime StartTime = DateTime.Now;
DateTime StopTime = DateTime.Now;
TimeSpan TimePassed = new TimeSpan();
CultureInfo StrictCulture = CultureInfo.InvariantCulture;
long MethodTick1 = 0;
long MethodTick2 = 0;
long MethodTick3 = 0;
long FrameTick = 0;

//Imported for use of c++ win msg variables

//For message handler version
[StructLayout(LayoutKind.Sequential)]
public struct NativeMessage
{
public IntPtr Handle;
public uint Message;
public IntPtr WParameter;
public IntPtr LParameter;
public uint Time;
public Point Location;
}
[DllImport("user32.dll")]
public static extern int PeekMessage(out NativeMessage message, IntPtr window, uint filterMin, uint filterMax, uint remove);

//Imported for winproc loop version processing
[DllImport("user32.dll")]
public static extern int SendNotifyMessage(IntPtr hWnd, int Msg, IntPtr wParam, IntPtr lParam);

//Primary form loop
public ClockForm()
{
//Loaded on program start

//Init components for C# environment
InitializeComponent();

//If overriding winproc based painting, need to tell system we are doing the drawing
//SetStyle(ControlStyles.UserPaint | ControlStyles.AllPaintingInWmPaint, true);

//Multithreaded method initialization:
t = new System.Threading.Thread(MainLoop);
t.Priority = System.Threading.ThreadPriority.Normal;
t.Start();

//Application idle event handling allocation:
Application.Idle += HandleApplicationIdle;

//Start the MainTimer for updates:
MainTimer.Tick += new EventHandler(MainTimerEvent);
MainTimer.Interval = 15; //15 ms = 67 fps max, may need to tweak this a bit!
MainTimer.Start();
}

//Multithreading method primary func (don't post GUI updates using Invoke here as it causes a huge bottleneck, holding up the mainloop to wait for a graphics update
public void MainLoop()
{
while (KeepLooping == true)
{
//Count Frames
MethodTick1++;
}

//When KeepLooping is no longer true, exit:
ExitApplicationNow = true;
}

//Application idle event handling method primary func
void HandleApplicationIdle(object sender, EventArgs e)
{
while (IsApplicationIdle())
{
//Count Frames
MethodTick2++;
}
}

//Application idle event handling method secondary func
bool IsApplicationIdle()
{
NativeMessage result;
return PeekMessage(out result, IntPtr.Zero, (uint)0, (uint)0, (uint)0) == 0;
}

//Winproc method (override for the WndProc handler): All messages are sent to the WndProc method after getting filtered through the PreProcessMessage method. Recursive infinite loop risk when working with windows control variables, due to updates and such
//Do NOT update any GUI elements in this function!
protected override void WndProc(ref Message m)
{
//Count Frames
MethodTick3++;

//Check for application exit (consider moving this to whatever function ends up handling periodic GUI updates and such)
if (ExitApplicationNow == true)
{
//Detach event handler for idle event processing
Application.Idle -= HandleApplicationIdle;

//Exit:
Application.Exit();
Environment.Exit(1);
}

//Proceed with regular message handling
base.WndProc(ref m);
}

private void UpdateInterface()
{
//Update Clock:
lblClock.Text = DateTime.Now.ToString("hh:mm:ss.fff tt");

//GUI Update tick:
FrameTick++;

//UPS Calc:
StopTime = DateTime.Now;
TimePassed = StopTime - StartTime;
if (TimePassed.Seconds >= 1)
{
//Update GUI FPS
lblGuiFps.Text = FrameTick.ToString("###,###,###,###", StrictCulture);

//Update UPS/FPS count displays:
lblUPS1.Text = MethodTick1.ToString("###,###,###,###", StrictCulture);
lblUPS2.Text = MethodTick2.ToString("###,###,###,###", StrictCulture);
lblUPS3.Text = MethodTick3.ToString("###,###,###,###", StrictCulture);

//Reset UPS count:
MethodTick1 = 0;
MethodTick2 = 0;
MethodTick3 = 0;
FrameTick = 0;
StartTime = DateTime.Now;
TimePassed = TimeSpan.Zero;
}
}

//Event handeler for exit button
private void btnExit_Click(object sender, EventArgs e)
{
//Stop the multi-threaded method
KeepLooping = false;
}

//Timer Event Handling: GUI Updates and related updates that don't require more than 60fps
private void MainTimerEvent(Object myObject, EventArgs myEventArgs)
{
//Call frame update
UpdateInterface();
}
}
}

最佳答案

这三种方法中最快的是独立的Thread

您的单独线程基准测试存在根本缺陷。它是 ClockText.Invoke(MI)。原因是 Invoke 在 GUI 更新完成之前阻塞。这非常慢。

如果您不想阻止,可以改用 BeginInvoke,但我也不推荐这样做。

当谈到高性能时,我会强烈避免“推送”到 GUI 线程(通过 InvokeBeginInvokeSynchronizationContext 等...)。这些都以相同的方式运行 - 它们使用 Windows 消息队列,就像您的 WndProc 解决方案所做的那样。你可以淹没那个队列。这是响应用户输入和无数其他事物(如绘画消息)的同一个队列。

相反,我建议从 GUI 线程“拉取”。有很多不同的方法可以做到这一点,所以我不能在不了解更多的情况下给你写一些具体的东西。但一个简单的方法是使用 System.Windows.Forms.Timer 并经常刷新 GUI。

关于c# - 游戏逻辑的最快迭代方法 : thread, 空闲处理程序、winproc 或其他我不知道的东西?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/25690359/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com