mirror of
https://git.vladimir.cc/vladimir/ewsdr.git
synced 2026-08-25 20:37:33 +00:00
fix(cw): обрыв манипуляции — поколение, а не флаг: пакет следом его отменял
Один дефект в двух местах: и TCWElemPlayer (чужая манипуляция по TCI), и TCWSender (передача текста — F1..F8, набор в терминале, CAT KY) сбрасывали FAbortReq прямо в Enqueue. Поток замечает обрыв только на очередном срезе Hold, то есть в пределах 5 мс; всё, что прилетело в это окно, снимало флаг, и уже отданный AbortPlay/AbortSending пропадал бесследно. Чем это кончается в эфире: обрыв существует ровно затем, чтобы касание манипулятора, снятие MOX или уход из телеграфа прекратили программную манипуляцию (как «key hit» в Thetis/pihpsdr). Проглоченный обрыв означает, что оператор взял ключ в руки, а софт продолжает манипулировать вместе с ним. Источники, кормящие Enqueue, ровно такие, чтобы в 5 мс попадать: клиент TCI шлёт элементы KEYER десятками в секунду; терминал отдаёт набор В ЭФИР ПО СИМВОЛУ на нажатие клавиши; поле текста KY — 25 знаков, поэтому длинное сообщение логгер шлёт несколькими командами подряд. У передачи текста цена выше: AbortSending чистит FPending, но взятое сообщение живёт в локальной переменной потока, и доигрывается ВЕСЬ его остаток — замер на стенде дал 620 мс (остаток 720-мс тире на 5 WPM), у KEYER — 742 мс. Лечение общее: вместо флага счётчик поколений. AbortPlay/AbortSending его инкрементируют, Enqueue не трогает вовсе, поток несёт своё поколение (Take, Aborted, Hold, SendChar) и принимает новое только сам — когда отпустил ключ и бросил очередь. У TCWSender есть точка получше: текст и поколение берутся одним заходом под лок (TakeText(out Gen)), а это закрывает заодно и вторую точку сброса — ту, что стояла в начале Execute. Добор по ходу передачи стал TakeMore(Gen): после обрыва не берём ничего, и набранное ПОСЛЕ него не теряется вместе с брошенным, а уходит следующим сообщением со своим поколением. Стенд: 244 проверки (было 238). В C2 — регрессия на KEYER, новая часть C3 на передачу текста (раньше TCWSender стендом не покрывался вовсе): длительность точки по скорости, старт сообщения, обрыв с пакетом следом, и что после обрыва передача снова идёт. Обе регрессии проверены негативным контролем — на старом CWMorse.pas они падают с теми самыми 742 и 620 мс. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -1039,6 +1039,7 @@ var
|
||||
Ev: TCWKeyEvent;
|
||||
i, Waited, Bad: Integer;
|
||||
D1, D2, D3: Int64;
|
||||
T0: QWord;
|
||||
begin
|
||||
WriteLn('C2. Чужая манипуляция (KEYER)');
|
||||
Log := TKeyLog.Create;
|
||||
@@ -1108,6 +1109,32 @@ begin
|
||||
Check('KEYER: обрыв гасит очередь', Log.Count = Bad, IntToStr(Log.Count - Bad));
|
||||
Check('KEYER: после обрыва ключ отпущен',
|
||||
(Log.Count > 0) and (not Log.Down[Log.Count - 1]));
|
||||
|
||||
// ★Обрыв нельзя отменить пакетом из сети. Клиент шлёт элементы пачками по
|
||||
// несколько десятков в секунду; такой пакет, прилетевший в те миллисекунды,
|
||||
// пока поток спит в Hold, раньше снимал флаг обрыва — оператор трогает
|
||||
// манипулятор, а чужая манипуляция продолжает держать ключ до конца
|
||||
// текущего элемента. Меряем задержку отпускания от момента обрыва.
|
||||
for i := 0 to 4 do P.Enqueue(True, 400);
|
||||
Sleep(60);
|
||||
Log.Count := 0; // ключ уже замкнут — ждём именно отпускания
|
||||
T0 := GetTickCount64;
|
||||
P.AbortPlay;
|
||||
P.Enqueue(True, 400); // «пакет пришёл следом за обрывом»
|
||||
Waited := 0;
|
||||
while (Log.Count = 0) and (Waited < 1000) do
|
||||
begin
|
||||
Sleep(5);
|
||||
Inc(Waited, 5);
|
||||
end;
|
||||
// Отсчёт по стенным часам: Log.At отсчитывается от ПЕРВОГО фронта пачки,
|
||||
// а нам нужна задержка от самого обрыва (опрос с шагом 5 мс).
|
||||
D1 := Int64(GetTickCount64) - Int64(T0);
|
||||
Check('KEYER: обрыв не отменяется пакетом следом',
|
||||
(Log.Count > 0) and (not Log.Down[0]) and (D1 < 100),
|
||||
Format('%d/%d', [Log.Count, D1]));
|
||||
P.AbortPlay; // и хвост этого пакета тоже гасим
|
||||
Sleep(120);
|
||||
// Нулевые и отрицательные длительности не элементы: первое нажатие клиента
|
||||
// (keyer:0,true,0) не должно порождать ни одного фронта.
|
||||
Log.Count := 0;
|
||||
@@ -1122,6 +1149,99 @@ begin
|
||||
end;
|
||||
end;
|
||||
|
||||
procedure TestTextSender;
|
||||
// Передача текста (F1..F8, набор в терминале, CAT KY). Проверяем не «пошли
|
||||
// фронты», а что ОБРЫВ работает: касание манипулятора и снятие MOX обязаны
|
||||
// прекратить программную манипуляцию, чем бы её ни кормили.
|
||||
var
|
||||
Log: TKeyLog;
|
||||
S: TCWSender;
|
||||
Ev: TCWKeyEvent;
|
||||
Waited: Integer;
|
||||
D: Int64;
|
||||
T0: QWord;
|
||||
begin
|
||||
WriteLn('C3. Передача текста (KY / F1..F8)');
|
||||
Log := TKeyLog.Create;
|
||||
Ev := Log.Key;
|
||||
S := TCWSender.Create(Ev);
|
||||
try
|
||||
// 'E' на 20 WPM — одна точка длиной в один Dit (1200/20 = 60 мс).
|
||||
S.SetSpeed(20, 50);
|
||||
S.Enqueue('E');
|
||||
Waited := 0;
|
||||
while (Log.Count < 2) and (Waited < 2000) do
|
||||
begin
|
||||
Sleep(5);
|
||||
Inc(Waited, 5);
|
||||
end;
|
||||
Sleep(50);
|
||||
Check('KY: точка — два фронта', Log.Count = 2, IntToStr(Log.Count));
|
||||
if Log.Count >= 2 then
|
||||
begin
|
||||
D := Int64(Log.At[1]) - Int64(Log.At[0]);
|
||||
Check('KY: длительность точки по скорости', Abs(D - 60) < 30, IntToStr(D));
|
||||
end
|
||||
else
|
||||
Check('KY: длительность точки по скорости', False);
|
||||
|
||||
// ★Обрыв нельзя отменить символом, набранным следом. Терминал шлёт текст
|
||||
// ПО СИМВОЛУ на нажатие клавиши, логгер — кусками по 25 знаков в KY;
|
||||
// такой символ, пришедший в те миллисекунды, пока поток спит в Hold,
|
||||
// раньше снимал флаг обрыва — и в эфир уходил весь остаток сообщения,
|
||||
// хотя оператор уже взял манипулятор. 5 WPM: тире длиной 720 мс, попасть
|
||||
// в него легко.
|
||||
S.SetSpeed(5, 50);
|
||||
Log.Count := 0;
|
||||
S.Enqueue('OOOO');
|
||||
// Ждём не «появился фронт», а замыкания ключа ЭТИМ сообщением: предыдущее
|
||||
// ещё доигрывает межзнаковую паузу, и его завершающий Key(False) прилетит
|
||||
// сюда же.
|
||||
Waited := 0;
|
||||
while (Waited < 3000)
|
||||
and not ((Log.Count > 0) and Log.Down[Log.Count - 1]) do
|
||||
begin
|
||||
Sleep(5);
|
||||
Inc(Waited, 5);
|
||||
end;
|
||||
Check('KY: сообщение пошло',
|
||||
(Log.Count > 0) and Log.Down[Log.Count - 1], IntToStr(Log.Count));
|
||||
Sleep(100); // середина первого тире (720 мс на 5 WPM)
|
||||
Log.Count := 0; // ключ уже замкнут — ждём именно отпускания
|
||||
T0 := GetTickCount64;
|
||||
S.AbortSending;
|
||||
S.Enqueue('E'); // «символ пришёл следом за обрывом»
|
||||
Waited := 0;
|
||||
while (Log.Count = 0) and (Waited < 2000) do
|
||||
begin
|
||||
Sleep(5);
|
||||
Inc(Waited, 5);
|
||||
end;
|
||||
D := Int64(GetTickCount64) - Int64(T0);
|
||||
Check('KY: обрыв не отменяется символом следом',
|
||||
(Log.Count > 0) and (not Log.Down[0]) and (D < 100),
|
||||
Format('%d/%d', [Log.Count, D]));
|
||||
S.AbortSending; // и хвост этого символа тоже гасим
|
||||
Sleep(150);
|
||||
|
||||
// Обрыв не глушит передачу навсегда: следующее сообщение играется.
|
||||
Log.Count := 0;
|
||||
S.SetSpeed(20, 50);
|
||||
S.Enqueue('E');
|
||||
Waited := 0;
|
||||
while (Log.Count < 2) and (Waited < 2000) do
|
||||
begin
|
||||
Sleep(5);
|
||||
Inc(Waited, 5);
|
||||
end;
|
||||
Check('KY: после обрыва передача снова идёт', Log.Count = 2,
|
||||
IntToStr(Log.Count));
|
||||
finally
|
||||
S.Free;
|
||||
Log.Free;
|
||||
end;
|
||||
end;
|
||||
|
||||
{ ═══════════════════════════════════════════════════════════════════════════
|
||||
D. Живой сервер: команды потоков
|
||||
═══════════════════════════════════════════════════════════════════════════ }
|
||||
@@ -2116,6 +2236,7 @@ begin
|
||||
TestStreamOut;
|
||||
TestRecorder;
|
||||
TestKeyPlayer;
|
||||
TestTextSender;
|
||||
TestServer;
|
||||
TestEndToEnd;
|
||||
WriteLn;
|
||||
|
||||
Reference in New Issue
Block a user