1743 lines · 53.5 KB · C
RawDownload
////  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.