diff --git a/libraries/entities-renderer/src/EntityTreeRenderer.cpp b/libraries/entities-renderer/src/EntityTreeRenderer.cpp index 9979202345..73b1c7d1e2 100644 --- a/libraries/entities-renderer/src/EntityTreeRenderer.cpp +++ b/libraries/entities-renderer/src/EntityTreeRenderer.cpp @@ -115,7 +115,6 @@ void EntityTreeRenderer::init() { connect(entityTree, &EntityTree::deletingEntity, this, &EntityTreeRenderer::deletingEntity); connect(entityTree, &EntityTree::addingEntity, this, &EntityTreeRenderer::addingEntity); connect(entityTree, &EntityTree::entityScriptChanging, this, &EntityTreeRenderer::entitySciptChanging); - connect(entityTree, &EntityTree::changingEntityID, this, &EntityTreeRenderer::changingEntityID); } void EntityTreeRenderer::shutdown() { @@ -875,6 +874,7 @@ void EntityTreeRenderer::connectSignalsToSlots(EntityScriptingInterface* entityS connect(this, &EntityTreeRenderer::enterEntity, entityScriptingInterface, &EntityScriptingInterface::enterEntity); connect(this, &EntityTreeRenderer::leaveEntity, entityScriptingInterface, &EntityScriptingInterface::leaveEntity); + connect(this, &EntityTreeRenderer::collisionWithEntity, entityScriptingInterface, &EntityScriptingInterface::collisionWithEntity); } QScriptValueList EntityTreeRenderer::createMouseEventArgs(const EntityItemID& entityID, QMouseEvent* event, unsigned int deviceID) { @@ -1103,14 +1103,6 @@ void EntityTreeRenderer::checkAndCallUnload(const EntityItemID& entityID) { } -void EntityTreeRenderer::changingEntityID(const EntityItemID& oldEntityID, const EntityItemID& newEntityID) { - if (_entityScripts.contains(oldEntityID)) { - EntityScriptDetails details = _entityScripts[oldEntityID]; - _entityScripts.remove(oldEntityID); - _entityScripts[newEntityID] = details; - } -} - void EntityTreeRenderer::playEntityCollisionSound(const QUuid& myNodeID, EntityTree* entityTree, const EntityItemID& id, const Collision& collision) { EntityItemPointer entity = entityTree->findEntityByEntityItemID(id); if (!entity) { @@ -1189,6 +1181,7 @@ void EntityTreeRenderer::entityCollisionWithEntity(const EntityItemID& idA, cons playEntityCollisionSound(myNodeID, entityTree, idB, collision); // And now the entity scripts + emit collisionWithEntity(idA, idB, collision); QScriptValue entityScriptA = loadEntityScript(idA); if (entityScriptA.property("collisionWithEntity").isValid()) { QScriptValueList args; @@ -1198,6 +1191,7 @@ void EntityTreeRenderer::entityCollisionWithEntity(const EntityItemID& idA, cons entityScriptA.property("collisionWithEntity").call(entityScriptA, args); } + emit collisionWithEntity(idB, idA, collision); QScriptValue entityScriptB = loadEntityScript(idB); if (entityScriptB.property("collisionWithEntity").isValid()) { QScriptValueList args; diff --git a/libraries/entities-renderer/src/EntityTreeRenderer.h b/libraries/entities-renderer/src/EntityTreeRenderer.h index ca0e7f955a..725f304e8b 100644 --- a/libraries/entities-renderer/src/EntityTreeRenderer.h +++ b/libraries/entities-renderer/src/EntityTreeRenderer.h @@ -107,11 +107,11 @@ signals: void enterEntity(const EntityItemID& entityItemID); void leaveEntity(const EntityItemID& entityItemID); + void collisionWithEntity(const EntityItemID& idA, const EntityItemID& idB, const Collision& collision); public slots: void addingEntity(const EntityItemID& entityID); void deletingEntity(const EntityItemID& entityID); - void changingEntityID(const EntityItemID& oldEntityID, const EntityItemID& newEntityID); void entitySciptChanging(const EntityItemID& entityID); void entityCollisionWithEntity(const EntityItemID& idA, const EntityItemID& idB, const Collision& collision); diff --git a/libraries/entities/src/EntityScriptingInterface.h b/libraries/entities/src/EntityScriptingInterface.h index 9aa4344536..6c2dc06579 100644 --- a/libraries/entities/src/EntityScriptingInterface.h +++ b/libraries/entities/src/EntityScriptingInterface.h @@ -123,6 +123,7 @@ public slots: signals: void entityCollisionWithEntity(const EntityItemID& idA, const EntityItemID& idB, const Collision& collision); + void collisionWithEntity(const EntityItemID& idA, const EntityItemID& idB, const Collision& collision); void canAdjustLocksChanged(bool canAdjustLocks); void canRezChanged(bool canRez); diff --git a/libraries/entities/src/EntityTree.h b/libraries/entities/src/EntityTree.h index 645bdc8587..b18e989571 100644 --- a/libraries/entities/src/EntityTree.h +++ b/libraries/entities/src/EntityTree.h @@ -171,7 +171,6 @@ signals: void deletingEntity(const EntityItemID& entityID); void addingEntity(const EntityItemID& entityID); void entityScriptChanging(const EntityItemID& entityItemID); - void changingEntityID(const EntityItemID& oldEntityID, const EntityItemID& newEntityID); void clearingEntities(); private: diff --git a/libraries/script-engine/src/ScriptEngine.cpp b/libraries/script-engine/src/ScriptEngine.cpp index 427944e254..91a4e3c397 100644 --- a/libraries/script-engine/src/ScriptEngine.cpp +++ b/libraries/script-engine/src/ScriptEngine.cpp @@ -389,6 +389,94 @@ void ScriptEngine::registerGetterSetter(const QString& name, QScriptEngine::Func } } +// Look up the handler associated with eventName and entityID. If found, evalute the argGenerator thunk and call the handler with those args +void ScriptEngine::generalHandler(const EntityItemID& entityID, const QString& eventName, std::function argGenerator) { + if (!_registeredHandlers.contains(entityID)) { + return; + } + const RegisteredEventHandlers& handlersOnEntity = _registeredHandlers[entityID]; + if (!handlersOnEntity.contains(eventName)) { + return; + } + QScriptValueList handlersForEvent = handlersOnEntity[eventName]; + if (!handlersForEvent.isEmpty()) { + QScriptValueList args = argGenerator(); + for (int i = 0; i < handlersForEvent.count(); ++i) { + handlersForEvent[i].call(QScriptValue(), args); + } + } +} +// Unregister the handlers for this eventName and entityID. +void ScriptEngine::removeEventHandler(const EntityItemID& entityID, const QString& eventName, QScriptValue handler) { + if (!_registeredHandlers.contains(entityID)) { + return; + } + RegisteredEventHandlers& handlersOnEntity = _registeredHandlers[entityID]; + QScriptValueList& handlersForEvent = handlersOnEntity[eventName]; + // QScriptValue does not have operator==(), so we can't use QList::removeOne and friends. So iterate. + for (int i = 0; i < handlersForEvent.count(); ++i) { + if (handlersForEvent[i].equals(handler)) { + handlersForEvent.removeAt(i); + return; // Design choice: since comparison is relatively expensive, just remove the first matching handler. + } + } +} +// Register the handler. +void ScriptEngine::addEventHandler(const EntityItemID& entityID, const QString& eventName, QScriptValue handler) { + if (_registeredHandlers.count() == 0) { // First time any per-entity handler has been added in this script... + // 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.) + auto entities = DependencyManager::get(); + connect(entities.data(), &EntityScriptingInterface::deletingEntity, this, + [=](const EntityItemID& entityID) { + _registeredHandlers.remove(entityID); + }); + + // Two common cases of event handler, differing only in argument signature. + auto makeSingleEntityHandler = [=](const QString& eventName) -> std::function { + return [=](const EntityItemID& entityItemID) -> void { + generalHandler(entityItemID, eventName, [=]() -> QScriptValueList { + return QScriptValueList() << entityItemID.toScriptValue(this); + }); + }; + }; + auto makeMouseHandler = [=](const QString& eventName) -> std::function { + return [=](const EntityItemID& entityItemID, const MouseEvent& event) -> void { + generalHandler(entityItemID, eventName, [=]() -> QScriptValueList { + return QScriptValueList() << entityItemID.toScriptValue(this) << event.toScriptValue(this); + }); + }; + }; + connect(entities.data(), &EntityScriptingInterface::enterEntity, this, makeSingleEntityHandler("enterEntity")); + connect(entities.data(), &EntityScriptingInterface::leaveEntity, this, makeSingleEntityHandler("leaveEntity")); + + connect(entities.data(), &EntityScriptingInterface::mousePressOnEntity, this, makeMouseHandler("mousePressOnEntity")); + connect(entities.data(), &EntityScriptingInterface::mouseMoveOnEntity, this, makeMouseHandler("mouseMoveOnEntity")); + connect(entities.data(), &EntityScriptingInterface::mouseReleaseOnEntity, this, makeMouseHandler("mouseReleaseOnEntity")); + + connect(entities.data(), &EntityScriptingInterface::clickDownOnEntity, this, makeMouseHandler("clickDownOnEntity")); + connect(entities.data(), &EntityScriptingInterface::holdingClickOnEntity, this, makeMouseHandler("holdingClickOnEntity")); + connect(entities.data(), &EntityScriptingInterface::clickReleaseOnEntity, this, makeMouseHandler("clickReleaseOnEntity")); + + connect(entities.data(), &EntityScriptingInterface::hoverEnterEntity, this, makeMouseHandler("hoverEnterEntity")); + connect(entities.data(), &EntityScriptingInterface::hoverOverEntity, this, makeMouseHandler("hoverOverEntity")); + connect(entities.data(), &EntityScriptingInterface::hoverLeaveEntity, this, makeMouseHandler("hoverLeaveEntity")); + + connect(entities.data(), &EntityScriptingInterface::collisionWithEntity, this, + [=](const EntityItemID& idA, const EntityItemID& idB, const Collision& collision) { + generalHandler(idA, "collisionWithEntity", [=]() { + return QScriptValueList () << idA.toScriptValue(this) << idB.toScriptValue(this) << collisionToScriptValue(this, collision); + }); + }); + } + if (!_registeredHandlers.contains(entityID)) { + _registeredHandlers[entityID] = RegisteredEventHandlers(); + } + QScriptValueList& handlersForEvent = _registeredHandlers[entityID][eventName]; + handlersForEvent << handler; // Note that the same handler can be added many times. See removeEntityEventHandler(). +} + + void ScriptEngine::evaluate() { if (_stoppingAllScripts) { return; // bail early diff --git a/libraries/script-engine/src/ScriptEngine.h b/libraries/script-engine/src/ScriptEngine.h index 45e56850a7..175eff059f 100644 --- a/libraries/script-engine/src/ScriptEngine.h +++ b/libraries/script-engine/src/ScriptEngine.h @@ -23,6 +23,7 @@ #include #include #include +#include #include "AbstractControllerScriptingInterface.h" #include "ArrayBufferClass.h" @@ -36,6 +37,8 @@ const QString NO_SCRIPT(""); const unsigned int SCRIPT_DATA_CALLBACK_USECS = floor(((1.0 / 60.0f) * 1000 * 1000) + 0.5); +typedef QHash RegisteredEventHandlers; + class ScriptEngine : public QScriptEngine, public ScriptUser { Q_OBJECT public: @@ -98,6 +101,9 @@ public: virtual void scriptContentsAvailable(const QUrl& url, const QString& scriptContents); virtual void errorInLoadingScript(const QUrl& url); + Q_INVOKABLE void addEventHandler(const EntityItemID& entityID, const QString& eventName, QScriptValue handler); + Q_INVOKABLE void removeEventHandler(const EntityItemID& entityID, const QString& eventName, QScriptValue handler); + public slots: void loadURL(const QUrl& scriptURL); void stop(); @@ -164,6 +170,8 @@ private: ArrayBufferClass* _arrayBufferClass; QHash _outgoingScriptAudioSequenceNumbers; + QHash _registeredHandlers; + void generalHandler(const EntityItemID& entityID, const QString& eventName, std::function argGenerator); private: static QSet _allKnownScriptEngines;