diff --git a/delphi/KPANSI.PAS b/delphi/KPANSI.PAS index a527bbf..7f4cde1 100644 --- a/delphi/KPANSI.PAS +++ b/delphi/KPANSI.PAS @@ -143,6 +143,7 @@ type procedure InsertChars(N: Integer); procedure InsertLines(N: Integer); procedure ParseData(const S: string); + procedure ParseDataBuf(Buf: PChar; Len: Integer); procedure ParseSGR; procedure ProcessChar(Ch: Char); procedure RecalcCellSize; @@ -171,6 +172,7 @@ type procedure TickBlink; procedure Write(const S: string); procedure WriteDeferred(const S: string); + procedure WriteDeferredBuf(Buf: PChar; Len: Integer); property CursorCol: Integer read GetCursorCol; property CursorRow: Integer read GetCursorRow; published @@ -1554,29 +1556,38 @@ end; procedure TKPAnsi.ParseData(const S: string); -{ Process incoming data with an inlined fast path for printable characters. } -{ ~80% of BBS data is printable text in normal state. Inlining avoids the } -{ per-character method call to ProcessChar, and caching the Line pointer } -{ eliminates repeated TList lookups for consecutive chars on the same row. } +{ String wrapper -- delegates to ParseDataBuf for actual processing. } +begin + if Length(S) > 0 then + ParseDataBuf(@S[1], Length(S)); +end; + + +procedure TKPAnsi.ParseDataBuf(Buf: PChar; Len: Integer); +{ Process incoming data from a PChar buffer (no string allocation needed). } +{ Fast path batches runs of printable characters: colors are computed once } +{ per run, and cells are filled in a tight loop without per-character state } +{ checks. Run length is bounded by end of input, end of current row, or } +{ next non-printable character -- whichever comes first. } { } -{ Does NOT call FlipToScreen -- the caller (Write) calls FlipToScreen } -{ after ParseData returns, ensuring immediate rendering. } +{ Does NOT call FlipToScreen -- the caller handles rendering. } var - I: Integer; - Ch: Char; - Line: PTermLine; - FGIdx: Byte; - BGIdx: Byte; + I: Integer; + Line: PTermLine; + FGIdx: Byte; + BGIdx: Byte; + RunEnd: Integer; + Remaining: Integer; begin Line := nil; + I := 0; - for I := 1 to Length(S) do + while I < Len do begin - Ch := S[I]; - { Fast path: printable character in normal state } - if (FParseState = psNormal) and (Ch >= ' ') then + if (FParseState = psNormal) and (Buf[I] >= ' ') then begin + { Handle wrap at right margin } if FCursorCol >= FCols then begin if FWrapMode then @@ -1597,33 +1608,55 @@ begin if Line = nil then Line := FScreen[FCursorRow]; + { Compute colors once for the entire run } if FAttrBold then FGIdx := FAttrFG + 8 else FGIdx := FAttrFG; BGIdx := FAttrBG; + { Find run end: stop at control char, end of input, or end of row } + Remaining := FCols - FCursorCol; + RunEnd := I; + while (RunEnd < Len) and (Buf[RunEnd] >= ' ') and + (RunEnd - I < Remaining) do + Inc(RunEnd); + + { Fill cells in tight loop -- no per-character state/wrap checks } if FAttrReverse then begin - Line^.Cells[FCursorCol].FG := BGIdx; - Line^.Cells[FCursorCol].BG := FGIdx; + while I < RunEnd do + begin + Line^.Cells[FCursorCol].Ch := Buf[I]; + Line^.Cells[FCursorCol].FG := BGIdx; + Line^.Cells[FCursorCol].BG := FGIdx; + Line^.Cells[FCursorCol].Bold := FAttrBold; + Line^.Cells[FCursorCol].Blink := FAttrBlink; + Inc(FCursorCol); + Inc(I); + end; end else begin - Line^.Cells[FCursorCol].FG := FGIdx; - Line^.Cells[FCursorCol].BG := BGIdx; + while I < RunEnd do + begin + Line^.Cells[FCursorCol].Ch := Buf[I]; + Line^.Cells[FCursorCol].FG := FGIdx; + Line^.Cells[FCursorCol].BG := BGIdx; + Line^.Cells[FCursorCol].Bold := FAttrBold; + Line^.Cells[FCursorCol].Blink := FAttrBlink; + Inc(FCursorCol); + Inc(I); + end; end; - Line^.Cells[FCursorCol].Ch := Ch; - Line^.Cells[FCursorCol].Bold := FAttrBold; - Line^.Cells[FCursorCol].Blink := FAttrBlink; FDirtyRow[FCursorRow] := True; - Inc(FCursorCol); end else begin { Slow path: control chars, escape sequences } Line := nil; - ProcessChar(Ch); + ProcessChar(Buf[I]); + Inc(I); end; end; @@ -1637,7 +1670,6 @@ begin FScrollbarDirty := True; FAllDirty := True; end; - end; @@ -2453,6 +2485,13 @@ begin end; +procedure TKPAnsi.WriteDeferredBuf(Buf: PChar; Len: Integer); +begin + if Len > 0 then + ParseDataBuf(Buf, Len); +end; + + { ----------------------------------------------------------------------- } { Component registration } { ----------------------------------------------------------------------- } diff --git a/delphi/KPCOMM.PAS b/delphi/KPCOMM.PAS index b76f178..f02821c 100644 --- a/delphi/KPCOMM.PAS +++ b/delphi/KPCOMM.PAS @@ -68,6 +68,7 @@ type public constructor Create(AOwner: TComponent); override; destructor Destroy; override; + function ReadInputBuf(Buf: PChar; BufSize: Integer): Integer; property Input: string read GetInput; property Output: string write SetOutput; property InBufferCount: Integer read GetInBufferCount; @@ -273,6 +274,22 @@ begin end; +function TKPComm.ReadInputBuf(Buf: PChar; BufSize: Integer): Integer; +{ Read up to BufSize bytes into a caller-supplied buffer. Bypasses the } +{ 255-byte short string limit of the Input property, allowing the drain } +{ loop to read 2048+ bytes per call and reducing ReadComm/API overhead. } +var + BytesRead: Integer; +begin + Result := 0; + if not FPortOpen or (FCommId < 0) then + Exit; + BytesRead := ReadComm(FCommId, Buf, BufSize); + if BytesRead > 0 then + Result := BytesRead; +end; + + function TKPComm.GetOutBufferCount: Integer; var Stat: TComStat; diff --git a/delphi/TESTMAIN.PAS b/delphi/TESTMAIN.PAS index 65e6ecb..fa472cc 100644 --- a/delphi/TESTMAIN.PAS +++ b/delphi/TESTMAIN.PAS @@ -167,9 +167,11 @@ end; procedure TMainForm.Run; const RenderMs = 50; { Minimum ms between renders during bulk flow (20 fps) } + BufSize = 2048; { Read buffer -- 8x larger than 255-byte string limit } var Msg: TMsg; - S: string; + Buf: array[0..BufSize - 1] of Char; + Len: Integer; HasData: Boolean; Now: Longint; LastRenderTick: Longint; @@ -194,16 +196,16 @@ begin if FDone then Break; - { Drain all available serial data before rendering. Batching } - { many chunks into one render pass avoids per-chunk GDI overhead. } - { Messages are checked between chunks so keyboard stays responsive.} + { Drain all available serial data before rendering. Reads up to } + { 2048 bytes per call, bypassing the 255-byte short string limit. } + { Messages are checked between chunks so keyboard stays responsive. } HasData := False; if FComm.PortOpen then begin - S := FComm.Input; - while (Length(S) > 0) and not FDone do + Len := FComm.ReadInputBuf(@Buf, BufSize); + while (Len > 0) and not FDone do begin - FAnsi.WriteDeferred(S); + FAnsi.WriteDeferredBuf(@Buf, Len); HasData := True; { Check for messages between chunks } while PeekMessage(Msg, 0, 0, 0, pm_Remove or pm_NoYield) do @@ -216,7 +218,7 @@ begin TranslateMessage(Msg); DispatchMessage(Msg); end; - S := FComm.Input; + Len := FComm.ReadInputBuf(@Buf, BufSize); end; end;