diff --git a/libraries/script-engine/src/ScriptEngine.h b/libraries/script-engine/src/ScriptEngine.h index 7f69eee990..63a4ba4f90 100644 --- a/libraries/script-engine/src/ScriptEngine.h +++ b/libraries/script-engine/src/ScriptEngine.h @@ -87,6 +87,10 @@ public: QUrl definingSandboxURL { QUrl("about:EntityScript") }; }; +/**jsdoc + * @namespace Script + * @property {string} context + */ class ScriptEngine : public BaseScriptEngine, public EntitiesScriptEngineProvider { Q_OBJECT Q_PROPERTY(QString context READ getContext) @@ -115,6 +119,11 @@ public: QString getFilename() const; + /**jsdoc + * Stop the current script. + * @function Script.stop + * @param {boolean} [marshal=false] + */ //////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // 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); @@ -126,26 +135,69 @@ public: // 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 + * @param {object} object + */ /// registers a global object by name Q_INVOKABLE void registerGlobalObject(const QString& name, QObject* object); + /**jsdoc + * @function Script.registerGetterSetter + * @param {string} name + * @param {object} getter + * @param {object} setter + * @param {string} [parent=""] + */ /// 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 + * @param {object} function + * @param {number} [numArguments=-1] + */ /// register a global function Q_INVOKABLE void registerFunction(const QString& name, QScriptEngine::FunctionSignature fun, int numArguments = -1); + /**jsdoc + * @function Script.registerFunction + * @param {string} parent + * @param {string} name + * @param {object} function + * @param {number} [numArguments=-1] + */ /// 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 + * @param {object} value + */ /// registers a global object by name Q_INVOKABLE void registerValue(const QString& valueName, QScriptValue value); + /**jsdoc + * @function Script.evaluate + * @param {string} program + * @param {string} filename + * @param {number} [lineNumber=-1] + * @returns {object} + */ /// 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 + * @param {object} program + * @returns {object} + */ 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 @@ -154,24 +206,122 @@ public: void loadURL(const QUrl& scriptURL, bool reload); bool hasValidScriptSuffix(const QString& scriptFileName); + /**jsdoc + * @function Script.getContext + * @returns {string} + */ Q_INVOKABLE QString getContext() const; + + /**jsdoc + * @function Script.isClientScript + * @returns {boolean} + */ Q_INVOKABLE bool isClientScript() const { return _context == CLIENT_SCRIPT; } + + /**jsdoc + * @function Script.isEntityClientScript + * @returns {boolean} + */ Q_INVOKABLE bool isEntityClientScript() const { return _context == ENTITY_CLIENT_SCRIPT; } + + /**jsdoc + * @function Script.isEntityServerScript + * @returns {boolean} + */ Q_INVOKABLE bool isEntityServerScript() const { return _context == ENTITY_SERVER_SCRIPT; } + + /**jsdoc + * @function Script.isAgentScript + * @returns {boolean} + */ Q_INVOKABLE bool isAgentScript() const { return _context == AGENT_SCRIPT; } //////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // NOTE - these are intended to be public interfaces available to scripts + + /**jsdoc + * @function Script.addEventHandler + * @param {Uuid} entityID + * @param {string} eventName + * @param {function} handler + */ Q_INVOKABLE void addEventHandler(const EntityItemID& entityID, const QString& eventName, QScriptValue handler); + + /**jsdoc + * @function Script.removeEventHandler + * @param {Uuid} entityID + * @param {string} eventName + * @param {function} handler + */ Q_INVOKABLE void removeEventHandler(const EntityItemID& entityID, const QString& eventName, QScriptValue handler); + /**jsdoc + * Start a new Interface or entity script. + * @function Script.load + * @param {string} filename - The URL of the script to load. Can be relative to the current script. + * @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 list. + * // And you should see the following output: + * // This is script B + * // This is script A + */ Q_INVOKABLE void load(const QString& loadfile); + + /**jsdoc + * Include 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 + * @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. Can be an in-line + * function or the name of a function. + */ Q_INVOKABLE void include(const QStringList& includeFiles, QScriptValue callback = QScriptValue()); + + /**jsdoc + * Include 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. Can be relative to the current script. + * @param {function} [callback=null] - The function to call back when the script has been included. Can be an in-line + * function or the name of a function. + * @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 + * @function Script.require + * @param {string} module + */ Q_INVOKABLE QScriptValue require(const QString& moduleId); + + /**jsdoc + * @function Script.resetModuleCache + * @param {boolean} [deleteScriptCache=false] + */ Q_INVOKABLE void resetModuleCache(bool deleteScriptCache = false); QScriptValue currentModule(); bool registerModuleWithParent(const QScriptValue& module, const QScriptValue& parent); @@ -179,34 +329,168 @@ public: QVariantMap fetchModuleSource(const QString& modulePath, const bool forceDownload = false); QScriptValue instantiateModule(const QScriptValue& module, const QString& sourceCode); + /**jsdoc + * Call a function at a set interval. + * @function Script.setInterval + * @param {function} function - The function to call. Can be an in-line function or the name of a function. + * @param {number} interval - The interval at which to call the function, in ms. + * @returns {object} A handle to the interval timer. Can be used by {@link Script.clearInterval}. + * @example Print a message every second. + * Script.setInterval(function () { + * print("Timer fired"); + * }, 1000); + */ Q_INVOKABLE QObject* setInterval(const QScriptValue& function, int intervalMS); + + /**jsdoc + * Call a function after a delay. + * @function Script.setTimeout + * @param {function} function - The function to call. Can be an in-line function or the name of a function. + * @param {number} timeout - The delay after which to call the function, in ms. + * @returns {object} A handle to the timeout timer. Can be used by {@link Script.clearTimeout}. + * @example Print a message after a second. + * Script.setTimeout(function () { + * print("Timer fired"); + * }, 1000); + */ Q_INVOKABLE QObject* setTimeout(const QScriptValue& function, int timeoutMS); + + /**jsdoc + * Stop an interval timer set by {@link Script.setInterval|setInterval}. + * @function Script.clearInterval + * @param {object} timer - The interval timer to clear. + * @example Stop an interval timer. + * // Print a message every second. + * var timer = Script.setInterval(function () { + * print("Timer fired"); + * }, 1000); + * + * // Stop the timer after 10 seconds. + * Script.setTimeout(function () { + * print("Stop timer"); + * Script.clearInterval(timer); + * }, 10000); + */ Q_INVOKABLE void clearInterval(QObject* timer) { stopTimer(reinterpret_cast(timer)); } + + /**jsdoc + * Clear a timeout timer set by {@link Script.setTimeout|setTimeout}. + * @function Script.clearTimeout + * @param {object} timer - The timeout timer to clear. + * @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 + * @function Script.print + * @param {string} message + */ Q_INVOKABLE void print(const QString& message); + + /**jsdoc + * Resolve a relative path to an absolute path. + * @function Script.resolvePath + * @param {string} path - The relative path to resolve. + * @returns {string} The absolute path. + */ Q_INVOKABLE QUrl resolvePath(const QString& path) const; + + /**jsdoc + * @function Script.resourcesPath + * @returns {string} + */ Q_INVOKABLE QUrl resourcesPath() const; + + /**jsdoc + * @function Script.beginProfileRange + * @param {string} label + */ Q_INVOKABLE void beginProfileRange(const QString& label) const; + + /**jsdoc + * @function Script.endProfileRange + * @param {string} label + */ Q_INVOKABLE void endProfileRange(const QString& label) const; + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // Entity Script Related methods + + /**jsdoc + * @function Script.isEntityScriptRunning + * @param {Uuid} entityID + * @returns {boolean} + */ Q_INVOKABLE bool isEntityScriptRunning(const EntityItemID& entityID) { return _entityScripts.contains(entityID) && _entityScripts[entityID].status == EntityScriptStatus::RUNNING; } QVariant cloneEntityScriptDetails(const EntityItemID& entityID); QFuture getLocalEntityScriptDetails(const EntityItemID& entityID) override; + + /**jsdoc + * @function Script.loadEntityScript + * @param {Uuid} entityID + * @param {string} script + * @param {boolean} forceRedownload + */ Q_INVOKABLE void loadEntityScript(const EntityItemID& entityID, const QString& entityScript, bool forceRedownload); + + /**jsdoc + * @function Script.unloadEntityScript + * @param {Uuid} entityID + * @param {boolean} [shouldRemoveFromMap=false] + */ Q_INVOKABLE void unloadEntityScript(const EntityItemID& entityID, bool shouldRemoveFromMap = false); // will call unload method + + /**jsdoc + * @function Script.unloadAllEntityScripts + */ Q_INVOKABLE void unloadAllEntityScripts(); + + /**jsdoc + * @function Script.callEntityScriptMethod + * @param {Uuid} entityID + * @param {string} methodName + * @param {string[]} parameters + * @param {Uuid} [remoteCallerID=Uuid.NULL] + */ Q_INVOKABLE void callEntityScriptMethod(const EntityItemID& entityID, const QString& methodName, const QStringList& params = QStringList(), const QUuid& remoteCallerID = QUuid()) override; + + /**jsdoc + * @function Script.callEntityScriptMethod + * @param {Uuid} entityID + * @param {string} methodName + * @param {PointerEvent} event + */ Q_INVOKABLE void callEntityScriptMethod(const EntityItemID& entityID, const QString& methodName, const PointerEvent& event); + + /**jsdoc + * @function Script.callEntityScriptMethod + * @param {Uuid} entityID + * @param {string} methodName + * @param {Uuid} otherID + * @param {Collision} collision + */ Q_INVOKABLE void callEntityScriptMethod(const EntityItemID& entityID, const QString& methodName, const EntityItemID& otherID, const Collision& collision); + /**jsdoc + * @function Script.requestGarbageCollection + */ Q_INVOKABLE void requestGarbageCollection() { collectGarbage(); } + /**jsdoc + * @function Script.generateUUID + * @returns {Uuid} + */ Q_INVOKABLE QUuid generateUUID() { return QUuid::createUuid(); } bool isFinished() const { return _isFinished; } // used by Application and ScriptWidget @@ -239,33 +523,170 @@ public: bool getEntityScriptDetails(const EntityItemID& entityID, EntityScriptDetails &details) const; public slots: + + /**jsdoc + * @function Script.callAnimationStateHandler + * @param {function} callback + * @param {object} parameters + * @param {string[]} names + * @param {boolean} useNames + * @param {object} resultHandler + */ void callAnimationStateHandler(QScriptValue callback, AnimVariantMap parameters, QStringList names, bool useNames, AnimVariantResultHandler resultHandler); + + /**jsdoc + * @function Script.updateMemoryCost + * @param {number} deltaSize + */ void updateMemoryCost(const qint64&); signals: + + /**jsdoc + * @function Script.scriptLoaded + * @param {string} filename + * @returns {Signal} + */ void scriptLoaded(const QString& scriptFilename); + + /**jsdoc + * @function Script.errorLoadingScript + * @param {string} filename + * @returns {Signal} + */ void errorLoadingScript(const QString& scriptFilename); + + /**jsdoc + * Triggered regularly at a system-determined frequency. + * @function Script.update + * @param {number} deltaTime - The time since the last update, in s. + * @returns {Signal} + */ void update(float deltaTime); + + /**jsdoc + * Triggered when the script is ending. + * @function Script.scriptEnding + * @returns {Signal} + * @example Connect to the scriptEnding signal. + * 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 + * @param {object} engine + * @returns {Signal} + */ void finished(const QString& fileNameString, ScriptEnginePointer); + + /**jsdoc + * @function Script.cleanupMenuItem + * @param {string} menuItem + * @returns {Signal} + */ void cleanupMenuItem(const QString& menuItemString); + + /**jsdoc + * @function Script.printedMessage + * @param {string} message + * @param {string} scriptName + * @returns {Signal} + */ void printedMessage(const QString& message, const QString& scriptName); + + /**jsdoc + * @function Script.errorMessage + * @param {string} message + * @param {string} scriptName + * @returns {Signal} + */ void errorMessage(const QString& message, const QString& scriptName); + + /**jsdoc + * @function Script.warningMessage + * @param {string} message + * @param {string} scriptName + * @returns {Signal} + */ void warningMessage(const QString& message, const QString& scriptName); + + /**jsdoc + * @function Script.infoMessage + * @param {string} message + * @param {string} scriptName + * @returns {Signal} + */ void infoMessage(const QString& message, const QString& scriptName); + + /**jsdoc + * @function Script.runningStateChanged + * @returns {Signal} + */ void runningStateChanged(); + + /**jsdoc + * @function Script.clearDebugWindow + * @returns {Signal} + */ void clearDebugWindow(); + + /**jsdoc + * @function Script.loadScript + * @param {string} scriptName + * @param {boolean} isUserLoaded + * @returns {Signal} + */ void loadScript(const QString& scriptName, bool isUserLoaded); + + /**jsdoc + * @function Script.reloadScript + * @param {string} scriptName + * @param {boolean} isUserLoaded + * @returns {Signal} + */ void reloadScript(const QString& scriptName, bool isUserLoaded); + + /**jsdoc + * @function Script.doneRunning + * @returns {Signal} + */ void doneRunning(); + /**jsdoc + * @function Script.entityScriptDetailsUpdated + * @returns {Signal} + */ // 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(); protected: void init(); + + /**jsdoc + * @function Script.executeOnScriptThread + * @param {object} function + * @param {ConnectionType} [type=2] + */ Q_INVOKABLE void executeOnScriptThread(std::function function, const Qt::ConnectionType& type = Qt::QueuedConnection ); + + /**jsdoc + * @function Script._requireResolve + * @param {string} module + * @param {string} [relativeTo=""] + * @returns {string} + */ // 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()); @@ -285,6 +706,16 @@ protected: QHash _registeredHandlers; void forwardHandlerCall(const EntityItemID& entityID, const QString& eventName, QScriptValueList eventHanderArgs); + + /**jsdoc + * @function Script.entityScriptContentAvailable + * @param {Uuid} entityID + * @param {string} scriptOrURL + * @param {string} contents + * @param {boolean} isURL + * @param {boolean} success + * @param {string} status + */ 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.