#include "toonz/scriptengine.h" #include "toonz/scriptbinding.h" #include "toonz/scriptbinding_files.h" #include "trenderer.h" #include "toonz/toonzfolders.h" #include #include #include #include #include #include #include #include #include //========================================================= namespace { void sleep(unsigned long msec) { QMutex mutex; mutex.lock(); QWaitCondition waitCondition; waitCondition.wait(&mutex, msec); mutex.unlock(); } QString print(QScriptValue arg, bool addQuotes) { if (arg.isArray()) { QString s = "["; quint32 len = arg.property(QLatin1String("length")).toUInt32(); for (quint32 i = 0; i < len; ++i) { QScriptValue item = arg.property(i); if (i > 0) s += ","; s += print(item, addQuotes); } s += "]"; return s; } else if (arg.isBool()) { return arg.toBool() ? "true" : "false"; } else if (arg.isString()) { if (addQuotes) return "\"" + arg.toString() + "\""; else return arg.toString(); } else return arg.toString(); } QScriptValue printFunction(QScriptContext *context, QScriptEngine *engine) { QString result; for (int i = 0; i < context->argumentCount(); ++i) { if (i > 0) result.append(" "); result.append(print(context->argument(i), false)); } QScriptValue calleeData = context->callee().data(); ScriptEngine *se = qobject_cast(calleeData.toQObject()); se->emitOutput(ScriptEngine::SimpleText, result); sleep(50); return se->voidValue(); } QScriptValue warningFunction(QScriptContext *context, QScriptEngine *engine) { QString result; for (int i = 0; i < context->argumentCount(); ++i) { if (i > 0) result.append(" "); result.append(print(context->argument(i), false)); } QScriptValue calleeData = context->callee().data(); ScriptEngine *se = qobject_cast(calleeData.toQObject()); se->emitOutput(ScriptEngine::Warning, result); sleep(50); return se->voidValue(); } QScriptValue runFunction(QScriptContext *context, QScriptEngine *engine) { if (context->argumentCount() != 1) { return context->throwError("expected one parameter"); } TFilePath fp; QScriptValue err = TScriptBinding::checkFilePath(context, context->argument(0), fp); if (err.isError()) return err; if (!fp.isAbsolute()) { fp = ToonzFolder::getLibraryFolder() + "scripts" + fp; } QString fpStr = QString::fromStdWString(fp.getWideString()); QFile file(fpStr); if (!file.open(QIODevice::ReadOnly)) { return context->throwError("can't read file " + fpStr); } QTextStream in(&file); QString content = in.readAll(); file.close(); QScriptProgram program(content, fpStr); QScriptContext *parent = context->parentContext(); if (parent != 0) { context->setActivationObject(context->parentContext()->activationObject()); context->setThisObject(context->parentContext()->thisObject()); } QScriptValue ret = engine->evaluate(program); if (engine->hasUncaughtException()) { int line = engine->uncaughtExceptionLineNumber(); return context->throwError(QString("%1, at line %2 of %3") .arg(ret.toString()) .arg(line) .arg(fpStr)); } return ret; } /* QScriptValue glub(QScriptContext *context, QScriptEngine *engine) { QScriptValue global = engine->globalObject(); QScriptValue te = global.property("_engine"); ScriptEngine *se = qscriptvalue_cast(te); se->postCommand(context->argument(0)); return 0; } */ } // namespace //========================================================= class ScriptEngine::Executor final : public QThread { ScriptEngine *m_engine; QString m_cmd; public: Executor(ScriptEngine *engine, const QString &cmd) : m_engine(engine), m_cmd(cmd) {} void run() override { m_engine->m_engine->collectGarbage(); QScriptValue result = m_engine->m_engine->evaluate(m_cmd); if (result.isError()) { m_engine->emitOutput(ScriptEngine::SyntaxError, result.toString()); } else if (result.isUndefined()) { m_engine->emitOutput(ScriptEngine::UndefinedEvaluationResult, "undefined"); } else { if (qscriptvalue_cast(result)) { } else { m_engine->emitOutput(ScriptEngine::EvaluationResult, print(result, true)); } } } }; class ScriptEngine::MainThreadEvaluationData { public: QMutex m_mutex; QWaitCondition m_cond; QScriptValue m_fun, m_args, m_result; }; //========================================================= inline void defineFunction(ScriptEngine *se, const QString &name, QScriptEngine::FunctionSignature f) { QScriptEngine *engine = se->getQScriptEngine(); QScriptValue fObj = engine->newFunction(f); fObj.setData(engine->newQObject(se)); engine->globalObject().setProperty(name, fObj); } //========================================================= ScriptEngine::ScriptEngine() : m_executor(0), m_engine(new QScriptEngine()) { // I must call TRenderer::initialize(), because a script could cause a // rendering driven by a working thread TRenderer::initialize(); m_mainThreadEvaluationData = new MainThreadEvaluationData(); QScriptValue global = m_engine->globalObject(); QScriptValue ctor; QScriptEngine &engine = *m_engine; defineFunction(this, "print", printFunction); defineFunction(this, "warning", warningFunction); defineFunction(this, "run", runFunction); /* QScriptValue print = engine.newFunction(printFunction); print.setData(engine.newQObject(this)); engine.globalObject().setProperty("print", print); QScriptValue print = engine.newFunction(printFunction); print.setData(engine.newQObject(this)); engine.globalObject().setProperty("print", print); QScriptValue run = engine.newFunction(runFunction); run.setData(engine.newQObject(this)); engine.globalObject().setProperty("run", run); */ // QScriptValue g = engine.newFunction(glub); // g.setData(engine.newQObject(this)); // engine.globalObject().setProperty("glub", g); // engine.globalObject().setProperty("_engine", engine.newQObject(this)); m_voidValue = new QScriptValue(); *m_voidValue = engine.newQObject(new TScriptBinding::Void(), QScriptEngine::AutoOwnership); engine.globalObject().setProperty("void", *m_voidValue); TScriptBinding::bindAll(engine); bool ret = connect(this, SIGNAL(mainThreadEvaluationPosted()), this, SLOT(onMainThreadEvaluationPosted())); assert(ret); } ScriptEngine::~ScriptEngine() { delete m_mainThreadEvaluationData; delete m_voidValue; } const QScriptValue &ScriptEngine::evaluateOnMainThread( const QScriptValue &fun, const QScriptValue &arguments) { MainThreadEvaluationData *d = m_mainThreadEvaluationData; QMutexLocker locker(&d->m_mutex); d->m_fun = fun; d->m_args = arguments; d->m_result = QScriptValue(); emit mainThreadEvaluationPosted(); d->m_cond.wait(&d->m_mutex); return d->m_result; } void ScriptEngine::onMainThreadEvaluationPosted() { Q_ASSERT(qApp && qApp->thread() == QThread::currentThread()); MainThreadEvaluationData *d = m_mainThreadEvaluationData; QMutexLocker locker(&d->m_mutex); d->m_result = d->m_fun.call(d->m_fun, d->m_args); d->m_cond.wakeOne(); } void ScriptEngine::evaluate(const QString &cmd) { if (m_executor) return; m_executor = new Executor(this, cmd); connect(m_executor, SIGNAL(finished()), this, SLOT(onTerminated())); m_executor->start(); } bool ScriptEngine::wait(unsigned long time) { return !m_executor || m_executor->wait(time); } bool ScriptEngine::isEvaluating() const { return m_engine->isEvaluating(); } void ScriptEngine::interrupt() { m_engine->abortEvaluation(); } void ScriptEngine::onTerminated() { emit evaluationDone(); delete m_executor; m_executor = 0; }