我正在用C#开发一个Chip-8仿真器,我几乎已经完成了几乎所有方面,但我仍然想知道仿真器的速度控制。
我现在正在做的是假设我每秒获得60帧,我使用一个以下列方式触发1/60秒的计时器(伪代码):
timer_ticked()
{
for(int i = 0; i < Settings.CyclesPerFrame; i++)
{
EmulateCycle();
}
if (IsDrawFlagSet)
{
DrawGraphics();
}
}
我正在使用一个名为microtimer http://www.codeproject.com/Articles/98346/Microsecond-and-Millisecond-NET-Timer的高分辨率计时器,我相信计时器不会等待timer_ticked完成以触发下一个周期(fon实例,创建一个新线程)而我是由于尝试使用GDI绘制到窗口(使用control.GetGraphics()方法),因此线程和表单存在问题似乎是线程安全的,但尝试创建SDLDotNet方法(仅作为示例)并不存在。
您认为哪种方法最好控制模拟器的速度而不会陷入计时器线程疯狂?
PS:您可以在GitHub中找到模拟器的源代码:https://github.com/AlFranco/C8POC
谢谢!
答案 0 :(得分:0)
如果在完成之前再次调用tick方法,则问题不在于您的计时器。这是因为处理时间超过16.6毫秒。获得更好的计时器并不能解决您的问题。
也就是说,你可以通过几种方式防止重入。
您可以在输入回调时禁用计时器,并在完成后重新启用它。这将阻止多次调用:
timer_ticked()
{
timer.Enabled = false;
// do stuff here
timer.Enabled = true;
}
请注意,这并不能为您提供完美的16.6毫秒滴答频率。相反,启用计时器后,下一个刻度将发生在16.6 ms(大约)。您的实际周期为16.6 ms加上处理所需的时间很长。
唯一一次失败的是,如果在下一个滴答发生之前没有调用timer_ticked
方法。
如果您想保证无法获得并发滴答,可以使用System.Threading.Timer
并将其设置为一次性(无定期信令)。例如:
Timer myTimer = new Timer(timer_tick, null, 16, -1);
最后一个参数中的-1
告诉它不是周期性计时器。它会发射并停止。
然后,在你的计时器中打勾:
timer_tick()
{
// do stuff
// restart the timer
myTimer.Change(16, -1);
}
如果处理程序仍处理上一个勾号,则无法轻易告诉计时器不发出勾号。但是,您可以阻止计时器滴答处理程序在后续滴答中执行任何操作。您只需使用Monitor
:
private object timerLock = new object();
timer_ticked()
{
if (!Monitor.TryEnter(timerLock))
return;
try
{
// do stuff here
}
finally
{
Monitor.Exit(timerLock);
}
}
这种解决方案的问题在于,如果您的计时器设置为16毫秒且处理程序需要17毫秒,那么您的有效更新速率将是每32毫秒一次,因为第二个滴答基本上被忽略。你最好用一次性定时器交易。
另一种可能性是使用Stopwatch
来计算处理程序所需的时间,并从下一个延迟期中减去该值:
timer_ticked()
{
var sw = Stopwatch.StartNew();
// do stuff
timer.Change(16-sw.ElapsedMilliseconds, -1);
}
但它并不那么简单。如果处理程序执行此操作所需的时间超过16毫秒,则最终会出现负延迟。所以你需要:
var delayTime = Math.Max(0, 16 - sw.ElapsedMilliseconds);
timer.Change(delayTime, -1);
但是,如果您的处理程序经常花费的时间超过计时器延迟,那么这对您没有帮助。您要么必须降低计时器频率(即延迟更长时间),要么优化处理代码。