From 91508fd7cfa23738bae6d86d1b766451dae557bf Mon Sep 17 00:00:00 2001 From: Haibo Date: Mon, 19 Nov 2018 17:56:17 +0800 Subject: [PATCH] fix: the problem of window message loop blocking main loop --- core/base/Game.cpp | 260 ++++++++++++++++++++++++++------------- core/base/Game.h | 11 +- core/base/Input.cpp | 15 ++- core/base/Input.h | 20 ++- core/base/Transition.cpp | 2 +- core/base/Transition.h | 2 +- core/base/render.cpp | 63 +++++++--- core/base/render.h | 1 + core/base/window.cpp | 129 +------------------ core/base/window.h | 11 +- 10 files changed, 260 insertions(+), 254 deletions(-) diff --git a/core/base/Game.cpp b/core/base/Game.cpp index e23e33db..b6f20209 100644 --- a/core/base/Game.cpp +++ b/core/base/Game.cpp @@ -19,17 +19,17 @@ // THE SOFTWARE. #include "Game.h" -#include "Scene.h" -#include "Transition.h" -#include "Image.h" -#include "../utils/Player.h" -#include "../math/Matrix.hpp" #include "logs.h" #include "render.h" #include "input.h" #include "audio.h" #include "modules.h" +#include "Scene.h" +#include "Transition.h" +#include "../math/Matrix.hpp" #include +#include +#pragma comment (lib ,"imm32.lib") namespace easy2d { @@ -40,6 +40,8 @@ namespace easy2d , transition_(nullptr) , debug_enabled_(false) , initialized_(false) + , hwnd_(nullptr) + , window_inactived_(false) { ::CoInitialize(nullptr); } @@ -62,26 +64,41 @@ namespace easy2d debug_enabled_ = options.debug; - Window::Instance()->Init(options.title, options.width, options.height, options.icon, debug_enabled_); - devices::Graphics::Instance()->Init(Window::Instance()->GetHandle(), debug_enabled_); - devices::Input::Instance()->Init(debug_enabled_); + Window::Instance()->Init( + options.title, + options.width, + options.height, + options.icon, + Game::WndProc, + debug_enabled_ + ); + + const auto window = Window::Instance(); + hwnd_ = window->GetHandle(); + + ::SetWindowLongW(hwnd_, GWLP_USERDATA, PtrToUlong(this)); + + devices::Graphics::Instance()->Init(hwnd_, debug_enabled_); + devices::Input::Instance()->Init(hwnd_, window->GetContentScaleX(), window->GetContentScaleY(), debug_enabled_); devices::Audio::Instance()->Init(debug_enabled_); + // disable imm + ::ImmAssociateContext(hwnd_, nullptr); + HWND console = ::GetConsoleWindow(); if (debug_enabled_) { if (console == nullptr) { - // 显示一个新控制台 if (::AllocConsole()) { console = ::GetConsoleWindow(); - // 重定向输入输出 FILE * stdoutStream, *stdinStream, *stderrStream; freopen_s(&stdoutStream, "conout$", "w+t", stdout); freopen_s(&stdinStream, "conin$", "r+t", stdin); freopen_s(&stderrStream, "conout$", "w+t", stderr); - // 禁用控制台关闭按钮 + + // disable the close button of console HMENU hmenu = ::GetSystemMenu(console, FALSE); ::RemoveMenu(hmenu, SC_CLOSE, MF_BYCOMMAND); } @@ -95,12 +112,6 @@ namespace easy2d } } - ::SetWindowLongPtrW( - Window::Instance()->GetHandle(), - GWLP_USERDATA, - PtrToUlong(this) - ); - initialized_ = true; } @@ -115,46 +126,14 @@ namespace easy2d next_scene_ = nullptr; } - const auto window = Window::Instance(); - ::ShowWindow(window->GetHandle(), SW_SHOWNORMAL); - ::UpdateWindow(window->GetHandle()); - window->Poll(); + ::ShowWindow(hwnd_, SW_SHOWNORMAL); + ::UpdateWindow(hwnd_); - const int64_t min_interval = 5; - auto last = time::Now(); - - while (!quit_) + MSG msg = {}; + while (::GetMessageW(&msg, nullptr, 0, 0) && !quit_) { - auto now = time::Now(); - auto dur = now - last; - - if (dur.Milliseconds() > min_interval) - { - const auto dt = now - last; - last = now; - - devices::Input::Instance()->Update( - window->GetHandle(), - window->GetContentScaleX(), - window->GetContentScaleY() - ); - - UpdateScene(dt); - DrawScene(); - - window->Poll(); - } - else - { - // ID2D1HwndRenderTarget 开启了垂直同步,在渲染时会等待显示器刷新, - // 它起到了非常稳定的延时作用,所以大部分时候不需要手动挂起线程进行延时。 - // 下面的代码仅在一些情况下(例如窗口最小化时)挂起线程,防止占用过高 CPU 。 - int64_t wait = min_interval - dur.Milliseconds(); - if (wait > 1LL) - { - std::this_thread::sleep_for(std::chrono::milliseconds(wait)); - } - } + ::TranslateMessage(&msg); + ::DispatchMessageW(&msg); } } @@ -201,30 +180,30 @@ namespace easy2d return curr_scene_; } - void Game::UpdateScene(Duration const& dt) + void Game::Update() { + static auto last = time::Now(); + + const auto now = time::Now(); + const auto dt = now - last; + last = now; + + devices::Input::Instance()->Update(); + if (curr_scene_) - { curr_scene_->Update(dt); - } if (next_scene_) - { next_scene_->Update(dt); - } if (transition_) { transition_->Update(dt); if (transition_->IsDone()) - { transition_ = nullptr; - } else - { return; - } } if (next_scene_) @@ -241,32 +220,14 @@ namespace easy2d } } - void Game::Dispatch(MouseEvent const & e) - { - if (transition_) - return; - - if (curr_scene_) - curr_scene_->Dispatch(e, false); - } - - void Game::Dispatch(KeyEvent const & e) - { - if (transition_) - return; - - if (curr_scene_) - curr_scene_->Dispatch(e, false); - } - - void Game::DrawScene() + void Game::Render() { auto graphics = devices::Graphics::Instance(); - graphics->BeginDraw(Window::Instance()->GetHandle()); + graphics->BeginDraw(hwnd_); if (transition_) { - transition_->Draw(); + transition_->Render(); } else if (curr_scene_) { @@ -292,5 +253,134 @@ namespace easy2d } graphics->EndDraw(); + + if (!window_inactived_) + ::InvalidateRect(hwnd_, NULL, FALSE); + } + + void Game::Dispatch(MouseEvent const & e) + { + if (transition_) + return; + + if (curr_scene_) + curr_scene_->Dispatch(e, false); + } + + void Game::Dispatch(KeyEvent const & e) + { + if (transition_) + return; + + if (curr_scene_) + curr_scene_->Dispatch(e, false); + } + + LRESULT CALLBACK Game::WndProc(HWND hwnd, UINT msg, WPARAM w_param, LPARAM l_param) + { + LRESULT result = 0; + bool was_handled = false; + + Game * game = reinterpret_cast( + static_cast(::GetWindowLongW(hwnd, GWLP_USERDATA)) + ); + + switch (msg) + { + case WM_PAINT: + { + PAINTSTRUCT ps; + ::BeginPaint(hwnd, &ps); + + game->Update(); + game->Render(); + + ::EndPaint(hwnd, &ps); + } + result = 0; + was_handled = true; + break; + + case WM_LBUTTONUP: + case WM_LBUTTONDOWN: + case WM_LBUTTONDBLCLK: + case WM_MBUTTONUP: + case WM_MBUTTONDOWN: + case WM_MBUTTONDBLCLK: + case WM_RBUTTONUP: + case WM_RBUTTONDOWN: + case WM_RBUTTONDBLCLK: + case WM_MOUSEMOVE: + case WM_MOUSEWHEEL: + { + game->Dispatch(MouseEvent(msg, w_param, l_param)); + } + result = 0; + was_handled = true; + break; + + case WM_KEYDOWN: + case WM_KEYUP: + { + game->Dispatch(KeyEvent(msg, w_param, l_param)); + } + result = 0; + was_handled = true; + break; + + case WM_SIZE: + { + if (SIZE_MAXHIDE == w_param || SIZE_MINIMIZED == w_param) + game->window_inactived_ = true; + else + { + game->window_inactived_ = false; + ::InvalidateRect(hwnd, nullptr, FALSE); + } + + UINT width = LOWORD(l_param); + UINT height = HIWORD(l_param); + + // 如果程序接收到一个 WM_SIZE 消息,这个方法将调整渲染 + // 目标的大小。它可能会调用失败,但是这里可以忽略有可能的 + // 错误,因为这个错误将在下一次调用 EndDraw 时产生 + devices::Graphics::Instance()->Resize(width, height); + } + break; + + case WM_DISPLAYCHANGE: + { + ::InvalidateRect(hwnd, nullptr, FALSE); + } + result = 0; + was_handled = true; + break; + + case WM_CLOSE: + { + if (game->OnClose()) + { + game->Quit(); + } + } + result = 0; + was_handled = true; + break; + + case WM_DESTROY: + { + ::PostQuitMessage(0); + } + result = 1; + was_handled = true; + break; + + } + + if (!was_handled) + { + result = ::DefWindowProcW(hwnd, msg, w_param, l_param); + } + return result; } } \ No newline at end of file diff --git a/core/base/Game.h b/core/base/Game.h index 4bd5b16e..de6cb005 100644 --- a/core/base/Game.h +++ b/core/base/Game.h @@ -89,11 +89,10 @@ namespace easy2d // 获取当前场景 spScene const& GetCurrentScene(); - void DrawScene(); + private: + void Render(); - void UpdateScene( - Duration const& dt - ); + void Update(); void Dispatch( MouseEvent const& e @@ -103,10 +102,14 @@ namespace easy2d KeyEvent const& e ); + static LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM); + private: bool initialized_; bool debug_enabled_; bool quit_; + bool window_inactived_; + HWND hwnd_; spScene curr_scene_; spScene next_scene_; spTransition transition_; diff --git a/core/base/Input.cpp b/core/base/Input.cpp index 9feb497a..f524f76c 100644 --- a/core/base/Input.cpp +++ b/core/base/Input.cpp @@ -28,6 +28,9 @@ namespace easy2d { InputDevice::InputDevice() : initialized(false) + , hwnd_(nullptr) + , scale_x_(1.f) + , scale_y_(1.f) { ZeroMemory(keys_, sizeof(keys_)); ZeroMemory(keys_cache_, sizeof(keys_cache_)); @@ -38,26 +41,30 @@ namespace easy2d E2D_LOG("Destroying input device"); } - void InputDevice::Init(bool debug) + void InputDevice::Init(HWND hwnd, float scale_x, float scale_y, bool debug) { if (initialized) return; E2D_LOG("Initing input device"); + hwnd_ = hwnd; + scale_x_ = scale_x; + scale_y_ = scale_y; + initialized = true; } - void InputDevice::Update(HWND hwnd, float scale_x, float scale_y) + void InputDevice::Update() { memcpy(keys_cache_, keys_, sizeof(keys_cache_)); GetKeyboardState(keys_); POINT client_cursor_pos; GetCursorPos(&client_cursor_pos); - ScreenToClient(hwnd, &client_cursor_pos); + ScreenToClient(hwnd_, &client_cursor_pos); - mouse_pos_ = Point(client_cursor_pos.x * scale_x, client_cursor_pos.y * scale_y); + mouse_pos_ = Point(client_cursor_pos.x * scale_x_, client_cursor_pos.y * scale_y_); } bool InputDevice::IsDown(KeyCode code) diff --git a/core/base/Input.h b/core/base/Input.h index 5908da10..ce62f780 100644 --- a/core/base/Input.h +++ b/core/base/Input.h @@ -32,7 +32,7 @@ namespace easy2d E2D_DECLARE_SINGLETON(InputDevice); public: - void Init(bool debug); + void Init(HWND hwnd, float scale_x, float scale_y, bool debug); // 检测键盘某按键是否正被按下 bool IsDown( @@ -63,12 +63,7 @@ namespace easy2d // 获得鼠标坐标值 Point GetMousePos(); - // 刷新设备状态 - void Update( - HWND hwnd, - float scale_x, - float scale_y - ); + void Update(); protected: InputDevice(); @@ -76,10 +71,13 @@ namespace easy2d ~InputDevice(); protected: - bool initialized; - BYTE keys_[256]; - BYTE keys_cache_[256]; - Point mouse_pos_; + bool initialized; + HWND hwnd_; + float scale_x_; + float scale_y_; + BYTE keys_[256]; + BYTE keys_cache_[256]; + Point mouse_pos_; }; E2D_DECLARE_SINGLETON_TYPE(InputDevice, Input); diff --git a/core/base/Transition.cpp b/core/base/Transition.cpp index 2fa42072..f9915c17 100644 --- a/core/base/Transition.cpp +++ b/core/base/Transition.cpp @@ -99,7 +99,7 @@ namespace easy2d } } - void Transition::Draw() + void Transition::Render() { auto graphics = devices::Graphics::Instance(); diff --git a/core/base/Transition.h b/core/base/Transition.h index 7be54cda..f157e407 100644 --- a/core/base/Transition.h +++ b/core/base/Transition.h @@ -49,7 +49,7 @@ namespace easy2d virtual void Update(Duration const& dt); - virtual void Draw(); + virtual void Render(); virtual void Stop(); diff --git a/core/base/render.cpp b/core/base/render.cpp index c9bee55e..442fdc4d 100644 --- a/core/base/render.cpp +++ b/core/base/render.cpp @@ -35,6 +35,7 @@ namespace easy2d , fps_text_layout_(nullptr) , clear_color_(D2D1::ColorF(D2D1::ColorF::Black)) , opacity_(1.f) + , window_occluded(false) , initialized(false) { ZeroMemory(&d2d, sizeof(D2DResources)); @@ -130,28 +131,36 @@ namespace easy2d { CreateDeviceResources(hwnd); - d2d.render_target->BeginDraw(); - d2d.render_target->Clear(clear_color_); + window_occluded = !!(d2d.render_target->CheckWindowState() & D2D1_WINDOW_STATE_OCCLUDED); + + if (!window_occluded) + { + d2d.render_target->BeginDraw(); + d2d.render_target->Clear(clear_color_); + } } void GraphicsDevice::EndDraw() { - HRESULT hr = d2d.render_target->EndDraw(); - - if (hr == D2DERR_RECREATE_TARGET) + if (!window_occluded) { - // 如果 Direct3D 设备在执行过程中消失,将丢弃当前的设备相关资源 - // 并在下一次调用时重建资源 - hr = S_OK; + HRESULT hr = d2d.render_target->EndDraw(); - fps_text_format_ = nullptr; - fps_text_layout_ = nullptr; - d2d.text_renderer = nullptr; - d2d.solid_brush = nullptr; - d2d.render_target = nullptr; + if (hr == D2DERR_RECREATE_TARGET) + { + // 如果 Direct3D 设备在执行过程中消失,将丢弃当前的设备相关资源 + // 并在下一次调用时重建资源 + hr = S_OK; + + fps_text_format_ = nullptr; + fps_text_layout_ = nullptr; + d2d.text_renderer = nullptr; + d2d.solid_brush = nullptr; + d2d.render_target = nullptr; + } + + ThrowIfFailed(hr); } - - ThrowIfFailed(hr); } void GraphicsDevice::ClearImageCache() @@ -375,6 +384,9 @@ namespace easy2d !d2d.render_target) return E_UNEXPECTED; + if (window_occluded) + return S_OK; + d2d.solid_brush->SetColor(border_color); d2d.render_target->DrawGeometry( geometry.Get(), @@ -393,6 +405,9 @@ namespace easy2d if (!image->GetBitmap()) return S_OK; + if (window_occluded) + return S_OK; + d2d.render_target->DrawBitmap( image->GetBitmap().Get(), D2D1::RectF(0.f, 0.f, image->GetWidth(), image->GetHeight()), @@ -427,6 +442,9 @@ namespace easy2d if (!d2d.render_target) return E_UNEXPECTED; + if (window_occluded) + return S_OK; + // Do not crop bitmap auto rect = D2D1::RectF(0.f, 0.f, bitmap->GetSize().width, bitmap->GetSize().height); d2d.render_target->DrawBitmap( @@ -444,6 +462,9 @@ namespace easy2d if (!d2d.text_renderer) return E_UNEXPECTED; + if (window_occluded) + return S_OK; + return text_layout->Draw(nullptr, d2d.text_renderer.Get(), 0, 0); } @@ -452,6 +473,9 @@ namespace easy2d if (!d2d.render_target) return E_UNEXPECTED; + if (window_occluded) + return S_OK; + d2d.render_target->SetTransform(ConvertToD2DMatrix(clip_matrix)); d2d.render_target->PushAxisAlignedClip( D2D1::RectF(0, 0, clip_size.width, clip_size.height), @@ -465,6 +489,9 @@ namespace easy2d if (!d2d.render_target) return E_UNEXPECTED; + if (window_occluded) + return S_OK; + d2d.render_target->PopAxisAlignedClip(); return S_OK; } @@ -475,6 +502,9 @@ namespace easy2d !d2d.solid_brush) return E_UNEXPECTED; + if (window_occluded) + return S_OK; + d2d.render_target->PushLayer( D2D1::LayerParameters( properties.area, @@ -495,6 +525,9 @@ namespace easy2d if (!d2d.render_target) return E_UNEXPECTED; + if (window_occluded) + return S_OK; + d2d.render_target->PopLayer(); return S_OK; } diff --git a/core/base/render.h b/core/base/render.h index d7cb492b..66af2b7f 100644 --- a/core/base/render.h +++ b/core/base/render.h @@ -194,6 +194,7 @@ namespace easy2d protected: bool initialized; + bool window_occluded; float opacity_; D2DResources d2d; D2D1_COLOR_F clear_color_; diff --git a/core/base/window.cpp b/core/base/window.cpp index 77e95504..4507351d 100644 --- a/core/base/window.cpp +++ b/core/base/window.cpp @@ -21,12 +21,7 @@ #include "window.h" #include "render.h" #include "logs.h" -#include "Game.h" -#include "KeyEvent.h" -#include "MouseEvent.h" #include "../math/scalar.hpp" -#include -#pragma comment (lib ,"imm32.lib") #define WINDOW_STYLE WS_OVERLAPPEDWINDOW & ~WS_MAXIMIZEBOX & ~WS_THICKFRAME #define REGISTER_CLASS L"Easy2DApp" @@ -38,8 +33,6 @@ namespace easy2d void GetContentScale(float* scale_x, float* scale_y); Rect LocateWindow(int width, int height, float scale_x, float scale_y); - - LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM w_param, LPARAM l_param); } WindowImpl::WindowImpl() @@ -58,7 +51,7 @@ namespace easy2d ::DestroyWindow(handle); } - void WindowImpl::Init(String title, int width, int height, LPCWSTR icon, bool debug) + void WindowImpl::Init(String title, int width, int height, LPCWSTR icon, WNDPROC proc, bool debug) { if (initialized) return; @@ -70,7 +63,7 @@ namespace easy2d wcex.cbSize = sizeof(WNDCLASSEX); wcex.lpszClassName = REGISTER_CLASS; wcex.style = CS_HREDRAW | CS_VREDRAW | CS_DBLCLKS; - wcex.lpfnWndProc = WndProc; + wcex.lpfnWndProc = proc; wcex.hIcon = nullptr; wcex.cbClsExtra = 0; wcex.cbWndExtra = sizeof(LONG_PTR); @@ -123,9 +116,6 @@ namespace easy2d throw std::runtime_error(err); } - // 禁用输入法 - ::ImmAssociateContext(handle, nullptr); - initialized = true; } @@ -220,17 +210,6 @@ namespace easy2d return scale_y; } - void WindowImpl::Poll() - { - static MSG msg = {}; - - while (::PeekMessage(&msg, nullptr, 0, 0, PM_REMOVE)) - { - ::TranslateMessage(&msg); - ::DispatchMessage(&msg); - } - } - namespace { void GetContentScale(float* scale_x, float* scale_y) @@ -276,109 +255,5 @@ namespace easy2d static_cast(height) ); } - - LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM w_param, LPARAM l_param) - { - LRESULT result = 0; - bool was_handled = false; - - Game * game = reinterpret_cast( - static_cast(::GetWindowLongPtrW(hwnd, GWLP_USERDATA)) - ); - - switch (msg) - { - - // 处理鼠标消息 - case WM_LBUTTONUP: - case WM_LBUTTONDOWN: - case WM_LBUTTONDBLCLK: - case WM_MBUTTONUP: - case WM_MBUTTONDOWN: - case WM_MBUTTONDBLCLK: - case WM_RBUTTONUP: - case WM_RBUTTONDOWN: - case WM_RBUTTONDBLCLK: - case WM_MOUSEMOVE: - case WM_MOUSEWHEEL: - { - game->Dispatch(MouseEvent(msg, w_param, l_param)); - } - result = 0; - was_handled = true; - break; - - // 处理按键消息 - case WM_KEYDOWN: - case WM_KEYUP: - { - game->Dispatch(KeyEvent(msg, w_param, l_param)); - } - result = 0; - was_handled = true; - break; - - // 处理窗口大小变化消息 - case WM_SIZE: - { - UINT width = LOWORD(l_param); - UINT height = HIWORD(l_param); - - // 如果程序接收到一个 WM_SIZE 消息,这个方法将调整渲染 - // 目标的大小。它可能会调用失败,但是这里可以忽略有可能的 - // 错误,因为这个错误将在下一次调用 EndDraw 时产生 - devices::Graphics::Instance()->Resize(width, height); - } - break; - - // 处理分辨率变化消息 - case WM_DISPLAYCHANGE: - { - // 重绘客户区 - ::InvalidateRect(hwnd, nullptr, FALSE); - } - result = 0; - was_handled = true; - break; - - // 重绘窗口 - case WM_PAINT: - { - game->DrawScene(); - ::ValidateRect(hwnd, nullptr); - } - result = 0; - was_handled = true; - break; - - // 窗口关闭消息 - case WM_CLOSE: - { - if (game->OnClose()) - { - game->Quit(); - } - } - result = 0; - was_handled = true; - break; - - // 窗口销毁消息 - case WM_DESTROY: - { - ::PostQuitMessage(0); - } - result = 1; - was_handled = true; - break; - - } - - if (!was_handled) - { - result = ::DefWindowProc(hwnd, msg, w_param, l_param); - } - return result; - } } } diff --git a/core/base/window.h b/core/base/window.h index 42dc41dd..ef4b6b25 100644 --- a/core/base/window.h +++ b/core/base/window.h @@ -35,6 +35,7 @@ namespace easy2d int width, int height, LPCWSTR icon, + WNDPROC proc, bool debug ); @@ -65,18 +66,16 @@ namespace easy2d float GetContentScaleY() const; - void Poll(); - protected: WindowImpl(); ~WindowImpl(); private: - bool initialized; - HWND handle; - float scale_x; - float scale_y; + bool initialized; + HWND handle; + float scale_x; + float scale_y; }; E2D_DECLARE_SINGLETON_TYPE(WindowImpl, Window);