mirror of
https://github.com/overte-org/overte.git
synced 2025-05-28 00:50:30 +02:00
first cut at moving entity scripts into ScriptEngine
This commit is contained in:
parent
56118e4204
commit
18fbf896f1
6 changed files with 283 additions and 316 deletions
|
@ -90,10 +90,12 @@ EntityTreeRenderer::~EntityTreeRenderer() {
|
|||
|
||||
void EntityTreeRenderer::clear() {
|
||||
leaveAllEntities();
|
||||
/*
|
||||
foreach (const EntityItemID& entityID, _entityScripts.keys()) {
|
||||
checkAndCallUnload(entityID);
|
||||
}
|
||||
_entityScripts.clear();
|
||||
*/
|
||||
|
||||
auto scene = _viewState->getMain3DScene();
|
||||
render::PendingChanges pendingChanges;
|
||||
|
@ -136,175 +138,8 @@ void EntityTreeRenderer::shutdown() {
|
|||
_shuttingDown = true;
|
||||
}
|
||||
|
||||
void EntityTreeRenderer::scriptContentsAvailable(const QUrl& url, const QString& scriptContents) {
|
||||
if (_waitingOnPreload.contains(url)) {
|
||||
QList<EntityItemID> entityIDs = _waitingOnPreload.values(url);
|
||||
_waitingOnPreload.remove(url);
|
||||
foreach(EntityItemID entityID, entityIDs) {
|
||||
checkAndCallPreload(entityID);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void EntityTreeRenderer::errorInLoadingScript(const QUrl& url) {
|
||||
if (_waitingOnPreload.contains(url)) {
|
||||
_waitingOnPreload.remove(url);
|
||||
}
|
||||
}
|
||||
|
||||
QScriptValue EntityTreeRenderer::loadEntityScript(const EntityItemID& entityItemID, bool isPreload, bool reload) {
|
||||
EntityItemPointer entity = static_cast<EntityTree*>(_tree)->findEntityByEntityItemID(entityItemID);
|
||||
return loadEntityScript(entity, isPreload, reload);
|
||||
}
|
||||
|
||||
|
||||
QString EntityTreeRenderer::loadScriptContents(const QString& scriptMaybeURLorText, bool& isURL, bool& isPending, QUrl& urlOut,
|
||||
bool& reload) {
|
||||
isPending = false;
|
||||
QUrl url(scriptMaybeURLorText);
|
||||
|
||||
// If the url is not valid, this must be script text...
|
||||
// We document "direct injection" scripts as starting with "(function...", and that would never be a valid url.
|
||||
// But QUrl thinks it is.
|
||||
if (!url.isValid() || scriptMaybeURLorText.startsWith("(")) {
|
||||
isURL = false;
|
||||
return scriptMaybeURLorText;
|
||||
}
|
||||
isURL = true;
|
||||
urlOut = url;
|
||||
|
||||
QString scriptContents; // assume empty
|
||||
|
||||
// if the scheme length is one or lower, maybe they typed in a file, let's try
|
||||
const int WINDOWS_DRIVE_LETTER_SIZE = 1;
|
||||
if (url.scheme().size() <= WINDOWS_DRIVE_LETTER_SIZE) {
|
||||
url = QUrl::fromLocalFile(scriptMaybeURLorText);
|
||||
}
|
||||
|
||||
// ok, let's see if it's valid... and if so, load it
|
||||
if (url.isValid()) {
|
||||
if (url.scheme() == "file") {
|
||||
QString fileName = url.toLocalFile();
|
||||
QFile scriptFile(fileName);
|
||||
if (scriptFile.open(QFile::ReadOnly | QFile::Text)) {
|
||||
qCDebug(entitiesrenderer) << "Loading file:" << fileName;
|
||||
QTextStream in(&scriptFile);
|
||||
scriptContents = in.readAll();
|
||||
} else {
|
||||
qCDebug(entitiesrenderer) << "ERROR Loading file:" << fileName;
|
||||
}
|
||||
} else {
|
||||
auto scriptCache = DependencyManager::get<ScriptCache>();
|
||||
|
||||
if (!scriptCache->isInBadScriptList(url)) {
|
||||
scriptContents = scriptCache->getScript(url, this, isPending, reload);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return scriptContents;
|
||||
}
|
||||
|
||||
|
||||
QScriptValue EntityTreeRenderer::loadEntityScript(EntityItemPointer entity, bool isPreload, bool reload) {
|
||||
if (_shuttingDown) {
|
||||
return QScriptValue(); // since we're shutting down, we don't load any more scripts
|
||||
}
|
||||
|
||||
if (!entity) {
|
||||
return QScriptValue(); // no entity...
|
||||
}
|
||||
|
||||
// NOTE: we keep local variables for the entityID and the script because
|
||||
// below in loadScriptContents() it's possible for us to execute the
|
||||
// application event loop, which may cause our entity to be deleted on
|
||||
// us. We don't really need access the entity after this point, can
|
||||
// can accomplish all we need to here with just the script "text" and the ID.
|
||||
EntityItemID entityID = entity->getEntityItemID();
|
||||
QString entityScript = entity->getScript();
|
||||
|
||||
if (_entityScripts.contains(entityID)) {
|
||||
EntityScriptDetails details = _entityScripts[entityID];
|
||||
|
||||
// check to make sure our script text hasn't changed on us since we last loaded it and we're not redownloading it
|
||||
if (details.scriptText == entityScript && !reload) {
|
||||
return details.scriptObject; // previously loaded
|
||||
}
|
||||
|
||||
// if we got here, then we previously loaded a script, but the entity's script value
|
||||
// has changed and so we need to reload it.
|
||||
_entityScripts.remove(entityID);
|
||||
}
|
||||
if (entityScript.isEmpty()) {
|
||||
return QScriptValue(); // no script
|
||||
}
|
||||
|
||||
bool isURL = false; // loadScriptContents() will tell us if this is a URL or just text.
|
||||
bool isPending = false;
|
||||
QUrl url;
|
||||
QString scriptContents = loadScriptContents(entityScript, isURL, isPending, url, reload);
|
||||
|
||||
if (isPending && isPreload && isURL) {
|
||||
_waitingOnPreload.insert(url, entityID);
|
||||
}
|
||||
|
||||
auto scriptCache = DependencyManager::get<ScriptCache>();
|
||||
|
||||
if (isURL && scriptCache->isInBadScriptList(url)) {
|
||||
return QScriptValue(); // no script contents...
|
||||
}
|
||||
|
||||
if (scriptContents.isEmpty()) {
|
||||
return QScriptValue(); // no script contents...
|
||||
}
|
||||
|
||||
QScriptSyntaxCheckResult syntaxCheck = QScriptEngine::checkSyntax(scriptContents);
|
||||
if (syntaxCheck.state() != QScriptSyntaxCheckResult::Valid) {
|
||||
qCDebug(entitiesrenderer) << "EntityTreeRenderer::loadEntityScript() entity:" << entityID;
|
||||
qCDebug(entitiesrenderer) << " " << syntaxCheck.errorMessage() << ":"
|
||||
<< syntaxCheck.errorLineNumber() << syntaxCheck.errorColumnNumber();
|
||||
qCDebug(entitiesrenderer) << " SCRIPT:" << entityScript;
|
||||
|
||||
scriptCache->addScriptToBadScriptList(url);
|
||||
|
||||
return QScriptValue(); // invalid script
|
||||
}
|
||||
|
||||
if (isURL) {
|
||||
_entitiesScriptEngine->setParentURL(entity->getScript());
|
||||
}
|
||||
QScriptValue entityScriptConstructor = _sandboxScriptEngine->evaluate(scriptContents);
|
||||
|
||||
if (!entityScriptConstructor.isFunction()) {
|
||||
qCDebug(entitiesrenderer) << "EntityTreeRenderer::loadEntityScript() entity:" << entityID;
|
||||
qCDebug(entitiesrenderer) << " NOT CONSTRUCTOR";
|
||||
qCDebug(entitiesrenderer) << " SCRIPT:" << entityScript;
|
||||
|
||||
scriptCache->addScriptToBadScriptList(url);
|
||||
|
||||
return QScriptValue(); // invalid script
|
||||
} else {
|
||||
entityScriptConstructor = _entitiesScriptEngine->evaluate(scriptContents);
|
||||
}
|
||||
|
||||
QScriptValue entityScriptObject = entityScriptConstructor.construct();
|
||||
EntityScriptDetails newDetails = { entityScript, entityScriptObject };
|
||||
_entityScripts[entityID] = newDetails;
|
||||
|
||||
if (isURL) {
|
||||
_entitiesScriptEngine->setParentURL("");
|
||||
}
|
||||
|
||||
return entityScriptObject; // newly constructed
|
||||
}
|
||||
|
||||
QScriptValue EntityTreeRenderer::getPreviouslyLoadedEntityScript(const EntityItemID& entityID) {
|
||||
if (_entityScripts.contains(entityID)) {
|
||||
EntityScriptDetails details = _entityScripts[entityID];
|
||||
return details.scriptObject; // previously loaded
|
||||
}
|
||||
return QScriptValue(); // no script
|
||||
}
|
||||
|
||||
void EntityTreeRenderer::setTree(Octree* newTree) {
|
||||
OctreeRenderer::setTree(newTree);
|
||||
|
@ -324,11 +159,7 @@ void EntityTreeRenderer::update() {
|
|||
// and we want to simulate this message here as well as in mouse move
|
||||
if (_lastMouseEventValid && !_currentClickingOnEntityID.isInvalidID()) {
|
||||
emit holdingClickOnEntity(_currentClickingOnEntityID, _lastMouseEvent);
|
||||
QScriptValueList currentClickingEntityArgs = createMouseEventArgs(_currentClickingOnEntityID, _lastMouseEvent);
|
||||
QScriptValue currentClickingEntity = loadEntityScript(_currentClickingOnEntityID);
|
||||
if (currentClickingEntity.property("holdingClickOnEntity").isValid()) {
|
||||
currentClickingEntity.property("holdingClickOnEntity").call(currentClickingEntity, currentClickingEntityArgs);
|
||||
}
|
||||
_entitiesScriptEngine->callEntityScriptMethod(_currentClickingOnEntityID, "holdingClickOnEntity", _lastMouseEvent);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -356,19 +187,14 @@ void EntityTreeRenderer::checkEnterLeaveEntities() {
|
|||
_tree->unlock();
|
||||
|
||||
// Note: at this point we don't need to worry about the tree being locked, because we only deal with
|
||||
// EntityItemIDs from here. The loadEntityScript() method is robust against attempting to load scripts
|
||||
// EntityItemIDs from here. The callEntityScriptMethod() method is robust against attempting to call scripts
|
||||
// for entity IDs that no longer exist.
|
||||
|
||||
// for all of our previous containing entities, if they are no longer containing then send them a leave event
|
||||
foreach(const EntityItemID& entityID, _currentEntitiesInside) {
|
||||
if (!entitiesContainingAvatar.contains(entityID)) {
|
||||
emit leaveEntity(entityID);
|
||||
QScriptValueList entityArgs = createEntityArgs(entityID);
|
||||
QScriptValue entityScript = loadEntityScript(entityID);
|
||||
if (entityScript.property("leaveEntity").isValid()) {
|
||||
entityScript.property("leaveEntity").call(entityScript, entityArgs);
|
||||
}
|
||||
|
||||
_entitiesScriptEngine->callEntityScriptMethod(entityID, "leaveEntity");
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -376,11 +202,7 @@ void EntityTreeRenderer::checkEnterLeaveEntities() {
|
|||
foreach(const EntityItemID& entityID, entitiesContainingAvatar) {
|
||||
if (!_currentEntitiesInside.contains(entityID)) {
|
||||
emit enterEntity(entityID);
|
||||
QScriptValueList entityArgs = createEntityArgs(entityID);
|
||||
QScriptValue entityScript = loadEntityScript(entityID);
|
||||
if (entityScript.property("enterEntity").isValid()) {
|
||||
entityScript.property("enterEntity").call(entityScript, entityArgs);
|
||||
}
|
||||
_entitiesScriptEngine->callEntityScriptMethod(entityID, "enterEntity");
|
||||
}
|
||||
}
|
||||
_currentEntitiesInside = entitiesContainingAvatar;
|
||||
|
@ -395,11 +217,7 @@ void EntityTreeRenderer::leaveAllEntities() {
|
|||
// for all of our previous containing entities, if they are no longer containing then send them a leave event
|
||||
foreach(const EntityItemID& entityID, _currentEntitiesInside) {
|
||||
emit leaveEntity(entityID);
|
||||
QScriptValueList entityArgs = createEntityArgs(entityID);
|
||||
QScriptValue entityScript = loadEntityScript(entityID);
|
||||
if (entityScript.property("leaveEntity").isValid()) {
|
||||
entityScript.property("leaveEntity").call(entityScript, entityArgs);
|
||||
}
|
||||
_entitiesScriptEngine->callEntityScriptMethod(entityID, "leaveEntity");
|
||||
}
|
||||
_currentEntitiesInside.clear();
|
||||
|
||||
|
@ -821,27 +639,6 @@ void EntityTreeRenderer::connectSignalsToSlots(EntityScriptingInterface* entityS
|
|||
connect(DependencyManager::get<SceneScriptingInterface>().data(), &SceneScriptingInterface::shouldRenderEntitiesChanged, this, &EntityTreeRenderer::updateEntityRenderStatus, Qt::QueuedConnection);
|
||||
}
|
||||
|
||||
QScriptValueList EntityTreeRenderer::createMouseEventArgs(const EntityItemID& entityID, QMouseEvent* event, unsigned int deviceID) {
|
||||
QScriptValueList args;
|
||||
args << entityID.toScriptValue(_entitiesScriptEngine);
|
||||
args << MouseEvent(*event, deviceID).toScriptValue(_entitiesScriptEngine);
|
||||
return args;
|
||||
}
|
||||
|
||||
QScriptValueList EntityTreeRenderer::createMouseEventArgs(const EntityItemID& entityID, const MouseEvent& mouseEvent) {
|
||||
QScriptValueList args;
|
||||
args << entityID.toScriptValue(_entitiesScriptEngine);
|
||||
args << mouseEvent.toScriptValue(_entitiesScriptEngine);
|
||||
return args;
|
||||
}
|
||||
|
||||
|
||||
QScriptValueList EntityTreeRenderer::createEntityArgs(const EntityItemID& entityID) {
|
||||
QScriptValueList args;
|
||||
args << entityID.toScriptValue(_entitiesScriptEngine);
|
||||
return args;
|
||||
}
|
||||
|
||||
void EntityTreeRenderer::mousePressEvent(QMouseEvent* event, unsigned int deviceID) {
|
||||
// If we don't have a tree, or we're in the process of shutting down, then don't
|
||||
// process these events.
|
||||
|
@ -864,18 +661,11 @@ void EntityTreeRenderer::mousePressEvent(QMouseEvent* event, unsigned int device
|
|||
}
|
||||
|
||||
emit mousePressOnEntity(rayPickResult, event, deviceID);
|
||||
|
||||
QScriptValueList entityScriptArgs = createMouseEventArgs(rayPickResult.entityID, event, deviceID);
|
||||
QScriptValue entityScript = loadEntityScript(rayPickResult.entity);
|
||||
if (entityScript.property("mousePressOnEntity").isValid()) {
|
||||
entityScript.property("mousePressOnEntity").call(entityScript, entityScriptArgs);
|
||||
}
|
||||
_entitiesScriptEngine->callEntityScriptMethod(rayPickResult.entityID, "mousePressOnEntity", MouseEvent(*event, deviceID));
|
||||
|
||||
_currentClickingOnEntityID = rayPickResult.entityID;
|
||||
emit clickDownOnEntity(_currentClickingOnEntityID, MouseEvent(*event, deviceID));
|
||||
if (entityScript.property("clickDownOnEntity").isValid()) {
|
||||
entityScript.property("clickDownOnEntity").call(entityScript, entityScriptArgs);
|
||||
}
|
||||
_entitiesScriptEngine->callEntityScriptMethod(_currentClickingOnEntityID, "clickDownOnEntity", MouseEvent(*event, deviceID));
|
||||
} else {
|
||||
emit mousePressOffEntity(rayPickResult, event, deviceID);
|
||||
}
|
||||
|
@ -896,24 +686,14 @@ void EntityTreeRenderer::mouseReleaseEvent(QMouseEvent* event, unsigned int devi
|
|||
if (rayPickResult.intersects) {
|
||||
//qCDebug(entitiesrenderer) << "mouseReleaseEvent over entity:" << rayPickResult.entityID;
|
||||
emit mouseReleaseOnEntity(rayPickResult, event, deviceID);
|
||||
|
||||
QScriptValueList entityScriptArgs = createMouseEventArgs(rayPickResult.entityID, event, deviceID);
|
||||
QScriptValue entityScript = loadEntityScript(rayPickResult.entity);
|
||||
if (entityScript.property("mouseReleaseOnEntity").isValid()) {
|
||||
entityScript.property("mouseReleaseOnEntity").call(entityScript, entityScriptArgs);
|
||||
}
|
||||
_entitiesScriptEngine->callEntityScriptMethod(rayPickResult.entityID, "mouseReleaseOnEntity", MouseEvent(*event, deviceID));
|
||||
}
|
||||
|
||||
// Even if we're no longer intersecting with an entity, if we started clicking on it, and now
|
||||
// we're releasing the button, then this is considered a clickOn event
|
||||
if (!_currentClickingOnEntityID.isInvalidID()) {
|
||||
emit clickReleaseOnEntity(_currentClickingOnEntityID, MouseEvent(*event, deviceID));
|
||||
|
||||
QScriptValueList currentClickingEntityArgs = createMouseEventArgs(_currentClickingOnEntityID, event, deviceID);
|
||||
QScriptValue currentClickingEntity = loadEntityScript(_currentClickingOnEntityID);
|
||||
if (currentClickingEntity.property("clickReleaseOnEntity").isValid()) {
|
||||
currentClickingEntity.property("clickReleaseOnEntity").call(currentClickingEntity, currentClickingEntityArgs);
|
||||
}
|
||||
_entitiesScriptEngine->callEntityScriptMethod(rayPickResult.entityID, "clickReleaseOnEntity", MouseEvent(*event, deviceID));
|
||||
}
|
||||
|
||||
// makes it the unknown ID, we just released so we can't be clicking on anything
|
||||
|
@ -935,17 +715,9 @@ void EntityTreeRenderer::mouseMoveEvent(QMouseEvent* event, unsigned int deviceI
|
|||
bool precisionPicking = false; // for mouse moves we do not do precision picking
|
||||
RayToEntityIntersectionResult rayPickResult = findRayIntersectionWorker(ray, Octree::TryLock, precisionPicking);
|
||||
if (rayPickResult.intersects) {
|
||||
//qCDebug(entitiesrenderer) << "mouseReleaseEvent over entity:" << rayPickResult.entityID;
|
||||
QScriptValueList entityScriptArgs = createMouseEventArgs(rayPickResult.entityID, event, deviceID);
|
||||
// load the entity script if needed...
|
||||
QScriptValue entityScript = loadEntityScript(rayPickResult.entity);
|
||||
if (entityScript.property("mouseMoveEvent").isValid()) {
|
||||
entityScript.property("mouseMoveEvent").call(entityScript, entityScriptArgs);
|
||||
}
|
||||
emit mouseMoveOnEntity(rayPickResult, event, deviceID);
|
||||
if (entityScript.property("mouseMoveOnEntity").isValid()) {
|
||||
entityScript.property("mouseMoveOnEntity").call(entityScript, entityScriptArgs);
|
||||
}
|
||||
|
||||
_entitiesScriptEngine->callEntityScriptMethod(rayPickResult.entityID, "mouseMoveEvent", MouseEvent(*event, deviceID));
|
||||
_entitiesScriptEngine->callEntityScriptMethod(rayPickResult.entityID, "mouseMoveOnEntity", MouseEvent(*event, deviceID));
|
||||
|
||||
// handle the hover logic...
|
||||
|
||||
|
@ -953,30 +725,19 @@ void EntityTreeRenderer::mouseMoveEvent(QMouseEvent* event, unsigned int deviceI
|
|||
// then we need to send the hover leave.
|
||||
if (!_currentHoverOverEntityID.isInvalidID() && rayPickResult.entityID != _currentHoverOverEntityID) {
|
||||
emit hoverLeaveEntity(_currentHoverOverEntityID, MouseEvent(*event, deviceID));
|
||||
|
||||
QScriptValueList currentHoverEntityArgs = createMouseEventArgs(_currentHoverOverEntityID, event, deviceID);
|
||||
|
||||
QScriptValue currentHoverEntity = loadEntityScript(_currentHoverOverEntityID);
|
||||
if (currentHoverEntity.property("hoverLeaveEntity").isValid()) {
|
||||
currentHoverEntity.property("hoverLeaveEntity").call(currentHoverEntity, currentHoverEntityArgs);
|
||||
}
|
||||
_entitiesScriptEngine->callEntityScriptMethod(_currentHoverOverEntityID, "hoverLeaveEntity", MouseEvent(*event, deviceID));
|
||||
}
|
||||
|
||||
// If the new hover entity does not match the previous hover entity then we are entering the new one
|
||||
// this is true if the _currentHoverOverEntityID is known or unknown
|
||||
if (rayPickResult.entityID != _currentHoverOverEntityID) {
|
||||
emit hoverEnterEntity(rayPickResult.entityID, MouseEvent(*event, deviceID));
|
||||
if (entityScript.property("hoverEnterEntity").isValid()) {
|
||||
entityScript.property("hoverEnterEntity").call(entityScript, entityScriptArgs);
|
||||
}
|
||||
_entitiesScriptEngine->callEntityScriptMethod(rayPickResult.entityID, "hoverEnterEntity", MouseEvent(*event, deviceID));
|
||||
}
|
||||
|
||||
// and finally, no matter what, if we're intersecting an entity then we're definitely hovering over it, and
|
||||
// we should send our hover over event
|
||||
emit hoverOverEntity(rayPickResult.entityID, MouseEvent(*event, deviceID));
|
||||
if (entityScript.property("hoverOverEntity").isValid()) {
|
||||
entityScript.property("hoverOverEntity").call(entityScript, entityScriptArgs);
|
||||
}
|
||||
_entitiesScriptEngine->callEntityScriptMethod(rayPickResult.entityID, "hoverOverEntity", MouseEvent(*event, deviceID));
|
||||
|
||||
// remember what we're hovering over
|
||||
_currentHoverOverEntityID = rayPickResult.entityID;
|
||||
|
@ -987,14 +748,7 @@ void EntityTreeRenderer::mouseMoveEvent(QMouseEvent* event, unsigned int deviceI
|
|||
// send the hover leave for our previous entity
|
||||
if (!_currentHoverOverEntityID.isInvalidID()) {
|
||||
emit hoverLeaveEntity(_currentHoverOverEntityID, MouseEvent(*event, deviceID));
|
||||
|
||||
QScriptValueList currentHoverEntityArgs = createMouseEventArgs(_currentHoverOverEntityID, event, deviceID);
|
||||
|
||||
QScriptValue currentHoverEntity = loadEntityScript(_currentHoverOverEntityID);
|
||||
if (currentHoverEntity.property("hoverLeaveEntity").isValid()) {
|
||||
currentHoverEntity.property("hoverLeaveEntity").call(currentHoverEntity, currentHoverEntityArgs);
|
||||
}
|
||||
|
||||
_entitiesScriptEngine->callEntityScriptMethod(_currentHoverOverEntityID, "hoverLeaveEntity", MouseEvent(*event, deviceID));
|
||||
_currentHoverOverEntityID = UNKNOWN_ENTITY_ID; // makes it the unknown ID
|
||||
}
|
||||
}
|
||||
|
@ -1003,13 +757,7 @@ void EntityTreeRenderer::mouseMoveEvent(QMouseEvent* event, unsigned int deviceI
|
|||
// not yet released the hold then this is still considered a holdingClickOnEntity event
|
||||
if (!_currentClickingOnEntityID.isInvalidID()) {
|
||||
emit holdingClickOnEntity(_currentClickingOnEntityID, MouseEvent(*event, deviceID));
|
||||
|
||||
QScriptValueList currentClickingEntityArgs = createMouseEventArgs(_currentClickingOnEntityID, event, deviceID);
|
||||
|
||||
QScriptValue currentClickingEntity = loadEntityScript(_currentClickingOnEntityID);
|
||||
if (currentClickingEntity.property("holdingClickOnEntity").isValid()) {
|
||||
currentClickingEntity.property("holdingClickOnEntity").call(currentClickingEntity, currentClickingEntityArgs);
|
||||
}
|
||||
_entitiesScriptEngine->callEntityScriptMethod(_currentClickingOnEntityID, "holdingClickOnEntity", MouseEvent(*event, deviceID));
|
||||
}
|
||||
_lastMouseEvent = MouseEvent(*event, deviceID);
|
||||
_lastMouseEventValid = true;
|
||||
|
@ -1017,9 +765,9 @@ void EntityTreeRenderer::mouseMoveEvent(QMouseEvent* event, unsigned int deviceI
|
|||
|
||||
void EntityTreeRenderer::deletingEntity(const EntityItemID& entityID) {
|
||||
if (_tree && !_shuttingDown) {
|
||||
checkAndCallUnload(entityID);
|
||||
//checkAndCallUnload(entityID);
|
||||
}
|
||||
_entityScripts.remove(entityID);
|
||||
//_entityScripts.remove(entityID);
|
||||
|
||||
// here's where we remove the entity payload from the scene
|
||||
if (_entitiesInScene.contains(entityID)) {
|
||||
|
@ -1032,7 +780,7 @@ void EntityTreeRenderer::deletingEntity(const EntityItemID& entityID) {
|
|||
}
|
||||
|
||||
void EntityTreeRenderer::addingEntity(const EntityItemID& entityID) {
|
||||
checkAndCallPreload(entityID);
|
||||
//checkAndCallPreload(entityID);
|
||||
auto entity = static_cast<EntityTree*>(_tree)->findEntityByID(entityID);
|
||||
if (entity) {
|
||||
addEntityToScene(entity);
|
||||
|
@ -1059,22 +807,14 @@ void EntityTreeRenderer::entitySciptChanging(const EntityItemID& entityID, const
|
|||
|
||||
void EntityTreeRenderer::checkAndCallPreload(const EntityItemID& entityID, const bool reload) {
|
||||
if (_tree && !_shuttingDown) {
|
||||
// load the entity script if needed...
|
||||
QScriptValue entityScript = loadEntityScript(entityID, true, reload); // is preload!
|
||||
if (entityScript.property("preload").isValid()) {
|
||||
QScriptValueList entityArgs = createEntityArgs(entityID);
|
||||
entityScript.property("preload").call(entityScript, entityArgs);
|
||||
}
|
||||
EntityItemPointer entity = getTree()->findEntityByEntityItemID(entityID);
|
||||
_entitiesScriptEngine->loadEntityScript(entityID, entity->getScript(), reload);
|
||||
}
|
||||
}
|
||||
|
||||
void EntityTreeRenderer::checkAndCallUnload(const EntityItemID& entityID) {
|
||||
if (_tree && !_shuttingDown) {
|
||||
QScriptValue entityScript = getPreviouslyLoadedEntityScript(entityID);
|
||||
if (entityScript.property("unload").isValid()) {
|
||||
QScriptValueList entityArgs = createEntityArgs(entityID);
|
||||
entityScript.property("unload").call(entityScript, entityArgs);
|
||||
}
|
||||
_entitiesScriptEngine->unloadEntityScript(entityID);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1152,24 +892,9 @@ void EntityTreeRenderer::entityCollisionWithEntity(const EntityItemID& idA, cons
|
|||
|
||||
// And now the entity scripts
|
||||
emit collisionWithEntity(idA, idB, collision);
|
||||
QScriptValue entityScriptA = loadEntityScript(idA);
|
||||
if (entityScriptA.property("collisionWithEntity").isValid()) {
|
||||
QScriptValueList args;
|
||||
args << idA.toScriptValue(_entitiesScriptEngine);
|
||||
args << idB.toScriptValue(_entitiesScriptEngine);
|
||||
args << collisionToScriptValue(_entitiesScriptEngine, collision);
|
||||
entityScriptA.property("collisionWithEntity").call(entityScriptA, args);
|
||||
}
|
||||
|
||||
_entitiesScriptEngine->callEntityScriptMethod(idA, "collisionWithEntity", idB, collision);
|
||||
emit collisionWithEntity(idB, idA, collision);
|
||||
QScriptValue entityScriptB = loadEntityScript(idB);
|
||||
if (entityScriptB.property("collisionWithEntity").isValid()) {
|
||||
QScriptValueList args;
|
||||
args << idB.toScriptValue(_entitiesScriptEngine);
|
||||
args << idA.toScriptValue(_entitiesScriptEngine);
|
||||
args << collisionToScriptValue(_entitiesScriptEngine, collision);
|
||||
entityScriptB.property("collisionWithEntity").call(entityScriptA, args);
|
||||
}
|
||||
_entitiesScriptEngine->callEntityScriptMethod(idB, "collisionWithEntity", idA, collision);
|
||||
}
|
||||
|
||||
void EntityTreeRenderer::updateEntityRenderStatus(bool shouldRenderEntities) {
|
||||
|
|
|
@ -28,14 +28,9 @@ class Model;
|
|||
class ScriptEngine;
|
||||
class ZoneEntityItem;
|
||||
|
||||
class EntityScriptDetails {
|
||||
public:
|
||||
QString scriptText;
|
||||
QScriptValue scriptObject;
|
||||
};
|
||||
|
||||
// Generic client side Octree renderer class.
|
||||
class EntityTreeRenderer : public OctreeRenderer, public EntityItemFBXService, public ScriptUser {
|
||||
class EntityTreeRenderer : public OctreeRenderer, public EntityItemFBXService {
|
||||
Q_OBJECT
|
||||
public:
|
||||
EntityTreeRenderer(bool wantScripts, AbstractViewStateInterface* viewState,
|
||||
|
@ -87,9 +82,6 @@ public:
|
|||
/// hovering over, and entering entities
|
||||
void connectSignalsToSlots(EntityScriptingInterface* entityScriptingInterface);
|
||||
|
||||
virtual void scriptContentsAvailable(const QUrl& url, const QString& scriptContents);
|
||||
virtual void errorInLoadingScript(const QUrl& url);
|
||||
|
||||
// For Scene.shouldRenderEntities
|
||||
QList<EntityItemID>& getEntitiesLastInScene() { return _entityIDsLastInScene; }
|
||||
|
||||
|
@ -157,10 +149,8 @@ private:
|
|||
QScriptValue loadEntityScript(const EntityItemID& entityItemID, bool isPreload = false, bool reload = false);
|
||||
QScriptValue getPreviouslyLoadedEntityScript(const EntityItemID& entityItemID);
|
||||
QString loadScriptContents(const QString& scriptMaybeURLorText, bool& isURL, bool& isPending, QUrl& url, bool& reload);
|
||||
QScriptValueList createMouseEventArgs(const EntityItemID& entityID, QMouseEvent* event, unsigned int deviceID);
|
||||
QScriptValueList createMouseEventArgs(const EntityItemID& entityID, const MouseEvent& mouseEvent);
|
||||
|
||||
QHash<EntityItemID, EntityScriptDetails> _entityScripts;
|
||||
//QHash<EntityItemID, EntityScriptDetails> _entityScripts;
|
||||
|
||||
void playEntityCollisionSound(const QUuid& myNodeID, EntityTree* entityTree, const EntityItemID& id, const Collision& collision);
|
||||
|
||||
|
|
|
@ -81,4 +81,51 @@ void ScriptCache::scriptDownloaded() {
|
|||
req->deleteLater();
|
||||
}
|
||||
|
||||
void ScriptCache::getScriptContents(const QString& scriptOrURL, contentAvailableCallback contentAvailable, bool forceDownload) {
|
||||
QUrl unnormalizedURL(scriptOrURL);
|
||||
QUrl url = ResourceManager::normalizeURL(unnormalizedURL);
|
||||
|
||||
// attempt to determine if this is a URL to a script, or if this is actually a script itself (which is valid in the entityScript use case)
|
||||
if (url.scheme().isEmpty() && scriptOrURL.simplified().replace(" ", "").contains("(function(){")) {
|
||||
contentAvailable(scriptOrURL, scriptOrURL, false, true);
|
||||
return;
|
||||
}
|
||||
|
||||
if (_scriptCache.contains(url) && !forceDownload) {
|
||||
qCDebug(scriptengine) << "Found script in cache:" << url.toString();
|
||||
contentAvailable(url.toString(), _scriptCache[url], true, true);
|
||||
} else {
|
||||
bool alreadyWaiting = _contentCallbacks.contains(url);
|
||||
_contentCallbacks.insert(url, contentAvailable);
|
||||
|
||||
if (alreadyWaiting) {
|
||||
qCDebug(scriptengine) << "Already downloading script at:" << url.toString();
|
||||
} else {
|
||||
auto request = ResourceManager::createResourceRequest(this, url);
|
||||
request->setCacheEnabled(!forceDownload);
|
||||
connect(request, &ResourceRequest::finished, this, &ScriptCache::scriptContentAvailable);
|
||||
request->send();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ScriptCache::scriptContentAvailable() {
|
||||
ResourceRequest* req = qobject_cast<ResourceRequest*>(sender());
|
||||
QUrl url = req->getUrl();
|
||||
QList<contentAvailableCallback> allCallbacks = _contentCallbacks.values(url);
|
||||
_contentCallbacks.remove(url);
|
||||
|
||||
bool success = req->getResult() == ResourceRequest::Success;
|
||||
|
||||
if (success) {
|
||||
_scriptCache[url] = req->getData();
|
||||
qCDebug(scriptengine) << "Done downloading script at:" << url.toString();
|
||||
} else {
|
||||
qCWarning(scriptengine) << "Error loading script from URL " << url;
|
||||
}
|
||||
foreach(contentAvailableCallback thisCallback, allCallbacks) {
|
||||
thisCallback(url.toString(), _scriptCache[url], true, success);
|
||||
}
|
||||
req->deleteLater();
|
||||
}
|
||||
|
||||
|
|
|
@ -20,23 +20,33 @@ public:
|
|||
virtual void errorInLoadingScript(const QUrl& url) = 0;
|
||||
};
|
||||
|
||||
using contentAvailableCallback = std::function<void(const QString& scriptOrURL, const QString& contents, bool isURL, bool contentAvailable)>;
|
||||
|
||||
/// Interface for loading scripts
|
||||
class ScriptCache : public QObject, public Dependency {
|
||||
Q_OBJECT
|
||||
SINGLETON_DEPENDENCY
|
||||
|
||||
public:
|
||||
void getScriptContents(const QString& scriptOrURL, contentAvailableCallback contentAvailable, bool forceDownload = false);
|
||||
|
||||
|
||||
QString getScript(const QUrl& unnormalizedURL, ScriptUser* scriptUser, bool& isPending, bool redownload = false);
|
||||
void deleteScript(const QUrl& unnormalizedURL);
|
||||
|
||||
// FIXME - how do we remove a script from the bad script list in the case of a redownload?
|
||||
void addScriptToBadScriptList(const QUrl& url) { _badScripts.insert(url); }
|
||||
bool isInBadScriptList(const QUrl& url) { return _badScripts.contains(url); }
|
||||
|
||||
private slots:
|
||||
void scriptDownloaded();
|
||||
|
||||
void scriptDownloaded(); // old version
|
||||
void scriptContentAvailable(); // new version
|
||||
|
||||
private:
|
||||
ScriptCache(QObject* parent = NULL);
|
||||
|
||||
QMultiMap<QUrl, contentAvailableCallback> _contentCallbacks;
|
||||
|
||||
QHash<QUrl, QString> _scriptCache;
|
||||
QMultiMap<QUrl, ScriptUser*> _scriptUsers;
|
||||
QSet<QUrl> _badScripts;
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
#include <QtNetwork/QNetworkRequest>
|
||||
#include <QtNetwork/QNetworkReply>
|
||||
#include <QScriptEngine>
|
||||
#include <QScriptValue>
|
||||
|
||||
#include <AudioConstants.h>
|
||||
#include <AudioEffectOptions.h>
|
||||
|
@ -848,3 +849,181 @@ void ScriptEngine::generalHandler(const EntityItemID& entityID, const QString& e
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
// since all of these operations can be asynch we will always do the actual work in the response handler
|
||||
// for the download
|
||||
void ScriptEngine::loadEntityScript(const EntityItemID& entityID, const QString& entityScript, bool forceRedownload) {
|
||||
if (QThread::currentThread() != thread()) {
|
||||
qDebug() << "*** WARNING *** ScriptEngine::loadEntityScript() called on wrong thread [" << QThread::currentThread() << "], invoking on correct thread [" << thread() << "] "
|
||||
"entityID:" << entityID <<
|
||||
"entityScript:" << entityScript <<
|
||||
"forceRedownload:" << forceRedownload;
|
||||
|
||||
QMetaObject::invokeMethod(this, "loadEntityScript",
|
||||
Q_ARG(const EntityItemID&, entityID),
|
||||
Q_ARG(const QString&, entityScript),
|
||||
Q_ARG(bool, forceRedownload));
|
||||
return;
|
||||
}
|
||||
qDebug() << "ScriptEngine::loadEntityScript() called on correct thread [" << thread() << "] "
|
||||
"entityID:" << entityID <<
|
||||
"entityScript:" << entityScript <<
|
||||
"forceRedownload:" << forceRedownload;
|
||||
|
||||
// If we've been called our known entityScripts should not know about us..
|
||||
assert(!_entityScripts.contains(entityID));
|
||||
|
||||
auto scriptCache = DependencyManager::get<ScriptCache>();
|
||||
|
||||
scriptCache->getScriptContents(entityScript, [=](const QString& scriptOrURL, const QString& contents, bool isURL, bool success) {
|
||||
|
||||
// first check the syntax of the script contents
|
||||
QScriptSyntaxCheckResult syntaxCheck = QScriptEngine::checkSyntax(contents);
|
||||
if (syntaxCheck.state() != QScriptSyntaxCheckResult::Valid) {
|
||||
qCDebug(scriptengine) << "ScriptEngine::loadEntityScript() entity:" << entityID;
|
||||
qCDebug(scriptengine) << " " << syntaxCheck.errorMessage() << ":"
|
||||
<< syntaxCheck.errorLineNumber() << syntaxCheck.errorColumnNumber();
|
||||
qCDebug(scriptengine) << " SCRIPT:" << scriptOrURL;
|
||||
scriptCache->addScriptToBadScriptList(scriptOrURL);
|
||||
return; // done processing script
|
||||
}
|
||||
|
||||
if (isURL) {
|
||||
setParentURL(scriptOrURL);
|
||||
}
|
||||
|
||||
QScriptEngine sandbox;
|
||||
QScriptValue testConstructor = sandbox.evaluate(contents);
|
||||
|
||||
if (!testConstructor.isFunction()) {
|
||||
qCDebug(scriptengine) << "ScriptEngine::loadEntityScript() entity:" << entityID;
|
||||
qCDebug(scriptengine) << " NOT CONSTRUCTOR";
|
||||
qCDebug(scriptengine) << " SCRIPT:" << scriptOrURL;
|
||||
scriptCache->addScriptToBadScriptList(scriptOrURL);
|
||||
return; // done processing script
|
||||
}
|
||||
|
||||
QScriptValue entityScriptConstructor = evaluate(contents);
|
||||
|
||||
QScriptValue entityScriptObject = entityScriptConstructor.construct();
|
||||
EntityScriptDetails newDetails = { scriptOrURL, entityScriptObject };
|
||||
_entityScripts[entityID] = newDetails;
|
||||
|
||||
if (isURL) {
|
||||
setParentURL("");
|
||||
}
|
||||
|
||||
// if we got this far, then call the preload method
|
||||
callEntityScriptMethod(entityID, "preload");
|
||||
|
||||
}, forceRedownload);
|
||||
}
|
||||
|
||||
void ScriptEngine::unloadEntityScript(const EntityItemID& entityID) {
|
||||
if (QThread::currentThread() != thread()) {
|
||||
qDebug() << "*** WARNING *** ScriptEngine::unloadEntityScript() called on wrong thread [" << QThread::currentThread() << "], invoking on correct thread [" << thread() << "] "
|
||||
"entityID:" << entityID;
|
||||
|
||||
QMetaObject::invokeMethod(this, "unloadEntityScript",
|
||||
Q_ARG(const EntityItemID&, entityID));
|
||||
return;
|
||||
}
|
||||
qDebug() << "ScriptEngine::unloadEntityScript() called on correct thread [" << thread() << "] "
|
||||
"entityID:" << entityID;
|
||||
|
||||
if (_entityScripts.contains(entityID)) {
|
||||
callEntityScriptMethod(entityID, "unload");
|
||||
_entityScripts.remove(entityID);
|
||||
}
|
||||
}
|
||||
|
||||
void ScriptEngine::callEntityScriptMethod(const EntityItemID& entityID, const QString& methodName) {
|
||||
if (QThread::currentThread() != thread()) {
|
||||
qDebug() << "*** WARNING *** ScriptEngine::callEntityScriptMethod() called on wrong thread [" << QThread::currentThread() << "], invoking on correct thread [" << thread() << "] "
|
||||
"entityID:" << entityID <<
|
||||
"methodName:" << methodName;
|
||||
|
||||
QMetaObject::invokeMethod(this, "callEntityScriptMethod",
|
||||
Q_ARG(const EntityItemID&, entityID),
|
||||
Q_ARG(const QString&, methodName));
|
||||
return;
|
||||
}
|
||||
qDebug() << "ScriptEngine::callEntityScriptMethod() called on correct thread [" << thread() << "] "
|
||||
"entityID:" << entityID <<
|
||||
"methodName:" << methodName;
|
||||
|
||||
if (_entityScripts.contains(entityID)) {
|
||||
EntityScriptDetails details = _entityScripts[entityID];
|
||||
QScriptValue entityScript = details.scriptObject; // previously loaded
|
||||
if (entityScript.property(methodName).isFunction()) {
|
||||
QScriptValueList args;
|
||||
args << entityID.toScriptValue(this);
|
||||
entityScript.property(methodName).call(entityScript, args);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ScriptEngine::callEntityScriptMethod(const EntityItemID& entityID, const QString& methodName, const MouseEvent& event) {
|
||||
if (QThread::currentThread() != thread()) {
|
||||
qDebug() << "*** WARNING *** ScriptEngine::callEntityScriptMethod() called on wrong thread [" << QThread::currentThread() << "], invoking on correct thread [" << thread() << "] "
|
||||
"entityID:" << entityID <<
|
||||
"methodName:" << methodName <<
|
||||
"event: mouseEvent";
|
||||
|
||||
QMetaObject::invokeMethod(this, "callEntityScriptMethod",
|
||||
Q_ARG(const EntityItemID&, entityID),
|
||||
Q_ARG(const QString&, methodName),
|
||||
Q_ARG(const MouseEvent&, event));
|
||||
return;
|
||||
}
|
||||
qDebug() << "ScriptEngine::callEntityScriptMethod() called on correct thread [" << thread() << "] "
|
||||
"entityID:" << entityID <<
|
||||
"methodName:" << methodName <<
|
||||
"event: mouseEvent";
|
||||
|
||||
if (_entityScripts.contains(entityID)) {
|
||||
EntityScriptDetails details = _entityScripts[entityID];
|
||||
QScriptValue entityScript = details.scriptObject; // previously loaded
|
||||
if (entityScript.property(methodName).isFunction()) {
|
||||
QScriptValueList args;
|
||||
args << entityID.toScriptValue(this);
|
||||
args << event.toScriptValue(this);
|
||||
entityScript.property(methodName).call(entityScript, args);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void ScriptEngine::callEntityScriptMethod(const EntityItemID& entityID, const QString& methodName, const EntityItemID& otherID, const Collision& collision) {
|
||||
if (QThread::currentThread() != thread()) {
|
||||
qDebug() << "*** WARNING *** ScriptEngine::callEntityScriptMethod() called on wrong thread [" << QThread::currentThread() << "], invoking on correct thread [" << thread() << "] "
|
||||
"entityID:" << entityID <<
|
||||
"methodName:" << methodName <<
|
||||
"otherID:" << otherID <<
|
||||
"collision: collision";
|
||||
|
||||
QMetaObject::invokeMethod(this, "callEntityScriptMethod",
|
||||
Q_ARG(const EntityItemID&, entityID),
|
||||
Q_ARG(const QString&, methodName),
|
||||
Q_ARG(const EntityItemID&, otherID),
|
||||
Q_ARG(const Collision&, collision));
|
||||
return;
|
||||
}
|
||||
qDebug() << "ScriptEngine::callEntityScriptMethod() called on correct thread [" << thread() << "] "
|
||||
"entityID:" << entityID <<
|
||||
"methodName:" << methodName <<
|
||||
"otherID:" << otherID <<
|
||||
"collision: collision";
|
||||
|
||||
if (_entityScripts.contains(entityID)) {
|
||||
EntityScriptDetails details = _entityScripts[entityID];
|
||||
QScriptValue entityScript = details.scriptObject; // previously loaded
|
||||
if (entityScript.property(methodName).isFunction()) {
|
||||
QScriptValueList args;
|
||||
args << entityID.toScriptValue(this);
|
||||
args << otherID.toScriptValue(this);
|
||||
args << collisionToScriptValue(this, collision);
|
||||
entityScript.property(methodName).call(entityScript, args);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -40,6 +40,12 @@ const unsigned int SCRIPT_DATA_CALLBACK_USECS = floor(((1.0f / 60.0f) * 1000 * 1
|
|||
|
||||
typedef QHash<QString, QScriptValueList> RegisteredEventHandlers;
|
||||
|
||||
class EntityScriptDetails {
|
||||
public:
|
||||
QString scriptText;
|
||||
QScriptValue scriptObject;
|
||||
};
|
||||
|
||||
class ScriptEngine : public QScriptEngine, public ScriptUser {
|
||||
Q_OBJECT
|
||||
public:
|
||||
|
@ -100,6 +106,13 @@ public:
|
|||
Q_INVOKABLE void print(const QString& message);
|
||||
Q_INVOKABLE QUrl resolvePath(const QString& path) const;
|
||||
|
||||
// Entity Script Related methods
|
||||
Q_INVOKABLE void loadEntityScript(const EntityItemID& entityID, const QString& entityScript, bool forceRedownload = false); // will call the preload method once loaded
|
||||
Q_INVOKABLE void unloadEntityScript(const EntityItemID& entityID); // will call unload method
|
||||
Q_INVOKABLE void callEntityScriptMethod(const EntityItemID& entityID, const QString& methodName);
|
||||
Q_INVOKABLE void callEntityScriptMethod(const EntityItemID& entityID, const QString& methodName, const MouseEvent& event);
|
||||
Q_INVOKABLE void callEntityScriptMethod(const EntityItemID& entityID, const QString& methodName, const EntityItemID& otherID, const Collision& collision);
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// NOTE - this is intended to be a public interface for Agent scripts, and local scripts, but not for EntityScripts
|
||||
Q_INVOKABLE void stop();
|
||||
|
@ -149,6 +162,9 @@ protected:
|
|||
QSet<QUrl> _includedURLs;
|
||||
bool _wantSignals = true;
|
||||
|
||||
QHash<EntityItemID, EntityScriptDetails> _entityScripts;
|
||||
|
||||
|
||||
private:
|
||||
void init();
|
||||
QString getFilename() const;
|
||||
|
|
Loading…
Reference in a new issue