Up the downstair

…or there and back again…see how far it is

Optimizing Paint Messages via Conflation on Eclipse RCP/SWT

Recently I have been writing a basket trading application from scratch on my own for a client investment bank. It was a requirement for the UI to be written in Eclipse RCP and thus SWT. Because of the nature of the application it gets alot of messages that require UI updates in terms of price and FX rate changes but also on the back these changes I do alot of calculations that themselves require UI updates. I have incorporated a number of optimaization techniques but one thing has made all the difference. It occured to me that an easy way to conflate messages (and thus paint messages) was to leverage the Display.asyncExec mechanism. SWT developers will be aware that this is the standard way for background threads to update the UI thread where all the painting occurs. So it would be a good idea if the final queue of paint messages only had an optimal set of messages. In other words within a given period of time (between paint events, or an iteration of the message dispatch loop in the message pump) only the ‘latest’ message for a given UI element is present, so in simple terms if a message that will update a price gets updated 10 times by a background thread in between paint events then we are only interested in the 10th – quite apart from the fact that the blur created from the other 9 would be meaningless to the user. Note that I’m not talking about any enforced time events simply the time it takes the underlying window paint messages to be processed in the message queue.

 

This is how a normal SWT message from a background thread finds it way to the UI thread :-

 

getRealm().asyncExec(new Runnable() 

{

@Override

public void run()

{

// update your UI here.

}

});

 

Note – getRealm is just a local helper function of mine for running this nicely alongside the Eclipse data binding objects.

 

The Runnable that gets created here isn’t a new thread instance, or called by one. It’s simply a placeholder for your code to be executed by the underlying systems when it is ready to and of course by the appropriate thread. It occured to me that a savinf could be made in re-using Runnable objects something like this:-

 

public class PriceRunnable implements Runnable

{

private Object syncLock = new Object();

private boolean updateLatch = true;

public PriceRunnable()

{

}

public void update(IQuote q)

{

synchronized(syncLock){this.quote = q;}

}

@Override

public  void run()

{

// Your UI code goes here

setUpdateLatch(true);

}

 

public boolean isUpdateLatchOpen()

{

synchronized(syncLock) {return updateLatch;}

}

 

public void setUpdateLatch(boolean updateLatch)

{

synchronized(syncLock) {this.updateLatch = updateLatch;}

}

}

 

Fig. 1.

 

So when we call asyncExec we are going to pass instances of this object rather than create a new runnable every time, but that in itself is not much of a saving, we need some way to decide which Runnable instance to pass. I use an observer pattern to subscribe to events (the aforementioned price updates) so something has to be the entry point for processing those events – it’s that that will decide which Runnable object instance to use. Have a look at this – hopefully it will become clearer. Please note that runnablePriceMap is simply a map of a key (in this case an industry code that uniquely identifies an instrument- which has a number of attributes one of which is the price. So we need to have a list of these (probably from static data) – simply enumerate those keys and create an empty PriceRunnable per key (‘key’ in the example shown). 

 

 

public class InstrumentPriceSourceListener implements PriceSourceListener

{

private Map<String, InstrumentPriceRunnable> runnablePriceMap;

private Realm realm;

private Object syncLock = new Object();

public InstrumentPriceSourceListener(Map<String, InstrumentPriceRunnable> runnableMap,

Realm realm)

{

super();

this.runnablePriceMap = runnableMap;

this.realm = realm;

}

 

@Override

public void processPriceUpdate(String name, String key, final IQuote quote)

{

PriceRunnable runner = runnablePriceMap.get(key);

synchronized(syncLock){runner.update(quote);}

if(runner.isUpdateLatchOpen())

{

realm.asyncExec(runner);

runner.setUpdateLatch(false);

}

}

}

 

Fig. 2.

 

So we have an interface (not shown) that has a processPriceUpdate member, this listener will have been registered with the appropriate provider and this effectively is the callback. The interesting part is the 

 

if(runner.isUpdateLatchOpen())

{

    realm.asyncExec(runner);

    runner.setUpdateLatch(false);

}

 

…section. Now to explain it… The listener receives a notification into the processPriceUpdate method, inside here we need to update the UI somehow, but we also want it to conflate. So when a message arrives we look up which runnable to use based on some key information we get in the message, then simply call update() on the runnable – (Fig 1) all this does – in a thread safe way is update the member variables inside the runnable instance. This is where the latch comes in. A latch is like a gateway if it’s open all can pass if it’s shut no-one can enter. Note that in Fig 1 the latch is set true on creation (open) so the listener in Fig 2 in the isLatchOpen call succeeds – and asyncExec is called and the latch is set false – closing the door. Now, more and more updates come in and the runnable object gets updated through the update method effectively maintaining the latest value for us; then when the system can manage it the (Fig 1) run() method is called on the UI thread and the latest value would be used in your ‘update the UI code’ and then the latch is opened again – thus enabling further calls to asyncExec.

 

In conclusion we have saved the creation of lots of runnables but more importantly controlled the number of objects requesting UI interaction and thus paint messages, we effectively set a ‘paint me’ flag and update the value (potentially) multiple times until the system can catch up with us.

 

This code was written using Java 6 update 4 on Eclipse 3.3.1.1


February 7, 2008 - Posted by delliman | Eclipse RCP | | No Comments Yet

No comments yet.

Leave a comment