Skip to content

2007

Time Monitor example

There was a request for a Time Monitor app that would show splits and the lap time including delta's. Currently LFS, shows you your splits and laps, but only briefly and without any information about how it compares to the previous lap. But with the LFS button system, custom HUD elements can now be created relatively simply. Really, "Button" is a misnomer. It's really a label control that can be set to clickable.

I figured the time monitor would be a good example to use the RaceTracker class I wrote a couple of days ago and exercise the button system to display the data gathered by the tracker. The resulting app, is a relatively simple winforms sample (the UI exists purely for configuration) that produces LFS stats like this (click image for full screenshot):

The UI for the app itself just lets the user specify how to connect to LFS

The app is not meant for LFS servers, but as a local Aid for clients. It connects to a client instance and displays the buttons locally, according to the currently viewed player Id. I.e. it switches the hud as you tab through the players. It won't start displaying anything until it hits a first split (i.e. if you just connected and the viewed player has already past the first split, no statistics for the player will show up until the next split 1 pass.

The code is a Visual Studio Solution, created in VS 2005, although it should work fine in Visual Studio Express. It can be downloaded from here. It's written in C# and uses the LFSLib.NET library for InSim connectivity. It's not meant to be an App, and you won't get any support from me for the binary. You can run it as is, but it's meant to show how LFSLib.NET can be used for tracking and creating buttons. As such, this is example code and completely free of any license (LFSLib.NET is still governed by the GPL, though). Use it however you wish.

OutGauge Monitor Windows App example

I had a request for an example of Win Forms with OutGauge. This is a very simple project that illustrate how to quickly wire up output from OutGauge to UI. The only tricky bit for novices is the whole Invoke business. I.e. updates from outgauge come from it's listener thread. So you cannot use the event handler for outgauge to directly update your UI, or you'll get Cross-Thread exceptions. The solution is the common Invoke Required boilerplate:

private void outGaugeHandler_Updated(object sender, Gauge gauge)
{
  // this code is required for Windows Forms, since our OutGauge update
  // comes from another thread and we can only perform form updates on
  // the UI thread
  if (this.InvokeRequired)
  {
    BeginInvoke(new OutGaugeHandler.GaugeEvent(outGaugeHandler_Updated), sender, gauge);
    return;
  }

  // now we update the form with the new data

  // text boxes
  timeTextBox.Text = gauge.Time.ToString();
  carTextBox.Text = gauge.Car;

  [...]
}
The resulting App, has a single window that refreshes OutGauge updates at the interval specified in the Live For Speed cfg.txt.

The Solution for this project is for Visual Studio 2005, but should work just fine in Visual C# Express. The zip of the project can be found here

Update: LFSLib.NET 0.14b has a bug where an OutGauge ID 0 zero would cause an exception. You can avoid this by using an OutGauge ID higher than 0 or wait for 0.15b to be released. Same thing is true for OutSim.

Cross-thread data access

Continuing on my recent asynchronous UI adventures, my background object tried to access the SelectedItem of a ComboBox and I came crashing down with the dreaded Cross-Thread exception. At this point most of my code has the if(InvokeRequired) boilerplate down....

And every time i cut-and-paste that snippet and tweak if for the current method, I again wonder why the UI framework can't do some Auto-Invoking magic. But I digress.

...The scenario this time was one of data access to the plain, call BeginInvoke and return approach didn't apply. This was a scenario I hadn't yet seen. Unlike normal delegate BeginInvoke, you don't provide a callback for the EndInvoke. Control.BeginInvoke works differently, but really, it also works simpler. It's just that it's not really pointed out on MSDN how the Begin and End fit together in the scope of a Control. The other issue was that I was accessing a property, so there wasn't a simple delegate for invoking it. Instead I created a simple wrapper Accessor Method for ComboBoxen:

public delegate object GetSelectedItemDelegate(ComboBox control);

