├── .gitignore ├── Cargo.toml ├── README.md └── src ├── future.rs ├── glib.rs ├── lib.rs ├── mainloop.rs ├── ruststd.rs ├── web.rs └── winmsg.rs /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | **/*.rs.bk 3 | Cargo.lock 4 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "thin_main_loop" 3 | version = "0.2.0" 4 | authors = ["David Henningsson "] 5 | edition = "2018" 6 | readme = "README.md" 7 | description = "Thin, cross-platform, main event loop. A building block for native GUI applications, among other use cases." 8 | repository = "https://github.com/diwic/thin_main_loop" 9 | documentation = "http://docs.rs/thin_main_loop" 10 | keywords = ["event", "loop", "message", "dispatcher", "main"] 11 | license = "Apache-2.0/MIT" 12 | categories = ["api-bindings", "asynchronous", "os::unix-apis", "os::windows-apis"] 13 | 14 | [dependencies] 15 | wasm-bindgen = { version = "0.2.29", optional = true } 16 | js-sys = { version = "0.3.6", optional = true } 17 | lazy_static = "1.1" 18 | futures = "0.3.1" 19 | 20 | [dependencies.glib-sys] 21 | version = "0.7" 22 | optional = true 23 | features = ["v2_36"] 24 | 25 | [dependencies.winapi] 26 | version = "0.3" 27 | optional = true 28 | features = ["winuser", "libloaderapi", "std", "winsock2"] 29 | 30 | [dependencies.web-sys] 31 | version = "0.3.4" 32 | optional = true 33 | features = [ 34 | 'Window', 35 | ] 36 | 37 | [features] 38 | glib = ["glib-sys"] 39 | win32 = ["winapi"] 40 | web = ["wasm-bindgen", "js-sys", "web-sys"] 41 | 42 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # A thin main loop for Rust 2 | 3 | Because Rust's native GUI story starts with the main loop. 4 | 5 | (Although this library might be useful for many other use cases too.) 6 | 7 | ## Goals 8 | 9 | * Support both callback based and async/await APIs 10 | * Cross platform 11 | * Negligible performance overhead for desktop applications 12 | * Bind to the best backend on each platform 13 | * No extra background threads 14 | * Provide access to raw handles to allow platform specific extensions 15 | 16 | ### Non-goals 17 | 18 | * Avoiding allocations and virtual dispatch at all costs 19 | * I/O scalability 20 | * no_std functionality 21 | 22 | ## Status 23 | 24 | The library has functions for running code: 25 | * ASAP (as soon as the main loop gets a chance to run something), 26 | * after a timeout, 27 | * at regular intervals, 28 | * ASAP, but in another thread, 29 | * when an I/O object is ready of reading or writing. 30 | 31 | and it can do so by: 32 | * Scheduling a callback 33 | * Scheduling a 0.3 future 34 | * Scheduling an async fn 35 | 36 | Maturity: Just up and running, not battle-tested. It's also a proof-of-concept, to spawn discussion and interest. 37 | I e, it's waiting for *you* to give it a spin, try it out, see what you like and what you don't like, what feature's you're missing, etc! 38 | 39 | Unsafe blocks: Only at the backend/FFI level. With the reference (Rust std) backend, there is no unsafe code at all. 40 | 41 | Rust version: Latest stable should be fine. 42 | 43 | ## Supported platforms 44 | 45 | Currently: 46 | 47 | * Win32 API - compile with `--features "win32"` 48 | * Glib - compile with `--features "glib"` 49 | * Rust std - reference implementation, does not support I/O. 50 | 51 | Wishlist: 52 | 53 | * OS X / Cocoa 54 | * Wasm / web (limited as we don't control the main loop) 55 | * QT 56 | * iOS 57 | * Android 58 | 59 | # Examples 60 | 61 | ## Borrowing 62 | 63 | If you have access to the mainloop, it supports borrowing closures so you don't have to clone/refcell your data: 64 | 65 | ```rust 66 | // extern crate thin_main_loop as tml; 67 | 68 | let mut x = false; 69 | { 70 | let mut ml = tml::MainLoop::new(); 71 | ml.call_asap(|| { 72 | x = true; // x is mutably borrowed by the closure 73 | tml::terminate(); 74 | }); 75 | ml.run(); 76 | } 77 | assert_eq!(x, true); 78 | ``` 79 | 80 | ## Non-borrowing, and a timer 81 | 82 | If you don't have access to the mainloop, you can still schedule `'static` callbacks: 83 | 84 | ```rust 85 | // extern crate thin_main_loop as tml; 86 | 87 | let mut ml = tml::MainLoop::new(); 88 | ml.call_asap(|| { 89 | // Inside a callback we can schedule another callback. 90 | tml::call_after(Duration::new(1, 0), || { 91 | tml::terminate(); 92 | }); 93 | }) 94 | ml.run(); 95 | // After one second, the main loop is terminated. 96 | ``` 97 | 98 | ## I/O 99 | 100 | Requires features "glib" or "win32". 101 | 102 | The following example connects to a TCP server and prints everything coming in. 103 | 104 | ```rust 105 | // extern crate thin_main_loop as tml; 106 | 107 | let mut io = TcpStream::connect(/* ..select server here.. */)?; 108 | io.set_nonblocking(true)?; 109 | let wr = tml::IOReader { io: io, f: move |io: &mut TcpStream, x| { 110 | // On incoming data, read it all 111 | let mut s = String::new(); 112 | let r = io.read_to_string(&mut s); 113 | 114 | // If we got something, print it 115 | if s != "" { println!(s); } 116 | 117 | // This is TcpStream's way of saying "connection closed" 118 | if let Ok(0) = r { tml::terminate(); } 119 | } 120 | 121 | let mut ml = MainLoop::new()?; 122 | ml.call_io(wr)?; 123 | ml.run(); 124 | ``` 125 | 126 | ## Async fn 127 | 128 | The following code waits one second, then terminates the program. 129 | 130 | ```rust 131 | // extern crate thin_main_loop as tml; 132 | 133 | use std::time::{Instant, Duration}; 134 | 135 | async fn wait_until(n: Instant) { 136 | delay(n).await.unwrap(); 137 | } 138 | 139 | let mut x = tml::futures::Executor::new().unwrap(); 140 | let n = Instant::now() + Duration::from_millis(1000); 141 | x.block_on(wait_until(n)); 142 | ``` 143 | 144 | # Background 145 | 146 | ## Callbacks 147 | 148 | Most of the APIs for native GUIs build on callbacks. When a button is clicked, you get a callback. The problem though, is that callbacks aren't really rustic: if you want to access your button object in two different callbacks, you end up having to Rc/RefCell it. And while that isn't the end of the world, people have been experimenting with other, more rustic, API designs. 149 | 150 | But that will be the task of another library. If we first make a thin cross platform library that binds to the native GUI apis, we can then experiment with making a more rustic API on top of that. And before we can make windows and buttons, we need to make a main loop which can process events from these objects. Hence this library. 151 | 152 | # Other Rust main loops 153 | 154 | ## Mio 155 | 156 | [Mio](https://crates.io/crates/mio) is also a cross platform main loop, but Mio has quite different design goals which ultimately makes it unsuitable for native GUI applications. Mio's primary use case is highly scalable servers, as such it binds to IOCP/epoll/etc which can take thousands of TCP sockets without problems, but does not integrate well with native APIs for GUI libraries: IOCP threads cannot process Windows messages, and so on. This library binds to [PeekMessage](https://docs.microsoft.com/en-us/windows/desktop/api/winuser/nf-winuser-peekmessagew)/[GMainLoop](https://developer.gnome.org/glib/stable/glib-The-Main-Event-Loop.html)/etc, which makes it suitable for GUI applications with native look and feel. 157 | 158 | Also, Mio is better at avoiding allocations, at the cost of being less ergonomic. 159 | 160 | ## Calloop 161 | 162 | [Calloop](https://crates.io/crates/calloop) is an event loop with very similar callback-style API to this crate. However, it is built on top of Mio, and so it binds to unsuitable native APIs for native GUI applications. 163 | 164 | ## Winit 165 | 166 | [Winit](https://crates.io/crates/winit) includes an event loop, and the crate has the purpose of creating windows. The event loop is not callback based, but enum based (every Event is an enum, which you need to dispatch yourself). Winit's focus is more on getting a window and custom drawing (through OpenGL, Vulcan etc) rather than drawing native GUI widgets, but nonetheless has some common ground with this crate. 167 | 168 | ## IUI / libui 169 | 170 | [IUI](https://crates.io/crates/iui) is a Rust binding to libui, which is a cross-platform GUI library written in C. Its event loop offers callbacks, much like this library. In comparison, this library is pure Rust only and binds to native libraries directly, skipping one abstraction level and is therefore easier to build. I also hope that with time this library could offer better Rust integration as well as some more flexibility, being usable for more than pure GUI applications, even if that is the current primary use case. 171 | -------------------------------------------------------------------------------- /src/future.rs: -------------------------------------------------------------------------------- 1 | //! 0.3 Futures support 2 | 3 | use std::future::Future; 4 | use futures::task; 5 | use futures::stream::Stream; 6 | use futures::task::{Poll, Waker, Context, ArcWake}; 7 | use std::pin::Pin; 8 | use std::mem; 9 | use std::sync::{Arc, Mutex}; 10 | use crate::{MainLoopError, MainLoop, IODirection, CbHandle, IOAble}; 11 | use std::collections::{HashMap, VecDeque}; 12 | use std::rc::Rc; 13 | use std::cell::{Cell, RefCell}; 14 | 15 | use std::time::Instant; 16 | 17 | /// Waits until a specific instant. 18 | pub struct Delay(Instant); 19 | 20 | impl Future for Delay { 21 | type Output = Result<(), MainLoopError>; 22 | fn poll(self: Pin<&mut Self>, ctx: &mut Context) -> Poll { 23 | let n = Instant::now(); 24 | // println!("Polled at {:?}", n); 25 | if self.0 <= n { Poll::Ready(Ok(())) } 26 | else { 27 | let lw = ctx.waker().clone(); 28 | match crate::call_after(self.0 - n, move || { lw.wake() }) { 29 | Ok(_) => Poll::Pending, 30 | Err(e) => Poll::Ready(Err(e)), 31 | } 32 | } 33 | } 34 | } 35 | 36 | /// Waits until a specific instant. 37 | pub fn delay(i: Instant) -> Delay { 38 | Delay(i) 39 | } 40 | 41 | struct IoInternal { 42 | cb_handle: CbHandle, 43 | direction: IODirection, 44 | queue: RefCell>>, 45 | alive: Cell, 46 | started: Cell, 47 | waker: RefCell>, 48 | } 49 | 50 | /// Io implements "futures::Stream", so it will output an item whenever 51 | /// the handle is ready for read / write. 52 | pub struct Io(Rc); 53 | 54 | impl IOAble for Io { 55 | fn handle(&self) -> CbHandle { self.0.cb_handle } 56 | fn direction(&self) -> IODirection { self.0.direction } 57 | fn on_rw(&mut self, r: Result) -> bool { 58 | self.0.queue.borrow_mut().push_back(r); 59 | let w = self.0.waker.borrow(); 60 | if let Some(waker) = &*w { waker.wake_by_ref() }; 61 | self.0.alive.get() 62 | } 63 | } 64 | 65 | impl Stream for Io { 66 | type Item = Result; 67 | fn poll_next(self: Pin<&mut Self>, ctx: &mut Context) -> Poll> { 68 | let s: &IoInternal = &(*self).0; 69 | if !s.alive.get() { return Poll::Ready(None); } 70 | 71 | if !s.started.get() { 72 | // Submit to the reactor 73 | let c: &Rc = &(*self).0; 74 | let c = Io(c.clone()); 75 | if let Err(e) = crate::call_io(c) { 76 | s.alive.set(false); 77 | return Poll::Ready(Some(Err(e))); 78 | } 79 | s.started.set(true); 80 | } 81 | 82 | let q = s.queue.borrow_mut().pop_front(); 83 | if let Some(item) = q { 84 | let item = item.map_err(|e| MainLoopError::Other(Box::new(e))); 85 | Poll::Ready(Some(item)) 86 | } else { 87 | *s.waker.borrow_mut() = Some(ctx.waker().clone()); 88 | Poll::Pending 89 | } 90 | } 91 | } 92 | 93 | impl Drop for Io { 94 | fn drop(&mut self) { 95 | let s: &IoInternal = &(*self).0; 96 | s.alive.set(false); 97 | } 98 | } 99 | 100 | /// Creates a new Io, which outputs an item whenever the handle is ready for reading / writing. 101 | pub fn io(handle: CbHandle, dir: IODirection) -> Io { 102 | Io(Rc::new(IoInternal { 103 | cb_handle: handle, 104 | direction: dir, 105 | alive: Cell::new(true), 106 | started: Cell::new(false), 107 | queue: Default::default(), 108 | waker: Default::default(), 109 | })) 110 | } 111 | 112 | // And the executor stuff 113 | 114 | type BoxFuture<'a> = Pin + 'a>>; 115 | 116 | type RunQueue = Arc>>; 117 | 118 | struct Task(u64, RunQueue); 119 | 120 | impl ArcWake for Task { 121 | fn wake_by_ref(x: &Arc) { 122 | x.1.lock().unwrap().push(x.0); 123 | // println!("Waking up"); 124 | } 125 | } 126 | 127 | /// A futures executor that supports spawning futures. 128 | /// 129 | /// If you use "Delay" or "Io", this is the executor you need to 130 | /// spawn it on. 131 | /// It contains a MainLoop inside, so you can spawn 'static callbacks too. 132 | pub struct Executor<'a> { 133 | ml: MainLoop<'a>, 134 | tasks: HashMap>, 135 | next_task: u64, 136 | run_queue: RunQueue, 137 | } 138 | 139 | impl<'a> Executor<'a> { 140 | pub fn new() -> Result { 141 | Ok(Executor { ml: MainLoop::new()?, next_task: 1, run_queue: Default::default(), tasks: Default::default() }) 142 | } 143 | 144 | /// Runs until the main loop is terminated. 145 | pub fn run(&mut self) { 146 | while self.run_one(true) {} 147 | } 148 | 149 | /// Processes futures ready to make progress. 150 | /// 151 | /// If no futures are ready to progress, may block in case allow_wait is true. 152 | /// Returns false if the mainloop was terminated. 153 | pub fn run_one(&mut self, allow_wait: bool) -> bool { 154 | let run_queue: Vec<_> = { 155 | let mut r = self.run_queue.lock().unwrap(); 156 | mem::replace(&mut *r, vec!()) 157 | }; 158 | if run_queue.len() == 0 { 159 | return self.ml.run_one(allow_wait); 160 | } 161 | for id in run_queue { 162 | let remove = { 163 | let f = self.tasks.get_mut(&id); 164 | if let Some(f) = f { 165 | let pinf = f.as_mut(); 166 | let t = Task(id, self.run_queue.clone()); 167 | let t = Arc::new(t); 168 | let waker = task::waker_ref(&t); 169 | let mut ctx = Context::from_waker(&waker); 170 | pinf.poll(&mut ctx) != Poll::Pending 171 | } else { false } 172 | }; 173 | if remove { 174 | self.tasks.remove(&id); 175 | } 176 | } 177 | true 178 | } 179 | 180 | /// Runs until the future is ready, or the main loop is terminated. 181 | /// 182 | /// Returns None if the main loop is terminated, or the result of the future otherwise. 183 | pub fn block_on + 'a>(&mut self, f: F) -> Option { 184 | use futures::future::{FutureExt, ready}; 185 | let res = Arc::new(RefCell::new(None)); 186 | let res2 = res.clone(); 187 | let f = f.then(move |r| { *res2.borrow_mut() = Some(r); ready(()) }); 188 | self.spawn(f); 189 | loop { 190 | if !self.run_one(true) { return None }; 191 | let x = res.borrow_mut().take(); 192 | if x.is_some() { return x; } 193 | } 194 | } 195 | 196 | pub fn spawn + 'a>(&mut self, f: F) { 197 | let x = Box::pin(f); 198 | self.tasks.insert(self.next_task, x); 199 | self.run_queue.lock().unwrap().push(self.next_task); 200 | self.next_task += 1; 201 | } 202 | } 203 | 204 | #[test] 205 | fn delay_test() { 206 | use std::time::Duration; 207 | use futures::future::{FutureExt, ready}; 208 | 209 | let mut x = Executor::new().unwrap(); 210 | let n = Instant::now() + Duration::from_millis(200); 211 | let f = delay(n).then(|_| { println!("Terminating!"); crate::terminate(); ready(()) }); 212 | x.spawn(f); 213 | x.run(); 214 | assert!(Instant::now() >= n); 215 | } 216 | 217 | #[test] 218 | fn async_fn_test() { 219 | use std::time::Duration; 220 | 221 | async fn foo(n: Instant) { 222 | delay(n).await.unwrap(); 223 | } 224 | 225 | let mut x = Executor::new().unwrap(); 226 | let n = Instant::now() + Duration::from_millis(200); 227 | x.block_on(foo(n)); 228 | assert!(Instant::now() >= n); 229 | } 230 | 231 | #[test] 232 | fn async_fn_test_ref() { 233 | use std::time::Duration; 234 | 235 | async fn takes_ref(s: &str) { 236 | delay(Instant::now() + Duration::from_millis(50)).await.unwrap(); 237 | println!("{}", s); 238 | } 239 | 240 | async fn calls_takes_ref() { 241 | let s = String::from("test3"); 242 | takes_ref(&s).await; 243 | } 244 | 245 | fn make_async() -> impl Future { 246 | takes_ref("test1") 247 | } 248 | 249 | /* fn call_async<'a, F: FnOnce(&'a str) -> G, G: Future + 'a>(f: F) -> G { 250 | let s = String::from("test2"); 251 | f(&s) 252 | } 253 | */ 254 | let _z = takes_ref; 255 | 256 | let mut x = Executor::new().unwrap(); 257 | // x.block_on(call_async(takes_ref)); 258 | x.block_on(make_async()); 259 | x.block_on(calls_takes_ref()); 260 | 261 | } 262 | -------------------------------------------------------------------------------- /src/glib.rs: -------------------------------------------------------------------------------- 1 | use crate::{CbKind, CbId, MainLoopError, IODirection}; 2 | use glib_sys; 3 | use std::{mem, panic}; 4 | use std::ptr::NonNull; 5 | use crate::mainloop::{SendFnOnce, ffi_cb_wrapper}; 6 | use std::os::raw::c_uint; 7 | 8 | use std::cell::RefCell; 9 | use std::collections::{HashMap}; 10 | 11 | const G_SOURCE_FUNCS: glib_sys::GSourceFuncs = glib_sys::GSourceFuncs { 12 | prepare: None,// Option gboolean>, 13 | check: Some(glib_source_check_cb), // Option gboolean>, 14 | dispatch: Some(glib_source_dispatch_cb), // Option gboolean>, 15 | finalize: Some(glib_source_finalize_cb), // , 16 | closure_callback: None, // GSourceFunc, 17 | closure_marshal: None, // GSourceDummyMarshal, 18 | }; 19 | 20 | #[repr(C)] 21 | struct GSourceIOData { 22 | gsource: glib_sys::GSource, 23 | tag: glib_sys::gpointer, 24 | cb_data: Option>>, 25 | } 26 | 27 | struct CbData<'a> { 28 | gsource: GSourceRef, 29 | cbid: CbId, 30 | kind: RefCell>>, 31 | } 32 | 33 | struct GSourceRef(NonNull); 34 | 35 | impl Drop for GSourceRef { 36 | fn drop(&mut self) { 37 | unsafe { 38 | glib_sys::g_source_destroy(self.0.as_mut()); 39 | glib_sys::g_source_unref(self.0.as_mut()); 40 | } 41 | } 42 | } 43 | 44 | thread_local! { 45 | static FINISHED_TLS: RefCell> = Default::default(); 46 | } 47 | 48 | pub struct Backend<'a> { 49 | ctx: *mut glib_sys::GMainContext, 50 | cb_map: RefCell>>>, 51 | } 52 | 53 | unsafe extern "C" fn glib_source_finalize_cb(gs: *mut glib_sys::GSource) { 54 | ffi_cb_wrapper(|| { 55 | let ss: &mut GSourceIOData = &mut *(gs as *mut _); 56 | if let Some(cb_data) = &ss.cb_data { 57 | FINISHED_TLS.with(|f| { f.borrow_mut().push(cb_data.as_ref().cbid); }); 58 | ss.cb_data.take(); 59 | } 60 | }, ()) 61 | } 62 | 63 | unsafe extern "C" fn glib_source_check_cb(gs: *mut glib_sys::GSource) -> glib_sys::gboolean { 64 | ffi_cb_wrapper(|| { 65 | let tag = { 66 | let ss: &mut GSourceIOData = &mut *(gs as *mut _); 67 | ss.tag 68 | }; 69 | let cond = glib_sys::g_source_query_unix_fd(gs, tag); 70 | // println!("Check {:?} {:?}!", tag, cond); 71 | if cond == 0 { glib_sys::GFALSE } else { glib_sys::GTRUE } 72 | }, glib_sys::GFALSE) 73 | } 74 | 75 | unsafe extern "C" fn glib_source_dispatch_cb(gs: *mut glib_sys::GSource, _: glib_sys::GSourceFunc, _: glib_sys::gpointer) -> glib_sys::gboolean { 76 | ffi_cb_wrapper(|| { 77 | let ss: &mut GSourceIOData = &mut *(gs as *mut _); 78 | let cond = glib_sys::g_source_query_unix_fd(gs, ss.tag); 79 | let dir = gio_to_dir(cond); 80 | 81 | let r = false; 82 | if let Some(mut cb_data) = &ss.cb_data { 83 | if cbdata_call(cb_data.as_mut(), Some(dir)) { return glib_sys::GTRUE; } 84 | } 85 | ss.cb_data.take(); 86 | glib_sys::GFALSE 87 | }, glib_sys::GFALSE) 88 | } 89 | 90 | fn cbdata_call(cb_data: &CbData, dir: Option>) -> bool { 91 | if let Some(ref mut kind) = *cb_data.kind.borrow_mut() { 92 | if kind.call_mut(dir) { return true; } 93 | }; 94 | cb_data.kind.borrow_mut().take().map(|kind| { kind.post_call_mut(); }); 95 | FINISHED_TLS.with(|f| { f.borrow_mut().push(cb_data.cbid); }); 96 | false 97 | } 98 | 99 | fn dir_to_gio(d: IODirection) -> glib_sys::GIOCondition { 100 | glib_sys::G_IO_HUP + glib_sys::G_IO_ERR + match d { 101 | IODirection::None => 0, 102 | IODirection::Read => glib_sys::G_IO_IN, 103 | IODirection::Write => glib_sys::G_IO_OUT, 104 | IODirection::Both => glib_sys::G_IO_IN + glib_sys::G_IO_OUT, 105 | } 106 | } 107 | 108 | fn gio_to_dir(cond: glib_sys::GIOCondition) -> Result { 109 | const BOTH: c_uint = glib_sys::G_IO_IN + glib_sys::G_IO_OUT; 110 | match cond { 111 | 0 => Ok(IODirection::None), 112 | glib_sys::G_IO_IN => Ok(IODirection::Read), 113 | glib_sys::G_IO_OUT => Ok(IODirection::Write), 114 | BOTH => Ok(IODirection::Both), 115 | _ => unimplemented!(), 116 | } 117 | } 118 | 119 | unsafe extern fn glib_cb(x: glib_sys::gpointer) -> glib_sys::gboolean { 120 | ffi_cb_wrapper(|| { 121 | let x = x as *const _ as *mut CbData; 122 | if cbdata_call(&mut (*x), None) { glib_sys::GTRUE } else { glib_sys::GFALSE } 123 | }, glib_sys::GFALSE) 124 | } 125 | 126 | struct Dummy(Box); 127 | 128 | struct Sender(*mut glib_sys::GMainContext); 129 | 130 | unsafe impl Send for Sender {} 131 | 132 | impl Drop for Sender { 133 | fn drop(&mut self) { unsafe { glib_sys::g_main_context_unref(self.0) } } 134 | } 135 | 136 | unsafe extern fn glib_send_cb(x: glib_sys::gpointer) -> glib_sys::gboolean { 137 | ffi_cb_wrapper(|| { 138 | let x: Box = Box::from_raw(x as *mut _); 139 | let f = x.0; 140 | f(); 141 | }, ()); 142 | glib_sys::GFALSE 143 | } 144 | 145 | impl SendFnOnce for Sender { 146 | fn send(&self, f: Box) -> Result<(), MainLoopError> { 147 | let f = Box::new(Dummy(f)); 148 | let f = Box::into_raw(f); 149 | let f = f as *mut _ as glib_sys::gpointer; 150 | // FIXME: glib docs are a bit vague on to what degree a GMainContext is equivalent to a thread. 151 | // Nonetheless this seems to be the recommended way to do things. But we should probably put 152 | // safeguards here or in glib_send_cb 153 | unsafe { glib_sys::g_main_context_invoke(self.0, Some(glib_send_cb), f); } 154 | Ok(()) 155 | } 156 | } 157 | 158 | impl Drop for Backend<'_> { 159 | fn drop(&mut self) { 160 | FINISHED_TLS.with(|f| { f.borrow_mut().clear(); }); 161 | self.cb_map.borrow_mut().clear(); 162 | unsafe { glib_sys::g_main_context_unref(self.ctx) } 163 | } 164 | } 165 | 166 | impl<'a> Backend<'a> { 167 | pub (crate) fn new() -> Result<(Self, Box), MainLoopError> { 168 | let be = Backend { 169 | ctx: unsafe { glib_sys::g_main_context_new() }, 170 | cb_map: Default::default(), 171 | }; 172 | FINISHED_TLS.with(|stls| { 173 | *stls.borrow_mut() = Default::default(); 174 | }); 175 | let sender = Sender(unsafe { glib_sys::g_main_context_ref(be.ctx) }); 176 | Ok((be, Box::new(sender))) 177 | } 178 | 179 | pub fn run_one(&self, wait: bool) -> bool { 180 | let w = if wait { glib_sys::GTRUE } else { glib_sys::GFALSE }; 181 | let r = unsafe { glib_sys::g_main_context_iteration(self.ctx, w) != glib_sys::GFALSE }; 182 | FINISHED_TLS.with(|f| { 183 | for cbid in f.borrow_mut().drain(..) { 184 | self.cb_map.borrow_mut().remove(&cbid); 185 | }; 186 | }); 187 | r 188 | } 189 | 190 | pub (crate) fn cancel(&self, cbid: CbId) -> Option> { 191 | self.cb_map.borrow_mut().remove(&cbid) 192 | .and_then(|s| { s.kind.borrow_mut().take() }) 193 | } 194 | 195 | pub (crate) fn push(&self, cbid: CbId, cb: CbKind<'a>) -> Result<(), MainLoopError> { 196 | let mut tag = None; 197 | let s = unsafe { 198 | if let Some((handle, direction)) = cb.handle() { 199 | let s = glib_sys::g_source_new(&G_SOURCE_FUNCS as *const _ as *mut _, mem::size_of::() as u32); 200 | tag = Some(glib_sys::g_source_add_unix_fd(s, handle.0, dir_to_gio(direction))); 201 | s 202 | } else if let Some(s) = cb.duration_millis()? { 203 | glib_sys::g_timeout_source_new(s) 204 | } else { 205 | glib_sys::g_idle_source_new() 206 | } 207 | }; 208 | 209 | let boxed = Box::new(CbData { 210 | gsource: GSourceRef(NonNull::new(s).unwrap()), 211 | cbid: cbid, 212 | kind: RefCell::new(Some(cb)), 213 | }); 214 | let x = NonNull::from(&*boxed); 215 | self.cb_map.borrow_mut().insert(cbid, boxed); 216 | 217 | unsafe { 218 | if let Some(tag) = tag { 219 | let ss: &mut GSourceIOData = &mut *(s as *mut _); 220 | ss.cb_data = Some(x.cast()); 221 | ss.tag = tag; 222 | } else { 223 | glib_sys::g_source_set_callback(s, Some(glib_cb), x.as_ptr() as *mut _ as *mut _, None); 224 | } 225 | 226 | glib_sys::g_source_set_priority(s, glib_sys::G_PRIORITY_DEFAULT); 227 | glib_sys::g_source_attach(s, self.ctx); 228 | } 229 | Ok(()) 230 | } 231 | } 232 | 233 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! A thin main loop library for desktop applications and async I/O. 2 | //! 3 | //! See README.md for an introduction and some examples. 4 | 5 | // Because not all backends use everything in the common code 6 | #![allow(unused_variables)] 7 | // #![allow(unused_imports)] 8 | #![allow(dead_code)] 9 | 10 | 11 | #[macro_use] 12 | extern crate lazy_static; 13 | 14 | #[cfg(feature = "glib")] 15 | mod glib; 16 | 17 | #[cfg(feature = "win32")] 18 | mod winmsg; 19 | 20 | #[cfg(feature = "web")] 21 | mod web; 22 | 23 | #[cfg(not(any(feature = "win32", feature = "glib", feature = "web")))] 24 | mod ruststd; 25 | 26 | #[cfg(not(feature = "web"))] 27 | mod mainloop; 28 | 29 | #[cfg(not(feature = "web"))] 30 | pub use crate::mainloop::MainLoop; 31 | 32 | use std::time::Duration; 33 | use std::thread::ThreadId; 34 | 35 | /// Possible error codes returned from the main loop API. 36 | #[derive(Debug)] 37 | pub enum MainLoopError { 38 | TooManyMainLoops, 39 | NoMainLoop, 40 | Unsupported, 41 | DurationTooLong, 42 | Other(Box), 43 | } 44 | 45 | /// Callback Id, can be used to cancel callback before its run. 46 | #[derive(Clone, Debug, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] 47 | pub struct CbId(u64); 48 | 49 | /// Abstraction around unix fds and windows sockets. 50 | #[cfg(windows)] 51 | #[derive(Copy, Clone, Debug, PartialEq, Eq, Ord, PartialOrd, Hash)] 52 | pub struct CbHandle(pub std::os::windows::io::RawSocket); 53 | 54 | /// Abstraction around unix fds and windows sockets. 55 | #[cfg(unix)] 56 | #[derive(Copy, Clone, Debug, PartialEq, Eq, Ord, PartialOrd, Hash)] 57 | pub struct CbHandle(pub std::os::unix::io::RawFd); 58 | 59 | /// Abstraction around unix fds and windows sockets. 60 | #[cfg(not(any(windows, unix)))] 61 | #[derive(Copy, Clone, Debug, PartialEq, Eq, Ord, PartialOrd, Hash)] 62 | pub struct CbHandle(pub i32); 63 | 64 | /* 65 | struct CbFuture<'a> { 66 | #[cfg(feature = "futures")] 67 | future: Box + Unpin + 'a>, 68 | #[cfg(not(feature = "futures"))] 69 | future: &'a (), 70 | instant: Option, 71 | handle: Option<(CbHandle, IODirection)>, 72 | } 73 | */ 74 | 75 | enum CbKind<'a> { 76 | Asap(Box), 77 | After(Box, Duration), 78 | Interval(Box bool + 'a>, Duration), 79 | IO(Box), 80 | // Future(CbFuture<'a>), 81 | } 82 | 83 | impl<'a> CbKind<'a> { 84 | // Constructors 85 | pub fn asap(f: F) -> Self { CbKind::Asap(Box::new(f)) } 86 | pub fn after(f: F, d: Duration) -> Self { CbKind::After(Box::new(f), d) } 87 | pub fn interval bool + 'a>(f: F, d: Duration) -> Self { CbKind::Interval(Box::new(f), d) } 88 | pub fn io(io: IO) -> Self { CbKind::IO(Box::new(io)) } 89 | 90 | // Used to figure out which one it is 91 | pub fn duration(&self) -> Option { 92 | match self { 93 | CbKind::IO(_) => None, 94 | CbKind::Asap(_) => None, 95 | CbKind::After(_, d) => Some(*d), 96 | CbKind::Interval(_, d) => Some(*d), 97 | // CbKind::Future(f) => f.instant.map(|x| x - Instant::now()), 98 | } 99 | } 100 | pub fn duration_millis(&self) -> Result, MainLoopError> { 101 | if let Some(d) = self.duration() { 102 | let m = (u32::max_value() / 1000) - 1; 103 | let s = d.as_secs(); 104 | if s >= m as u64 { return Err(MainLoopError::DurationTooLong) } 105 | Ok(Some((s as u32) * 1000 + d.subsec_millis())) 106 | } else { Ok(None) } 107 | } 108 | 109 | pub fn handle(&self) -> Option<(CbHandle, IODirection)> { 110 | match self { 111 | CbKind::IO(io) => Some((io.handle(), io.direction())), 112 | CbKind::Asap(_) => None, 113 | CbKind::After(_, _) => None, 114 | CbKind::Interval(_, _) => None, 115 | // CbKind::Future(f) => f.handle, 116 | } 117 | } 118 | 119 | // If "false" is returned, please continue with making a call to post_call_mut. 120 | pub (crate) fn call_mut(&mut self, io_dir: Option>) -> bool { 121 | match self { 122 | CbKind::Interval(f, _) => f(), 123 | CbKind::IO(io) => io.on_rw(io_dir.unwrap()), 124 | CbKind::After(_, _) => false, 125 | CbKind::Asap(_) => false, 126 | /* CbKind::Future(f) => { 127 | #[cfg(feature = "futures")] 128 | { 129 | future_impl::do_poll(f) 130 | } 131 | #[cfg(not(feature = "futures"))] 132 | unreachable!() 133 | } */ 134 | } 135 | } 136 | 137 | pub (crate) fn post_call_mut(self) { 138 | match self { 139 | CbKind::After(f, _) => f(), 140 | CbKind::Asap(f) => f(), 141 | CbKind::Interval(_, _) => {}, 142 | CbKind::IO(_) => {}, 143 | // CbKind::Future(_) => {}, 144 | } 145 | } 146 | } 147 | 148 | fn call_internal(cb: CbKind<'static>) -> Result<(), MainLoopError> { 149 | #[cfg(not(feature = "web"))] 150 | let r = mainloop::call_internal(cb); 151 | 152 | #[cfg(feature = "web")] 153 | let r = web::call_internal(cb); 154 | r 155 | } 156 | 157 | 158 | /// Runs a function as soon as possible, i e, when the main loop runs. 159 | /// 160 | /// Corresponding platform specific APIs: 161 | /// * glib: g_idle_add 162 | /// * node.js: process.nextTick 163 | /// * web: Promise.resolve().then(...) 164 | /// * win32: PostMessage 165 | pub fn call_asap(f: F) -> Result<(), MainLoopError> { 166 | let cb = CbKind::asap(f); 167 | call_internal(cb) 168 | } 169 | 170 | /// Runs a function once, after a specified duration. 171 | /// 172 | /// Corresponding platform specific APIs: 173 | /// * glib: g_timeout_add 174 | /// * node.js: setTimeout 175 | /// * web: window.setTimeout 176 | /// * win32: SetTimer 177 | pub fn call_after(d: Duration, f: F) -> Result<(), MainLoopError> { 178 | let cb = CbKind::after(f, d); 179 | call_internal(cb) 180 | } 181 | 182 | /// Runs a function at regular intervals 183 | /// 184 | /// Return "true" from the function to continue running or "false" to 185 | /// remove the callback from the main loop. 186 | /// 187 | /// Corresponding platform specific APIs: 188 | /// * glib: g_timeout_add 189 | /// * node.js: setInterval 190 | /// * web: window.setInterval 191 | /// * win32: SetTimer 192 | pub fn call_interval bool + 'static>(d: Duration, f: F) -> Result<(), MainLoopError> { 193 | let cb = CbKind::interval(f, d); 194 | call_internal(cb) 195 | } 196 | 197 | /// Runs a function on another thread. The target thread must run a main loop. 198 | #[cfg(not(feature = "web"))] 199 | pub fn call_thread(thread: ThreadId, f: F) -> Result<(), MainLoopError> { 200 | mainloop::call_thread_internal(thread, Box::new(f)) 201 | } 202 | 203 | /// Selects whether to wait for a CbHandle to be available for reading, writing, or both. 204 | #[derive(Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd)] 205 | pub enum IODirection { 206 | None, 207 | Read, 208 | Write, 209 | Both, 210 | } 211 | 212 | /// Represents an object that can be read from and/or written to. 213 | pub trait IOAble { 214 | fn handle(&self) -> CbHandle; 215 | 216 | fn direction(&self) -> IODirection; 217 | 218 | fn on_rw(&mut self, _: Result) -> bool; 219 | /* TODO: Handle Errors / hangup / etc */ 220 | } 221 | 222 | /// The most common I/O object is one from which you can read asynchronously. 223 | /// This is a simple convenience wrapper for that kind of I/O object. 224 | pub struct IOReader)>{ 225 | pub io: IO, 226 | pub f: F, 227 | } 228 | 229 | #[cfg(unix)] 230 | impl IOAble for IOReader 231 | where IO: std::os::unix::io::AsRawFd, 232 | F: FnMut(&mut IO, Result) 233 | { 234 | fn handle(&self) -> CbHandle { CbHandle(self.io.as_raw_fd()) } 235 | 236 | fn direction(&self) -> IODirection { IODirection::Read } 237 | fn on_rw(&mut self, r: Result) -> bool { 238 | (self.f)(&mut self.io, r); 239 | true 240 | } 241 | } 242 | 243 | #[cfg(windows)] 244 | impl IOAble for IOReader 245 | where IO: std::os::windows::io::AsRawSocket, 246 | F: FnMut(&mut IO, Result) 247 | { 248 | fn handle(&self) -> CbHandle { CbHandle(self.io.as_raw_socket()) } 249 | 250 | fn direction(&self) -> IODirection { IODirection::Read } 251 | fn on_rw(&mut self, r: Result) -> bool { 252 | (self.f)(&mut self.io, r); 253 | true 254 | } 255 | } 256 | 257 | /// Calls IOAble's callbacks when there is data to be read or written. 258 | pub fn call_io(io: IO) -> Result<(), MainLoopError> { 259 | let cb = CbKind::io(io); 260 | call_internal(cb) 261 | } 262 | 263 | /// Terminates the currently running main loop. 264 | /// 265 | /// This function does nothing if the main loop is not running. 266 | /// This function does nothing with the "web" feature. 267 | pub fn terminate() { 268 | #[cfg(not(feature = "web"))] 269 | mainloop::terminate(); 270 | } 271 | 272 | pub mod future; 273 | 274 | -------------------------------------------------------------------------------- /src/mainloop.rs: -------------------------------------------------------------------------------- 1 | #[cfg(feature = "glib")] 2 | use crate::glib::Backend; 3 | 4 | #[cfg(feature = "win32")] 5 | use crate::winmsg::Backend; 6 | 7 | #[cfg(not(any(feature = "win32", feature = "glib")))] 8 | use crate::ruststd::Backend; 9 | 10 | use std::cell::{Cell, RefCell}; 11 | use std::marker::PhantomData; 12 | use std::rc::Rc; 13 | use std::panic; 14 | use std::any::Any; 15 | use std::time::Duration; 16 | use std::sync::Mutex; 17 | use std::collections::HashMap; 18 | use std::thread::ThreadId; 19 | use crate::{CbKind, CbId, MainLoopError, IOAble}; 20 | 21 | #[derive(Default)] 22 | struct MlTls { 23 | exists: Cell, 24 | terminated: Cell, 25 | running: Cell, 26 | in_queue: RefCell>>, 27 | current_panic: RefCell>>, 28 | } 29 | 30 | // Panic handling 31 | 32 | thread_local! { 33 | static ML_TLS: MlTls = Default::default(); 34 | } 35 | 36 | pub (crate) fn ffi_cb_wrapper R>(f: F, on_panic: R) -> R { 37 | match panic::catch_unwind(panic::AssertUnwindSafe(|| { f() })) { 38 | Ok(x) => x, 39 | Err(e) => { 40 | ML_TLS.with(|m| { 41 | // We should never get a double panic, but if we do, let's ignore the info from the second one. 42 | // Probably the info from the first one is the more helpful. 43 | let _ = m.current_panic.try_borrow_mut().map(|mut cp| { *cp = Some(e); }); 44 | }); 45 | on_panic 46 | } 47 | } 48 | } 49 | 50 | // Thread sends 51 | 52 | pub (crate) trait SendFnOnce: Send { 53 | fn send(&self, f: Box) -> Result<(), MainLoopError>; 54 | } 55 | 56 | lazy_static! { 57 | static ref THREAD_SENDER: Mutex>> = Default::default(); 58 | } 59 | 60 | pub (crate) fn call_thread_internal(thread: ThreadId, f: Box) -> Result<(), MainLoopError> { 61 | let map = THREAD_SENDER.lock().unwrap(); 62 | let sender = map.get(&thread).ok_or(MainLoopError::NoMainLoop)?; 63 | sender.send(f) 64 | } 65 | 66 | pub (crate) fn call_internal(cb: CbKind<'static>) -> Result<(), MainLoopError> { 67 | ML_TLS.with(|m| { 68 | if !m.exists.get() { return Err(MainLoopError::NoMainLoop) } 69 | m.in_queue.borrow_mut().push(cb); 70 | Ok(()) 71 | }) 72 | } 73 | 74 | pub (crate) fn terminate() { 75 | ML_TLS.with(|m| { 76 | m.terminated.set(true); 77 | }); 78 | } 79 | 80 | pub struct MainLoop<'a> { 81 | backend: Backend<'a>, 82 | next_id: Cell, 83 | _z: PhantomData>, // !Send, !Sync 84 | } 85 | 86 | 87 | impl<'a> MainLoop<'a> { 88 | pub fn terminate(&self) { terminate() } 89 | pub fn call_asap(&self, f: F) -> Result { self.push(CbKind::asap(f)) } 90 | pub fn call_after(&self, d: Duration, f: F) -> Result { self.push(CbKind::after(f, d)) } 91 | pub fn call_interval bool + 'a>(&self, d: Duration, f: F) -> Result { self.push(CbKind::interval(f, d)) } 92 | pub fn call_io(&self, io: IO) -> Result { self.push(CbKind::io(io)) } 93 | pub fn cancel(&self, cbid: CbId) -> bool { self.backend.cancel(cbid).is_some() } 94 | 95 | fn push(&self, cb: CbKind<'a>) -> Result { 96 | let x = self.next_id.get(); 97 | self.next_id.set(CbId(x.0 + 1)); 98 | self.backend.push(x, cb)?; 99 | Ok(x) 100 | } 101 | 102 | fn run_wrapper(&self, f: F) -> bool { 103 | ML_TLS.with(|m| { 104 | if m.terminated.get() { return false; } 105 | { 106 | let mut q = m.in_queue.borrow_mut(); 107 | for cbk in q.drain(..) { 108 | self.push(cbk).unwrap(); // TODO: Should probably be reported better 109 | } 110 | } 111 | if m.running.get() { panic!("Reentrant call to MainLoop") } 112 | m.running.set(true); 113 | f(); 114 | m.running.set(false); 115 | if let Some(e) = m.current_panic.borrow_mut().take() { 116 | panic::resume_unwind(e); 117 | } 118 | true 119 | }) 120 | } 121 | 122 | /// Runs the main loop until terminated. 123 | pub fn run(&mut self) { 124 | while self.run_wrapper(|| { 125 | self.backend.run_one(true); 126 | }) {} 127 | } 128 | 129 | /// Runs the main loop once 130 | /// 131 | /// Returns false if the mainloop was terminated. 132 | pub fn run_one(&mut self, allow_wait: bool) -> bool { 133 | self.run_wrapper(|| { 134 | self.backend.run_one(allow_wait); 135 | }) 136 | } 137 | 138 | /// Creates a new main loop 139 | pub fn new() -> Result { 140 | ML_TLS.with(|m| { 141 | if m.exists.get() { return Err(MainLoopError::TooManyMainLoops) }; 142 | 143 | let (be, sender) = Backend::new()?; 144 | let thread_id = std::thread::current().id(); 145 | { 146 | let mut s = THREAD_SENDER.lock().unwrap(); 147 | if s.contains_key(&thread_id) { return Err(MainLoopError::TooManyMainLoops) }; 148 | s.insert(thread_id, sender); 149 | } 150 | 151 | m.in_queue.borrow_mut().clear(); 152 | m.current_panic.borrow_mut().take(); 153 | m.terminated.set(false); 154 | m.running.set(false); 155 | m.exists.set(true); 156 | 157 | Ok(MainLoop { 158 | backend: be, 159 | next_id: Cell::new(CbId(1)), 160 | _z: PhantomData 161 | }) 162 | }) 163 | } 164 | } 165 | 166 | impl Drop for MainLoop<'_> { 167 | fn drop(&mut self) { 168 | ML_TLS.with(|m| { m.exists.set(false); }); 169 | let thread_id = std::thread::current().id(); 170 | THREAD_SENDER.lock().unwrap().remove(&thread_id); 171 | } 172 | } 173 | 174 | #[test] 175 | fn borrowed() { 176 | let mut x; 177 | { 178 | let mut ml = MainLoop::new().unwrap(); 179 | x = false; 180 | ml.call_asap(|| { x = true; terminate(); }).unwrap(); 181 | ml.run(); 182 | } 183 | assert_eq!(x, true); 184 | } 185 | 186 | #[test] 187 | fn asap_static() { 188 | use std::rc::Rc; 189 | 190 | let x; 191 | let mut ml = MainLoop::new().unwrap(); 192 | x = Rc::new(Cell::new(0)); 193 | let xcl = x.clone(); 194 | ml.call_asap(|| { 195 | assert_eq!(x.get(), 0); 196 | x.set(1); 197 | crate::call_asap(move || { 198 | assert_eq!(xcl.get(), 1); 199 | xcl.set(2); 200 | terminate(); 201 | }).unwrap(); 202 | }).unwrap(); 203 | ml.run(); 204 | assert_eq!(x.get(), 2); 205 | } 206 | 207 | #[test] 208 | fn after() { 209 | use std::time::Instant; 210 | let x; 211 | let mut ml = MainLoop::new().unwrap(); 212 | x = Cell::new(false); 213 | let n = Instant::now(); 214 | ml.call_after(Duration::from_millis(300), || { x.set(true); terminate(); }).unwrap(); 215 | ml.run(); 216 | assert_eq!(x.get(), true); 217 | let n2 = Instant::now(); 218 | // Windows seems to have an accuracy of 10 - 20 ms 219 | if (n2 - n) < Duration::from_millis(280) { 220 | panic!("Duration: {:?}", n2 - n); 221 | } 222 | } 223 | 224 | #[test] 225 | fn interval() { 226 | use std::time::Instant; 227 | let mut x = 0; 228 | let mut y = 0; 229 | let n = Instant::now(); 230 | { 231 | let mut ml = MainLoop::new().unwrap(); 232 | ml.call_interval(Duration::from_millis(150), || { 233 | y += 1; 234 | false 235 | }).unwrap(); 236 | ml.call_interval(Duration::from_millis(100), || { 237 | println!("{}", x); 238 | x += 1; 239 | if x >= 4 { terminate(); } 240 | true 241 | }).unwrap(); 242 | ml.run(); 243 | } 244 | assert_eq!(y, 1); 245 | assert_eq!(x, 4); 246 | assert!(Instant::now() - n >= Duration::from_millis(400)); 247 | } 248 | 249 | #[test] 250 | fn thread_test() { 251 | use std::thread; 252 | use std::sync::atomic::{AtomicUsize, Ordering}; 253 | use std::sync::Arc; 254 | 255 | let mut ml = MainLoop::new().unwrap(); 256 | let id = thread::current().id(); 257 | let x = Arc::new(AtomicUsize::new(0)); 258 | let xcl = x.clone(); 259 | thread::spawn(move || { 260 | let srcid = thread::current().id(); 261 | crate::call_thread(id, move || { 262 | assert_eq!(id, thread::current().id()); 263 | assert!(id != srcid); 264 | // println!("Received"); 265 | xcl.store(1, Ordering::SeqCst); 266 | terminate(); 267 | }).unwrap(); 268 | // println!("Sent"); 269 | }); 270 | ml.run(); 271 | assert_eq!(x.load(Ordering::SeqCst), 1); 272 | } 273 | 274 | #[cfg(any(feature = "glib", feature = "win32"))] 275 | #[test] 276 | fn io_test() { 277 | use std::net::TcpStream; 278 | use std::io::{Write, Read}; 279 | use crate::IOReader; 280 | 281 | // Let's first make a blocking call. 282 | let mut io = TcpStream::connect("example.com:80").unwrap(); 283 | io.write(b"GET /someinvalidurl HTTP/1.0\r\n\r\n").unwrap(); 284 | let mut reply1 = String::new(); 285 | io.read_to_string(&mut reply1).unwrap(); 286 | println!("{}", reply1); 287 | 288 | // And now the non-blocking call. 289 | let mut ml = MainLoop::new().unwrap(); 290 | let mut io = TcpStream::connect("example.com:80").unwrap(); 291 | io.set_nonblocking(true).unwrap(); 292 | io.write(b"GET /someinvalidurl HTTP/1.0\r\n\r\n").unwrap(); 293 | 294 | let mut reply2 = String::new(); 295 | let wr = IOReader { io: io, f: move |io: &mut TcpStream, x| { 296 | println!("{:?}", x); 297 | // assert_eq!(x.unwrap(), IODirection::Read); 298 | let r = io.read_to_string(&mut reply2); 299 | println!("r = {:?}, len = {}", r, reply2.len()); 300 | if let Ok(n) = r { 301 | if n == 0 { 302 | println!("{}", reply2); 303 | // Skip the headers, they contain a date field that causes spurious failures 304 | let r1: Vec<_> = reply1.split("\r\n\r\n").collect(); 305 | let r2: Vec<_> = reply2.split("\r\n\r\n").collect(); 306 | assert_eq!(r1.len(), r2.len()); 307 | assert!(r2.len() > 1); 308 | assert_eq!(r1[1], r2[1]); 309 | terminate(); 310 | } 311 | } 312 | }}; 313 | ml.call_io(wr).unwrap(); 314 | ml.run(); 315 | } 316 | 317 | #[test] 318 | fn panic_inside_cb() { 319 | let mut ml = MainLoop::new().unwrap(); 320 | ml.call_asap(|| { panic!("Keep calm and carry on"); }).unwrap(); 321 | let z = panic::catch_unwind(panic::AssertUnwindSafe(|| { ml.run(); })); 322 | let z = z.unwrap_err(); 323 | let zstr = z.downcast_ref::<&str>().unwrap(); 324 | assert_eq!(*zstr, "Keep calm and carry on"); 325 | } 326 | 327 | #[test] 328 | fn cancel_cb() { 329 | let mut ml = MainLoop::new().unwrap(); 330 | let id = ml.call_asap(|| { panic!("This should have been cancelled!"); }).unwrap(); 331 | ml.call_after(Duration::from_millis(50), terminate).unwrap(); 332 | assert_eq!(ml.cancel(id), true); 333 | assert_eq!(ml.cancel(id), false); 334 | ml.run(); 335 | } 336 | 337 | 338 | -------------------------------------------------------------------------------- /src/ruststd.rs: -------------------------------------------------------------------------------- 1 | use std::cell::RefCell; 2 | use std::collections::VecDeque; 3 | use crate::{CbKind, CbId, MainLoopError}; 4 | use std::time::{Instant, Duration}; 5 | use std::thread; 6 | use crate::mainloop::SendFnOnce; 7 | use std::sync::mpsc::{channel, Sender, Receiver}; 8 | 9 | struct Data<'a> { 10 | id: CbId, 11 | next: Instant, 12 | kind: CbKind<'a>, 13 | } 14 | 15 | struct TSender { 16 | thread: thread::Thread, 17 | sender: Sender>, 18 | } 19 | 20 | impl SendFnOnce for TSender { 21 | fn send(&self, f: Box) -> Result<(), MainLoopError> { 22 | self.sender.send(f).map_err(|e| MainLoopError::Other(e.into()))?; 23 | self.thread.unpark(); 24 | Ok(()) 25 | } 26 | } 27 | 28 | pub struct Backend<'a> { 29 | data: RefCell>>, 30 | recv: Receiver>, 31 | } 32 | 33 | impl<'a> Backend<'a> { 34 | pub (crate) fn new() -> Result<(Self, Box), MainLoopError> { 35 | let (tx, rx) = channel(); 36 | let be = Backend { recv: rx, data: Default::default() }; 37 | let sender = TSender { thread: thread::current(), sender: tx }; 38 | Ok((be, Box::new(sender))) 39 | } 40 | 41 | pub fn run_one(&self, wait: bool) -> bool { 42 | let mut d = self.data.borrow_mut(); 43 | let mut item = d.pop_front(); 44 | let mut next: Option = item.as_ref().map(|i| i.next); 45 | let now = Instant::now(); 46 | if let Some(n) = next { 47 | if n > now { 48 | d.push_front(item.take().unwrap()); 49 | } else { 50 | next = d.get(0).map(|i| i.next); 51 | } 52 | } 53 | drop(d); 54 | 55 | if item.is_none() { 56 | if let Ok(cb) = self.recv.try_recv() { 57 | cb(); 58 | return true; 59 | } 60 | } 61 | 62 | if let Some(mut item) = item { 63 | if item.kind.call_mut(None) { 64 | // Remain on the main loop 65 | item.next += item.kind.duration().unwrap(); 66 | self.push_internal(item); 67 | } else { item.kind.post_call_mut() } 68 | true 69 | } else if wait { 70 | if let Some(next) = next { 71 | thread::park_timeout(next - now); 72 | } else { 73 | thread::park(); 74 | } 75 | false 76 | } else { false } 77 | } 78 | 79 | fn push_internal(&self, item: Data<'a>) { 80 | let mut d = self.data.borrow_mut(); 81 | let mut i = 0; 82 | while let Some(x) = d.get(i) { 83 | if x.next > item.next { break; } else { i += 1; } 84 | } 85 | d.insert(i, item); 86 | } 87 | 88 | pub (crate) fn push(&self, id: CbId, cb: CbKind<'a>) -> Result<(), MainLoopError> { 89 | if cb.handle().is_some() { return Err(MainLoopError::Unsupported) }; 90 | 91 | self.push_internal(Data { 92 | id: id, 93 | next: Instant::now() + cb.duration().unwrap_or(Duration::from_secs(0)), 94 | kind: cb 95 | }); 96 | Ok(()) 97 | } 98 | 99 | pub (crate) fn cancel(&self, id: CbId) -> Option> { 100 | let mut d = self.data.borrow_mut(); 101 | d.iter().position(|x| x.id == id) 102 | .and_then(|idx| d.remove(idx)) 103 | .map(|data| data.kind) 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /src/web.rs: -------------------------------------------------------------------------------- 1 | use crate::{CbKind, CbId, MainLoopError}; 2 | 3 | use js_sys::Promise; 4 | use wasm_bindgen::{JsValue, closure::Closure}; 5 | use std::cell::RefCell; 6 | 7 | pub (crate) fn call_internal(cb: CbKind<'static>) -> Result { 8 | let d = cb.duration_millis()?; 9 | match cb { 10 | CbKind::Asap(f) => { 11 | let f2 = RefCell::new(Some(f)); 12 | let id = Closure::wrap(Box::new(move |_| { 13 | let f = f2.borrow_mut().take().unwrap(); 14 | f(); 15 | }) as Box); 16 | Promise::resolve(&JsValue::TRUE).then(&id); 17 | id.forget() 18 | } 19 | _ => unimplemented!(), 20 | } 21 | Ok(CbId()) 22 | } 23 | -------------------------------------------------------------------------------- /src/winmsg.rs: -------------------------------------------------------------------------------- 1 | use crate::{CbKind, CbId, MainLoopError, IODirection}; 2 | use crate::mainloop::{SendFnOnce, ffi_cb_wrapper}; 3 | use winapi; 4 | use std::{mem, ptr}; 5 | use std::sync::{Once, Arc}; 6 | use std::collections::HashMap; 7 | use std::cell::RefCell; 8 | 9 | use winapi::shared::windef::HWND; 10 | use winapi::um::winuser; 11 | use winapi::um::libloaderapi; 12 | use winapi::um::winnt; 13 | use winapi::um::winsock2; 14 | 15 | struct OwnedHwnd(HWND); 16 | 17 | unsafe impl Send for OwnedHwnd {} 18 | unsafe impl Sync for OwnedHwnd {} 19 | 20 | impl Drop for OwnedHwnd { 21 | fn drop(&mut self) { unsafe { winuser::DestroyWindow(self.0); } } 22 | } 23 | 24 | struct BeInternal<'a> { 25 | wnd: Arc, 26 | cb_map: RefCell>>, 27 | socket_map: RefCell>, 28 | } 29 | 30 | impl<'a> BeInternal<'a> { 31 | fn call_data(&self, cbid: CbId, dir: Option>) -> bool { 32 | let kind = self.cb_map.borrow_mut().remove(&cbid); 33 | if let Some(mut kind) = kind { 34 | if kind.call_mut(dir) { 35 | self.cb_map.borrow_mut().insert(cbid, kind); 36 | return true; 37 | } 38 | self.remove(cbid, &kind); 39 | kind.post_call_mut(); 40 | } 41 | false 42 | } 43 | 44 | fn remove(&self, cbid: CbId, kind: &CbKind<'a>) { 45 | if let Some((sock, _)) = kind.handle() { 46 | let sock = sock.0 as usize; 47 | unsafe { winsock2::WSAAsyncSelect(sock, self.wnd.0, WM_SOCKET, 0); } 48 | self.socket_map.borrow_mut().remove(&sock); 49 | } 50 | if let Some(_) = kind.duration() { 51 | unsafe { winuser::KillTimer(self.wnd.0, cbid.0 as usize); } 52 | } 53 | } 54 | } 55 | 56 | // Boxed because we need the pointer not to move in callbacks 57 | pub struct Backend<'a>(Box>); 58 | 59 | impl SendFnOnce for Arc { 60 | fn send(&self, f: Box) -> Result<(), MainLoopError> { 61 | let cb = CbKind::Asap(f as _); 62 | let x = Box::into_raw(Box::new(cb)); 63 | unsafe { 64 | winuser::PostMessageA(self.0, WM_CALL_THREAD, x as usize, 0); 65 | } 66 | Ok(()) 67 | } 68 | } 69 | 70 | const WM_CALL_ASAP: u32 = winuser::WM_USER + 10; 71 | const WM_SOCKET: u32 = winuser::WM_USER + 11; 72 | const WM_CALL_THREAD: u32 = winuser::WM_USER + 12; 73 | static WINDOW_CLASS: Once = Once::new(); 74 | static WINDOW_CLASS_NAME: &[u8] = b"Rust function dispatch\0"; 75 | 76 | fn ensure_window_class() { 77 | WINDOW_CLASS.call_once(|| unsafe { 78 | let mut wc: winuser::WNDCLASSA = mem::zeroed(); 79 | wc.lpszClassName = WINDOW_CLASS_NAME.as_ptr() as *const winnt::CHAR; 80 | wc.hInstance = libloaderapi::GetModuleHandleA(ptr::null_mut()); 81 | wc.lpfnWndProc = Some(wnd_callback); 82 | winuser::RegisterClassA(&wc); 83 | }); 84 | } 85 | 86 | unsafe extern "system" fn wnd_callback(wnd: HWND, msg: u32, wparam: usize, lparam: isize) -> isize { 87 | match msg { 88 | WM_SOCKET => {}, 89 | winuser::WM_TIMER => {}, 90 | WM_CALL_ASAP => {}, 91 | WM_CALL_THREAD => {}, 92 | _ => { 93 | return winuser::DefWindowProcA(wnd, msg, wparam, lparam); 94 | } 95 | }; 96 | 97 | ffi_cb_wrapper(|| { 98 | let be = winuser::GetWindowLongPtrW(wnd, winuser::GWLP_USERDATA); 99 | assert!(be != 0); 100 | let be: &BeInternal = mem::transmute(be); 101 | match msg { 102 | WM_SOCKET => { 103 | // println!("WM_Socket: {} {}", wparam, lparam); 104 | let dir = match (lparam as i32) & (winsock2::FD_READ | winsock2::FD_WRITE) { 105 | 0 => Ok(IODirection::None), 106 | winsock2::FD_READ => Ok(IODirection::Read), 107 | winsock2::FD_WRITE => Ok(IODirection::Write), 108 | _ => Ok(IODirection::Both), 109 | }; 110 | let cbid = *be.socket_map.borrow().get(&wparam).unwrap(); 111 | if !be.call_data(cbid, Some(dir)) { 112 | winsock2::WSAAsyncSelect(wparam, wnd, WM_SOCKET, 0); 113 | be.socket_map.borrow_mut().remove(&wparam); 114 | } 115 | }, 116 | winuser::WM_TIMER | WM_CALL_ASAP => { 117 | let cbid = CbId(wparam as u64); 118 | be.call_data(cbid, None); 119 | }, 120 | WM_CALL_THREAD => { 121 | let mut kind: Box> = Box::from_raw(wparam as *mut _); 122 | assert!(!kind.call_mut(None)); 123 | kind.post_call_mut(); 124 | } 125 | _ => unreachable!(), 126 | }; 127 | 0 128 | }, 0) 129 | } 130 | 131 | impl<'a> Drop for Backend<'a> { 132 | fn drop(&mut self) { 133 | unsafe { winuser::SetWindowLongPtrW(self.0.wnd.0, winuser::GWLP_USERDATA, 0) }; 134 | } 135 | } 136 | 137 | impl<'a> Backend<'a> { 138 | pub (crate) fn new() -> Result<(Self, Box), MainLoopError> { 139 | ensure_window_class(); 140 | //println!("call CreateWindowExA"); 141 | let wnd = unsafe { winuser::CreateWindowExA( 142 | 0, 143 | WINDOW_CLASS_NAME.as_ptr() as *const winnt::CHAR, 144 | b"Test\0".as_ptr() as *const winnt::CHAR, 145 | winuser::WS_OVERLAPPEDWINDOW, 146 | winuser::CW_USEDEFAULT, 147 | winuser::CW_USEDEFAULT, 148 | winuser::CW_USEDEFAULT, 149 | winuser::CW_USEDEFAULT, 150 | ptr::null_mut(), 151 | ptr::null_mut(), 152 | libloaderapi::GetModuleHandleA(ptr::null_mut()), 153 | ptr::null_mut() 154 | ) }; 155 | // println!("call CreateWindowExA finish"); 156 | // println!("wnd: {:?}", wnd); 157 | assert!(!wnd.is_null()); 158 | let ownd = Arc::new(OwnedHwnd(wnd)); 159 | let be = Box::new(BeInternal { 160 | wnd: ownd.clone(), 161 | cb_map: Default::default(), 162 | socket_map: Default::default() 163 | }); 164 | unsafe { 165 | let be_ptr: &BeInternal = &be; 166 | let be_ptr = mem::transmute(be_ptr); 167 | winuser::SetWindowLongPtrW(wnd, winuser::GWLP_USERDATA, be_ptr); 168 | }; 169 | Ok((Backend(be), Box::new(ownd))) 170 | } 171 | 172 | pub fn run_one(&self, wait: bool) -> bool { 173 | unsafe { 174 | let mut msg = mem::zeroed(); 175 | if winuser::PeekMessageW(&mut msg, self.0.wnd.0, 0, 0, winuser::PM_REMOVE) != 0 { 176 | winuser::TranslateMessage(&msg); 177 | winuser::DispatchMessageW(&msg); 178 | true 179 | } else if wait { 180 | winuser::WaitMessage(); 181 | false 182 | } else { false } 183 | } 184 | } 185 | 186 | pub (crate) fn cancel(&self, cbid: CbId) -> Option> { 187 | let z = self.0.cb_map.borrow_mut().remove(&cbid); 188 | if let Some(cb) = z.as_ref() { 189 | self.0.remove(cbid, cb); 190 | }; 191 | z 192 | } 193 | 194 | pub (crate) fn push(&self, cbid: CbId, cb: CbKind<'a>) -> Result<(), MainLoopError> { 195 | assert!(cbid.0 <= std::usize::MAX as u64); 196 | let cbu = cbid.0 as usize; 197 | let wnd = self.0.wnd.0; 198 | if let Some((socket, direction)) = cb.handle() { 199 | let events = match direction { 200 | IODirection::None => 0, 201 | IODirection::Read => winsock2::FD_READ, 202 | IODirection::Write => winsock2::FD_WRITE, 203 | IODirection::Both => winsock2::FD_READ | winsock2::FD_WRITE, 204 | } + winsock2::FD_CLOSE; 205 | let sock = socket.0 as usize; 206 | unsafe { winsock2::WSAAsyncSelect(sock, wnd, WM_SOCKET, events) }; 207 | self.0.socket_map.borrow_mut().insert(sock, cbid); 208 | } else if let Some(d) = cb.duration_millis()? { 209 | unsafe { winuser::SetTimer(wnd, cbu, d, None); } 210 | } else { 211 | unsafe { winuser::PostMessageW(wnd, WM_CALL_ASAP, cbu, 0); } 212 | }; 213 | self.0.cb_map.borrow_mut().insert(cbid, cb); 214 | Ok(()) 215 | } 216 | } 217 | --------------------------------------------------------------------------------