-
-
Notifications
You must be signed in to change notification settings - Fork 21.4k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Race Condition in EmbeddedProcess::_try_embed_process
#101068
Comments
@0x0ACB thanks for testing and reporting this issue. The fact that the window appears on the wrong monitor is troubing. It seems that the embedding control is not able to get the correct embedded control global position. Can you check the values returned by |
OK so I had a bit more time to do some more digging and I also remembered a similar issue I had in the past when using godot as an overaly. The y coordinate for the window is calculated incorrectly. My two monitors are stacked vertically not horizontally and it seems that godot has trouble calculating the correct global position. When embedding is successful with godot running on the bottom monitor or godot is running on my top monitor and the embedding fails. The global position of the game window ends up as:
I had it disabled for my latest tests but the not showing up on monitor 1 issue does exist whether it is enabled or not. With it enabled the floating window will appear on the bottom monitor though if the godot editor is on the top.
I can reproduce the issue with an empty project as well yes.
no
The coords do seem to be correct however the actual position of the game window does not match the result of I can offer you the code and workarounds I am using for my Overlay class I wrote for godot which has fixes for the issue of appearing on the wrong monitor or not correctly being overlayed on top of the target window maybe it helps. Of particular note is the need to hide the window before applying the styles and then showing the window again since otherwise the calls to void overlay::OverlayWIN32::match_size() const
{
print_verbose("Matching window");
RECT rect;
ERR_FAIL_COND(GetClientRect(m_targetWindow, &rect) == FALSE);
MapWindowPoints(m_targetWindow, nullptr, reinterpret_cast<POINT *>(&rect), 2);
const HMONITOR hMonitor = MonitorFromWindow(m_targetWindow, MONITOR_DEFAULTTONEAREST);
MONITORINFO mi = { sizeof(mi) };
GetMonitorInfoW(hMonitor, &mi);
const Rect2i screen_rect(mi.rcMonitor.left, mi.rcMonitor.top, mi.rcMonitor.right - mi.rcMonitor.left, mi.rcMonitor.bottom - mi.rcMonitor.top);
const Rect2i window_rect(rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top);
print_verbose("Screen rect: " + screen_rect + " Window rect: " + window_rect);
if(window_rect.position == screen_rect.position && window_rect.size == screen_rect.size)
{
// prevent fullscreen
print_verbose("Preventing fullscreen");
rect.left += 1;
rect.top += 1;
rect.right -= 1;
rect.bottom -= 1;
}
ERR_FAIL_COND(SetWindowPos(m_engineWnd, nullptr, rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top, SWP_NOZORDER | SWP_NOACTIVATE | SWP_ASYNCWINDOWPOS) == FALSE);
}
void overlay::OverlayWIN32::attach()
{
ERR_FAIL_COND(m_attached);
m_attached = true;
const auto display = DisplayServer::get_singleton();
m_engineWnd = std::bit_cast<HWND>(display->window_get_native_handle(DisplayServer::HandleType::WINDOW_HANDLE));
ShowWindow(m_engineWnd, SW_HIDE);
bool hasWindow = false;
if(m_targetWindow)
{
DWORD pid = 0;
if (GetWindowThreadProcessId(m_targetWindow, &pid) != 0)
{
if (pid == client::get_instance()->get_process_id())
{
hasWindow = true;
}
}
}
if(hasWindow)
{
print_line("Reattaching to existing window");
MessageQueue::get_singleton()->push_callable(callable_mp(this, &OverlayWIN32::deferred_attach));
return;
}
std::thread([this]()
{
print_line("Waiting for window");
std::optional<ipc::actions::window_created> windowCreated;
while (!windowCreated)
{
windowCreated = ipc::shared_memory::get_instance()->receive_action<ipc::actions::window_created>();
std::this_thread::sleep_for(std::chrono::milliseconds(100));
}
m_targetWindow = std::bit_cast<HWND>(windowCreated->hwnd);
MessageQueue::get_singleton()->push_callable(callable_mp(this, &OverlayWIN32::deferred_attach));
}).detach();
}
void overlay::OverlayWIN32::deferred_attach()
{
print_line("Attaching");
const auto display = DisplayServer::get_singleton();
m_engineWnd = std::bit_cast<HWND>(display->window_get_native_handle(DisplayServer::HandleType::WINDOW_HANDLE));
// disable all kinds of input buffering
Input::get_singleton()->set_use_accumulated_input(false);
Input::get_singleton()->set_agile_input_event_flushing(false);
// Attach to the window
display->window_set_flag(DisplayServer::WINDOW_FLAG_BORDERLESS, true);
display->window_set_flag(DisplayServer::WINDOW_FLAG_TRANSPARENT, true);
SceneTree::get_singleton()->get_root()->set_transparent_background(true);
auto exstyle = GetWindowLongW(m_engineWnd, GWL_EXSTYLE);
exstyle = exstyle & ~WS_EX_APPWINDOW & ~WS_EX_TOOLWINDOW;
exstyle = exstyle | WS_EX_LAYERED | WS_EX_TRANSPARENT | WS_EX_NOACTIVATE;
SetWindowLongW(m_engineWnd, GWL_EXSTYLE, exstyle);
SetWindowLongPtrW(m_engineWnd, GWLP_HWNDPARENT, std::bit_cast<LONG_PTR>(m_targetWindow));
match_size();
// create input handler window
static const char* cls_name = "overlay_message";
UnregisterClassA(cls_name, nullptr);
WNDCLASSEX wx = { 0 };
wx.cbSize = sizeof(WNDCLASSEX);
wx.lpfnWndProc = &OverlayWIN32::StaticMessageWndProc;
wx.hInstance = nullptr;
wx.lpszClassName = cls_name;
CRASH_COND(!RegisterClassExA(&wx));
m_messageWnd = CreateWindowExA(0, wx.lpszClassName, cls_name, 0, 0, 0, 0, 0, HWND_MESSAGE, nullptr, nullptr, nullptr);
CRASH_COND(!m_messageWnd);
ipc::write::window_info windowInfo;
windowInfo.godot_hwnd = std::bit_cast<uint64_t>(m_engineWnd);
windowInfo.message_hwnd = std::bit_cast<uint64_t>(m_messageWnd);
windowInfo.want_input = false;
display->cursor_set_shape(DisplayServer::CURSOR_BUSY);
display->cursor_set_shape(DisplayServer::CURSOR_ARROW);
windowInfo.cursor = reinterpret_cast<uint64_t>(GetCursor());
ipc::shared_memory::get_instance()->set_window_info(windowInfo);
ShowWindow(m_engineWnd, SW_SHOW);
SetActiveWindow(m_engineWnd);
m_wantInput = true;
print_line("Attached");
emit_signal(SNAME("attached"));
} |
Thanks a lot for that, I'll take take a closer look to your answer and try to fix the issue! |
Thanks again for time, the code sample and your explanations. I'm not able to reproduce with 2 monitors stack vertically on my side despite all my different trials and tests. From what I understand, because the issue is intermittent, one of my hypothesis is that I created a draft PR why added debug code. Can you try the build from this PR: https://github.com/godotengine/godot/actions/runs/12613127398?pr=101135 and run an empty project, resize the game area and send me the output log please. I added some debug code to see if the found embedded handle is the correct one by printing the window title and I added the coords calculated and the screen origins. I also modify some parameters to the call to Note: the parent window handle is passed to the new godot process in command line via the |
Interesting. It does get called when the embedding fails for me.
those are the debug logs from your PR. I notice that in your enum proc func you are never actually setting the last error to anything other than success and I think you are using it wrong:
Your enum callback returns I added some debug prints to the enum windows part and I think that is where the issue is. When embedding fails the editor get the wrong window handle. There is more than 1 window that has the Editor as its parent. The issue seems to be that I am starting godot with the console wrapper:
I added a crude filter on the window class and that works.
Also as a side note. Calling |
Great finding! The conditions in Can you test again with my modifications, please? Here's the link: https://github.com/godotengine/godot/actions/runs/12621205716 Regarding the use of I'm hesitant to use |
The doc advises against it because it does not set the parent. If you call Edit: I tested your PR and it does seem to work now. Didn't even notice the wrong nesting there. |
Thanks again for testing!
You are absolutely right, this article explains what you are saying: https://devblogs.microsoft.com/oldnewthing/20100315-00/?p=14613 and based on it, I think setting using |
Interesting. Probably I forgot about that special case because I didn't want to modify the engine and do it from a module. |
Tested versions
4.4-dev7
System information
Godot v4.4.dev (f2d9746) - Windows 11 (build 26100) - Multi-window, 2 monitors - Direct3D 12 (Forward+) - dedicated NVIDIA GeForce RTX 4080 (NVIDIA; 32.0.15.6614) - Intel(R) Core(TM) i9-14900K (32 threads)
Issue description
There seems to be a race condition somewhere when embedding the game. When running things normally the Game tab shows nothing around 80% of the time. Resizing and none of the buttons in the game tab change anything. If I put a break point on
godot/editor/plugins/embedded_process.cpp
Line 189 in bdf625b
I wasn't able to narrow this down further yet but the error branches are never entered even if the Game tab does not display anything.
Steps to reproduce
Minimal reproduction project (MRP)
The text was updated successfully, but these errors were encountered: