Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

nanosleep() call from Lua script paused QT GUI thread

Tags:

c++

c

lua

qt4

I am developing a test tool to generate waveform from PC parallel port. This tools is designed to generate any pattern of waveform with timing accuracy of ms, so I use Lua script to define the waveform pattern, the GUI start new QThread to run the script when user clicks [Start] button.

The following three functions for Lua are implemented as C++ global functions:

  • pwrite: write data to parallel port.
  • msleep: wait for certain ms (implemented using nanosleep())
  • print: overwrite Lua default print function, this one will append message to one QTextEdit widget.

when pwrite is called, the written data is stored in global variable, then the GUI is updated with 20ms interval to update the parallel port data on the GUI. (this 20ms interval refresh is not a good design, but I haven't figure out how to use signal to make GUI update when data changed).

The tool is basically functional now. The waveform output has no problem, but the parallel port data updating has some problem:

When Lua call msleep, GUI thread is stopped, the parallel port data updates only after msleep ends.

So my questions are:

  1. How to implement the sleep method so that it won't stop the GUI thread from updating?

  2. How to implement the pwrite, so that the GUI can receive a signal to update the parallel port data when written data changed?

Program GUI as the link below: Program GUI

The related code:

    /* common.cpp file */

int L_MSleep(lua_State* l)
{
    int milisec=0;
    struct timespec req={0, 0};
    time_t sec;

    milisec=luaL_optint(l,1,0); // obtain parameter

    if (milisec==0)
       return 0;

    sec=(int)(milisec/1000);

    milisec=milisec-(sec*1000);
    req.tv_sec=sec;
    req.tv_nsec=milisec*1000000L;

    while(nanosleep(&req,&req)==-1)
         continue;

    return 1;
}


/* LuaRunner.cpp file */
LuaRunner::LuaRunner(QObject *parent) :
    QThread(parent)
{
    runlua = false;
}

void LuaRunner::run()
{
    QString err = "";

    runlua = true;
    LUA_RunScript(this->ff, err);
    runlua = false;

    if(err != "")
    {
        emit errorMessage(err);
    }
}

int LuaRunner::LUA_RunScript(QString ff, QString &err)
{
    L = lua_open();
    luaL_openlibs(L);

    if (luaL_loadfile(L, ff.toAscii()) || lua_pcall(L, 0, 0, 0))
    {
        err = QString(lua_tostring(L, -1));
        return -1;
    }

    lua_register(L, "ssleep", L_SSleep);
    lua_register(L, "msleep", L_MSleep);
    lua_register(L, "pwrite", L_PortWrite);
    lua_register(L, "print", L_Log);

    lua_getglobal(L, "dotest");
    if (!lua_isfunction(L, -1))
    {
        err = QString("Test function(dotest) should be a function");
        return -1;
    }

    if(lua_pcall(L, 0, 0, 0))
    {
        err = QString(lua_tostring(L, -1));
        return -1;
    }

    lua_close(L);

    return 0;
}
like image 424
yongzhy Avatar asked Oct 19 '11 08:10

yongzhy


1 Answers

You correctly run your Lua script in dedicated thread. That's the right way to do it -- almost. You are restarting the thread each time you want to run the script. That's wrong. You are also accessing the data in the GUI thread from the LUA thread without any sort of synchronization. That's not good. Qt provides an excellent mechanism for that in the form of queued connections between signals and slots. When signal-slot calls pass thread boundaries, the parameters get wrapped in a QEvent and get asynchronously delivered to the target QObject. Within each thread, the event delivery is serialized and thus you don't need to worry about data corruption etc.

Here's how it should be done:

// LUAObject.h
#include <QObject>

class LUAObject : public QObject
{
   Q_OBJECT
public:
   LUAObject(QObject * parent = 0);
public slots:
   void setScript(const QString &);
   void runScript();
   void stop();

signals:
   void hasError(const QString &);
   void finished();
   void hasParallelData(int);
   void hasMessage(const QString &);

private:
   QString script;
   bool stop;
}

// LUAObject.cpp

// whatever Lua includes you need etc

LUAObject::LUAObject(QObject* p) : QObject(p)
{}

void LUAObject::stop() { stopped = true; }    

void LUAObject::setScript(const QString & scr)
{ script = scr; }

int L_PWrite(lua_State* l)
{
   int data = luaL_optint(l, 1, -1);
   if (data != -1) {
      // access the parallel port HERE, NOT in the GUI thread!
      emit hasParallelData(luaL_optint(l, 1, 0));
   }
   return 0;
}

// returns a bool - true means we are stopped and should exit
int L_MSleep(lua_State* l)
{
   int ms = luaL_optint(l, 1, -1);
   if (ms == -1) return 0;
   QApplication::processEvents(QEventLoop::WaitForMoreEvents, ms);
   lua_pushBoolean(l, stopped); // event processing would run the stop() slot call
   return 1;
}

int L_SSleep(lua_State* l)
{
   int secs = luaL_optint(l, 1, -1);
   if (secs == -1) return 0;
   QApplication::processEvents(QEventLoop::WaitForMoreEvents, secs*1000);
   lua_pushBoolean(l, stopped); // event processing would run the stop() slot call
   return 1;
}

int L_Log(lua_State* l)
{
   const char * msg = luaL_optstring(l, 1, 0);
   if (!msg) return 0;
   emit hasMessage(msg);
   return 0;
}

class Lua // RAII
{
public:
   explicit Lua(lua_state * l) : L(l) {}
   ~Lua() { lua_close(L); }
   operator lua_state*() const { return L; }
private:
   lua_state * L;
   Q_DISABLE_COPY(LUA)
};

LUAObject::runScript()
{
   stopped = false;
   Lua L(lua_open());
   luaL_openlibs(L);

   if (luaL_loadbuffer(L, script.toAscii().constData(), script.length(), "script") || lua_pcall(L, 0, 0, 0))
   {
       emit hasError(lua_tostring(L, -1));
       return;
   }

   lua_register(L, "ssleep", L_SSleep);
   lua_register(L, "msleep", L_MSleep);
   lua_register(L, "pwrite", L_PWrite);
   lua_register(L, "print", L_Log);

   lua_getglobal(L, "dotest");
   if (!lua_isfunction(L, -1))
   {
      emit hasError("Test function(dotest) should be a function");
      return;
   }

   if(lua_pcall(L, 0, 0, 0))
   {
      emit hasError(lua_tostring(L, -1));
      return;
   }

   emit finished();
}

// main.cpp

#include <QApplication>
#include <QMetaMethod>
#include "LUAObject.h"

...

int main(int argc, char** argv)
{
   QApplication(argc, argv);

   MainWindow window;

   ...
   QThread thread;
   LUAObject lua;
   thread.start(QThread::TimeCriticalPriority);
   lua.moveToThread(&thread);

   ...

   // NOTE: you can ONLY connect to LUAObject slots, you CANNOT call them
   // directly since it runs in a separate thread!
   connect(&window, SIGNAL(startClicked()), &lua, SLOT(runScript());
   connect(&lua, SIGNAL(hasError(QString)), &window, SLOT(luaError(QString)));

   ...
   window.show();
   int rc = qApp->exec();
   QMetaObject::invokeMethod(&lua, SLOT(stop())); // cross-thread slot invocation
   thread.exit();
   thread.wait();
   return rc;
}

I leave the implementation of the UI to your imagination. Note that it's untested code. It may blow up your computer for all I know.

like image 90
2 revs Avatar answered Nov 15 '22 09:11

2 revs