overte-thingvellir/libraries/script-engine/src/ScriptEngines.h

441 lines
20 KiB
C++

//
// 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 interface
class 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
/// @}