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
@@ -1506,6 +1506,24 @@ void L4NetworkManager::HostDisconnectedMessageHandler(HostDisconnectedMessage* H
|
|||||||
// relaySelf and everything downstream (egg self-match, HELLO) proceeds as if
|
// relaySelf and everything downstream (egg self-match, HELLO) proceeds as if
|
||||||
// BT_SELF had been set. SEAT_FULL / timeout => False.
|
// BT_SELF had been set. SEAT_FULL / timeout => False.
|
||||||
//
|
//
|
||||||
|
//
|
||||||
|
// RelayWaitTick -- pump the window thread + paint the animated waiting screen
|
||||||
|
// (join/join_lan UX fix 2026-07-22: the seat-wait loop blocked the message
|
||||||
|
// pump for its whole 30-min patience window, so the main window showed "Not
|
||||||
|
// Responding" until the console started the game).
|
||||||
|
//
|
||||||
|
static void RelayWaitTick(const char *line1, const char *line2)
|
||||||
|
{
|
||||||
|
MSG msg;
|
||||||
|
while (PeekMessageA(&msg, NULL, 0, 0, PM_REMOVE))
|
||||||
|
{
|
||||||
|
TranslateMessage(&msg);
|
||||||
|
DispatchMessageA(&msg);
|
||||||
|
}
|
||||||
|
extern void BTWaitScreenPaint(const char *, const char *);
|
||||||
|
BTWaitScreenPaint(line1, line2);
|
||||||
|
}
|
||||||
|
|
||||||
Logical L4NetworkManager::RelayRequestSeat()
|
Logical L4NetworkManager::RelayRequestSeat()
|
||||||
{
|
{
|
||||||
//
|
//
|
||||||
@@ -1541,7 +1559,17 @@ Logical L4NetworkManager::RelayRequestSeat()
|
|||||||
{
|
{
|
||||||
BTRelayWaitStatus("\n");
|
BTRelayWaitStatus("\n");
|
||||||
}
|
}
|
||||||
Sleep(2000);
|
{
|
||||||
|
char detail[128];
|
||||||
|
sprintf(detail, "session at %s:%d -- close this window to cancel",
|
||||||
|
inet_ntoa(relayGameAddress.sin_addr),
|
||||||
|
(int)ntohs(relayGameAddress.sin_port) - 1);
|
||||||
|
for (int slice = 0; slice < 20; ++slice) // 2s, pumped + animated
|
||||||
|
{
|
||||||
|
RelayWaitTick("WAITING FOR THE OPERATOR'S SESSION", detail);
|
||||||
|
Sleep(100);
|
||||||
|
}
|
||||||
|
}
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
Logical got_seat = False;
|
Logical got_seat = False;
|
||||||
@@ -1564,6 +1592,8 @@ Logical L4NetworkManager::RelayRequestSeat()
|
|||||||
tv.tv_usec = 200000;
|
tv.tv_usec = 200000;
|
||||||
if (select(0, &read_set, NULL, NULL, &tv) != 1)
|
if (select(0, &read_set, NULL, NULL, &tv) != 1)
|
||||||
{
|
{
|
||||||
|
RelayWaitTick("WAITING FOR THE OPERATOR'S SESSION",
|
||||||
|
"requesting a seat...");
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
int received = recv(seat_socket, reply + have,
|
int received = recv(seat_socket, reply + have,
|
||||||
|
|||||||
@@ -8772,6 +8772,66 @@ void DPLRenderer::ExecuteImplementation(RendererComplexity, RendererOrigin::Inte
|
|||||||
#endif
|
#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()
|
void DPLRenderer::ExecuteIdle()
|
||||||
{
|
{
|
||||||
HRESULT hr;
|
HRESULT hr;
|
||||||
@@ -8782,7 +8842,13 @@ void DPLRenderer::ExecuteIdle()
|
|||||||
|
|
||||||
hr = mDevice->EndScene();
|
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 bbCount = mPresentParams.BackBufferCount;
|
||||||
int bbWidth = mPresentParams.BackBufferWidth;
|
int bbWidth = mPresentParams.BackBufferWidth;
|
||||||
@@ -8797,6 +8863,7 @@ void DPLRenderer::ExecuteIdle()
|
|||||||
mPresentParams.BackBufferWidth = bbWidth;
|
mPresentParams.BackBufferWidth = bbWidth;
|
||||||
mPresentParams.BackBufferHeight = bbHeight;
|
mPresentParams.BackBufferHeight = bbHeight;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
//
|
//
|
||||||
//#############################################################################
|
//#############################################################################
|
||||||
|
|||||||
Reference in New Issue
Block a user