public object GetSelectedItem(ComboBox control)
{
  if (this.InvokeRequired)
  {
    IAsyncResult ar = BeginInvoke(new GetSelectedItemDelegate(GetSelectedItem), control);
    return EndInvoke(ar);
  }
  return control.SelectedItem;
}

A simple race tracking class for LFSLib.NET

Continuing in providing some usage examples for LFSLib.NET, I've created a very simple race tracking class. I figure it covers a lot of ground, since most people want to keep track of race progress, and it does show the basic use of the InSimHandler.

This class only tracks players entering and leaving and reports their split and lap times. It ignores player renames and other race information, and just spits that info out to the console. There is a static RaceTracker.Test() method that's just to be able to run the class from your Main, i.e. it's not how you'd likely use it in your actual tracking code.

using System;
using System.Collections.Generic;
using System.Text;
using FullMotion.LiveForSpeed.InSim;
using FullMotion.LiveForSpeed.InSim.EventHandlers;
using FullMotion.LiveForSpeed.InSim.Events;

namespace FullMotion.LiveForSpeed
{
  /// <summary>
  /// Simple Race Tracking example. Keeps track of player names
  /// and reports the split and lap times for each player
  /// </summary>
  public class RaceTracker
  {
    /// <summary>
    /// Just a static test call for running RaceTracker against a local LFS instance
    /// </summary>
    public static void Test()
    {
      RaceTracker tracker = new RaceTracker("127.0.0.1", 30000);
      tracker.Start();
      Console.WriteLine("Press Enter to Exit");
      Console.ReadLine();
      tracker.Stop();
    }

    InSimHandler handler;

    // state variables to make sure we don't start and stop
    // the handler multiple times
    bool started = false;
    bool running = false;

    // keep track of player names, since race tracking only gives us IDs
    Dictionary<byte, string> players = new Dictionary<byte, string>();

    /// <summary>
    /// Set up an insim handler for tracking basic race info
    /// </summary>
    /// <param name="host"></param>
    /// <param name="port"></param>
    public RaceTracker(string host, int port)
    {
      handler = new InSimHandler();
      handler.Configuration.LFSHost = host;
      handler.Configuration.LFSHostPort = port;
      handler.Configuration.UseTCP = true;
      handler.RaceTrackPlayer 
        += new RaceTrackPlayerHandler(handler_RaceTrackPlayer);
      handler.RaceTrackPlayerLeave 
        += new RaceTrackPlayerLeaveHandler(handler_RaceTrackPlayerLeave);
      handler.RaceTrackPlayerSplitTime 
        += new RaceTrackPlayerSplitTimeHandler(handler_RaceTrackPlayerSplitTime);
      handler.RaceTrackPlayerLap 
        += new RaceTrackPlayerLapHandler(handler_RaceTrackPlayerLap);
    }

    /// <summary>
    /// Connect to LFS and start tracking
    /// </summary>
    public void Start()
    {
      if (started)
      {
        throw new InvalidOperationException("RaceTracker cannot be started multiple times");
      }
      handler.Initialize();
      started = true;
      running = true;

      // make sure we get all players in the race
      handler.RequestPlayerInfo();
    }

    /// <summary>
    /// Close down the connection
    /// </summary>
    public void Stop()
    {
      if (running)
      {
        handler.Close();
        running = false;
      }
    }

    private void handler_RaceTrackPlayer(InSimHandler sender, RaceTrackPlayer e)
    {
      if (!players.ContainsKey(e.PlayerId))
      {
        players.Add(e.PlayerId, e.Playername);
        Console.WriteLine("Player joined: {0} ({1})", e.Playername, e.PlayerId);
      }
    }

    private void handler_RaceTrackPlayerLeave(InSimHandler sender, RaceTrackPlayerLeave e)
    {
      Console.WriteLine("Player left: {0} ({1})", players[e.PlayerId], e.PlayerId);
      players.Remove(e.PlayerId);
    }

