* @brief Builds the engine, evals the SDK, and grabs setup()/loop(). Only __ss_bridge goes * in: the direct helper bridges wrap main-thread singletons and must never execute on * this worker thread (__ss_control tells the prelude to marshal instead). User source * is evaluated under the armed watchdog so a top-level loop cannot wedge the worker. */
| 476 | * is evaluated under the armed watchdog so a top-level loop cannot wedge the worker. |
| 477 | */ |
| 478 | bool DataModel::ControlScriptWorker::compile(const QString& source, QString& errorOut) |
| 479 | { |
| 480 | m_engine = std::make_unique<QJSEngine>(); |
| 481 | m_engine->installExtensions(QJSEngine::ConsoleExtension | QJSEngine::GarbageCollectionExtension); |
| 482 | m_watchdog = std::make_unique<DataModel::JsWatchdog>( |
| 483 | m_engine.get(), kRuntimeWatchdogMs, QStringLiteral("Control script")); |
| 484 | |
| 485 | m_bridge = new ControlApiBridge(m_marshaller, m_engine.get()); |
| 486 | m_bridge->setWatchdog(m_watchdog.get()); |
| 487 | auto bridgeVal = m_engine->newQObject(m_bridge); |
| 488 | m_engine->globalObject().setProperty(QStringLiteral("__ss_bridge"), bridgeVal); |
| 489 | m_engine->globalObject().setProperty(QStringLiteral("__ss_control"), QJSValue(true)); |
| 490 | |
| 491 | QFile sdkFile(QStringLiteral(":/api/SerialStudio.js")); |
| 492 | if (sdkFile.open(QFile::ReadOnly)) |
| 493 | m_engine->evaluate(QString::fromUtf8(sdkFile.readAll())); |
| 494 | |
| 495 | m_watchdog->arm(); |
| 496 | const auto result = m_engine->evaluate(source, QStringLiteral("control-script.js")); |
| 497 | m_watchdog->disarm(); |
| 498 | |
| 499 | if (m_engine->isInterrupted()) { |
| 500 | m_engine->setInterrupted(false); |
| 501 | errorOut = QStringLiteral("Script did not finish evaluating within %1 ms and was " |
| 502 | "interrupted (infinite loop at the top level?).") |
| 503 | .arg(kRuntimeWatchdogMs); |
| 504 | releaseEngine(); |
| 505 | return false; |
| 506 | } |
| 507 | |
| 508 | if (result.isError()) { |
| 509 | errorOut = QStringLiteral("Line %1: %2") |
| 510 | .arg(result.property(QStringLiteral("lineNumber")).toInt()) |
| 511 | .arg(result.toString()); |
| 512 | releaseEngine(); |
| 513 | return false; |
| 514 | } |
| 515 | |
| 516 | m_setupFn = m_engine->globalObject().property(QStringLiteral("setup")); |
| 517 | m_loopFn = m_engine->globalObject().property(QStringLiteral("loop")); |
| 518 | |
| 519 | if (!m_setupFn.isCallable() && !m_loopFn.isCallable()) { |
| 520 | errorOut = QStringLiteral("Script must define setup() and/or loop()."); |
| 521 | releaseEngine(); |
| 522 | return false; |
| 523 | } |
| 524 | |
| 525 | m_loaded = true; |
| 526 | return true; |
| 527 | } |