[Prev][Next][Index][Thread]
FD 2.0 is quite a tool! --long post
Having just spent about half a day during working hours and numerous other
at home hours working on my pet Dylan project--getting serial data from an
avionics product for testing the product--I have learned a number of things
about FD 2.0 and DUIM. These are somewhat random thoughts, so be
forewarned. You don't have to read this if you don't want to.
1. I really think that FD 2.0 is a full-fledged, heavyweight tool now, and
Dylan is a very powerful and flexible language. Previous releases of the
Harlequin version had a few too many rough spots. FD 2.0 works well in
almost all areas (except perhaps documentation of DUIM. Dustin Voss may
have similar opinions), and downright great in some areas. I used nothing
but FD 2.0 to set up and read from the COM1 port via Win32 calls. It was
painless to do in FD 2.0. The hard part was learning how the Win32 calls
worked, and trying to learn DUIM. Although it's not FD's job to help with
Win32, the profiler ended up helping me with the Win32 stuff (see below).
The compiler is fast. Setting up projects is easy once you've learned the
library structure. Linking is really lightning fast now that I use the
Microsoft linker. Kudos to FunctionalObjects for supporting it. I can make
dll's as easily as exe's, and I can incorporate a dll project in an
executable as a subproject. It just works well, even intuitively. Color
dispath optimization is COOL..
There are definitely some rough spots still remaining. Sometimes the IDE
seems to get confused about rebuilding when I've got a project and its
subproject open. The editor still has a few gotchas. Perhaps some more
native look and feel tools for DUIM-- I don't know. But these are minor,
and I'm sure that the next release will eliminate most of these annoyances
(I hope).
2. I still have a long way to go in understanding DUIM. Most of my at home
time was spent looking over DUIM doc and example code trying to figure out
how to get a <gadget> to update itself. I was lost...for hours. The
problem (for me) was that the <gadget> was buried inside a pane which was
buried inside a frame. It may sound silly, but it's true. I finally
figured out by looking at some other code that the pane surrounding the
gadget works when passed to update-gadget (see the code below). I still
don't have the relationship between frames, panes, and sheets straight. If
I understood that, building DUIM apps would probably be much easier.
define frame <thread-frame> (<dialog-frame>)
pane stop-button (frame)
make(<push-button>, label: "Stop",
activate-callback: stop-app);
pane quicktune-pane (frame)
make(<text-field>, label: "QuickTune:",
width: 100, fixed-width?: #t,
activate-callback: not-yet-implemented);
pane task-layout (frame)
vertically ()
frame.quicktune-pane;
frame.stop-button;
end;
layout (frame) frame.task-layout;
keyword title: = "KLN 94 QuickTune Info";
end frame <thread-frame>;
define variable display-thread-frame :: false-or( <thread-frame> ) = #f;
define method update-gui (data :: <byte-string> ) => ()
local method update-quicktune( s :: <byte-string> )
gadget-text-setter( s, display-thread-frame.quicktune-pane );
update-gadget( quicktune-pane( display-thread-frame ) ); // insert
display changing code here
end method update-quicktune;
call-in-frame( display-thread-frame, update-quicktune, as( <byte-string>
, data));
end method;
define method main () => ()
// Your program starts here...
display-thread-frame := make( <thread-frame> );
make(<thread>, function: curry(quicktune-thread, display-thread-frame));
start-frame( display-thread-frame );
end method main;
<-------------------------- AND IN ANOTHER PROJECT, THE CODE FOR THE OTHER
THREAD -------------------->
define open generic update-gui( data :: <byte-string> ) => ();
define method quicktune-thread ( gui-frame :: <frame> ) => ()
let port = setup-94-serial-data();
while (#t)
let (result , num-read ) = get-94-serial-data(port);
let quicktune-pattern = make( <byte-string> , size: 1 );
quicktune-pattern[0] := 'q';
//quicktune-pattern[1] := as( <byte> , 'q' );
if (num-read > 0 )
let current-data = copy-sequence( buffer-94.buf1, start: 0, end:
num-read );
let quicktune-found = subsequence-position( current-data ,
quicktune-pattern );
if ( quicktune-found ~= #f )
let quicktune = copy-sequence( current-data,
start: quicktune-found, end:
quicktune-found + 7 );
update-gui( as( <byte-string>, quicktune ));
end if;
end if;
threads-sleep( 0.5 );
end while;
end method quicktune-thread;
3. The threads library is very rich. It may have everything you'd ever
need for threads and/or synchronization. But, even though the code shown
above works as two separate threads, I don't think it goes as far as I would
like to in decoupling the two threads. I'm not exactly sure what the
sematics of call-in-frame is. Do the calling thread and the called thread
(i.e., the frame thread) synchronize in this function somehow? Suppose I
wanted the quicktune-thread to just dump its data, a buffer at a time, into
a queue. Then signal (I don't know how) the GUI thread that it has done so.
How does a GUI thread running in a frame "catch" a signal? I assume that
the main thread loop for a gui frame looks something like:
while ( alive )
event := get-next-event();
process-event( event );
end while;
The "event" would be some DUIM translation of Windows events (I'm guessing).
Granted that this is all based on my possibly flawed assumptions, but how
could you extend/modify the processing of a frame's thread to somehow check
for a signal from another thread?
4. The profiler is slick. Even though the high level code for my whole toy
(but useful) application is contained in just the few lines above, my
application was taking about 35% of a Pentium III, 500. Pretty terrible,
huh. Well, I had never even played with the FD profiler before, but I
fired it up, and it quickly showed me just what I was expecting--that I was
probably wasting a lot of time in a wait in the Windows ReadFile function.
The commented out code below and its replacement below make all the
difference in how much cpu the application takes. With the BAD, BAD, BAD
code, it takes about 35% of the cpu. With the good code, it takes the much
more reasonable 3%. My point is that with the Profiler I quickly was able
to confirm my suspicions about the source of the bad performance. With a
little experimentation, I fixed it. (I still don't think I get why the
$MAXDWORD approach is bad though). The Profiler is one of the necessary
tools of a heavyweight compiler. FD has it and it works.
define method get-94-serial-data( port-handle ) => ( #rest results)
/* BAD BAD BAD
set-comm-timeouts( port-handle, read-interval: $MAXDWORD,
read-total-multiplier: as( <machine-word>, 0) ,
read-total-constant: as( <machine-word>, 0) );
*/
set-comm-timeouts( port-handle, read-interval: as( <machine-word>, 3),
read-total-multiplier: as( <machine-word>, 0) ,
read-total-constant: as( <machine-word>, 0) );
let temp-buf = make( <c-string>, size: 2 );
let ( result , num-read ) = read-comm-data( port-handle, temp-buf ,
num-bytes: 1 );
while ( num-read > 0 & (temp-buf[0] ~= '\<2>') )
let (result, num-read ) = read-comm-data( port-handle, temp-buf,
num-bytes: 1 );
end while;
if ( num-read > 0)
set-comm-timeouts( port-handle, read-interval: as( <machine-word>, 2),
read-total-multiplier: as( <machine-word>, 3) ,
read-total-constant: as( <machine-word>, 20) );
read-comm-data( port-handle, buffer-94.buf1, num-bytes: (115 + 34) );
else
PurgeComm( port-handle, $PURGE-RXABORT );
values( #f, 0 );
end if;
end method get-94-serial-data;
Follow-Ups:
References: