[prev in list] [next in list] [prev in thread] [next in thread] 

List:       xwt-dev
Subject:    [xwt-dev] Solution: interrupting a thread
From:       Andrew Kohlsmith <akohlsmith-xwt () benshaw ! com>
Date:       2003-01-31 2:21:07
[Download RAW message or body]

Rod and I (well mostly Rod) have been working my partial match widget (debut 
this list 24 Oct 2002).  He's done some amazing work with the partial match 
widget and the edit widget and all kinds of little fixups and cleanups.

He'd been spending the past few days trying to get the partial match widget to 
work faster when it has been -- the particular application it's going in to 
has literally thousands of boxes in the list and all that matching on every 
keypress is causing the widget to become _really_ laggy.

The core of the partial match is this loop:
for(var i=0; list.numchildren > i; i++)
   {
   if(list[i].text.substring(0, matchText.length).toLowerCase() == matchText)
     list[i].invisible = false;
   else
     list[i].invisible = true;
   }

(If anyone knows how to make this faster, please _please_ jump in....  Rod's 
got a great speedup with that I'm about to show, but it could always be 
better.)

Every keypress will call this routine.  With small lists this isn't a problem, 
but as the lists grow (or if used on lighter-weight processors) the widget 
will lag since the list is updated completely for each keypress.  We sought 
out a way to interrupt the loop when a new keypress was received.

Now perhaps we aren't reading the reference thoroughly enough, but it was not 
clear that threads don't actually run concurrently, especially with all the 
talk of "if you need to do a lot of processing, do it in a background thread" 
and "if you exceed the platform-dependant maximum number of threads, new 
threads will pause until background threads finish off".  Rod read through 
the engine source in CVS to discover that only one thread at a time runs.

So now the keypress read trap calls hideNonMatching in a background thread:
edit.__keypress = function(k)
    {
    if(0 >= list.numchildren) return;

    xwt.println("__keypress("+k+")");
    if(k != "enter")
      arguments.cascade(k);

    if(open and (k.length == 1 || k == "delete" || k == "back_space"))
      { xwt.thread = function() { hideNonMatching(k); } }
    }

var hideNonMatching = function(key)
   {
   matchText = text.toLowerCase();

   oneVisible = false;
   for(var i=0; list.numchildren > i; i++)
      {
      if(list[i].text.substring(0, matchText.length).toLowerCase() == 
matchText)
        list[i].invisible = false;
      else
        {
        list[i].invisible = true;
        if(! oneVisible) topitem = list[i];
        oneVisible = true;
        }
      }

   // if at least one entry is visible, then select it and sync the list
   if(oneVisible)
     {
     list.setselect(topitem, true);
     list.syncscroll(topitem.y);
     }
 }

(Aside: why does "var myFunc = xwt.thread = function(key) { }" not work?)

This didn't work well since the threads only run one at a time, and putting 
"and !abort" in the for() loop doesn't help.  What we finally discovered was 
that we needed to put an xwt.yield() in the for loop to let the foreground 
thread run and see keypresses and assert an abort flag.

The final code works pretty decently:
edit.__keypress = function(k)
     {
     if(0 >= list.numchildren) return;

     if(k != "enter")
       arguments.cascade(k);

     if(open and (k.length == 1 || k == "delete" || k == "back_space"))
       {
       xwt.thread = function()
          {
          while(matching) { abort = true; xwt.yield(); }
          abort = false;
          matching = true;
          hideNonMatch(k);
          matching = false;
          }
       }
     }


var hideNonMatch = function(key)
   {
   matchText = text.toLowerCase();
   
   oneVisible = false;
   for(var i=0; list.numchildren > i and !abort; i++)
      {
      if(list[i].text.substring(0, matchText.length).toLowerCase() == 
matchText)
        list[i].invisible = false;
      else
        {
        list[i].invisible = true;
        if(! oneVisible) topitem = list[i];
        oneVisible = true;
        }
      if(i % 50 == 0) xwt.yield();
      }

   // if at least one entry is visible, then select it and sync the list
   if(oneVisible)
     {
     list.setselect(topitem, true);
     list.syncscroll(topitem.y);
     }
 }

We put the if(i % 50 == 0) xwt.yield() since there was absolutely no need to 
yield on every iteration (although it causes some really neat effects on the 
partial match when you are typing quickly).

If anything else I think this would make a good tutorial and/or entry into the 
wiki, but I'd like to finish it off with a good fast way to match the text 
against all the boxes in a list.  Hopefully whoever's got the ability to edit 
the reference can clarify a few of the things brought up here too:

1. threads run atomically, until a new thread is started or yield() or sleep()
2. why var myfunc = xwt.thread = function(x) { } does not create a new thread

Anyway, just wanted to share some successes amidst all my "why the 
dist.xwt.org builds don't run on Rod's computer" woes.  :-)

Regards,
Andrew

[prev in list] [next in list] [prev in thread] [next in thread] 

Configure | About | News | Add a list | Sponsored by KoreLogic