mirror of
https://github.com/overte-org/overte.git
synced 2025-08-08 13:38:02 +02:00
Merge pull request #5804 from ZappoMan/scriptEngineCleanup
ScriptEngine Threading Repairs
This commit is contained in:
commit
4ce1db16f4
11 changed files with 827 additions and 761 deletions
|
@ -42,8 +42,7 @@ Agent::Agent(NLPacket& packet) :
|
||||||
DEFAULT_WINDOW_SECONDS_FOR_DESIRED_REDUCTION, false))
|
DEFAULT_WINDOW_SECONDS_FOR_DESIRED_REDUCTION, false))
|
||||||
{
|
{
|
||||||
// be the parent of the script engine so it gets moved when we do
|
// be the parent of the script engine so it gets moved when we do
|
||||||
_scriptEngine.setParent(this);
|
_scriptEngine->setParent(this);
|
||||||
_scriptEngine.setIsAgent(true);
|
|
||||||
|
|
||||||
DependencyManager::get<EntityScriptingInterface>()->setPacketSender(&_entityEditSender);
|
DependencyManager::get<EntityScriptingInterface>()->setPacketSender(&_entityEditSender);
|
||||||
|
|
||||||
|
@ -157,8 +156,10 @@ void Agent::run() {
|
||||||
|
|
||||||
qDebug() << "Downloaded script:" << scriptContents;
|
qDebug() << "Downloaded script:" << scriptContents;
|
||||||
|
|
||||||
|
_scriptEngine = new ScriptEngine(scriptContents, _payload);
|
||||||
|
|
||||||
// setup an Avatar for the script to use
|
// setup an Avatar for the script to use
|
||||||
ScriptableAvatar scriptedAvatar(&_scriptEngine);
|
ScriptableAvatar scriptedAvatar(_scriptEngine);
|
||||||
scriptedAvatar.setForceFaceTrackerConnected(true);
|
scriptedAvatar.setForceFaceTrackerConnected(true);
|
||||||
|
|
||||||
// call model URL setters with empty URLs so our avatar, if user, will have the default models
|
// call model URL setters with empty URLs so our avatar, if user, will have the default models
|
||||||
|
@ -166,11 +167,10 @@ void Agent::run() {
|
||||||
scriptedAvatar.setSkeletonModelURL(QUrl());
|
scriptedAvatar.setSkeletonModelURL(QUrl());
|
||||||
|
|
||||||
// give this AvatarData object to the script engine
|
// give this AvatarData object to the script engine
|
||||||
_scriptEngine.setAvatarData(&scriptedAvatar, "Avatar");
|
setAvatarData(&scriptedAvatar, "Avatar");
|
||||||
|
|
||||||
auto avatarHashMap = DependencyManager::set<AvatarHashMap>();
|
auto avatarHashMap = DependencyManager::set<AvatarHashMap>();
|
||||||
|
_scriptEngine->registerGlobalObject("AvatarList", avatarHashMap.data());
|
||||||
_scriptEngine.setAvatarHashMap(avatarHashMap.data(), "AvatarList");
|
|
||||||
|
|
||||||
auto& packetReceiver = DependencyManager::get<NodeList>()->getPacketReceiver();
|
auto& packetReceiver = DependencyManager::get<NodeList>()->getPacketReceiver();
|
||||||
packetReceiver.registerListener(PacketType::BulkAvatarData, avatarHashMap.data(), "processAvatarDataPacket");
|
packetReceiver.registerListener(PacketType::BulkAvatarData, avatarHashMap.data(), "processAvatarDataPacket");
|
||||||
|
@ -179,33 +179,189 @@ void Agent::run() {
|
||||||
packetReceiver.registerListener(PacketType::AvatarBillboard, avatarHashMap.data(), "processAvatarBillboardPacket");
|
packetReceiver.registerListener(PacketType::AvatarBillboard, avatarHashMap.data(), "processAvatarBillboardPacket");
|
||||||
|
|
||||||
// register ourselves to the script engine
|
// register ourselves to the script engine
|
||||||
_scriptEngine.registerGlobalObject("Agent", this);
|
_scriptEngine->registerGlobalObject("Agent", this);
|
||||||
|
|
||||||
if (!_payload.isEmpty()) {
|
// FIXME -we shouldn't be calling this directly, it's normally called by run(), not sure why
|
||||||
_scriptEngine.setParentURL(_payload);
|
// viewers would need this called.
|
||||||
}
|
//_scriptEngine->init(); // must be done before we set up the viewers
|
||||||
|
|
||||||
_scriptEngine.init(); // must be done before we set up the viewers
|
_scriptEngine->registerGlobalObject("SoundCache", DependencyManager::get<SoundCache>().data());
|
||||||
|
|
||||||
_scriptEngine.registerGlobalObject("SoundCache", DependencyManager::get<SoundCache>().data());
|
QScriptValue webSocketServerConstructorValue = _scriptEngine->newFunction(WebSocketServerClass::constructor);
|
||||||
|
_scriptEngine->globalObject().setProperty("WebSocketServer", webSocketServerConstructorValue);
|
||||||
QScriptValue webSocketServerConstructorValue = _scriptEngine.newFunction(WebSocketServerClass::constructor);
|
|
||||||
_scriptEngine.globalObject().setProperty("WebSocketServer", webSocketServerConstructorValue);
|
|
||||||
|
|
||||||
auto entityScriptingInterface = DependencyManager::get<EntityScriptingInterface>();
|
auto entityScriptingInterface = DependencyManager::get<EntityScriptingInterface>();
|
||||||
|
|
||||||
_scriptEngine.registerGlobalObject("EntityViewer", &_entityViewer);
|
_scriptEngine->registerGlobalObject("EntityViewer", &_entityViewer);
|
||||||
_entityViewer.setJurisdictionListener(entityScriptingInterface->getJurisdictionListener());
|
_entityViewer.setJurisdictionListener(entityScriptingInterface->getJurisdictionListener());
|
||||||
_entityViewer.init();
|
_entityViewer.init();
|
||||||
entityScriptingInterface->setEntityTree(_entityViewer.getTree());
|
entityScriptingInterface->setEntityTree(_entityViewer.getTree());
|
||||||
|
|
||||||
_scriptEngine.setScriptContents(scriptContents);
|
// wire up our additional agent related processing to the update signal
|
||||||
_scriptEngine.run();
|
QObject::connect(_scriptEngine, &ScriptEngine::update, this, &Agent::processAgentAvatarAndAudio);
|
||||||
|
|
||||||
|
_scriptEngine->run();
|
||||||
setFinished(true);
|
setFinished(true);
|
||||||
|
|
||||||
|
// kill the avatar identity timer
|
||||||
|
delete _avatarIdentityTimer;
|
||||||
|
|
||||||
|
// delete the script engine
|
||||||
|
delete _scriptEngine;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void Agent::setIsAvatar(bool isAvatar) {
|
||||||
|
_isAvatar = isAvatar;
|
||||||
|
|
||||||
|
if (_isAvatar && !_avatarIdentityTimer) {
|
||||||
|
// set up the avatar timers
|
||||||
|
_avatarIdentityTimer = new QTimer(this);
|
||||||
|
_avatarBillboardTimer = new QTimer(this);
|
||||||
|
|
||||||
|
// connect our slot
|
||||||
|
connect(_avatarIdentityTimer, &QTimer::timeout, this, &Agent::sendAvatarIdentityPacket);
|
||||||
|
connect(_avatarBillboardTimer, &QTimer::timeout, this, &Agent::sendAvatarBillboardPacket);
|
||||||
|
|
||||||
|
// start the timers
|
||||||
|
_avatarIdentityTimer->start(AVATAR_IDENTITY_PACKET_SEND_INTERVAL_MSECS);
|
||||||
|
_avatarBillboardTimer->start(AVATAR_BILLBOARD_PACKET_SEND_INTERVAL_MSECS);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!_isAvatar) {
|
||||||
|
delete _avatarIdentityTimer;
|
||||||
|
_avatarIdentityTimer = NULL;
|
||||||
|
delete _avatarBillboardTimer;
|
||||||
|
_avatarBillboardTimer = NULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Agent::setAvatarData(AvatarData* avatarData, const QString& objectName) {
|
||||||
|
_avatarData = avatarData;
|
||||||
|
_scriptEngine->registerGlobalObject(objectName, avatarData);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Agent::sendAvatarIdentityPacket() {
|
||||||
|
if (_isAvatar && _avatarData) {
|
||||||
|
_avatarData->sendIdentityPacket();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Agent::sendAvatarBillboardPacket() {
|
||||||
|
if (_isAvatar && _avatarData) {
|
||||||
|
_avatarData->sendBillboardPacket();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void Agent::processAgentAvatarAndAudio(float deltaTime) {
|
||||||
|
qDebug() << "processAgentAvatarAndAudio()";
|
||||||
|
if (!_scriptEngine->isFinished() && _isAvatar && _avatarData) {
|
||||||
|
|
||||||
|
const int SCRIPT_AUDIO_BUFFER_SAMPLES = floor(((SCRIPT_DATA_CALLBACK_USECS * AudioConstants::SAMPLE_RATE)
|
||||||
|
/ (1000 * 1000)) + 0.5);
|
||||||
|
const int SCRIPT_AUDIO_BUFFER_BYTES = SCRIPT_AUDIO_BUFFER_SAMPLES * sizeof(int16_t);
|
||||||
|
|
||||||
|
QByteArray avatarByteArray = _avatarData->toByteArray(true, randFloat() < AVATAR_SEND_FULL_UPDATE_RATIO);
|
||||||
|
_avatarData->doneEncoding(true);
|
||||||
|
auto avatarPacket = NLPacket::create(PacketType::AvatarData, avatarByteArray.size());
|
||||||
|
|
||||||
|
avatarPacket->write(avatarByteArray);
|
||||||
|
|
||||||
|
auto nodeList = DependencyManager::get<NodeList>();
|
||||||
|
|
||||||
|
nodeList->broadcastToNodes(std::move(avatarPacket), NodeSet() << NodeType::AvatarMixer);
|
||||||
|
|
||||||
|
if (_isListeningToAudioStream || _avatarSound) {
|
||||||
|
// if we have an avatar audio stream then send it out to our audio-mixer
|
||||||
|
bool silentFrame = true;
|
||||||
|
|
||||||
|
int16_t numAvailableSamples = SCRIPT_AUDIO_BUFFER_SAMPLES;
|
||||||
|
const int16_t* nextSoundOutput = NULL;
|
||||||
|
|
||||||
|
if (_avatarSound) {
|
||||||
|
|
||||||
|
const QByteArray& soundByteArray = _avatarSound->getByteArray();
|
||||||
|
nextSoundOutput = reinterpret_cast<const int16_t*>(soundByteArray.data()
|
||||||
|
+ _numAvatarSoundSentBytes);
|
||||||
|
|
||||||
|
int numAvailableBytes = (soundByteArray.size() - _numAvatarSoundSentBytes) > SCRIPT_AUDIO_BUFFER_BYTES
|
||||||
|
? SCRIPT_AUDIO_BUFFER_BYTES
|
||||||
|
: soundByteArray.size() - _numAvatarSoundSentBytes;
|
||||||
|
numAvailableSamples = numAvailableBytes / sizeof(int16_t);
|
||||||
|
|
||||||
|
|
||||||
|
// check if the all of the _numAvatarAudioBufferSamples to be sent are silence
|
||||||
|
for (int i = 0; i < numAvailableSamples; ++i) {
|
||||||
|
if (nextSoundOutput[i] != 0) {
|
||||||
|
silentFrame = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_numAvatarSoundSentBytes += numAvailableBytes;
|
||||||
|
if (_numAvatarSoundSentBytes == soundByteArray.size()) {
|
||||||
|
// we're done with this sound object - so set our pointer back to NULL
|
||||||
|
// and our sent bytes back to zero
|
||||||
|
_avatarSound = NULL;
|
||||||
|
_numAvatarSoundSentBytes = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
auto audioPacket = NLPacket::create(silentFrame
|
||||||
|
? PacketType::SilentAudioFrame
|
||||||
|
: PacketType::MicrophoneAudioNoEcho);
|
||||||
|
|
||||||
|
// seek past the sequence number, will be packed when destination node is known
|
||||||
|
audioPacket->seek(sizeof(quint16));
|
||||||
|
|
||||||
|
if (silentFrame) {
|
||||||
|
if (!_isListeningToAudioStream) {
|
||||||
|
// if we have a silent frame and we're not listening then just send nothing and break out of here
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// write the number of silent samples so the audio-mixer can uphold timing
|
||||||
|
audioPacket->writePrimitive(SCRIPT_AUDIO_BUFFER_SAMPLES);
|
||||||
|
|
||||||
|
// use the orientation and position of this avatar for the source of this audio
|
||||||
|
audioPacket->writePrimitive(_avatarData->getPosition());
|
||||||
|
glm::quat headOrientation = _avatarData->getHeadOrientation();
|
||||||
|
audioPacket->writePrimitive(headOrientation);
|
||||||
|
|
||||||
|
}else if (nextSoundOutput) {
|
||||||
|
// assume scripted avatar audio is mono and set channel flag to zero
|
||||||
|
audioPacket->writePrimitive((quint8)0);
|
||||||
|
|
||||||
|
// use the orientation and position of this avatar for the source of this audio
|
||||||
|
audioPacket->writePrimitive(_avatarData->getPosition());
|
||||||
|
glm::quat headOrientation = _avatarData->getHeadOrientation();
|
||||||
|
audioPacket->writePrimitive(headOrientation);
|
||||||
|
|
||||||
|
// write the raw audio data
|
||||||
|
audioPacket->write(reinterpret_cast<const char*>(nextSoundOutput), numAvailableSamples * sizeof(int16_t));
|
||||||
|
}
|
||||||
|
|
||||||
|
// write audio packet to AudioMixer nodes
|
||||||
|
auto nodeList = DependencyManager::get<NodeList>();
|
||||||
|
nodeList->eachNode([this, &nodeList, &audioPacket](const SharedNodePointer& node){
|
||||||
|
// only send to nodes of type AudioMixer
|
||||||
|
if (node->getType() == NodeType::AudioMixer) {
|
||||||
|
// pack sequence number
|
||||||
|
quint16 sequence = _outgoingScriptAudioSequenceNumbers[node->getUUID()]++;
|
||||||
|
audioPacket->seek(0);
|
||||||
|
audioPacket->writePrimitive(sequence);
|
||||||
|
|
||||||
|
// send audio packet
|
||||||
|
nodeList->sendUnreliablePacket(*audioPacket, *node);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Agent::aboutToFinish() {
|
void Agent::aboutToFinish() {
|
||||||
_scriptEngine.stop();
|
_scriptEngine->stop();
|
||||||
|
|
||||||
_pingTimer->stop();
|
_pingTimer->stop();
|
||||||
delete _pingTimer;
|
delete _pingTimer;
|
||||||
|
|
|
@ -37,14 +37,13 @@ class Agent : public ThreadedAssignment {
|
||||||
public:
|
public:
|
||||||
Agent(NLPacket& packet);
|
Agent(NLPacket& packet);
|
||||||
|
|
||||||
void setIsAvatar(bool isAvatar) { QMetaObject::invokeMethod(&_scriptEngine, "setIsAvatar", Q_ARG(bool, isAvatar)); }
|
void setIsAvatar(bool isAvatar);
|
||||||
bool isAvatar() const { return _scriptEngine.isAvatar(); }
|
bool isAvatar() const { return _isAvatar; }
|
||||||
|
|
||||||
bool isPlayingAvatarSound() const { return _scriptEngine.isPlayingAvatarSound(); }
|
bool isPlayingAvatarSound() const { return _avatarSound != NULL; }
|
||||||
|
|
||||||
bool isListeningToAudioStream() const { return _scriptEngine.isListeningToAudioStream(); }
|
bool isListeningToAudioStream() const { return _isListeningToAudioStream; }
|
||||||
void setIsListeningToAudioStream(bool isListeningToAudioStream)
|
void setIsListeningToAudioStream(bool isListeningToAudioStream) { _isListeningToAudioStream = isListeningToAudioStream; }
|
||||||
{ _scriptEngine.setIsListeningToAudioStream(isListeningToAudioStream); }
|
|
||||||
|
|
||||||
float getLastReceivedAudioLoudness() const { return _lastReceivedAudioLoudness; }
|
float getLastReceivedAudioLoudness() const { return _lastReceivedAudioLoudness; }
|
||||||
|
|
||||||
|
@ -52,22 +51,39 @@ public:
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
void run();
|
void run();
|
||||||
void playAvatarSound(Sound* avatarSound) { _scriptEngine.setAvatarSound(avatarSound); }
|
void playAvatarSound(Sound* avatarSound) { setAvatarSound(avatarSound); }
|
||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
void handleAudioPacket(QSharedPointer<NLPacket> packet);
|
void handleAudioPacket(QSharedPointer<NLPacket> packet);
|
||||||
void handleOctreePacket(QSharedPointer<NLPacket> packet, SharedNodePointer senderNode);
|
void handleOctreePacket(QSharedPointer<NLPacket> packet, SharedNodePointer senderNode);
|
||||||
void handleJurisdictionPacket(QSharedPointer<NLPacket> packet, SharedNodePointer senderNode);
|
void handleJurisdictionPacket(QSharedPointer<NLPacket> packet, SharedNodePointer senderNode);
|
||||||
void sendPingRequests();
|
void sendPingRequests();
|
||||||
|
void processAgentAvatarAndAudio(float deltaTime);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
ScriptEngine _scriptEngine;
|
ScriptEngine* _scriptEngine;
|
||||||
EntityEditPacketSender _entityEditSender;
|
EntityEditPacketSender _entityEditSender;
|
||||||
EntityTreeHeadlessViewer _entityViewer;
|
EntityTreeHeadlessViewer _entityViewer;
|
||||||
QTimer* _pingTimer;
|
QTimer* _pingTimer;
|
||||||
|
|
||||||
MixedAudioStream _receivedAudioStream;
|
MixedAudioStream _receivedAudioStream;
|
||||||
float _lastReceivedAudioLoudness;
|
float _lastReceivedAudioLoudness;
|
||||||
|
|
||||||
|
void setAvatarData(AvatarData* avatarData, const QString& objectName);
|
||||||
|
void setAvatarSound(Sound* avatarSound) { _avatarSound = avatarSound; }
|
||||||
|
|
||||||
|
void sendAvatarIdentityPacket();
|
||||||
|
void sendAvatarBillboardPacket();
|
||||||
|
|
||||||
|
AvatarData* _avatarData = nullptr;
|
||||||
|
bool _isListeningToAudioStream = false;
|
||||||
|
Sound* _avatarSound = nullptr;
|
||||||
|
int _numAvatarSoundSentBytes = 0;
|
||||||
|
bool _isAvatar = false;
|
||||||
|
QTimer* _avatarIdentityTimer = nullptr;
|
||||||
|
QTimer* _avatarBillboardTimer = nullptr;
|
||||||
|
QHash<QUuid, quint16> _outgoingScriptAudioSequenceNumbers;
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // hifi_Agent_h
|
#endif // hifi_Agent_h
|
||||||
|
|
|
@ -4026,8 +4026,8 @@ void Application::registerScriptEngineWithApplicationServices(ScriptEngine* scri
|
||||||
AvatarManager::registerMetaTypes(scriptEngine);
|
AvatarManager::registerMetaTypes(scriptEngine);
|
||||||
|
|
||||||
// hook our avatar and avatar hash map object into this script engine
|
// hook our avatar and avatar hash map object into this script engine
|
||||||
scriptEngine->setAvatarData(_myAvatar, "MyAvatar"); // leave it as a MyAvatar class to expose thrust features
|
scriptEngine->registerGlobalObject("MyAvatar", _myAvatar);
|
||||||
scriptEngine->setAvatarHashMap(DependencyManager::get<AvatarManager>().data(), "AvatarList");
|
scriptEngine->registerGlobalObject("AvatarList", DependencyManager::get<AvatarManager>().data());
|
||||||
|
|
||||||
scriptEngine->registerGlobalObject("Camera", &_myCamera);
|
scriptEngine->registerGlobalObject("Camera", &_myCamera);
|
||||||
|
|
||||||
|
@ -4051,9 +4051,9 @@ void Application::registerScriptEngineWithApplicationServices(ScriptEngine* scri
|
||||||
|
|
||||||
scriptEngine->registerGlobalObject("Desktop", DependencyManager::get<DesktopScriptingInterface>().data());
|
scriptEngine->registerGlobalObject("Desktop", DependencyManager::get<DesktopScriptingInterface>().data());
|
||||||
|
|
||||||
QScriptValue windowValue = scriptEngine->registerGlobalObject("Window", DependencyManager::get<WindowScriptingInterface>().data());
|
scriptEngine->registerGlobalObject("Window", DependencyManager::get<WindowScriptingInterface>().data());
|
||||||
scriptEngine->registerGetterSetter("location", LocationScriptingInterface::locationGetter,
|
scriptEngine->registerGetterSetter("location", LocationScriptingInterface::locationGetter,
|
||||||
LocationScriptingInterface::locationSetter, windowValue);
|
LocationScriptingInterface::locationSetter, "Window");
|
||||||
// register `location` on the global object.
|
// register `location` on the global object.
|
||||||
scriptEngine->registerGetterSetter("location", LocationScriptingInterface::locationGetter,
|
scriptEngine->registerGetterSetter("location", LocationScriptingInterface::locationGetter,
|
||||||
LocationScriptingInterface::locationSetter);
|
LocationScriptingInterface::locationSetter);
|
||||||
|
@ -4083,9 +4083,9 @@ void Application::registerScriptEngineWithApplicationServices(ScriptEngine* scri
|
||||||
|
|
||||||
scriptEngine->registerGlobalObject("Paths", DependencyManager::get<PathUtils>().data());
|
scriptEngine->registerGlobalObject("Paths", DependencyManager::get<PathUtils>().data());
|
||||||
|
|
||||||
QScriptValue hmdInterface = scriptEngine->registerGlobalObject("HMD", &HMDScriptingInterface::getInstance());
|
scriptEngine->registerGlobalObject("HMD", &HMDScriptingInterface::getInstance());
|
||||||
scriptEngine->registerFunction(hmdInterface, "getHUDLookAtPosition2D", HMDScriptingInterface::getHUDLookAtPosition2D, 0);
|
scriptEngine->registerFunction("HMD", "getHUDLookAtPosition2D", HMDScriptingInterface::getHUDLookAtPosition2D, 0);
|
||||||
scriptEngine->registerFunction(hmdInterface, "getHUDLookAtPosition3D", HMDScriptingInterface::getHUDLookAtPosition3D, 0);
|
scriptEngine->registerFunction("HMD", "getHUDLookAtPosition3D", HMDScriptingInterface::getHUDLookAtPosition3D, 0);
|
||||||
|
|
||||||
scriptEngine->registerGlobalObject("Scene", DependencyManager::get<SceneScriptingInterface>().data());
|
scriptEngine->registerGlobalObject("Scene", DependencyManager::get<SceneScriptingInterface>().data());
|
||||||
|
|
||||||
|
@ -4094,31 +4094,6 @@ void Application::registerScriptEngineWithApplicationServices(ScriptEngine* scri
|
||||||
#ifdef HAVE_RTMIDI
|
#ifdef HAVE_RTMIDI
|
||||||
scriptEngine->registerGlobalObject("MIDI", &MIDIManager::getInstance());
|
scriptEngine->registerGlobalObject("MIDI", &MIDIManager::getInstance());
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// TODO: Consider moving some of this functionality into the ScriptEngine class instead. It seems wrong that this
|
|
||||||
// work is being done in the Application class when really these dependencies are more related to the ScriptEngine's
|
|
||||||
// implementation
|
|
||||||
QThread* workerThread = new QThread(this);
|
|
||||||
QString scriptEngineName = QString("Script Thread:") + scriptEngine->getFilename();
|
|
||||||
workerThread->setObjectName(scriptEngineName);
|
|
||||||
|
|
||||||
// when the worker thread is started, call our engine's run..
|
|
||||||
connect(workerThread, &QThread::started, scriptEngine, &ScriptEngine::run);
|
|
||||||
|
|
||||||
// when the thread is terminated, add both scriptEngine and thread to the deleteLater queue
|
|
||||||
connect(scriptEngine, &ScriptEngine::doneRunning, scriptEngine, &ScriptEngine::deleteLater);
|
|
||||||
connect(workerThread, &QThread::finished, workerThread, &QThread::deleteLater);
|
|
||||||
|
|
||||||
// tell the thread to stop when the script engine is done
|
|
||||||
connect(scriptEngine, &ScriptEngine::destroyed, workerThread, &QThread::quit);
|
|
||||||
|
|
||||||
auto nodeList = DependencyManager::get<NodeList>();
|
|
||||||
connect(nodeList.data(), &NodeList::nodeKilled, scriptEngine, &ScriptEngine::nodeKilled);
|
|
||||||
|
|
||||||
scriptEngine->moveToThread(workerThread);
|
|
||||||
|
|
||||||
// Starts an event loop, and emits workerThread->started()
|
|
||||||
workerThread->start();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Application::initializeAcceptedFiles() {
|
void Application::initializeAcceptedFiles() {
|
||||||
|
@ -4264,11 +4239,14 @@ ScriptEngine* Application::loadScript(const QString& scriptFilename, bool isUser
|
||||||
scriptEngine->setUserLoaded(isUserLoaded);
|
scriptEngine->setUserLoaded(isUserLoaded);
|
||||||
|
|
||||||
if (scriptFilename.isNull()) {
|
if (scriptFilename.isNull()) {
|
||||||
|
// This appears to be the script engine used by the script widget's evaluation window before the file has been saved...
|
||||||
|
|
||||||
// this had better be the script editor (we should de-couple so somebody who thinks they are loading a script
|
// this had better be the script editor (we should de-couple so somebody who thinks they are loading a script
|
||||||
// doesn't just get an empty script engine)
|
// doesn't just get an empty script engine)
|
||||||
|
|
||||||
// we can complete setup now since there isn't a script we have to load
|
// we can complete setup now since there isn't a script we have to load
|
||||||
registerScriptEngineWithApplicationServices(scriptEngine);
|
registerScriptEngineWithApplicationServices(scriptEngine);
|
||||||
|
scriptEngine->runInThread();
|
||||||
} else {
|
} else {
|
||||||
// connect to the appropriate signals of this script engine
|
// connect to the appropriate signals of this script engine
|
||||||
connect(scriptEngine, &ScriptEngine::scriptLoaded, this, &Application::handleScriptEngineLoaded);
|
connect(scriptEngine, &ScriptEngine::scriptLoaded, this, &Application::handleScriptEngineLoaded);
|
||||||
|
@ -4290,6 +4268,7 @@ void Application::reloadScript(const QString& scriptName, bool isUserLoaded) {
|
||||||
loadScript(scriptName, isUserLoaded, false, false, true);
|
loadScript(scriptName, isUserLoaded, false, false, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// FIXME - change to new version of ScriptCache loading notification
|
||||||
void Application::handleScriptEngineLoaded(const QString& scriptFilename) {
|
void Application::handleScriptEngineLoaded(const QString& scriptFilename) {
|
||||||
ScriptEngine* scriptEngine = qobject_cast<ScriptEngine*>(sender());
|
ScriptEngine* scriptEngine = qobject_cast<ScriptEngine*>(sender());
|
||||||
|
|
||||||
|
@ -4299,8 +4278,10 @@ void Application::handleScriptEngineLoaded(const QString& scriptFilename) {
|
||||||
|
|
||||||
// register our application services and set it off on its own thread
|
// register our application services and set it off on its own thread
|
||||||
registerScriptEngineWithApplicationServices(scriptEngine);
|
registerScriptEngineWithApplicationServices(scriptEngine);
|
||||||
|
scriptEngine->runInThread();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// FIXME - change to new version of ScriptCache loading notification
|
||||||
void Application::handleScriptLoadError(const QString& scriptFilename) {
|
void Application::handleScriptLoadError(const QString& scriptFilename) {
|
||||||
qCDebug(interfaceapp) << "Application::loadScript(), script failed to load...";
|
qCDebug(interfaceapp) << "Application::loadScript(), script failed to load...";
|
||||||
QMessageBox::warning(getWindow(), "Error Loading Script", scriptFilename + " failed to load.");
|
QMessageBox::warning(getWindow(), "Error Loading Script", scriptFilename + " failed to load.");
|
||||||
|
|
|
@ -49,7 +49,6 @@ EntityTreeRenderer::EntityTreeRenderer(bool wantScripts, AbstractViewStateInterf
|
||||||
OctreeRenderer(),
|
OctreeRenderer(),
|
||||||
_wantScripts(wantScripts),
|
_wantScripts(wantScripts),
|
||||||
_entitiesScriptEngine(NULL),
|
_entitiesScriptEngine(NULL),
|
||||||
_sandboxScriptEngine(NULL),
|
|
||||||
_lastMouseEventValid(false),
|
_lastMouseEventValid(false),
|
||||||
_viewState(viewState),
|
_viewState(viewState),
|
||||||
_scriptingServices(scriptingServices),
|
_scriptingServices(scriptingServices),
|
||||||
|
@ -77,23 +76,11 @@ EntityTreeRenderer::EntityTreeRenderer(bool wantScripts, AbstractViewStateInterf
|
||||||
EntityTreeRenderer::~EntityTreeRenderer() {
|
EntityTreeRenderer::~EntityTreeRenderer() {
|
||||||
// NOTE: we don't need to delete _entitiesScriptEngine because it is registered with the application and has a
|
// NOTE: we don't need to delete _entitiesScriptEngine because it is registered with the application and has a
|
||||||
// signal tied to call it's deleteLater on doneRunning
|
// signal tied to call it's deleteLater on doneRunning
|
||||||
if (_sandboxScriptEngine) {
|
|
||||||
// TODO: consider reworking how _sandboxScriptEngine is managed. It's treated differently than _entitiesScriptEngine
|
|
||||||
// because we don't call registerScriptEngineWithApplicationServices() for it. This implementation is confusing and
|
|
||||||
// potentially error prone because it's not a full fledged ScriptEngine that has been fully connected to the
|
|
||||||
// application. We did this so that scripts that were ill-formed could be evaluated but not execute against the
|
|
||||||
// application services. But this means it's shutdown behavior is different from other ScriptEngines
|
|
||||||
delete _sandboxScriptEngine;
|
|
||||||
_sandboxScriptEngine = NULL;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void EntityTreeRenderer::clear() {
|
void EntityTreeRenderer::clear() {
|
||||||
leaveAllEntities();
|
leaveAllEntities();
|
||||||
foreach (const EntityItemID& entityID, _entityScripts.keys()) {
|
_entitiesScriptEngine->unloadAllEntityScripts();
|
||||||
checkAndCallUnload(entityID);
|
|
||||||
}
|
|
||||||
_entityScripts.clear();
|
|
||||||
|
|
||||||
auto scene = _viewState->getMain3DScene();
|
auto scene = _viewState->getMain3DScene();
|
||||||
render::PendingChanges pendingChanges;
|
render::PendingChanges pendingChanges;
|
||||||
|
@ -113,10 +100,9 @@ void EntityTreeRenderer::init() {
|
||||||
|
|
||||||
if (_wantScripts) {
|
if (_wantScripts) {
|
||||||
_entitiesScriptEngine = new ScriptEngine(NO_SCRIPT, "Entities",
|
_entitiesScriptEngine = new ScriptEngine(NO_SCRIPT, "Entities",
|
||||||
_scriptingServices->getControllerScriptingInterface(), false);
|
_scriptingServices->getControllerScriptingInterface());
|
||||||
_scriptingServices->registerScriptEngineWithApplicationServices(_entitiesScriptEngine);
|
_scriptingServices->registerScriptEngineWithApplicationServices(_entitiesScriptEngine);
|
||||||
|
_entitiesScriptEngine->runInThread();
|
||||||
_sandboxScriptEngine = new ScriptEngine(NO_SCRIPT, "Entities Sandbox", NULL, false);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// make sure our "last avatar position" is something other than our current position, so that on our
|
// make sure our "last avatar position" is something other than our current position, so that on our
|
||||||
|
@ -134,176 +120,6 @@ void EntityTreeRenderer::shutdown() {
|
||||||
_shuttingDown = true;
|
_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 = std::static_pointer_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(OctreePointer newTree) {
|
void EntityTreeRenderer::setTree(OctreePointer newTree) {
|
||||||
OctreeRenderer::setTree(newTree);
|
OctreeRenderer::setTree(newTree);
|
||||||
std::static_pointer_cast<EntityTree>(_tree)->setFBXService(this);
|
std::static_pointer_cast<EntityTree>(_tree)->setFBXService(this);
|
||||||
|
@ -322,11 +138,7 @@ void EntityTreeRenderer::update() {
|
||||||
// and we want to simulate this message here as well as in mouse move
|
// and we want to simulate this message here as well as in mouse move
|
||||||
if (_lastMouseEventValid && !_currentClickingOnEntityID.isInvalidID()) {
|
if (_lastMouseEventValid && !_currentClickingOnEntityID.isInvalidID()) {
|
||||||
emit holdingClickOnEntity(_currentClickingOnEntityID, _lastMouseEvent);
|
emit holdingClickOnEntity(_currentClickingOnEntityID, _lastMouseEvent);
|
||||||
QScriptValueList currentClickingEntityArgs = createMouseEventArgs(_currentClickingOnEntityID, _lastMouseEvent);
|
_entitiesScriptEngine->callEntityScriptMethod(_currentClickingOnEntityID, "holdingClickOnEntity", _lastMouseEvent);
|
||||||
QScriptValue currentClickingEntity = loadEntityScript(_currentClickingOnEntityID);
|
|
||||||
if (currentClickingEntity.property("holdingClickOnEntity").isValid()) {
|
|
||||||
currentClickingEntity.property("holdingClickOnEntity").call(currentClickingEntity, currentClickingEntityArgs);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -355,19 +167,14 @@ void EntityTreeRenderer::checkEnterLeaveEntities() {
|
||||||
});
|
});
|
||||||
|
|
||||||
// Note: at this point we don't need to worry about the tree being locked, because we only deal with
|
// 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 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
|
// for all of our previous containing entities, if they are no longer containing then send them a leave event
|
||||||
foreach(const EntityItemID& entityID, _currentEntitiesInside) {
|
foreach(const EntityItemID& entityID, _currentEntitiesInside) {
|
||||||
if (!entitiesContainingAvatar.contains(entityID)) {
|
if (!entitiesContainingAvatar.contains(entityID)) {
|
||||||
emit leaveEntity(entityID);
|
emit leaveEntity(entityID);
|
||||||
QScriptValueList entityArgs = createEntityArgs(entityID);
|
_entitiesScriptEngine->callEntityScriptMethod(entityID, "leaveEntity");
|
||||||
QScriptValue entityScript = loadEntityScript(entityID);
|
|
||||||
if (entityScript.property("leaveEntity").isValid()) {
|
|
||||||
entityScript.property("leaveEntity").call(entityScript, entityArgs);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -375,11 +182,7 @@ void EntityTreeRenderer::checkEnterLeaveEntities() {
|
||||||
foreach(const EntityItemID& entityID, entitiesContainingAvatar) {
|
foreach(const EntityItemID& entityID, entitiesContainingAvatar) {
|
||||||
if (!_currentEntitiesInside.contains(entityID)) {
|
if (!_currentEntitiesInside.contains(entityID)) {
|
||||||
emit enterEntity(entityID);
|
emit enterEntity(entityID);
|
||||||
QScriptValueList entityArgs = createEntityArgs(entityID);
|
_entitiesScriptEngine->callEntityScriptMethod(entityID, "enterEntity");
|
||||||
QScriptValue entityScript = loadEntityScript(entityID);
|
|
||||||
if (entityScript.property("enterEntity").isValid()) {
|
|
||||||
entityScript.property("enterEntity").call(entityScript, entityArgs);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_currentEntitiesInside = entitiesContainingAvatar;
|
_currentEntitiesInside = entitiesContainingAvatar;
|
||||||
|
@ -394,11 +197,7 @@ void EntityTreeRenderer::leaveAllEntities() {
|
||||||
// for all of our previous containing entities, if they are no longer containing then send them a leave event
|
// for all of our previous containing entities, if they are no longer containing then send them a leave event
|
||||||
foreach(const EntityItemID& entityID, _currentEntitiesInside) {
|
foreach(const EntityItemID& entityID, _currentEntitiesInside) {
|
||||||
emit leaveEntity(entityID);
|
emit leaveEntity(entityID);
|
||||||
QScriptValueList entityArgs = createEntityArgs(entityID);
|
_entitiesScriptEngine->callEntityScriptMethod(entityID, "leaveEntity");
|
||||||
QScriptValue entityScript = loadEntityScript(entityID);
|
|
||||||
if (entityScript.property("leaveEntity").isValid()) {
|
|
||||||
entityScript.property("leaveEntity").call(entityScript, entityArgs);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
_currentEntitiesInside.clear();
|
_currentEntitiesInside.clear();
|
||||||
|
|
||||||
|
@ -811,27 +610,6 @@ void EntityTreeRenderer::connectSignalsToSlots(EntityScriptingInterface* entityS
|
||||||
connect(DependencyManager::get<SceneScriptingInterface>().data(), &SceneScriptingInterface::shouldRenderEntitiesChanged, this, &EntityTreeRenderer::updateEntityRenderStatus, Qt::QueuedConnection);
|
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) {
|
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
|
// If we don't have a tree, or we're in the process of shutting down, then don't
|
||||||
// process these events.
|
// process these events.
|
||||||
|
@ -854,18 +632,11 @@ void EntityTreeRenderer::mousePressEvent(QMouseEvent* event, unsigned int device
|
||||||
}
|
}
|
||||||
|
|
||||||
emit mousePressOnEntity(rayPickResult, event, deviceID);
|
emit mousePressOnEntity(rayPickResult, event, deviceID);
|
||||||
|
_entitiesScriptEngine->callEntityScriptMethod(rayPickResult.entityID, "mousePressOnEntity", MouseEvent(*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);
|
|
||||||
}
|
|
||||||
|
|
||||||
_currentClickingOnEntityID = rayPickResult.entityID;
|
_currentClickingOnEntityID = rayPickResult.entityID;
|
||||||
emit clickDownOnEntity(_currentClickingOnEntityID, MouseEvent(*event, deviceID));
|
emit clickDownOnEntity(_currentClickingOnEntityID, MouseEvent(*event, deviceID));
|
||||||
if (entityScript.property("clickDownOnEntity").isValid()) {
|
_entitiesScriptEngine->callEntityScriptMethod(_currentClickingOnEntityID, "clickDownOnEntity", MouseEvent(*event, deviceID));
|
||||||
entityScript.property("clickDownOnEntity").call(entityScript, entityScriptArgs);
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
emit mousePressOffEntity(rayPickResult, event, deviceID);
|
emit mousePressOffEntity(rayPickResult, event, deviceID);
|
||||||
}
|
}
|
||||||
|
@ -886,24 +657,14 @@ void EntityTreeRenderer::mouseReleaseEvent(QMouseEvent* event, unsigned int devi
|
||||||
if (rayPickResult.intersects) {
|
if (rayPickResult.intersects) {
|
||||||
//qCDebug(entitiesrenderer) << "mouseReleaseEvent over entity:" << rayPickResult.entityID;
|
//qCDebug(entitiesrenderer) << "mouseReleaseEvent over entity:" << rayPickResult.entityID;
|
||||||
emit mouseReleaseOnEntity(rayPickResult, event, deviceID);
|
emit mouseReleaseOnEntity(rayPickResult, event, deviceID);
|
||||||
|
_entitiesScriptEngine->callEntityScriptMethod(rayPickResult.entityID, "mouseReleaseOnEntity", MouseEvent(*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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Even if we're no longer intersecting with an entity, if we started clicking on it, and now
|
// 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
|
// we're releasing the button, then this is considered a clickOn event
|
||||||
if (!_currentClickingOnEntityID.isInvalidID()) {
|
if (!_currentClickingOnEntityID.isInvalidID()) {
|
||||||
emit clickReleaseOnEntity(_currentClickingOnEntityID, MouseEvent(*event, deviceID));
|
emit clickReleaseOnEntity(_currentClickingOnEntityID, MouseEvent(*event, deviceID));
|
||||||
|
_entitiesScriptEngine->callEntityScriptMethod(rayPickResult.entityID, "clickReleaseOnEntity", MouseEvent(*event, deviceID));
|
||||||
QScriptValueList currentClickingEntityArgs = createMouseEventArgs(_currentClickingOnEntityID, event, deviceID);
|
|
||||||
QScriptValue currentClickingEntity = loadEntityScript(_currentClickingOnEntityID);
|
|
||||||
if (currentClickingEntity.property("clickReleaseOnEntity").isValid()) {
|
|
||||||
currentClickingEntity.property("clickReleaseOnEntity").call(currentClickingEntity, currentClickingEntityArgs);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// makes it the unknown ID, we just released so we can't be clicking on anything
|
// makes it the unknown ID, we just released so we can't be clicking on anything
|
||||||
|
@ -925,17 +686,9 @@ void EntityTreeRenderer::mouseMoveEvent(QMouseEvent* event, unsigned int deviceI
|
||||||
bool precisionPicking = false; // for mouse moves we do not do precision picking
|
bool precisionPicking = false; // for mouse moves we do not do precision picking
|
||||||
RayToEntityIntersectionResult rayPickResult = findRayIntersectionWorker(ray, Octree::TryLock, precisionPicking);
|
RayToEntityIntersectionResult rayPickResult = findRayIntersectionWorker(ray, Octree::TryLock, precisionPicking);
|
||||||
if (rayPickResult.intersects) {
|
if (rayPickResult.intersects) {
|
||||||
//qCDebug(entitiesrenderer) << "mouseReleaseEvent over entity:" << rayPickResult.entityID;
|
|
||||||
QScriptValueList entityScriptArgs = createMouseEventArgs(rayPickResult.entityID, event, deviceID);
|
_entitiesScriptEngine->callEntityScriptMethod(rayPickResult.entityID, "mouseMoveEvent", MouseEvent(*event, deviceID));
|
||||||
// load the entity script if needed...
|
_entitiesScriptEngine->callEntityScriptMethod(rayPickResult.entityID, "mouseMoveOnEntity", MouseEvent(*event, deviceID));
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
// handle the hover logic...
|
// handle the hover logic...
|
||||||
|
|
||||||
|
@ -943,30 +696,19 @@ void EntityTreeRenderer::mouseMoveEvent(QMouseEvent* event, unsigned int deviceI
|
||||||
// then we need to send the hover leave.
|
// then we need to send the hover leave.
|
||||||
if (!_currentHoverOverEntityID.isInvalidID() && rayPickResult.entityID != _currentHoverOverEntityID) {
|
if (!_currentHoverOverEntityID.isInvalidID() && rayPickResult.entityID != _currentHoverOverEntityID) {
|
||||||
emit hoverLeaveEntity(_currentHoverOverEntityID, MouseEvent(*event, deviceID));
|
emit hoverLeaveEntity(_currentHoverOverEntityID, MouseEvent(*event, deviceID));
|
||||||
|
_entitiesScriptEngine->callEntityScriptMethod(_currentHoverOverEntityID, "hoverLeaveEntity", MouseEvent(*event, deviceID));
|
||||||
QScriptValueList currentHoverEntityArgs = createMouseEventArgs(_currentHoverOverEntityID, event, deviceID);
|
|
||||||
|
|
||||||
QScriptValue currentHoverEntity = loadEntityScript(_currentHoverOverEntityID);
|
|
||||||
if (currentHoverEntity.property("hoverLeaveEntity").isValid()) {
|
|
||||||
currentHoverEntity.property("hoverLeaveEntity").call(currentHoverEntity, currentHoverEntityArgs);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// If the new hover entity does not match the previous hover entity then we are entering the new one
|
// 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
|
// this is true if the _currentHoverOverEntityID is known or unknown
|
||||||
if (rayPickResult.entityID != _currentHoverOverEntityID) {
|
if (rayPickResult.entityID != _currentHoverOverEntityID) {
|
||||||
emit hoverEnterEntity(rayPickResult.entityID, MouseEvent(*event, deviceID));
|
_entitiesScriptEngine->callEntityScriptMethod(rayPickResult.entityID, "hoverEnterEntity", MouseEvent(*event, deviceID));
|
||||||
if (entityScript.property("hoverEnterEntity").isValid()) {
|
|
||||||
entityScript.property("hoverEnterEntity").call(entityScript, entityScriptArgs);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// and finally, no matter what, if we're intersecting an entity then we're definitely hovering over it, and
|
// 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
|
// we should send our hover over event
|
||||||
emit hoverOverEntity(rayPickResult.entityID, MouseEvent(*event, deviceID));
|
emit hoverOverEntity(rayPickResult.entityID, MouseEvent(*event, deviceID));
|
||||||
if (entityScript.property("hoverOverEntity").isValid()) {
|
_entitiesScriptEngine->callEntityScriptMethod(rayPickResult.entityID, "hoverOverEntity", MouseEvent(*event, deviceID));
|
||||||
entityScript.property("hoverOverEntity").call(entityScript, entityScriptArgs);
|
|
||||||
}
|
|
||||||
|
|
||||||
// remember what we're hovering over
|
// remember what we're hovering over
|
||||||
_currentHoverOverEntityID = rayPickResult.entityID;
|
_currentHoverOverEntityID = rayPickResult.entityID;
|
||||||
|
@ -977,14 +719,7 @@ void EntityTreeRenderer::mouseMoveEvent(QMouseEvent* event, unsigned int deviceI
|
||||||
// send the hover leave for our previous entity
|
// send the hover leave for our previous entity
|
||||||
if (!_currentHoverOverEntityID.isInvalidID()) {
|
if (!_currentHoverOverEntityID.isInvalidID()) {
|
||||||
emit hoverLeaveEntity(_currentHoverOverEntityID, MouseEvent(*event, deviceID));
|
emit hoverLeaveEntity(_currentHoverOverEntityID, MouseEvent(*event, deviceID));
|
||||||
|
_entitiesScriptEngine->callEntityScriptMethod(_currentHoverOverEntityID, "hoverLeaveEntity", MouseEvent(*event, deviceID));
|
||||||
QScriptValueList currentHoverEntityArgs = createMouseEventArgs(_currentHoverOverEntityID, event, deviceID);
|
|
||||||
|
|
||||||
QScriptValue currentHoverEntity = loadEntityScript(_currentHoverOverEntityID);
|
|
||||||
if (currentHoverEntity.property("hoverLeaveEntity").isValid()) {
|
|
||||||
currentHoverEntity.property("hoverLeaveEntity").call(currentHoverEntity, currentHoverEntityArgs);
|
|
||||||
}
|
|
||||||
|
|
||||||
_currentHoverOverEntityID = UNKNOWN_ENTITY_ID; // makes it the unknown ID
|
_currentHoverOverEntityID = UNKNOWN_ENTITY_ID; // makes it the unknown ID
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -993,13 +728,7 @@ void EntityTreeRenderer::mouseMoveEvent(QMouseEvent* event, unsigned int deviceI
|
||||||
// not yet released the hold then this is still considered a holdingClickOnEntity event
|
// not yet released the hold then this is still considered a holdingClickOnEntity event
|
||||||
if (!_currentClickingOnEntityID.isInvalidID()) {
|
if (!_currentClickingOnEntityID.isInvalidID()) {
|
||||||
emit holdingClickOnEntity(_currentClickingOnEntityID, MouseEvent(*event, deviceID));
|
emit holdingClickOnEntity(_currentClickingOnEntityID, MouseEvent(*event, deviceID));
|
||||||
|
_entitiesScriptEngine->callEntityScriptMethod(_currentClickingOnEntityID, "holdingClickOnEntity", MouseEvent(*event, deviceID));
|
||||||
QScriptValueList currentClickingEntityArgs = createMouseEventArgs(_currentClickingOnEntityID, event, deviceID);
|
|
||||||
|
|
||||||
QScriptValue currentClickingEntity = loadEntityScript(_currentClickingOnEntityID);
|
|
||||||
if (currentClickingEntity.property("holdingClickOnEntity").isValid()) {
|
|
||||||
currentClickingEntity.property("holdingClickOnEntity").call(currentClickingEntity, currentClickingEntityArgs);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
_lastMouseEvent = MouseEvent(*event, deviceID);
|
_lastMouseEvent = MouseEvent(*event, deviceID);
|
||||||
_lastMouseEventValid = true;
|
_lastMouseEventValid = true;
|
||||||
|
@ -1007,9 +736,8 @@ void EntityTreeRenderer::mouseMoveEvent(QMouseEvent* event, unsigned int deviceI
|
||||||
|
|
||||||
void EntityTreeRenderer::deletingEntity(const EntityItemID& entityID) {
|
void EntityTreeRenderer::deletingEntity(const EntityItemID& entityID) {
|
||||||
if (_tree && !_shuttingDown) {
|
if (_tree && !_shuttingDown) {
|
||||||
checkAndCallUnload(entityID);
|
_entitiesScriptEngine->unloadEntityScript(entityID);
|
||||||
}
|
}
|
||||||
_entityScripts.remove(entityID);
|
|
||||||
|
|
||||||
// here's where we remove the entity payload from the scene
|
// here's where we remove the entity payload from the scene
|
||||||
if (_entitiesInScene.contains(entityID)) {
|
if (_entitiesInScene.contains(entityID)) {
|
||||||
|
@ -1042,28 +770,16 @@ void EntityTreeRenderer::addEntityToScene(EntityItemPointer entity) {
|
||||||
|
|
||||||
void EntityTreeRenderer::entitySciptChanging(const EntityItemID& entityID, const bool reload) {
|
void EntityTreeRenderer::entitySciptChanging(const EntityItemID& entityID, const bool reload) {
|
||||||
if (_tree && !_shuttingDown) {
|
if (_tree && !_shuttingDown) {
|
||||||
checkAndCallUnload(entityID);
|
_entitiesScriptEngine->unloadEntityScript(entityID);
|
||||||
checkAndCallPreload(entityID, reload);
|
checkAndCallPreload(entityID, reload);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void EntityTreeRenderer::checkAndCallPreload(const EntityItemID& entityID, const bool reload) {
|
void EntityTreeRenderer::checkAndCallPreload(const EntityItemID& entityID, const bool reload) {
|
||||||
if (_tree && !_shuttingDown) {
|
if (_tree && !_shuttingDown) {
|
||||||
// load the entity script if needed...
|
EntityItemPointer entity = getTree()->findEntityByEntityItemID(entityID);
|
||||||
QScriptValue entityScript = loadEntityScript(entityID, true, reload); // is preload!
|
if (entity && !entity->getScript().isEmpty()) {
|
||||||
if (entityScript.property("preload").isValid()) {
|
_entitiesScriptEngine->loadEntityScript(entityID, entity->getScript(), reload);
|
||||||
QScriptValueList entityArgs = createEntityArgs(entityID);
|
|
||||||
entityScript.property("preload").call(entityScript, entityArgs);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1143,24 +859,9 @@ void EntityTreeRenderer::entityCollisionWithEntity(const EntityItemID& idA, cons
|
||||||
|
|
||||||
// And now the entity scripts
|
// And now the entity scripts
|
||||||
emit collisionWithEntity(idA, idB, collision);
|
emit collisionWithEntity(idA, idB, collision);
|
||||||
QScriptValue entityScriptA = loadEntityScript(idA);
|
_entitiesScriptEngine->callEntityScriptMethod(idA, "collisionWithEntity", idB, collision);
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
emit collisionWithEntity(idB, idA, collision);
|
emit collisionWithEntity(idB, idA, collision);
|
||||||
QScriptValue entityScriptB = loadEntityScript(idB);
|
_entitiesScriptEngine->callEntityScriptMethod(idB, "collisionWithEntity", idA, collision);
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void EntityTreeRenderer::updateEntityRenderStatus(bool shouldRenderEntities) {
|
void EntityTreeRenderer::updateEntityRenderStatus(bool shouldRenderEntities) {
|
||||||
|
|
|
@ -28,14 +28,9 @@ class Model;
|
||||||
class ScriptEngine;
|
class ScriptEngine;
|
||||||
class ZoneEntityItem;
|
class ZoneEntityItem;
|
||||||
|
|
||||||
class EntityScriptDetails {
|
|
||||||
public:
|
|
||||||
QString scriptText;
|
|
||||||
QScriptValue scriptObject;
|
|
||||||
};
|
|
||||||
|
|
||||||
// Generic client side Octree renderer class.
|
// Generic client side Octree renderer class.
|
||||||
class EntityTreeRenderer : public OctreeRenderer, public EntityItemFBXService, public ScriptUser {
|
class EntityTreeRenderer : public OctreeRenderer, public EntityItemFBXService {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
public:
|
public:
|
||||||
EntityTreeRenderer(bool wantScripts, AbstractViewStateInterface* viewState,
|
EntityTreeRenderer(bool wantScripts, AbstractViewStateInterface* viewState,
|
||||||
|
@ -87,9 +82,6 @@ public:
|
||||||
/// hovering over, and entering entities
|
/// hovering over, and entering entities
|
||||||
void connectSignalsToSlots(EntityScriptingInterface* entityScriptingInterface);
|
void connectSignalsToSlots(EntityScriptingInterface* entityScriptingInterface);
|
||||||
|
|
||||||
virtual void scriptContentsAvailable(const QUrl& url, const QString& scriptContents);
|
|
||||||
virtual void errorInLoadingScript(const QUrl& url);
|
|
||||||
|
|
||||||
// For Scene.shouldRenderEntities
|
// For Scene.shouldRenderEntities
|
||||||
QList<EntityItemID>& getEntitiesLastInScene() { return _entityIDsLastInScene; }
|
QList<EntityItemID>& getEntitiesLastInScene() { return _entityIDsLastInScene; }
|
||||||
|
|
||||||
|
@ -137,7 +129,6 @@ private:
|
||||||
void applyZonePropertiesToScene(std::shared_ptr<ZoneEntityItem> zone);
|
void applyZonePropertiesToScene(std::shared_ptr<ZoneEntityItem> zone);
|
||||||
void renderElementProxy(EntityTreeElementPointer entityTreeElement, RenderArgs* args);
|
void renderElementProxy(EntityTreeElementPointer entityTreeElement, RenderArgs* args);
|
||||||
void checkAndCallPreload(const EntityItemID& entityID, const bool reload = false);
|
void checkAndCallPreload(const EntityItemID& entityID, const bool reload = false);
|
||||||
void checkAndCallUnload(const EntityItemID& entityID);
|
|
||||||
|
|
||||||
QList<Model*> _releasedModels;
|
QList<Model*> _releasedModels;
|
||||||
void renderProxies(EntityItemPointer entity, RenderArgs* args);
|
void renderProxies(EntityItemPointer entity, RenderArgs* args);
|
||||||
|
@ -155,16 +146,6 @@ private:
|
||||||
|
|
||||||
bool _wantScripts;
|
bool _wantScripts;
|
||||||
ScriptEngine* _entitiesScriptEngine;
|
ScriptEngine* _entitiesScriptEngine;
|
||||||
ScriptEngine* _sandboxScriptEngine;
|
|
||||||
|
|
||||||
QScriptValue loadEntityScript(EntityItemPointer entity, bool isPreload = false, bool reload = false);
|
|
||||||
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;
|
|
||||||
|
|
||||||
void playEntityCollisionSound(const QUuid& myNodeID, EntityTreePointer entityTree,
|
void playEntityCollisionSound(const QUuid& myNodeID, EntityTreePointer entityTree,
|
||||||
const EntityItemID& id, const Collision& collision);
|
const EntityItemID& id, const Collision& collision);
|
||||||
|
|
|
@ -17,21 +17,31 @@
|
||||||
|
|
||||||
#include <SharedUtil.h>
|
#include <SharedUtil.h>
|
||||||
|
|
||||||
ResourceRequest* ResourceManager::createResourceRequest(QObject* parent, const QUrl& url) {
|
QUrl ResourceManager::normalizeURL(const QUrl& url) {
|
||||||
auto scheme = url.scheme();
|
auto scheme = url.scheme();
|
||||||
|
if (!(scheme == URL_SCHEME_FILE ||
|
||||||
|
scheme == URL_SCHEME_HTTP || scheme == URL_SCHEME_HTTPS || scheme == URL_SCHEME_FTP ||
|
||||||
|
scheme == URL_SCHEME_ATP)) {
|
||||||
|
|
||||||
|
// check the degenerative file case: on windows we can often have urls of the form c:/filename
|
||||||
|
// this checks for and works around that case.
|
||||||
|
QUrl urlWithFileScheme{ URL_SCHEME_FILE + ":///" + url.toString() };
|
||||||
|
if (!urlWithFileScheme.toLocalFile().isEmpty()) {
|
||||||
|
return urlWithFileScheme;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return url;
|
||||||
|
}
|
||||||
|
|
||||||
|
ResourceRequest* ResourceManager::createResourceRequest(QObject* parent, const QUrl& url) {
|
||||||
|
auto normalizedURL = normalizeURL(url);
|
||||||
|
auto scheme = normalizedURL.scheme();
|
||||||
if (scheme == URL_SCHEME_FILE) {
|
if (scheme == URL_SCHEME_FILE) {
|
||||||
return new FileResourceRequest(parent, url);
|
return new FileResourceRequest(parent, url);
|
||||||
} else if (scheme == URL_SCHEME_HTTP || scheme == URL_SCHEME_HTTPS || scheme == URL_SCHEME_FTP) {
|
} else if (scheme == URL_SCHEME_HTTP || scheme == URL_SCHEME_HTTPS || scheme == URL_SCHEME_FTP) {
|
||||||
return new HTTPResourceRequest(parent, url);
|
return new HTTPResourceRequest(parent, url);
|
||||||
} else if (scheme == URL_SCHEME_ATP) {
|
} else if (scheme == URL_SCHEME_ATP) {
|
||||||
return new AssetResourceRequest(parent, url);
|
return new AssetResourceRequest(parent, url);
|
||||||
} else {
|
|
||||||
// check the degenerative file case: on windows we can often have urls of the form c:/filename
|
|
||||||
// this checks for and works around that case.
|
|
||||||
QUrl urlWithFileScheme { URL_SCHEME_FILE + ":///" + url.toString() };
|
|
||||||
if (!urlWithFileScheme.toLocalFile().isEmpty()) {
|
|
||||||
return new FileResourceRequest(parent, urlWithFileScheme);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
qDebug() << "Unknown scheme (" << scheme << ") for URL: " << url.url();
|
qDebug() << "Unknown scheme (" << scheme << ") for URL: " << url.url();
|
||||||
|
|
|
@ -24,6 +24,7 @@ const QString URL_SCHEME_ATP = "atp";
|
||||||
|
|
||||||
class ResourceManager {
|
class ResourceManager {
|
||||||
public:
|
public:
|
||||||
|
static QUrl normalizeURL(const QUrl& url);
|
||||||
static ResourceRequest* createResourceRequest(QObject* parent, const QUrl& url);
|
static ResourceRequest* createResourceRequest(QObject* parent, const QUrl& url);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -15,6 +15,7 @@
|
||||||
#include <QNetworkConfiguration>
|
#include <QNetworkConfiguration>
|
||||||
#include <QNetworkReply>
|
#include <QNetworkReply>
|
||||||
#include <QObject>
|
#include <QObject>
|
||||||
|
#include <QThread>
|
||||||
|
|
||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
#include <SharedUtil.h>
|
#include <SharedUtil.h>
|
||||||
|
@ -26,7 +27,8 @@ ScriptCache::ScriptCache(QObject* parent) {
|
||||||
// nothing to do here...
|
// nothing to do here...
|
||||||
}
|
}
|
||||||
|
|
||||||
QString ScriptCache::getScript(const QUrl& url, ScriptUser* scriptUser, bool& isPending, bool reload) {
|
QString ScriptCache::getScript(const QUrl& unnormalizedURL, ScriptUser* scriptUser, bool& isPending, bool reload) {
|
||||||
|
QUrl url = ResourceManager::normalizeURL(unnormalizedURL);
|
||||||
QString scriptContents;
|
QString scriptContents;
|
||||||
if (_scriptCache.contains(url) && !reload) {
|
if (_scriptCache.contains(url) && !reload) {
|
||||||
qCDebug(scriptengine) << "Found script in cache:" << url.toString();
|
qCDebug(scriptengine) << "Found script in cache:" << url.toString();
|
||||||
|
@ -41,7 +43,7 @@ QString ScriptCache::getScript(const QUrl& url, ScriptUser* scriptUser, bool& is
|
||||||
if (alreadyWaiting) {
|
if (alreadyWaiting) {
|
||||||
qCDebug(scriptengine) << "Already downloading script at:" << url.toString();
|
qCDebug(scriptengine) << "Already downloading script at:" << url.toString();
|
||||||
} else {
|
} else {
|
||||||
auto request = ResourceManager::createResourceRequest(this, url);
|
auto request = ResourceManager::createResourceRequest(nullptr, url);
|
||||||
request->setCacheEnabled(!reload);
|
request->setCacheEnabled(!reload);
|
||||||
connect(request, &ResourceRequest::finished, this, &ScriptCache::scriptDownloaded);
|
connect(request, &ResourceRequest::finished, this, &ScriptCache::scriptDownloaded);
|
||||||
request->send();
|
request->send();
|
||||||
|
@ -50,7 +52,8 @@ QString ScriptCache::getScript(const QUrl& url, ScriptUser* scriptUser, bool& is
|
||||||
return scriptContents;
|
return scriptContents;
|
||||||
}
|
}
|
||||||
|
|
||||||
void ScriptCache::deleteScript(const QUrl& url) {
|
void ScriptCache::deleteScript(const QUrl& unnormalizedURL) {
|
||||||
|
QUrl url = ResourceManager::normalizeURL(unnormalizedURL);
|
||||||
if (_scriptCache.contains(url)) {
|
if (_scriptCache.contains(url)) {
|
||||||
qCDebug(scriptengine) << "Delete script from cache:" << url.toString();
|
qCDebug(scriptengine) << "Delete script from cache:" << url.toString();
|
||||||
_scriptCache.remove(url);
|
_scriptCache.remove(url);
|
||||||
|
@ -79,4 +82,63 @@ void ScriptCache::scriptDownloaded() {
|
||||||
req->deleteLater();
|
req->deleteLater();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ScriptCache::getScriptContents(const QString& scriptOrURL, contentAvailableCallback contentAvailable, bool forceDownload) {
|
||||||
|
#ifdef THREAD_DEBUGGING
|
||||||
|
qCDebug(scriptengine) << "ScriptCache::getScriptContents() on thread [" << QThread::currentThread() << "] expected thread [" << thread() << "]";
|
||||||
|
#endif
|
||||||
|
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();
|
||||||
|
#if 1 // def THREAD_DEBUGGING
|
||||||
|
qCDebug(scriptengine) << "ScriptCache::getScriptContents() about to call contentAvailable() on thread [" << QThread::currentThread() << "] expected thread [" << thread() << "]";
|
||||||
|
#endif
|
||||||
|
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 {
|
||||||
|
#ifdef THREAD_DEBUGGING
|
||||||
|
qCDebug(scriptengine) << "about to call: ResourceManager::createResourceRequest(this, url); on thread [" << QThread::currentThread() << "] expected thread [" << thread() << "]";
|
||||||
|
#endif
|
||||||
|
auto request = ResourceManager::createResourceRequest(nullptr, url);
|
||||||
|
request->setCacheEnabled(!forceDownload);
|
||||||
|
connect(request, &ResourceRequest::finished, this, &ScriptCache::scriptContentAvailable);
|
||||||
|
request->send();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ScriptCache::scriptContentAvailable() {
|
||||||
|
#ifdef THREAD_DEBUGGING
|
||||||
|
qCDebug(scriptengine) << "ScriptCache::scriptContentAvailable() on thread [" << QThread::currentThread() << "] expected thread [" << thread() << "]";
|
||||||
|
#endif
|
||||||
|
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;
|
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
|
/// Interface for loading scripts
|
||||||
class ScriptCache : public QObject, public Dependency {
|
class ScriptCache : public QObject, public Dependency {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
SINGLETON_DEPENDENCY
|
SINGLETON_DEPENDENCY
|
||||||
|
|
||||||
public:
|
public:
|
||||||
QString getScript(const QUrl& url, ScriptUser* scriptUser, bool& isPending, bool redownload = false);
|
void getScriptContents(const QString& scriptOrURL, contentAvailableCallback contentAvailable, bool forceDownload = false);
|
||||||
void deleteScript(const QUrl& url);
|
|
||||||
|
|
||||||
|
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); }
|
void addScriptToBadScriptList(const QUrl& url) { _badScripts.insert(url); }
|
||||||
bool isInBadScriptList(const QUrl& url) { return _badScripts.contains(url); }
|
bool isInBadScriptList(const QUrl& url) { return _badScripts.contains(url); }
|
||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
void scriptDownloaded();
|
void scriptDownloaded(); // old version
|
||||||
|
void scriptContentAvailable(); // new version
|
||||||
|
|
||||||
private:
|
private:
|
||||||
ScriptCache(QObject* parent = NULL);
|
ScriptCache(QObject* parent = NULL);
|
||||||
|
|
||||||
|
QMultiMap<QUrl, contentAvailableCallback> _contentCallbacks;
|
||||||
|
|
||||||
QHash<QUrl, QString> _scriptCache;
|
QHash<QUrl, QString> _scriptCache;
|
||||||
QMultiMap<QUrl, ScriptUser*> _scriptUsers;
|
QMultiMap<QUrl, ScriptUser*> _scriptUsers;
|
||||||
QSet<QUrl> _badScripts;
|
QSet<QUrl> _badScripts;
|
||||||
|
|
|
@ -16,6 +16,7 @@
|
||||||
#include <QtNetwork/QNetworkRequest>
|
#include <QtNetwork/QNetworkRequest>
|
||||||
#include <QtNetwork/QNetworkReply>
|
#include <QtNetwork/QNetworkReply>
|
||||||
#include <QScriptEngine>
|
#include <QScriptEngine>
|
||||||
|
#include <QScriptValue>
|
||||||
|
|
||||||
#include <AudioConstants.h>
|
#include <AudioConstants.h>
|
||||||
#include <AudioEffectOptions.h>
|
#include <AudioEffectOptions.h>
|
||||||
|
@ -44,6 +45,8 @@
|
||||||
|
|
||||||
#include "MIDIEvent.h"
|
#include "MIDIEvent.h"
|
||||||
|
|
||||||
|
Q_DECLARE_METATYPE(QScriptEngine::FunctionSignature)
|
||||||
|
static int functionSignatureMetaID = qRegisterMetaType<QScriptEngine::FunctionSignature>();
|
||||||
|
|
||||||
static QScriptValue debugPrint(QScriptContext* context, QScriptEngine* engine){
|
static QScriptValue debugPrint(QScriptContext* context, QScriptEngine* engine){
|
||||||
QString message = "";
|
QString message = "";
|
||||||
|
@ -87,17 +90,9 @@ ScriptEngine::ScriptEngine(const QString& scriptContents, const QString& fileNam
|
||||||
_isFinished(false),
|
_isFinished(false),
|
||||||
_isRunning(false),
|
_isRunning(false),
|
||||||
_isInitialized(false),
|
_isInitialized(false),
|
||||||
_isAvatar(false),
|
|
||||||
_avatarIdentityTimer(NULL),
|
|
||||||
_avatarBillboardTimer(NULL),
|
|
||||||
_timerFunctionMap(),
|
_timerFunctionMap(),
|
||||||
_isListeningToAudioStream(false),
|
|
||||||
_avatarSound(NULL),
|
|
||||||
_numAvatarSoundSentBytes(0),
|
|
||||||
_wantSignals(wantSignals),
|
_wantSignals(wantSignals),
|
||||||
_controllerScriptingInterface(controllerScriptingInterface),
|
_controllerScriptingInterface(controllerScriptingInterface),
|
||||||
_avatarData(NULL),
|
|
||||||
_scriptName(),
|
|
||||||
_fileNameString(fileNameString),
|
_fileNameString(fileNameString),
|
||||||
_quatLibrary(),
|
_quatLibrary(),
|
||||||
_vec3Library(),
|
_vec3Library(),
|
||||||
|
@ -122,6 +117,27 @@ ScriptEngine::~ScriptEngine() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ScriptEngine::runInThread() {
|
||||||
|
QThread* workerThread = new QThread(this);
|
||||||
|
QString scriptEngineName = QString("Script Thread:") + getFilename();
|
||||||
|
workerThread->setObjectName(scriptEngineName);
|
||||||
|
|
||||||
|
// when the worker thread is started, call our engine's run..
|
||||||
|
connect(workerThread, &QThread::started, this, &ScriptEngine::run);
|
||||||
|
|
||||||
|
// when the thread is terminated, add both scriptEngine and thread to the deleteLater queue
|
||||||
|
connect(this, &ScriptEngine::doneRunning, this, &ScriptEngine::deleteLater);
|
||||||
|
connect(workerThread, &QThread::finished, workerThread, &QThread::deleteLater);
|
||||||
|
|
||||||
|
// tell the thread to stop when the script engine is done
|
||||||
|
connect(this, &ScriptEngine::destroyed, workerThread, &QThread::quit);
|
||||||
|
|
||||||
|
moveToThread(workerThread);
|
||||||
|
|
||||||
|
// Starts an event loop, and emits workerThread->started()
|
||||||
|
workerThread->start();
|
||||||
|
}
|
||||||
|
|
||||||
QSet<ScriptEngine*> ScriptEngine::_allKnownScriptEngines;
|
QSet<ScriptEngine*> ScriptEngine::_allKnownScriptEngines;
|
||||||
QMutex ScriptEngine::_allScriptsMutex;
|
QMutex ScriptEngine::_allScriptsMutex;
|
||||||
bool ScriptEngine::_stoppingAllScripts = false;
|
bool ScriptEngine::_stoppingAllScripts = false;
|
||||||
|
@ -180,8 +196,6 @@ void ScriptEngine::stopAllScripts(QObject* application) {
|
||||||
|
|
||||||
|
|
||||||
void ScriptEngine::waitTillDoneRunning() {
|
void ScriptEngine::waitTillDoneRunning() {
|
||||||
QString scriptName = getFilename();
|
|
||||||
|
|
||||||
// If the script never started running or finished running before we got here, we don't need to wait for it
|
// If the script never started running or finished running before we got here, we don't need to wait for it
|
||||||
if (_isRunning) {
|
if (_isRunning) {
|
||||||
|
|
||||||
|
@ -209,58 +223,7 @@ QString ScriptEngine::getFilename() const {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void ScriptEngine::setIsAvatar(bool isAvatar) {
|
// FIXME - switch this to the new model of ScriptCache callbacks
|
||||||
_isAvatar = isAvatar;
|
|
||||||
|
|
||||||
if (_isAvatar && !_avatarIdentityTimer) {
|
|
||||||
// set up the avatar timers
|
|
||||||
_avatarIdentityTimer = new QTimer(this);
|
|
||||||
_avatarBillboardTimer = new QTimer(this);
|
|
||||||
|
|
||||||
// connect our slot
|
|
||||||
connect(_avatarIdentityTimer, &QTimer::timeout, this, &ScriptEngine::sendAvatarIdentityPacket);
|
|
||||||
connect(_avatarBillboardTimer, &QTimer::timeout, this, &ScriptEngine::sendAvatarBillboardPacket);
|
|
||||||
|
|
||||||
// start the timers
|
|
||||||
_avatarIdentityTimer->start(AVATAR_IDENTITY_PACKET_SEND_INTERVAL_MSECS);
|
|
||||||
_avatarBillboardTimer->start(AVATAR_BILLBOARD_PACKET_SEND_INTERVAL_MSECS);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!_isAvatar) {
|
|
||||||
delete _avatarIdentityTimer;
|
|
||||||
_avatarIdentityTimer = NULL;
|
|
||||||
delete _avatarBillboardTimer;
|
|
||||||
_avatarBillboardTimer = NULL;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void ScriptEngine::setAvatarData(AvatarData* avatarData, const QString& objectName) {
|
|
||||||
_avatarData = avatarData;
|
|
||||||
|
|
||||||
// remove the old Avatar property, if it exists
|
|
||||||
globalObject().setProperty(objectName, QScriptValue());
|
|
||||||
|
|
||||||
// give the script engine the new Avatar script property
|
|
||||||
registerGlobalObject(objectName, _avatarData);
|
|
||||||
}
|
|
||||||
|
|
||||||
void ScriptEngine::setAvatarHashMap(AvatarHashMap* avatarHashMap, const QString& objectName) {
|
|
||||||
// remove the old Avatar property, if it exists
|
|
||||||
globalObject().setProperty(objectName, QScriptValue());
|
|
||||||
|
|
||||||
// give the script engine the new avatar hash map
|
|
||||||
registerGlobalObject(objectName, avatarHashMap);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool ScriptEngine::setScriptContents(const QString& scriptContents, const QString& fileNameString) {
|
|
||||||
if (_isRunning) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
_scriptContents = scriptContents;
|
|
||||||
_fileNameString = fileNameString;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void ScriptEngine::loadURL(const QUrl& scriptURL, bool reload) {
|
void ScriptEngine::loadURL(const QUrl& scriptURL, bool reload) {
|
||||||
if (_isRunning) {
|
if (_isRunning) {
|
||||||
return;
|
return;
|
||||||
|
@ -271,38 +234,12 @@ void ScriptEngine::loadURL(const QUrl& scriptURL, bool reload) {
|
||||||
|
|
||||||
QUrl url(scriptURL);
|
QUrl url(scriptURL);
|
||||||
|
|
||||||
// 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(_fileNameString);
|
|
||||||
}
|
|
||||||
|
|
||||||
// ok, let's see if it's valid... and if so, load it
|
|
||||||
if (url.isValid()) {
|
|
||||||
if (url.scheme() == "file") {
|
|
||||||
_fileNameString = url.toLocalFile();
|
|
||||||
QFile scriptFile(_fileNameString);
|
|
||||||
if (scriptFile.open(QFile::ReadOnly | QFile::Text)) {
|
|
||||||
qCDebug(scriptengine) << "ScriptEngine loading file:" << _fileNameString;
|
|
||||||
QTextStream in(&scriptFile);
|
|
||||||
_scriptContents = in.readAll();
|
|
||||||
if (_wantSignals) {
|
|
||||||
emit scriptLoaded(_fileNameString);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
qCDebug(scriptengine) << "ERROR Loading file:" << _fileNameString << "line:" << __LINE__;
|
|
||||||
if (_wantSignals) {
|
|
||||||
emit errorLoadingScript(_fileNameString);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
bool isPending;
|
bool isPending;
|
||||||
auto scriptCache = DependencyManager::get<ScriptCache>();
|
auto scriptCache = DependencyManager::get<ScriptCache>();
|
||||||
scriptCache->getScript(url, this, isPending, reload);
|
scriptCache->getScript(url, this, isPending, reload);
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// FIXME - switch this to the new model of ScriptCache callbacks
|
||||||
void ScriptEngine::scriptContentsAvailable(const QUrl& url, const QString& scriptContents) {
|
void ScriptEngine::scriptContentsAvailable(const QUrl& url, const QString& scriptContents) {
|
||||||
_scriptContents = scriptContents;
|
_scriptContents = scriptContents;
|
||||||
if (_wantSignals) {
|
if (_wantSignals) {
|
||||||
|
@ -310,6 +247,7 @@ void ScriptEngine::scriptContentsAvailable(const QUrl& url, const QString& scrip
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// FIXME - switch this to the new model of ScriptCache callbacks
|
||||||
void ScriptEngine::errorInLoadingScript(const QUrl& url) {
|
void ScriptEngine::errorInLoadingScript(const QUrl& url) {
|
||||||
qCDebug(scriptengine) << "ERROR Loading file:" << url.toString() << "line:" << __LINE__;
|
qCDebug(scriptengine) << "ERROR Loading file:" << url.toString() << "line:" << __LINE__;
|
||||||
if (_wantSignals) {
|
if (_wantSignals) {
|
||||||
|
@ -383,57 +321,117 @@ void ScriptEngine::init() {
|
||||||
globalObject().setProperty("TREE_SCALE", newVariant(QVariant(TREE_SCALE)));
|
globalObject().setProperty("TREE_SCALE", newVariant(QVariant(TREE_SCALE)));
|
||||||
}
|
}
|
||||||
|
|
||||||
QScriptValue ScriptEngine::registerGlobalObject(const QString& name, QObject* object) {
|
void ScriptEngine::registerGlobalObject(const QString& name, QObject* object) {
|
||||||
|
if (QThread::currentThread() != thread()) {
|
||||||
|
#ifdef THREAD_DEBUGGING
|
||||||
|
qDebug() << "*** WARNING *** ScriptEngine::registerGlobalObject() called on wrong thread [" << QThread::currentThread() << "], invoking on correct thread [" << thread() << "] name:" << name;
|
||||||
|
#endif
|
||||||
|
QMetaObject::invokeMethod(this, "registerGlobalObject",
|
||||||
|
Q_ARG(const QString&, name),
|
||||||
|
Q_ARG(QObject*, object));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
#ifdef THREAD_DEBUGGING
|
||||||
|
qDebug() << "ScriptEngine::registerGlobalObject() called on thread [" << QThread::currentThread() << "] name:" << name;
|
||||||
|
#endif
|
||||||
|
|
||||||
if (object) {
|
if (object) {
|
||||||
QScriptValue value = newQObject(object);
|
QScriptValue value = newQObject(object);
|
||||||
globalObject().setProperty(name, value);
|
globalObject().setProperty(name, value);
|
||||||
return value;
|
|
||||||
}
|
}
|
||||||
return QScriptValue::NullValue;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void ScriptEngine::registerFunction(const QString& name, QScriptEngine::FunctionSignature fun, int numArguments) {
|
void ScriptEngine::registerFunction(const QString& name, QScriptEngine::FunctionSignature functionSignature, int numArguments) {
|
||||||
registerFunction(globalObject(), name, fun, numArguments);
|
if (QThread::currentThread() != thread()) {
|
||||||
|
#ifdef THREAD_DEBUGGING
|
||||||
|
qDebug() << "*** WARNING *** ScriptEngine::registerFunction() called on wrong thread [" << QThread::currentThread() << "], invoking on correct thread [" << thread() << "] name:" << name;
|
||||||
|
#endif
|
||||||
|
QMetaObject::invokeMethod(this, "registerFunction",
|
||||||
|
Q_ARG(const QString&, name),
|
||||||
|
Q_ARG(QScriptEngine::FunctionSignature, functionSignature),
|
||||||
|
Q_ARG(int, numArguments));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
#ifdef THREAD_DEBUGGING
|
||||||
|
qDebug() << "ScriptEngine::registerFunction() called on thread [" << QThread::currentThread() << "] name:" << name;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
QScriptValue scriptFun = newFunction(functionSignature, numArguments);
|
||||||
|
globalObject().setProperty(name, scriptFun);
|
||||||
}
|
}
|
||||||
|
|
||||||
void ScriptEngine::registerFunction(QScriptValue parent, const QString& name, QScriptEngine::FunctionSignature fun, int numArguments) {
|
void ScriptEngine::registerFunction(const QString& parent, const QString& name, QScriptEngine::FunctionSignature functionSignature, int numArguments) {
|
||||||
QScriptValue scriptFun = newFunction(fun, numArguments);
|
if (QThread::currentThread() != thread()) {
|
||||||
parent.setProperty(name, scriptFun);
|
#ifdef THREAD_DEBUGGING
|
||||||
|
qDebug() << "*** WARNING *** ScriptEngine::registerFunction() called on wrong thread [" << QThread::currentThread() << "], invoking on correct thread [" << thread() << "] parent:" << parent << "name:" << name;
|
||||||
|
#endif
|
||||||
|
QMetaObject::invokeMethod(this, "registerFunction",
|
||||||
|
Q_ARG(const QString&, name),
|
||||||
|
Q_ARG(QScriptEngine::FunctionSignature, functionSignature),
|
||||||
|
Q_ARG(int, numArguments));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
#ifdef THREAD_DEBUGGING
|
||||||
|
qDebug() << "ScriptEngine::registerFunction() called on thread [" << QThread::currentThread() << "] parent:" << parent << "name:" << name;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
QScriptValue object = globalObject().property(parent);
|
||||||
|
if (object.isValid()) {
|
||||||
|
QScriptValue scriptFun = newFunction(functionSignature, numArguments);
|
||||||
|
object.setProperty(name, scriptFun);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void ScriptEngine::registerGetterSetter(const QString& name, QScriptEngine::FunctionSignature getter,
|
void ScriptEngine::registerGetterSetter(const QString& name, QScriptEngine::FunctionSignature getter,
|
||||||
QScriptEngine::FunctionSignature setter, QScriptValue object) {
|
QScriptEngine::FunctionSignature setter, const QString& parent) {
|
||||||
|
if (QThread::currentThread() != thread()) {
|
||||||
|
#ifdef THREAD_DEBUGGING
|
||||||
|
qDebug() << "*** WARNING *** ScriptEngine::registerGetterSetter() called on wrong thread [" << QThread::currentThread() << "], invoking on correct thread [" << thread() << "] "
|
||||||
|
" name:" << name << "parent:" << parent;
|
||||||
|
#endif
|
||||||
|
QMetaObject::invokeMethod(this, "registerGetterSetter",
|
||||||
|
Q_ARG(const QString&, name),
|
||||||
|
Q_ARG(QScriptEngine::FunctionSignature, getter),
|
||||||
|
Q_ARG(QScriptEngine::FunctionSignature, setter),
|
||||||
|
Q_ARG(const QString&, parent));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
#ifdef THREAD_DEBUGGING
|
||||||
|
qDebug() << "ScriptEngine::registerGetterSetter() called on thread [" << QThread::currentThread() << "] name:" << name << "parent:" << parent;
|
||||||
|
#endif
|
||||||
|
|
||||||
QScriptValue setterFunction = newFunction(setter, 1);
|
QScriptValue setterFunction = newFunction(setter, 1);
|
||||||
QScriptValue getterFunction = newFunction(getter);
|
QScriptValue getterFunction = newFunction(getter);
|
||||||
|
|
||||||
if (!object.isNull()) {
|
if (!parent.isNull()) {
|
||||||
|
QScriptValue object = globalObject().property(parent);
|
||||||
|
if (object.isValid()) {
|
||||||
object.setProperty(name, setterFunction, QScriptValue::PropertySetter);
|
object.setProperty(name, setterFunction, QScriptValue::PropertySetter);
|
||||||
object.setProperty(name, getterFunction, QScriptValue::PropertyGetter);
|
object.setProperty(name, getterFunction, QScriptValue::PropertyGetter);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
globalObject().setProperty(name, setterFunction, QScriptValue::PropertySetter);
|
globalObject().setProperty(name, setterFunction, QScriptValue::PropertySetter);
|
||||||
globalObject().setProperty(name, getterFunction, QScriptValue::PropertyGetter);
|
globalObject().setProperty(name, getterFunction, QScriptValue::PropertyGetter);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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<QScriptValueList()> 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.
|
// Unregister the handlers for this eventName and entityID.
|
||||||
void ScriptEngine::removeEventHandler(const EntityItemID& entityID, const QString& eventName, QScriptValue handler) {
|
void ScriptEngine::removeEventHandler(const EntityItemID& entityID, const QString& eventName, QScriptValue handler) {
|
||||||
|
if (QThread::currentThread() != thread()) {
|
||||||
|
#ifdef THREAD_DEBUGGING
|
||||||
|
qDebug() << "*** WARNING *** ScriptEngine::removeEventHandler() called on wrong thread [" << QThread::currentThread() << "], invoking on correct thread [" << thread() << "] "
|
||||||
|
"entityID:" << entityID << " eventName:" << eventName;
|
||||||
|
#endif
|
||||||
|
QMetaObject::invokeMethod(this, "removeEventHandler",
|
||||||
|
Q_ARG(const EntityItemID&, entityID),
|
||||||
|
Q_ARG(const QString&, eventName),
|
||||||
|
Q_ARG(QScriptValue, handler));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
#ifdef THREAD_DEBUGGING
|
||||||
|
qDebug() << "ScriptEngine::removeEventHandler() called on thread [" << QThread::currentThread() << "] entityID:" << entityID << " eventName : " << eventName;
|
||||||
|
#endif
|
||||||
|
|
||||||
if (!_registeredHandlers.contains(entityID)) {
|
if (!_registeredHandlers.contains(entityID)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -449,6 +447,22 @@ void ScriptEngine::removeEventHandler(const EntityItemID& entityID, const QStrin
|
||||||
}
|
}
|
||||||
// Register the handler.
|
// Register the handler.
|
||||||
void ScriptEngine::addEventHandler(const EntityItemID& entityID, const QString& eventName, QScriptValue handler) {
|
void ScriptEngine::addEventHandler(const EntityItemID& entityID, const QString& eventName, QScriptValue handler) {
|
||||||
|
if (QThread::currentThread() != thread()) {
|
||||||
|
#ifdef THREAD_DEBUGGING
|
||||||
|
qDebug() << "*** WARNING *** ScriptEngine::addEventHandler() called on wrong thread [" << QThread::currentThread() << "], invoking on correct thread [" << thread() << "] "
|
||||||
|
"entityID:" << entityID << " eventName:" << eventName;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
QMetaObject::invokeMethod(this, "addEventHandler",
|
||||||
|
Q_ARG(const EntityItemID&, entityID),
|
||||||
|
Q_ARG(const QString&, eventName),
|
||||||
|
Q_ARG(QScriptValue, handler));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
#ifdef THREAD_DEBUGGING
|
||||||
|
qDebug() << "ScriptEngine::addEventHandler() called on thread [" << QThread::currentThread() << "] entityID:" << entityID << " eventName : " << eventName;
|
||||||
|
#endif
|
||||||
|
|
||||||
if (_registeredHandlers.count() == 0) { // First time any per-entity handler has been added in this script...
|
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.
|
// 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.)
|
||||||
|
@ -503,34 +517,25 @@ void ScriptEngine::addEventHandler(const EntityItemID& entityID, const QString&
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void ScriptEngine::evaluate() {
|
|
||||||
if (_stoppingAllScripts) {
|
|
||||||
return; // bail early
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!_isInitialized) {
|
|
||||||
init();
|
|
||||||
}
|
|
||||||
|
|
||||||
QScriptValue result = evaluate(_scriptContents);
|
|
||||||
|
|
||||||
// TODO: why do we check this twice? It seems like the call to clearExceptions() in the lower level evaluate call
|
|
||||||
// will cause this code to never actually run...
|
|
||||||
if (hasUncaughtException()) {
|
|
||||||
int line = uncaughtExceptionLineNumber();
|
|
||||||
qCDebug(scriptengine) << "Uncaught exception at (" << _fileNameString << ") line" << line << ":" << result.toString();
|
|
||||||
if (_wantSignals) {
|
|
||||||
emit errorMessage("Uncaught exception at (" + _fileNameString + ") line" + QString::number(line) + ":" + result.toString());
|
|
||||||
}
|
|
||||||
clearExceptions();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
QScriptValue ScriptEngine::evaluate(const QString& program, const QString& fileName, int lineNumber) {
|
QScriptValue ScriptEngine::evaluate(const QString& program, const QString& fileName, int lineNumber) {
|
||||||
if (_stoppingAllScripts) {
|
if (_stoppingAllScripts) {
|
||||||
return QScriptValue(); // bail early
|
return QScriptValue(); // bail early
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (QThread::currentThread() != thread()) {
|
||||||
|
QScriptValue result;
|
||||||
|
#ifdef THREAD_DEBUGGING
|
||||||
|
qDebug() << "*** WARNING *** ScriptEngine::evaluate() called on wrong thread [" << QThread::currentThread() << "], invoking on correct thread [" << thread() << "] "
|
||||||
|
"program:" << program << " fileName:" << fileName << "lineNumber:" << lineNumber;
|
||||||
|
#endif
|
||||||
|
QMetaObject::invokeMethod(this, "evaluate", Qt::BlockingQueuedConnection,
|
||||||
|
Q_RETURN_ARG(QScriptValue, result),
|
||||||
|
Q_ARG(const QString&, program),
|
||||||
|
Q_ARG(const QString&, fileName),
|
||||||
|
Q_ARG(int, lineNumber));
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
_evaluatesPending++;
|
_evaluatesPending++;
|
||||||
QScriptValue result = QScriptEngine::evaluate(program, fileName, lineNumber);
|
QScriptValue result = QScriptEngine::evaluate(program, fileName, lineNumber);
|
||||||
if (hasUncaughtException()) {
|
if (hasUncaughtException()) {
|
||||||
|
@ -545,18 +550,6 @@ QScriptValue ScriptEngine::evaluate(const QString& program, const QString& fileN
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
void ScriptEngine::sendAvatarIdentityPacket() {
|
|
||||||
if (_isAvatar && _avatarData) {
|
|
||||||
_avatarData->sendIdentityPacket();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void ScriptEngine::sendAvatarBillboardPacket() {
|
|
||||||
if (_isAvatar && _avatarData) {
|
|
||||||
_avatarData->sendBillboardPacket();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void ScriptEngine::run() {
|
void ScriptEngine::run() {
|
||||||
// TODO: can we add a short circuit for _stoppingAllScripts here? What does it mean to not start running if
|
// TODO: can we add a short circuit for _stoppingAllScripts here? What does it mean to not start running if
|
||||||
// we're in the process of stopping?
|
// we're in the process of stopping?
|
||||||
|
@ -608,107 +601,6 @@ void ScriptEngine::run() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!_isFinished && _isAvatar && _avatarData) {
|
|
||||||
|
|
||||||
const int SCRIPT_AUDIO_BUFFER_SAMPLES = floor(((SCRIPT_DATA_CALLBACK_USECS * AudioConstants::SAMPLE_RATE)
|
|
||||||
/ (1000 * 1000)) + 0.5);
|
|
||||||
const int SCRIPT_AUDIO_BUFFER_BYTES = SCRIPT_AUDIO_BUFFER_SAMPLES * sizeof(int16_t);
|
|
||||||
|
|
||||||
QByteArray avatarByteArray = _avatarData->toByteArray(true, randFloat() < AVATAR_SEND_FULL_UPDATE_RATIO);
|
|
||||||
_avatarData->doneEncoding(true);
|
|
||||||
auto avatarPacket = NLPacket::create(PacketType::AvatarData, avatarByteArray.size());
|
|
||||||
|
|
||||||
avatarPacket->write(avatarByteArray);
|
|
||||||
|
|
||||||
nodeList->broadcastToNodes(std::move(avatarPacket), NodeSet() << NodeType::AvatarMixer);
|
|
||||||
|
|
||||||
if (_isListeningToAudioStream || _avatarSound) {
|
|
||||||
// if we have an avatar audio stream then send it out to our audio-mixer
|
|
||||||
bool silentFrame = true;
|
|
||||||
|
|
||||||
int16_t numAvailableSamples = SCRIPT_AUDIO_BUFFER_SAMPLES;
|
|
||||||
const int16_t* nextSoundOutput = NULL;
|
|
||||||
|
|
||||||
if (_avatarSound) {
|
|
||||||
|
|
||||||
const QByteArray& soundByteArray = _avatarSound->getByteArray();
|
|
||||||
nextSoundOutput = reinterpret_cast<const int16_t*>(soundByteArray.data()
|
|
||||||
+ _numAvatarSoundSentBytes);
|
|
||||||
|
|
||||||
int numAvailableBytes = (soundByteArray.size() - _numAvatarSoundSentBytes) > SCRIPT_AUDIO_BUFFER_BYTES
|
|
||||||
? SCRIPT_AUDIO_BUFFER_BYTES
|
|
||||||
: soundByteArray.size() - _numAvatarSoundSentBytes;
|
|
||||||
numAvailableSamples = numAvailableBytes / sizeof(int16_t);
|
|
||||||
|
|
||||||
|
|
||||||
// check if the all of the _numAvatarAudioBufferSamples to be sent are silence
|
|
||||||
for (int i = 0; i < numAvailableSamples; ++i) {
|
|
||||||
if (nextSoundOutput[i] != 0) {
|
|
||||||
silentFrame = false;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
_numAvatarSoundSentBytes += numAvailableBytes;
|
|
||||||
if (_numAvatarSoundSentBytes == soundByteArray.size()) {
|
|
||||||
// we're done with this sound object - so set our pointer back to NULL
|
|
||||||
// and our sent bytes back to zero
|
|
||||||
_avatarSound = NULL;
|
|
||||||
_numAvatarSoundSentBytes = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
auto audioPacket = NLPacket::create(silentFrame
|
|
||||||
? PacketType::SilentAudioFrame
|
|
||||||
: PacketType::MicrophoneAudioNoEcho);
|
|
||||||
|
|
||||||
// seek past the sequence number, will be packed when destination node is known
|
|
||||||
audioPacket->seek(sizeof(quint16));
|
|
||||||
|
|
||||||
if (silentFrame) {
|
|
||||||
if (!_isListeningToAudioStream) {
|
|
||||||
// if we have a silent frame and we're not listening then just send nothing and break out of here
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
// write the number of silent samples so the audio-mixer can uphold timing
|
|
||||||
audioPacket->writePrimitive(SCRIPT_AUDIO_BUFFER_SAMPLES);
|
|
||||||
|
|
||||||
// use the orientation and position of this avatar for the source of this audio
|
|
||||||
audioPacket->writePrimitive(_avatarData->getPosition());
|
|
||||||
glm::quat headOrientation = _avatarData->getHeadOrientation();
|
|
||||||
audioPacket->writePrimitive(headOrientation);
|
|
||||||
|
|
||||||
} else if (nextSoundOutput) {
|
|
||||||
// assume scripted avatar audio is mono and set channel flag to zero
|
|
||||||
audioPacket->writePrimitive((quint8) 0);
|
|
||||||
|
|
||||||
// use the orientation and position of this avatar for the source of this audio
|
|
||||||
audioPacket->writePrimitive(_avatarData->getPosition());
|
|
||||||
glm::quat headOrientation = _avatarData->getHeadOrientation();
|
|
||||||
audioPacket->writePrimitive(headOrientation);
|
|
||||||
|
|
||||||
// write the raw audio data
|
|
||||||
audioPacket->write(reinterpret_cast<const char*>(nextSoundOutput), numAvailableSamples * sizeof(int16_t));
|
|
||||||
}
|
|
||||||
|
|
||||||
// write audio packet to AudioMixer nodes
|
|
||||||
auto nodeList = DependencyManager::get<NodeList>();
|
|
||||||
nodeList->eachNode([this, &nodeList, &audioPacket](const SharedNodePointer& node){
|
|
||||||
// only send to nodes of type AudioMixer
|
|
||||||
if (node->getType() == NodeType::AudioMixer) {
|
|
||||||
// pack sequence number
|
|
||||||
quint16 sequence = _outgoingScriptAudioSequenceNumbers[node->getUUID()]++;
|
|
||||||
audioPacket->seek(0);
|
|
||||||
audioPacket->writePrimitive(sequence);
|
|
||||||
|
|
||||||
// send audio packet
|
|
||||||
nodeList->sendUnreliablePacket(*audioPacket, *node);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
qint64 now = usecTimestampNow();
|
qint64 now = usecTimestampNow();
|
||||||
float deltaTime = (float) (now - lastUpdate) / (float) USECS_PER_SECOND;
|
float deltaTime = (float) (now - lastUpdate) / (float) USECS_PER_SECOND;
|
||||||
|
|
||||||
|
@ -735,9 +627,6 @@ void ScriptEngine::run() {
|
||||||
emit scriptEnding();
|
emit scriptEnding();
|
||||||
}
|
}
|
||||||
|
|
||||||
// kill the avatar identity timer
|
|
||||||
delete _avatarIdentityTimer;
|
|
||||||
|
|
||||||
if (entityScriptingInterface->getEntityPacketSender()->serversExist()) {
|
if (entityScriptingInterface->getEntityPacketSender()->serversExist()) {
|
||||||
// release the queue of edit entity messages.
|
// release the queue of edit entity messages.
|
||||||
entityScriptingInterface->getEntityPacketSender()->releaseQueuedMessages();
|
entityScriptingInterface->getEntityPacketSender()->releaseQueuedMessages();
|
||||||
|
@ -967,6 +856,256 @@ void ScriptEngine::load(const QString& loadFile) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void ScriptEngine::nodeKilled(SharedNodePointer node) {
|
// Look up the handler associated with eventName and entityID. If found, evalute the argGenerator thunk and call the handler with those args
|
||||||
_outgoingScriptAudioSequenceNumbers.remove(node->getUUID());
|
void ScriptEngine::generalHandler(const EntityItemID& entityID, const QString& eventName, std::function<QScriptValueList()> argGenerator) {
|
||||||
|
if (QThread::currentThread() != thread()) {
|
||||||
|
qDebug() << "*** ERROR *** ScriptEngine::generalHandler() called on wrong thread [" << QThread::currentThread() << "], invoking on correct thread [" << thread() << "]";
|
||||||
|
assert(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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()) {
|
||||||
|
#ifdef THREAD_DEBUGGING
|
||||||
|
qDebug() << "*** WARNING *** ScriptEngine::loadEntityScript() called on wrong thread [" << QThread::currentThread() << "], invoking on correct thread [" << thread() << "] "
|
||||||
|
"entityID:" << entityID << "entityScript:" << entityScript <<"forceRedownload:" << forceRedownload;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
QMetaObject::invokeMethod(this, "loadEntityScript",
|
||||||
|
Q_ARG(const EntityItemID&, entityID),
|
||||||
|
Q_ARG(const QString&, entityScript),
|
||||||
|
Q_ARG(bool, forceRedownload));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
#ifdef THREAD_DEBUGGING
|
||||||
|
qDebug() << "ScriptEngine::loadEntityScript() called on correct thread [" << thread() << "] "
|
||||||
|
"entityID:" << entityID << "entityScript:" << entityScript << "forceRedownload:" << forceRedownload;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// If we've been called our known entityScripts should not know about us..
|
||||||
|
assert(!_entityScripts.contains(entityID));
|
||||||
|
|
||||||
|
#ifdef THREAD_DEBUGGING
|
||||||
|
qDebug() << "ScriptEngine::loadEntityScript() calling scriptCache->getScriptContents() on thread [" << QThread::currentThread() << "] expected thread [" << thread() << "]";
|
||||||
|
#endif
|
||||||
|
DependencyManager::get<ScriptCache>()->getScriptContents(entityScript, [=](const QString& scriptOrURL, const QString& contents, bool isURL, bool success) {
|
||||||
|
#ifdef THREAD_DEBUGGING
|
||||||
|
qDebug() << "ScriptEngine::entityScriptContentAvailable() IN LAMBDA contentAvailable on thread [" << QThread::currentThread() << "] expected thread [" << thread() << "]";
|
||||||
|
#endif
|
||||||
|
|
||||||
|
this->entityScriptContentAvailable(entityID, scriptOrURL, contents, isURL, success);
|
||||||
|
}, forceRedownload);
|
||||||
|
}
|
||||||
|
|
||||||
|
// since all of these operations can be asynch we will always do the actual work in the response handler
|
||||||
|
// for the download
|
||||||
|
void ScriptEngine::entityScriptContentAvailable(const EntityItemID& entityID, const QString& scriptOrURL, const QString& contents, bool isURL, bool success) {
|
||||||
|
if (QThread::currentThread() != thread()) {
|
||||||
|
#ifdef THREAD_DEBUGGING
|
||||||
|
qDebug() << "*** WARNING *** ScriptEngine::entityScriptContentAvailable() called on wrong thread [" << QThread::currentThread() << "], invoking on correct thread [" << thread() << "] "
|
||||||
|
"entityID:" << entityID << "scriptOrURL:" << scriptOrURL << "contents:" << contents << "isURL:" << isURL << "success:" << success;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
QMetaObject::invokeMethod(this, "entityScriptContentAvailable",
|
||||||
|
Q_ARG(const EntityItemID&, entityID),
|
||||||
|
Q_ARG(const QString&, scriptOrURL),
|
||||||
|
Q_ARG(const QString&, contents),
|
||||||
|
Q_ARG(bool, isURL),
|
||||||
|
Q_ARG(bool, success));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef THREAD_DEBUGGING
|
||||||
|
qDebug() << "ScriptEngine::entityScriptContentAvailable() thread [" << QThread::currentThread() << "] expected thread [" << thread() << "]";
|
||||||
|
#endif
|
||||||
|
|
||||||
|
auto scriptCache = DependencyManager::get<ScriptCache>();
|
||||||
|
|
||||||
|
// 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");
|
||||||
|
}
|
||||||
|
|
||||||
|
void ScriptEngine::unloadEntityScript(const EntityItemID& entityID) {
|
||||||
|
if (QThread::currentThread() != thread()) {
|
||||||
|
#ifdef THREAD_DEBUGGING
|
||||||
|
qDebug() << "*** WARNING *** ScriptEngine::unloadEntityScript() called on wrong thread [" << QThread::currentThread() << "], invoking on correct thread [" << thread() << "] "
|
||||||
|
"entityID:" << entityID;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
QMetaObject::invokeMethod(this, "unloadEntityScript",
|
||||||
|
Q_ARG(const EntityItemID&, entityID));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
#ifdef THREAD_DEBUGGING
|
||||||
|
qDebug() << "ScriptEngine::unloadEntityScript() called on correct thread [" << thread() << "] "
|
||||||
|
"entityID:" << entityID;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
if (_entityScripts.contains(entityID)) {
|
||||||
|
callEntityScriptMethod(entityID, "unload");
|
||||||
|
_entityScripts.remove(entityID);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ScriptEngine::unloadAllEntityScripts() {
|
||||||
|
if (QThread::currentThread() != thread()) {
|
||||||
|
#ifdef THREAD_DEBUGGING
|
||||||
|
qDebug() << "*** WARNING *** ScriptEngine::unloadAllEntityScripts() called on wrong thread [" << QThread::currentThread() << "], invoking on correct thread [" << thread() << "]";
|
||||||
|
#endif
|
||||||
|
|
||||||
|
QMetaObject::invokeMethod(this, "unloadAllEntityScripts");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
#ifdef THREAD_DEBUGGING
|
||||||
|
qDebug() << "ScriptEngine::unloadAllEntityScripts() called on correct thread [" << thread() << "]";
|
||||||
|
#endif
|
||||||
|
foreach(const EntityItemID& entityID, _entityScripts.keys()) {
|
||||||
|
callEntityScriptMethod(entityID, "unload");
|
||||||
|
}
|
||||||
|
_entityScripts.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ScriptEngine::callEntityScriptMethod(const EntityItemID& entityID, const QString& methodName) {
|
||||||
|
if (QThread::currentThread() != thread()) {
|
||||||
|
#ifdef THREAD_DEBUGGING
|
||||||
|
qDebug() << "*** WARNING *** ScriptEngine::callEntityScriptMethod() called on wrong thread [" << QThread::currentThread() << "], invoking on correct thread [" << thread() << "] "
|
||||||
|
"entityID:" << entityID << "methodName:" << methodName;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
QMetaObject::invokeMethod(this, "callEntityScriptMethod",
|
||||||
|
Q_ARG(const EntityItemID&, entityID),
|
||||||
|
Q_ARG(const QString&, methodName));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
#ifdef THREAD_DEBUGGING
|
||||||
|
qDebug() << "ScriptEngine::callEntityScriptMethod() called on correct thread [" << thread() << "] "
|
||||||
|
"entityID:" << entityID << "methodName:" << methodName;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
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()) {
|
||||||
|
#ifdef THREAD_DEBUGGING
|
||||||
|
qDebug() << "*** WARNING *** ScriptEngine::callEntityScriptMethod() called on wrong thread [" << QThread::currentThread() << "], invoking on correct thread [" << thread() << "] "
|
||||||
|
"entityID:" << entityID << "methodName:" << methodName << "event: mouseEvent";
|
||||||
|
#endif
|
||||||
|
|
||||||
|
QMetaObject::invokeMethod(this, "callEntityScriptMethod",
|
||||||
|
Q_ARG(const EntityItemID&, entityID),
|
||||||
|
Q_ARG(const QString&, methodName),
|
||||||
|
Q_ARG(const MouseEvent&, event));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
#ifdef THREAD_DEBUGGING
|
||||||
|
qDebug() << "ScriptEngine::callEntityScriptMethod() called on correct thread [" << thread() << "] "
|
||||||
|
"entityID:" << entityID << "methodName:" << methodName << "event: mouseEvent";
|
||||||
|
#endif
|
||||||
|
|
||||||
|
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()) {
|
||||||
|
#ifdef THREAD_DEBUGGING
|
||||||
|
qDebug() << "*** WARNING *** ScriptEngine::callEntityScriptMethod() called on wrong thread [" << QThread::currentThread() << "], invoking on correct thread [" << thread() << "] "
|
||||||
|
"entityID:" << entityID << "methodName:" << methodName << "otherID:" << otherID << "collision: collision";
|
||||||
|
#endif
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
#ifdef THREAD_DEBUGGING
|
||||||
|
qDebug() << "ScriptEngine::callEntityScriptMethod() called on correct thread [" << thread() << "] "
|
||||||
|
"entityID:" << entityID << "methodName:" << methodName << "otherID:" << otherID << "collision: collision";
|
||||||
|
#endif
|
||||||
|
|
||||||
|
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;
|
typedef QHash<QString, QScriptValueList> RegisteredEventHandlers;
|
||||||
|
|
||||||
|
class EntityScriptDetails {
|
||||||
|
public:
|
||||||
|
QString scriptText;
|
||||||
|
QScriptValue scriptObject;
|
||||||
|
};
|
||||||
|
|
||||||
class ScriptEngine : public QScriptEngine, public ScriptUser {
|
class ScriptEngine : public QScriptEngine, public ScriptUser {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
public:
|
public:
|
||||||
|
@ -50,79 +56,86 @@ public:
|
||||||
|
|
||||||
~ScriptEngine();
|
~ScriptEngine();
|
||||||
|
|
||||||
ArrayBufferClass* getArrayBufferClass() { return _arrayBufferClass; }
|
/// 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();
|
||||||
|
|
||||||
/// sets the script contents, will return false if failed, will fail if script is already running
|
/// run the script in the callers thread, exit when stop() is called.
|
||||||
bool setScriptContents(const QString& scriptContents, const QString& fileNameString = QString(""));
|
void run();
|
||||||
|
|
||||||
const QString& getScriptName() const { return _scriptName; }
|
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// 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
|
||||||
|
|
||||||
QScriptValue registerGlobalObject(const QString& name, QObject* object); /// registers a global object by name
|
/// registers a global object by name
|
||||||
void registerGetterSetter(const QString& name, QScriptEngine::FunctionSignature getter,
|
Q_INVOKABLE void registerGlobalObject(const QString& name, QObject* object);
|
||||||
QScriptEngine::FunctionSignature setter, QScriptValue object = QScriptValue::NullValue);
|
|
||||||
void registerFunction(const QString& name, QScriptEngine::FunctionSignature fun, int numArguments = -1);
|
/// registers a global getter/setter
|
||||||
void registerFunction(QScriptValue parent, const QString& name, QScriptEngine::FunctionSignature fun,
|
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);
|
int numArguments = -1);
|
||||||
|
|
||||||
Q_INVOKABLE void setIsAvatar(bool isAvatar);
|
/// evaluate some code in the context of the ScriptEngine and return the result
|
||||||
bool isAvatar() const { return _isAvatar; }
|
Q_INVOKABLE QScriptValue evaluate(const QString& program, const QString& fileName = QString(), int lineNumber = 1); // this is also used by the script tool widget
|
||||||
|
|
||||||
void setAvatarData(AvatarData* avatarData, const QString& objectName);
|
/// if the script engine is not already running, this will download the URL and start the process of seting it up
|
||||||
void setAvatarHashMap(AvatarHashMap* avatarHashMap, const QString& objectName);
|
/// 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
|
||||||
bool isListeningToAudioStream() const { return _isListeningToAudioStream; }
|
void loadURL(const QUrl& scriptURL, bool reload);
|
||||||
void setIsListeningToAudioStream(bool isListeningToAudioStream) { _isListeningToAudioStream = isListeningToAudioStream; }
|
|
||||||
|
|
||||||
void setAvatarSound(Sound* avatarSound) { _avatarSound = avatarSound; }
|
|
||||||
bool isPlayingAvatarSound() const { return _avatarSound != NULL; }
|
|
||||||
|
|
||||||
void init();
|
|
||||||
void run(); /// runs continuously until Agent.stop() is called
|
|
||||||
void evaluate(); /// initializes the engine, and evaluates the script, but then returns control to caller
|
|
||||||
|
|
||||||
void timerFired();
|
|
||||||
|
|
||||||
bool hasScript() const { return !_scriptContents.isEmpty(); }
|
|
||||||
|
|
||||||
bool isFinished() const { return _isFinished; }
|
|
||||||
bool isRunning() const { return _isRunning; }
|
|
||||||
bool evaluatePending() const { return _evaluatesPending > 0; }
|
|
||||||
|
|
||||||
void setUserLoaded(bool isUserLoaded) { _isUserLoaded = isUserLoaded; }
|
|
||||||
bool isUserLoaded() const { return _isUserLoaded; }
|
|
||||||
|
|
||||||
void setIsAgent(bool isAgent) { _isAgent = isAgent; }
|
|
||||||
|
|
||||||
void setParentURL(const QString& parentURL) { _parentURL = parentURL; }
|
|
||||||
|
|
||||||
QString getFilename() const;
|
|
||||||
|
|
||||||
static void stopAllScripts(QObject* application);
|
|
||||||
|
|
||||||
void waitTillDoneRunning();
|
|
||||||
|
|
||||||
virtual void scriptContentsAvailable(const QUrl& url, const QString& scriptContents);
|
|
||||||
virtual void errorInLoadingScript(const QUrl& url);
|
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// 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 addEventHandler(const EntityItemID& entityID, const QString& eventName, QScriptValue handler);
|
||||||
Q_INVOKABLE void removeEventHandler(const EntityItemID& entityID, const QString& eventName, QScriptValue handler);
|
Q_INVOKABLE void removeEventHandler(const EntityItemID& entityID, const QString& eventName, QScriptValue handler);
|
||||||
|
|
||||||
public slots:
|
Q_INVOKABLE void load(const QString& loadfile);
|
||||||
void loadURL(const QUrl& scriptURL, bool reload);
|
Q_INVOKABLE void include(const QStringList& includeFiles, QScriptValue callback = QScriptValue());
|
||||||
void stop();
|
Q_INVOKABLE void include(const QString& includeFile, QScriptValue callback = QScriptValue());
|
||||||
|
|
||||||
QScriptValue evaluate(const QString& program, const QString& fileName = QString(), int lineNumber = 1);
|
Q_INVOKABLE QObject* setInterval(const QScriptValue& function, int intervalMS);
|
||||||
QObject* setInterval(const QScriptValue& function, int intervalMS);
|
Q_INVOKABLE QObject* setTimeout(const QScriptValue& function, int timeoutMS);
|
||||||
QObject* setTimeout(const QScriptValue& function, int timeoutMS);
|
Q_INVOKABLE void clearInterval(QObject* timer) { stopTimer(reinterpret_cast<QTimer*>(timer)); }
|
||||||
void clearInterval(QObject* timer) { stopTimer(reinterpret_cast<QTimer*>(timer)); }
|
Q_INVOKABLE void clearTimeout(QObject* timer) { stopTimer(reinterpret_cast<QTimer*>(timer)); }
|
||||||
void clearTimeout(QObject* timer) { stopTimer(reinterpret_cast<QTimer*>(timer)); }
|
Q_INVOKABLE void print(const QString& message);
|
||||||
void include(const QStringList& includeFiles, QScriptValue callback = QScriptValue());
|
Q_INVOKABLE QUrl resolvePath(const QString& path) const;
|
||||||
void include(const QString& includeFile, QScriptValue callback = QScriptValue());
|
|
||||||
void load(const QString& loadfile);
|
|
||||||
void print(const QString& message);
|
|
||||||
QUrl resolvePath(const QString& path) const;
|
|
||||||
|
|
||||||
void nodeKilled(SharedNodePointer node);
|
// 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 unloadAllEntityScripts();
|
||||||
|
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();
|
||||||
|
|
||||||
|
bool isFinished() const { return _isFinished; } // used by Application and ScriptWidget
|
||||||
|
bool isRunning() const { return _isRunning; } // used by ScriptWidget
|
||||||
|
|
||||||
|
static void stopAllScripts(QObject* application); // used by Application on shutdown
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// NOTE - These are the callback implementations for ScriptUser the get called by ScriptCache when the contents
|
||||||
|
// of a script are available.
|
||||||
|
virtual void scriptContentsAvailable(const QUrl& url, const QString& scriptContents);
|
||||||
|
virtual void errorInLoadingScript(const QUrl& url);
|
||||||
|
|
||||||
|
// 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 implemetation. we need to review this for thread safety
|
||||||
|
ArrayBufferClass* getArrayBufferClass() { return _arrayBufferClass; }
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void scriptLoaded(const QString& scriptFilename);
|
void scriptLoaded(const QString& scriptFilename);
|
||||||
|
@ -146,28 +159,24 @@ protected:
|
||||||
bool _isRunning;
|
bool _isRunning;
|
||||||
int _evaluatesPending = 0;
|
int _evaluatesPending = 0;
|
||||||
bool _isInitialized;
|
bool _isInitialized;
|
||||||
bool _isAvatar;
|
|
||||||
QTimer* _avatarIdentityTimer;
|
|
||||||
QTimer* _avatarBillboardTimer;
|
|
||||||
QHash<QTimer*, QScriptValue> _timerFunctionMap;
|
QHash<QTimer*, QScriptValue> _timerFunctionMap;
|
||||||
bool _isListeningToAudioStream;
|
|
||||||
Sound* _avatarSound;
|
|
||||||
int _numAvatarSoundSentBytes;
|
|
||||||
bool _isAgent = false;
|
|
||||||
QSet<QUrl> _includedURLs;
|
QSet<QUrl> _includedURLs;
|
||||||
bool _wantSignals = true;
|
bool _wantSignals = true;
|
||||||
|
QHash<EntityItemID, EntityScriptDetails> _entityScripts;
|
||||||
private:
|
private:
|
||||||
|
void init();
|
||||||
|
QString getFilename() const;
|
||||||
|
void waitTillDoneRunning();
|
||||||
|
bool evaluatePending() const { return _evaluatesPending > 0; }
|
||||||
|
void timerFired();
|
||||||
void stopAllTimers();
|
void stopAllTimers();
|
||||||
void sendAvatarIdentityPacket();
|
|
||||||
void sendAvatarBillboardPacket();
|
void setParentURL(const QString& parentURL) { _parentURL = parentURL; }
|
||||||
|
|
||||||
QObject* setupTimerWithInterval(const QScriptValue& function, int intervalMS, bool isSingleShot);
|
QObject* setupTimerWithInterval(const QScriptValue& function, int intervalMS, bool isSingleShot);
|
||||||
void stopTimer(QTimer* timer);
|
void stopTimer(QTimer* timer);
|
||||||
|
|
||||||
AbstractControllerScriptingInterface* _controllerScriptingInterface;
|
AbstractControllerScriptingInterface* _controllerScriptingInterface;
|
||||||
AvatarData* _avatarData;
|
|
||||||
QString _scriptName;
|
|
||||||
QString _fileNameString;
|
QString _fileNameString;
|
||||||
Quat _quatLibrary;
|
Quat _quatLibrary;
|
||||||
Vec3 _vec3Library;
|
Vec3 _vec3Library;
|
||||||
|
@ -177,15 +186,15 @@ private:
|
||||||
|
|
||||||
ArrayBufferClass* _arrayBufferClass;
|
ArrayBufferClass* _arrayBufferClass;
|
||||||
|
|
||||||
QHash<QUuid, quint16> _outgoingScriptAudioSequenceNumbers;
|
|
||||||
QHash<EntityItemID, RegisteredEventHandlers> _registeredHandlers;
|
QHash<EntityItemID, RegisteredEventHandlers> _registeredHandlers;
|
||||||
void generalHandler(const EntityItemID& entityID, const QString& eventName, std::function<QScriptValueList()> argGenerator);
|
void generalHandler(const EntityItemID& entityID, const QString& eventName, std::function<QScriptValueList()> argGenerator);
|
||||||
|
Q_INVOKABLE void entityScriptContentAvailable(const EntityItemID& entityID, const QString& scriptOrURL, const QString& contents, bool isURL, bool success);
|
||||||
|
|
||||||
private:
|
|
||||||
static QSet<ScriptEngine*> _allKnownScriptEngines;
|
static QSet<ScriptEngine*> _allKnownScriptEngines;
|
||||||
static QMutex _allScriptsMutex;
|
static QMutex _allScriptsMutex;
|
||||||
static bool _stoppingAllScripts;
|
static bool _stoppingAllScripts;
|
||||||
static bool _doneRunningThisScript;
|
static bool _doneRunningThisScript;
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // hifi_ScriptEngine_h
|
#endif // hifi_ScriptEngine_h
|
||||||
|
|
Loading…
Reference in a new issue