Join wait: animated waiting screen instead of "Not Responding"
The relay seat-wait loop (RelayRequestSeat -- the join/join_lan "patient
walk-up") blocked the window thread's message pump for its whole 30-minute
patience window: dial (2s timeout) -> Sleep(2000) -> repeat, with status
going only to the parent bat console. Windows flagged the frozen game
window "Not Responding" until the operator started the session
(user-reported).
- BTWaitScreenPaint (L4VIDEO.cpp): a GDI waiting screen on the main window
-- headline + detail line in the console green + a 12-segment marching
spinner phased on the tick clock. Pure GDI: flicker-free during the
seat wait (no D3D presenting yet), and re-painted after each
ExecuteIdle Present so the WaitingForEgg phase (previously a bare black
frame) shows "WAITING FOR MISSION ASSIGNMENT" too.
- RelayWaitTick (L4NET.CPP): PeekMessage pump + paint. The outer dial
loop's Sleep(2000) becomes 20 pumped 100ms slices ("WAITING FOR THE
OPERATOR'S SESSION" / "session at <ip:port> -- close this window to
cancel"); the inner seat-reply wait pumps per 200ms select slice.
Verified live against a dead relay address: window-only PrintWindow capture
shows the animated screen, and user32 IsHungAppWindow (the API behind "Not
Responding") reports FALSE throughout the wait.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
af22c4c78d
commit
f5e0ffcd14
@@ -8772,6 +8772,66 @@ void DPLRenderer::ExecuteImplementation(RendererComplexity, RendererOrigin::Inte
|
||||
#endif
|
||||
}
|
||||
|
||||
//
|
||||
// BTWaitScreenPaint -- the animated "waiting" screen (join/join_lan UX fix,
|
||||
// 2026-07-22). Pure GDI over the main window: used while the relay seat-wait
|
||||
// loop blocks startup (no D3D presenting yet -> flicker-free) and re-painted
|
||||
// after each ExecuteIdle Present during WaitingForEgg (the black idle frame).
|
||||
// A 12-segment spinner phases on the tick clock; text centered above it.
|
||||
//
|
||||
void BTWaitScreenPaint(const char *line1, const char *line2)
|
||||
{
|
||||
extern void *BTResolveMainWindow();
|
||||
HWND wnd = (HWND)BTResolveMainWindow();
|
||||
if (wnd == NULL)
|
||||
return;
|
||||
HDC dc = GetDC(wnd);
|
||||
if (dc == NULL)
|
||||
return;
|
||||
RECT rc;
|
||||
GetClientRect(wnd, &rc);
|
||||
int w = rc.right - rc.left, h = rc.bottom - rc.top;
|
||||
FillRect(dc, &rc, (HBRUSH)GetStockObject(BLACK_BRUSH));
|
||||
|
||||
static HFONT s_font = NULL, s_fontDim = NULL;
|
||||
if (s_font == NULL)
|
||||
{
|
||||
s_font = CreateFontA(30, 0, 0, 0, FW_BOLD, 0, 0, 0, DEFAULT_CHARSET,
|
||||
OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS, DEFAULT_QUALITY,
|
||||
FIXED_PITCH | FF_MODERN, "Consolas");
|
||||
s_fontDim = CreateFontA(20, 0, 0, 0, FW_NORMAL, 0, 0, 0, DEFAULT_CHARSET,
|
||||
OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS, DEFAULT_QUALITY,
|
||||
FIXED_PITCH | FF_MODERN, "Consolas");
|
||||
}
|
||||
SetBkMode(dc, TRANSPARENT);
|
||||
HGDIOBJ old_font = SelectObject(dc, s_font);
|
||||
SetTextColor(dc, RGB(64, 255, 64));
|
||||
RECT r1 = { 0, h / 2 - 70, w, h / 2 - 30 };
|
||||
DrawTextA(dc, line1, -1, &r1, DT_CENTER | DT_SINGLELINE | DT_VCENTER);
|
||||
SelectObject(dc, s_fontDim);
|
||||
SetTextColor(dc, RGB(24, 140, 24));
|
||||
RECT r2 = { 0, h / 2 - 24, w, h / 2 + 8 };
|
||||
DrawTextA(dc, line2, -1, &r2, DT_CENTER | DT_SINGLELINE | DT_VCENTER);
|
||||
SelectObject(dc, old_font);
|
||||
|
||||
// the spinner: 12 segments, three brightness tiers marching clockwise
|
||||
int cx = w / 2, cy = h / 2 + 80, r_in = 18, r_out = 34;
|
||||
int phase = (int)((GetTickCount() / 80UL) % 12UL);
|
||||
for (int i = 0; i < 12; ++i)
|
||||
{
|
||||
int age = (i - phase + 12) % 12;
|
||||
int lum = (age == 0) ? 255 : (age < 3) ? 150 : 60;
|
||||
HPEN pen = CreatePen(PS_SOLID, 4, RGB(lum / 4, lum, lum / 4));
|
||||
HGDIOBJ old_pen = SelectObject(dc, pen);
|
||||
double a = i * 3.14159265 / 6.0;
|
||||
MoveToEx(dc, cx + (int)(r_in * cos(a)), cy + (int)(r_in * sin(a)), NULL);
|
||||
LineTo(dc, cx + (int)(r_out * cos(a)), cy + (int)(r_out * sin(a)));
|
||||
SelectObject(dc, old_pen);
|
||||
DeleteObject(pen);
|
||||
}
|
||||
ReleaseDC(wnd, dc);
|
||||
}
|
||||
|
||||
void DPLRenderer::ExecuteIdle()
|
||||
{
|
||||
HRESULT hr;
|
||||
@@ -8782,7 +8842,13 @@ void DPLRenderer::ExecuteIdle()
|
||||
|
||||
hr = mDevice->EndScene();
|
||||
|
||||
if (mDevice->Present(NULL, NULL, NULL, NULL) == D3DERR_DEVICELOST)
|
||||
// WaitingForEgg is a black frame -- overlay the waiting screen (GDI,
|
||||
// repainted after every Present; join/join_lan UX fix 2026-07-22).
|
||||
{
|
||||
HRESULT present_hr = mDevice->Present(NULL, NULL, NULL, NULL);
|
||||
BTWaitScreenPaint("WAITING FOR MISSION ASSIGNMENT",
|
||||
"the operator starts the session from the console");
|
||||
if (present_hr == D3DERR_DEVICELOST)
|
||||
{
|
||||
int bbCount = mPresentParams.BackBufferCount;
|
||||
int bbWidth = mPresentParams.BackBufferWidth;
|
||||
@@ -8797,6 +8863,7 @@ void DPLRenderer::ExecuteIdle()
|
||||
mPresentParams.BackBufferWidth = bbWidth;
|
||||
mPresentParams.BackBufferHeight = bbHeight;
|
||||
}
|
||||
}
|
||||
}
|
||||
//
|
||||
//#############################################################################
|
||||
|
||||
Reference in New Issue
Block a user