Files
UnrealEngineUWP/Engine/Source/Developer/DesktopPlatform/Private/Windows/WindowsNativeFeedbackContext.cpp
devin doucette 14a2e7482d Logging: Updated Windows and Linux feedback contexts to support structured logging
This sets up the base FFeedbackContext to do most of the work because there is very little behavioral difference between platforms, yet the majority of the code is duplicated for each platform.

#jira UE-152840
#preflight 63b45fe435203bc7aab23837
#rb Zousar.Shaker
#rnx

[CL 23567792 by devin doucette in ue5-main branch]
2023-01-03 13:07:24 -05:00

372 lines
12 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "WindowsNativeFeedbackContext.h"
#include "HAL/ThreadHeartBeat.h"
#include "Logging/StructuredLog.h"
#include "Misc/CoreMisc.h"
#include "Misc/OutputDeviceHelper.h"
#include "Misc/OutputDeviceConsole.h"
#include "Misc/OutputDeviceRedirector.h"
#include "Misc/App.h"
#include "Internationalization/Internationalization.h"
#include "Windows/WindowsPlatformApplicationMisc.h"
#include "Windows/AllowWindowsPlatformTypes.h"
FWindowsNativeFeedbackContext::FWindowsNativeFeedbackContext()
: FFeedbackContext()
, Context( NULL )
, hThread( NULL )
, hCloseEvent( NULL )
, hUpdateEvent( NULL )
, Progress( 0.0f )
, bReceivedUserCancel( false )
, bShowCancelButton( false )
{
}
FWindowsNativeFeedbackContext::~FWindowsNativeFeedbackContext()
{
DestroySlowTaskWindow();
}
void FWindowsNativeFeedbackContext::Serialize(const TCHAR* V, ELogVerbosity::Type Verbosity, const FName& Category)
{
Serialize(V, Verbosity, Category, -1.0);
}
void FWindowsNativeFeedbackContext::Serialize(const TCHAR* V, ELogVerbosity::Type Verbosity, const FName& Category, double Time)
{
FFeedbackContext::Serialize(V, Verbosity, Category, Time);
// Buffer up the output during a slow task so that we can dump it all to the log console if the show log button is clicked
if (GIsSlowTask)
{
FScopeLock Lock(&CriticalSection);
if (hThread)
{
LogOutput += V;
LogOutput += TEXT("\r\n");
SetEvent(hUpdateEvent);
}
}
}
void FWindowsNativeFeedbackContext::SerializeRecord(const UE::FLogRecord& Record)
{
FFeedbackContext::SerializeRecord(Record);
// Buffer up the output during a slow task so that we can dump it all to the log console if the show log button is clicked
if (GIsSlowTask)
{
TStringBuilder<512> Message;
Record.FormatMessageTo(Message);
Message.Append(TEXTVIEW("\r\n"));
FScopeLock Lock(&CriticalSection);
if (hThread)
{
LogOutput += Message.ToView();
SetEvent(hUpdateEvent);
}
}
}
bool FWindowsNativeFeedbackContext::ReceivedUserCancel()
{
FScopeLock Lock(&CriticalSection);
return bReceivedUserCancel;
}
void FWindowsNativeFeedbackContext::StartSlowTask( const FText& Task, bool bShouldShowCancelButton )
{
FFeedbackContext::StartSlowTask( Task, bShouldShowCancelButton );
CreateSlowTaskWindow(Task, bShouldShowCancelButton);
}
void FWindowsNativeFeedbackContext::FinalizeSlowTask( )
{
FFeedbackContext::FinalizeSlowTask( );
DestroySlowTaskWindow();
}
void FWindowsNativeFeedbackContext::ProgressReported( const float TotalProgressInterp, FText DisplayMessage )
{
FScopeLock Lock(&CriticalSection);
if(hThread != NULL)
{
Progress = TotalProgressInterp;
Status = DisplayMessage.ToString();
SetEvent(hUpdateEvent);
}
}
FContextSupplier* FWindowsNativeFeedbackContext::GetContext() const
{
return Context;
}
void FWindowsNativeFeedbackContext::SetContext( FContextSupplier* InSupplier )
{
Context = InSupplier;
}
void FWindowsNativeFeedbackContext::CreateSlowTaskWindow(const FText &InStatus, bool bInShowCancelButton)
{
FScopeLock Lock(&CriticalSection);
if(hThread == NULL && !GIsSilent && !FApp::IsUnattended() && !IsRunningCommandlet())
{
Status = InStatus.ToString();
Progress = 0.0f;
LogOutput.Empty();
bReceivedUserCancel = false;
bShowCancelButton = bInShowCancelButton;
hCloseEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
hUpdateEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
hThread = CreateThread(NULL, 0, &SlowTaskThreadProc, this, 0, NULL);
}
}
void FWindowsNativeFeedbackContext::DestroySlowTaskWindow()
{
FScopeLock Lock(&CriticalSection);
if(hThread != NULL)
{
SetEvent(hCloseEvent);
CriticalSection.Unlock();
WaitForSingleObject(hThread, INFINITE);
CriticalSection.Lock();
CloseHandle(hThread);
hThread = NULL;
CloseHandle(hCloseEvent);
hCloseEvent = NULL;
CloseHandle(hUpdateEvent);
hUpdateEvent = NULL;
LogOutput.Empty();
}
}
DWORD FWindowsNativeFeedbackContext::SlowTaskThreadProc(void* ThreadParam)
{
FWindowsNativeFeedbackContext* Context = (FWindowsNativeFeedbackContext*)ThreadParam;
HINSTANCE HInstance = (HINSTANCE)GetModuleHandle(NULL);
extern APPLICATIONCORE_API HWND GetSplashScreenWindowHandle();
HWND ParentWindowHandle = GetSplashScreenWindowHandle();
WNDCLASSEX WndClassEx;
ZeroMemory(&WndClassEx, sizeof(WndClassEx));
WndClassEx.cbSize = sizeof(WndClassEx);
WndClassEx.style = CS_HREDRAW | CS_VREDRAW | (Context->bShowCancelButton? 0 : CS_NOCLOSE);
WndClassEx.lpfnWndProc = &SlowTaskWindowProc;
WndClassEx.hIcon = LoadIcon(HInstance, MAKEINTRESOURCE(FWindowsPlatformApplicationMisc::GetAppIcon()));
WndClassEx.hCursor = LoadCursor(NULL, IDC_ARROW);
WndClassEx.hInstance = HInstance;
WndClassEx.hbrBackground = (HBRUSH)(COLOR_BTNFACE + 1);
WndClassEx.lpszClassName = TEXT("FFeedbackContextWindows");
ATOM WndClassAtom = RegisterClassEx(&WndClassEx);
NONCLIENTMETRICS NonClientMetrics;
NonClientMetrics.cbSize = sizeof(NonClientMetrics);
SystemParametersInfo(SPI_GETNONCLIENTMETRICS, sizeof(NonClientMetrics), &NonClientMetrics, 0);
HANDLE hFont = CreateFontIndirect(&NonClientMetrics.lfMessageFont);
int FontHeight = -MulDiv(8, GetDeviceCaps(GetDC(NULL), LOGPIXELSY), 72);
HANDLE hLogFont = CreateFontW(FontHeight, 0, 0, 0, FW_NORMAL, FALSE, FALSE, FALSE, ANSI_CHARSET, OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS, ANTIALIASED_QUALITY, FIXED_PITCH | FF_MODERN, TEXT("Courier New"));
TEXTMETRIC TextMetric;
HDC hDC = CreateCompatibleDC(NULL);
HGDIOBJ hPrevObj = SelectObject(hDC, hFont);
GetTextMetrics(hDC, &TextMetric);
SelectObject(hDC, hPrevObj);
DeleteDC(hDC);
FWindowParams Params;
Params.Context = Context;
Params.ScaleX = TextMetric.tmAveCharWidth;
Params.ScaleY = TextMetric.tmHeight;
Params.StandardW = Params.ScaleX * 80;
Params.StandardH = Params.ScaleY * 4;
Params.bLogVisible = false;
DWORD WindowStyle = WS_VISIBLE | WS_CAPTION | WS_SYSMENU | WS_THICKFRAME;
RECT WindowRect;
ZeroMemory(&WindowRect, sizeof(WindowRect));
WindowRect.left = (GetSystemMetrics(SM_CXSCREEN) - Params.StandardW) / 2;
WindowRect.top = (GetSystemMetrics(SM_CYSCREEN) - Params.StandardH) / 2;
WindowRect.right = WindowRect.left + Params.StandardW;
WindowRect.bottom = WindowRect.top + Params.StandardH;
AdjustWindowRectEx(&WindowRect, WindowStyle, 0, 0);
const TCHAR* WindowClassName = MAKEINTATOM( WndClassAtom );
HWND hWnd = CreateWindow(WindowClassName, TEXT("Unreal Engine"), WindowStyle, WindowRect.left, WindowRect.top, WindowRect.right - WindowRect.left, WindowRect.bottom - WindowRect.top, ParentWindowHandle, NULL, HInstance, NULL);
SetWindowLongPtr(hWnd, GWLP_USERDATA, (LONG_PTR)&Params);
SendMessageW(hWnd, WM_SETFONT, (WPARAM)hFont, 0);
HWND hWndOpenLog = CreateWindow(WC_BUTTON, TEXT("Show log"), BS_CENTER | BS_VCENTER | BS_PUSHBUTTON | BS_TEXT | WS_CHILD | WS_VISIBLE, 10, 10, 10, 10, hWnd, (HMENU)ShowLogCtlId, HInstance, NULL);
SendMessageW(hWndOpenLog, WM_SETFONT, (WPARAM)hFont, 0);
HWND hWndStatus = CreateWindow(WC_STATIC, TEXT(""), SS_CENTER | WS_CHILD | WS_VISIBLE, 10, 10, 10, 10, hWnd, (HMENU)StatusCtlId, HInstance, NULL);
SendMessageW(hWndStatus, WM_SETFONT, (WPARAM)hFont, 0);
HWND hWndProgress = CreateWindowEx(0, PROGRESS_CLASS, TEXT(""), WS_CHILD | WS_VISIBLE, 10, 10, 10, 10, hWnd, (HMENU)ProgressCtlId, HInstance, NULL);
SendMessageW(hWndProgress, PBM_SETRANGE32, 0, 1000);
HWND hWndLogOutput = CreateWindowEx(WS_EX_STATICEDGE, WC_EDIT, TEXT(""), ES_MULTILINE | ES_READONLY | WS_HSCROLL | WS_VSCROLL | WS_CHILD | WS_VISIBLE, 10, 10, 10, 10, hWnd, (HMENU)LogOutputCtlId, HInstance, NULL);
SendMessageW(hWndLogOutput, WM_SETFONT, (WPARAM)hLogFont, 0);
LayoutControls(hWnd, &Params);
SetEvent(Context->hUpdateEvent);
ShowWindow(hWnd, SW_SHOW);
UpdateWindow(hWnd);
SetForegroundWindow(hWnd);
FString PrevStatus;
float PrevProgress = 0.0f;
int32 PrevLogOutputLength = 0;
for(;;)
{
HANDLE Handles[] = { Context->hCloseEvent, Context->hUpdateEvent };
DWORD Result = MsgWaitForMultipleObjects(2, Handles, 0, INFINITE, QS_ALLEVENTS);
if(Result == WAIT_OBJECT_0)
{
break;
}
else if(Result == WAIT_OBJECT_0 + 1)
{
FScopeLock Lock(&Context->CriticalSection);
if(Context->Status != PrevStatus)
{
SetWindowText(hWndStatus, *Context->Status);
PrevStatus = Context->Status;
}
if(Context->Progress != PrevProgress)
{
SendMessageW(hWndProgress, PBM_SETPOS, (int32)(Context->Progress * 1000.0f), 0);
PrevProgress = Context->Progress;
}
if(Context->LogOutput.Len() > PrevLogOutputLength)
{
SendMessageW(hWndLogOutput, EM_SETSEL, PrevLogOutputLength, PrevLogOutputLength);
SendMessageW(hWndLogOutput, EM_REPLACESEL, FALSE, (LPARAM)(*Context->LogOutput + PrevLogOutputLength));
SendMessageW(hWndLogOutput, EM_SCROLLCARET, 0, 0);
PrevLogOutputLength = Context->LogOutput.Len();
}
}
MSG Msg;
while(PeekMessage(&Msg, NULL, 0, 0, PM_REMOVE))
{
TranslateMessage(&Msg);
DispatchMessage(&Msg);
}
}
DestroyWindow(hWnd);
DeleteObject(hLogFont);
DeleteObject(hFont);
UnregisterClass(WindowClassName, HInstance);
return 0;
}
LRESULT CALLBACK FWindowsNativeFeedbackContext::SlowTaskWindowProc(HWND hWnd, UINT Msg, WPARAM wParam, LPARAM lParam)
{
switch (Msg)
{
case WM_COMMAND:
if(wParam == ShowLogCtlId)
{
FWindowParams *Params = (FWindowParams*)GetWindowLongPtr(hWnd, GWLP_USERDATA);
Params->bLogVisible ^= true;
RECT WindowRect;
GetClientRect(hWnd, &WindowRect);
WindowRect.bottom = Params->StandardH + (Params->bLogVisible? Params->ScaleY * 10 : 0);
AdjustWindowRectEx(&WindowRect, GetWindowLong(hWnd, GWL_STYLE), 0, 0);
SetWindowPos(hWnd, NULL, 0, 0, WindowRect.right - WindowRect.left, WindowRect.bottom - WindowRect.top, SWP_NOZORDER | SWP_NOMOVE);
SetDlgItemText(hWnd, ShowLogCtlId, Params->bLogVisible? TEXT("Hide log") : TEXT("Show log"));
ShowWindow(GetDlgItem(hWnd, LogOutputCtlId), Params->bLogVisible? SW_SHOW : SW_HIDE);
LayoutControls(hWnd, Params);
}
return 0;
case WM_SIZE:
{
FWindowParams *Params = (FWindowParams*)GetWindowLongPtr(hWnd, GWLP_USERDATA);
if(Params != NULL)
{
LayoutControls(hWnd, Params);
}
}
return 0;
case WM_GETMINMAXINFO:
{
FWindowParams *Params = (FWindowParams*)GetWindowLongPtr(hWnd, GWLP_USERDATA);
if(Params != NULL)
{
RECT WindowRect;
SetRect(&WindowRect, 0, 0, Params->StandardW, Params->StandardH + (Params->bLogVisible? (Params->ScaleY * 5) : 0));
AdjustWindowRectEx(&WindowRect, WS_CAPTION, 0, 0);
MINMAXINFO *MinMaxInfo = (MINMAXINFO*)lParam;
MinMaxInfo->ptMinTrackSize.x = WindowRect.right - WindowRect.left;
MinMaxInfo->ptMinTrackSize.y = WindowRect.bottom - WindowRect.top;
if(!Params->bLogVisible)
{
MinMaxInfo->ptMaxTrackSize.y = MinMaxInfo->ptMinTrackSize.y;
}
}
}
return 0;
case WM_CLOSE:
{
FWindowParams *Params = (FWindowParams*)GetWindowLongPtr(hWnd, GWLP_USERDATA);
FScopeLock Lock(&Params->Context->CriticalSection);
Params->Context->bReceivedUserCancel = true;
}
return 0;
}
return DefWindowProc(hWnd, Msg, wParam, lParam);
}
void FWindowsNativeFeedbackContext::LayoutControls(HWND hWnd, const FWindowParams* Params)
{
RECT ClientRect;
GetClientRect(hWnd, &ClientRect);
int32 MarginW = Params->ScaleX * 2;
int32 MarginH = Params->ScaleY;
int32 SplitX = ClientRect.right - (Params->ScaleX * 15);
int32 SplitY = Params->ScaleY * 4;
int32 ButtonH = (Params->ScaleY * 7) / 4;
HWND hWndOpenLog = GetDlgItem(hWnd, ShowLogCtlId);
MoveWindow(hWndOpenLog, SplitX, (SplitY - ButtonH) / 2, ClientRect.right - SplitX - MarginW, ButtonH, TRUE);
HWND hWndStatus = GetDlgItem(hWnd, StatusCtlId);
MoveWindow(hWndStatus, MarginW, MarginH, SplitX - (MarginW * 2), Params->ScaleY, TRUE);
HWND hWndProgress = GetDlgItem(hWnd, ProgressCtlId);
MoveWindow(hWndProgress, MarginW, MarginH + (Params->ScaleY * 3) / 2, SplitX - (MarginW * 2), (Params->ScaleY + 1) / 2, TRUE);
HWND hWndLogOutput = GetDlgItem(hWnd, LogOutputCtlId);
MoveWindow(hWndLogOutput, MarginW, SplitY, ClientRect.right - MarginW * 2, ClientRect.bottom - SplitY - MarginH, TRUE);
}
#include "Windows/HideWindowsPlatformTypes.h"