    void handler_RaceTrackPlayerLap(InSimHandler sender, RaceTrackPlayerLap e)
    {
      Console.WriteLine(
        "Player '{0}': Lap {1} @ {2}:{3}.{4}",
        players[e.PlayerId],
        e.LapsDone,
        e.LapTime.Minutes,
        e.LapTime.Seconds,
        e.LapTime.Milliseconds);
    }

    void handler_RaceTrackPlayerSplitTime(InSimHandler sender, RaceTrackPlayerSplitTime e)
    {
      Console.WriteLine(
        "Player '{0}': Split {1} @ {2:00}:{3:00}.{4:000}",
        players[e.PlayerId],
        e.Split,
        e.SplitTime.Minutes,
        e.SplitTime.Seconds,
        e.SplitTime.Milliseconds);
    }
  }
}

Sample output of this running against a race of three AIs on Aston Cadet Reverse looks like this:

Press Enter to Exit
Player joined: AI 2 (6)
Player joined: AI 3 (8)
Player joined: AI 4 (10)
Player 'AI 2': Split 1 @ 00:29.870
Player 'AI 4': Split 1 @ 00:31.280
Player 'AI 3': Split 1 @ 00:31.800
Player 'AI 2': Lap 1 @ 1:5.960
Player 'AI 4': Lap 1 @ 1:9.180
Player 'AI 3': Lap 1 @ 1:10.220
Player left: AI 2 (6)
Player left: AI 3 (8)
Player 'AI 4': Split 1 @ 00:26.410
Player left: AI 4 (10)

The Player left are me manual throwing the AIs into spectate mode.

The players dictionary could easily be changed to hold a Player class instead of string as the value slot and then more data can be tracked and even a continuous log of the race results could be kept. But this should be a place to start building a race tracking application

A generic asynchronous INotifyPropertyChanged helper

The other day I was lamenting what I consider to be an omission in Data-binding's responsibilities, i.e. the lack of invocation of an INotifyPropertyChanged PropertyChanged event on the appropriate UI thread. Instead, INotifyPropertyChanged implementors are expected to be updated only on the UI thread in order to work.

I figured I should be able to throw a proxy between the actual INotifyPropertyChanged implementor and the binding, and have this proxy be instantiated with awareness of the observing form. Thus the proxy could do the invocation on the UI thread and still let me use INotifyPropertyChanged without creating a dependency on knowing that the object was being observed by a UI element. That line of thought lead to worked as a static proxy, that I had to create by hand. Once I switched to Castle's Dynamic Proxy, it didn't work anymore, the event somehow being swallowed along the way.

I also considered looking at creating my own BindingContext but that didn't bear fruit either. It was then that I realized that I didn't need to create a proxy that matched my source object, since data binding didn't care what it bound to, as long as it had the dataMember it was being told about. I started down a path of creating a wrapper class using Reflection.Emit when I further reasoned that even the dataMember that's exposed doesn't have to match the one of the data source. This opened the door to just using dynamic property invocation and a simple generic helper class emerged:

using System;
using System.Collections.Generic;
using System.Text;
using System.ComponentModel;
using System.Windows.Forms;

namespace FullMotion.Helpers
{
  /// <summary>
  /// A helper class for creating a binding on an object that may be changed
  /// asynchronously from the bound UI thread.
  /// </summary>
  public class AsyncBindingHelper : INotifyPropertyChanged
  {
    /// <summary>
    /// Get a binding instance that can invoke a property change
    /// on the UI thread, regardless of the originating thread
    /// </summary>
    /// <param name="bindingControl">The UI control this binding is added to</param>
    /// <param name="propertyName">The property on the UI control to bind to</param>
    /// <param name="bindingSource">The source INotifyPropertyChanged to be
    /// observed for changes</param>
    /// <param name="dataMember">The property on the source to watch</param>
    /// <returns></returns>
    public static Binding GetBinding( Control bindingControl,
                                      string propertyName,
                                      INotifyPropertyChanged bindingSource,
                                      string dataMember )
    {
      AsyncBindingHelper helper 
        = new AsyncBindingHelper(bindingControl,bindingSource,dataMember);
      return new Binding(propertyName,helper,"Value");
    }

