using System;
using System.Diagnostics;
using System.Windows.Forms;
using Amphora;
using Blacklight.Core;
using Blacklight.Core.Cameras;
namespace Konis.Testbed
{
public partial class Testbed : Form
{
// Windows runs its own message loop while a window is being dragged or resized, so Application.Idle stops
// firing and the next frame would otherwise get the whole drag as one step.
private static readonly TimeSpan LongestFrame = TimeSpan.FromMilliseconds(100);
private readonly Stopwatch clock = Stopwatch.StartNew();
private readonly ParticleDemo demo;
private TimeSpan? lastFrame = null;
private double speed = 1.0;
public Testbed()
{
InitializeComponent();
demo = new ParticleDemo(viewport.DrawableSurface, viewport.Camera, new Rand((ulong)DateTime.UtcNow.Ticks));
// The left button spawns, so the camera drags with the middle one.
viewport.Controller.Options.DragNavigateButton = PointerButtons.Middle;
viewport.InputRouter.AddLowestPriority(viewport.Controller);
viewport.InputRouter.AddHighestPriority(
new ClickSpawner(demo, viewport.DrawableSurface, viewport.Camera));
}
///
/// Gets or sets whether the form closes itself once it has drawn a frame.
///
public bool ExitAfterFirstFrame { get; set; } = false;
///
/// Whether the pump has nothing waiting, so the loop can run another frame rather than yield to it.
///
private static bool IsApplicationIdle()
{
return !NativeMethods.PeekMessage(
out NativeMethods.QueuedMessage _,
window: IntPtr.Zero, // Messages from any window on this thread.
filterMin: 0, filterMax: 0, // Every message.
remove: NativeMethods.PM_NOREMOVE);
}
private void Frame()
{
TimeSpan now = clock.Elapsed;
TimeSpan elapsed = now - (lastFrame ?? now);
lastFrame = now;
if (elapsed > LongestFrame)
{
elapsed = LongestFrame;
}
Duration real = Duration.FromSeconds(elapsed.TotalSeconds);
demo.World.Update(new FxTime(real, real * speed, speed));
demo.Draw();
if (ExitAfterFirstFrame)
{
Application.Idle -= ApplicationIdle;
Close();
}
}
// The loop runs flat out and lets the vsync inside the frame's submit pace it, so the pump only gets a turn
// once something is actually waiting on it.
private void ApplicationIdle(object sender, EventArgs e)
{
try
{
while (IsApplicationIdle())
{
Frame();
}
}
catch (GraphicsDeviceFailedException exception)
{
// The device can't draw again, so there's nothing to resume.
Application.Idle -= ApplicationIdle;
MessageBox.Show(this,
exception.Message + "\n\nThis is a problem with the graphics driver or hardware, not the testbed.",
"Graphics driver failure", MessageBoxButtons.OK, MessageBoxIcon.Error);
Close();
}
}
protected override void OnLoad(EventArgs e)
{
base.OnLoad(e);
// A run that closes after one frame spawns every effect first, so that frame draws particles.
if (ExitAfterFirstFrame)
{
for (int i = 0; i < Effects.All.Length; i++)
{
demo.EffectIndex = i;
demo.Spawn(new Azimuth.Vector3(0, 0, 0), preWarmSeconds: 1f);
}
demo.EffectIndex = 0;
}
Application.Idle += ApplicationIdle;
}
protected override void OnFormClosing(FormClosingEventArgs e)
{
Application.Idle -= ApplicationIdle;
base.OnFormClosing(e);
}
}
}