Re: [fpc-devel] TList or TFPList - a Linked list ?

2005-12-14 Thread Sterling Bates

Micha Nelissen wrote:


Hi,

I've been thinking about adding a linked list implementation to either TList
or TFPList. The basic problem to that is of course
1) space overhead of linked list is quite large
2) Index[..] will be O(N)

For (1) I was thinking about making a linked list of an array of items, for
example 14 pointers (so that 8 bytes are left for next pointer and memory
manager needs on 32 bit platform). 


To solve (2), we can make the observation
that generally people access lists in a linear fashion, and we might cache
the previous and next list entry.

The big advantage is getting rid of the many reallocs needed to grow
the lists, because one usually doesn't set Capacity in advance, but keeps
adding items until done.

Using aggregation possibly, TStringList must benefit from it too.

What do you think about it ?

Micha
___
fpc-devel maillist  -  fpc-devel@lists.freepascal.org
http://lists.freepascal.org/mailman/listinfo/fpc-

My most recent project at work involves a good deal of non-linear access 
to TList items (sorry, not Free Pascal g), and this implementation 
would essentially kill the efficiency gains that we achieved by doing 
so.  In addition, the caching overhead (the last requested index, as 
well as next  prev pointers, and corresponding logic) would blow away 
the efficiency of iterating through TList items.  There's no way to work 
around that.


Linked lists are very specialized, and definitely have their place, and 
classes should be built for them.  However, they're definitely their own 
beast, and should be treated as such.  For instance, you made the valid 
point that they grow very easily and without the overhead of having to 
find large contiguous chunks of memory when the list grows, but 
iterating them is relatively slow.  Programmers just need to recognize 
when this is an advantage and when it isn't.


Just my $0.02.
___
fpc-devel maillist  -  fpc-devel@lists.freepascal.org
http://lists.freepascal.org/mailman/listinfo/fpc-devel


Re: [fpc-devel] TList or TFPList - a Linked list ?

2005-12-14 Thread Sterling Bates



Michael Van Canneyt wrote:


People that have large lists know this and take care of it.

What is more, I think that 1 large memory block (an array) is much more 
efficient memory wise than many small blocks. 
 

 

This is true in some cases; VMs and scripting engines (such as 
SpiderMonkey) use this technique.  However, when the list size is 
underestimated and has to grow it can be a huge detriment to performance 
and memory efficiency.

___
fpc-devel maillist  -  fpc-devel@lists.freepascal.org
http://lists.freepascal.org/mailman/listinfo/fpc-devel


Re: [fpc-devel] TList or TFPList - a Linked list ?

2005-12-14 Thread Sterling Bates




Mattias Gaertner wrote:

  Your trick will only give a constant factor on growing/shrinking the list
memory, gives an extra O(n) factor for sorting a TList, the caching costs
time, and the memory usage will also grow.
  

Just saw this last statement. The memory usage is very comparable to
TList, even with bi-directional (doubly-linked) lists, since TLists
tend to grow by leaps.

For example, assuming a linked list item comprises of only a "next"
pointer, it requires 8 bytes of memory (4 for the structure itself, 4
for the next pointer). In this case, 10,000 entries occupy 80,008
bytes (80,000 + 4 for pointer to First + 4 for pointer to Last),
distributed around the memory table. Also keep in mind that the data
payload for the linked list item is usually contained within the
structure itself.

A TList (stripped down for this case) requires 4 bytes for the list
allocation, plus 4 bytes per list entry. 10,000 entries occupy 80,004
bytes. Now, two things:

1. With automatically growing lists you have a very high likelihood of
unused pointers, so while a linked list of 10,000 items is utilizing
all 80,004 bytes of memory, the TList allocated (10,000-TList.Count)*4
unused bytes of memory.

2. The TList entry only points to the actual data payload, meaning
another 4+n bytes must be allocated to store the data. This
means an additional 40,000 bytes is required for a TList vs. a linked
list. On the other hand, this is equalized in a doubly-linked list.