    Control bindingControl;
    INotifyPropertyChanged bindingSource;
    string dataMember;

    private AsyncBindingHelper( Control bindingControl,
                                INotifyPropertyChanged bindingSource, 
                                string dataMember )
    {
      this.bindingControl = bindingControl;
      this.bindingSource = bindingSource;
      this.dataMember = dataMember;
      bindingSource.PropertyChanged 
        += new PropertyChangedEventHandler(bindingSource_PropertyChanged);
    }

    void bindingSource_PropertyChanged(object sender, PropertyChangedEventArgs e)
    {
      if (PropertyChanged != null && e.PropertyName == dataMember)
      {
        if (bindingControl.InvokeRequired)
        {
          bindingControl.BeginInvoke(
            new PropertyChangedEventHandler(bindingSource_PropertyChanged),
            sender,
            e );
          return;
        }
        PropertyChanged(this, new PropertyChangedEventArgs("Value"));
      }
    }

    /// <summary>
    /// The current value of the data sources' datamember
    /// </summary>
    public object Value
    {
      get
      {
        return bindingSource.GetType().GetProperty(dataMember)
          .GetValue(bindingSource,null);
      }
    }
    #region INotifyPropertyChanged Members
    /// <summary>
    /// Event fired when the dataMember property on the data source is changed
    /// </summary>
    public event PropertyChangedEventHandler PropertyChanged;

    #endregion
  }
}

To use this class, simply replace this:

myLabel.DataBindings.Add("Text", myDataSource,"MyProperty");

with this:

myLabel.DataBindings.Add(
  AsyncBindingHelper.GetBinding(myLabel, "Text", myDataSource,"MyProperty") );

The one tricky thing about this is that it may cause uncollectable references. We're creating an instance of AsyncBindingHelper behind the scenes that sets up an event handler on our binding source. Which means we can't get rid of that event subscription. Theoretically, once the binding control goes out of scope, the helper should also go out of scope, relinquishing the reference it holds on the binding source.

The other thing that would be simple to do is to turn this helper into an Extension Method on INotifyPropertyChanged, once I move my code over to Orcas. That way creating an asynchronous binding on an instance of INotifyPropertyChanged would become even cleaner and straight forward.

LFSLib.NET 0.14b released

UPDATE: If you downloaded the files before 12pm PST, then you may have a binary with a bug. Please get the current one.

This is is a minor bugfix/clean-up release. There were two bugs that were the result of TCP connections. In addition, I took the opportunity to clean up some naming conventions and odds and ends. This should be a non-breaking upgrade, although a couple of members have been marked obsolete and will generate warnings until replaced with their successors.

Full details are in the release notes.

I've added a pure binary release of the lib, so you don't have to do digging through the output folders anymore, if all you want is a dll to reference. The binary release can be found here.

All links, docs, etc are at lfs.fullmotionracing.com

INotifyPropertyChanged and Cross-thread exceptions

I'm currently reading Adam Nathan's WPF Unleashed. It's a good read and has me ready for doing some serious WPF work. However, the things I have on my plate are a already Windows Forms and can't really take a late in the project UI architecture change, so my production use of WPF will have to wait a little longer. I originally thought WPF was horribly over-engineered, but going through Adam's book, the decisions that led to the sometimes odd APIs make sense. It's clear that toolability of Xaml, separation of logic and code and allowing the greatest amount of design without any code are what led to the cumbersome manual C# syntax. But once you mix in Expression Blend, you won't be doing any of those nasty things unless you're writing your own custom controls. Ok, this is a lovely tangent, but hardly the subject of this entry...

