Skip to content

Commit

Permalink
ggobi#10 - Added event loop synchronization between R and Qt for Wind…
Browse files Browse the repository at this point in the history
…ows platform
  • Loading branch information
kaisernahid committed Aug 20, 2013
1 parent 843ef6d commit 9069cad
Showing 1 changed file with 172 additions and 12 deletions.
184 changes: 172 additions & 12 deletions src/EventLoop.cpp
Original file line number Diff line number Diff line change
@@ -1,3 +1,19 @@
/*
* Inspired by the Rttpd.c file from R library and a discussion with Simon Urbanek
* Written on top of the Linux/Mac EventLoop code of Michael & Deepayan
* Warning: The changes made here for Windows platform made it much more stabler than
* before. However, there are know instances of crashes even after that. And I do not
* claim that whatever changes I have made is the right way to go.
* -- Kaiser Md. Nahiduzzaman
*/

/* The event loop code synchronizes R with the Qt event loop (which listens for user input).
* On Linux/Mac, when R is idle, we tell Qt to iterate its event loop. On Windows, somehow
* events get propagated to Qt in a separate thread. This file tries to solve the problem
* for Windows and thus is an effort to make event loop code truly cross-platform.
*/


#include <QApplication>
#include <QTimer>
#include "EventLoop.hpp"
Expand All @@ -11,23 +27,51 @@
at build time, but without autconf, it's annoying. Don't tell BR. */
extern quintptr R_CStackLimit; /* C stack limit */
extern quintptr R_CStackStart; /* Initial stack address */
#include <R_ext/eventloop.h>
#include <R_ext/eventloop.h> /* UNIX-specific input handler implementation */
#include <unistd.h>
#else
/* --- Windows-only --- */
#include <QMutexLocker>
extern __declspec(dllimport) uintptr_t R_CStackLimit; /* C stack limit */
extern __declspec(dllimport) uintptr_t R_CStackStart; /* Initial stack address */
#include <windows.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#define WM_EVENTLOOP_CALLBACK ( WM_USER + 1 )
static HWND message_window;
static LRESULT CALLBACK
EventLoopWindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
#ifndef HWND_MESSAGE
#define HWND_MESSAGE ((HWND)-3) /* NOTE: this is supported by W2k/XP and up only! */
#endif
#endif/* WIN32 */


/* Much of this code inspired by Simon Urbanek's CarbonEL package */
/* The Windows platform specific code is inspired by Rhttpd.c file in R -- Kaiser */

QApplication *app;
static int qapp_argc = 2;
static char *qapp_argv[] = { "qtbase", "-nograb" };

static int processingEvent = 0;
static int in_process = 0;
static int fired = 0, active = 1;

QMutex mutex;

static QtMsgHandler prevMsgHandler;
/* --- flag determining whether one-time initialization is yet to be performed --- */
static int needs_init = 1;

static void callback_input_handler();
static void QEventLoop_exec();

#ifndef WIN32
InputHandler *eventLoopInputHandler = NULL;
static int ifd, ofd;
static int fired = 0, active = 1;
//static int fired = 0, active = 1;//moved outside of ifndef WIN32

static void
R_Qt_eventHandler()
Expand Down Expand Up @@ -59,15 +103,40 @@ void EventLoop::run() {
}
}

static EventLoop* eventLoop = NULL;
/* Why in Unix only? */
//static EventLoop* eventLoop = NULL;//moved outside -- Kaiser

#else
/* WIN32 */
void EventLoop::run() {
//Do busy polling in Windows too
while(active) {
if (!fired) {
fired=1;
callback_input_handler();
}
}
//Will never reach here
return;
}
#endif

void EventLoop::run() {
return;
static void QEventLoop_exec()
{
//An attempt to make the process thread-safe
mutex.lock();
try{
//300 is an empirical value
app->processEvents(QEventLoop::WaitForMoreEvents | QEventLoop::EventLoopExec, 300);
app->sendPostedEvents(0, 0);
}
catch(...){
Rprintf("Exception from processEvents | sendPostedEvents\n");
}
mutex.unlock();
}

#endif
static EventLoop* eventLoop = NULL;//

void R_Qt_msgHandler(QtMsgType type, const char *msg)
{
Expand All @@ -86,16 +155,74 @@ void R_Qt_msgHandler(QtMsgType type, const char *msg)
}
}

static QtMsgHandler prevMsgHandler;

#ifdef WIN32
/* on Windows we have to guarantee that run_callback is performed
on the main thread, so we have to dispatch it through a message */
//static void run_callback_main_thread(bg_conn_t *c);
static void run_callback_main_thread();

static void run_callback()
{
/* SendMessage is synchronous, so it will wait until the message
is processed */
SendMessage(message_window, WM_EVENTLOOP_CALLBACK, 0, 0);
// Alternative
/* PostMessage is asynchronous, so it will return immediately */
//PostMessage(message_window, WM_EVENTLOOP_CALLBACK, 0, (LPARAM) 0);
}
#define run_callback run_callback_main_thread
#endif

/* wrap the actual call with ToplevelExec since we need to have a guaranteed
return so we can track the presence of a worker code inside R to prevent
re-entrance from other clients */
static void run_callback()
{
if (!in_process){
in_process = 1;
if (!processingEvent) {
processingEvent = 1;
QEventLoop_exec();
processingEvent = 0;
}
in_process = 0;
fired=0;
}
}

#ifdef WIN32
#undef run_callback
#endif

static void first_init()
{
#ifdef WIN32
/* create a dummy message-only window for synchronization with the
* main event loop */
HINSTANCE instance = GetModuleHandle(NULL);
LPCTSTR str_class = "EventLoop";
WNDCLASS wndclass = { 0, EventLoopWindowProc, 0, 0, instance, NULL, 0, 0,
NULL, str_class };
RegisterClass(&wndclass);
message_window = CreateWindow(str_class, "EventLoop", 0, 1, 1, 1, 1,
HWND_MESSAGE, NULL, instance, NULL);
#endif/* WIN32 */
needs_init = 0;
}

static void
R_Qt_init()
{
prevMsgHandler = qInstallMsgHandler(R_Qt_msgHandler);
app = new QApplication(qapp_argc, qapp_argv);
// app->exec();
//following call starts a thread and will run the Qt event loop there, which may never return -- Kaiser
//app->exec();
/* WIN32 */
if (needs_init) /* initialization may need to be performed on first use */
first_init();
}

static void
R_Qt_cleanup()
{
Expand All @@ -107,11 +234,33 @@ R_Qt_cleanup()
close(ifd);
close(ofd);
#endif
Rprintf("app->quit().");
app->quit();
qInstallMsgHandler(prevMsgHandler);
delete app;
}


#ifdef WIN32
/* Windows implementation uses threads to do the same as watching the FD and the main event
loop to synchronize with R through a message-only window which is created
on the R thread */
static LRESULT CALLBACK EventLoopWindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
if (hwnd == message_window && uMsg == WM_EVENTLOOP_CALLBACK) {
run_callback_main_thread();
return 0;
}
return DefWindowProc(hwnd, uMsg, wParam, lParam);
}
#endif

/* this is really superfluous - we could just cast run_callback accordingly .. - Simon */
static void callback_input_handler()
{
run_callback();
}

void EventLoop::begin() {
if (!qApp) {
R_Qt_init();
Expand All @@ -130,15 +279,26 @@ void EventLoop::begin() {
eventLoop = new EventLoop();
eventLoop->start();
} else error("Failed to establish pipe for event handling");

#else
/* WIN32 */
/* do the desired Windows synchronization */
/* disable stack checking, because threads will thow it off */
R_CStackLimit = (uintptr_t) -1;

//Rprintf("Entering eventloop thread...");
eventLoop = new EventLoop();
eventLoop->start();
//Rprintf("Outside eventloop thread...");
#endif
}
}

}
extern "C" {
// Entry point in this file
SEXP
addQtEventHandler()
{
//This is the function that gets called from R
EventLoop::begin();
return R_NilValue;
}
Expand Down

0 comments on commit 9069cad

Please sign in to comment.