Disclaimer: this is all based on the Delphi implementation of TList,
and may differ slightly (but probably not much) for the FP lists.

Sterling


___
fpc-devel maillist  -  fpc-devel@lists.freepascal.org
http://lists.freepascal.org/mailman/listinfo/fpc-devel


Re: [fpc-devel] TList or TFPList - a Linked list ?

2005-12-14 Thread Sterling Bates




Mattias Gaertner wrote:

  Plus some bytes for the memory manager for each mem block. Typically 8.
  

Forgot about that, sorry. In any case, the math still works. I'll
explain.

  
In this case, 10,000 entries occupy 80,008 bytes
(80,000 + 4 for pointer to First + 4 for pointer to Last),

  
  
~160,000

  
  
distributed
around the memory table.  Also keep in mind that the data payload for the
linked list item is usually contained within the structure itself.

A TList (stripped down for this case) requires 4 bytes for the list
allocation, plus 4 bytes per list entry.  10,000 entries occupy 80,004
bytes. 

  
  
~40,000
  

Actually, if you account for the allocation of each of the 10,000 items
added to the TList, it is ~120,000 (4 + 8 for memory manager that you
pointed out above) plus the 40,000, bringing the total to 160,000. My
point above is that the linked list item itself typically houses the
payload, so no additional pointer allocation (or memory manager record)
is required.

  

  
  
Now, two things:

1. With automatically growing lists you have a very high likelihood of
unused pointers, so while a linked list of 10,000 items is utilizing all
80,004 bytes of memory, the TList allocated (10,000-TList.Count)*4 unused
bytes of memory.

  
  i.e. plus a maximum of 25% with the current implementation
~50,000
 
  
  
2. The TList entry only points to the actual data payload, meaning another
4+n bytes must be allocated to store the data.  This means an additional
40,000 bytes is required for a TList vs. a linked list. 

  
  Huh?
  

Here I'm pointing out that the item each entry in the TList refers to
has to be allocated somewhere. Best case scenario, a record, means a
minimum of 4 bytes for each allocation, plus 8 for the memory manager.
It all adds up.


  The main problem is the mem fragmentation. Here a growing TList can need up
to 4 times its used memory. So in worst case it will need the memory of a
single linked list. 
  

Regarding fragmentation, it's my personal experience that allocation of
large numbers (millions) of
small data packets is easier to manage in a suitably unpredictable
environment. In cases where I need TList functionality, I really have
to estimate (in my case at run-time, which is very hit-and-miss) how
large to make the TList. If I mispredict then I chew up large chunks
of contiguous memory very very quickly. If I overpredict, which
usually happens by very large amounts, I'm wasting memory. Granted
that's not a huge issue on servers with four gigabytes of RAM, but on
these high-traffic servers it could be.


___
fpc-devel maillist  -  fpc-devel@lists.freepascal.org
http://lists.freepascal.org/mailman/listinfo/fpc-devel


Re: [fpc-devel] TList or TFPList - a Linked list ?

2005-12-14 Thread Sterling Bates

Mattias Gaertner wrote:


If the linked list item contains the whole data, then you are either not
talking of the generic list this thread is about, or you use templates. In
the later case a TList will also use templates and the 'payload' is the
same.
 

That's true of records, sure.  Someone could also create a 
TLinkedListItem base class from which to descend and store their data 
there.  The next  prev pointers can be stored in the base class.


Anyway, thanks for the discussion :)
___
fpc-devel maillist  -  fpc-devel@lists.freepascal.org
http://lists.freepascal.org/mailman/listinfo/fpc-devel


Re: [fpc-devel] TList or TFPList - a Linked list ?

2005-12-14 Thread Sterling Bates

Mattias Gaertner wrote:


Well, if you use objects, then you get even more mem overhead ... :)
 

