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