While reading I came across INotifyPropertyChanged. Since it's meant for binding in general, I thought this might be a perfect fit for a design issue I was having on a WinForms project. Basically, I have an object that manages a native process and can get manipulated via remoting. The UI in the program really just exists for status updates on this management object. The solution for keeping the UI in sync was either a timer that polled the object's properties or create an event for each property change, manually subscribe to them in the form and update the UI that way. However, INotifyPropertyChanged provided a simple methodology for tracking changes in all objects and let the implementing object be used for data-binding. I'm usually against code that uses strings to reference object members, like data-binding is wont to do, since it falls outside of compile-time checking and is liable to get out of sync in refactoring, but data-binding is convenient and so prevalent in .NET, that I decided to use it for this.

Implementing INotifyPropertyChanged on my object was dead simple and the syntax for binding my labels to property changes was even easier. Perfect, another process simplified. Or so I thought. What happened next is what I personally would describe as a bug or at least mis-feature in data binding, but at least according to the docs, it's by design.

INotifyPropertyChangeddoes not work for asynchronous operations

What happened was this: I made changes to my object via the remoting proxy and my client UI died with an InvalidOperationException(Cross-thread operation not valid...). On it's face this is straight forward: My object is on a background thread, being manipulated by remoting. My UI is on the UI thread, so tweaking the UI by the background thread is a no-no. This is confirmed by the MSDN documentation which says:

If your data source implements the INotifyPropertyChanged and you are performing asynchronous operations you should not make changes to the data source on a background thread. Instead, you should read the data on a background thread and merge the data into a list on the UI thread.

That to me is bad advice and a lazy way of saying that data binding doesn't do its due diligence on binding operations. Why? Well, the purpose of something like INotifyPropertyChanged is Separation of Concerns, decoupling the observer of the property data from the observable object. But what the above says is that the observed object is responsible for knowing how it might be observed.

The logical thing is that your INotifyPropertyChanged object neither knows nor should care that it's data may be used to update the UI on another thread. The party that's responsible for that bit of house-keeping is the one that does the actual updating of the UI controls, i.e. data-binding. Data-binding is intimately aware of the UI object it's tied to and it's the one that isactually updating the UI in response to an incoming event. It seems logical and practical that it would do the necessary check on Control.InvokeRequired and perform said invoke. Instead telling you that your object better live on the UI thread is just not very good advice, imho.

Where does that leave us?

Well, even if the business object wanted to invoke on the UI thread, it couldn't unless it was aware of the UI thread, and that once again violates the Separation of Concerns that should exist between business logic and UI. So that leaves us with two options: a) change the way binding works to the way described above or b) put a proxy between the binding and the INotifyPropertyChanged implementor.

Since you can assign a new BindingContext to a control, a) may be an option, and it's one I'm going to investigate next. However for the time being, going down the path of b) was simpler.

I hardcoded a proxy for my business object, had it implement INotifyPropertyChanged as well and created a factory that would take both the form and the proxied object to create a new proxy. The proxy then subscribed to the proxied PropertyChanged event and used its reference to the form to make sure it was invoked on the UI thread.

{
  [...]
  public int MyProperty
  {
    get { [...] }
  }
  [...]
}

public class MyBusinessObjectProxy : INotifyPropertyChanged
{
  Control bindingControl;
  MyBusinessObject bindingSource;

  private MyBusinessObjectProxy(Control bindingControl, MyBusinessObject bindingSource)
  {
    this.bindingControl = bindingControl;
    this.bindingSource = bindingSource;
    bindingSource.PropertyChanged += new PropertyChangedEventHandler(bindingSource_PropertyChanged);
  }

  void bindingSource_PropertyChanged(object sender, PropertyChangedEventArgs e)
  {
    if (PropertyChanged != null)
    {
      if (bindingControl.InvokeRequired)
      {
        bindingControl.BeginInvoke(new PropertyChangedEventHandler(bindingSource_PropertyChanged), sender, e);
        return;
      }
      PropertyChanged(this, new PropertyChangedEventArgs(e.PropertyName));
    }
  }

  public int MyProperty
  {
    get { return bindingSource.MyProperty; }
  }

  #region INotifyPropertyChanged Members

  public event PropertyChangedEventHandler PropertyChanged;

  #endregion
}

That works beautifully, but since it's specific to my particular object that's a lot of hand-coding. This whole proxy/adapter scheme screams for a generic implementation. But now we need to intercept requests of properties we may not know exist. Back in the perl days (and most dynamic languages feature this as well) I would have just created an AUTOLOAD method that catches all calls to methods that don't exist. It didn't seem like this would exist in C#, since that violates the whole compile time checking of a static language like C#. But there is Reflection, so maybe I was wrong. However, after checking with Oren Eini, who clearly has a much greater understanding of how to do funky things with C#, it turns out that such a facility indeed does not exist.

Next up was creating a proxy object that could be cast to the original type and handle the binding calls that way. I built a prototype based on Castle's Dynamic Proxy, which proxied the properties just fine, but apparently the event subscription doesn't work, because the proxy's PropertyChanged event never acquired any subscribers. So until I can figure out what happens to events on Dynamic Proxy, I'm stuck with statically built proxies.

In the meantime, I guess I'm going to look at BindingContext to see if the problem can be solved generically at that end, while trying to figure out why Dynamic Proxy isn't working. Stay tuned.

LFSLib.NET 0.13b released

Now with full patch X goodness!

This version of LFSLib.NET takes advantage of all the new features of Live For Speed S2 0.5X (as well as the X2 test patch additions). The new version can be found here and the release notes are here. There is a new binary distro with a winforms Tester app, which is located here, but in general the tester app does not provide full coverage and will eventually be replaced with a better example and test harness. The highlights are:

  • TCP connections (more on that below)
  • Multiple InSim Connections at the same time
  • Custom Button system
  • Lots more tracking events

I've run it through its paces, but as InSim coverages is getting quite large now and I don't have an automated test suite, I cannot claim to have full test coverage. In general, if something doesn't work, assume it's LFSLib.NET, not LFS and let me know. Contact me via the email on the left, blog comments or posting about LFSLib.NET on the LFS programmer forums (which I monitor fairly consistently).

Moving the documentation to SandCastle has taken more time than I hoped. The new doc system is now a dynamic web application and provides a (IMHO) much improved interface thanks to Dave Sexton's excellent DocProject. This has required my moving of the docs off this server to a new host, so the docs and future information for LFSLib.NET from now on lives at lfs.fullmotionracing.com.

About TCP & UDP

A quick note about TCP and UDP in 0.13b. You can have up to 8 TCP connections, but you still can only have one UDP connection. When you configure the InSimHandler, set the UseTCP flag in the configuration. In general, you want to make sure that you do NOT set ReplyPort. Why? As soon as you set reply port, LFS will send RaceTrackNodelap (IS_NLP) and RaceTrackMultiCarInfo (IS_MCI) events via that port on UDP. LFSLib.NET handles that transparently, but since you've now crossed over into UDP land, you have now used up that one UDP connection that is available and things will get wonky if you connect another InSimHandler with the same settings, even though you thought you were just using TCP.

Simple OutSim & OutGauge samples

Version 0.13b is finished, just cleaning up the docs, and packaging everything (switching to sandcastle for docs kind of added a new complication). Should have that out tomorrow, I hope.

In the meantime, especially, since the Tester app I include is really just something to exercise the lib, not to learn coding from (i.e. worst winforms code patterns example), I thought i'd throw out some simple code examples until i build a proper test suite. Now, these samples, with their use of straight up static methods are also nothing to learn program design from -- they're just API illustration. These examples work with the current and new release, so they're not tied to 0.13b.

The tricky thing to note about both OutSim and OutGauge is that the cfg.txt values take precedence. I.e. you can get both sent via InSim, but not if they are already set in the configuration file. Generally, I suggest sticking to manually configuring these two and going through the dedicated handlers for both and keeping them separate from your InSim handler.

OutGauge

This little Console app, just subscribes to OutGauge being sent once a second and refreshes the terminal with the new values:

