Asynchrony is Everything

You may know “Position is Everything” already. And I believe you should also know “Asynchony is Everything” or “Everything is Asynchronous” in RIA development.

Asynchrony is not only very important in web application development but also vital in normal desktop applications and embed systems. Asynchronous programming may help a lot in improving user experience. In normal desktop applications, if the main thread blocks UI for its long-time-consuming computing, users will feel desperately, waiting the busy-cursor to turn into default. But if such computing is run in background, user would find other things to do, such as switching focuses or typing something.

What about the definition of Asynchronous Computing? It is not only about asynchronous loading or asynchronous remote procedure call (RPC), it is also about local calculating. One thing should be mentioned here is UI layout computing. Lots of UI need complex layout calculating. For example, rendering a web page, rendering a 3D interfaces, or rending a complex dialog box. This CPU times required is especially obvious when it’s performed in a slow computer or in a slow computing environment, such as JavaScript runtime in browsers.

Here, I concerns about asynchronous layout. In implementing Java2Script’s SWT library, there is a huge problem for us. JavaScript is so slow that it can not finish a complex layout in less than 5 seconds. Or a complex layout requires more than 1 second will freeze browser UI, which make the user experience very bad and unacceptable. In order to overcome such problems. Java2Script introduced “Asynchronous Layout”.

Asynchronous layout is about to split the layout jobs into small pieces of jobs and run them step by step. As you may know, in SWT, all widgets are placed inside some containers. And to make a layout over container, the bounds of parent container, which is a Composite widget, is calculated first. And then the bounds will be passed as constraints of its child widgets to make children layouts. In such layout algorithms, we could split the layout jobs into parent container layout and child widget layout jobs, queue these jobs, and monitor each job’s required CPU times to avoid complex layout takes more than 200ms. If layout computing takes more than 200ms, then sleep for about 50ms before calling the next layout job.

Here are some snippets about Asynchronous Layout:

Display#readAndDispatch

public boolean readAndDispatch () {
checkDevice ();
drawMenuBars ();
runPopups ();
/*
if (OS.PeekMessage (msg, 0, 0, 0, OS.PM_REMOVE)) {
if (!filterMessage (msg)) {
OS.TranslateMessage (msg);
OS.DispatchMessage (msg);
}
runDeferredEvents ();
return true;
}
return runAsyncMessages (false);
*/

if (messageProc != 0) {
return true; //already hooked, return directly
}
messageProc = window.setInterval(new RunnableCompatibility() {
private boolean messageLoop = false;
public void run() {
runPopups ();
MESSAGE[] msgs = Display.this.msgs;
if (msgs.length == 0 && messageLoop)
/**
* @j2sNative
* var layoutFinished = window["j2s.swt.shell.finish.layout"];
* if (layoutFinished != null) {
* layoutFinished ();
* }
* this.messageLoop = false;
*/
{ }
if (msgs.length != 0) {
messageLoop = true;
MESSAGE[] defered = new MESSAGE[0];

int defsize = 0;
for (int i = msgs.length - 1; i >= 0; i--) {
MESSAGE m1 = msgs[i];
if (m1 == null) {
continue;
}
m1.defer = false;
for (int j = i - 1; j >= 0; j--) {
MESSAGE m2 = msgs[j];
if (m2 != null && m2.control == m1.control
&& m2.type == m1.type) {
msgs[j] = null;
}
}

if(m1.type == MESSAGE.CONTROL_LAYOUT){
if(m1.control.parent != null && m1.control.parent.waitingForLayout){
m1.defer = true;
defered[defsize++] = m1;
}
}

}
long time = 0;

for (int i = 0; i < msgs.length; i++) {
MESSAGE m = msgs[i];

if(m != null && m.defer){
continue;
}
msgs[i] = null;
if (m != null && m.type == MESSAGE.CONTROL_LAYOUT) {
m.control.waitingForLayout = false;
if (!m.control.isVisible()) { continue; }
Date d = new Date();
Composite c = (Composite) m.control;
if(c.waitingForLayoutWithResize){
c.setResizeChildren (false);
}
if(c.layout != null){
c.layout.layout (c, (c.state & Composite.LAYOUT_CHANGED) != 0);
c.state &= ~(Composite.LAYOUT_NEEDED | Composite.LAYOUT_CHANGED);
}
if(c.waitingForLayoutWithResize){
c.setResizeChildren (true);
c.waitingForLayoutWithResize = false;
}

if (m.data != null) {
boolean[] bs = (boolean[]) m.data;
c.updateLayout(bs[0], bs[1]);
} else {
c.layout();
}
time += new Date().getTime() - d.getTime();
if (time > 200) {
for (int j = i + 1; j < msgs.length; j++) {
msgs[j - i - 1] = msgs[j];
}
int length = msgs.length - i - 1;
for(int j = 0; j < defsize; j++){
msgs[length + j] = defered[j];
}
/**
* @j2sNativeSrc
* msgs.length -= i + 1;
* @j2sNative
* a.length -= f + 1;
*/
{}
return ;
}
}
}
/**
* @j2sNativeSrc
* msgs.length = 0;
* @j2sNative
* a.length = 0;
*/
{}
Display.this.msgs = defered;
// for(int j = 0; j < defsize; j++){
// msgs[j] = defered[j];
// }
}
}
}, 100);
return true;
}

