441 lines · 19.7 KB · C
RawDownload
////  Created by Bradley Austin Davis on 2016/01/08//  Copyright 2013-2016 High Fidelity, Inc.//  Copyright 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// /// @addtogroup ScriptEngine/// @{ #ifndef hifi_ScriptEngines_h#define hifi_ScriptEngines_h #include <functional>#include <atomic>#include <memory> #include <QtCore/QObject>#include <QtCore/QMutex>#include <QtCore/QReadWriteLock>#include <QtCore/QUrl> #include <SettingHandle.h>#include <DependencyManager.h>#include <shared/ScriptInitializerMixin.h>#include "ScriptManager.h" #include "ScriptsModel.h"#include "ScriptsModelFilter.h"#include "ScriptGatekeeper.h" class ScriptEngine; /*@jsdoc * The <code>ScriptDiscoveryService</code> API provides facilities to work with Interface scripts. * * @namespace ScriptDiscoveryService * * @hifi-interface * @hifi-avatar * @hifi-client-entity * * @property {string} debugScriptUrl="" - The path and name of a script to debug using the "API Debugger" developer tool *     (currentAPI.js). If set, the API Debugger dialog displays the objects and values exposed by the script using *     {@link Script.registerValue} and similar. * @property {string} defaultScriptsPath - The path where the default scripts are located in the Interface installation. *     <em>Read-only.</em> * @property {ScriptsModel} scriptsModel - Information on the scripts that are in the default scripts directory of the *     Interface installation. *     <em>Read-only.</em> * @property {ScriptsModelFilter} scriptsModelFilter - Sorted and filtered information on the scripts that are in the default *     scripts directory of the Interface installation. *     <em>Read-only.</em> *//// Provides the <code><a href="https://apidocs.overte.org/ScriptDiscoveryService.html">ScriptDiscoveryService</a></code> scripting interfaceclass ScriptEngines : public QObject, public Dependency, public ScriptInitializerMixin<ScriptManagerPointer> {    Q_OBJECT     Q_PROPERTY(ScriptsModel* scriptsModel READ scriptsModel CONSTANT)    Q_PROPERTY(ScriptsModelFilter* scriptsModelFilter READ scriptsModelFilter CONSTANT)    Q_PROPERTY(QString debugScriptUrl READ getDebugScriptUrl WRITE setDebugScriptUrl) public:    ScriptEngines(ScriptManager::Context context, const QUrl& defaultScriptsOverride = QUrl());    int runScriptInitializers(ScriptManagerPointer manager) override;     void loadScripts();    void saveScripts();     QString getDebugScriptUrl() { return _debugScriptUrl; };    void setDebugScriptUrl(const QString& url) { _debugScriptUrl = url; };     void loadDefaultScripts();    void reloadLocalFiles();     QStringList getRunningScripts();    ScriptManagerPointer getScriptEngine(const QUrl& scriptHash);     ScriptsModel* scriptsModel() { return &_scriptsModel; };    ScriptsModelFilter* scriptsModelFilter() { return &_scriptsModelFilter; };     QString getDefaultScriptsLocation() const;     /*@jsdoc     * Starts running an Interface script, if it isn't already running. The script is automatically loaded next time Interface     * starts.     * <p>This is a synonym for calling {@link ScriptDiscoveryService.loadScript|loadScript} with just the script URL.</p>     * <p class="availableIn"><strong>Supported Script Types:</strong> Interface Scripts &bull; Avatar Scripts</p>     * <p>See also, {@link Script.load}.</p>     * @function ScriptDiscoveryService.loadOneScript     * @param {string} url - The path and name of the script. If a local file, including the <code>"file:///"</code> scheme is     *     optional.     */    Q_INVOKABLE void loadOneScript(const QString& scriptFilename);     /*@jsdoc     * Starts running an Interface script, if it isn't already running.     * <p class="availableIn"><strong>Supported Script Types:</strong> Interface Scripts &bull; Avatar Scripts</p>     * <p>See also, {@link Script.load}.</p>     * @function ScriptDiscoveryService.loadScript     * @param {string} [url=""] - The path and name of the script. If a local file, including the <code>"file:///"</code>     *     scheme is optional.     * @param {boolean} [isUserLoaded=true] - <code>true</code> if the user specifically loaded it, <code>false</code> if not     *     (e.g., a script loaded it). If <code>false</code>, the script is not automatically loaded next time Interface starts.     * @param {boolean} [loadScriptFromEditor=false] - <em>Not used.</em>     * @param {boolean} [activateMainWindow=false] - <em>Not used.</em>     * @param {boolean} [reload=false] - <code>true</code> to redownload the script, <code>false</code> to use the copy from     *     the cache if available.     * @param {boolean} [quitWhenFinished=false] - <code>true</code> to close Interface when the script finishes,     *     <code>false</code> to not close Interface.     * @returns {object} An empty object, <code>{}</code>.     */    Q_INVOKABLE ScriptManagerPointer loadScript(const QUrl& scriptFilename = QString(),        bool isUserLoaded = true, bool loadScriptFromEditor = false, bool activateMainWindow = false, bool reload = false, bool quitWhenFinished = false);     /*@jsdoc     * Stops or restarts an Interface script.     * @function ScriptDiscoveryService.stopScript     * @param {string} url - The path and name of the script. If a local file, including the <code>"file:///"</code> scheme is     *     optional.     * @param {boolean} [restart=false] -  <code>true</code> to redownload and restart the script, <code>false</code> to stop     *     it.     * @returns {boolean} <code>true</code> if the script was successfully stopped or restarted, <code>false</code> if it     *     wasn't (e.g., the script couldn't be found).     */    Q_INVOKABLE bool stopScript(const QString& scriptHash, bool restart = false);      /*@jsdoc     * Restarts all Interface, avatar, and client entity scripts after clearing the scripts cache.     * @function ScriptDiscoveryService.reloadAllScripts     */    Q_INVOKABLE void reloadAllScripts();     /*@jsdoc     * Stops or restarts all Interface scripts. The scripts cache is not cleared. If restarting, avatar and client entity     * scripts are also restarted.     * @function ScriptDiscoveryService.stopAllScripts     * @param {boolean} [restart=false] - <code>true</code> to restart the scripts, <code>false</code> to stop them.     */    Q_INVOKABLE void stopAllScripts(bool restart = false);      /*@jsdoc     * Gets a list of all Interface scripts that are currently running.     * @function ScriptDiscoveryService.getRunning     * @returns {ScriptDiscoveryService.RunningScript[]} All Interface scripts that are currently running.     * @example <caption>Report all running scripts.</caption>     * var runningScripts = ScriptDiscoveryService.getRunning();     * print("Running scripts:");     * for (var i = 0; i < runningScripts.length; i++) {     *     print(JSON.stringify(runningScripts[i]));     * }     */    Q_INVOKABLE QVariantList getRunning();     /*@jsdoc     * Gets a list of all script files that are in the default scripts directory of the Interface installation.     * @function ScriptDiscoveryService.getPublic     * @returns {ScriptDiscoveryService.PublicScript[]} All scripts in the "scripts" directory of the Interface     *     installation.     */    Q_INVOKABLE QVariantList getPublic();     /*@jsdoc     * @function ScriptDiscoveryService.getLocal     * @returns {ScriptDiscoveryService.LocalScript[]} Local scripts.     * @deprecated This function is deprecated and will be removed.     */    // Deprecated because there is no longer a notion of a "local" scripts folder where you would put your personal scripts.    Q_INVOKABLE QVariantList getLocal();     // FIXME: Move to other Q_PROPERTY declarations.    Q_PROPERTY(QString defaultScriptsPath READ getDefaultScriptsLocation)     void defaultScriptsLocationOverridden(bool overridden) { _defaultScriptsLocationOverridden = overridden; };     // Called at shutdown time    void shutdownScripting();    bool isStopped() const { return _isStopped; }     void addScriptEngine(ScriptManagerPointer);     void removeScriptEngine(ScriptManagerPointer);     // Called by ScriptManagerScriptingInterface    void requestServerEntityScriptMessages(ScriptManager *manager);    void requestServerEntityScriptMessages(ScriptManager *manager, const QUuid& entityID);     void removeServerEntityScriptMessagesRequest(ScriptManager *manager);    void removeServerEntityScriptMessagesRequest(ScriptManager *manager, const QUuid& entityID);     ScriptGatekeeper scriptGatekeeper; signals:     /*@jsdoc     * Triggered when the number of Interface scripts running changes.     * @function ScriptDiscoveryService.scriptCountChanged     * @returns {Signal}     * @example <caption>Report when the number of running scripts changes.</caption>     * ScriptDiscoveryService.scriptCountChanged.connect(function () {     *     print("Scripts count changed: " + ScriptDiscoveryService.getRunning().length);     * });     */    void scriptCountChanged();     /*@jsdoc     * Triggered when Interface, avatar, and client entity scripts are restarting as a result of     * {@link ScriptDiscoveryService.reloadAllScripts|reloadAllScripts} or     * {@link ScriptDiscoveryService.stopAllScripts|stopAllScripts}.     * @function ScriptDiscoveryService.scriptsReloading     * @returns {Signal}     */    void scriptsReloading();     /*@jsdoc     * Triggered when a script could not be loaded.     * @function ScriptDiscoveryService.scriptLoadError     * @param {string} url - The path and name of the script that could not be loaded.     * @param {string} error - <code>""</code> always.     * @returns {Signal}     */    void scriptLoadError(const QString& filename, const QString& error);     /*@jsdoc     * Triggered when any script prints a message to the program log via {@link  print}, {@link Script.print},     * {@link console.log}, or {@link console.debug}.     * @function ScriptDiscoveryService.printedMessage     * @param {string} message - The message.     * @param {string} scriptName - The name of the script that generated the message.     * @returns {Signal}     */    void printedMessage(const QString& message, const QString& engineName);     /*@jsdoc     * Triggered when any script generates an error or {@link console.error} is called.     * @function ScriptDiscoveryService.errorMessage     * @param {string} message - The error message.     * @param {string} scriptName - The name of the script that generated the error message.     * @returns {Signal}     */    void errorMessage(const QString& message, const QString& engineName);     /*@jsdoc     * Triggered when any script generates a warning or {@link console.warn} is called.     * @function ScriptDiscoveryService.warningMessage     * @param {string} message - The warning message.     * @param {string} scriptName - The name of the script that generated the warning message.     * @returns {Signal}     */    void warningMessage(const QString& message, const QString& engineName);     /*@jsdoc     * Triggered when any script generates an information message or {@link console.info} is called.     * @function ScriptDiscoveryService.infoMessage     * @param {string} message - The information message.     * @param {string} scriptName - The name of the script that generated the information message.     * @returns {Signal}     */    void infoMessage(const QString& message, const QString& engineName);     /*@jsdoc     * Triggered when a client side entity script prints a message to the program log via {@link  print}, {@link Script.print},     * {@link console.log}, {@link console.debug}, {@link console.group}, {@link console.groupEnd}, {@link console.time}, or     * {@link console.timeEnd}.     * @function Script.printedEntityMessage     * @param {string} message - The message.     * @param {string} fileName - Name of the file in which message was generated. Empty string when no file name is available.     * @param {number} lineNumber - Number of the line on which message was generated. -1 if there line number is not available.     * @param {Uuid} entityID - Entity ID.     * @param {boolean} isServerScript - true if entity script is server-side, false if it is client-side.     * @returns {Signal}     */    void printedEntityMessage(const QString& message, const QString& fileName, int lineNumber, const EntityItemID& entityID, bool isServerScript);     /*@jsdoc     * Triggered when a client side entity script generates an error, {@link console.error} or {@link console.exception} is called, or     * {@link console.assert} is called and fails.     * @function Script.errorEntityMessage     * @param {string} message - The error message.     * @param {string} fileName - Name of the file in which message was generated. Empty string when no file name is available.     * @param {number} lineNumber - Number of the line on which message was generated. -1 if there line number is not available.     * @param {Uuid} entityID - Entity ID.     * @param {boolean} isServerScript - true if entity script is server-side, false if it is client-side.     * @returns {Signal}     */    void errorEntityMessage(const QString& message, const QString& fileName, int lineNumber, const EntityItemID& entityID, bool isServerScript);     /*@jsdoc     * Triggered when a client side entity script generates a warning or {@link console.warn} is called.     * @function Script.warningEntityMessage     * @param {string} message - The warning message.     * @param {string} fileName - Name of the file in which message was generated. Empty string when no file name is available.     * @param {number} lineNumber - Number of the line on which message was generated. -1 if there line number is not available.     * @param {Uuid} entityID - Entity ID.     * @param {boolean} isServerScript - true if entity script is server-side, false if it is client-side.     * @returns {Signal}     */    void warningEntityMessage(const QString& message, const QString& fileName, int lineNumber, const EntityItemID& entityID, bool isServerScript);     /*@jsdoc     * Triggered when a client side entity script generates an information message or {@link console.info} is called.     * @function Script.infoEntityMessage     * @param {string} message - The information message.     * @param {string} fileName - Name of the file in which message was generated. Empty string when no file name is available.     * @param {number} lineNumber - Number of the line on which message was generated. -1 if there line number is not available.     * @param {Uuid} entityID - Entity ID.     * @param {boolean} isServerScript - true if entity script is server-side, false if it is client-side.     * @returns {Signal}     */    void infoEntityMessage(const QString& message, const QString& fileName, int lineNumber, const EntityItemID& entityID, bool isServerScript);     /*@jsdoc     * @function ScriptDiscoveryService.errorLoadingScript     * @param {string} url - URL.     * @returns {Signal}     * @deprecated This signal is deprecated and will be removed.     */    // Deprecated because never emitted.    void errorLoadingScript(const QString& url);     /*@jsdoc     * Triggered when the Debug Window is cleared.     * @function ScriptDiscoveryService.clearDebugWindow     * @returns {Signal}     */    void clearDebugWindow();     /**     * @brief Fires when script engines need entity server script messages (areMessagesRequested == true)     * and when messages are not needed anymore (areMessagesRequested == false).     */    void requestingEntityScriptServerLog(bool areMessagesRequested); public slots:     /*@jsdoc     * @function ScriptDiscoveryService.onPrintedMessage     * @param {string} message - Message.     * @param {string} scriptName - Script name.     * @deprecated This function is deprecated and will be removed.     */    // Deprecated because only use is to emit a signal.    void onPrintedMessage(const QString& message, const QString& scriptName);     /*@jsdoc     * @function ScriptDiscoveryService.onErrorMessage     * @param {string} message - Message.     * @param {string} scriptName - Script name.     * @deprecated This function is deprecated and will be removed.     */    // Deprecated because only use is to emit a signal.    void onErrorMessage(const QString& message, const QString& scriptName);     /*@jsdoc     * @function ScriptDiscoveryService.onWarningMessage     * @param {string} message - Message.     * @param {string} scriptName - Script name.     * @deprecated This function is deprecated and will be removed.     */    // Deprecated because only use is to emit a signal.    void onWarningMessage(const QString& message, const QString& scriptName);     /*@jsdoc     * @function ScriptDiscoveryService.onInfoMessage     * @param {string} message - Message.     * @param {string} scriptName - Script name.     * @deprecated This function is deprecated and will be removed.     */    // Deprecated because only use is to emit a signal.    void onInfoMessage(const QString& message, const QString& scriptName);     /*@jsdoc     * @function ScriptDiscoveryService.onErrorLoadingScript     * @param {string} url - URL.     * @deprecated This function is deprecated and will be removed.     */    // Deprecated because only use is to emit a signal. And it isn't used.    void onErrorLoadingScript(const QString& url);     /*@jsdoc     * @function ScriptDiscoveryService.onClearDebugWindow     * @deprecated This function is deprecated and will be removed.     */    // Deprecated because only use is to emit a signal.    void onClearDebugWindow(); protected slots:     /*@jsdoc     * @function ScriptDiscoveryService.onScriptFinished     * @param {string} scriptName - Script name.     * @param {object} manager - Script manager.     * @deprecated This function is deprecated and will be removed.     */    // Deprecated because it wasn't intended to be in the API.    void onScriptFinished(const QString& fileNameString, ScriptManagerPointer manager); protected:    ScriptManagerPointer reloadScript(const QString& scriptName, bool isUserLoaded = true) { return loadScript(scriptName, isUserLoaded, false, false, true); }    void onScriptEngineLoaded(const QString& scriptFilename);    void quitWhenFinished();    void onScriptEngineError(const QString& scriptFilename);    void launchScriptEngine(ScriptManagerPointer);     ScriptManager::Context _context;    QReadWriteLock _scriptManagersHashLock;    QMultiHash<QUrl, ScriptManagerPointer> _scriptManagersHash;    QSet<ScriptManagerPointer> _allKnownScriptManagers;    QMutex _allScriptsMutex;    ScriptsModel _scriptsModel;    ScriptsModelFilter _scriptsModelFilter;    std::atomic<bool> _isStopped { false };    std::atomic<bool> _isReloading { false };    bool _defaultScriptsLocationOverridden { false };    QString _debugScriptUrl;     // For subscriptions to server entity script messages    std::mutex _subscriptionsToEntityScriptMessagesMutex;    QSet<ScriptManager*> _managersSubscribedToEntityScriptMessages;    // Since multiple entity scripts run in the same script engine, there's a need to track subscriptions per entity    QHash<ScriptManager*, QSet<QUuid>> _entitiesSubscribedToEntityScriptMessages;     // If this is set, defaultScripts.js will not be run if it is in the settings,    // and this will be run instead. This script will not be persisted to settings.    const QUrl _defaultScriptsOverride { };    // If an override is set, this will be true if defaultScripts.js was previously running.    bool _defaultScriptsWasRunning { false };}; QUrl normalizeScriptURL(const QUrl& rawScriptURL);QString expandScriptPath(const QString& rawPath);QUrl expandScriptUrl(const QUrl& rawScriptURL); #endif // hifi_ScriptEngine_h /// @}