// // ScriptEngine.h // libraries/script-engine/src // // Created by Brad Hefta-Gaub on 12/14/13. // Copyright 2013 High Fidelity, Inc. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // #ifndef hifi_ScriptEngine_h #define hifi_ScriptEngine_h #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "PointerEvent.h" #include "ArrayBufferClass.h" #include "AssetScriptingInterface.h" #include "AudioScriptingInterface.h" #include "BaseScriptEngine.h" #include "Quat.h" #include "Mat4.h" #include "ScriptCache.h" #include "ScriptUUID.h" #include "Vec3.h" #include "ConsoleScriptingInterface.h" #include "SettingHandle.h" #include "Profile.h" class QScriptEngineDebugger; 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 ScriptEngines; Q_DECLARE_METATYPE(ScriptEnginePointer) class CallbackData { public: QScriptValue function; EntityItemID definingEntityIdentifier; QUrl definingSandboxURL; }; class DeferredLoadEntity { public: EntityItemID entityID; QString entityScript; //bool forceRedownload; }; struct EntityScriptContentAvailable { EntityItemID entityID; QString scriptOrURL; QString contents; bool isURL; bool success; QString status; }; typedef std::unordered_map EntityScriptContentAvailableMap; typedef QList CallbackList; typedef QHash RegisteredEventHandlers; class EntityScriptDetails { public: EntityScriptStatus status { EntityScriptStatus::PENDING }; // If status indicates an error, this contains a human-readable string giving more information about the error. QString errorInfo { "" }; QString scriptText { "" }; QScriptValue scriptObject { QScriptValue() }; int64_t lastModified { 0 }; QUrl definingSandboxURL { QUrl("about:EntityScript") }; }; /**jsdoc * The Script API provides facilities for working with scripts. * * @namespace Script * * @hifi-interface * @hifi-client-entity * @hifi-avatar * @hifi-server-entity * @hifi-assignment-client * * @property {string} context - The context that the script is running in: *
    *
  • "client": An Interface or avatar script.
  • *
  • "entity_client": A client entity script.
  • *
  • "entity_server": A server entity script.
  • *
  • "agent": An assignment client script.
  • *
