fix: the problem of window message loop blocking main loop

This commit is contained in:
Haibo 2018-11-19 17:56:17 +08:00 committed by Nomango
parent 523262866a
commit 91508fd7cf
10 changed files with 260 additions and 254 deletions

View File

@ -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 <thread>
#include <imm.h>
#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,31 +180,31 @@ 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<Game*>(
static_cast<LONG_PTR>(::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;
}
}

View File

@ -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_;

View File

@ -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)

View File

@ -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();
@ -77,6 +72,9 @@ namespace easy2d
protected:
bool initialized;
HWND hwnd_;
float scale_x_;
float scale_y_;
BYTE keys_[256];
BYTE keys_cache_[256];
Point mouse_pos_;

View File

@ -99,7 +99,7 @@ namespace easy2d
}
}
void Transition::Draw()
void Transition::Render()
{
auto graphics = devices::Graphics::Instance();

View File

@ -49,7 +49,7 @@ namespace easy2d
virtual void Update(Duration const& dt);
virtual void Draw();
virtual void Render();
virtual void Stop();

View File

@ -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,11 +131,18 @@ namespace easy2d
{
CreateDeviceResources(hwnd);
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()
{
if (!window_occluded)
{
HRESULT hr = d2d.render_target->EndDraw();
@ -153,6 +161,7 @@ namespace easy2d
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;
}

View File

@ -194,6 +194,7 @@ namespace easy2d
protected:
bool initialized;
bool window_occluded;
float opacity_;
D2DResources d2d;
D2D1_COLOR_F clear_color_;

View File

@ -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 <imm.h>
#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<float>(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<Game*>(
static_cast<LONG_PTR>(::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;
}
}
}

View File

@ -35,6 +35,7 @@ namespace easy2d
int width,
int height,
LPCWSTR icon,
WNDPROC proc,
bool debug
);
@ -65,8 +66,6 @@ namespace easy2d
float GetContentScaleY() const;
void Poll();
protected:
WindowImpl();