mirror of
https://github.com/JulianGro/overte.git
synced 2025-04-25 22:35:14 +02:00
327 lines
15 KiB
C++
327 lines
15 KiB
C++
//
|
|
// 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 <vector>
|
|
|
|
#include <QtCore/QObject>
|
|
#include <QtCore/QUrl>
|
|
#include <QtCore/QSet>
|
|
#include <QtCore/QWaitCondition>
|
|
#include <QtCore/QStringList>
|
|
|
|
#include <QtScript/QScriptEngine>
|
|
|
|
#include <AnimationCache.h>
|
|
#include <AnimVariant.h>
|
|
#include <AvatarData.h>
|
|
#include <AvatarHashMap.h>
|
|
#include <LimitedNodeList.h>
|
|
#include <EntityItemID.h>
|
|
#include <EntitiesScriptEngineProvider.h>
|
|
#include <EntityScriptUtils.h>
|
|
|
|
#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 "SettingHandle.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 CallbackData {
|
|
public:
|
|
QScriptValue function;
|
|
EntityItemID definingEntityIdentifier;
|
|
QUrl definingSandboxURL;
|
|
};
|
|
|
|
class DeferredLoadEntity {
|
|
public:
|
|
EntityItemID entityID;
|
|
QString entityScript;
|
|
//bool forceRedownload;
|
|
};
|
|
|
|
typedef QList<CallbackData> CallbackList;
|
|
typedef QHash<QString, CallbackList> 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") };
|
|
};
|
|
|
|
class ScriptEngine : public BaseScriptEngine, public EntitiesScriptEngineProvider {
|
|
Q_OBJECT
|
|
Q_PROPERTY(QString context READ getContext)
|
|
public:
|
|
|
|
enum Context {
|
|
CLIENT_SCRIPT,
|
|
ENTITY_CLIENT_SCRIPT,
|
|
ENTITY_SERVER_SCRIPT,
|
|
AGENT_SCRIPT
|
|
};
|
|
|
|
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;
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
// 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
|
|
|
|
/// registers a global object by name
|
|
Q_INVOKABLE void registerGlobalObject(const QString& name, QObject* object);
|
|
|
|
/// registers a global getter/setter
|
|
Q_INVOKABLE void registerGetterSetter(const QString& name, QScriptEngine::FunctionSignature getter,
|
|
QScriptEngine::FunctionSignature setter, const QString& parent = QString(""));
|
|
|
|
/// register a global function
|
|
Q_INVOKABLE void registerFunction(const QString& name, QScriptEngine::FunctionSignature fun, int 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);
|
|
|
|
/// registers a global object by name
|
|
Q_INVOKABLE void registerValue(const QString& valueName, QScriptValue value);
|
|
|
|
/// 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
|
|
|
|
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);
|
|
|
|
Q_INVOKABLE QString getContext() const;
|
|
Q_INVOKABLE bool isClientScript() const { return _context == CLIENT_SCRIPT; }
|
|
Q_INVOKABLE bool isEntityClientScript() const { return _context == ENTITY_CLIENT_SCRIPT; }
|
|
Q_INVOKABLE bool isEntityServerScript() const { return _context == ENTITY_SERVER_SCRIPT; }
|
|
Q_INVOKABLE bool isAgentScript() const { return _context == AGENT_SCRIPT; }
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
// NOTE - these are intended to be public interfaces available to scripts
|
|
Q_INVOKABLE void addEventHandler(const EntityItemID& entityID, const QString& eventName, QScriptValue handler);
|
|
Q_INVOKABLE void removeEventHandler(const EntityItemID& entityID, const QString& eventName, QScriptValue handler);
|
|
|
|
Q_INVOKABLE void load(const QString& loadfile);
|
|
Q_INVOKABLE void include(const QStringList& includeFiles, QScriptValue callback = QScriptValue());
|
|
Q_INVOKABLE void include(const QString& includeFile, QScriptValue callback = QScriptValue());
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
// MODULE related methods
|
|
Q_INVOKABLE QScriptValue require(const QString& moduleId);
|
|
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);
|
|
|
|
Q_INVOKABLE QObject* setInterval(const QScriptValue& function, int intervalMS);
|
|
Q_INVOKABLE QObject* setTimeout(const QScriptValue& function, int timeoutMS);
|
|
Q_INVOKABLE void clearInterval(QObject* timer) { stopTimer(reinterpret_cast<QTimer*>(timer)); }
|
|
Q_INVOKABLE void clearTimeout(QObject* timer) { stopTimer(reinterpret_cast<QTimer*>(timer)); }
|
|
|
|
Q_INVOKABLE void print(const QString& message);
|
|
Q_INVOKABLE QUrl resolvePath(const QString& path) const;
|
|
Q_INVOKABLE QUrl resourcesPath() const;
|
|
|
|
// Entity Script Related methods
|
|
Q_INVOKABLE bool isEntityScriptRunning(const EntityItemID& entityID) {
|
|
return _entityScripts.contains(entityID) && _entityScripts[entityID].status == EntityScriptStatus::RUNNING;
|
|
}
|
|
QVariant cloneEntityScriptDetails(const EntityItemID& entityID);
|
|
QFuture<QVariant> getLocalEntityScriptDetails(const EntityItemID& entityID) override;
|
|
Q_INVOKABLE void loadEntityScript(const EntityItemID& entityID, const QString& entityScript, bool forceRedownload);
|
|
Q_INVOKABLE void unloadEntityScript(const EntityItemID& entityID, bool shouldRemoveFromMap = false); // will call unload method
|
|
Q_INVOKABLE void unloadAllEntityScripts();
|
|
Q_INVOKABLE void callEntityScriptMethod(const EntityItemID& entityID, const QString& methodName,
|
|
const QStringList& params = QStringList()) override;
|
|
Q_INVOKABLE void callEntityScriptMethod(const EntityItemID& entityID, const QString& methodName, const PointerEvent& event);
|
|
Q_INVOKABLE void callEntityScriptMethod(const EntityItemID& entityID, const QString& methodName, const EntityItemID& otherID, const Collision& collision);
|
|
|
|
Q_INVOKABLE void requestGarbageCollection() { collectGarbage(); }
|
|
|
|
Q_INVOKABLE QUuid generateUUID() { return QUuid::createUuid(); }
|
|
|
|
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; }
|
|
|
|
// NOTE - this is used by the TypedArray implementation. we need to review this for thread safety
|
|
ArrayBufferClass* getArrayBufferClass() { return _arrayBufferClass; }
|
|
|
|
void setEmitScriptUpdatesFunction(std::function<bool()> func) { _emitScriptUpdates = func; }
|
|
|
|
void scriptErrorMessage(const QString& message);
|
|
void scriptWarningMessage(const QString& message);
|
|
void scriptInfoMessage(const QString& message);
|
|
void scriptPrintedMessage(const QString& message);
|
|
|
|
int getNumRunningEntityScripts() const;
|
|
bool getEntityScriptDetails(const EntityItemID& entityID, EntityScriptDetails &details) const;
|
|
|
|
public slots:
|
|
void callAnimationStateHandler(QScriptValue callback, AnimVariantMap parameters, QStringList names, bool useNames, AnimVariantResultHandler resultHandler);
|
|
void updateMemoryCost(const qint64&);
|
|
|
|
signals:
|
|
void scriptLoaded(const QString& scriptFilename);
|
|
void errorLoadingScript(const QString& scriptFilename);
|
|
void update(float deltaTime);
|
|
void scriptEnding();
|
|
void finished(const QString& fileNameString, ScriptEngine* engine);
|
|
void cleanupMenuItem(const QString& menuItemString);
|
|
void printedMessage(const QString& message, const QString& scriptName);
|
|
void errorMessage(const QString& message, const QString& scriptName);
|
|
void warningMessage(const QString& message, const QString& scriptName);
|
|
void infoMessage(const QString& message, const QString& scriptName);
|
|
void runningStateChanged();
|
|
void loadScript(const QString& scriptName, bool isUserLoaded);
|
|
void reloadScript(const QString& scriptName, bool isUserLoaded);
|
|
void doneRunning();
|
|
|
|
// 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();
|
|
Q_INVOKABLE void executeOnScriptThread(std::function<void()> function, const Qt::ConnectionType& type = Qt::QueuedConnection );
|
|
// 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; }
|
|
void processDeferredEntityLoads(const QString& entityScript, const EntityItemID& leaderID);
|
|
|
|
QObject* setupTimerWithInterval(const QScriptValue& function, int intervalMS, bool isSingleShot);
|
|
void stopTimer(QTimer* timer);
|
|
|
|
QHash<EntityItemID, RegisteredEventHandlers> _registeredHandlers;
|
|
void forwardHandlerCall(const EntityItemID& entityID, const QString& eventName, QScriptValueList eventHanderArgs);
|
|
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<void()> operation);
|
|
void callWithEnvironment(const EntityItemID& entityID, const QUrl& sandboxURL, QScriptValue function, QScriptValue thisObject, QScriptValueList args);
|
|
|
|
Context _context;
|
|
QString _scriptContents;
|
|
QString _parentURL;
|
|
std::atomic<bool> _isFinished { false };
|
|
std::atomic<bool> _isRunning { false };
|
|
std::atomic<bool> _isStopping { false };
|
|
bool _isInitialized { false };
|
|
QHash<QTimer*, CallbackData> _timerFunctionMap;
|
|
QSet<QUrl> _includedURLs;
|
|
QHash<EntityItemID, EntityScriptDetails> _entityScripts;
|
|
QHash<QString, EntityItemID> _occupiedScriptURLs;
|
|
QList<DeferredLoadEntity> _deferredEntityLoads;
|
|
|
|
bool _isThreaded { false };
|
|
QScriptEngineDebugger* _debugger { nullptr };
|
|
bool _debuggable { false };
|
|
qint64 _lastUpdate;
|
|
|
|
QString _fileNameString;
|
|
Quat _quatLibrary;
|
|
Vec3 _vec3Library;
|
|
Mat4 _mat4Library;
|
|
ScriptUUID _uuidLibrary;
|
|
std::atomic<bool> _isUserLoaded { false };
|
|
bool _isReloading { false };
|
|
|
|
ArrayBufferClass* _arrayBufferClass;
|
|
|
|
AssetScriptingInterface _assetScriptingInterface{ this };
|
|
|
|
std::function<bool()> _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<bool> _enableExtendedJSExceptions { _SETTINGS_ENABLE_EXTENDED_EXCEPTIONS, true };
|
|
};
|
|
|
|
#endif // hifi_ScriptEngine_h
|