static void Main(string[] args)
{
  // Assumes the following settings in LFS cfg.txt:
  //    OutGauge Mode 1
  //    OutGauge Delay 100
  //    OutGauge IP 127.0.0.1
  //    OutGauge Port 26020
  //    OutGauge ID 1

  OutGaugeHandler outGauge = new OutGaugeHandler(26020);
  outGauge.Updated += new OutGaugeHandler.GaugeEvent(outGauge_Updated);
  Console.WriteLine("created handler");
  outGauge.Initialize();
  Console.WriteLine("initialized handlers");
  Console.WriteLine("Press RETURN to exit");
  Console.ReadLine();
  outGauge.Close();
}

static void outGauge_Updated(object sender, FullMotion.LiveForSpeed.OutGauge.Events.Gauge gauge)
{
  Console.Clear();
  Console.WriteLine("OutGauge -----------------------");
  Console.WriteLine("Time:     {0}", gauge.Time);
  Console.WriteLine("ID:       {0}", gauge.Id);
  Console.WriteLine("Car:      {0}", gauge.Car);
  Console.WriteLine("RPM:      {0}", gauge.RPM);
  Console.WriteLine("Speed:    {0}", gauge.Speed);
  Console.WriteLine("Message1: {0}", gauge.DisplayMessage1);
  Console.WriteLine("Message2: {0}", gauge.DisplayMessage2);
}

OutSim

The OutSim version is virtually the same for setup, just that the eventargs contain different data

static void Main(string[] args)
{
  // Assumes the following settings in LFS cfg.txt:
  //  OutSim Mode 1
  //  OutSim Delay 100
  //  OutSim IP 127.0.0.1
  //  OutSim Port 26010
  //  OutSim ID 1
  OutSimHandler outSim = new OutSimHandler(26010);
  outSim.Updated += new OutSimHandler.PhysicsEvent(outSim_Updated);
  Console.WriteLine("created handlers");
  outSim.Initialize();
  Console.WriteLine("initialized handlers");
  Console.WriteLine("Press RETURN to exit");
  Console.ReadLine();
  outSim.Close();
}

static void outSim_Updated(object sender, FullMotion.LiveForSpeed.OutSim.Events.PhysicsState physicsState)
{
  Console.Clear();
  Console.WriteLine("OutSim -----------------------");
  Console.WriteLine("Time:     {0}", physicsState.Time);
  Console.WriteLine("ID:       {0}", physicsState.Id);
  Console.WriteLine("X:        {0}", physicsState.PositionX);
  Console.WriteLine("Y:        {0}", physicsState.PositionY);
  Console.WriteLine("Z:        {0}", physicsState.PositionZ);
}

Moonlight at Remix 07

Miguel de Icaza just posted a long entry on a Hackathon the mono team did in the last 21 days to get some version of Moonlight ready for Remix 07 and it looks like they succeeded. Further progress information can also be found on the Moonlight site.

Next to the Silverlight 1.1 alpha which exposes the CLR, Moonlight is probably one of my favorite tech project to follow at this time. I don't even run X anywhere anymore, just using linux for servers, so I don't think i'll ever need Moonlight. But its the availability that has me excited. I am personally rooting for a web programming model that doesn't force javascript on you for client side programming, but I don't think Win/MacOS is "cross-platform" enough to establish this as a norm, rather than a specialty plug-in. I hope Moonlight tips the adoption likelihood in Silverlight's favor.

Currently, I'm mostly waiting for a Socket API to make it into Silverlight 1.1. Once that's available, I'm going to make sure that my LFSLib can run under silverlight. This would open the door for some very cool in-brower LFS admin tools. I also have an internal DirectX implementation of the LFS smx and pth formats for rendering top down views. I've been looking at porting that to WPF, since i don't required 3D and just rendering a plane at this point. If I can reduce it further to the Silverlight Xaml subset, I could easily produce a Silverlight equivalent of LFS Spectator. But I'm getting ahead of myself. Right now I just need to get full patch X support out the door for LFSLib.NET. The protocol is complete now, I just need to complete the TCP code, do some doc clean-up and testing.