//// ScriptManager.h// libraries/script-engine/src//// Created by Brad Hefta-Gaub on 12/14/13.// Copyright 2013 High Fidelity, Inc.// Copyright 2020 Vircadia contributors.// Copyright 2022-2023 Overte e.V.//// Distributed under the Apache License, Version 2.0.// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html// SPDX-License-Identifier: Apache-2.0// #ifndef hifi_ScriptManager_h#define hifi_ScriptManager_h #include <atomic>#include <chrono>#include <functional>#include <memory>#include <unordered_map>#include <mutex> #include <QtCore/QFuture>#include <QtCore/QHash>#include <QtCore/QObject>#include <QtCore/QReadWriteLock>#include <QtCore/QSet>#include <QtCore/QSharedPointer>#include <QtCore/QString>#include <QtCore/QTimer>#include <QtCore/QUrl>#include <QtCore/QVariant> #include "EntityItemID.h"#include "EntitiesScriptEngineProvider.h"#include "EntityScriptUtils.h"#include <ExternalResource.h>#include <SettingHandle.h> #include "AssetScriptingInterface.h"#include "ConsoleScriptingInterface.h"#include "Mat4.h"#include "PointerEvent.h"#include "Quat.h"#include "ScriptUUID.h"#include "ScriptValue.h"#include "ScriptException.h"#include "Vec3.h"#include "ScriptEngine.h" static const QString NO_SCRIPT(""); static const int SCRIPT_FPS = 60;static const int DEFAULT_MAX_ENTITY_PPS = 9000;static const int DEFAULT_ENTITY_PPS_PER_SCRIPT = 900; class ScriptEngine;class ScriptEngines;class ScriptManager;class ScriptManagerScriptingInterface; using ScriptEnginePointer = std::shared_ptr<ScriptEngine>;using ScriptManagerPointer = std::shared_ptr<ScriptManager>;using ScriptManagerScriptingInterfacePointer = std::shared_ptr<ScriptManagerScriptingInterface>;using ScriptValueList = QList<ScriptValue>; Q_DECLARE_METATYPE(ScriptManagerPointer) const int QTREGISTER_QTimerStar = qRegisterMetaType<QTimer*>(); /** * @brief Callback data for addEventHandler * */class CallbackData {public: /** * @brief Function to call * */ ScriptValue function; /** * @brief Entity ID * */ EntityItemID definingEntityIdentifier; /** * @brief Sandbox URL for the script * */ QUrl definingSandboxURL;}; /** * @brief DeferredLoadEntity * @deprecated This appars unused * */class DeferredLoadEntity {public: EntityItemID entityID; QString entityScript; //bool forceRedownload;}; /** * @brief Entity with available script contents * */struct EntityScriptContentAvailable { /** * @brief Entity ID * */ EntityItemID entityID; /** * @brief URL to the script, or the actual script if embedded in the URL field * */ QString scriptOrURL; /** * @brief Contents of the script * */ QString contents; /** * @brief Whether scriptOrURL contains an URL * */ bool isURL; /** * @brief Whether the request has been successful * */ bool success; /** * @brief Status as text * */ QString status;}; typedef std::unordered_map<EntityItemID, EntityScriptContentAvailable> EntityScriptContentAvailableMap; typedef QList<CallbackData> CallbackList;typedef QHash<QString, CallbackList> RegisteredEventHandlers; /** * @brief Details about an entity script * */class EntityScriptDetails {public: /** * @brief Current status * */ EntityScriptStatus status { EntityScriptStatus::PENDING }; /** * @brief Error information * * If status indicates an error, this contains a human-readable string giving more information about the error. * */ QString errorInfo { "" }; /** * @brief The source code of the script * */ QString scriptText { "" }; /** * @brief The return value of the script * */ ScriptValue scriptObject{ ScriptValue() }; /** * @brief Last modified time of the underlying script file * * This is used to determine if the script needs reloading when it changes on disk. */ int64_t lastModified { 0 }; /** * @brief URL under which the script is allowed to have access * * The script is allowed access below this URL (eg, sub-directories), but * not to the parent context. */ QUrl definingSandboxURL { QUrl("about:EntityScript") };}; // declare a static script initializers#define STATIC_SCRIPT_TYPES_INITIALIZER(init) \ static ScriptManager::StaticTypesInitializerNode static_script_types_initializer_(init); #define STATIC_SCRIPT_INITIALIZER(init) \ static ScriptManager::StaticInitializerNode static_script_initializer_(init); /** * @brief Manages a single scripting engine * * This class manages and sets up a single scripting engine to make it execute scripts. * * It passes the objects needed to expose the public API, provides console access and error * reporting and event management. * * This manipulates a single underlying instance of ScriptEngine. * * Part of this class' functionality exists only to provide helper functions to the scripts that are * run by the scripting engine, and shouldn't be considered part of the C++ API. Those are the functions * in the "Script support methods", "Module support methods", "Entity Script methods", and "Scripting signals" sections. * * The script-facing interface is in ScriptManagerScriptingInterface and documented in JSDoc * as the <a href="https://apidocs.overte.org/Script.html">Script</a> class. * * The ScriptManager provides the following functionality to scripts: * * * A math library: Quat, Vec3, Mat4 * * UUID generation: Uuid * * Filesystem access: File * * Console access: console, print * * Resource access: Resource, Asset, Resources, ExternalPaths * * Scripting system management: Script * * Module loading: require * * Web access: XMLHttpRequest, WebSocket * * Other miscellaneous functionality. * * Example: * * @code {.cpp} * #include "ScriptManager.h" * * // Scripts only stop running when Script.stop() is called. * // In the normal environment this isn't needed, but for things like unit tests we need * // to use it to make the ScriptManager return from run(). * * QString scriptSource = "print(\"Hello, world!\"); Script.stop(true);"; * QString scriptFilename = "test.js"; * * ScriptManagerPointer sm = newScriptManager(ScriptManager::NETWORKLESS_TEST_SCRIPT, scriptSource, scriptFilename); * connect(sm.get(), &ScriptManager::printedMessage, [](const QString& message, const QString& engineName){ * qCDebug(scriptengine) << "Printed message from engine" << engineName << ": " << message; * }); * * qInfo() << "Running script!"; * sm->run(); * qInfo() << "Done!" * @endcode * * @note * Technically, the ScriptManager isn't generic enough -- it implements things that imitate * Node.js for examine in the module loading code, which makes it JS specific. This code * should probably be moved into the JS ScriptEngine class instead. * * The EntityScript functionality might also benefit from being split off into a separate * class, for better organization. * * Some more functionality can be shifted to ScriptManagerScriptingInterface, since * it only provides services to scripts and isn't called from C++. */class ScriptManager : public QObject, public EntitiesScriptEngineProvider, public std::enable_shared_from_this<ScriptManager> { Q_OBJECTpublic: static const QString SCRIPT_EXCEPTION_FORMAT; static const QString SCRIPT_BACKTRACE_SEP; /** * @brief Context of the script * */ enum Context { /** * @brief Client script. * Allowed to access local HTML files on UI created from C++ calls. * */ CLIENT_SCRIPT, /** * @brief Entity client script * */ ENTITY_CLIENT_SCRIPT, /** * @brief Entity server script * */ ENTITY_SERVER_SCRIPT, /** * @brief Agent script * */ AGENT_SCRIPT, /** * @brief Network-less test system context. * This is used for the QTest self-tests, and minimizes the API that is made available to * the running script. It removes the need for network access, which makes for much faster * test execution. * * * @warning This is a development-targeted bit of functionality. * * @warning This is going to break functionality like loadURL and require */ NETWORKLESS_TEST_SCRIPT }; /** * @brief Type of the script * */ enum Type { /** * @brief Client * */ CLIENT, /** * @brief Entity client * Receives the update event. */ ENTITY_CLIENT, /** * @brief Entity server * Receives the update event * */ ENTITY_SERVER, /** * @brief Agent script * */ AGENT, /** * @brief Avatar script * */ AVATAR, /** * @brief Test system script * * This is used for the QTest self-tests, and minimizes the API that is made available to * the running script. It removes the need for network access, which makes for much faster * test execution. * * @warning This is a development-targeted bit of functionality. */ NETWORKLESS_TEST }; Q_ENUM(Type); static int processLevelMaxRetries;private: // Constructor is private so that only properly generated shared pointer can be used explicit ScriptManager(Context context, const QString& scriptContents = NO_SCRIPT, const QString& fileNameString = QString("about:ScriptEngine"));public: ~ScriptManager() override; // static initialization support typedef void (*ScriptManagerInitializer)(ScriptManager*); class StaticInitializerNode { public: ScriptManagerInitializer init; StaticInitializerNode* prev; inline explicit StaticInitializerNode(ScriptManagerInitializer&& pInit) : init(std::move(pInit)),prev(nullptr) { registerNewStaticInitializer(this); } }; static void registerNewStaticInitializer(StaticInitializerNode* dest); class StaticTypesInitializerNode { public: ScriptManagerInitializer init; StaticTypesInitializerNode* prev; inline explicit StaticTypesInitializerNode(ScriptManagerInitializer&& pInit) : init(std::move(pInit)),prev(nullptr) { registerNewStaticTypesInitializer(this); } }; static void registerNewStaticTypesInitializer(StaticTypesInitializerNode* dest); /** * @brief Run the script in a dedicated thread * * This will have the side effect of evaluating the current script contents and calling run(). * Callers will likely want to register the script with external services before calling this. * * This function will return immediately, and work will continue on the newly created thread. * * @note Can't be called twice. * @note The underlying thread is not accessible. */ void runInThread(); /** * @brief Run the script in the caller's thread, exit when Script.stop() is called. * * Most scripts never stop running, so this function will never return for them. */ void run(); /** * @brief Get the filename of the running script, without the path. * * @return QString Filename */ QString getFilename() const; /** * @brief Get the filename of the running script, with absolute path. * * @return QString Filename */ QString getAbsoluteFilename() const; /** * @brief Underlying scripting engine * * @return ScriptEnginePointer Scripting engine */ inline ScriptEnginePointer engine() { return _engine; } QList<EntityItemID> getListOfEntityScriptIDs(); /** * @brief Whether the ScriptManager is stopped and unable to run scripts * * This is always false for NETWORKLESS_TEST_SCRIPT scripts. * * Otherwise, it checks whether scriptEngines is set and is not stopped. * * @return true * @return false */ bool isStopped() const; /** * @name Script support functions * * These functions exist to support the scripting API */ ///@{ /** * @brief Stops and unloads the current script. * * @note This is part of the public scripting API for Agent scripts and local scripts, but not for EntityScripts * @param marshal Deprecated */ Q_INVOKABLE void stop(bool marshal = false); ///@} /** * @brief Stop any evaluating scripts and wait for the scripting thread to finish. * * @param shutdown True if we are currently shutting down. Setting this to true will allow * processing events emitted during the script's shutdown, such as scripts saving settings. * * @note This function has an internal timeout, and will forcefully abort the script if it * takes too long. */ void waitTillDoneRunning(bool shutdown = false); /** * @brief Removes shared pointer to script engine from the list of all script engines. * * This allows deletion of the script engine once all shared pointer instances are gone. * This function is called for entity script engines when they are being destroyed. * */ void removeFromScriptEngines(); Lines 1–500 of 1743. Load more
// // ScriptManager.h // libraries/script-engine/src // // Created by Brad Hefta-Gaub on 12/14/13. // Copyright 2013 High Fidelity, Inc. // Copyright 2020 Vircadia contributors. // Copyright 2022-2023 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // SPDX-License-Identifier: Apache-2.0 // #ifndef hifi_ScriptManager_h #define hifi_ScriptManager_h #include <atomic> #include <chrono> #include <functional> #include <memory> #include <unordered_map> #include <mutex> #include <QtCore/QFuture> #include <QtCore/QHash> #include <QtCore/QObject> #include <QtCore/QReadWriteLock> #include <QtCore/QSet> #include <QtCore/QSharedPointer> #include <QtCore/QString> #include <QtCore/QTimer> #include <QtCore/QUrl> #include <QtCore/QVariant> #include "EntityItemID.h" #include "EntitiesScriptEngineProvider.h" #include "EntityScriptUtils.h" #include <ExternalResource.h> #include <SettingHandle.h> #include "AssetScriptingInterface.h" #include "ConsoleScriptingInterface.h" #include "Mat4.h" #include "PointerEvent.h" #include "Quat.h" #include "ScriptUUID.h" #include "ScriptValue.h" #include "ScriptException.h" #include "Vec3.h" #include "ScriptEngine.h" static const QString NO_SCRIPT(""); static const int SCRIPT_FPS = 60; static const int DEFAULT_MAX_ENTITY_PPS = 9000; static const int DEFAULT_ENTITY_PPS_PER_SCRIPT = 900; class ScriptEngine; class ScriptEngines; class ScriptManager; class ScriptManagerScriptingInterface; using ScriptEnginePointer = std::shared_ptr<ScriptEngine>; using ScriptManagerPointer = std::shared_ptr<ScriptManager>; using ScriptManagerScriptingInterfacePointer = std::shared_ptr<ScriptManagerScriptingInterface>; using ScriptValueList = QList<ScriptValue>; Q_DECLARE_METATYPE(ScriptManagerPointer) const int QTREGISTER_QTimerStar = qRegisterMetaType<QTimer*>(); /** * @brief Callback data for addEventHandler * */ class CallbackData { public: /** * @brief Function to call * */ ScriptValue function; /** * @brief Entity ID * */ EntityItemID definingEntityIdentifier; /** * @brief Sandbox URL for the script * */ QUrl definingSandboxURL; }; /** * @brief DeferredLoadEntity * @deprecated This appars unused * */ class DeferredLoadEntity { public: EntityItemID entityID; QString entityScript; //bool forceRedownload; }; /** * @brief Entity with available script contents * */ struct EntityScriptContentAvailable { /** * @brief Entity ID * */ EntityItemID entityID; /** * @brief URL to the script, or the actual script if embedded in the URL field * */ QString scriptOrURL; /** * @brief Contents of the script * */ QString contents; /** * @brief Whether scriptOrURL contains an URL * */ bool isURL; /** * @brief Whether the request has been successful * */ bool success; /** * @brief Status as text * */ QString status; }; typedef std::unordered_map<EntityItemID, EntityScriptContentAvailable> EntityScriptContentAvailableMap; typedef QList<CallbackData> CallbackList; typedef QHash<QString, CallbackList> RegisteredEventHandlers; /** * @brief Details about an entity script * */ class EntityScriptDetails { public: /** * @brief Current status * */ EntityScriptStatus status { EntityScriptStatus::PENDING }; /** * @brief Error information * * If status indicates an error, this contains a human-readable string giving more information about the error. * */ QString errorInfo { "" }; /** * @brief The source code of the script * */ QString scriptText { "" }; /** * @brief The return value of the script * */ ScriptValue scriptObject{ ScriptValue() }; /** * @brief Last modified time of the underlying script file * * This is used to determine if the script needs reloading when it changes on disk. */ int64_t lastModified { 0 }; /** * @brief URL under which the script is allowed to have access * * The script is allowed access below this URL (eg, sub-directories), but * not to the parent context. */ QUrl definingSandboxURL { QUrl("about:EntityScript") }; }; // declare a static script initializers #define STATIC_SCRIPT_TYPES_INITIALIZER(init) \ static ScriptManager::StaticTypesInitializerNode static_script_types_initializer_(init); #define STATIC_SCRIPT_INITIALIZER(init) \ static ScriptManager::StaticInitializerNode static_script_initializer_(init); /** * @brief Manages a single scripting engine * * This class manages and sets up a single scripting engine to make it execute scripts. * * It passes the objects needed to expose the public API, provides console access and error * reporting and event management. * * This manipulates a single underlying instance of ScriptEngine. * * Part of this class' functionality exists only to provide helper functions to the scripts that are * run by the scripting engine, and shouldn't be considered part of the C++ API. Those are the functions * in the "Script support methods", "Module support methods", "Entity Script methods", and "Scripting signals" sections. * * The script-facing interface is in ScriptManagerScriptingInterface and documented in JSDoc * as the <a href="https://apidocs.overte.org/Script.html">Script</a> class. * * The ScriptManager provides the following functionality to scripts: * * * A math library: Quat, Vec3, Mat4 * * UUID generation: Uuid * * Filesystem access: File * * Console access: console, print * * Resource access: Resource, Asset, Resources, ExternalPaths * * Scripting system management: Script * * Module loading: require * * Web access: XMLHttpRequest, WebSocket * * Other miscellaneous functionality. * * Example: * * @code {.cpp} * #include "ScriptManager.h" * * // Scripts only stop running when Script.stop() is called. * // In the normal environment this isn't needed, but for things like unit tests we need * // to use it to make the ScriptManager return from run(). * * QString scriptSource = "print(\"Hello, world!\"); Script.stop(true);"; * QString scriptFilename = "test.js"; * * ScriptManagerPointer sm = newScriptManager(ScriptManager::NETWORKLESS_TEST_SCRIPT, scriptSource, scriptFilename); * connect(sm.get(), &ScriptManager::printedMessage, [](const QString& message, const QString& engineName){ * qCDebug(scriptengine) << "Printed message from engine" << engineName << ": " << message; * }); * * qInfo() << "Running script!"; * sm->run(); * qInfo() << "Done!" * @endcode * * @note * Technically, the ScriptManager isn't generic enough -- it implements things that imitate * Node.js for examine in the module loading code, which makes it JS specific. This code * should probably be moved into the JS ScriptEngine class instead. * * The EntityScript functionality might also benefit from being split off into a separate * class, for better organization. * * Some more functionality can be shifted to ScriptManagerScriptingInterface, since * it only provides services to scripts and isn't called from C++. */ class ScriptManager : public QObject, public EntitiesScriptEngineProvider, public std::enable_shared_from_this<ScriptManager> { Q_OBJECT public: static const QString SCRIPT_EXCEPTION_FORMAT; static const QString SCRIPT_BACKTRACE_SEP; /** * @brief Context of the script * */ enum Context { /** * @brief Client script. * Allowed to access local HTML files on UI created from C++ calls. * */ CLIENT_SCRIPT, /** * @brief Entity client script * */ ENTITY_CLIENT_SCRIPT, /** * @brief Entity server script * */ ENTITY_SERVER_SCRIPT, /** * @brief Agent script * */ AGENT_SCRIPT, /** * @brief Network-less test system context. * This is used for the QTest self-tests, and minimizes the API that is made available to * the running script. It removes the need for network access, which makes for much faster * test execution. * * * @warning This is a development-targeted bit of functionality. * * @warning This is going to break functionality like loadURL and require */ NETWORKLESS_TEST_SCRIPT }; /** * @brief Type of the script * */ enum Type { /** * @brief Client * */ CLIENT, /** * @brief Entity client * Receives the update event. */ ENTITY_CLIENT, /** * @brief Entity server * Receives the update event * */ ENTITY_SERVER, /** * @brief Agent script * */ AGENT, /** * @brief Avatar script * */ AVATAR, /** * @brief Test system script * * This is used for the QTest self-tests, and minimizes the API that is made available to * the running script. It removes the need for network access, which makes for much faster * test execution. * * @warning This is a development-targeted bit of functionality. */ NETWORKLESS_TEST }; Q_ENUM(Type); static int processLevelMaxRetries; private: // Constructor is private so that only properly generated shared pointer can be used explicit ScriptManager(Context context, const QString& scriptContents = NO_SCRIPT, const QString& fileNameString = QString("about:ScriptEngine")); public: ~ScriptManager() override; // static initialization support typedef void (*ScriptManagerInitializer)(ScriptManager*); class StaticInitializerNode { public: ScriptManagerInitializer init; StaticInitializerNode* prev; inline explicit StaticInitializerNode(ScriptManagerInitializer&& pInit) : init(std::move(pInit)),prev(nullptr) { registerNewStaticInitializer(this); } }; static void registerNewStaticInitializer(StaticInitializerNode* dest); class StaticTypesInitializerNode { public: ScriptManagerInitializer init; StaticTypesInitializerNode* prev; inline explicit StaticTypesInitializerNode(ScriptManagerInitializer&& pInit) : init(std::move(pInit)),prev(nullptr) { registerNewStaticTypesInitializer(this); } }; static void registerNewStaticTypesInitializer(StaticTypesInitializerNode* dest); /** * @brief Run the script in a dedicated thread * * This will have the side effect of evaluating the current script contents and calling run(). * Callers will likely want to register the script with external services before calling this. * * This function will return immediately, and work will continue on the newly created thread. * * @note Can't be called twice. * @note The underlying thread is not accessible. */ void runInThread(); /** * @brief Run the script in the caller's thread, exit when Script.stop() is called. * * Most scripts never stop running, so this function will never return for them. */ void run(); /** * @brief Get the filename of the running script, without the path. * * @return QString Filename */ QString getFilename() const; /** * @brief Get the filename of the running script, with absolute path. * * @return QString Filename */ QString getAbsoluteFilename() const; /** * @brief Underlying scripting engine * * @return ScriptEnginePointer Scripting engine */ inline ScriptEnginePointer engine() { return _engine; } QList<EntityItemID> getListOfEntityScriptIDs(); /** * @brief Whether the ScriptManager is stopped and unable to run scripts * * This is always false for NETWORKLESS_TEST_SCRIPT scripts. * * Otherwise, it checks whether scriptEngines is set and is not stopped. * * @return true * @return false */ bool isStopped() const; /** * @name Script support functions * * These functions exist to support the scripting API */ ///@{ /** * @brief Stops and unloads the current script. * * @note This is part of the public scripting API for Agent scripts and local scripts, but not for EntityScripts * @param marshal Deprecated */ Q_INVOKABLE void stop(bool marshal = false); ///@} /** * @brief Stop any evaluating scripts and wait for the scripting thread to finish. * * @param shutdown True if we are currently shutting down. Setting this to true will allow * processing events emitted during the script's shutdown, such as scripts saving settings. * * @note This function has an internal timeout, and will forcefully abort the script if it * takes too long. */ void waitTillDoneRunning(bool shutdown = false); /** * @brief Removes shared pointer to script engine from the list of all script engines. * * This allows deletion of the script engine once all shared pointer instances are gone. * This function is called for entity script engines when they are being destroyed. * */ void removeFromScriptEngines();