Qt4-preview-feedback Archive, September 2007
QtScripts
Message 1 in thread
QtScripts are very counter-intuitive. The QScriptEngine doesn't
function properly with infinite loop scripts because evaluate never
returns, this is how I feel the functionality should respond:
setProcessEventsInterval(-1); This blocks, alright, excellent,
just as we'd have expected.
setProcessEventsInterval(N); This does not block at all, it
spawns an internal handler which posts a script section every N
millisecond tic.
setProcessEventsInterval(0); This will post as many lines as the
internal handler can spit out without blocking.
As it is now, as long as a script is locked in an infinite loop, it
prevents a: Other loops from being handled, and b: application.quit();
Which means that if a script somehow gets locked into a loop there is
no way to kill it. You cannot delete a QScriptEngine mid-evaluation
because this behavior will throw the assertion below. This makes
giving the end user the ability to create a script a very dangerous
thing.
There are multiple other problems and complaints I have with this
system. An example is QScriptEngines handling of unhandledExceptions
without passing of a scriptId (why give the functionality to be able
to run scripts concurrently when there's no way to decide who threw
the exception without the use of an agent?).
As QScriptAgents are now, without the 'control' they promise, they
offer me almost no extra functionality to what I need. There is still
no way to pause the execution of a script (because evaluate never
returns, so this functionality would be useless anyhow), for line by
line stepping, among other things. If the setProcessEventsInterval is
'fixed' as I described, QScriptAgents will become very useful,
especially if some more functions are added to it.. the 'control' side
that was never delivered.
Some 'incorrectly, but aptly named' functions I came up with that
would improve this sitatuation, given a different
setProcessEventsInterval is implemented would be:
QScriptAgent::stopScript(script_id)
QScriptAgent::startScript(script_id)
While still allowing an arbitrary backtrace on a script.
Now there are further problems yet, as the inability to pass a global
environment between QScriptEngines (I attempted this as a testing
work-around for some of the above problems). There's no way to pass
the global environment from one QScriptEngine to another as far as I
know. Meaning that whenever a new QScriptEngine is created, you have
to physically re-create all the properties to be passed to the
QScriptEngine, which makes any sort of 'jail' environment implausible
on a large scale. Maybe I have not researched this enough, this isn't
a big issue with me.
In case you're wondering why I'm straining the library so much, I'm
trying to create scripts that fall under these constraints:
1: Many scripts running concurrently.
2: The ability to track scripts and debug them even while running.
3: The ability to pause, and restart scripts, and arbitrarily kill scripts.
4: The ability to parse a script line by line.
My original attempt was to use one QScriptEngine to run many script
bodies at once, and this failed because I had no control. Maybe
QScriptAgent will fix this but as it stands now, even when attempting
to work around these problems I just run into another one. Right now
I simply can't get past evaluate never returning on an infinite loop..
and I can't interrupt infinite loops because there's nothing to
facilitate this.
As it stands now, any attempt I try to make at arbitrarily killing a
script results in an assertion being thrown.
ASSERT: "iPtr->operand[0].isString()" in file qscriptcontext_p.cpp, line 609
Now, should I post any of these as bugs?
To unsubscribe - send "unsubscribe" in the subject to qt4-preview-feedback-request@xxxxxxxxxxxxx
Message 2 in thread
Hi Tyler,
Thanks for the detailed feedback. I haven't had time to look into every
issue in detail yet, but below are my preliminary comments.
Tyler Ross wrote:
> QtScripts are very counter-intuitive. The QScriptEngine doesn't
> function properly with infinite loop scripts because evaluate never
> returns, this is how I feel the functionality should respond:
> setProcessEventsInterval(-1); This blocks, alright, excellent,
> just as we'd have expected.
> setProcessEventsInterval(N); This does not block at all, it
> spawns an internal handler which posts a script section every N
> millisecond tic.
> setProcessEventsInterval(0); This will post as many lines as the
> internal handler can spit out without blocking.
>
Interesting idea; however, it doesn't correspond with the intention of
processEventsInterval, so we would have to add it as a new API (e.g. an
evaluateLater() function that returns a script ID).
> As it is now, as long as a script is locked in an infinite loop, it
> prevents a: Other loops from being handled, and b: application.quit();
> Which means that if a script somehow gets locked into a loop there is
> no way to kill it. You cannot delete a QScriptEngine mid-evaluation
> because this behavior will throw the assertion below. This makes
> giving the end user the ability to create a script a very dangerous
> thing.
>
QScriptEngine::abortEvaluation() (and QScriptEngine::isEvaluating())
have just been added. You'll see them in tomorrow's snapshots. I've
attached some example code here to show how you can combine this with
setProcessEventsInterval() to abort a long-running script.
> There are multiple other problems and complaints I have with this
> system. An example is QScriptEngines handling of unhandledExceptions
> without passing of a scriptId (why give the functionality to be able
> to run scripts concurrently when there's no way to decide who threw
> the exception without the use of an agent?).
>
A single script engine can't run scripts concurrently, so the engine
itself can be used to identify the script. But yes, I agree that the ID
could be made available without having to use an agent, and we will look
into adding an API for this.
> As QScriptAgents are now, without the 'control' they promise, they
> offer me almost no extra functionality to what I need. There is still
> no way to pause the execution of a script (because evaluate never
> returns, so this functionality would be useless anyhow), for line by
> line stepping, among other things. If the setProcessEventsInterval is
> 'fixed' as I described, QScriptAgents will become very useful,
> especially if some more functions are added to it.. the 'control' side
> that was never delivered.
>
You do have control, but you have to 1) run your own event loop from
within the agent callbacks (if you only have a single (GUI) thread), or
2) evaluate scripts in another thread (or even another process, if you
want) and use Qt's synchronization mechanisms to manage the interaction
between the GUI and script thread(s).
> Some 'incorrectly, but aptly named' functions I came up with that
> would improve this sitatuation, given a different
> setProcessEventsInterval is implemented would be:
>
> QScriptAgent::stopScript(script_id)
> QScriptAgent::startScript(script_id)
>
> While still allowing an arbitrary backtrace on a script.
>
Noted (see my first comment).
> Now there are further problems yet, as the inability to pass a global
> environment between QScriptEngines (I attempted this as a testing
> work-around for some of the above problems). There's no way to pass
> the global environment from one QScriptEngine to another as far as I
> know. Meaning that whenever a new QScriptEngine is created, you have
> to physically re-create all the properties to be passed to the
> QScriptEngine, which makes any sort of 'jail' environment implausible
> on a large scale. Maybe I have not researched this enough, this isn't
> a big issue with me.
>
Indeed, the Global Object of an engine is tied to that particular engine.
> In case you're wondering why I'm straining the library so much, I'm
> trying to create scripts that fall under these constraints:
> 1: Many scripts running concurrently.
>
Sure, have multiple threads each with one QScriptEngine.
> 2: The ability to track scripts and debug them even while running.
>
Yep, in your case (using separate threads to run scripts) you can do
that by implementing an agent that communicates with the GUI thread.
E.g. when the agent receives a positionChange() callback and finds that
a breakpoint should be triggered, it would notify the GUI thread, then
block (using QWaitCondition) until the GUI thread wakes it up. At that
point, the positionChange() callback in the script thread would return
(i.e. evaluate() continues to run the show).
> 3: The ability to pause, and restart scripts, and arbitrarily kill scripts.
>
Hopefully abortEvaluation() covers the killing part. For pausing, it's
similar to breakpoints and stepping, only that it's initiated from the
GUI side; the GUI threads sends off a "pause request" event to the
script thread, which then (the next time positionChange() is called, for
example) blocks on a condition until the GUI thread wakes it up again
(e.g. in response to a "Resume" button being clicked). For restarting,
you could call abortEvaluation() passing it a special value as argument
(this will be the value actually returned from the aborted evaluate()
call); the code that originally called evaluate() would check the return
value and find that "a-ha, I should call evaluate() again with the same
arguments". Or just have a dedicated restart-variable in the thread that
evaluates the script, and set it to true before calling abortEvaluation().
> 4: The ability to parse a script line by line.
>
Hmm, could you explain a bit more what you mean here? What's the use case?
> My original attempt was to use one QScriptEngine to run many script
> bodies at once, and this failed because I had no control. Maybe
> QScriptAgent will fix this but as it stands now, even when attempting
> to work around these problems I just run into another one. Right now
> I simply can't get past evaluate never returning on an infinite loop..
> and I can't interrupt infinite loops because there's nothing to
> facilitate this.
>
See previous comment regarding abortEvaluation(), hopefully it resolves
your biggest issue. If there are other issues or questions you have, we
will try to assist in resolving these as well. Feel free to post some
code if there are special details you're stuck on.
Thanks again for the feedback!
Regards,
Kent
#include <QtGui>
#include <QtGui>
#include <QtScript>
class Window : public QWidget
{
Q_OBJECT
public:
Window(QWidget *parent = 0);
protected:
void timerEvent(QTimerEvent *event);
private Q_SLOTS:
void runScript();
void abortScript();
private:
QTextEdit *editor;
QTextEdit *logger;
QPushButton *abortButton;
QScriptEngine *engine;
int scriptTimerId;
int elapsed;
};
const int interval = 100;
Window::Window(QWidget *parent)
: QWidget(parent)
{
scriptTimerId = -1;
engine = new QScriptEngine(this);
engine->setProcessEventsInterval(interval);
QVBoxLayout *vbox = new QVBoxLayout(this);
editor = new QTextEdit();
vbox->addWidget(editor);
QHBoxLayout *hbox = new QHBoxLayout();
QPushButton *runButton = new QPushButton("Run");
connect(runButton, SIGNAL(clicked()), this, SLOT(runScript()));
abortButton = new QPushButton("Abort");
abortButton->setEnabled(false);
connect(abortButton, SIGNAL(clicked()), this, SLOT(abortScript()));
hbox->addWidget(runButton);
hbox->addWidget(abortButton);
vbox->addLayout(hbox);
logger = new QTextEdit();
logger->setReadOnly(true);
vbox->addWidget(logger);
}
void Window::runScript()
{
QString code = editor->toPlainText();
scriptTimerId = startTimer(interval);
elapsed = 0;
abortButton->setEnabled(true);
QScriptValue result = engine->evaluate(code);
abortButton->setEnabled(false);
killTimer(scriptTimerId);
scriptTimerId = -1;
logger->moveCursor(QTextCursor::End);
if (result.isError())
logger->insertHtml("<font color=\"red\">" + result.toString() + "</font><br>");
else
logger->insertHtml(result.toString() + "<br>");
}
void Window::abortScript()
{
engine->abortEvaluation(engine->currentContext()->throwError("Aborted"));
}
void Window::timerEvent(QTimerEvent *event)
{
if (event->timerId() == scriptTimerId) {
++elapsed;
if (elapsed*interval == 3500) {
if (QMessageBox::warning(
this, "Script Runner",
"This script is taking a long time to finish. Abort it?",
QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) {
engine->abortEvaluation(engine->currentContext()->throwError("Aborted"));
}
elapsed = 0;
}
}
}
int main(int argc, char **argv)
{
QApplication app(argc, argv);
Window win;
win.show();
return app.exec();
}
#include "main.moc"
Message 3 in thread
Thank you for your in-depth reply, Kent. It really helped clear up
some misunderstandings and give me a much broader view of the system.
> Interesting idea; however, it doesn't correspond with the intention of
> processEventsInterval, so we would have to add it as a new API (e.g. an
> evaluateLater() function that returns a script ID).
The more I think about evaluateLater() the more I think it's probably
exactly what I want. I wanted to avoid using more threads to run
scripts, although now that I think about using a thread-per-script
this may prove to be more beneficial than a hit despite the overhead.
The only problem with it I see is I want to run many small, succinct
scripts that are very focused and in-line, not wanting to have 1000
threads running 1000 small scripts at one time (exaggeration, but
possible).
> QScriptEngine::abortEvaluation() (and QScriptEngine::isEvaluating())
> have just been added. You'll see them in tomorrow's snapshots. I've
> attached some example code here to show how you can combine this with
> setProcessEventsInterval() to abort a long-running script.
Using thread-per-QScriptEngine is still an interesting, and yet
plausible idea to me. However, with evaluateLater(), this would prove
to be maybe even more effective.
What I meant by parsing a script line by line is, to be able to run a
script line by line, instead of running it all at once via evaluate.
Maybe evaluateLater() could deal with this? What I'm really trying to
ask for, I guess, is something in the spirit of evaluateLater() but
will read a script line by line (line by line, I mean functional
section by functional section) instead of running the entire script.
This way I could schedule when I want another section of the script to
be parsed. I could combine this, then with timers or hand make my own
script scheduler. The problem originally arose when I tried to
implement a C++ native 'sleep' function for scripts.
I've gone trawling through some code and see that you have a
'compiler' for scripts, I DO know that you're able to send signals
line by line, and this leads me to believe that it wouldn't be
difficult to add in just such an evaluateLater() function (is this an
unreasonable idea?). This would lift the necessity of running a
script-per-thread, give me the ability to implement an artificial
sleep function for scripts, give me pause-resume functionality without
being per-thread, and make QScriptAgent just that much more useful for
me. Especially in the spirit of debuggers and profilers.
I'd like to know your thoughts and ideas on this, and if you have any
plans for something like this. I know I'm still looking at scripts
with an 'outside view', and not one from an API designers point of
view and would like to thank you for taking the time to read this
ahead of time. I've also well considered your entire response.
Very Respectfully,
Tyler
To unsubscribe - send "unsubscribe" in the subject to qt4-preview-feedback-request@xxxxxxxxxxxxx