Decouple rendering from data processing with timer-based display

Data parsing (ProcessChar) is now purely memory operations. EndUpdate
and ParseData set FRenderPending instead of calling FlipToScreen.
A 55ms timer (~18 Hz, matching Win16 tick resolution) drives rendering:
dirty rows are painted and BitBlt'd to screen only on timer ticks.
Blink toggles every 9 ticks (~500ms). This prevents high-throughput
data (door games, file transfers) from saturating the CPU with GDI
calls — between timer ticks, data just accumulates in cell buffers.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Scott Duensing 2026-02-26 21:28:14 -06:00
parent a96dbb3faa
commit c565be8489

View file

@ -62,6 +62,8 @@ type
FPaintFont: HFont;
FStockFont: Boolean;
FBlinkPhase: Integer;
FBlinkCount: Integer;
FRenderPending: Boolean;
FUpdateCount: Integer;
FDirtyRow: array[0..255] of Boolean;
FAllDirty: Boolean;
@ -163,7 +165,10 @@ const
$00FFFFFF { 15 White (bright) }
);
CursorBlinkMs = 500;
{ Timer fires at ~18 Hz (Win16 tick resolution is ~55 ms). }
{ Cursor and text blink toggle every 9 ticks (~500 ms). }
RenderTickMs = 55;
BlinkInterval = 9;
{ OUT_RASTER_PRECIS may not be defined in Delphi 1.0 WinTypes }
OutRasterPrecis = 6;
@ -334,6 +339,8 @@ begin
FPaintFont := 0;
FStockFont := False;
FBlinkPhase := 0;
FBlinkCount := 0;
FRenderPending := False;
FUpdateCount := 0;
FAllDirty := True;
FBufDC[0] := 0;
@ -875,7 +882,7 @@ begin
if FUpdateCount <= 0 then
begin
FUpdateCount := 0;
FlipToScreen;
FRenderPending := True;
end;
end;
@ -1539,7 +1546,7 @@ begin
FBlinkOn := True;
if FUpdateCount = 0 then
FlipToScreen;
FRenderPending := True;
end;
@ -1827,10 +1834,10 @@ begin
CreateBuffers;
FAllDirty := True;
{ Start cursor blink timer }
{ Start render/blink timer }
if not FTimerActive then
begin
SetTimer(Handle, 1, CursorBlinkMs, nil);
SetTimer(Handle, 1, RenderTickMs, nil);
FTimerActive := True;
end;
@ -1976,43 +1983,25 @@ end;
procedure TKPAnsi.WMTimer(var Msg: TWMTimer);
var
DC: HDC;
Line: PTermLine;
X: Integer;
Y: Integer;
NeedFlip: Boolean;
begin
FBlinkOn := not FBlinkOn;
NeedFlip := FRenderPending;
FRenderPending := False;
{ Toggle text blink phase by swapping which buffer is displayed. }
{ Buffers are already up-to-date; just BitBlt the other one. }
Inc(FBlinkPhase);
if FBlinkPhase > 1 then
FBlinkPhase := 0;
if not HandleAllocated then
Exit;
if FBufDC[0] = 0 then
Exit;
DC := GetDC(Handle);
BitBlt(DC, 0, 0, FBufW, FBufH,
FBufDC[FBlinkPhase], 0, 0, SRCCOPY);
{ Cursor overlay }
if FCursorVisible and FBlinkOn and (FScrollPos = 0) and
(FCursorRow >= 0) and (FCursorRow < FRows) and
(FCursorRow < FScreen.Count) and
(FCursorCol >= 0) and (FCursorCol < FCols) then
{ Blink counter: toggle cursor and text blink every BlinkInterval ticks }
Inc(FBlinkCount);
if FBlinkCount >= BlinkInterval then
begin
Line := FScreen[FCursorRow];
X := FCursorCol * FCellWidth;
Y := FCursorRow * FCellHeight;
SetTextColor(DC, ColorToRGB(Line^.Cells[FCursorCol].BG));
SetBkColor(DC, ColorToRGB(Line^.Cells[FCursorCol].FG));
WinProcs.TextOut(DC, X, Y, @Line^.Cells[FCursorCol].Ch, 1);
FBlinkCount := 0;
FBlinkOn := not FBlinkOn;
Inc(FBlinkPhase);
if FBlinkPhase > 1 then
FBlinkPhase := 0;
NeedFlip := True;
end;
ReleaseDC(Handle, DC);
if NeedFlip then
FlipToScreen;
end;