Skip to content

Commit

Permalink
Add a timer to assist with waking the runloop
Browse files Browse the repository at this point in the history
  • Loading branch information
willglynn committed Jul 17, 2017
1 parent 11fdc83 commit 1ffc1bf
Show file tree
Hide file tree
Showing 4 changed files with 107 additions and 13 deletions.
5 changes: 5 additions & 0 deletions src/platform/macos/events_loop.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ use super::window::Window;
use std;
use super::DeviceId;
use super::send_event::SendEvent;
use super::timer::Timer;

pub struct EventsLoop {
modifiers: Modifiers,
Expand All @@ -23,6 +24,8 @@ pub struct Shared {

// A queue of events that are pending delivery to the library user.
pub pending_events: Mutex<VecDeque<Event>>,

timer: Timer,
}

pub struct Proxy {
Expand All @@ -42,6 +45,7 @@ impl Shared {
Shared {
windows: Mutex::new(Vec::new()),
pending_events: Mutex::new(VecDeque::new()),
timer: Timer::new(),
}
}

Expand All @@ -50,6 +54,7 @@ impl Shared {
self.pending_events.lock().unwrap().push_back(event);

// attempt to wake the runloop
self.timer.trigger();
unsafe {
runloop::CFRunLoopWakeUp(runloop::CFRunLoopGetMain());
}
Expand Down
2 changes: 2 additions & 0 deletions src/platform/macos/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@ mod events_loop;
mod monitor;
mod window;

mod timer;

#[cfg(not(feature="context"))]
mod send_event;

Expand Down
35 changes: 22 additions & 13 deletions src/platform/macos/send_event_context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,18 +23,26 @@ thread_local!{
}

unsafe fn resume(value: usize) {
// get the context we're resuming
let context = INNER_CONTEXT.with(|c| {
c.take()
}).expect("resume context");

// resume it, getting a new context
let result = context.resume(value);

// store the new context and return
INNER_CONTEXT.with(move |c| {
c.set(Some(result.context));
});
if try_resume(value) == false {
panic!("no coroutine context to resume");
}
}

pub unsafe fn try_resume(value: usize) -> bool {
if let Some(context) = INNER_CONTEXT.with(|c| { c.take() }) {
// resume it, getting a new context
let result = context.resume(value);

// store the new context and return
INNER_CONTEXT.with(move |c| {
c.set(Some(result.context));
});

true
} else {
// no context
false
}
}

// A RunLoopObserver corresponds to a CFRunLoopObserver.
Expand All @@ -45,8 +53,9 @@ struct RunLoopObserver {
extern "C" fn runloop_observer_callback(_observer: CFRunLoopObserverRef, _activity: CFRunLoopActivity, _info: *mut c_void) {
// we're either about to wait or just finished waiting
// in either case, yield back to the caller, signaling the operation is still in progress
// this is strictly advisory, so don't worry about it if there's nothing o resume
unsafe {
resume(1);
try_resume(1);
}
}

Expand Down
78 changes: 78 additions & 0 deletions src/platform/macos/timer.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
use std::mem;
use libc::c_void;
use core_foundation::base::*;
use core_foundation::runloop::*;
use core_foundation::date::*;

// Encapsulates a CFRunLoopTimer that has a far-future time to fire, but which can be triggered
// across threads for the purpose of waking up an event loop.
pub struct Timer {
timer: CFRunLoopTimerRef,
}

#[cfg(feature="context")]
extern "C" fn timer_callback(timer: CFRunLoopTimerRef, info: *mut c_void) {
// attempt to yield back to the caller
use super::send_event_context::try_resume;
unsafe {
try_resume(1);
}
}

#[cfg(not(feature="context"))]
extern "C" fn timer_callback(timer: CFRunLoopTimerRef, info: *mut c_void) {
// nothing to do
}

impl Timer {
pub fn new() -> Timer {
// default to firing every year, starting one year in the future
let one_year: CFTimeInterval = 86400f64 * 365f64;
let now = unsafe { CFAbsoluteTimeGetCurrent() };
let one_year_from_now = now + one_year;

let mut context: CFRunLoopTimerContext = unsafe { mem::zeroed() };

// create a timer
let timer = unsafe {
CFRunLoopTimerCreate(
kCFAllocatorDefault,
one_year_from_now, // fireDate
one_year, // interval
0, // flags
0, // order
timer_callback,
&mut context as *mut CFRunLoopTimerContext,
)
};

// add it to the runloop
unsafe {
CFRunLoopAddTimer(CFRunLoopGetMain(), timer, kCFRunLoopCommonModes);
}

Timer{
timer
}
}

// Cause the timer to fire ASAP. Can be called across threads.
pub fn trigger(&self) {
unsafe {
CFRunLoopTimerSetNextFireDate(self.timer, CFAbsoluteTimeGetCurrent());
}
}
}

impl Drop for Timer {
fn drop(&mut self) {
unsafe {
CFRunLoopRemoveTimer(CFRunLoopGetMain(), self.timer, kCFRunLoopCommonModes);
CFRelease(self.timer as _);
}
}
}

// Rust doesn't know that __CFRunLoopTimer is thread safe, but the docs say it is
unsafe impl Send for Timer {}
unsafe impl Sync for Timer {}

0 comments on commit 1ffc1bf

Please sign in to comment.