Composite#updateLayout

void updateLayout (boolean resize, boolean all) {
if (isLayoutDeferred ()) return;
if ((state & LAYOUT_NEEDED) != 0 && !waitingForLayout) {
// boolean changed = (state & LAYOUT_CHANGED) != 0;
// state &= ~(LAYOUT_NEEDED | LAYOUT_CHANGED);
// if (resize) setResizeChildren (false);
// layout.layout (this, changed);
// if (resize) setResizeChildren (true);
this.waitingForLayout = true;
this.waitingForLayoutWithResize = resize;
display.sendMessage(new MESSAGE(this, MESSAGE.CONTROL_LAYOUT, new boolean[] {resize, all}));
}

if (all) {
Control [] children = _getChildren ();
int length = children.length;
for (int i=0; i<length; i++) {
// children [i].updateLayout (resize, all);
if (children[i] instanceof Composite) {
display.sendMessage(new MESSAGE(children[i], MESSAGE.CONTROL_LAYOUT, new boolean[] {resize, all}));
}
}
}
}

Control#setBounds:

void setBounds (int x, int y, int width, int height, int flags, boolean defer) {
/**
* A patch to send bounds to support mirroring features like what Windows have.
*/

boundsSet = true;
int tempX = x;
if(parent != null){
if((parent.style & SWT.RIGHT_TO_LEFT) != 0){
x = Math.max(0, parent.getClientArea().width - x - width);
}
}
Element topHandle = topHandle ();
if (defer && parent != null) {
forceResize ();
WINDOWPOS [] lpwp = parent.lpwp;
if (lpwp == null) {
/*
* This code is intentionally commented. All widgets that
* are created by SWT have WS_CLIPSIBLINGS to ensure that
* application code does not draw outside of the control.
*/

// int count = parent.getChildrenCount ();
// if (count > 1) {
// int bits = OS.GetWindowLong (handle, OS.GWL_STYLE);
// if ((bits & OS.WS_CLIPSIBLINGS) == 0) flags |= OS.SWP_NOCOPYBITS;
// }
if ((width != this.width || height != this.height)
&& this instanceof Composite) {
display.sendMessage(new MESSAGE(this, MESSAGE.CONTROL_LAYOUT, null));
}
this.left = x;
this.top = y;
this.width = width;
this.height = height;
SetWindowPos (topHandle, null, x, y, width, height, flags);
} else {
int index = 0;
while (index < lpwp.length) {
if (lpwp [index] == null) break;
index ++;
}
if (index == lpwp.length) {
WINDOWPOS [] newLpwp = new WINDOWPOS [lpwp.length + 4];
System.arraycopy (lpwp, 0, newLpwp, 0, lpwp.length);
parent.lpwp = lpwp = newLpwp;
}
WINDOWPOS wp = new WINDOWPOS ();
wp.hwnd = topHandle;
wp.x = x;
wp.y = y;
wp.cx = width;
wp.cy = height;
wp.flags = flags;
lpwp [index] = wp;
}
} else {
if ((width != this.width || height != this.height)
&& this instanceof Composite) {
display.sendMessage(new MESSAGE(this, MESSAGE.CONTROL_LAYOUT, null));
}
this.left = x;
this.top = y;
this.width = width;
this.height = height;
SetWindowPos (topHandle, null, x, y, width, height, flags);
}
/*
* The x coordination should be preserved, because the right to left emulation is just
* for the view, not the data!
*/

this.left = tempX;
}

For more details about Java2Script’s SWT Asynchronous Layout implementation, please check the sources history from SVN repository at http://j2s.svn.sourceforge.net/

This entry was posted in Architecture, Hacks. Bookmark the permalink.

Leave a Reply

Your email address will not be published. Required fields are marked *