* Read-only. */ class ScriptEngine : public BaseScriptEngine, public EntitiesScriptEngineProvider { Q_OBJECT Q_PROPERTY(QString context READ getContext) Q_PROPERTY(QString type READ getTypeAsString) Q_PROPERTY(QString fileName MEMBER _fileNameString CONSTANT) public: enum Context { CLIENT_SCRIPT, ENTITY_CLIENT_SCRIPT, ENTITY_SERVER_SCRIPT, AGENT_SCRIPT }; enum Type { CLIENT, ENTITY_CLIENT, ENTITY_SERVER, AGENT, AVATAR }; Q_ENUM(Type) static int processLevelMaxRetries; ScriptEngine(Context context, const QString& scriptContents = NO_SCRIPT, const QString& fileNameString = QString("about:ScriptEngine")); ~ScriptEngine(); /// run the script in a dedicated thread. This will have the side effect of evalulating /// the current script contents and calling run(). Callers will likely want to register the script with external /// services before calling this. void runInThread(); void runDebuggable(); /// run the script in the callers thread, exit when stop() is called. void run(); QString getFilename() const; QList getListOfEntityScriptIDs(); /**jsdoc * Stops and unloads the current script. *

Warning: If an assignment client script, the script gets restarted after stopping.

* @function Script.stop * @param {boolean} [marshal=false] - Marshal. *

Deprecated: This parameter is deprecated and will be removed.

* @example Stop a script after 5s. * Script.setInterval(function () { * print("Hello"); * }, 1000); * * Script.setTimeout(function () { * Script.stop(true); * }, 5000); */ //////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // NOTE - this is intended to be a public interface for Agent scripts, and local scripts, but not for EntityScripts Q_INVOKABLE void stop(bool marshal = false); // Stop any evaluating scripts and wait for the scripting thread to finish. void waitTillDoneRunning(); //////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // NOTE - these are NOT intended to be public interfaces available to scripts, the are only Q_INVOKABLE so we can // properly ensure they are only called on the correct thread /**jsdoc * @function Script.registerGlobalObject * @param {string} name - Name. * @param {object} object - Object. * @deprecated This function is deprecated and will be removed. */ /// registers a global object by name Q_INVOKABLE void registerGlobalObject(const QString& name, QObject* object); /**jsdoc * @function Script.registerGetterSetter * @param {string} name - Name. * @param {function} getter - Getter. * @param {function} setter - Setter. * @param {string} [parent=""] - Parent. * @deprecated This function is deprecated and will be removed. */ /// registers a global getter/setter Q_INVOKABLE void registerGetterSetter(const QString& name, QScriptEngine::FunctionSignature getter, QScriptEngine::FunctionSignature setter, const QString& parent = QString("")); /**jsdoc * @function Script.registerFunction * @param {string} name - Name. * @param {function} function - Function. * @param {number} [numArguments=-1] - Number of arguments. * @deprecated This function is deprecated and will be removed. */ /// register a global function Q_INVOKABLE void registerFunction(const QString& name, QScriptEngine::FunctionSignature fun, int numArguments = -1); /**jsdoc * @function Script.registerFunction * @param {string} parent - Parent. * @param {string} name - Name. * @param {function} function - Function. * @param {number} [numArguments=-1] - Number of arguments. * @deprecated This function is deprecated and will be removed. */ /// register a function as a method on a previously registered global object Q_INVOKABLE void registerFunction(const QString& parent, const QString& name, QScriptEngine::FunctionSignature fun, int numArguments = -1); /**jsdoc * @function Script.registerValue * @param {string} name - Name. * @param {object} value - Value. * @deprecated This function is deprecated and will be removed. */ /// registers a global object by name Q_INVOKABLE void registerValue(const QString& valueName, QScriptValue value); /**jsdoc * @function Script.evaluate * @param {string} program - Program. * @param {string} filename - File name. * @param {number} [lineNumber=-1] - Line number. * @returns {object} Object. * @deprecated This function is deprecated and will be removed. */ /// evaluate some code in the context of the ScriptEngine and return the result Q_INVOKABLE QScriptValue evaluate(const QString& program, const QString& fileName, int lineNumber = 1); // this is also used by the script tool widget /**jsdoc * @function Script.evaluateInClosure * @param {object} locals - Locals. * @param {object} program - Program. * @returns {object} Object. * @deprecated This function is deprecated and will be removed. */ Q_INVOKABLE QScriptValue evaluateInClosure(const QScriptValue& locals, const QScriptProgram& program); /// if the script engine is not already running, this will download the URL and start the process of seting it up /// to run... NOTE - this is used by Application currently to load the url. We don't really want it to be exposed /// to scripts. we may not need this to be invokable void loadURL(const QUrl& scriptURL, bool reload); bool hasValidScriptSuffix(const QString& scriptFileName); /**jsdoc * Gets the context that the script is running in: Interface/avatar, client entity, server entity, or assignment client. * @function Script.getContext * @returns {string} The context that the script is running in: *
    *
  • "client": An Interface or avatar script.
  • *
  • "entity_client": A client entity script.
  • *
  • "entity_server": A server entity script.
  • *
  • "agent": An assignment client script.
  • *
*/ Q_INVOKABLE QString getContext() const; /**jsdoc * Checks whether the script is running as an Interface or avatar script. * @function Script.isClientScript * @returns {boolean} true if the script is running as an Interface or avatar script, false if it * isn't. */ Q_INVOKABLE bool isClientScript() const { return _context == CLIENT_SCRIPT; } /**jsdoc * Checks whether the application was compiled as a debug build. * @function Script.isDebugMode * @returns {boolean} true if the application was compiled as a debug build, false if it was * compiled as a release build. */ Q_INVOKABLE bool isDebugMode() const; /**jsdoc * Checks whether the script is running as a client entity script. * @function Script.isEntityClientScript * @returns {boolean} true if the script is running as a client entity script, false if it isn't. */ Q_INVOKABLE bool isEntityClientScript() const { return _context == ENTITY_CLIENT_SCRIPT; } /**jsdoc * Checks whether the script is running as a server entity script. * @function Script.isEntityServerScript * @returns {boolean} true if the script is running as a server entity script, false if it isn't. */ Q_INVOKABLE bool isEntityServerScript() const { return _context == ENTITY_SERVER_SCRIPT; } /**jsdoc * Checks whether the script is running as an assignment client script. * @function Script.isAgentScript * @returns {boolean} true if the script is running as an assignment client script, false if it * isn't. */ Q_INVOKABLE bool isAgentScript() const { return _context == AGENT_SCRIPT; } //////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // NOTE - these are intended to be public interfaces available to scripts /**jsdoc * Adds a function to the list of functions called when an entity event occurs on a particular entity. *

See also, the {@link Entities} API.

* @function Script.addEventHandler * @param {Uuid} entityID - The ID of the entity. * @param {Script.EntityEvent} eventName - The name of the entity event. * @param {function} handler - The function to call when the entity event occurs on the entity. It can be either the name * of a function or an in-line definition. * @example Report when a mouse press occurs on a particular entity. * var entityID = Entities.addEntity({ * type: "Box", * position: Vec3.sum(MyAvatar.position, Vec3.multiplyQbyV(MyAvatar.orientation, { x: 0, y: 0, z: -5 })), * dimensions: { x: 0.5, y: 0.5, z: 0.5 }, * lifetime: 300 // Delete after 5 minutes. * }); * * function reportMousePress(entityID, event) { * print("Mouse pressed on entity: " + JSON.stringify(event)); * } * * Script.addEventHandler(entityID, "mousePressOnEntity", reportMousePress); */ Q_INVOKABLE void addEventHandler(const EntityItemID& entityID, const QString& eventName, QScriptValue handler); /**jsdoc * Removes a function from the list of functions called when an entity event occurs on a particular entity. *

See also, the {@link Entities} API.

* @function Script.removeEventHandler * @param {Uuid} entityID - The ID of the entity. * @param {Script.EntityEvent} eventName - The name of the entity event. * @param {function} handler - The name of the function to no longer call when the entity event occurs on the entity. */ Q_INVOKABLE void removeEventHandler(const EntityItemID& entityID, const QString& eventName, QScriptValue handler); /**jsdoc * Starts running another script in Interface, if it isn't already running. The script is not automatically loaded next * time Interface starts. *

Supported Script Types: Interface Scripts • Avatar Scripts

*

See also, {@link ScriptDiscoveryService.loadScript}.

* @function Script.load * @param {string} filename - The URL of the script to load. This can be relative to the current script's URL. * @example Load a script from another script. * // First file: scriptA.js * print("This is script A"); * * // Second file: scriptB.js * print("This is script B"); * Script.load("scriptA.js"); * * // If you run scriptB.js you should see both scripts in the Running Scripts dialog. * // And you should see the following output: * // This is script B * // This is script A */ Q_INVOKABLE void load(const QString& loadfile); /**jsdoc * Includes JavaScript from other files in the current script. If a callback is specified, the files are loaded and * included asynchronously, otherwise they are included synchronously (i.e., script execution blocks while the files are * included). * @function Script.include * @variation 0 * @param {string[]} filenames - The URLs of the scripts to include. Each can be relative to the current script. * @param {function} [callback=null] - The function to call back when the scripts have been included. It can be either the * name of a function or an in-line definition. */ Q_INVOKABLE void include(const QStringList& includeFiles, QScriptValue callback = QScriptValue()); /**jsdoc * Includes JavaScript from another file in the current script. If a callback is specified, the file is loaded and included * asynchronously, otherwise it is included synchronously (i.e., script execution blocks while the file is included). * @function Script.include * @param {string} filename - The URL of the script to include. It can be relative to the current script. * @param {function} [callback=null] - The function to call back when the script has been included. It can be either the * name of a function or an in-line definition. * @example Include a script file asynchronously. * // First file: scriptA.js * print("This is script A"); * * // Second file: scriptB.js * print("This is script B"); * Script.include("scriptA.js", function () { * print("Script A has been included"); * }); * * // If you run scriptB.js you should see only scriptB.js in the running scripts list. * // And you should see the following output: * // This is script B * // This is script A * // Script A has been included */ Q_INVOKABLE void include(const QString& includeFile, QScriptValue callback = QScriptValue()); //////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // MODULE related methods /**jsdoc * Provides access to methods or objects provided in an external JavaScript or JSON file. * See {@link https://docs.highfidelity.com/script/js-tips.html} for further details. * @function Script.require * @param {string} module - The module to use. May be a JavaScript file, a JSON file, or the name of a system module such * as "appUi" (i.e., the "appUi.js" system module JavaScript file). * @returns {object|array} The value assigned to module.exports in the JavaScript file, or the value defined * in the JSON file. */ Q_INVOKABLE QScriptValue require(const QString& moduleId); /**jsdoc * @function Script.resetModuleCache * @param {boolean} [deleteScriptCache=false] - Delete script cache. * @deprecated This function is deprecated and will be removed. */ Q_INVOKABLE void resetModuleCache(bool deleteScriptCache = false); QScriptValue currentModule(); bool registerModuleWithParent(const QScriptValue& module, const QScriptValue& parent); QScriptValue newModule(const QString& modulePath, const QScriptValue& parent = QScriptValue()); QVariantMap fetchModuleSource(const QString& modulePath, const bool forceDownload = false); QScriptValue instantiateModule(const QScriptValue& module, const QString& sourceCode); /**jsdoc * Calls a function repeatedly, at a set interval. * @function Script.setInterval * @param {function} function - The function to call. This can be either the name of a function or an in-line definition. * @param {number} interval - The interval at which to call the function, in ms. * @returns {object} A handle to the interval timer. This can be used in {@link Script.clearInterval}. * @example Print a message every second. * Script.setInterval(function () { * print("Interval timer fired"); * }, 1000); */ Q_INVOKABLE QObject* setInterval(const QScriptValue& function, int intervalMS); /**jsdoc * Calls a function once, after a delay. * @function Script.setTimeout * @param {function} function - The function to call. This can be either the name of a function or an in-line definition. * @param {number} timeout - The delay after which to call the function, in ms. * @returns {object} A handle to the timeout timer. This can be used in {@link Script.clearTimeout}. * @example Print a message once, after a second. * Script.setTimeout(function () { * print("Timeout timer fired"); * }, 1000); */ Q_INVOKABLE QObject* setTimeout(const QScriptValue& function, int timeoutMS); /**jsdoc * Stops an interval timer set by {@link Script.setInterval|setInterval}. * @function Script.clearInterval * @param {object} timer - The interval timer to stop. * @example Stop an interval timer. * // Print a message every second. * var timer = Script.setInterval(function () { * print("Interval timer fired"); * }, 1000); * * // Stop the timer after 10 seconds. * Script.setTimeout(function () { * print("Stop interval timer"); * Script.clearInterval(timer); * }, 10000); */ Q_INVOKABLE void clearInterval(QObject* timer) { stopTimer(reinterpret_cast(timer)); } /**jsdoc * Stops a timeout timer set by {@link Script.setTimeout|setTimeout}. * @function Script.clearTimeout * @param {object} timer - The timeout timer to stop. * @example Stop a timeout timer. * // Print a message after two seconds. * var timer = Script.setTimeout(function () { * print("Timer fired"); * }, 2000); * * // Uncomment the following line to stop the timer from firing. * //Script.clearTimeout(timer); */ Q_INVOKABLE void clearTimeout(QObject* timer) { stopTimer(reinterpret_cast(timer)); } /**jsdoc * Prints a message to the program log. *

Alternatively, you can use {@link print}, {@link console.log}, or one of the other {@link console} methods.

* @function Script.print * @param {string} message - The message to print. */ /**jsdoc * Prints a message to the program log. *

This is an alias of {@link Script.print}.

* @function print * @param {string} message - The message to print. */ Q_INVOKABLE void print(const QString& message); /**jsdoc * Resolves a relative path to an absolute path. The relative path is relative to the script's location. * @function Script.resolvePath * @param {string} path - The relative path to resolve. * @returns {string} The absolute path. * @example Report the directory and filename of the running script. * print(Script.resolvePath("")); * @example Report the directory of the running script. * print(Script.resolvePath(".")); * @example Report the path to a file located relative to the running script. * print(Script.resolvePath("../assets/sounds/hello.wav")); */ Q_INVOKABLE QUrl resolvePath(const QString& path) const; /**jsdoc * Gets the path to the resources directory for QML files. * @function Script.resourcesPath * @returns {string} The path to the resources directory for QML files. */ Q_INVOKABLE QUrl resourcesPath() const; /**jsdoc * Starts timing a section of code in order to send usage data about it to High Fidelity. Shouldn't be used outside of the * standard scripts. * @function Script.beginProfileRange * @param {string} label - A name that identifies the section of code. */ Q_INVOKABLE void beginProfileRange(const QString& label) const; /**jsdoc * Finishes timing a section of code in order to send usage data about it to High Fidelity. Shouldn't be used outside of * the standard scripts. * @function Script.endProfileRange * @param {string} label - A name that identifies the section of code. */ Q_INVOKABLE void endProfileRange(const QString& label) const; //////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // Entity Script Related methods /**jsdoc * Checks whether an entity has an entity script running. * @function Script.isEntityScriptRunning * @param {Uuid} entityID - The ID of the entity. * @returns {boolean} true if the entity has an entity script running, false if it doesn't. */ Q_INVOKABLE bool isEntityScriptRunning(const EntityItemID& entityID) { QReadLocker locker { &_entityScriptsLock }; auto it = _entityScripts.constFind(entityID); return it != _entityScripts.constEnd() && it->status == EntityScriptStatus::RUNNING; } QVariant cloneEntityScriptDetails(const EntityItemID& entityID); QFuture getLocalEntityScriptDetails(const EntityItemID& entityID) override; /**jsdoc * @function Script.loadEntityScript * @param {Uuid} entityID - Entity ID. * @param {string} script - Script. * @param {boolean} forceRedownload - Force re-download. * @deprecated This function is deprecated and will be removed. */ Q_INVOKABLE void loadEntityScript(const EntityItemID& entityID, const QString& entityScript, bool forceRedownload); /**jsdoc * @function Script.unloadEntityScript * @param {Uuid} entityID - Entity ID. * @param {boolean} [shouldRemoveFromMap=false] - Should remove from map. * @deprecated This function is deprecated and will be removed. */ Q_INVOKABLE void unloadEntityScript(const EntityItemID& entityID, bool shouldRemoveFromMap = false); // will call unload method /**jsdoc * @function Script.unloadAllEntityScripts * @param {boolean} [blockingCall=false] - Wait for completion if call moved to another thread. * @deprecated This function is deprecated and will be removed. */ Q_INVOKABLE void unloadAllEntityScripts(bool blockingCall = false); /**jsdoc * Calls a method in an entity script. * @function Script.callEntityScriptMethod * @param {Uuid} entityID - The ID of the entity running the entity script. * @param {string} methodName - The name of the method to call. * @param {string[]} [parameters=[]] - The parameters to call the specified method with. * @param {Uuid} [remoteCallerID=Uuid.NULL] - An ID that identifies the caller. */ Q_INVOKABLE void callEntityScriptMethod(const EntityItemID& entityID, const QString& methodName, const QStringList& params = QStringList(), const QUuid& remoteCallerID = QUuid()) override; /**jsdoc * Calls a method in an entity script. * @function Script.callEntityScriptMethod * @param {Uuid} entityID - Entity ID. * @param {string} methodName - Method name. * @param {PointerEvent} event - Pointer event. * @deprecated This function is deprecated and will be removed. */ Q_INVOKABLE void callEntityScriptMethod(const EntityItemID& entityID, const QString& methodName, const PointerEvent& event); /**jsdoc * Calls a method in an entity script. * @function Script.callEntityScriptMethod * @param {Uuid} entityID - Entity ID. * @param {string} methodName - Method name. * @param {Uuid} otherID - Other entity ID. * @param {Collision} collision - Collision. * @deprecated This function is deprecated and will be removed. */ Q_INVOKABLE void callEntityScriptMethod(const EntityItemID& entityID, const QString& methodName, const EntityItemID& otherID, const Collision& collision); /**jsdoc * Manually runs the JavaScript garbage collector which reclaims memory by disposing of objects that are no longer * reachable. * @function Script.requestGarbageCollection */ Q_INVOKABLE void requestGarbageCollection() { collectGarbage(); } /**jsdoc * @function Script.generateUUID * @returns {Uuid} A new UUID. * @deprecated This function is deprecated and will be removed. Use {@link Uuid(0).generate|Uuid.generate} instead. */ Q_INVOKABLE QUuid generateUUID() { return QUuid::createUuid(); } void setType(Type type) { _type = type; }; Type getType() { return _type; }; QString getTypeAsString() const; bool isFinished() const { return _isFinished; } // used by Application and ScriptWidget bool isRunning() const { return _isRunning; } // used by ScriptWidget // this is used by code in ScriptEngines.cpp during the "reload all" operation bool isStopping() const { return _isStopping; } bool isDebuggable() const { return _debuggable; } void disconnectNonEssentialSignals(); //////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // These are currently used by Application to track if a script is user loaded or not. Consider finding a solution // inside of Application so that the ScriptEngine class is not polluted by this notion void setUserLoaded(bool isUserLoaded) { _isUserLoaded = isUserLoaded; } bool isUserLoaded() const { return _isUserLoaded; } void setQuitWhenFinished(const bool quitWhenFinished) { _quitWhenFinished = quitWhenFinished; } bool isQuitWhenFinished() const { return _quitWhenFinished; } // NOTE - this is used by the TypedArray implementation. we need to review this for thread safety ArrayBufferClass* getArrayBufferClass() { return _arrayBufferClass; } void setEmitScriptUpdatesFunction(std::function func) { _emitScriptUpdates = func; } void scriptErrorMessage(const QString& message); void scriptWarningMessage(const QString& message); void scriptInfoMessage(const QString& message); void scriptPrintedMessage(const QString& message); void clearDebugLogWindow(); int getNumRunningEntityScripts() const; bool getEntityScriptDetails(const EntityItemID& entityID, EntityScriptDetails &details) const; bool hasEntityScriptDetails(const EntityItemID& entityID) const; void setScriptEngines(QSharedPointer& scriptEngines) { _scriptEngines = scriptEngines; } public slots: /**jsdoc * @function Script.callAnimationStateHandler * @param {function} callback - Callback function. * @param {object} parameters - Parameters. * @param {string[]} names - Names. * @param {boolean} useNames - Use names. * @param {function} resultHandler - Result handler. * @deprecated This function is deprecated and will be removed. */ void callAnimationStateHandler(QScriptValue callback, AnimVariantMap parameters, QStringList names, bool useNames, AnimVariantResultHandler resultHandler); /**jsdoc * @function Script.updateMemoryCost * @param {number} deltaSize - Delta size. * @deprecated This function is deprecated and will be removed. */ void updateMemoryCost(const qint64&); signals: /**jsdoc * @function Script.scriptLoaded * @param {string} filename - File name. * @returns {Signal} * @deprecated This signal is deprecated and will be removed. */ void scriptLoaded(const QString& scriptFilename); /**jsdoc * @function Script.errorLoadingScript * @param {string} filename - File name. * @returns {Signal} * @deprecated This signal is deprecated and will be removed. */ void errorLoadingScript(const QString& scriptFilename); /**jsdoc * Triggered frequently at a system-determined interval. * @function Script.update * @param {number} deltaTime - The time since the last update, in s. * @returns {Signal} * @example Report script update intervals. * Script.update.connect(function (deltaTime) { * print("Update: " + deltaTime); * }); */ void update(float deltaTime); /**jsdoc * Triggered when the script is stopping. * @function Script.scriptEnding * @returns {Signal} * @example Report when a script is stopping. * print("Script started"); * * Script.scriptEnding.connect(function () { * print("Script ending"); * }); * * Script.setTimeout(function () { * print("Stopping script"); * Script.stop(); * }, 1000); */ void scriptEnding(); /**jsdoc * @function Script.finished * @param {string} filename - File name. * @param {object} engine - Engine. * @returns {Signal} * @deprecated This signal is deprecated and will be removed. */ void finished(const QString& fileNameString, ScriptEnginePointer); /**jsdoc * @function Script.cleanupMenuItem * @param {string} menuItem - Menu item. * @returns {Signal} * @deprecated This signal is deprecated and will be removed. */ void cleanupMenuItem(const QString& menuItemString); /**jsdoc * Triggered when the script prints a message to the program log via {@link print}, {@link Script.print}, * {@link console.log}, or {@link console.debug}. * @function Script.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& scriptName); /**jsdoc * Triggered when the script generates an error or {@link console.error} is called. * @function Script.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& scriptName); /**jsdoc * Triggered when the script generates a warning or {@link console.warn} is called. * @function Script.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& scriptName); /**jsdoc * Triggered when the script generates an information message or {@link console.info} is called. * @function Script.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& scriptName); /**jsdoc * Triggered when the running state of the script changes, e.g., from running to stopping. * @function Script.runningStateChanged * @returns {Signal} */ void runningStateChanged(); /**jsdoc * @function Script.clearDebugWindow * @returns {Signal} * @deprecated This signal is deprecated and will be removed. */ void clearDebugWindow(); /**jsdoc * @function Script.loadScript * @param {string} scriptName - Script name. * @param {boolean} isUserLoaded - Is user loaded. * @returns {Signal} * @deprecated This signal is deprecated and will be removed. */ void loadScript(const QString& scriptName, bool isUserLoaded); /**jsdoc * @function Script.reloadScript * @param {string} scriptName - Script name. * @param {boolean} isUserLoaded - Is user loaded. * @returns {Signal} * @deprecated This signal is deprecated and will be removed. */ void reloadScript(const QString& scriptName, bool isUserLoaded); /**jsdoc * Triggered when the script has stopped. * @function Script.doneRunning * @returns {Signal} */ void doneRunning(); /**jsdoc * @function Script.entityScriptDetailsUpdated * @returns {Signal} * @deprecated This signal is deprecated and will be removed. */ // Emitted when an entity script is added or removed, or when the status of an entity // script is updated (goes from RUNNING to ERROR_RUNNING_SCRIPT, for example) void entityScriptDetailsUpdated(); /**jsdoc * Triggered when the script starts for the user. See also, {@link Entities.preload}. *

Supported Script Types: Client Entity Scripts • Server Entity Scripts

* @function Script.entityScriptPreloadFinished * @param {Uuid} entityID - The ID of the entity that the script is running in. * @returns {Signal} * @example Get the ID of the entity that a client entity script is running in. * var entityScript = function () { * this.entityID = Uuid.NULL; * }; * * Script.entityScriptPreloadFinished.connect(function (entityID) { * this.entityID = entityID; * print("Entity ID: " + this.entityID); * }); * * var entityID = Entities.addEntity({ * type: "Box", * position: Vec3.sum(MyAvatar.position, Vec3.multiplyQbyV(MyAvatar.orientation, { x: 0, y: 0, z: -5 })), * dimensions: { x: 0.5, y: 0.5, z: 0.5 }, * color: { red: 255, green: 0, blue: 0 }, * script: "(" + entityScript + ")", // Could host the script on a Web server instead. * lifetime: 300 // Delete after 5 minutes. * }); */ // Emitted when an entity script has finished running preload void entityScriptPreloadFinished(const EntityItemID& entityID); protected: void init(); /**jsdoc * @function Script.executeOnScriptThread * @param {function} function - Function. * @param {ConnectionType} [type=2] - Connection type. * @deprecated This function is deprecated and will be removed. */ Q_INVOKABLE void executeOnScriptThread(std::function function, const Qt::ConnectionType& type = Qt::QueuedConnection ); /**jsdoc * @function Script._requireResolve * @param {string} module - Module. * @param {string} [relativeTo=""] - Relative to. * @returns {string} Result. * @deprecated This function is deprecated and will be removed. */ // note: this is not meant to be called directly, but just to have QMetaObject take care of wiring it up in general; // then inside of init() we just have to do "Script.require.resolve = Script._requireResolve;" Q_INVOKABLE QString _requireResolve(const QString& moduleId, const QString& relativeTo = QString()); QString logException(const QScriptValue& exception); void timerFired(); void stopAllTimers(); void stopAllTimersForEntityScript(const EntityItemID& entityID); void refreshFileScript(const EntityItemID& entityID); void updateEntityScriptStatus(const EntityItemID& entityID, const EntityScriptStatus& status, const QString& errorInfo = QString()); void setEntityScriptDetails(const EntityItemID& entityID, const EntityScriptDetails& details); void setParentURL(const QString& parentURL) { _parentURL = parentURL; } QObject* setupTimerWithInterval(const QScriptValue& function, int intervalMS, bool isSingleShot); void stopTimer(QTimer* timer); QHash _registeredHandlers; void forwardHandlerCall(const EntityItemID& entityID, const QString& eventName, QScriptValueList eventHanderArgs); /**jsdoc * @function Script.entityScriptContentAvailable * @param {Uuid} entityID - Entity ID. * @param {string} scriptOrURL - Path. * @param {string} contents - Contents. * @param {boolean} isURL - Is a URL. * @param {boolean} success - Success. * @param {string} status - Status. * @deprecated This function is deprecated and will be removed. */ Q_INVOKABLE void entityScriptContentAvailable(const EntityItemID& entityID, const QString& scriptOrURL, const QString& contents, bool isURL, bool success, const QString& status); EntityItemID currentEntityIdentifier; // Contains the defining entity script entity id during execution, if any. Empty for interface script execution. QUrl currentSandboxURL; // The toplevel url string for the entity script that loaded the code being executed, else empty. void doWithEnvironment(const EntityItemID& entityID, const QUrl& sandboxURL, std::function operation); void callWithEnvironment(const EntityItemID& entityID, const QUrl& sandboxURL, QScriptValue function, QScriptValue thisObject, QScriptValueList args); Context _context; Type _type; QString _scriptContents; QString _parentURL; std::atomic _isFinished { false }; std::atomic _isRunning { false }; std::atomic _isStopping { false }; bool _isInitialized { false }; QHash _timerFunctionMap; QSet _includedURLs; mutable QReadWriteLock _entityScriptsLock { QReadWriteLock::Recursive }; QHash _entityScripts; EntityScriptContentAvailableMap _contentAvailableQueue; bool _isThreaded { false }; QScriptEngineDebugger* _debugger { nullptr }; bool _debuggable { false }; qint64 _lastUpdate; QString _fileNameString; Quat _quatLibrary; Vec3 _vec3Library; Mat4 _mat4Library; ScriptUUID _uuidLibrary; ConsoleScriptingInterface _consoleScriptingInterface; std::atomic _isUserLoaded { false }; bool _isReloading { false }; std::atomic _quitWhenFinished; ArrayBufferClass* _arrayBufferClass; AssetScriptingInterface* _assetScriptingInterface; std::function _emitScriptUpdates{ []() { return true; } }; std::recursive_mutex _lock; std::chrono::microseconds _totalTimerExecution { 0 }; static const QString _SETTINGS_ENABLE_EXTENDED_MODULE_COMPAT; static const QString _SETTINGS_ENABLE_EXTENDED_EXCEPTIONS; Setting::Handle _enableExtendedJSExceptions { _SETTINGS_ENABLE_EXTENDED_EXCEPTIONS, true }; QWeakPointer _scriptEngines; }; ScriptEnginePointer scriptEngineFactory(ScriptEngine::Context context, const QString& scriptContents, const QString& fileNameString); #endif // hifi_ScriptEngine_h