I was thinking about that too.  Then, IIRC, I read that object overhead 
is a one-time allocation of class tables and such.  Of course, you might 
have some extra overhead inherited from TObject (ClassName etc).  In my 
case, though, I use TList for objects anyway, so it all balances out.  
Guess it really depends on how one uses the tools.

___
fpc-devel maillist  -  fpc-devel@lists.freepascal.org
http://lists.freepascal.org/mailman/listinfo/fpc-devel


Re: [fpc-devel] Patch for bug 3774

2005-05-29 Thread Sterling Bates
In reply to Thomas Schatzl:

Another optimization for your patch is to set a var to length(s) at the beginning of the proc, and use the var instead. It's called often enough that a little time could be shaved off.Post your free ad now! Yahoo! Canada Personals___
fpc-devel maillist  -  fpc-devel@lists.freepascal.org
http://lists.freepascal.org/mailman/listinfo/fpc-devel


[fpc-devel] Patch for bug 2453

2005-03-30 Thread Sterling Bates
(My ISP's mail server is blacklisted, so sending this from Yahoo. Ugh.)

This is my first shot, so be gentle :-)

The patch assumes FP wantscompatibility with BP 7. If so, it fixes two problems; if not, at least it was a fun exercise.

First, BP ignores non-numeric characters when a ReadLn is called with an integer parameter. To fix this, I changed ReadNumeric's end condition to explicitly terminate on all non-numerics.

Second, BP will not read a Text file past an EOF character at all, regardless of its location in the file. The fix for this is inelegant (inline buffer check), but I can move this out to an external proc if it's onerous to maintain.

It's likely there are things I haven't thought of, so I appreciate comments on it.

Sterling Post your free ad now! Yahoo! Canada PersonalsIndex: text.inc
===
RCS file: /FPC/CVS/fpc/rtl/inc/text.inc,v
retrieving revision 1.29
diff -w -b -i -u -p -1 -0 -r1.29 text.inc
--- text.inc14 Feb 2005 17:13:29 -  1.29
+++ text.inc26 Mar 2005 21:34:02 -
@@ -756,21 +756,21 @@ Begin
 End;
 {$endif HASWIDECHAR}
 
 
 {*
 Read(Ln)
 *}
 
 Function NextChar(var f:Text;var s:string):Boolean;
 begin
