mirror of
https://thingvellir.net/git/overte
synced 2025-03-27 23:52:03 +01:00
496 lines
17 KiB
C++
496 lines
17 KiB
C++
//
|
|
// ScriptEngine.h
|
|
// libraries/script-engine/src
|
|
//
|
|
// Created by Brad Hefta-Gaub on 12/14/13.
|
|
// Copyright 2013 High Fidelity, Inc.
|
|
// Copyright 2020 Vircadia contributors.
|
|
// 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_ScriptEngine_h
|
|
#define hifi_ScriptEngine_h
|
|
|
|
#include <memory>
|
|
|
|
#include <QtCore/QFlags>
|
|
#include <QtCore/QObject>
|
|
|
|
#include "ScriptValue.h"
|
|
#include "ScriptException.h"
|
|
|
|
// These are used for debugging memory leaks caused by persistent handles
|
|
//#define OVERTE_V8_MEMORY_DEBUG
|
|
|
|
class QByteArray;
|
|
class QLatin1String;
|
|
class QString;
|
|
class QThread;
|
|
class QVariant;
|
|
class ScriptContext;
|
|
class ScriptEngine;
|
|
class ScriptManager;
|
|
class ScriptProgram;
|
|
using ScriptEnginePointer = std::shared_ptr<ScriptEngine>;
|
|
using ScriptProgramPointer = std::shared_ptr<ScriptProgram>;
|
|
|
|
Q_DECLARE_METATYPE(ScriptEnginePointer);
|
|
|
|
template <typename T>
|
|
inline ScriptValue
|
|
|
|
scriptValueFromValue(ScriptEngine* engine, const T& t);
|
|
|
|
template <typename T>
|
|
inline T scriptvalue_cast(const ScriptValue& value);
|
|
|
|
class ScriptEngineMemoryStatistics {
|
|
public:
|
|
size_t totalHeapSize;
|
|
size_t usedHeapSize;
|
|
size_t totalAvailableSize;
|
|
size_t totalGlobalHandlesSize;
|
|
size_t usedGlobalHandlesSize;
|
|
#ifdef OVERTE_V8_MEMORY_DEBUG
|
|
size_t scriptValueCount;
|
|
size_t scriptValueProxyCount;
|
|
size_t qObjectCount;
|
|
//size_t qVariantProxyCount;
|
|
#endif
|
|
};
|
|
|
|
/**
|
|
* @brief Provides an engine-independent interface for a scripting engine
|
|
*
|
|
* Each script engine is strictly single threaded.
|
|
*
|
|
* This class only provides an interface to the underlying scripting engine, and doesn't
|
|
* provide the full environment needed to execute scripts.
|
|
*
|
|
* To execute scripts that have access to the API, use ScriptManager.
|
|
*
|
|
* Exception handling
|
|
* ==================
|
|
*
|
|
* Exceptions are handled in two directions: exceptions thrown by the code executing in the scripting
|
|
* engine, but not captured by the running code are caught by this object and can be inspected.
|
|
*
|
|
* If an exception in the running code occurs, then the exception() signal is emitted. Also,
|
|
* hasUncaughtException() returns true, and uncaughException() returns the ScriptException with the
|
|
* details. Both the signal and uncaughtException() return the same information, and either can
|
|
* be used depending on what best fits the program.
|
|
*
|
|
* To inject an exception into the running script, use raiseException(). This may result in the script
|
|
* not capturing it and an uncaughtException happening as a result.
|
|
*/
|
|
class ScriptEngine : public QObject {
|
|
Q_OBJECT
|
|
public:
|
|
|
|
ScriptEngine(ScriptManager *manager = nullptr) : _manager(manager) {
|
|
|
|
}
|
|
|
|
typedef ScriptValue (*FunctionSignature)(ScriptContext*, ScriptEngine*);
|
|
typedef ScriptValue (*MarshalFunction)(ScriptEngine*, const void*);
|
|
typedef bool (*DemarshalFunction)(const ScriptValue&, QVariant &dest);
|
|
|
|
/**
|
|
* @brief Who owns a given object
|
|
*
|
|
*/
|
|
enum ValueOwnership {
|
|
/**
|
|
* @brief Object is managed by Qt
|
|
*
|
|
*/
|
|
QtOwnership = 0,
|
|
|
|
/**
|
|
* @brief Object is managed by the script
|
|
*
|
|
*/
|
|
ScriptOwnership = 1,
|
|
|
|
/**
|
|
* @brief Ownership is determined automatically.
|
|
* If the object has a parent, it's deemed QtOwnership.
|
|
* If the object has no parent, it's deemed ScriptOwnership.
|
|
*
|
|
*/
|
|
AutoOwnership = 2,
|
|
};
|
|
|
|
/**
|
|
* @brief Which part of an object is exposed to the script
|
|
*
|
|
*/
|
|
enum QObjectWrapOption {
|
|
|
|
/**
|
|
* @brief The script object will not expose child objects as properties.
|
|
*
|
|
*/
|
|
//ExcludeChildObjects = 0x0001,
|
|
|
|
/**
|
|
* @brief The script object will not expose signals and slots inherited from the superclass.
|
|
*
|
|
*/
|
|
ExcludeSuperClassMethods = 0x0002,
|
|
|
|
/**
|
|
* @brief The script object will not expose properties inherited from the superclass.
|
|
*
|
|
*/
|
|
ExcludeSuperClassProperties = 0x0004,
|
|
|
|
/**
|
|
* @brief The script object will not expose the QObject::deleteLater() slot.
|
|
*
|
|
*/
|
|
ExcludeSuperClassContents = ExcludeSuperClassMethods | ExcludeSuperClassProperties,
|
|
|
|
//ExcludeDeleteLater = 0x0010,
|
|
|
|
/**
|
|
* @brief The script object will not expose the QObject's slots.
|
|
*
|
|
*/
|
|
ExcludeSlots = 0x0020,
|
|
|
|
/**
|
|
* @brief Properties that don't already exist in the QObject will be created as dynamic properties of that object, rather than as properties of the script object.
|
|
*
|
|
*/
|
|
AutoCreateDynamicProperties = 0x0100,
|
|
|
|
/**
|
|
* @brief If a wrapper object with the requested configuration already exists, return that object.
|
|
*
|
|
*/
|
|
PreferExistingWrapperObject = 0x0200,
|
|
|
|
/**
|
|
* @brief Don't include methods (signals and slots) when enumerating the object's properties.
|
|
*
|
|
*/
|
|
SkipMethodsInEnumeration = 0x0008,
|
|
};
|
|
Q_DECLARE_FLAGS(QObjectWrapOptions, QObjectWrapOption);
|
|
|
|
public:
|
|
/**
|
|
* @brief Stops the currently running script
|
|
*
|
|
*/
|
|
virtual void abortEvaluation() = 0;
|
|
|
|
/**
|
|
* @brief Clears uncaughtException and related
|
|
*
|
|
*/
|
|
virtual void clearExceptions() = 0;
|
|
|
|
/**
|
|
* @brief Context of the currently running script
|
|
*
|
|
* This allows getting a backtrace, the local variables of the currently running function, etc.
|
|
*
|
|
* @return ScriptContext*
|
|
*/
|
|
virtual ScriptContext* currentContext() const = 0;
|
|
|
|
/**
|
|
* @brief Runs a script
|
|
*
|
|
* This may be called several times during the lifetime of a scripting engine, with the
|
|
* side effects accumulating.
|
|
*
|
|
* @param program Code to run
|
|
* @param fileName Name of the script, for informational purposes
|
|
* @return ScriptValue Return value of the script when it finishes running.
|
|
*/
|
|
virtual ScriptValue evaluate(const QString& program, const QString& fileName = QString()) = 0;
|
|
|
|
/**
|
|
* @brief Evaluates a pre-compiled program
|
|
*
|
|
* @param program Program to evaluaate
|
|
* @return ScriptValue
|
|
*/
|
|
virtual ScriptValue evaluate(const ScriptProgramPointer &program) = 0;
|
|
|
|
|
|
/**
|
|
* @brief Evaluate a script in a separate environment
|
|
*
|
|
* Used for evaluating included scripts
|
|
*
|
|
* @param locals Local variables available to the script
|
|
* @param program Code to run
|
|
* @return ScriptValue
|
|
*/
|
|
virtual ScriptValue evaluateInClosure(const ScriptValue& locals, const ScriptProgramPointer& program) = 0;
|
|
|
|
/**
|
|
* @brief Global object which holds all the functions and variables available everywhere
|
|
*
|
|
* This is a JavaScript concept, https://javascript.info/global-object
|
|
*
|
|
* @note This may not belong in the base class.
|
|
* @return ScriptValue Global Object
|
|
*/
|
|
virtual ScriptValue globalObject() {
|
|
Q_ASSERT(false);
|
|
return ScriptValue();
|
|
}
|
|
|
|
/**
|
|
* @brief Whether the script has an uncaught exception
|
|
*
|
|
* @return true There is an uncaught exception
|
|
* @return false There's no exception
|
|
*/
|
|
virtual bool hasUncaughtException() const = 0;
|
|
|
|
/**
|
|
* @brief Whether a script is currently being evaluated
|
|
*
|
|
* @return true A script is currently being evaluated
|
|
* @return false No script is being evaluated
|
|
*/
|
|
virtual bool isEvaluating() const = 0;
|
|
//virtual ScriptValue lintScript(const QString& sourceCode, const QString& fileName, const int lineNumber = 1) = 0;
|
|
|
|
/**
|
|
* @brief Check a program for syntax errors
|
|
*
|
|
* Returns an object with at least the following properties:
|
|
* * fileName
|
|
* * lineNumber
|
|
* * stack
|
|
* * formatted
|
|
*
|
|
* @param program Program to check
|
|
* @return ScriptValue Result
|
|
*
|
|
* @note It could be a good improvement to redo this to return a struct instead.
|
|
*/
|
|
virtual ScriptValue checkScriptSyntax(ScriptProgramPointer program) = 0;
|
|
|
|
/**
|
|
* @brief Pointer to the ScriptManager that controls this scripting engine
|
|
*
|
|
* @return ScriptManager* ScriptManager
|
|
*/
|
|
ScriptManager* manager() const { return _manager; }
|
|
|
|
virtual ScriptValue newArray(uint length = 0) = 0;
|
|
virtual ScriptValue newArrayBuffer(const QByteArray& message) = 0;
|
|
virtual ScriptValue newFunction(FunctionSignature fun, int length = 0) {
|
|
Q_ASSERT(false);
|
|
return ScriptValue();
|
|
}
|
|
virtual ScriptValue newObject() = 0;
|
|
virtual ScriptProgramPointer newProgram(const QString& sourceCode, const QString& fileName) = 0;
|
|
virtual ScriptValue newQObject(QObject *object, ValueOwnership ownership = QtOwnership, const QObjectWrapOptions &options = QObjectWrapOptions()) = 0;
|
|
virtual ScriptValue newValue(bool value) = 0;
|
|
virtual ScriptValue newValue(int value) = 0;
|
|
virtual ScriptValue newValue(uint value) = 0;
|
|
virtual ScriptValue newValue(double value) = 0;
|
|
virtual ScriptValue newValue(const QString& value) = 0;
|
|
virtual ScriptValue newValue(const QLatin1String& value) = 0;
|
|
virtual ScriptValue newValue(const char* value) = 0;
|
|
virtual ScriptValue newVariant(const QVariant& value) = 0;
|
|
virtual ScriptValue nullValue() = 0;
|
|
|
|
|
|
/**
|
|
* @brief Make a ScriptValue that contains an error
|
|
*
|
|
* This is used to throw an error inside the running script
|
|
*
|
|
* @param other
|
|
* @param type
|
|
* @return ScriptValue ScriptValue containing error
|
|
*/
|
|
virtual ScriptValue makeError(const ScriptValue& other, const QString& type = "Error") = 0;
|
|
|
|
|
|
/**
|
|
* @brief Causes an exception to be raised in the currently executing script
|
|
*
|
|
* @param exception Exception to be thrown in the script
|
|
* @param reason Explanatory text about why the exception happened, for logging
|
|
* @return true Exception was successfully thrown
|
|
* @return false Exception couldn't be thrown because no script is running
|
|
*/
|
|
virtual bool raiseException(const ScriptValue& exception, const QString &reason = QString()) = 0;
|
|
|
|
/**
|
|
* @brief Causes an exception to be raised in the currently executing script
|
|
*
|
|
* @param error Exception to be thrown in the script
|
|
* @param reason Explanatory text about why the exception happened, for logging
|
|
* @return true Exception was successfully thrown
|
|
* @return false Exception couldn't be thrown because no script is running
|
|
*/
|
|
virtual bool raiseException(const QString& error, const QString &reason = QString()) = 0;
|
|
|
|
|
|
virtual void registerEnum(const QString& enumName, QMetaEnum newEnum) = 0;
|
|
virtual void registerFunction(const QString& name, FunctionSignature fun, int numArguments = -1) = 0;
|
|
virtual void registerFunction(const QString& parent, const QString& name, FunctionSignature fun, int numArguments = -1) = 0;
|
|
virtual void registerGetterSetter(const QString& name, FunctionSignature getter, FunctionSignature setter, const QString& parent = QString("")) = 0;
|
|
virtual void registerGlobalObject(const QString& name, QObject* object, ScriptEngine::ValueOwnership = ScriptEngine::QtOwnership) = 0;
|
|
virtual void setDefaultPrototype(int metaTypeId, const ScriptValue& prototype) = 0;
|
|
virtual void setObjectName(const QString& name) = 0;
|
|
virtual bool setProperty(const char* name, const QVariant& value) = 0;
|
|
virtual void setProcessEventsInterval(int interval) = 0;
|
|
virtual QThread* thread() const = 0;
|
|
virtual void setThread(QThread* thread) = 0;
|
|
//Q_INVOKABLE virtual void enterIsolateOnThisThread() = 0;
|
|
virtual ScriptValue undefinedValue() = 0;
|
|
|
|
/**
|
|
* @brief Last uncaught exception, if any.
|
|
*
|
|
* The returned shared pointer is newly allocated by the function,
|
|
* and modifying it has no effect on the internal state of the ScriptEngine.
|
|
*
|
|
* @return std::shared_ptr<ScriptValue> Uncaught exception from the script
|
|
*/
|
|
virtual std::shared_ptr<ScriptException> uncaughtException() const = 0;
|
|
|
|
virtual void updateMemoryCost(const qint64& deltaSize) = 0;
|
|
virtual void requestCollectGarbage() = 0;
|
|
|
|
/**
|
|
* @brief Test the underlying scripting engine
|
|
*
|
|
* This compiles, executes and verifies the execution of a trivial test program
|
|
* to make sure the underlying scripting engine actually works.
|
|
*
|
|
* @deprecated Test function, not part of the API, can be removed
|
|
*/
|
|
virtual void compileTest() = 0;
|
|
virtual QString scriptValueDebugDetails(const ScriptValue &value) = 0;
|
|
virtual QString scriptValueDebugListMembers(const ScriptValue &value) = 0;
|
|
|
|
/**
|
|
* @brief Log the current backtrace
|
|
*
|
|
* Logs the current backtrace for debugging
|
|
*
|
|
* @param title Informative title for the backtrace
|
|
*/
|
|
virtual void logBacktrace(const QString &title) = 0;
|
|
|
|
/**
|
|
* @brief Return memory usage statistics data.
|
|
*
|
|
* Returns memory usage statistics data for debugging.
|
|
*
|
|
* @return ScriptEngineMemoryStatistics Object containing memory usage statistics data.
|
|
*/
|
|
virtual ScriptEngineMemoryStatistics getMemoryUsageStatistics() = 0;
|
|
|
|
/**
|
|
* @brief Start collecting object statistics that can later be reported with dumpHeapObjectStatistics().
|
|
*/
|
|
virtual void startCollectingObjectStatistics() = 0;
|
|
|
|
/**
|
|
* @brief Prints heap statistics to a file. Collecting needs to first be started with dumpHeapObjectStatistics().
|
|
*/
|
|
virtual void dumpHeapObjectStatistics() = 0;
|
|
|
|
/**
|
|
* @brief Starts collecting profiling data.
|
|
*/
|
|
virtual void startProfiling() = 0;
|
|
|
|
/**
|
|
* @brief Stops collecting profiling data and saves it to a CSV file in Logs directory.
|
|
*/
|
|
virtual void stopProfilingAndSave() = 0;
|
|
|
|
/**
|
|
* @brief Cleanup function that disconnects signals connected to script proxies to avoid use-after-delete crash when shutting down script engine.
|
|
*/
|
|
virtual void disconnectSignalProxies() = 0;
|
|
|
|
public:
|
|
// helper to detect and log warnings when other code invokes QScriptEngine/BaseScriptEngine in thread-unsafe ways
|
|
bool IS_THREADSAFE_INVOCATION(const QString& method);
|
|
|
|
public:
|
|
template <typename T>
|
|
inline T fromScriptValue(const ScriptValue& value) {
|
|
return scriptvalue_cast<T>(value);
|
|
}
|
|
|
|
template <typename T>
|
|
inline ScriptValue toScriptValue(const T& value) {
|
|
return scriptValueFromValue(this, value);
|
|
}
|
|
|
|
public: // not for public use, but I don't like how Qt strings this along with private friend functions
|
|
virtual ScriptValue create(int type, const void* ptr) = 0;
|
|
virtual QVariant convert(const ScriptValue& value, int type) = 0;
|
|
virtual void registerCustomType(int type, MarshalFunction mf, DemarshalFunction df) = 0;
|
|
virtual QStringList getCurrentScriptURLs() const = 0;
|
|
virtual void perManagerLoopIterationCleanup() = 0;
|
|
|
|
signals:
|
|
/**
|
|
* @brief The script being run threw an exception
|
|
*
|
|
* @param exception Exception that was thrown
|
|
*/
|
|
void exception(std::shared_ptr<ScriptException> exception);
|
|
|
|
protected:
|
|
virtual ~ScriptEngine() {} // prevent explicit deletion of base class
|
|
|
|
ScriptManager *_manager;
|
|
};
|
|
Q_DECLARE_OPERATORS_FOR_FLAGS(ScriptEngine::QObjectWrapOptions);
|
|
|
|
ScriptEnginePointer newScriptEngine(ScriptManager* manager = nullptr);
|
|
|
|
// Standardized CPS callback helpers (see: http://fredkschott.com/post/2014/03/understanding-error-first-callbacks-in-node-js/)
|
|
// These two helpers allow async JS APIs that use a callback parameter to be more friendly to scripters by accepting thisObject
|
|
// context and adopting a consistent and intuitable callback signature:
|
|
// function callback(err, result) { if (err) { ... } else { /* do stuff with result */ } }
|
|
//
|
|
// To use, first pass the user-specified callback args in the same order used with optionally-scoped Qt signal connections:
|
|
// auto handler = makeScopedHandlerObject(scopeOrCallback, optionalMethodOrName);
|
|
// And then invoke the scoped handler later per CPS conventions:
|
|
// auto result = callScopedHandlerObject(handler, err, result);
|
|
ScriptValue makeScopedHandlerObject(const ScriptValue& scopeOrCallback, const ScriptValue& methodOrName);
|
|
ScriptValue callScopedHandlerObject(const ScriptValue& handler, const ScriptValue& err, const ScriptValue& result);
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
// Inline implementations
|
|
/*
|
|
QThread* ScriptEngine::thread() const {
|
|
QObject* qobject = toQObject();
|
|
if (qobject == nullptr) {
|
|
return nullptr;
|
|
}
|
|
return qobject->thread();
|
|
}
|
|
*/
|
|
|
|
#endif // hifi_ScriptEngine_h
|
|
|
|
/// @}
|