mirror of
https://github.com/overte-org/overte.git
synced 2025-08-05 15:39:57 +02:00
Merge pull request #7236 from howard-stearns/entity-script-safety
entity script safety
This commit is contained in:
commit
0d48803761
2 changed files with 104 additions and 21 deletions
|
@ -485,10 +485,10 @@ void ScriptEngine::removeEventHandler(const EntityItemID& entityID, const QStrin
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
RegisteredEventHandlers& handlersOnEntity = _registeredHandlers[entityID];
|
RegisteredEventHandlers& handlersOnEntity = _registeredHandlers[entityID];
|
||||||
QScriptValueList& handlersForEvent = handlersOnEntity[eventName];
|
CallbackList& handlersForEvent = handlersOnEntity[eventName];
|
||||||
// QScriptValue does not have operator==(), so we can't use QList::removeOne and friends. So iterate.
|
// QScriptValue does not have operator==(), so we can't use QList::removeOne and friends. So iterate.
|
||||||
for (int i = 0; i < handlersForEvent.count(); ++i) {
|
for (int i = 0; i < handlersForEvent.count(); ++i) {
|
||||||
if (handlersForEvent[i].equals(handler)) {
|
if (handlersForEvent[i].function.equals(handler)) {
|
||||||
handlersForEvent.removeAt(i);
|
handlersForEvent.removeAt(i);
|
||||||
return; // Design choice: since comparison is relatively expensive, just remove the first matching handler.
|
return; // Design choice: since comparison is relatively expensive, just remove the first matching handler.
|
||||||
}
|
}
|
||||||
|
@ -516,6 +516,13 @@ void ScriptEngine::addEventHandler(const EntityItemID& entityID, const QString&
|
||||||
// Connect up ALL the handlers to the global entities object's signals.
|
// Connect up ALL the handlers to the global entities object's signals.
|
||||||
// (We could go signal by signal, or even handler by handler, but I don't think the efficiency is worth the complexity.)
|
// (We could go signal by signal, or even handler by handler, but I don't think the efficiency is worth the complexity.)
|
||||||
auto entities = DependencyManager::get<EntityScriptingInterface>();
|
auto entities = DependencyManager::get<EntityScriptingInterface>();
|
||||||
|
// Bug? These handlers are deleted when entityID is deleted, which is nice.
|
||||||
|
// But if they are created by an entity script on a different entity, should they also be deleted when the entity script unloads?
|
||||||
|
// E.g., suppose a bow has an entity script that causes arrows to be created with a potential lifetime greater than the bow,
|
||||||
|
// and that the entity script adds (e.g., collision) handlers to the arrows. Should those handlers fire if the bow is unloaded?
|
||||||
|
// Also, what about when the entity script is REloaded?
|
||||||
|
// For now, we are leaving them around. Changing that would require some non-trivial digging around to find the
|
||||||
|
// handlers that were added while a given currentEntityIdentifier was in place. I don't think this is dangerous. Just perhaps unexpected. -HRS
|
||||||
connect(entities.data(), &EntityScriptingInterface::deletingEntity, this, [this](const EntityItemID& entityID) {
|
connect(entities.data(), &EntityScriptingInterface::deletingEntity, this, [this](const EntityItemID& entityID) {
|
||||||
_registeredHandlers.remove(entityID);
|
_registeredHandlers.remove(entityID);
|
||||||
});
|
});
|
||||||
|
@ -563,8 +570,9 @@ void ScriptEngine::addEventHandler(const EntityItemID& entityID, const QString&
|
||||||
if (!_registeredHandlers.contains(entityID)) {
|
if (!_registeredHandlers.contains(entityID)) {
|
||||||
_registeredHandlers[entityID] = RegisteredEventHandlers();
|
_registeredHandlers[entityID] = RegisteredEventHandlers();
|
||||||
}
|
}
|
||||||
QScriptValueList& handlersForEvent = _registeredHandlers[entityID][eventName];
|
CallbackList& handlersForEvent = _registeredHandlers[entityID][eventName];
|
||||||
handlersForEvent << handler; // Note that the same handler can be added many times. See removeEntityEventHandler().
|
CallbackData handlerData = {handler, currentEntityIdentifier};
|
||||||
|
handlersForEvent << handlerData; // Note that the same handler can be added many times. See removeEntityEventHandler().
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -705,13 +713,30 @@ void ScriptEngine::run() {
|
||||||
// NOTE: This is private because it must be called on the same thread that created the timers, which is why
|
// NOTE: This is private because it must be called on the same thread that created the timers, which is why
|
||||||
// we want to only call it in our own run "shutdown" processing.
|
// we want to only call it in our own run "shutdown" processing.
|
||||||
void ScriptEngine::stopAllTimers() {
|
void ScriptEngine::stopAllTimers() {
|
||||||
QMutableHashIterator<QTimer*, QScriptValue> i(_timerFunctionMap);
|
QMutableHashIterator<QTimer*, CallbackData> i(_timerFunctionMap);
|
||||||
while (i.hasNext()) {
|
while (i.hasNext()) {
|
||||||
i.next();
|
i.next();
|
||||||
QTimer* timer = i.key();
|
QTimer* timer = i.key();
|
||||||
stopTimer(timer);
|
stopTimer(timer);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
void ScriptEngine::stopAllTimersForEntityScript(const EntityItemID& entityID) {
|
||||||
|
// We could maintain a separate map of entityID => QTimer, but someone will have to prove to me that it's worth the complexity. -HRS
|
||||||
|
QVector<QTimer*> toDelete;
|
||||||
|
QMutableHashIterator<QTimer*, CallbackData> i(_timerFunctionMap);
|
||||||
|
while (i.hasNext()) {
|
||||||
|
i.next();
|
||||||
|
if (i.value().definingEntityIdentifier != entityID) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
QTimer* timer = i.key();
|
||||||
|
toDelete << timer; // don't delete while we're iterating. save it.
|
||||||
|
}
|
||||||
|
for (auto timer:toDelete) { // now reap 'em
|
||||||
|
stopTimer(timer);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
void ScriptEngine::stop() {
|
void ScriptEngine::stop() {
|
||||||
if (!_isFinished) {
|
if (!_isFinished) {
|
||||||
|
@ -743,13 +768,14 @@ void ScriptEngine::callAnimationStateHandler(QScriptValue callback, AnimVariantM
|
||||||
QScriptValue javascriptParameters = parameters.animVariantMapToScriptValue(this, names, useNames);
|
QScriptValue javascriptParameters = parameters.animVariantMapToScriptValue(this, names, useNames);
|
||||||
QScriptValueList callingArguments;
|
QScriptValueList callingArguments;
|
||||||
callingArguments << javascriptParameters;
|
callingArguments << javascriptParameters;
|
||||||
|
assert(currentEntityIdentifier.isInvalidID()); // No animation state handlers from entity scripts.
|
||||||
QScriptValue result = callback.call(QScriptValue(), callingArguments);
|
QScriptValue result = callback.call(QScriptValue(), callingArguments);
|
||||||
resultHandler(result);
|
resultHandler(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
void ScriptEngine::timerFired() {
|
void ScriptEngine::timerFired() {
|
||||||
QTimer* callingTimer = reinterpret_cast<QTimer*>(sender());
|
QTimer* callingTimer = reinterpret_cast<QTimer*>(sender());
|
||||||
QScriptValue timerFunction = _timerFunctionMap.value(callingTimer);
|
CallbackData timerData = _timerFunctionMap.value(callingTimer);
|
||||||
|
|
||||||
if (!callingTimer->isActive()) {
|
if (!callingTimer->isActive()) {
|
||||||
// this timer is done, we can kill it
|
// this timer is done, we can kill it
|
||||||
|
@ -758,11 +784,12 @@ void ScriptEngine::timerFired() {
|
||||||
}
|
}
|
||||||
|
|
||||||
// call the associated JS function, if it exists
|
// call the associated JS function, if it exists
|
||||||
if (timerFunction.isValid()) {
|
if (timerData.function.isValid()) {
|
||||||
timerFunction.call();
|
callWithEnvironment(timerData.definingEntityIdentifier, timerData.function, timerData.function, QScriptValueList());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
QObject* ScriptEngine::setupTimerWithInterval(const QScriptValue& function, int intervalMS, bool isSingleShot) {
|
QObject* ScriptEngine::setupTimerWithInterval(const QScriptValue& function, int intervalMS, bool isSingleShot) {
|
||||||
// create the timer, add it to the map, and start it
|
// create the timer, add it to the map, and start it
|
||||||
QTimer* newTimer = new QTimer(this);
|
QTimer* newTimer = new QTimer(this);
|
||||||
|
@ -773,7 +800,8 @@ QObject* ScriptEngine::setupTimerWithInterval(const QScriptValue& function, int
|
||||||
// make sure the timer stops when the script does
|
// make sure the timer stops when the script does
|
||||||
connect(this, &ScriptEngine::scriptEnding, newTimer, &QTimer::stop);
|
connect(this, &ScriptEngine::scriptEnding, newTimer, &QTimer::stop);
|
||||||
|
|
||||||
_timerFunctionMap.insert(newTimer, function);
|
CallbackData timerData = {function, currentEntityIdentifier};
|
||||||
|
_timerFunctionMap.insert(newTimer, timerData);
|
||||||
|
|
||||||
newTimer->start(intervalMS);
|
newTimer->start(intervalMS);
|
||||||
return newTimer;
|
return newTimer;
|
||||||
|
@ -859,6 +887,7 @@ void ScriptEngine::include(const QStringList& includeFiles, QScriptValue callbac
|
||||||
}
|
}
|
||||||
|
|
||||||
BatchLoader* loader = new BatchLoader(urls);
|
BatchLoader* loader = new BatchLoader(urls);
|
||||||
|
EntityItemID capturedEntityIdentifier = currentEntityIdentifier;
|
||||||
|
|
||||||
auto evaluateScripts = [=](const QMap<QUrl, QString>& data) {
|
auto evaluateScripts = [=](const QMap<QUrl, QString>& data) {
|
||||||
auto parentURL = _parentURL;
|
auto parentURL = _parentURL;
|
||||||
|
@ -870,13 +899,16 @@ void ScriptEngine::include(const QStringList& includeFiles, QScriptValue callbac
|
||||||
// Set the parent url so that path resolution will be relative
|
// Set the parent url so that path resolution will be relative
|
||||||
// to this script's url during its initial evaluation
|
// to this script's url during its initial evaluation
|
||||||
_parentURL = url.toString();
|
_parentURL = url.toString();
|
||||||
QScriptValue result = evaluate(contents, url.toString());
|
auto operation = [&]() {
|
||||||
|
evaluate(contents, url.toString());
|
||||||
|
};
|
||||||
|
doWithEnvironment(capturedEntityIdentifier, operation);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_parentURL = parentURL;
|
_parentURL = parentURL;
|
||||||
|
|
||||||
if (callback.isFunction()) {
|
if (callback.isFunction()) {
|
||||||
QScriptValue(callback).call();
|
callWithEnvironment(capturedEntityIdentifier, QScriptValue(callback), QScriptValue(), QScriptValueList());
|
||||||
}
|
}
|
||||||
|
|
||||||
loader->deleteLater();
|
loader->deleteLater();
|
||||||
|
@ -917,6 +949,11 @@ void ScriptEngine::load(const QString& loadFile) {
|
||||||
<< "loadFile:" << loadFile << "parent script:" << getFilename();
|
<< "loadFile:" << loadFile << "parent script:" << getFilename();
|
||||||
return; // bail early
|
return; // bail early
|
||||||
}
|
}
|
||||||
|
if (!currentEntityIdentifier.isInvalidID()) {
|
||||||
|
qCWarning(scriptengine) << "Script.load() from entity script is ignored... "
|
||||||
|
<< "loadFile:" << loadFile << "parent script:" << getFilename();
|
||||||
|
return; // bail early
|
||||||
|
}
|
||||||
|
|
||||||
QUrl url = resolvePath(loadFile);
|
QUrl url = resolvePath(loadFile);
|
||||||
if (_isReloading) {
|
if (_isReloading) {
|
||||||
|
@ -933,7 +970,7 @@ void ScriptEngine::load(const QString& loadFile) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Look up the handler associated with eventName and entityID. If found, evalute the argGenerator thunk and call the handler with those args
|
// Look up the handler associated with eventName and entityID. If found, evalute the argGenerator thunk and call the handler with those args
|
||||||
void ScriptEngine::forwardHandlerCall(const EntityItemID& entityID, const QString& eventName, QScriptValueList eventHanderArgs) {
|
void ScriptEngine::forwardHandlerCall(const EntityItemID& entityID, const QString& eventName, QScriptValueList eventHandlerArgs) {
|
||||||
if (QThread::currentThread() != thread()) {
|
if (QThread::currentThread() != thread()) {
|
||||||
qDebug() << "*** ERROR *** ScriptEngine::forwardHandlerCall() called on wrong thread [" << QThread::currentThread() << "], invoking on correct thread [" << thread() << "]";
|
qDebug() << "*** ERROR *** ScriptEngine::forwardHandlerCall() called on wrong thread [" << QThread::currentThread() << "], invoking on correct thread [" << thread() << "]";
|
||||||
assert(false);
|
assert(false);
|
||||||
|
@ -946,10 +983,13 @@ void ScriptEngine::forwardHandlerCall(const EntityItemID& entityID, const QStrin
|
||||||
if (!handlersOnEntity.contains(eventName)) {
|
if (!handlersOnEntity.contains(eventName)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
QScriptValueList handlersForEvent = handlersOnEntity[eventName];
|
CallbackList handlersForEvent = handlersOnEntity[eventName];
|
||||||
if (!handlersForEvent.isEmpty()) {
|
if (!handlersForEvent.isEmpty()) {
|
||||||
for (int i = 0; i < handlersForEvent.count(); ++i) {
|
for (int i = 0; i < handlersForEvent.count(); ++i) {
|
||||||
handlersForEvent[i].call(QScriptValue(), eventHanderArgs);
|
// handlersForEvent[i] can tonain many handlers that may have each been added by different interface or entity scripts,
|
||||||
|
// and the entity scripts may be for entities other than the one this is a handler for.
|
||||||
|
// Fortunately, the definingEntityIdentifier captured the entity script id (if any) when the handler was added.
|
||||||
|
callWithEnvironment(handlersForEvent[i].definingEntityIdentifier, handlersForEvent[i].function, QScriptValue(), eventHandlerArgs);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1055,9 +1095,13 @@ void ScriptEngine::entityScriptContentAvailable(const EntityItemID& entityID, co
|
||||||
QString file = QUrl(scriptOrURL).toLocalFile();
|
QString file = QUrl(scriptOrURL).toLocalFile();
|
||||||
lastModified = (quint64)QFileInfo(file).lastModified().toMSecsSinceEpoch();
|
lastModified = (quint64)QFileInfo(file).lastModified().toMSecsSinceEpoch();
|
||||||
}
|
}
|
||||||
|
QScriptValue entityScriptConstructor, entityScriptObject;
|
||||||
|
auto initialization = [&]{
|
||||||
|
entityScriptConstructor = evaluate(contents, fileName);
|
||||||
|
entityScriptObject = entityScriptConstructor.construct();
|
||||||
|
};
|
||||||
|
doWithEnvironment(entityID, initialization);
|
||||||
|
|
||||||
QScriptValue entityScriptConstructor = evaluate(contents, fileName);
|
|
||||||
QScriptValue entityScriptObject = entityScriptConstructor.construct();
|
|
||||||
EntityScriptDetails newDetails = { scriptOrURL, entityScriptObject, lastModified };
|
EntityScriptDetails newDetails = { scriptOrURL, entityScriptObject, lastModified };
|
||||||
_entityScripts[entityID] = newDetails;
|
_entityScripts[entityID] = newDetails;
|
||||||
if (isURL) {
|
if (isURL) {
|
||||||
|
@ -1087,6 +1131,7 @@ void ScriptEngine::unloadEntityScript(const EntityItemID& entityID) {
|
||||||
if (_entityScripts.contains(entityID)) {
|
if (_entityScripts.contains(entityID)) {
|
||||||
callEntityScriptMethod(entityID, "unload");
|
callEntityScriptMethod(entityID, "unload");
|
||||||
_entityScripts.remove(entityID);
|
_entityScripts.remove(entityID);
|
||||||
|
stopAllTimersForEntityScript(entityID);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1142,6 +1187,32 @@ void ScriptEngine::refreshFileScript(const EntityItemID& entityID) {
|
||||||
recurseGuard = false;
|
recurseGuard = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Execute operation in the appropriate context for (the possibly empty) entityID.
|
||||||
|
// Even if entityID is supplied as currentEntityIdentifier, this still documents the source
|
||||||
|
// of the code being executed (e.g., if we ever sandbox different entity scripts, or provide different
|
||||||
|
// global values for different entity scripts).
|
||||||
|
void ScriptEngine::doWithEnvironment(const EntityItemID& entityID, std::function<void()> operation) {
|
||||||
|
EntityItemID oldIdentifier = currentEntityIdentifier;
|
||||||
|
currentEntityIdentifier = entityID;
|
||||||
|
|
||||||
|
#if DEBUG_CURRENT_ENTITY
|
||||||
|
QScriptValue oldData = this->globalObject().property("debugEntityID");
|
||||||
|
this->globalObject().setProperty("debugEntityID", entityID.toScriptValue(this)); // Make the entityID available to javascript as a global.
|
||||||
|
operation();
|
||||||
|
this->globalObject().setProperty("debugEntityID", oldData);
|
||||||
|
#else
|
||||||
|
operation();
|
||||||
|
#endif
|
||||||
|
|
||||||
|
currentEntityIdentifier = oldIdentifier;
|
||||||
|
}
|
||||||
|
void ScriptEngine::callWithEnvironment(const EntityItemID& entityID, QScriptValue function, QScriptValue thisObject, QScriptValueList args) {
|
||||||
|
auto operation = [&]() {
|
||||||
|
function.call(thisObject, args);
|
||||||
|
};
|
||||||
|
doWithEnvironment(entityID, operation);
|
||||||
|
}
|
||||||
|
|
||||||
void ScriptEngine::callEntityScriptMethod(const EntityItemID& entityID, const QString& methodName, const QStringList& params) {
|
void ScriptEngine::callEntityScriptMethod(const EntityItemID& entityID, const QString& methodName, const QStringList& params) {
|
||||||
if (QThread::currentThread() != thread()) {
|
if (QThread::currentThread() != thread()) {
|
||||||
#ifdef THREAD_DEBUGGING
|
#ifdef THREAD_DEBUGGING
|
||||||
|
@ -1168,7 +1239,7 @@ void ScriptEngine::callEntityScriptMethod(const EntityItemID& entityID, const QS
|
||||||
QScriptValueList args;
|
QScriptValueList args;
|
||||||
args << entityID.toScriptValue(this);
|
args << entityID.toScriptValue(this);
|
||||||
args << qScriptValueFromSequence(this, params);
|
args << qScriptValueFromSequence(this, params);
|
||||||
entityScript.property(methodName).call(entityScript, args);
|
callWithEnvironment(entityID, entityScript.property(methodName), entityScript, args);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1200,7 +1271,7 @@ void ScriptEngine::callEntityScriptMethod(const EntityItemID& entityID, const QS
|
||||||
QScriptValueList args;
|
QScriptValueList args;
|
||||||
args << entityID.toScriptValue(this);
|
args << entityID.toScriptValue(this);
|
||||||
args << event.toScriptValue(this);
|
args << event.toScriptValue(this);
|
||||||
entityScript.property(methodName).call(entityScript, args);
|
callWithEnvironment(entityID, entityScript.property(methodName), entityScript, args);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1234,7 +1305,7 @@ void ScriptEngine::callEntityScriptMethod(const EntityItemID& entityID, const QS
|
||||||
args << entityID.toScriptValue(this);
|
args << entityID.toScriptValue(this);
|
||||||
args << otherID.toScriptValue(this);
|
args << otherID.toScriptValue(this);
|
||||||
args << collisionToScriptValue(this, collision);
|
args << collisionToScriptValue(this, collision);
|
||||||
entityScript.property(methodName).call(entityScript, args);
|
callWithEnvironment(entityID, entityScript.property(methodName), entityScript, args);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -42,7 +42,14 @@ const QString NO_SCRIPT("");
|
||||||
|
|
||||||
const unsigned int SCRIPT_DATA_CALLBACK_USECS = floor(((1.0f / 60.0f) * 1000 * 1000) + 0.5f);
|
const unsigned int SCRIPT_DATA_CALLBACK_USECS = floor(((1.0f / 60.0f) * 1000 * 1000) + 0.5f);
|
||||||
|
|
||||||
typedef QHash<QString, QScriptValueList> RegisteredEventHandlers;
|
class CallbackData {
|
||||||
|
public:
|
||||||
|
QScriptValue function;
|
||||||
|
EntityItemID definingEntityIdentifier;
|
||||||
|
};
|
||||||
|
|
||||||
|
typedef QList<CallbackData> CallbackList;
|
||||||
|
typedef QHash<QString, CallbackList> RegisteredEventHandlers;
|
||||||
|
|
||||||
class EntityScriptDetails {
|
class EntityScriptDetails {
|
||||||
public:
|
public:
|
||||||
|
@ -169,7 +176,7 @@ protected:
|
||||||
std::atomic<bool> _isRunning { false };
|
std::atomic<bool> _isRunning { false };
|
||||||
int _evaluatesPending { 0 };
|
int _evaluatesPending { 0 };
|
||||||
bool _isInitialized { false };
|
bool _isInitialized { false };
|
||||||
QHash<QTimer*, QScriptValue> _timerFunctionMap;
|
QHash<QTimer*, CallbackData> _timerFunctionMap;
|
||||||
QSet<QUrl> _includedURLs;
|
QSet<QUrl> _includedURLs;
|
||||||
bool _wantSignals { true };
|
bool _wantSignals { true };
|
||||||
QHash<EntityItemID, EntityScriptDetails> _entityScripts;
|
QHash<EntityItemID, EntityScriptDetails> _entityScripts;
|
||||||
|
@ -181,6 +188,7 @@ protected:
|
||||||
bool evaluatePending() const { return _evaluatesPending > 0; }
|
bool evaluatePending() const { return _evaluatesPending > 0; }
|
||||||
void timerFired();
|
void timerFired();
|
||||||
void stopAllTimers();
|
void stopAllTimers();
|
||||||
|
void stopAllTimersForEntityScript(const EntityItemID& entityID);
|
||||||
void refreshFileScript(const EntityItemID& entityID);
|
void refreshFileScript(const EntityItemID& entityID);
|
||||||
|
|
||||||
void setParentURL(const QString& parentURL) { _parentURL = parentURL; }
|
void setParentURL(const QString& parentURL) { _parentURL = parentURL; }
|
||||||
|
@ -203,6 +211,10 @@ protected:
|
||||||
void forwardHandlerCall(const EntityItemID& entityID, const QString& eventName, QScriptValueList eventHanderArgs);
|
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);
|
Q_INVOKABLE void entityScriptContentAvailable(const EntityItemID& entityID, const QString& scriptOrURL, const QString& contents, bool isURL, bool success);
|
||||||
|
|
||||||
|
EntityItemID currentEntityIdentifier {}; // Contains the defining entity script entity id during execution, if any. Empty for interface script execution.
|
||||||
|
void doWithEnvironment(const EntityItemID& entityID, std::function<void()> operation);
|
||||||
|
void callWithEnvironment(const EntityItemID& entityID, QScriptValue function, QScriptValue thisObject, QScriptValueList args);
|
||||||
|
|
||||||
friend class ScriptEngines;
|
friend class ScriptEngines;
|
||||||
static std::atomic<bool> _stoppingAllScripts;
|
static std::atomic<bool> _stoppingAllScripts;
|
||||||
};
|
};
|
||||||
|
|
Loading…
Reference in a new issue