Skip to content
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

Fix window contents not resizing in lockstep with window resize on win32 #250

Merged
merged 4 commits into from
Aug 5, 2017
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
82 changes: 70 additions & 12 deletions src/platform/windows/events_loop.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
//! An events loop on Win32 is a background thread.
//!
//!
//! Creating an events loop spawns a thread and blocks it in a permanent Win32 events loop.
//! Destroying the events loop stops the thread.
//!
//!
//! You can use the `execute_in_thread` method to execute some code in the background thread.
//! Since Win32 requires you to create a window in the right thread, you must use this method
//! to create a window.
//!
//!
//! If you create a window whose class is set to `callback`, the window's events will be
//! propagated with `run_forever` and `poll_events`.
//! The closure passed to the `execute_in_thread` method takes an `Inserter` that you can use to
Expand All @@ -22,6 +22,7 @@ use std::ptr;
use std::sync::mpsc;
use std::sync::Arc;
use std::sync::Mutex;
use std::sync::Condvar;
use std::thread;

use kernel32;
Expand Down Expand Up @@ -78,22 +79,29 @@ pub struct EventsLoop {
thread_id: winapi::DWORD,
// Receiver for the events. The sender is in the background thread.
receiver: mpsc::Receiver<Event>,
// Variable that contains the block state of the win32 event loop thread during a WM_SIZE event.
// The mutex's value is `true` when it's blocked, and should be set to false when it's done
// blocking. That's done by the parent thread when it receives a Resized event.
win32_block_loop: Arc<(Mutex<bool>, Condvar)>
}

