A Question of Hardware

Someone asked me what sort of hardware I am going to be running this little project on. This is good news: Someone is reading what I write! Always makes me happy, and makes me keen to answer. So, here goes...

In all simplicity, most of the heavy lifting will be done by FSX/ESP for rendering the outside views from the cockpit and doing the physics calculations that are needed to model flight. I am most likely not hooking up extra hardware (interface boards, that is) to the box that runs FSX/ESP. This is despite the fact that I wrote a small app to poll the joystick at a certain frequency (it varied in my tests from 10 Hz to 60 Hz) just to see what the cost of such an app would be in terms of resource usage. It was barely noticable! I don't know if that is because I used CCR or not, but one thing is sure: CCR made it extremely easy to write. I ran this app on a dual core. I forget what the specs of that box is, but it is a fairly old Dell Dimension 9100 with 2 Gb RAM.

Continuing this train of thought: I think the boxes that will actually be running the interface software will be the cheapest boxes I can find online. I looked at assembling such a box from spare parts found online, and the tally came out to be about $200. With prices like that, it seems that the over all cost of this little project will come from knobs and switches that make it look like the real thing, not from the computers that do the hardware I/O (with the exception of the main FSX/ESP computer, which I hope will be a beast of dimensions).

The code that runs the polling of the joystick is given below. Again, the Interleave arbiter is your friend (or, mine at least. You could potentially rewrite this using other constructs, I have just come to love the Interleave arbiter with all my soul).

internal delegate void TimerDelegate(object state);

internal sealed class SchedulerCompletion

{

}

internal class FBWTaskScheduler : IDisposable

{

private class TimerJobInfo

{

public double Frequency

{ get; set; }

public TimerDelegate TimerFunc

{ get; set; }

}

private Dispatcher dispatcher;

private DispatcherQueue taskQueue;

private Port<DateTime> timerPort;

private TimerJobInfo timerJobInfo;

private PortSet<SchedulerCompletion, Exception> terminatingPort;

private bool hasTimerJob;

private static object syncLock = new object();

public TaskScheduler()

{

this.timerPort = new Port<DateTime>();

this.terminatingPort = new PortSet<SchedulerCompletion, Exception>();

this.dispatcher = new Dispatcher(0, "TaskScheduler");

this.taskQueue = new DispatcherQueue("TaskQueue", dispatcher);

this.taskQueue.Suspend();

}

public void EnqueueTimerJob(double frequency, TimerDelegate timerFunc)

{

lock (syncLock)

{

if (hasTimerJob)

{

throw new InvalidOperationException("Only one timer job can be enqueued");

}

hasTimerJob = true;

if (null == this.timerJobInfo)

{

this.timerJobInfo = new TimerJobInfo();

this.timerJobInfo.Frequency = frequency;

this.timerJobInfo.TimerFunc = timerFunc;

}

}

}

public void Stop()

{

this.terminatingPort.Post(new SchedulerCompletion());

}

public void Execute()

{

TeardownReceiverGroup failureOrCompletionGroup = new TeardownReceiverGroup(

Arbiter.Receive<Exception>(false, terminatingPort, exception =>

{

Console.WriteLine("Got exception " + exception.Message);

}),

Arbiter.Receive<SchedulerCompletion>(false, terminatingPort, contextCompletion =>

{

Console.WriteLine("Completed context");

}));

ExclusiveReceiverGroup exclusiveGroup = new ExclusiveReceiverGroup();

ConcurrentReceiverGroup concurrentGroup = new ConcurrentReceiverGroup(

Arbiter.Receive<DateTime>(true, this.timerPort, dateTime =>

{

if (null != this.timerJobInfo)

{

try

{

timerJobInfo.TimerFunc(dateTime);

}

catch (Exception e)

{

this.terminatingPort.Post(e);

}

this.taskQueue.EnqueueTimer(TimeSpan.FromMilliseconds(1000 / timerJobInfo.Frequency), this.timerPort);

}

}));

Arbiter.Activate(this.taskQueue,

Arbiter.Interleave(failureOrCompletionGroup,

exclusiveGroup,

concurrentGroup));

if (null != this.timerJobInfo)

StartTimerJob();

if (this.taskQueue.IsSuspended)

this.taskQueue.Resume();

}

private void StartTimerJob()

{

this.taskQueue.EnqueueTimer(TimeSpan.FromMilliseconds(1000 / timerJobInfo.Frequency),

timerPort);

}

#region IDisposable Members

public void Dispose()

{

taskQueue.Dispose();

dispatcher.Dispose();

}

#endregion

}

There should be no magic in the code above. The code that you want to execute at the give frequency is put in the TimerDelegate instance , and thing work from there. Note, however, that this code does not guarantee that the delegate will be invoked on the tick as specified by the frequency: If your code in the TimerDelegate takes a long time to run, all that will happen is that there will be a certain number of ticks between invocations. For my usage, that is close enough. Now, finally, all the legalese: This code is provided as is. It may not be the most efficient, elegant or cool. If you have any questions, please let me know.