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.

Comments

  • Anonymous
    December 09, 2008
    I don't understand the posting to the terminationport in the TeardownReceiverGroup, Receive<Exception>. I think that I mis something but it looks recursive, however the port is disconnected from the receiver after the delegate is executed. So where does the post "this.terminatingPort.Post(exception);" go ?? Are there two ports the "terminatingport" and the "this.terminatingport" ?? Could you use the Multi Media Timer to post messages. This would give less jitter, because you can make the timer persistent (TIME_PERIODIC). You can then use a DispatcherQueue that does trottling to limit the frequency.

  • Anonymous
    December 09, 2008
    I think that was just a simple bug. However, I don't think it will have much effect since the TeardownReceiverGroup is guaranteed to be the last handler to run. It also unregisters all other receivers, so I guess I was just posting to a port that would never be retrieved from. If that was not the case, I think you are right that I would have a recursive situation on my hands. I have not thought about using any other timers, really. I wanted to figure out how a timer is modelled in CCR. Further, for my use, the strictness in terms of the period between two consequtive polls is not that important. It seems, though, to be a good idea to use the MM timer. That said, I have never used it before, and am not sure that there is a non-interop way of using it from managed code. Another point: I had my TeardownReceiverGroup handle exceptions in such a way that if an exception goes unhandled from the TimerDelegate, the scheduling of tasks stop (eventually). I have updated the code such that this can occur.