-  if TextRec(f).BufPosTextRec(f).BufEnd then
+  if (TextRec(f).BufPosTextRec(f).BufEnd) {$ifdef EOF_CTRLZ} and 
(TextRec(f).Bufptr^[TextRec(f).BufPos]#26) {$endif} then
begin
  if length(s)high(s) then
   begin
 inc(s[0]);
 s[length(s)]:=TextRec(f).BufPtr^[TextRec(f).BufPos];
   end;
  Inc(TextRec(f).BufPos);
  If TextRec(f).BufPos=TextRec(f).BufEnd Then
   FileFunc(TextRec(f).InOutFunc)(TextRec(f));
  NextChar:=true;
@@ -784,43 +784,43 @@ Function IgnoreSpaces(var f:Text):Boolea
 {
   Removes all leading spaces,tab,eols from the input buffer, returns true if
   the buffer is empty
 }
 var
   s : string;
 begin
   s:='';
   IgnoreSpaces:=false;
   { Return false when already at EOF }
-  if (TextRec(f).BufPos=TextRec(f).BufEnd) then
+  if (TextRec(f).BufPos=TextRec(f).BufEnd) {$ifdef EOF_CTRLZ} and 
(TextRec(f).Bufptr^[TextRec(f).BufPos]=#26) {$endif} then
exit;
   while (TextRec(f).Bufptr^[TextRec(f).BufPos] in [#9,#10,#13,' ']) do
begin
  if not NextChar(f,s) then
   exit;
  { EOF? }
- if (TextRec(f).BufPos=TextRec(f).BufEnd) then
+ if (TextRec(f).BufPos=TextRec(f).BufEnd) {$ifdef EOF_CTRLZ} or 
(TextRec(f).Bufptr^[TextRec(f).BufPos]=#26) {$endif} then
   break;
end;
   IgnoreSpaces:=true;
 end;
 
 
 procedure ReadNumeric(var f:Text;var s:string);
 {
   Read numeric input, if buffer is empty then return True
 }
 begin
   repeat
 if not NextChar(f,s) then
   exit;
-  until (length(s)=high(s)) or (TextRec(f).BufPtr^[TextRec(f).BufPos] in 
[#9,#10,#13,' ']);
+  until (length(s)=high(s)) or not (TextRec(f).BufPtr^[TextRec(f).BufPos] in 
['+','-','.',',','0'..'9']);
 end;
 
 
 Procedure fpc_Read_End(var f:Text);[Public,Alias:'FPC_READ_END']; iocheck; 
{$ifdef hascompilerproc} compilerproc; {$endif}
 begin
   if TextRec(f).FlushFuncnil then
FileFunc(TextRec(f).FlushFunc)(TextRec(f));
 end;
 
 
@@ -1049,24 +1049,25 @@ Begin
  end;
  exit;
end;
   If TextRec(f).BufPos=TextRec(f).BufEnd Then
FileFunc(TextRec(f).InOutFunc)(TextRec(f));
   hs:='';
   if IgnoreSpaces(f) then
begin
  { When spaces were found and we are now at EOF,
then we return 0 }
- if (TextRec(f).BufPos=TextRec(f).BufEnd) then
+ if (TextRec(f).BufPos=TextRec(f).BufEnd) {$ifdef EOF_CTRLZ} or 
(TextRec(f).Bufptr^[TextRec(f).BufPos]=#26) {$endif} then
   exit;
  ReadNumeric(f,hs);
end;
+   if (hs  '') then
 {$ifdef hascompilerproc}
   Val(hs,l,code);
 {$else hascompilerproc}
   Val(hs,fpc_Read_Text_SInt,code);
 {$endif hascompilerproc}
   If code0 Then
InOutRes:=106;
 End;
___
fpc-devel maillist  -  fpc-devel@lists.freepascal.org
http://lists.freepascal.org/mailman/listinfo/fpc-devel


[fpc-devel] Quick patch for bug 3762

2005-03-30 Thread Sterling Bates

Turns out the CRT unit in OS/2-- which, by my searches, uses #13#10 as CRLF -- was recognizing #13 alone as CRLF. #10 was completed ignored.

SterlingPost your free ad now! Yahoo! Canada PersonalsIndex: crt.pas
===
RCS file: /FPC/CVS/fpc/rtl/os2/crt.pas,v
retrieving revision 1.8
diff -w -b -i -u -p -1 -0 -r1.8 crt.pas
--- crt.pas 14 Feb 2005 17:13:31 -  1.8
+++ crt.pas 27 Mar 2005 04:27:58 -
@@ -379,25 +379,22 @@ var
   ca:Pchar;
 
 begin
   i:=0;
   getcursor(y,x);
   while i=len-1 do
   begin
 case s[i] of
   #8: x:=x-1;
   #9: x:=(x-lo(windmin)) and $fff8+8+lo(windmin);
-  #10: ;
-  #13: begin
-  x:=lo(windmin);
-  inc(y);
-end;
+  #10: inc(y);
+  #13: x:=lo(windmin);
   else
   begin
 ca:[EMAIL PROTECTED];
 n:=1;
 while not(s[i+1] in [#8,#9,#10,#13]) and
   (x+n=lo(windmax)) and (ilen-1) do
 begin
   inc(n);
   inc(i);
 end;
___
fpc-devel maillist  -  fpc-devel@lists.freepascal.org
http://lists.freepascal.org/mailman/listinfo/fpc-devel


[fpc-devel] Patch for bug 2453

2005-03-30 Thread Sterling Bates
This is my first shot, so be gentle :-) 

The patch assumes FP wants sufficient compatibility with BP 7. If so, it
fixes two problems; if not, at least it was a fun exercise.

First, BP ignores non-numeric characters when a ReadLn is called with an
integer parameter. To fix this, I changed ReadNumeric's end condition to
explicitly terminate on all non-numerics.

Second, BP will not read a Text file past an EOF character at all,
regardless of its location in the file. The fix for this is inelegant
(inline buffer check), but I can move this out to an external proc if it's
onerous to maintain.

It's likely there are things I haven't thought of, so I appreciate comments
on it.
 
Sterling
Index: text.inc
===
RCS file: /FPC/CVS/fpc/rtl/inc/text.inc,v
retrieving revision 1.29
diff -w -b -i -u -p -1 -0 -r1.29 text.inc
--- text.inc14 Feb 2005 17:13:29 -  1.29
+++ text.inc26 Mar 2005 21:34:02 -
@@ -756,21 +756,21 @@ Begin
 End;
 {$endif HASWIDECHAR}
 
 
 {*
 Read(Ln)
 *}
 
 Function NextChar(var f:Text;var s:string):Boolean;
 begin
-  if TextRec(f).BufPosTextRec(f).BufEnd then
+  if (TextRec(f).BufPosTextRec(f).BufEnd) {$ifdef EOF_CTRLZ} and 
(TextRec(f).Bufptr^[TextRec(f).BufPos]#26) {$endif} then
begin
  if length(s)high(s) then
   begin
 inc(s[0]);
 s[length(s)]:=TextRec(f).BufPtr^[TextRec(f).BufPos];
   end;
  Inc(TextRec(f).BufPos);
  If TextRec(f).BufPos=TextRec(f).BufEnd Then
   FileFunc(TextRec(f).InOutFunc)(TextRec(f));
  NextChar:=true;
@@ -784,43 +784,43 @@ Function IgnoreSpaces(var f:Text):Boolea
 {
   Removes all leading spaces,tab,eols from the input buffer, returns true if
   the buffer is empty
 }
 var
   s : string;
 begin
   s:='';
   IgnoreSpaces:=false;
   { Return false when already at EOF }
-  if (TextRec(f).BufPos=TextRec(f).BufEnd) then
+  if (TextRec(f).BufPos=TextRec(f).BufEnd) {$ifdef EOF_CTRLZ} and 
(TextRec(f).Bufptr^[TextRec(f).BufPos]=#26) {$endif} then
exit;
   while (TextRec(f).Bufptr^[TextRec(f).BufPos] in [#9,#10,#13,' ']) do
begin
  if not NextChar(f,s) then
   exit;
  { EOF? }
- if (TextRec(f).BufPos=TextRec(f).BufEnd) then
+ if (TextRec(f).BufPos=TextRec(f).BufEnd) {$ifdef EOF_CTRLZ} or 
(TextRec(f).Bufptr^[TextRec(f).BufPos]=#26) {$endif} then
   break;
end;
   IgnoreSpaces:=true;
 end;
 
 
 procedure ReadNumeric(var f:Text;var s:string);
 {
   Read numeric input, if buffer is empty then return True
 }
 begin
   repeat
 if not NextChar(f,s) then
   exit;
-  until (length(s)=high(s)) or (TextRec(f).BufPtr^[TextRec(f).BufPos] in 
[#9,#10,#13,' ']);
+  until (length(s)=high(s)) or not (TextRec(f).BufPtr^[TextRec(f).BufPos] in 
['+','-','.',',','0'..'9']);
 end;
 
 
 Procedure fpc_Read_End(var f:Text);[Public,Alias:'FPC_READ_END']; iocheck; 
{$ifdef hascompilerproc} compilerproc; {$endif}
 begin
   if TextRec(f).FlushFuncnil then
FileFunc(TextRec(f).FlushFunc)(TextRec(f));
 end;
 
 
@@ -1049,24 +1049,25 @@ Begin
  end;
  exit;
end;
   If TextRec(f).BufPos=TextRec(f).BufEnd Then
FileFunc(TextRec(f).InOutFunc)(TextRec(f));
   hs:='';
   if IgnoreSpaces(f) then
begin
  { When spaces were found and we are now at EOF,
then we return 0 }
- if (TextRec(f).BufPos=TextRec(f).BufEnd) then
+ if (TextRec(f).BufPos=TextRec(f).BufEnd) {$ifdef EOF_CTRLZ} or 
(TextRec(f).Bufptr^[TextRec(f).BufPos]=#26) {$endif} then
   exit;
  ReadNumeric(f,hs);
end;
+   if (hs  '') then
 {$ifdef hascompilerproc}
   Val(hs,l,code);
 {$else hascompilerproc}
   Val(hs,fpc_Read_Text_SInt,code);
 {$endif hascompilerproc}
   If code0 Then
InOutRes:=106;
 End;
___
fpc-devel maillist  -  fpc-devel@lists.freepascal.org
http://lists.freepascal.org/mailman/listinfo/fpc-devel


[fpc-devel] Patch for bug 3774

2005-03-30 Thread Sterling Bates
This patch adds recognition for hex to Val().

SterlingPost your free ad now! Yahoo! Canada PersonalsIndex: sstrings.inc
===
RCS file: /FPC/CVS/fpc/rtl/inc/sstrings.inc,v
retrieving revision 1.35
diff -w -b -i -u -p -1 -0 -r1.35 sstrings.inc
--- sstrings.inc20 Mar 2005 12:45:19 -  1.35
+++ sstrings.inc27 Mar 2005 05:12:42 -
@@ -552,20 +552,25 @@ begin
   '%' : begin
   base:=2;
   inc(code);
 end;
   '' : begin
   Base:=8;
   repeat
 inc(code);
   until (code=length(s)) or (s[code]'0');
 end;
+  '0' : if (code  length(s)) and (s[code+1]='x') then
+begin
+  base := 16;
+  Inc(code, 2);
+end;
  end;
   end;
   InitVal:=code;
 end;
 
 
 Function fpc_Val_SInt_ShortStr(DestSize: SizeInt; Const S: ShortString; var 
Code: ValSInt): ValSInt; [public, alias:'FPC_VAL_SINT_SHORTSTR']; {$ifdef 
hascompilerproc} compilerproc; {$endif}
 var
   u, temp, prev, maxPrevValue, maxNewValue: ValUInt;
   base : byte;
___
fpc-devel maillist  -  fpc-devel@lists.freepascal.org
http://lists.freepascal.org/mailman/listinfo/fpc-devel


RE: [fpc-devel] Quick patch for bug 3762

2005-03-30 Thread Sterling Bates

In response to Tomas Hajny:

I'd certainly be willing to give it a try. Granted, I only have Windows XP, but if I'm careful it should be a smooth transition. No promises on a timeline :)

Another problem with Windows (not sure about other OSs) is in bug 2084. (Use the second example in the description.) I was able to narrow down what the problem is, and it's pretty daunting. Essentially, initmouse will call SetMouseEventHandler() in winevent.pp. This spawns a thread that runs EventHandleThread in a fairly tight loop, which captures all console input.

The problem comes when ReadKey then calls KeyPressed, which also attempts to read console input. No matter what, EventHandleThread will capture the input (since it places a call to WaitForSingleObject, waking up the instant something is in the queue), leaving KeyPressed with nothing to process.

My initial attempt to get around this is to tie KeyPressed to EventHandleThread as well. (If you can't beat 'em, join 'em, right?) I've attached the work I've done so far, but it's not quite right. With two ReadKey calls in a row, the first will read the key, the second will exit without even reading the queue. I think this is because the first ReadKey hasn't had a chance to clear the ScanCode variable before the second ReadKey call to KeyPressed (which exits when ScanCode  #0). This results in the DOS window outputting a garbage character when the program finishes because there's still something in the queue.

It's possible to fix it by wrapping the contents of ReadKey in a critical section, but that, to me, is just more overhead. I think there's a more elegant solution waiting to be discovered :-)

Thanks,

Sterling


Your message below:
--
Unfortunately, the problem is wider - there are several other issues with the current OS/2 implementation (if you just quickly compare it with e.g. the GO32v2 implementation, you'd find some other problems too - e.g. handling of #8 is surely incorrect etc.).
The main problem is that there's a lot platform independent functionality in Crt unit which is re-implemented for every platform again and again. The best solution would be to throw all the individual implementations away completely and implement cross-platform Crt unit based on capabilities provided by units Keyboard and Video (possibly missing functionalities within these units necessary for Crt could be either handled by platform specific include file, or by extending current Keyboard and/or Video). This issue has been discussed several times in the core team already, but nobody found the time for doing it yet due to higher priority tasks. Would you be willing to give it a try by any chance?Post your free ad now! Yahoo! Canada Personals===
RCS file: /FPC/CVS/fpc/rtl/win32/crt.pp,v
retrieving revision 1.24
diff -w -b -i -u -p -1 -0 -r1.24 crt.pp
--- crt.pp  14 Feb 2005 17:13:32 -  1.24
+++ crt.pp  31 Mar 2005 06:08:59 -
@@ -20,21 +20,22 @@ interface
 {$i crth.inc}
 
 procedure Window32(X1,Y1,X2,Y2: DWord);
 procedure GotoXY32(X,Y: DWord);
 function WhereX32: DWord;
 function WhereY32: DWord;
 
 implementation
 
 uses
-  windows;
+  windows,
+  winevent;
 
 var
 SaveCursorSize: Longint;
 
 
 {
   definition of textrec is in textrec.inc
 }
 {$i textrec.inc}
 
@@ -347,130 +348,138 @@ begin
 end
   else
 case Scancode of
 // Function keys
 $57..$58: inc(Scancode, $2E); // F11 and F12
   end;
   RemapScanCode := ScanCode;
 end;
 
 
+var
+  KeyEvt: THandle;  // signal that a key is ready for processing
+  KeyPrcEvt: THandle;   // signal that the key has been processed
+  KeyBuf: PInputRecord; // pointer to incoming INPUT_RECORD
+  KeyCS: TCriticalSection;  // restricts KeyPressed access to single thread
+
+
+procedure OnKeyEvent(var ir: INPUT_RECORD);
+begin
+  KeyBuf := @ir;
+  Windows.SetEvent(KeyEvt);
+  WaitForSingleObject(KeyPrcEvt, INFINITE);
+  Windows.ResetEvent(KeyEvt);
+end;
+
+
 function KeyPressed : boolean;
 var
   nevents,nread : dword;
-  buf : TINPUTRECORD;
   AltKey: Boolean;
   c : longint;
 begin
   KeyPressed := FALSE;
+  { Leave KeyPrcEvt set at the end of the proc; this avoids deadlocks in }
+  { OnKeyEvent while waiting for the event to signal. }
+  Windows.ResetEvent(KeyPrcEvt);
+
   if ScanCode  #0 then
 KeyPressed := TRUE
   else
begin
- GetNumberOfConsoleInputEvents(TextRec(input).Handle,nevents);
- while nevents0 do
-   begin
-  ReadConsoleInputA(TextRec(input).Handle,buf,1,nread);
-  if buf.EventType = KEY_EVENT then
-if buf.Event.KeyEvent.bKeyDown then
+ WaitForSingleObject(KeyEvt, INFINITE);
+
+ if KeyBuf^.Event.KeyEvent.bKeyDown then
   begin
  { Alt key is VK_MENU }
  { Capslock key is VK_CAPITAL }
 
- AltKey := ((Buf.Event.KeyEvent.dwControlKeyState AND
+ AltKey :=