impl EventsLoop {
pub fn new() -> EventsLoop {
// The main events transfer channel.
let (tx, rx) = mpsc::channel();
let win32_block_loop = Arc::new((Mutex::new(false), Condvar::new()));
let win32_block_loop_child = win32_block_loop.clone();

// Local channel in order to block the `new()` function until the background thread has
// an events queue.
let (local_block_tx, local_block_rx) = mpsc::channel();

let thread = thread::spawn(move || {
let thread = thread::spawn(move || {
CONTEXT_STASH.with(|context_stash| {
*context_stash.borrow_mut() = Some(ThreadLocalData {
sender: tx,
windows: HashMap::with_capacity(4),
win32_block_loop: win32_block_loop_child
});
});

Expand Down Expand Up @@ -139,6 +147,7 @@ impl EventsLoop {
EventsLoop {
thread_id: unsafe { kernel32::GetThreadId(thread.as_raw_handle()) },
receiver: rx,
win32_block_loop
}
}

Expand All @@ -150,8 +159,18 @@ impl EventsLoop {
Ok(e) => e,
Err(_) => return
};
let is_resize = match event {
Event::WindowEvent{ event: WindowEvent::Resized(..), .. } => true,
_ => false
};

callback(event);
if is_resize {
let (ref mutex, ref cvar) = *self.win32_block_loop;
let mut block_thread = mutex.lock().unwrap();
*block_thread = false;
cvar.notify_all();
}
}
}

Expand All @@ -163,8 +182,18 @@ impl EventsLoop {
Ok(e) => e,
Err(_) => return
};
let is_resize = match event {
Event::WindowEvent{ event: WindowEvent::Resized(..), .. } => true,
_ => false
};

let flow = callback(event);
if is_resize {
let (ref mutex, ref cvar) = *self.win32_block_loop;
let mut block_thread = mutex.lock().unwrap();
*block_thread = false;
cvar.notify_all();
}
match flow {
ControlFlow::Continue => continue,
ControlFlow::Break => break,
Expand Down Expand Up @@ -240,15 +269,15 @@ lazy_static! {
// WPARAM and LPARAM are unused.
static ref WAKEUP_MSG_ID: u32 = {
unsafe {
user32::RegisterWindowMessageA("Winit::WakeupMsg".as_ptr() as *const i8)
user32::RegisterWindowMessageA("Winit::WakeupMsg\0".as_ptr() as *const i8)
}
};
// Message sent when we want to execute a closure in the thread.
// WPARAM contains a Box<Box<FnMut()>> that must be retreived with `Box::from_raw`,
// and LPARAM is unused.
static ref EXEC_MSG_ID: u32 = {
unsafe {
user32::RegisterWindowMessageA("Winit::ExecMsg".as_ptr() as *const i8)
user32::RegisterWindowMessageA("Winit::ExecMsg\0".as_ptr() as *const i8)
}
};
}
Expand All @@ -259,12 +288,14 @@ thread_local!(static CONTEXT_STASH: RefCell<Option<ThreadLocalData>> = RefCell::
struct ThreadLocalData {
sender: mpsc::Sender<Event>,
windows: HashMap<winapi::HWND, Arc<Mutex<WindowState>>>,
win32_block_loop: Arc<(Mutex<bool>, Condvar)>
}

// Utility function that dispatches an event on the current thread.
fn send_event(event: Event) {
CONTEXT_STASH.with(|context_stash| {
let context_stash = context_stash.borrow();

let _ = context_stash.as_ref().unwrap().sender.send(event); // Ignoring if closed
});
}
Expand Down Expand Up @@ -302,9 +333,36 @@ pub unsafe extern "system" fn callback(window: winapi::HWND, msg: winapi::UINT,
use events::WindowEvent::Resized;
let w = winapi::LOWORD(lparam as winapi::DWORD) as u32;
let h = winapi::HIWORD(lparam as winapi::DWORD) as u32;
send_event(Event::WindowEvent {
window_id: SuperWindowId(WindowId(window)),
event: Resized(w, h),

// Wait for the parent thread to process the resize event before returning from the
// callback.
CONTEXT_STASH.with(|context_stash| {
let mut context_stash = context_stash.borrow_mut();
let cstash = context_stash.as_mut().unwrap();

let event = Event::WindowEvent {
window_id: SuperWindowId(WindowId(window)),
event: Resized(w, h),
};

// If this window has been inserted into the window map, the resize event happened
// during the event loop. If it hasn't, the event happened on window creation and
// should be ignored.
if cstash.windows.get(&window).is_some() {
let (ref mutex, ref cvar) = *cstash.win32_block_loop;
let mut block_thread = mutex.lock().unwrap();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You need to lock the mutex before calling send_event, otherwise it is possible that the event gets processed and notify_all() gets called before we even reach that point.

I think the best thing to do is to not call send_event here but directly context_stash.as_ref().unwrap().sender.send(event).

*block_thread = true;

// The event needs to be sent after the lock to ensure that `notify_all` is
// called after `wait`.
cstash.sender.send(event).ok();

while *block_thread {
block_thread = cvar.wait(block_thread).unwrap();
}
} else {
cstash.sender.send(event).ok();
}
});
0
},
Expand Down Expand Up @@ -361,7 +419,7 @@ pub unsafe extern "system" fn callback(window: winapi::HWND, msg: winapi::UINT,
window_id: SuperWindowId(WindowId(window)),
event: MouseEntered { device_id: DEVICE_ID },
});

// Calling TrackMouseEvent in order to receive mouse leave events.
user32::TrackMouseEvent(&mut winapi::TRACKMOUSEEVENT {
cbSize: mem::size_of::<winapi::TRACKMOUSEEVENT>() as winapi::DWORD,
Expand Down Expand Up @@ -548,7 +606,7 @@ pub unsafe extern "system" fn callback(window: winapi::HWND, msg: winapi::UINT,
use events::WindowEvent::MouseInput;
use events::MouseButton::Other;
use events::ElementState::Released;
let xbutton = winapi::HIWORD(wparam as winapi::DWORD) as winapi::c_int;
let xbutton = winapi::HIWORD(wparam as winapi::DWORD) as winapi::c_int;
send_event(Event::WindowEvent {
window_id: SuperWindowId(WindowId(window)),
event: MouseInput { device_id: DEVICE_ID, state: Released, button: Other(xbutton as u8) }
Expand Down Expand Up @@ -638,7 +696,7 @@ pub unsafe extern "system" fn callback(window: winapi::HWND, msg: winapi::UINT,

if call_def_window_proc {
user32::DefWindowProcW(window, msg, wparam, lparam)
} else {
} else {
0
}
},
Expand Down