diff --git a/include/tinia/qtcontroller/QTController.hpp b/include/tinia/qtcontroller/QTController.hpp index 6d91ab3..dd5460e 100644 --- a/include/tinia/qtcontroller/QTController.hpp +++ b/include/tinia/qtcontroller/QTController.hpp @@ -42,6 +42,7 @@ namespace jobcontroller { namespace qtcontroller { namespace impl { class ServerController; + class Invoker; } class GUIBuilder; @@ -84,7 +85,7 @@ class QTController : public jobcontroller::Controller boost::scoped_ptr m_app; boost::scoped_ptr m_main_window; - + impl::Invoker* m_invoker; // Lifetime managed by qt parent-child machinery. impl::ServerController* m_serverController; // Lifetime managed by qt parent-child machinery. QToolBar* m_toolBar; // Lifetime managed by qt parent-child machinery. diff --git a/include/tinia/qtcontroller/impl/ServerThread.hpp b/include/tinia/qtcontroller/impl/ServerThread.hpp index b68710f..11dc0c3 100644 --- a/include/tinia/qtcontroller/impl/ServerThread.hpp +++ b/include/tinia/qtcontroller/impl/ServerThread.hpp @@ -4,6 +4,7 @@ #include "tinia/jobcontroller.hpp" #include "tinia/model/impl/xml/XMLHandler.hpp" #include "tinia/qtcontroller/moc/OpenGLServerGrabber.hpp" +#include "tinia/qtcontroller/moc/Invoker.hpp" namespace tinia { namespace qtcontroller { @@ -12,10 +13,10 @@ namespace impl { class ServerThread : public QRunnable { public: - explicit ServerThread(OpenGLServerGrabber& grabber, + explicit ServerThread(OpenGLServerGrabber* grabber, + Invoker* mainthread_invoker, tinia::jobcontroller::Job* job, - int socket, - QObject *parent = 0); + int socket ); void run(); @@ -23,7 +24,6 @@ class ServerThread : public QRunnable bool isLongPoll(const QString& request); - void getSnapshotTxt(QTextStream& os, const QString& request); /** Handles non-static content, if applicable. * @returns true if the file is non-static, false otherwise. @@ -33,8 +33,6 @@ class ServerThread : public QRunnable void updateState(QTextStream& os, const QString& request); - void getRenderList(QTextStream& os, const QString& request); - /** Writes the error code to the stream formated as HTTP requires, * with the optional message formated in HTML */ @@ -44,9 +42,9 @@ class ServerThread : public QRunnable int m_socket; tinia::model::impl::xml::XMLHandler m_xmlHandler; - tinia::jobcontroller::Job* m_job; - - OpenGLServerGrabber& m_grabber; + tinia::jobcontroller::Job* m_job; + OpenGLServerGrabber* m_grabber; + Invoker* m_mainthread_invoker; }; } // namespace impl diff --git a/include/tinia/qtcontroller/moc/HTTPServer.hpp b/include/tinia/qtcontroller/moc/HTTPServer.hpp index 6d8eec6..2a2f6ec 100644 --- a/include/tinia/qtcontroller/moc/HTTPServer.hpp +++ b/include/tinia/qtcontroller/moc/HTTPServer.hpp @@ -2,6 +2,7 @@ #include "tinia/jobcontroller.hpp" #include "tinia/qtcontroller/moc/OpenGLServerGrabber.hpp" +#include "tinia/qtcontroller/moc/Invoker.hpp" #include "tinia/model/impl/xml/XMLHandler.hpp" #include #include @@ -18,16 +19,15 @@ class HTTPServer : public QTcpServer /** * Takes control over imageSource */ - explicit HTTPServer(tinia::jobcontroller::Job*, - tinia::qtcontroller::impl::OpenGLServerGrabber* imageSource, + explicit HTTPServer( tinia::jobcontroller::Job*, QObject *parent = 0); void incomingConnection(int socket); private: - tinia::jobcontroller::Job* m_job; - - boost::scoped_ptr m_serverGrabber; + tinia::jobcontroller::Job* m_job; + OpenGLServerGrabber* m_serverGrabber; // Lifetime managed by Qt child-parent + Invoker* m_mainthread_invoker; // Lifetime managed by Qt child-parent. }; diff --git a/include/tinia/qtcontroller/moc/Invoker.hpp b/include/tinia/qtcontroller/moc/Invoker.hpp new file mode 100644 index 0000000..240715f --- /dev/null +++ b/include/tinia/qtcontroller/moc/Invoker.hpp @@ -0,0 +1,56 @@ +#pragma once + +#include +#include +#include +#include + +namespace tinia { +namespace qtcontroller { +namespace impl { + + +class Invoker : public QObject +{ + Q_OBJECT +public: + + + /** Create a new invoker object. + * \note Must be created in main thread. + */ + explicit Invoker( QObject* parent ); + + void + invokeInMainThread( QRunnable* function, bool block = true ); + + +signals: + + void + requestMainThreadInvocation( QRunnable* function, bool* i_am_done ); + +private slots: + + void + handleMainThreadInvocation( QRunnable* function, bool *i_am_done ); + +private: + QThread* m_main_thread; + QMutex m_wait_mutex; + + /** Shared wait conditions for all invocations. + * + * This wait condition is shared by all invocations, as the number of + * concurrent invocations is currently assumed to be low. We use a bool + * flag to specify which condition that is true. + * + * Refers to \ref m_wait_mutex. + */ + QWaitCondition m_wait_condition; + +}; + +} // namespace impl +} // namespace qtcontroller +} // namespace tinia diff --git a/include/tinia/qtcontroller/moc/OpenGLServerGrabber.hpp b/include/tinia/qtcontroller/moc/OpenGLServerGrabber.hpp index 4f3d75f..4c7b746 100644 --- a/include/tinia/qtcontroller/moc/OpenGLServerGrabber.hpp +++ b/include/tinia/qtcontroller/moc/OpenGLServerGrabber.hpp @@ -11,54 +11,53 @@ namespace tinia { namespace qtcontroller { namespace impl { -class OpenGLServerGrabber : public QObject, public ImageSource +class OpenGLServerGrabber : public QObject { Q_OBJECT public: - explicit OpenGLServerGrabber(tinia::jobcontroller::Job* job, - QObject *parent = 0); + explicit OpenGLServerGrabber( QObject *parent ); ~OpenGLServerGrabber(); - void getImageAsText(QTextStream& os, unsigned int width, unsigned int height, QString key); + /** Mutex that governs exclusive access to this object. */ + QMutex* + exclusiveAccessMutex() + { return &m_mainMutex; } + + /** Returns a pointer to the grabbed image. + * + * \note \ref exclusiveAccessMutex must be held before invocation. + */ + const unsigned char* + imageBuffer() const + { return m_buffer; } + + /** Grabs an image of a view + * + * \note Must be invoked in the thread that holds the OpenGL context, + * usually the main/GUI-thread. + * \note \ref exclusiveAccessMutex must be held before invocation. + */ + void + grab( tinia::jobcontroller::OpenGLJob* job, + unsigned int width, + unsigned int height, + const std::string &key ); - void getRenderListUpdateResponse( QTextStream& response, - const QString& request ); -signals: - void glImageReady(); - void getGLImage(unsigned int width, unsigned int height, QString key); - void signalGetRenderListUpdate( const QString& request ); - -private slots: - void getImage(unsigned int width, unsigned int height, QString key); - void getRenderListUpdate( const QString& request ); - void wakeListeners(); - private: void setupOpenGL(); void resize(unsigned int width, unsigned int height); - bool m_glImageIsReady; - // We only want to grab one image at the time - QMutex m_mainMutex; - - // This is for waiting. We let the event-loop take the image, and wait - // for it to finish. - QMutex m_waitMutex; - QWaitCondition m_waitCondition; - - tinia::jobcontroller::Job* m_job; - unsigned char* m_buffer; + QMutex m_mainMutex; + unsigned char* m_buffer; size_t m_buffer_size; - QString m_renderlist_update_xml; - bool m_openglIsReady; - unsigned int m_fbo; - unsigned int m_renderbufferRGBA; - unsigned int m_renderbufferDepth; - - unsigned int m_width; - unsigned int m_height; + bool m_openglIsReady; + unsigned int m_fbo; + unsigned int m_renderbufferRGBA; + unsigned int m_renderbufferDepth; + unsigned int m_width; + unsigned int m_height; }; } // namespace impl diff --git a/src/qtcontroller/HTTPServer.cpp b/src/qtcontroller/HTTPServer.cpp index a9f7dd8..1cbc2e8 100644 --- a/src/qtcontroller/HTTPServer.cpp +++ b/src/qtcontroller/HTTPServer.cpp @@ -39,16 +39,22 @@ namespace tinia { namespace qtcontroller { namespace impl { -HTTPServer::HTTPServer(tinia::jobcontroller::Job* job, tinia::qtcontroller::impl::OpenGLServerGrabber* imageSource, QObject *parent) : - QTcpServer(parent), m_job(job), - m_serverGrabber(imageSource) +HTTPServer::HTTPServer( tinia::jobcontroller::Job* job, + QObject *parent) + : QTcpServer(parent), + m_job(job), + m_serverGrabber( new OpenGLServerGrabber( this ) ), + m_mainthread_invoker( new Invoker( this ) ) { listen(QHostAddress::Any, 8080); } void HTTPServer::incomingConnection(int socket) { - ServerThread* thread = new ServerThread(*m_serverGrabber, m_job, socket); + ServerThread* thread = new ServerThread( m_serverGrabber, + m_mainthread_invoker, + m_job, + socket ); //connect(thread, SIGNAL(finished()), thread, SLOT(deleteLater())); diff --git a/src/qtcontroller/Invoker.cpp b/src/qtcontroller/Invoker.cpp new file mode 100644 index 0000000..c4e8485 --- /dev/null +++ b/src/qtcontroller/Invoker.cpp @@ -0,0 +1,55 @@ +#include +#include +#include +#include "tinia/qtcontroller/moc/Invoker.hpp" +namespace tinia { +namespace qtcontroller { +namespace impl { + +Invoker::Invoker( QObject* parent ) + : QObject( parent ), + m_main_thread( QThread::currentThread() ) +{ + connect( this, SIGNAL( requestMainThreadInvocation(QRunnable*,bool*)), + this, SLOT( handleMainThreadInvocation(QRunnable*,bool*)) ); +} + +void +Invoker::invokeInMainThread( QRunnable* function, bool block ) +{ + Q_ASSERT( function != NULL ); + if( !block ) { + emit requestMainThreadInvocation( function, NULL ); + } + else { + bool i_am_done = false; // specific condition for this request. + emit requestMainThreadInvocation( function, &i_am_done ); + + // wait for function to complete + QMutexLocker locker( &m_wait_mutex ); + while ( !i_am_done ) { + m_wait_condition.wait( &m_wait_mutex ); + } + } +} + + +void +Invoker::handleMainThreadInvocation( QRunnable* function, bool* i_am_done ) +{ + Q_ASSERT( QThread::currentThread() == m_main_thread ); + function->run(); + + // notify completion if invocation is blocking. + if( i_am_done != NULL ) { + QMutexLocker locker( &m_wait_mutex ); + *i_am_done = true; + m_wait_condition.wakeAll(); + } +} + + + +} // namespace impl +} // namespace qtcontroller +} // namespace tinia diff --git a/src/qtcontroller/OpenGLServerGrabber.cpp b/src/qtcontroller/OpenGLServerGrabber.cpp index ee80164..b8afc53 100644 --- a/src/qtcontroller/OpenGLServerGrabber.cpp +++ b/src/qtcontroller/OpenGLServerGrabber.cpp @@ -9,19 +9,14 @@ namespace tinia { namespace qtcontroller { namespace impl { -OpenGLServerGrabber::OpenGLServerGrabber(tinia::jobcontroller::Job* job, - QObject *parent) : - QObject(parent), m_glImageIsReady(false), m_job(job), m_buffer( NULL ), m_buffer_size(0), m_openglIsReady(false), - m_width(500), m_height(500) +OpenGLServerGrabber::OpenGLServerGrabber( QObject *parent ) + : QObject(parent), + m_buffer( NULL ), + m_buffer_size(0), + m_openglIsReady(false), + m_width(500), + m_height(500) { - connect(this, SIGNAL(glImageReady()), this, - SLOT(wakeListeners())); - - connect(this, SIGNAL(getGLImage(uint,uint,QString)), this, - SLOT(getImage(uint,uint,QString))); - - connect( this, SIGNAL(signalGetRenderListUpdate(QString)), - this, SLOT(getRenderListUpdate(QString)) ); } OpenGLServerGrabber::~OpenGLServerGrabber() @@ -33,108 +28,22 @@ OpenGLServerGrabber::~OpenGLServerGrabber() } } -void OpenGLServerGrabber::getRenderListUpdateResponse( QTextStream& response, - const QString& request ) +void +OpenGLServerGrabber::grab( jobcontroller::OpenGLJob *job, + unsigned int width, + unsigned int height, + const std::string& key ) { - m_mainMutex.lock(); - - emit signalGetRenderListUpdate( request ); - - m_waitMutex.lock(); - while(!m_glImageIsReady) { - m_waitCondition.wait(&m_waitMutex); - } - - response << httpHeader("application/xml") << "\r\n"; - response << m_renderlist_update_xml << "\n"; - - m_glImageIsReady = false; - m_waitMutex.unlock(); - m_mainMutex.unlock(); -} - -void OpenGLServerGrabber::getRenderListUpdate( const QString& request ) -{ - // runs as main thread - tinia::jobcontroller::OpenGLJob* openGLJob = static_cast(m_job); - if(!openGLJob) { - throw std::invalid_argument("This is not an OpenGL job!"); - } - - typedef boost::tuple params_t; - params_t params = parseGet( decodeGetParameters(request), - "key timestamp" ); - const tinia::renderlist::DataBase* db = openGLJob->getRenderList( "session", - params.get<0>() ); - if(db) { - std::string list = renderlist::getUpdateXML( db, - renderlist::ENCODING_JSON, - params.get<1>() ); - m_renderlist_update_xml = QString(list.c_str()); - } - else { - m_renderlist_update_xml.clear(); - } - - // notify waiting server thread that we are finished. - m_waitMutex.lock(); - m_glImageIsReady = true; - m_waitCondition.wakeAll(); - m_waitMutex.unlock(); -} - -void OpenGLServerGrabber::getImageAsText(QTextStream &os, unsigned int width, unsigned int height, QString key) -{ - m_mainMutex.lock(); - emit getGLImage(width, height, key); - m_waitMutex.lock(); - while(!m_glImageIsReady) { - m_waitCondition.wait(&m_waitMutex); - } - - QImage img(m_buffer, width, height, QImage::Format_RGB888); - - // This is a temporary fix. The image is reflected through the horizontal - // line y=height ((x, y) |--> (x, h-y) ). - QTransform flipTransformation(1, 0, - 0, -1, - 0, height); - img = img.transformed(flipTransformation); - - - QBuffer qBuffer; - - img.save(&qBuffer, "png"); - os << httpHeader(getMimeType("file.txt")); - QString str(QByteArray(qBuffer.data(), int(qBuffer.size())).toBase64()); - os << "\r\n"<(m_job); - if(!openGLJob) { - throw std::invalid_argument("This is not an OpenGL job!"); - } - if(!m_openglIsReady) { + if( !m_openglIsReady ) { setupOpenGL(); } - - glBindFramebuffer( GL_FRAMEBUFFER, m_fbo ); - if(m_width != width || m_height != height) { resize(width, height); } + glBindFramebuffer( GL_FRAMEBUFFER, m_fbo ); glViewport( 0, 0, width, height ); - - - - openGLJob->renderFrame( "session", key.toStdString(), m_fbo, width, height ); + job->renderFrame( "session", key, m_fbo, width, height ); // QImage requires scanline size to be a multiple of 32 bits. size_t scanline_size = 4*((3*width+3)/4); @@ -152,18 +61,11 @@ void OpenGLServerGrabber::getImage(unsigned int width, unsigned int height, QStr glBindFramebuffer( GL_FRAMEBUFFER, m_fbo ); glReadPixels( 0, 0, width, height, GL_RGB, GL_UNSIGNED_BYTE, m_buffer ); - glBindFramebuffer(GL_FRAMEBUFFER, 0); - - emit glImageReady(); + glBindFramebuffer(GL_FRAMEBUFFER, 0); } -void OpenGLServerGrabber::wakeListeners() -{ - m_waitMutex.lock(); - m_glImageIsReady = true; - m_waitCondition.wakeAll(); - m_waitMutex.unlock(); -} + + void OpenGLServerGrabber::setupOpenGL() { diff --git a/src/qtcontroller/QTController.cpp b/src/qtcontroller/QTController.cpp index b4f05ba..802758e 100644 --- a/src/qtcontroller/QTController.cpp +++ b/src/qtcontroller/QTController.cpp @@ -22,6 +22,7 @@ #include "tinia/qtcontroller/GUIBuilder.hpp" #include #include +#include #ifdef TINIA_HAVE_LIBXML #include #endif @@ -46,7 +47,8 @@ namespace tinia { namespace qtcontroller { QTController::QTController() - : m_root_context(NULL), + : m_invoker( NULL ), + m_root_context(NULL), m_job(NULL), m_perf_mode( false ), m_renderlist_mode( false ) @@ -117,8 +119,9 @@ int QTController::run(int argc, char **argv) m_app.reset( new QApplication( argc, argv ) ); - m_main_window.reset( new QMainWindow() ); + m_invoker = new impl::Invoker( m_main_window.get() ); + setupServerController(); // Now we may init the script. diff --git a/src/qtcontroller/ServerController.cpp b/src/qtcontroller/ServerController.cpp index 3ccdd7b..99be16d 100644 --- a/src/qtcontroller/ServerController.cpp +++ b/src/qtcontroller/ServerController.cpp @@ -13,7 +13,7 @@ ServerController::ServerController(tinia::jobcontroller::Job* job, void ServerController::startServer(bool start) { if(start) { - m_server = new HTTPServer(m_job, new OpenGLServerGrabber(m_job), this); + m_server = new HTTPServer(m_job, this); } else if(m_server != NULL) { delete m_server; diff --git a/src/qtcontroller/ServerThread.cpp b/src/qtcontroller/ServerThread.cpp index 3241bd4..55918aa 100644 --- a/src/qtcontroller/ServerThread.cpp +++ b/src/qtcontroller/ServerThread.cpp @@ -7,21 +7,151 @@ #include #include "tinia/renderlist.hpp" #include +#include #include "tinia/qtcontroller/moc/LongPollHandler.hpp" #include "tinia/model/ExposedModelLock.hpp" +namespace { + +class RenderListFetcher : public QRunnable +{ +public: + explicit RenderListFetcher( QTextStream& reply, + const QString& request, + tinia::jobcontroller::Job* job ) + : m_reply( reply ), + m_request( request ) + { + using namespace tinia::qtcontroller::impl; + + m_job = dynamic_cast( job ); + if( m_job == NULL ) { + throw std::invalid_argument("This is not an OpenGL job!"); + } + + m_params = parseGet >( decodeGetParameters(request), + "key timestamp" ); + } + + ~RenderListFetcher() + { + using namespace tinia::qtcontroller::impl; + + m_reply << httpHeader("application/xml") << "\r\n"; + m_reply << m_update << "\n"; + } + + void + run() + { + // runs as GUI thread + using namespace tinia::renderlist; + const DataBase* db = m_job->getRenderList( "session", m_params.get<0>() ); + if(db) { + std::string list = getUpdateXML( db, ENCODING_JSON, m_params.get<1>() ); + m_update = QString( list.c_str() ); + } + } + +protected: + QTextStream& m_reply; + const QString& m_request; + tinia::jobcontroller::OpenGLJob* m_job; + boost::tuple m_params; + QString m_update; +}; + + +class SnapshotAsTextFetcher : public QRunnable +{ +public: + + explicit SnapshotAsTextFetcher( QTextStream& reply, + const QString& request, + tinia::jobcontroller::Job* job, + tinia::qtcontroller::impl::OpenGLServerGrabber* gl_grabber ) + : m_reply( reply ), + m_request( request ), + m_job( NULL ), + m_gl_grabber( gl_grabber ), + m_gl_grabber_locker( gl_grabber->exclusiveAccessMutex() ) + { + using namespace tinia::qtcontroller::impl; + + m_job = dynamic_cast( job ); + if( m_job == NULL ) { + throw std::invalid_argument("This is not an OpenGL job!"); + } + + + typedef boost::tuple params_t; + params_t arguments = parseGet(decodeGetParameters(request), + "width height key" ); + m_width = arguments.get<0>(); + m_height = arguments.get<1>(); + m_key = arguments.get<2>(); + } + + ~SnapshotAsTextFetcher() + { + using namespace tinia::qtcontroller::impl; + QImage img( m_gl_grabber->imageBuffer(), + m_width, + m_height, + QImage::Format_RGB888 ); + + // This is a temporary fix. The image is reflected through the horizontal + // line y=height ((x, y) |--> (x, h-y) ). + QTransform flipTransformation(1, 0, + 0, -1, + 0, m_height); + img = img.transformed(flipTransformation); + QBuffer qBuffer; + img.save(&qBuffer, "png"); + //m_gl_grabber_locker.unlock(); + + m_reply << httpHeader(getMimeType("file.txt")); + QString str( QByteArray( qBuffer.data(), + int(qBuffer.size()) ).toBase64() ); + m_reply << "\r\n"<grab( m_job, m_width, m_height, m_key ); + } + + + +protected: + QTextStream& m_reply; + const QString& m_request; + tinia::jobcontroller::OpenGLJob* m_job; + tinia::qtcontroller::impl::OpenGLServerGrabber* m_gl_grabber; + QMutexLocker m_gl_grabber_locker; + unsigned int m_width; + unsigned int m_height; + std::string m_key; +}; + + +} + + namespace tinia { namespace qtcontroller { namespace impl { -ServerThread::ServerThread(OpenGLServerGrabber &grabber, +ServerThread::ServerThread(OpenGLServerGrabber* grabber, + Invoker* mainthread_invoker, tinia::jobcontroller::Job* job, - int socket, - QObject *parent) : + int socket ) : m_socket(socket), m_xmlHandler(job->getExposedModel()), m_job(job), - m_grabber(grabber) + m_grabber(grabber), + m_mainthread_invoker(mainthread_invoker) { } @@ -81,31 +211,20 @@ bool ServerThread::isLongPoll(const QString &request) } -void ServerThread::getSnapshotTxt(QTextStream &os, const QString &request) -{ - boost::tuple arguments = - parseGet >(decodeGetParameters(request), "width height key"); - - unsigned int width = arguments.get<0>(); - unsigned int height = arguments.get<1>(); - std::string key = arguments.get<2>(); - m_grabber.getImageAsText(os, width, height, QString(key.c_str())); -} bool ServerThread::handleNonStatic(QTextStream &os, const QString& file, const QString& request) { try { if(file == "/snapshot.txt") { - updateState(os, request); - getSnapshotTxt(os, request); + SnapshotAsTextFetcher f( os, request, m_job, m_grabber ); + m_mainthread_invoker->invokeInMainThread( &f, true ); return true; } else if(file == "/getRenderList.xml") { - getRenderList(os, request); + RenderListFetcher f( os, request, m_job ); + m_mainthread_invoker->invokeInMainThread( &f, true ); return true; } else if(file =="/updateState.xml") { @@ -132,25 +251,6 @@ void ServerThread::updateState(QTextStream &os, const QString &request) } } -void ServerThread::getRenderList(QTextStream &os, const QString &request) -{ -#if 1 - m_grabber.getRenderListUpdateResponse( os, request ); -#else - boost::tuple params = parseGet > (decodeGetParameters(request), "key timestamp"); - os << httpHeader("application/xml") << "\r\n"; - tinia::jobcontroller::OpenGLJob* openglJob = dynamic_cast(m_job); - if(openglJob) { - const tinia::renderlist::DataBase* db = openglJob->getRenderList("session", params.get<0>()); - if(db) { - std::string list = renderlist::getUpdateXML( db, - renderlist::ENCODING_JSON, - params.get<1>() ); - os << QString(list.c_str()) << "\n"; - } - } -#endif -} void ServerThread::errorCode(QTextStream &os, unsigned int code, const QString &msg) {