mirror of
https://github.com/overte-org/overte.git
synced 2025-08-08 08:17:35 +02:00
Merge branch 'master' of github.com:highfidelity/hifi into avatar-entities-3
This commit is contained in:
commit
3380b2a668
24 changed files with 481 additions and 404 deletions
2
LICENSE
2
LICENSE
|
@ -6,7 +6,7 @@ Licensed under the Apache License version 2.0 (the "License");
|
||||||
You may not use this software except in compliance with the License.
|
You may not use this software except in compliance with the License.
|
||||||
You may obtain a copy of the License at: http://www.apache.org/licenses/LICENSE-2.0
|
You may obtain a copy of the License at: http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
This software includes third-party software.
|
This software includes third-party and other platform software.
|
||||||
Please see each individual software license for additional details.
|
Please see each individual software license for additional details.
|
||||||
|
|
||||||
This software is distributed "as-is" without any warranties, conditions, or representations whether express or implied, including without limitation the implied warranties and conditions of merchantability, merchantable quality, fitness for a particular purpose, performance, durability, title, non-infringement, and those arising from statute or from custom or usage of trade or course of dealing.
|
This software is distributed "as-is" without any warranties, conditions, or representations whether express or implied, including without limitation the implied warranties and conditions of merchantability, merchantable quality, fitness for a particular purpose, performance, durability, title, non-infringement, and those arising from statute or from custom or usage of trade or course of dealing.
|
||||||
|
|
|
@ -290,7 +290,6 @@ void Agent::executeScript() {
|
||||||
packetReceiver.registerListener(PacketType::BulkAvatarData, avatarHashMap.data(), "processAvatarDataPacket");
|
packetReceiver.registerListener(PacketType::BulkAvatarData, avatarHashMap.data(), "processAvatarDataPacket");
|
||||||
packetReceiver.registerListener(PacketType::KillAvatar, avatarHashMap.data(), "processKillAvatar");
|
packetReceiver.registerListener(PacketType::KillAvatar, avatarHashMap.data(), "processKillAvatar");
|
||||||
packetReceiver.registerListener(PacketType::AvatarIdentity, avatarHashMap.data(), "processAvatarIdentityPacket");
|
packetReceiver.registerListener(PacketType::AvatarIdentity, avatarHashMap.data(), "processAvatarIdentityPacket");
|
||||||
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);
|
||||||
|
@ -341,15 +340,12 @@ void Agent::setIsAvatar(bool isAvatar) {
|
||||||
if (_isAvatar && !_avatarIdentityTimer) {
|
if (_isAvatar && !_avatarIdentityTimer) {
|
||||||
// set up the avatar timers
|
// set up the avatar timers
|
||||||
_avatarIdentityTimer = new QTimer(this);
|
_avatarIdentityTimer = new QTimer(this);
|
||||||
_avatarBillboardTimer = new QTimer(this);
|
|
||||||
|
|
||||||
// connect our slot
|
// connect our slot
|
||||||
connect(_avatarIdentityTimer, &QTimer::timeout, this, &Agent::sendAvatarIdentityPacket);
|
connect(_avatarIdentityTimer, &QTimer::timeout, this, &Agent::sendAvatarIdentityPacket);
|
||||||
connect(_avatarBillboardTimer, &QTimer::timeout, this, &Agent::sendAvatarBillboardPacket);
|
|
||||||
|
|
||||||
// start the timers
|
// start the timers
|
||||||
_avatarIdentityTimer->start(AVATAR_IDENTITY_PACKET_SEND_INTERVAL_MSECS);
|
_avatarIdentityTimer->start(AVATAR_IDENTITY_PACKET_SEND_INTERVAL_MSECS);
|
||||||
_avatarBillboardTimer->start(AVATAR_BILLBOARD_PACKET_SEND_INTERVAL_MSECS);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!_isAvatar) {
|
if (!_isAvatar) {
|
||||||
|
@ -359,12 +355,6 @@ void Agent::setIsAvatar(bool isAvatar) {
|
||||||
delete _avatarIdentityTimer;
|
delete _avatarIdentityTimer;
|
||||||
_avatarIdentityTimer = nullptr;
|
_avatarIdentityTimer = nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_avatarBillboardTimer) {
|
|
||||||
_avatarBillboardTimer->stop();
|
|
||||||
delete _avatarBillboardTimer;
|
|
||||||
_avatarBillboardTimer = nullptr;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -375,14 +365,6 @@ void Agent::sendAvatarIdentityPacket() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Agent::sendAvatarBillboardPacket() {
|
|
||||||
if (_isAvatar) {
|
|
||||||
auto scriptedAvatar = DependencyManager::get<ScriptableAvatar>();
|
|
||||||
scriptedAvatar->sendBillboardPacket();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void Agent::processAgentAvatarAndAudio(float deltaTime) {
|
void Agent::processAgentAvatarAndAudio(float deltaTime) {
|
||||||
if (!_scriptEngine->isFinished() && _isAvatar) {
|
if (!_scriptEngine->isFinished() && _isAvatar) {
|
||||||
auto scriptedAvatar = DependencyManager::get<ScriptableAvatar>();
|
auto scriptedAvatar = DependencyManager::get<ScriptableAvatar>();
|
||||||
|
@ -491,7 +473,7 @@ void Agent::processAgentAvatarAndAudio(float deltaTime) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void Agent::aboutToFinish() {
|
void Agent::aboutToFinish() {
|
||||||
setIsAvatar(false);// will stop timers for sending billboards and identity packets
|
setIsAvatar(false);// will stop timers for sending identity packets
|
||||||
|
|
||||||
if (_scriptEngine) {
|
if (_scriptEngine) {
|
||||||
_scriptEngine->stop();
|
_scriptEngine->stop();
|
||||||
|
|
|
@ -82,7 +82,6 @@ private:
|
||||||
void setAvatarSound(SharedSoundPointer avatarSound) { _avatarSound = avatarSound; }
|
void setAvatarSound(SharedSoundPointer avatarSound) { _avatarSound = avatarSound; }
|
||||||
|
|
||||||
void sendAvatarIdentityPacket();
|
void sendAvatarIdentityPacket();
|
||||||
void sendAvatarBillboardPacket();
|
|
||||||
|
|
||||||
QString _scriptContents;
|
QString _scriptContents;
|
||||||
QTimer* _scriptRequestTimeout { nullptr };
|
QTimer* _scriptRequestTimeout { nullptr };
|
||||||
|
@ -92,7 +91,6 @@ private:
|
||||||
int _numAvatarSoundSentBytes = 0;
|
int _numAvatarSoundSentBytes = 0;
|
||||||
bool _isAvatar = false;
|
bool _isAvatar = false;
|
||||||
QTimer* _avatarIdentityTimer = nullptr;
|
QTimer* _avatarIdentityTimer = nullptr;
|
||||||
QTimer* _avatarBillboardTimer = nullptr;
|
|
||||||
QHash<QUuid, quint16> _outgoingScriptAudioSequenceNumbers;
|
QHash<QUuid, quint16> _outgoingScriptAudioSequenceNumbers;
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
|
@ -349,19 +349,14 @@ public:
|
||||||
};
|
};
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
enum CustomEventTypes {
|
|
||||||
Lambda = QEvent::User + 1,
|
|
||||||
Paint = Lambda + 1,
|
|
||||||
};
|
|
||||||
|
|
||||||
class LambdaEvent : public QEvent {
|
class LambdaEvent : public QEvent {
|
||||||
std::function<void()> _fun;
|
std::function<void()> _fun;
|
||||||
public:
|
public:
|
||||||
LambdaEvent(const std::function<void()> & fun) :
|
LambdaEvent(const std::function<void()> & fun) :
|
||||||
QEvent(static_cast<QEvent::Type>(Lambda)), _fun(fun) {
|
QEvent(static_cast<QEvent::Type>(Application::Lambda)), _fun(fun) {
|
||||||
}
|
}
|
||||||
LambdaEvent(std::function<void()> && fun) :
|
LambdaEvent(std::function<void()> && fun) :
|
||||||
QEvent(static_cast<QEvent::Type>(Lambda)), _fun(fun) {
|
QEvent(static_cast<QEvent::Type>(Application::Lambda)), _fun(fun) {
|
||||||
}
|
}
|
||||||
void call() const { _fun(); }
|
void call() const { _fun(); }
|
||||||
};
|
};
|
||||||
|
@ -1062,18 +1057,6 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer) :
|
||||||
connect(this, &Application::applicationStateChanged, this, &Application::activeChanged);
|
connect(this, &Application::applicationStateChanged, this, &Application::activeChanged);
|
||||||
qCDebug(interfaceapp, "Startup time: %4.2f seconds.", (double)startupTimer.elapsed() / 1000.0);
|
qCDebug(interfaceapp, "Startup time: %4.2f seconds.", (double)startupTimer.elapsed() / 1000.0);
|
||||||
|
|
||||||
_idleTimer = new QTimer(this);
|
|
||||||
connect(_idleTimer, &QTimer::timeout, [=] {
|
|
||||||
idle(usecTimestampNow());
|
|
||||||
});
|
|
||||||
connect(this, &Application::beforeAboutToQuit, [=] {
|
|
||||||
disconnect(_idleTimer);
|
|
||||||
});
|
|
||||||
// Setting the interval to zero forces this to get called whenever there are no messages
|
|
||||||
// in the queue, which can be pretty damn frequent. Hence the idle function has a bunch
|
|
||||||
// of logic to abort early if it's being called too often.
|
|
||||||
_idleTimer->start(0);
|
|
||||||
|
|
||||||
// After all of the constructor is completed, then set firstRun to false.
|
// After all of the constructor is completed, then set firstRun to false.
|
||||||
Setting::Handle<bool> firstRun{ Settings::firstRun, true };
|
Setting::Handle<bool> firstRun{ Settings::firstRun, true };
|
||||||
firstRun.set(false);
|
firstRun.set(false);
|
||||||
|
@ -1161,10 +1144,16 @@ void Application::cleanupBeforeQuit() {
|
||||||
|
|
||||||
getEntities()->shutdown(); // tell the entities system we're shutting down, so it will stop running scripts
|
getEntities()->shutdown(); // tell the entities system we're shutting down, so it will stop running scripts
|
||||||
|
|
||||||
|
// Clear any queued processing (I/O, FBX/OBJ/Texture parsing)
|
||||||
|
QThreadPool::globalInstance()->clear();
|
||||||
|
|
||||||
DependencyManager::get<ScriptEngines>()->saveScripts();
|
DependencyManager::get<ScriptEngines>()->saveScripts();
|
||||||
DependencyManager::get<ScriptEngines>()->shutdownScripting(); // stop all currently running global scripts
|
DependencyManager::get<ScriptEngines>()->shutdownScripting(); // stop all currently running global scripts
|
||||||
DependencyManager::destroy<ScriptEngines>();
|
DependencyManager::destroy<ScriptEngines>();
|
||||||
|
|
||||||
|
// Cleanup all overlays after the scripts, as scripts might add more
|
||||||
|
_overlays.cleanupAllOverlays();
|
||||||
|
|
||||||
// first stop all timers directly or by invokeMethod
|
// first stop all timers directly or by invokeMethod
|
||||||
// depending on what thread they run in
|
// depending on what thread they run in
|
||||||
locationUpdateTimer.stop();
|
locationUpdateTimer.stop();
|
||||||
|
@ -1440,23 +1429,15 @@ void Application::initializeUi() {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void Application::paintGL() {
|
void Application::paintGL() {
|
||||||
updateHeartbeat();
|
// Some plugins process message events, allowing paintGL to be called reentrantly.
|
||||||
// Some plugins process message events, potentially leading to
|
if (_inPaint || _aboutToQuit) {
|
||||||
// re-entering a paint event. don't allow further processing if this
|
|
||||||
// happens
|
|
||||||
if (_inPaint) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
_inPaint = true;
|
|
||||||
Finally clearFlagLambda([this] { _inPaint = false; });
|
|
||||||
|
|
||||||
// paintGL uses a queued connection, so we can get messages from the queue even after we've quit
|
_inPaint = true;
|
||||||
// and the plugins have shutdown
|
Finally clearFlag([this] { _inPaint = false; });
|
||||||
if (_aboutToQuit) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
_frameCount++;
|
_frameCount++;
|
||||||
_frameCounter.increment();
|
_frameCounter.increment();
|
||||||
|
|
||||||
|
@ -1814,16 +1795,33 @@ bool Application::event(QEvent* event) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((int)event->type() == (int)Lambda) {
|
static bool justPresented = false;
|
||||||
static_cast<LambdaEvent*>(event)->call();
|
if ((int)event->type() == (int)Present) {
|
||||||
|
if (justPresented) {
|
||||||
|
justPresented = false;
|
||||||
|
|
||||||
|
// If presentation is hogging the main thread, repost as low priority to avoid hanging the GUI.
|
||||||
|
// This has the effect of allowing presentation to exceed the paint budget by X times and
|
||||||
|
// only dropping every (1/X) frames, instead of every ceil(X) frames.
|
||||||
|
// (e.g. at a 60FPS target, painting for 17us would fall to 58.82FPS instead of 30FPS).
|
||||||
|
removePostedEvents(this, Present);
|
||||||
|
postEvent(this, new QEvent(static_cast<QEvent::Type>(Present)), Qt::LowEventPriority);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((int)event->type() == (int)Paint) {
|
idle();
|
||||||
|
return true;
|
||||||
|
} else if ((int)event->type() == (int)Paint) {
|
||||||
|
justPresented = true;
|
||||||
paintGL();
|
paintGL();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ((int)event->type() == (int)Lambda) {
|
||||||
|
static_cast<LambdaEvent*>(event)->call();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
if (!_keyboardFocusedItem.isInvalidID()) {
|
if (!_keyboardFocusedItem.isInvalidID()) {
|
||||||
switch (event->type()) {
|
switch (event->type()) {
|
||||||
case QEvent::KeyPress:
|
case QEvent::KeyPress:
|
||||||
|
@ -2598,72 +2596,63 @@ bool Application::acceptSnapshot(const QString& urlString) {
|
||||||
|
|
||||||
static uint32_t _renderedFrameIndex { INVALID_FRAME };
|
static uint32_t _renderedFrameIndex { INVALID_FRAME };
|
||||||
|
|
||||||
void Application::idle(uint64_t now) {
|
void Application::idle() {
|
||||||
// NOTICE NOTICE NOTICE NOTICE
|
// idle is called on a queued connection, so make sure we should be here.
|
||||||
// Do not insert new code between here and the PROFILE_RANGE declaration
|
if (_inPaint || _aboutToQuit) {
|
||||||
// unless you know exactly what you're doing. This idle function can be
|
|
||||||
// called thousands per second or more, so any additional work that's done
|
|
||||||
// here will have a serious impact on CPU usage. Only add code after all
|
|
||||||
// the thottling logic, i.e. after PROFILE_RANGE
|
|
||||||
// NOTICE NOTICE NOTICE NOTICE
|
|
||||||
updateHeartbeat();
|
|
||||||
|
|
||||||
if (_aboutToQuit || _inPaint) {
|
|
||||||
return; // bail early, nothing to do here.
|
|
||||||
}
|
|
||||||
|
|
||||||
auto displayPlugin = getActiveDisplayPlugin();
|
|
||||||
// depending on whether we're throttling or not.
|
|
||||||
// Once rendering is off on another thread we should be able to have Application::idle run at start(0) in
|
|
||||||
// perpetuity and not expect events to get backed up.
|
|
||||||
bool isThrottled = displayPlugin->isThrottled();
|
|
||||||
// Only run simulation code if more than the targetFramePeriod have passed since last time we ran
|
|
||||||
// This attempts to lock the simulation at 60 updates per second, regardless of framerate
|
|
||||||
float timeSinceLastUpdateUs = (float)_lastTimeUpdated.nsecsElapsed() / NSECS_PER_USEC;
|
|
||||||
float secondsSinceLastUpdate = timeSinceLastUpdateUs / USECS_PER_SECOND;
|
|
||||||
|
|
||||||
if (isThrottled && (timeSinceLastUpdateUs / USECS_PER_MSEC) < THROTTLED_SIM_FRAME_PERIOD_MS) {
|
|
||||||
// Throttling both rendering and idle
|
|
||||||
return; // bail early, we're throttled and not enough time has elapsed
|
|
||||||
}
|
|
||||||
|
|
||||||
auto presentCount = displayPlugin->presentCount();
|
|
||||||
if (presentCount < _renderedFrameIndex) {
|
|
||||||
_renderedFrameIndex = INVALID_FRAME;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Don't saturate the main thread with rendering and simulation,
|
|
||||||
// unless display plugin has increased by at least one frame
|
|
||||||
if (_renderedFrameIndex == INVALID_FRAME || presentCount > _renderedFrameIndex) {
|
|
||||||
// Record what present frame we're on
|
|
||||||
_renderedFrameIndex = presentCount;
|
|
||||||
|
|
||||||
// request a paint, get to it as soon as possible: high priority
|
|
||||||
postEvent(this, new QEvent(static_cast<QEvent::Type>(Paint)), Qt::HighEventPriority);
|
|
||||||
} else {
|
|
||||||
// there's no use in simulating or rendering faster then the present rate.
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// NOTICE NOTICE NOTICE NOTICE
|
auto displayPlugin = getActiveDisplayPlugin();
|
||||||
// do NOT add new code above this line unless you want it to be executed potentially
|
|
||||||
// thousands of times per second
|
|
||||||
// NOTICE NOTICE NOTICE NOTICE
|
|
||||||
|
|
||||||
PROFILE_RANGE(__FUNCTION__);
|
#ifdef DEBUG_PAINT_DELAY
|
||||||
|
static uint64_t paintDelaySamples{ 0 };
|
||||||
|
static uint64_t paintDelayUsecs{ 0 };
|
||||||
|
|
||||||
|
paintDelayUsecs += displayPlugin->getPaintDelayUsecs();
|
||||||
|
|
||||||
|
static const int PAINT_DELAY_THROTTLE = 1000;
|
||||||
|
if (++paintDelaySamples % PAINT_DELAY_THROTTLE == 0) {
|
||||||
|
qCDebug(interfaceapp).nospace() <<
|
||||||
|
"Paint delay (" << paintDelaySamples << " samples): " <<
|
||||||
|
(float)paintDelaySamples / paintDelayUsecs << "us";
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
float msecondsSinceLastUpdate = (float)_lastTimeUpdated.nsecsElapsed() / NSECS_PER_USEC / USECS_PER_MSEC;
|
||||||
|
|
||||||
|
// Throttle if requested
|
||||||
|
if (displayPlugin->isThrottled() && (msecondsSinceLastUpdate < THROTTLED_SIM_FRAME_PERIOD_MS)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sync up the _renderedFrameIndex
|
||||||
|
_renderedFrameIndex = displayPlugin->presentCount();
|
||||||
|
|
||||||
|
// Request a paint ASAP
|
||||||
|
postEvent(this, new QEvent(static_cast<QEvent::Type>(Paint)), Qt::HighEventPriority + 1);
|
||||||
|
|
||||||
|
// Update the deadlock watchdog
|
||||||
|
updateHeartbeat();
|
||||||
|
|
||||||
|
auto offscreenUi = DependencyManager::get<OffscreenUi>();
|
||||||
|
|
||||||
// These tasks need to be done on our first idle, because we don't want the showing of
|
// These tasks need to be done on our first idle, because we don't want the showing of
|
||||||
// overlay subwindows to do a showDesktop() until after the first time through
|
// overlay subwindows to do a showDesktop() until after the first time through
|
||||||
static bool firstIdle = true;
|
static bool firstIdle = true;
|
||||||
if (firstIdle) {
|
if (firstIdle) {
|
||||||
firstIdle = false;
|
firstIdle = false;
|
||||||
auto offscreenUi = DependencyManager::get<OffscreenUi>();
|
|
||||||
connect(offscreenUi.data(), &OffscreenUi::showDesktop, this, &Application::showDesktop);
|
connect(offscreenUi.data(), &OffscreenUi::showDesktop, this, &Application::showDesktop);
|
||||||
_overlayConductor.setEnabled(Menu::getInstance()->isOptionChecked(MenuOption::Overlays));
|
_overlayConductor.setEnabled(Menu::getInstance()->isOptionChecked(MenuOption::Overlays));
|
||||||
|
} else {
|
||||||
|
// FIXME: AvatarInputs are positioned incorrectly if instantiated before the first paint
|
||||||
|
AvatarInputs::getInstance()->update();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
PROFILE_RANGE(__FUNCTION__);
|
||||||
|
|
||||||
|
float secondsSinceLastUpdate = msecondsSinceLastUpdate / MSECS_PER_SECOND;
|
||||||
|
|
||||||
// If the offscreen Ui has something active that is NOT the root, then assume it has keyboard focus.
|
// If the offscreen Ui has something active that is NOT the root, then assume it has keyboard focus.
|
||||||
auto offscreenUi = DependencyManager::get<OffscreenUi>();
|
|
||||||
if (_keyboardDeviceHasFocus && offscreenUi && offscreenUi->getWindow()->activeFocusItem() != offscreenUi->getRootItem()) {
|
if (_keyboardDeviceHasFocus && offscreenUi && offscreenUi->getWindow()->activeFocusItem() != offscreenUi->getRootItem()) {
|
||||||
_keyboardMouseDevice->pluginFocusOutEvent();
|
_keyboardMouseDevice->pluginFocusOutEvent();
|
||||||
_keyboardDeviceHasFocus = false;
|
_keyboardDeviceHasFocus = false;
|
||||||
|
@ -2679,7 +2668,6 @@ void Application::idle(uint64_t now) {
|
||||||
checkChangeCursor();
|
checkChangeCursor();
|
||||||
|
|
||||||
Stats::getInstance()->updateStats();
|
Stats::getInstance()->updateStats();
|
||||||
AvatarInputs::getInstance()->update();
|
|
||||||
|
|
||||||
_simCounter.increment();
|
_simCounter.increment();
|
||||||
|
|
||||||
|
|
|
@ -33,6 +33,7 @@
|
||||||
#include <PhysicalEntitySimulation.h>
|
#include <PhysicalEntitySimulation.h>
|
||||||
#include <PhysicsEngine.h>
|
#include <PhysicsEngine.h>
|
||||||
#include <plugins/Forward.h>
|
#include <plugins/Forward.h>
|
||||||
|
#include <plugins/DisplayPlugin.h>
|
||||||
#include <ScriptEngine.h>
|
#include <ScriptEngine.h>
|
||||||
#include <ShapeManager.h>
|
#include <ShapeManager.h>
|
||||||
#include <SimpleMovingAverage.h>
|
#include <SimpleMovingAverage.h>
|
||||||
|
@ -93,6 +94,12 @@ class Application : public QApplication, public AbstractViewStateInterface, publ
|
||||||
friend class PluginContainerProxy;
|
friend class PluginContainerProxy;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
enum Event {
|
||||||
|
Present = DisplayPlugin::Present,
|
||||||
|
Paint = Present + 1,
|
||||||
|
Lambda = Paint + 1
|
||||||
|
};
|
||||||
|
|
||||||
// FIXME? Empty methods, do we still need them?
|
// FIXME? Empty methods, do we still need them?
|
||||||
static void initPlugins();
|
static void initPlugins();
|
||||||
static void shutdownPlugins();
|
static void shutdownPlugins();
|
||||||
|
@ -281,7 +288,6 @@ public slots:
|
||||||
private slots:
|
private slots:
|
||||||
void showDesktop();
|
void showDesktop();
|
||||||
void clearDomainOctreeDetails();
|
void clearDomainOctreeDetails();
|
||||||
void idle(uint64_t now);
|
|
||||||
void aboutToQuit();
|
void aboutToQuit();
|
||||||
|
|
||||||
void resettingDomain();
|
void resettingDomain();
|
||||||
|
@ -320,6 +326,7 @@ private:
|
||||||
|
|
||||||
void cleanupBeforeQuit();
|
void cleanupBeforeQuit();
|
||||||
|
|
||||||
|
void idle();
|
||||||
void update(float deltaTime);
|
void update(float deltaTime);
|
||||||
|
|
||||||
// Various helper functions called during update()
|
// Various helper functions called during update()
|
||||||
|
@ -497,7 +504,6 @@ private:
|
||||||
int _avatarAttachmentRequest = 0;
|
int _avatarAttachmentRequest = 0;
|
||||||
|
|
||||||
bool _settingsLoaded { false };
|
bool _settingsLoaded { false };
|
||||||
QTimer* _idleTimer { nullptr };
|
|
||||||
|
|
||||||
bool _fakedMouseEvent { false };
|
bool _fakedMouseEvent { false };
|
||||||
|
|
||||||
|
|
|
@ -73,7 +73,6 @@ AvatarManager::AvatarManager(QObject* parent) :
|
||||||
packetReceiver.registerListener(PacketType::BulkAvatarData, this, "processAvatarDataPacket");
|
packetReceiver.registerListener(PacketType::BulkAvatarData, this, "processAvatarDataPacket");
|
||||||
packetReceiver.registerListener(PacketType::KillAvatar, this, "processKillAvatar");
|
packetReceiver.registerListener(PacketType::KillAvatar, this, "processKillAvatar");
|
||||||
packetReceiver.registerListener(PacketType::AvatarIdentity, this, "processAvatarIdentityPacket");
|
packetReceiver.registerListener(PacketType::AvatarIdentity, this, "processAvatarIdentityPacket");
|
||||||
packetReceiver.registerListener(PacketType::AvatarBillboard, this, "processAvatarBillboardPacket");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
AvatarManager::~AvatarManager() {
|
AvatarManager::~AvatarManager() {
|
||||||
|
|
|
@ -36,15 +36,8 @@
|
||||||
#include <QtQuick/QQuickWindow>
|
#include <QtQuick/QQuickWindow>
|
||||||
|
|
||||||
|
|
||||||
Overlays::Overlays() : _nextOverlayID(1) {
|
Overlays::Overlays() :
|
||||||
connect(qApp, &Application::beforeAboutToQuit, [=] {
|
_nextOverlayID(1) {}
|
||||||
cleanupAllOverlays();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
Overlays::~Overlays() {
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void Overlays::cleanupAllOverlays() {
|
void Overlays::cleanupAllOverlays() {
|
||||||
{
|
{
|
||||||
|
|
|
@ -62,7 +62,6 @@ class Overlays : public QObject {
|
||||||
|
|
||||||
public:
|
public:
|
||||||
Overlays();
|
Overlays();
|
||||||
~Overlays();
|
|
||||||
|
|
||||||
void init();
|
void init();
|
||||||
void update(float deltatime);
|
void update(float deltatime);
|
||||||
|
@ -73,6 +72,8 @@ public:
|
||||||
Overlay::Pointer getOverlay(unsigned int id) const;
|
Overlay::Pointer getOverlay(unsigned int id) const;
|
||||||
OverlayPanel::Pointer getPanel(unsigned int id) const { return _panels[id]; }
|
OverlayPanel::Pointer getPanel(unsigned int id) const { return _panels[id]; }
|
||||||
|
|
||||||
|
void cleanupAllOverlays();
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
/// adds an overlay with the specific properties
|
/// adds an overlay with the specific properties
|
||||||
unsigned int addOverlay(const QString& type, const QVariant& properties);
|
unsigned int addOverlay(const QString& type, const QVariant& properties);
|
||||||
|
@ -145,7 +146,6 @@ signals:
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void cleanupOverlaysToDelete();
|
void cleanupOverlaysToDelete();
|
||||||
void cleanupAllOverlays();
|
|
||||||
|
|
||||||
QMap<unsigned int, Overlay::Pointer> _overlaysHUD;
|
QMap<unsigned int, Overlay::Pointer> _overlaysHUD;
|
||||||
QMap<unsigned int, Overlay::Pointer> _overlaysWorld;
|
QMap<unsigned int, Overlay::Pointer> _overlaysWorld;
|
||||||
|
|
|
@ -58,7 +58,6 @@ AvatarData::AvatarData() :
|
||||||
_headData(NULL),
|
_headData(NULL),
|
||||||
_displayNameTargetAlpha(1.0f),
|
_displayNameTargetAlpha(1.0f),
|
||||||
_displayNameAlpha(1.0f),
|
_displayNameAlpha(1.0f),
|
||||||
_billboard(),
|
|
||||||
_errorLogExpiry(0),
|
_errorLogExpiry(0),
|
||||||
_owningAvatarMixer(),
|
_owningAvatarMixer(),
|
||||||
_targetVelocity(0.0f),
|
_targetVelocity(0.0f),
|
||||||
|
@ -1006,13 +1005,6 @@ QByteArray AvatarData::identityByteArray() {
|
||||||
return identityData;
|
return identityData;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool AvatarData::hasBillboardChangedAfterParsing(const QByteArray& data) {
|
|
||||||
if (data == _billboard) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
_billboard = data;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void AvatarData::setSkeletonModelURL(const QUrl& skeletonModelURL) {
|
void AvatarData::setSkeletonModelURL(const QUrl& skeletonModelURL) {
|
||||||
const QUrl& expanded = skeletonModelURL.isEmpty() ? AvatarData::defaultFullAvatarModelUrl() : skeletonModelURL;
|
const QUrl& expanded = skeletonModelURL.isEmpty() ? AvatarData::defaultFullAvatarModelUrl() : skeletonModelURL;
|
||||||
|
@ -1110,33 +1102,6 @@ void AvatarData::detachAll(const QString& modelURL, const QString& jointName) {
|
||||||
setAttachmentData(attachmentData);
|
setAttachmentData(attachmentData);
|
||||||
}
|
}
|
||||||
|
|
||||||
void AvatarData::setBillboard(const QByteArray& billboard) {
|
|
||||||
_billboard = billboard;
|
|
||||||
|
|
||||||
qCDebug(avatars) << "Changing billboard for avatar.";
|
|
||||||
}
|
|
||||||
|
|
||||||
void AvatarData::setBillboardFromURL(const QString &billboardURL) {
|
|
||||||
_billboardURL = billboardURL;
|
|
||||||
|
|
||||||
|
|
||||||
qCDebug(avatars) << "Changing billboard for avatar to PNG at" << qPrintable(billboardURL);
|
|
||||||
|
|
||||||
QNetworkRequest billboardRequest;
|
|
||||||
billboardRequest.setHeader(QNetworkRequest::UserAgentHeader, HIGH_FIDELITY_USER_AGENT);
|
|
||||||
billboardRequest.setUrl(QUrl(billboardURL));
|
|
||||||
|
|
||||||
QNetworkAccessManager& networkAccessManager = NetworkAccessManager::getInstance();
|
|
||||||
QNetworkReply* networkReply = networkAccessManager.get(billboardRequest);
|
|
||||||
connect(networkReply, SIGNAL(finished()), this, SLOT(setBillboardFromNetworkReply()));
|
|
||||||
}
|
|
||||||
|
|
||||||
void AvatarData::setBillboardFromNetworkReply() {
|
|
||||||
QNetworkReply* networkReply = static_cast<QNetworkReply*>(sender());
|
|
||||||
setBillboard(networkReply->readAll());
|
|
||||||
networkReply->deleteLater();
|
|
||||||
}
|
|
||||||
|
|
||||||
void AvatarData::setJointMappingsFromNetworkReply() {
|
void AvatarData::setJointMappingsFromNetworkReply() {
|
||||||
QNetworkReply* networkReply = static_cast<QNetworkReply*>(sender());
|
QNetworkReply* networkReply = static_cast<QNetworkReply*>(sender());
|
||||||
|
|
||||||
|
@ -1213,21 +1178,6 @@ void AvatarData::sendIdentityPacket() {
|
||||||
_avatarEntityDataLocallyEdited = false;
|
_avatarEntityDataLocallyEdited = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
void AvatarData::sendBillboardPacket() {
|
|
||||||
if (!_billboard.isEmpty()) {
|
|
||||||
auto nodeList = DependencyManager::get<NodeList>();
|
|
||||||
|
|
||||||
// This makes sure the billboard won't be too large to send.
|
|
||||||
// Once more protocol changes are done and we can send blocks of data we can support sending > MTU sized billboards.
|
|
||||||
if (_billboard.size() <= NLPacket::maxPayloadSize(PacketType::AvatarBillboard)) {
|
|
||||||
auto billboardPacket = NLPacket::create(PacketType::AvatarBillboard, _billboard.size());
|
|
||||||
billboardPacket->write(_billboard);
|
|
||||||
|
|
||||||
nodeList->broadcastToNodes(std::move(billboardPacket), NodeSet() << NodeType::AvatarMixer);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void AvatarData::updateJointMappings() {
|
void AvatarData::updateJointMappings() {
|
||||||
_jointIndices.clear();
|
_jointIndices.clear();
|
||||||
_jointNames.clear();
|
_jointNames.clear();
|
||||||
|
|
|
@ -109,7 +109,6 @@ static const float MIN_AVATAR_SCALE = .005f;
|
||||||
const float MAX_AUDIO_LOUDNESS = 1000.0f; // close enough for mouth animation
|
const float MAX_AUDIO_LOUDNESS = 1000.0f; // close enough for mouth animation
|
||||||
|
|
||||||
const int AVATAR_IDENTITY_PACKET_SEND_INTERVAL_MSECS = 1000;
|
const int AVATAR_IDENTITY_PACKET_SEND_INTERVAL_MSECS = 1000;
|
||||||
const int AVATAR_BILLBOARD_PACKET_SEND_INTERVAL_MSECS = 5000;
|
|
||||||
|
|
||||||
// See also static AvatarData::defaultFullAvatarModelUrl().
|
// See also static AvatarData::defaultFullAvatarModelUrl().
|
||||||
const QString DEFAULT_FULL_AVATAR_MODEL_NAME = QString("Default");
|
const QString DEFAULT_FULL_AVATAR_MODEL_NAME = QString("Default");
|
||||||
|
@ -166,7 +165,6 @@ class AvatarData : public QObject, public SpatiallyNestable {
|
||||||
Q_PROPERTY(QString displayName READ getDisplayName WRITE setDisplayName)
|
Q_PROPERTY(QString displayName READ getDisplayName WRITE setDisplayName)
|
||||||
Q_PROPERTY(QString skeletonModelURL READ getSkeletonModelURLFromScript WRITE setSkeletonModelURLFromScript)
|
Q_PROPERTY(QString skeletonModelURL READ getSkeletonModelURLFromScript WRITE setSkeletonModelURLFromScript)
|
||||||
Q_PROPERTY(QVector<AttachmentData> attachmentData READ getAttachmentData WRITE setAttachmentData)
|
Q_PROPERTY(QVector<AttachmentData> attachmentData READ getAttachmentData WRITE setAttachmentData)
|
||||||
Q_PROPERTY(QString billboardURL READ getBillboardURL WRITE setBillboardFromURL)
|
|
||||||
|
|
||||||
Q_PROPERTY(QStringList jointNames READ getJointNames)
|
Q_PROPERTY(QStringList jointNames READ getJointNames)
|
||||||
|
|
||||||
|
@ -294,8 +292,6 @@ public:
|
||||||
bool hasIdentityChangedAfterParsing(const QByteArray& data);
|
bool hasIdentityChangedAfterParsing(const QByteArray& data);
|
||||||
QByteArray identityByteArray();
|
QByteArray identityByteArray();
|
||||||
|
|
||||||
bool hasBillboardChangedAfterParsing(const QByteArray& data);
|
|
||||||
|
|
||||||
const QUrl& getSkeletonModelURL() const { return _skeletonModelURL; }
|
const QUrl& getSkeletonModelURL() const { return _skeletonModelURL; }
|
||||||
const QString& getDisplayName() const { return _displayName; }
|
const QString& getDisplayName() const { return _displayName; }
|
||||||
virtual void setSkeletonModelURL(const QUrl& skeletonModelURL);
|
virtual void setSkeletonModelURL(const QUrl& skeletonModelURL);
|
||||||
|
@ -313,12 +309,6 @@ public:
|
||||||
Q_INVOKABLE void detachOne(const QString& modelURL, const QString& jointName = QString());
|
Q_INVOKABLE void detachOne(const QString& modelURL, const QString& jointName = QString());
|
||||||
Q_INVOKABLE void detachAll(const QString& modelURL, const QString& jointName = QString());
|
Q_INVOKABLE void detachAll(const QString& modelURL, const QString& jointName = QString());
|
||||||
|
|
||||||
virtual void setBillboard(const QByteArray& billboard);
|
|
||||||
const QByteArray& getBillboard() const { return _billboard; }
|
|
||||||
|
|
||||||
void setBillboardFromURL(const QString& billboardURL);
|
|
||||||
const QString& getBillboardURL() { return _billboardURL; }
|
|
||||||
|
|
||||||
QString getSkeletonModelURLFromScript() const { return _skeletonModelURL.toString(); }
|
QString getSkeletonModelURLFromScript() const { return _skeletonModelURL.toString(); }
|
||||||
void setSkeletonModelURLFromScript(const QString& skeletonModelString) { setSkeletonModelURL(QUrl(skeletonModelString)); }
|
void setSkeletonModelURLFromScript(const QString& skeletonModelString) { setSkeletonModelURL(QUrl(skeletonModelString)); }
|
||||||
|
|
||||||
|
@ -350,9 +340,7 @@ public:
|
||||||
public slots:
|
public slots:
|
||||||
void sendAvatarDataPacket();
|
void sendAvatarDataPacket();
|
||||||
void sendIdentityPacket();
|
void sendIdentityPacket();
|
||||||
void sendBillboardPacket();
|
|
||||||
|
|
||||||
void setBillboardFromNetworkReply();
|
|
||||||
void setJointMappingsFromNetworkReply();
|
void setJointMappingsFromNetworkReply();
|
||||||
void setSessionUUID(const QUuid& sessionUUID) { setID(sessionUUID); }
|
void setSessionUUID(const QUuid& sessionUUID) { setID(sessionUUID); }
|
||||||
|
|
||||||
|
@ -391,9 +379,6 @@ protected:
|
||||||
float _displayNameTargetAlpha;
|
float _displayNameTargetAlpha;
|
||||||
float _displayNameAlpha;
|
float _displayNameAlpha;
|
||||||
|
|
||||||
QByteArray _billboard;
|
|
||||||
QString _billboardURL;
|
|
||||||
|
|
||||||
QHash<QString, int> _jointIndices; ///< 1-based, since zero is returned for missing keys
|
QHash<QString, int> _jointIndices; ///< 1-based, since zero is returned for missing keys
|
||||||
QStringList _jointNames; ///< in order of depth-first traversal
|
QStringList _jointNames; ///< in order of depth-first traversal
|
||||||
|
|
||||||
|
|
|
@ -140,17 +140,6 @@ void AvatarHashMap::processAvatarIdentityPacket(QSharedPointer<ReceivedMessage>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void AvatarHashMap::processAvatarBillboardPacket(QSharedPointer<ReceivedMessage> message, SharedNodePointer sendingNode) {
|
|
||||||
QUuid sessionUUID = QUuid::fromRfc4122(message->readWithoutCopy(NUM_BYTES_RFC4122_UUID));
|
|
||||||
|
|
||||||
auto avatar = newOrExistingAvatar(sessionUUID, sendingNode);
|
|
||||||
|
|
||||||
QByteArray billboard = message->read(message->getBytesLeftToRead());
|
|
||||||
if (avatar->getBillboard() != billboard) {
|
|
||||||
avatar->setBillboard(billboard);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void AvatarHashMap::processKillAvatar(QSharedPointer<ReceivedMessage> message, SharedNodePointer sendingNode) {
|
void AvatarHashMap::processKillAvatar(QSharedPointer<ReceivedMessage> message, SharedNodePointer sendingNode) {
|
||||||
// read the node id
|
// read the node id
|
||||||
QUuid sessionUUID = QUuid::fromRfc4122(message->readWithoutCopy(NUM_BYTES_RFC4122_UUID));
|
QUuid sessionUUID = QUuid::fromRfc4122(message->readWithoutCopy(NUM_BYTES_RFC4122_UUID));
|
||||||
|
|
|
@ -53,7 +53,6 @@ private slots:
|
||||||
|
|
||||||
void processAvatarDataPacket(QSharedPointer<ReceivedMessage> message, SharedNodePointer sendingNode);
|
void processAvatarDataPacket(QSharedPointer<ReceivedMessage> message, SharedNodePointer sendingNode);
|
||||||
void processAvatarIdentityPacket(QSharedPointer<ReceivedMessage> message, SharedNodePointer sendingNode);
|
void processAvatarIdentityPacket(QSharedPointer<ReceivedMessage> message, SharedNodePointer sendingNode);
|
||||||
void processAvatarBillboardPacket(QSharedPointer<ReceivedMessage> message, SharedNodePointer sendingNode);
|
|
||||||
void processKillAvatar(QSharedPointer<ReceivedMessage> message, SharedNodePointer sendingNode);
|
void processKillAvatar(QSharedPointer<ReceivedMessage> message, SharedNodePointer sendingNode);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
|
|
|
@ -13,6 +13,7 @@
|
||||||
|
|
||||||
#include <QEventLoop>
|
#include <QEventLoop>
|
||||||
#include <QScriptSyntaxCheckResult>
|
#include <QScriptSyntaxCheckResult>
|
||||||
|
#include <QThreadPool>
|
||||||
|
|
||||||
#include <ColorUtils.h>
|
#include <ColorUtils.h>
|
||||||
#include <AbstractScriptingServicesInterface.h>
|
#include <AbstractScriptingServicesInterface.h>
|
||||||
|
@ -77,9 +78,30 @@ EntityTreeRenderer::~EntityTreeRenderer() {
|
||||||
|
|
||||||
int EntityTreeRenderer::_entitiesScriptEngineCount = 0;
|
int EntityTreeRenderer::_entitiesScriptEngineCount = 0;
|
||||||
|
|
||||||
void EntityTreeRenderer::setupEntitiesScriptEngine() {
|
void entitiesScriptEngineDeleter(ScriptEngine* engine) {
|
||||||
QSharedPointer<ScriptEngine> oldEngine = _entitiesScriptEngine; // save the old engine through this function, so the EntityScriptingInterface doesn't have problems with it.
|
class WaitRunnable : public QRunnable {
|
||||||
_entitiesScriptEngine = QSharedPointer<ScriptEngine>(new ScriptEngine(NO_SCRIPT, QString("Entities %1").arg(++_entitiesScriptEngineCount)), &QObject::deleteLater);
|
public:
|
||||||
|
WaitRunnable(ScriptEngine* engine) : _engine(engine) {}
|
||||||
|
virtual void run() override {
|
||||||
|
_engine->waitTillDoneRunning();
|
||||||
|
_engine->deleteLater();
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
ScriptEngine* _engine;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Wait for the scripting thread from the thread pool to avoid hanging the main thread
|
||||||
|
QThreadPool::globalInstance()->start(new WaitRunnable(engine));
|
||||||
|
}
|
||||||
|
|
||||||
|
void EntityTreeRenderer::resetEntitiesScriptEngine() {
|
||||||
|
// Keep a ref to oldEngine until newEngine is ready so EntityScriptingInterface has something to use
|
||||||
|
auto oldEngine = _entitiesScriptEngine;
|
||||||
|
|
||||||
|
auto newEngine = new ScriptEngine(NO_SCRIPT, QString("Entities %1").arg(++_entitiesScriptEngineCount));
|
||||||
|
_entitiesScriptEngine = QSharedPointer<ScriptEngine>(newEngine, entitiesScriptEngineDeleter);
|
||||||
|
|
||||||
_scriptingServices->registerScriptEngineWithApplicationServices(_entitiesScriptEngine.data());
|
_scriptingServices->registerScriptEngineWithApplicationServices(_entitiesScriptEngine.data());
|
||||||
_entitiesScriptEngine->runInThread();
|
_entitiesScriptEngine->runInThread();
|
||||||
DependencyManager::get<EntityScriptingInterface>()->setEntitiesScriptEngine(_entitiesScriptEngine.data());
|
DependencyManager::get<EntityScriptingInterface>()->setEntitiesScriptEngine(_entitiesScriptEngine.data());
|
||||||
|
@ -87,16 +109,16 @@ void EntityTreeRenderer::setupEntitiesScriptEngine() {
|
||||||
|
|
||||||
void EntityTreeRenderer::clear() {
|
void EntityTreeRenderer::clear() {
|
||||||
leaveAllEntities();
|
leaveAllEntities();
|
||||||
|
|
||||||
if (_entitiesScriptEngine) {
|
if (_entitiesScriptEngine) {
|
||||||
|
// Unload and stop the engine here (instead of in its deleter) to
|
||||||
|
// avoid marshalling unload signals back to this thread
|
||||||
_entitiesScriptEngine->unloadAllEntityScripts();
|
_entitiesScriptEngine->unloadAllEntityScripts();
|
||||||
_entitiesScriptEngine->stop();
|
_entitiesScriptEngine->stop();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_wantScripts && !_shuttingDown) {
|
if (_wantScripts && !_shuttingDown) {
|
||||||
// NOTE: you can't actually need to delete it here because when we call setupEntitiesScriptEngine it will
|
resetEntitiesScriptEngine();
|
||||||
// assign a new instance to our shared pointer, which will deref the old instance and ultimately call
|
|
||||||
// the custom deleter which calls deleteLater
|
|
||||||
setupEntitiesScriptEngine();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
auto scene = _viewState->getMain3DScene();
|
auto scene = _viewState->getMain3DScene();
|
||||||
|
@ -125,7 +147,7 @@ void EntityTreeRenderer::init() {
|
||||||
entityTree->setFBXService(this);
|
entityTree->setFBXService(this);
|
||||||
|
|
||||||
if (_wantScripts) {
|
if (_wantScripts) {
|
||||||
setupEntitiesScriptEngine();
|
resetEntitiesScriptEngine();
|
||||||
}
|
}
|
||||||
|
|
||||||
forceRecheckEntities(); // setup our state to force checking our inside/outsideness of entities
|
forceRecheckEntities(); // setup our state to force checking our inside/outsideness of entities
|
||||||
|
|
|
@ -126,7 +126,7 @@ protected:
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void setupEntitiesScriptEngine();
|
void resetEntitiesScriptEngine();
|
||||||
|
|
||||||
void addEntityToScene(EntityItemPointer entity);
|
void addEntityToScene(EntityItemPointer entity);
|
||||||
bool findBestZoneAndMaybeContainingEntities(const glm::vec3& avatarPosition, QVector<EntityItemID>* entitiesContainingAvatar);
|
bool findBestZoneAndMaybeContainingEntities(const glm::vec3& avatarPosition, QVector<EntityItemID>* entitiesContainingAvatar);
|
||||||
|
|
|
@ -45,23 +45,17 @@ UpdateEntityOperator::UpdateEntityOperator(EntityTreePointer tree,
|
||||||
_newEntityCube = newQueryAACube;
|
_newEntityCube = newQueryAACube;
|
||||||
_newEntityBox = _newEntityCube.clamp((float)-HALF_TREE_SCALE, (float)HALF_TREE_SCALE); // clamp to domain bounds
|
_newEntityBox = _newEntityCube.clamp((float)-HALF_TREE_SCALE, (float)HALF_TREE_SCALE); // clamp to domain bounds
|
||||||
|
|
||||||
// If our new properties don't have bounds details (no change to position, etc) or if this containing element would
|
// set oldElementBestFit true if the entity was in the correct element before this operator was run.
|
||||||
// be the best fit for our new properties, then just do the new portion of the store pass, since the change path will
|
|
||||||
// be the same for both parts of the update
|
|
||||||
bool oldElementBestFit = _containingElement->bestFitBounds(_oldEntityBox);
|
bool oldElementBestFit = _containingElement->bestFitBounds(_oldEntityBox);
|
||||||
|
|
||||||
// For some reason we've seen a case where the original containing element isn't a best fit for the old properties
|
// For some reason we've seen a case where the original containing element isn't a best fit for the old properties
|
||||||
// in this case we want to move it, even if the properties haven't changed.
|
// in this case we want to move it, even if the properties haven't changed.
|
||||||
if (oldElementBestFit) {
|
if (!oldElementBestFit) {
|
||||||
if (_wantDebug) {
|
|
||||||
qCDebug(entities) << " **** TYPICAL NO MOVE CASE **** oldElementBestFit:" << oldElementBestFit;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
_oldEntityBox = _existingEntity->getElement()->getAACube();
|
_oldEntityBox = _existingEntity->getElement()->getAACube();
|
||||||
_removeOld = true; // our properties are going to move us, so remember this for later processing
|
_removeOld = true; // our properties are going to move us, so remember this for later processing
|
||||||
|
|
||||||
if (_wantDebug) {
|
if (_wantDebug) {
|
||||||
qCDebug(entities) << " **** UNUSUAL CASE **** no changes, but not best fit... consider it a move.... **";
|
qCDebug(entities) << " **** UNUSUAL CASE **** not best fit.... **";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -535,13 +535,12 @@ void GLBackend::syncSampler(const Sampler& sampler, Texture::Type type, const GL
|
||||||
GLint minFilter;
|
GLint minFilter;
|
||||||
GLint magFilter;
|
GLint magFilter;
|
||||||
};
|
};
|
||||||
static const GLFilterMode filterModes[] = {
|
static const GLFilterMode filterModes[Sampler::NUM_FILTERS] = {
|
||||||
{ GL_NEAREST, GL_NEAREST }, //FILTER_MIN_MAG_POINT,
|
{ GL_NEAREST, GL_NEAREST }, //FILTER_MIN_MAG_POINT,
|
||||||
{ GL_NEAREST, GL_LINEAR }, //FILTER_MIN_POINT_MAG_LINEAR,
|
{ GL_NEAREST, GL_LINEAR }, //FILTER_MIN_POINT_MAG_LINEAR,
|
||||||
{ GL_LINEAR, GL_NEAREST }, //FILTER_MIN_LINEAR_MAG_POINT,
|
{ GL_LINEAR, GL_NEAREST }, //FILTER_MIN_LINEAR_MAG_POINT,
|
||||||
{ GL_LINEAR, GL_LINEAR }, //FILTER_MIN_MAG_LINEAR,
|
{ GL_LINEAR, GL_LINEAR }, //FILTER_MIN_MAG_LINEAR,
|
||||||
|
|
||||||
{ GL_NEAREST_MIPMAP_NEAREST, GL_NEAREST }, //FILTER_MIN_MAG_MIP_POINT,
|
|
||||||
{ GL_NEAREST_MIPMAP_NEAREST, GL_NEAREST }, //FILTER_MIN_MAG_MIP_POINT,
|
{ GL_NEAREST_MIPMAP_NEAREST, GL_NEAREST }, //FILTER_MIN_MAG_MIP_POINT,
|
||||||
{ GL_NEAREST_MIPMAP_LINEAR, GL_NEAREST }, //FILTER_MIN_MAG_POINT_MIP_LINEAR,
|
{ GL_NEAREST_MIPMAP_LINEAR, GL_NEAREST }, //FILTER_MIN_MAG_POINT_MIP_LINEAR,
|
||||||
{ GL_NEAREST_MIPMAP_NEAREST, GL_LINEAR }, //FILTER_MIN_POINT_MAG_LINEAR_MIP_POINT,
|
{ GL_NEAREST_MIPMAP_NEAREST, GL_LINEAR }, //FILTER_MIN_POINT_MAG_LINEAR_MIP_POINT,
|
||||||
|
@ -557,7 +556,7 @@ void GLBackend::syncSampler(const Sampler& sampler, Texture::Type type, const GL
|
||||||
glTexParameteri(object->_target, GL_TEXTURE_MIN_FILTER, fm.minFilter);
|
glTexParameteri(object->_target, GL_TEXTURE_MIN_FILTER, fm.minFilter);
|
||||||
glTexParameteri(object->_target, GL_TEXTURE_MAG_FILTER, fm.magFilter);
|
glTexParameteri(object->_target, GL_TEXTURE_MAG_FILTER, fm.magFilter);
|
||||||
|
|
||||||
static const GLenum comparisonFuncs[] = {
|
static const GLenum comparisonFuncs[NUM_COMPARISON_FUNCS] = {
|
||||||
GL_NEVER,
|
GL_NEVER,
|
||||||
GL_LESS,
|
GL_LESS,
|
||||||
GL_EQUAL,
|
GL_EQUAL,
|
||||||
|
@ -574,7 +573,7 @@ void GLBackend::syncSampler(const Sampler& sampler, Texture::Type type, const GL
|
||||||
glTexParameteri(object->_target, GL_TEXTURE_COMPARE_MODE, GL_NONE);
|
glTexParameteri(object->_target, GL_TEXTURE_COMPARE_MODE, GL_NONE);
|
||||||
}
|
}
|
||||||
|
|
||||||
static const GLenum wrapModes[] = {
|
static const GLenum wrapModes[Sampler::NUM_WRAP_MODES] = {
|
||||||
GL_REPEAT, // WRAP_REPEAT,
|
GL_REPEAT, // WRAP_REPEAT,
|
||||||
GL_MIRRORED_REPEAT, // WRAP_MIRROR,
|
GL_MIRRORED_REPEAT, // WRAP_MIRROR,
|
||||||
GL_CLAMP_TO_EDGE, // WRAP_CLAMP,
|
GL_CLAMP_TO_EDGE, // WRAP_CLAMP,
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
#include "DisplayPlugin.h"
|
#include "DisplayPlugin.h"
|
||||||
|
|
||||||
|
#include <NumericalConstants.h>
|
||||||
#include <ui/Menu.h>
|
#include <ui/Menu.h>
|
||||||
|
|
||||||
#include "PluginContainer.h"
|
#include "PluginContainer.h"
|
||||||
|
@ -23,4 +24,22 @@ void DisplayPlugin::deactivate() {
|
||||||
Parent::deactivate();
|
Parent::deactivate();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int64_t DisplayPlugin::getPaintDelayUsecs() const {
|
||||||
|
std::lock_guard<std::mutex> lock(_paintDelayMutex);
|
||||||
|
return _paintDelayTimer.isValid() ? _paintDelayTimer.nsecsElapsed() / NSECS_PER_USEC : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void DisplayPlugin::incrementPresentCount() {
|
||||||
|
#ifdef DEBUG_PAINT_DELAY
|
||||||
|
// Avoid overhead if we are not debugging
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> lock(_paintDelayMutex);
|
||||||
|
_paintDelayTimer.start();
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
++_presentedFrameIndex;
|
||||||
|
|
||||||
|
// Alert the app that it needs to paint a new presentation frame
|
||||||
|
qApp->postEvent(qApp, new QEvent(static_cast<QEvent::Type>(Present)), Qt::HighEventPriority);
|
||||||
|
}
|
||||||
|
|
|
@ -15,6 +15,7 @@
|
||||||
|
|
||||||
#include <QtCore/QSize>
|
#include <QtCore/QSize>
|
||||||
#include <QtCore/QPoint>
|
#include <QtCore/QPoint>
|
||||||
|
#include <QtCore/QElapsedTimer>
|
||||||
class QImage;
|
class QImage;
|
||||||
|
|
||||||
#include <GLMHelpers.h>
|
#include <GLMHelpers.h>
|
||||||
|
@ -59,6 +60,10 @@ class DisplayPlugin : public Plugin {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
using Parent = Plugin;
|
using Parent = Plugin;
|
||||||
public:
|
public:
|
||||||
|
enum Event {
|
||||||
|
Present = QEvent::User + 1
|
||||||
|
};
|
||||||
|
|
||||||
bool activate() override;
|
bool activate() override;
|
||||||
void deactivate() override;
|
void deactivate() override;
|
||||||
virtual bool isHmd() const { return false; }
|
virtual bool isHmd() const { return false; }
|
||||||
|
@ -156,6 +161,8 @@ public:
|
||||||
// Rate at which rendered frames are being skipped
|
// Rate at which rendered frames are being skipped
|
||||||
virtual float droppedFrameRate() const { return -1.0f; }
|
virtual float droppedFrameRate() const { return -1.0f; }
|
||||||
uint32_t presentCount() const { return _presentedFrameIndex; }
|
uint32_t presentCount() const { return _presentedFrameIndex; }
|
||||||
|
// Time since last call to incrementPresentCount (only valid if DEBUG_PAINT_DELAY is defined)
|
||||||
|
int64_t getPaintDelayUsecs() const;
|
||||||
|
|
||||||
virtual void cycleDebugOutput() {}
|
virtual void cycleDebugOutput() {}
|
||||||
|
|
||||||
|
@ -165,9 +172,11 @@ signals:
|
||||||
void recommendedFramebufferSizeChanged(const QSize & size);
|
void recommendedFramebufferSizeChanged(const QSize & size);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
void incrementPresentCount() { ++_presentedFrameIndex; }
|
void incrementPresentCount();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
std::atomic<uint32_t> _presentedFrameIndex;
|
std::atomic<uint32_t> _presentedFrameIndex;
|
||||||
|
mutable std::mutex _paintDelayMutex;
|
||||||
|
QElapsedTimer _paintDelayTimer;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -101,7 +101,7 @@ DeferredFragment unpackDeferredFragmentNoPosition(vec2 texcoord) {
|
||||||
|
|
||||||
// Unpack the normal from the map
|
// Unpack the normal from the map
|
||||||
frag.normal = normalize(frag.normalVal.xyz * 2.0 - vec3(1.0));
|
frag.normal = normalize(frag.normalVal.xyz * 2.0 - vec3(1.0));
|
||||||
frag.roughness = 2.0 * frag.normalVal.a;
|
frag.roughness = frag.normalVal.a;
|
||||||
|
|
||||||
// Diffuse color and unpack the mode and the metallicness
|
// Diffuse color and unpack the mode and the metallicness
|
||||||
frag.diffuse = frag.diffuseVal.xyz;
|
frag.diffuse = frag.diffuseVal.xyz;
|
||||||
|
|
|
@ -62,8 +62,6 @@
|
||||||
|
|
||||||
#include "MIDIEvent.h"
|
#include "MIDIEvent.h"
|
||||||
|
|
||||||
std::atomic<bool> ScriptEngine::_stoppingAllScripts { false };
|
|
||||||
|
|
||||||
static const QString SCRIPT_EXCEPTION_FORMAT = "[UncaughtException] %1 in %2:%3";
|
static const QString SCRIPT_EXCEPTION_FORMAT = "[UncaughtException] %1 in %2:%3";
|
||||||
|
|
||||||
Q_DECLARE_METATYPE(QScriptEngine::FunctionSignature)
|
Q_DECLARE_METATYPE(QScriptEngine::FunctionSignature)
|
||||||
|
@ -138,10 +136,9 @@ static bool hadUncaughtExceptions(QScriptEngine& engine, const QString& fileName
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
ScriptEngine::ScriptEngine(const QString& scriptContents, const QString& fileNameString, bool wantSignals) :
|
ScriptEngine::ScriptEngine(const QString& scriptContents, const QString& fileNameString) :
|
||||||
_scriptContents(scriptContents),
|
_scriptContents(scriptContents),
|
||||||
_timerFunctionMap(),
|
_timerFunctionMap(),
|
||||||
_wantSignals(wantSignals),
|
|
||||||
_fileNameString(fileNameString),
|
_fileNameString(fileNameString),
|
||||||
_arrayBufferClass(new ArrayBufferClass(this))
|
_arrayBufferClass(new ArrayBufferClass(this))
|
||||||
{
|
{
|
||||||
|
@ -155,7 +152,7 @@ ScriptEngine::ScriptEngine(const QString& scriptContents, const QString& fileNam
|
||||||
}
|
}
|
||||||
|
|
||||||
ScriptEngine::~ScriptEngine() {
|
ScriptEngine::~ScriptEngine() {
|
||||||
qCDebug(scriptengine) << "Script Engine shutting down (destructor) for script:" << getFilename();
|
qCDebug(scriptengine) << "Script Engine shutting down:" << getFilename();
|
||||||
|
|
||||||
auto scriptEngines = DependencyManager::get<ScriptEngines>();
|
auto scriptEngines = DependencyManager::get<ScriptEngines>();
|
||||||
if (scriptEngines) {
|
if (scriptEngines) {
|
||||||
|
@ -163,16 +160,15 @@ ScriptEngine::~ScriptEngine() {
|
||||||
} else {
|
} else {
|
||||||
qCWarning(scriptengine) << "Script destroyed after ScriptEngines!";
|
qCWarning(scriptengine) << "Script destroyed after ScriptEngines!";
|
||||||
}
|
}
|
||||||
|
|
||||||
waitTillDoneRunning();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void ScriptEngine::disconnectNonEssentialSignals() {
|
void ScriptEngine::disconnectNonEssentialSignals() {
|
||||||
disconnect();
|
disconnect();
|
||||||
QThread* receiver;
|
QThread* workerThread;
|
||||||
// Ensure the thread should be running, and does exist
|
// Ensure the thread should be running, and does exist
|
||||||
if (_isRunning && _isThreaded && (receiver = thread())) {
|
if (_isRunning && _isThreaded && (workerThread = thread())) {
|
||||||
connect(this, &ScriptEngine::doneRunning, receiver, &QThread::quit);
|
connect(this, &ScriptEngine::doneRunning, workerThread, &QThread::quit);
|
||||||
|
connect(workerThread, &QThread::finished, workerThread, &QObject::deleteLater);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -231,15 +227,14 @@ void ScriptEngine::runDebuggable() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
stopAllTimers(); // make sure all our timers are stopped if the script is ending
|
stopAllTimers(); // make sure all our timers are stopped if the script is ending
|
||||||
if (_wantSignals) {
|
|
||||||
emit scriptEnding();
|
emit scriptEnding();
|
||||||
emit finished(_fileNameString, this);
|
emit finished(_fileNameString, this);
|
||||||
}
|
|
||||||
_isRunning = false;
|
_isRunning = false;
|
||||||
if (_wantSignals) {
|
|
||||||
emit runningStateChanged();
|
emit runningStateChanged();
|
||||||
emit doneRunning();
|
emit doneRunning();
|
||||||
}
|
|
||||||
timer->deleteLater();
|
timer->deleteLater();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -249,11 +244,9 @@ void ScriptEngine::runDebuggable() {
|
||||||
if (_lastUpdate < now) {
|
if (_lastUpdate < now) {
|
||||||
float deltaTime = (float)(now - _lastUpdate) / (float)USECS_PER_SECOND;
|
float deltaTime = (float)(now - _lastUpdate) / (float)USECS_PER_SECOND;
|
||||||
if (!_isFinished) {
|
if (!_isFinished) {
|
||||||
if (_wantSignals) {
|
|
||||||
emit update(deltaTime);
|
emit update(deltaTime);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
_lastUpdate = now;
|
_lastUpdate = now;
|
||||||
// Debug and clear exceptions
|
// Debug and clear exceptions
|
||||||
hadUncaughtExceptions(*this, _fileNameString);
|
hadUncaughtExceptions(*this, _fileNameString);
|
||||||
|
@ -272,57 +265,72 @@ void ScriptEngine::runInThread() {
|
||||||
}
|
}
|
||||||
|
|
||||||
_isThreaded = true;
|
_isThreaded = true;
|
||||||
|
|
||||||
|
// The thread interface cannot live on itself, and we want to move this into the thread, so
|
||||||
|
// the thread cannot have this as a parent.
|
||||||
QThread* workerThread = new QThread();
|
QThread* workerThread = new QThread();
|
||||||
QString scriptEngineName = QString("Script Thread:") + getFilename();
|
workerThread->setObjectName(QString("Script Thread:") + getFilename());
|
||||||
workerThread->setObjectName(scriptEngineName);
|
moveToThread(workerThread);
|
||||||
|
|
||||||
// NOTE: If you connect any essential signals for proper shutdown or cleanup of
|
// NOTE: If you connect any essential signals for proper shutdown or cleanup of
|
||||||
// the script engine, make sure to add code to "reconnect" them to the
|
// the script engine, make sure to add code to "reconnect" them to the
|
||||||
// disconnectNonEssentialSignals() method
|
// disconnectNonEssentialSignals() method
|
||||||
|
|
||||||
// when the worker thread is started, call our engine's run..
|
|
||||||
connect(workerThread, &QThread::started, this, &ScriptEngine::run);
|
connect(workerThread, &QThread::started, this, &ScriptEngine::run);
|
||||||
|
|
||||||
// tell the thread to stop when the script engine is done
|
|
||||||
connect(this, &ScriptEngine::doneRunning, workerThread, &QThread::quit);
|
connect(this, &ScriptEngine::doneRunning, workerThread, &QThread::quit);
|
||||||
|
connect(workerThread, &QThread::finished, workerThread, &QObject::deleteLater);
|
||||||
|
|
||||||
moveToThread(workerThread);
|
|
||||||
|
|
||||||
// Starts an event loop, and emits workerThread->started()
|
|
||||||
workerThread->start();
|
workerThread->start();
|
||||||
}
|
}
|
||||||
|
|
||||||
void ScriptEngine::waitTillDoneRunning() {
|
void ScriptEngine::waitTillDoneRunning() {
|
||||||
// If the script never started running or finished running before we got here, we don't need to wait for it
|
|
||||||
auto workerThread = thread();
|
auto workerThread = thread();
|
||||||
if (_isThreaded && workerThread) {
|
|
||||||
QString scriptName = getFilename();
|
|
||||||
auto startedWaiting = usecTimestampNow();
|
|
||||||
|
|
||||||
|
if (_isThreaded && workerThread) {
|
||||||
|
// We should never be waiting (blocking) on our own thread
|
||||||
|
assert(workerThread != QThread::currentThread());
|
||||||
|
|
||||||
|
// Engine should be stopped already, but be defensive
|
||||||
|
stop();
|
||||||
|
|
||||||
|
auto startedWaiting = usecTimestampNow();
|
||||||
while (workerThread->isRunning()) {
|
while (workerThread->isRunning()) {
|
||||||
// NOTE: This will be called on the main application thread from stopAllScripts.
|
// NOTE: This will be called on the main application thread from stopAllScripts.
|
||||||
// The application thread will need to continue to process events, because
|
// The application thread will need to continue to process events, because
|
||||||
// the scripts will likely need to marshall messages across to the main thread, e.g.
|
// the scripts will likely need to marshall messages across to the main thread, e.g.
|
||||||
// if they access Settings or Menu in any of their shutdown code. So:
|
// if they access Settings or Menu in any of their shutdown code. So:
|
||||||
// Process events for the main application thread, allowing invokeMethod calls to pass between threads.
|
// Process events for the main application thread, allowing invokeMethod calls to pass between threads.
|
||||||
QCoreApplication::processEvents(); // thread-safe :)
|
QCoreApplication::processEvents();
|
||||||
|
|
||||||
// If we've been waiting a second or more, then tell the script engine to stop evaluating
|
// If the final evaluation takes too long, then tell the script engine to stop running
|
||||||
static const auto MAX_SCRIPT_EVALUATION_TIME = USECS_PER_SECOND;
|
|
||||||
auto elapsedUsecs = usecTimestampNow() - startedWaiting;
|
auto elapsedUsecs = usecTimestampNow() - startedWaiting;
|
||||||
|
static const auto MAX_SCRIPT_EVALUATION_TIME = USECS_PER_SECOND;
|
||||||
if (elapsedUsecs > MAX_SCRIPT_EVALUATION_TIME) {
|
if (elapsedUsecs > MAX_SCRIPT_EVALUATION_TIME) {
|
||||||
qCDebug(scriptengine) <<
|
|
||||||
"Script " << scriptName << " has been running too long [" << elapsedUsecs << " usecs] quitting.";
|
|
||||||
abortEvaluation(); // to allow the thread to quit
|
|
||||||
workerThread->quit();
|
workerThread->quit();
|
||||||
break;
|
|
||||||
|
if (isEvaluating()) {
|
||||||
|
qCWarning(scriptengine) << "Script Engine has been running too long, aborting:" << getFilename();
|
||||||
|
abortEvaluation();
|
||||||
|
} else {
|
||||||
|
qCWarning(scriptengine) << "Script Engine has been running too long, throwing:" << getFilename();
|
||||||
|
auto context = currentContext();
|
||||||
|
if (context) {
|
||||||
|
context->throwError("Timed out during shutdown");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wait for the scripting thread to stop running, as
|
||||||
|
// flooding it with aborts/exceptions will persist it longer
|
||||||
|
static const auto MAX_SCRIPT_QUITTING_TIME = 0.5 * MSECS_PER_SECOND;
|
||||||
|
if (workerThread->wait(MAX_SCRIPT_QUITTING_TIME)) {
|
||||||
|
workerThread->terminate();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Avoid a pure busy wait
|
// Avoid a pure busy wait
|
||||||
QThread::yieldCurrentThread();
|
QThread::yieldCurrentThread();
|
||||||
}
|
}
|
||||||
|
|
||||||
workerThread->deleteLater();
|
qCDebug(scriptengine) << "Script Engine has stopped:" << getFilename();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -358,18 +366,14 @@ void ScriptEngine::scriptContentsAvailable(const QUrl& url, const QString& scrip
|
||||||
if (QRegularExpression(DEBUG_FLAG).match(scriptContents).hasMatch()) {
|
if (QRegularExpression(DEBUG_FLAG).match(scriptContents).hasMatch()) {
|
||||||
_debuggable = true;
|
_debuggable = true;
|
||||||
}
|
}
|
||||||
if (_wantSignals) {
|
|
||||||
emit scriptLoaded(url.toString());
|
emit scriptLoaded(url.toString());
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// FIXME - switch this to the new model of ScriptCache callbacks
|
// 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) {
|
|
||||||
emit errorLoadingScript(_fileNameString); // ??
|
emit errorLoadingScript(_fileNameString); // ??
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// Even though we never pass AnimVariantMap directly to and from javascript, the queued invokeMethod of
|
// Even though we never pass AnimVariantMap directly to and from javascript, the queued invokeMethod of
|
||||||
// callAnimationStateHandler requires that the type be registered.
|
// callAnimationStateHandler requires that the type be registered.
|
||||||
|
@ -756,7 +760,7 @@ void ScriptEngine::addEventHandler(const EntityItemID& entityID, const QString&
|
||||||
|
|
||||||
|
|
||||||
QScriptValue ScriptEngine::evaluate(const QString& sourceCode, const QString& fileName, int lineNumber) {
|
QScriptValue ScriptEngine::evaluate(const QString& sourceCode, const QString& fileName, int lineNumber) {
|
||||||
if (_stoppingAllScripts) {
|
if (DependencyManager::get<ScriptEngines>()->isStopped()) {
|
||||||
return QScriptValue(); // bail early
|
return QScriptValue(); // bail early
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -785,14 +789,12 @@ QScriptValue ScriptEngine::evaluate(const QString& sourceCode, const QString& fi
|
||||||
--_evaluatesPending;
|
--_evaluatesPending;
|
||||||
|
|
||||||
const auto hadUncaughtException = hadUncaughtExceptions(*this, program.fileName());
|
const auto hadUncaughtException = hadUncaughtExceptions(*this, program.fileName());
|
||||||
if (_wantSignals) {
|
|
||||||
emit evaluationFinished(result, hadUncaughtException);
|
emit evaluationFinished(result, hadUncaughtException);
|
||||||
}
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
void ScriptEngine::run() {
|
void ScriptEngine::run() {
|
||||||
if (_stoppingAllScripts) {
|
if (DependencyManager::get<ScriptEngines>()->isStopped()) {
|
||||||
return; // bail early - avoid setting state in init(), as evaluate() will bail too
|
return; // bail early - avoid setting state in init(), as evaluate() will bail too
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -801,9 +803,7 @@ void ScriptEngine::run() {
|
||||||
}
|
}
|
||||||
|
|
||||||
_isRunning = true;
|
_isRunning = true;
|
||||||
if (_wantSignals) {
|
|
||||||
emit runningStateChanged();
|
emit runningStateChanged();
|
||||||
}
|
|
||||||
|
|
||||||
QScriptValue result = evaluate(_scriptContents, _fileNameString);
|
QScriptValue result = evaluate(_scriptContents, _fileNameString);
|
||||||
|
|
||||||
|
@ -872,21 +872,19 @@ void ScriptEngine::run() {
|
||||||
if (_lastUpdate < now) {
|
if (_lastUpdate < now) {
|
||||||
float deltaTime = (float) (now - _lastUpdate) / (float) USECS_PER_SECOND;
|
float deltaTime = (float) (now - _lastUpdate) / (float) USECS_PER_SECOND;
|
||||||
if (!_isFinished) {
|
if (!_isFinished) {
|
||||||
if (_wantSignals) {
|
|
||||||
emit update(deltaTime);
|
emit update(deltaTime);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
_lastUpdate = now;
|
_lastUpdate = now;
|
||||||
|
|
||||||
// Debug and clear exceptions
|
// Debug and clear exceptions
|
||||||
hadUncaughtExceptions(*this, _fileNameString);
|
hadUncaughtExceptions(*this, _fileNameString);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
qCDebug(scriptengine) << "Script Engine stopping:" << getFilename();
|
||||||
|
|
||||||
stopAllTimers(); // make sure all our timers are stopped if the script is ending
|
stopAllTimers(); // make sure all our timers are stopped if the script is ending
|
||||||
if (_wantSignals) {
|
|
||||||
emit scriptEnding();
|
emit scriptEnding();
|
||||||
}
|
|
||||||
|
|
||||||
if (entityScriptingInterface->getEntityPacketSender()->serversExist()) {
|
if (entityScriptingInterface->getEntityPacketSender()->serversExist()) {
|
||||||
// release the queue of edit entity messages.
|
// release the queue of edit entity messages.
|
||||||
|
@ -904,16 +902,12 @@ void ScriptEngine::run() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_wantSignals) {
|
|
||||||
emit finished(_fileNameString, this);
|
emit finished(_fileNameString, this);
|
||||||
}
|
|
||||||
|
|
||||||
_isRunning = false;
|
_isRunning = false;
|
||||||
if (_wantSignals) {
|
|
||||||
emit runningStateChanged();
|
emit runningStateChanged();
|
||||||
emit doneRunning();
|
emit doneRunning();
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// NOTE: This is private because it must be called on the same thread that created the timers, which is why
|
// NOTE: This is private because it must be called on the same thread that created the timers, which is why
|
||||||
// we want to only call it in our own run "shutdown" processing.
|
// we want to only call it in our own run "shutdown" processing.
|
||||||
|
@ -945,16 +939,10 @@ void ScriptEngine::stopAllTimersForEntityScript(const EntityItemID& entityID) {
|
||||||
|
|
||||||
void ScriptEngine::stop() {
|
void ScriptEngine::stop() {
|
||||||
if (!_isFinished) {
|
if (!_isFinished) {
|
||||||
if (QThread::currentThread() != thread()) {
|
|
||||||
QMetaObject::invokeMethod(this, "stop");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
_isFinished = true;
|
_isFinished = true;
|
||||||
if (_wantSignals) {
|
|
||||||
emit runningStateChanged();
|
emit runningStateChanged();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// Other threads can invoke this through invokeMethod, which causes the callback to be asynchronously executed in this script's thread.
|
// Other threads can invoke this through invokeMethod, which causes the callback to be asynchronously executed in this script's thread.
|
||||||
void ScriptEngine::callAnimationStateHandler(QScriptValue callback, AnimVariantMap parameters, QStringList names, bool useNames, AnimVariantResultHandler resultHandler) {
|
void ScriptEngine::callAnimationStateHandler(QScriptValue callback, AnimVariantMap parameters, QStringList names, bool useNames, AnimVariantResultHandler resultHandler) {
|
||||||
|
@ -1025,7 +1013,7 @@ QObject* ScriptEngine::setupTimerWithInterval(const QScriptValue& function, int
|
||||||
}
|
}
|
||||||
|
|
||||||
QObject* ScriptEngine::setInterval(const QScriptValue& function, int intervalMS) {
|
QObject* ScriptEngine::setInterval(const QScriptValue& function, int intervalMS) {
|
||||||
if (_stoppingAllScripts) {
|
if (DependencyManager::get<ScriptEngines>()->isStopped()) {
|
||||||
qCDebug(scriptengine) << "Script.setInterval() while shutting down is ignored... parent script:" << getFilename();
|
qCDebug(scriptengine) << "Script.setInterval() while shutting down is ignored... parent script:" << getFilename();
|
||||||
return NULL; // bail early
|
return NULL; // bail early
|
||||||
}
|
}
|
||||||
|
@ -1034,7 +1022,7 @@ QObject* ScriptEngine::setInterval(const QScriptValue& function, int intervalMS)
|
||||||
}
|
}
|
||||||
|
|
||||||
QObject* ScriptEngine::setTimeout(const QScriptValue& function, int timeoutMS) {
|
QObject* ScriptEngine::setTimeout(const QScriptValue& function, int timeoutMS) {
|
||||||
if (_stoppingAllScripts) {
|
if (DependencyManager::get<ScriptEngines>()->isStopped()) {
|
||||||
qCDebug(scriptengine) << "Script.setTimeout() while shutting down is ignored... parent script:" << getFilename();
|
qCDebug(scriptengine) << "Script.setTimeout() while shutting down is ignored... parent script:" << getFilename();
|
||||||
return NULL; // bail early
|
return NULL; // bail early
|
||||||
}
|
}
|
||||||
|
@ -1076,17 +1064,15 @@ QUrl ScriptEngine::resolvePath(const QString& include) const {
|
||||||
}
|
}
|
||||||
|
|
||||||
void ScriptEngine::print(const QString& message) {
|
void ScriptEngine::print(const QString& message) {
|
||||||
if (_wantSignals) {
|
|
||||||
emit printedMessage(message);
|
emit printedMessage(message);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// If a callback is specified, the included files will be loaded asynchronously and the callback will be called
|
// If a callback is specified, the included files will be loaded asynchronously and the callback will be called
|
||||||
// when all of the files have finished loading.
|
// when all of the files have finished loading.
|
||||||
// If no callback is specified, the included files will be loaded synchronously and will block execution until
|
// If no callback is specified, the included files will be loaded synchronously and will block execution until
|
||||||
// all of the files have finished loading.
|
// all of the files have finished loading.
|
||||||
void ScriptEngine::include(const QStringList& includeFiles, QScriptValue callback) {
|
void ScriptEngine::include(const QStringList& includeFiles, QScriptValue callback) {
|
||||||
if (_stoppingAllScripts) {
|
if (DependencyManager::get<ScriptEngines>()->isStopped()) {
|
||||||
qCDebug(scriptengine) << "Script.include() while shutting down is ignored..."
|
qCDebug(scriptengine) << "Script.include() while shutting down is ignored..."
|
||||||
<< "includeFiles:" << includeFiles << "parent script:" << getFilename();
|
<< "includeFiles:" << includeFiles << "parent script:" << getFilename();
|
||||||
return; // bail early
|
return; // bail early
|
||||||
|
@ -1184,7 +1170,7 @@ void ScriptEngine::include(const QStringList& includeFiles, QScriptValue callbac
|
||||||
}
|
}
|
||||||
|
|
||||||
void ScriptEngine::include(const QString& includeFile, QScriptValue callback) {
|
void ScriptEngine::include(const QString& includeFile, QScriptValue callback) {
|
||||||
if (_stoppingAllScripts) {
|
if (DependencyManager::get<ScriptEngines>()->isStopped()) {
|
||||||
qCDebug(scriptengine) << "Script.include() while shutting down is ignored... "
|
qCDebug(scriptengine) << "Script.include() while shutting down is ignored... "
|
||||||
<< "includeFile:" << includeFile << "parent script:" << getFilename();
|
<< "includeFile:" << includeFile << "parent script:" << getFilename();
|
||||||
return; // bail early
|
return; // bail early
|
||||||
|
@ -1199,7 +1185,7 @@ void ScriptEngine::include(const QString& includeFile, QScriptValue callback) {
|
||||||
// as a stand-alone script. To accomplish this, the ScriptEngine class just emits a signal which
|
// as a stand-alone script. To accomplish this, the ScriptEngine class just emits a signal which
|
||||||
// the Application or other context will connect to in order to know to actually load the script
|
// the Application or other context will connect to in order to know to actually load the script
|
||||||
void ScriptEngine::load(const QString& loadFile) {
|
void ScriptEngine::load(const QString& loadFile) {
|
||||||
if (_stoppingAllScripts) {
|
if (DependencyManager::get<ScriptEngines>()->isStopped()) {
|
||||||
qCDebug(scriptengine) << "Script.load() while shutting down is ignored... "
|
qCDebug(scriptengine) << "Script.load() while shutting down is ignored... "
|
||||||
<< "loadFile:" << loadFile << "parent script:" << getFilename();
|
<< "loadFile:" << loadFile << "parent script:" << getFilename();
|
||||||
return; // bail early
|
return; // bail early
|
||||||
|
@ -1214,15 +1200,11 @@ void ScriptEngine::load(const QString& loadFile) {
|
||||||
if (_isReloading) {
|
if (_isReloading) {
|
||||||
auto scriptCache = DependencyManager::get<ScriptCache>();
|
auto scriptCache = DependencyManager::get<ScriptCache>();
|
||||||
scriptCache->deleteScript(url.toString());
|
scriptCache->deleteScript(url.toString());
|
||||||
if (_wantSignals) {
|
|
||||||
emit reloadScript(url.toString(), false);
|
emit reloadScript(url.toString(), false);
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
if (_wantSignals) {
|
|
||||||
emit loadScript(url.toString(), false);
|
emit loadScript(url.toString(), false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// Look up the handler associated with eventName and entityID. If found, evalute the argGenerator thunk and call the handler with those args
|
// Look up the handler associated with eventName and entityID. If found, evalute the argGenerator thunk and call the handler with those args
|
||||||
void ScriptEngine::forwardHandlerCall(const EntityItemID& entityID, const QString& eventName, QScriptValueList eventHandlerArgs) {
|
void ScriptEngine::forwardHandlerCall(const EntityItemID& entityID, const QString& eventName, QScriptValueList eventHandlerArgs) {
|
||||||
|
@ -1309,8 +1291,23 @@ void ScriptEngine::entityScriptContentAvailable(const EntityItemID& entityID, co
|
||||||
setParentURL(scriptOrURL);
|
setParentURL(scriptOrURL);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const int SANDBOX_TIMEOUT = 0.25 * MSECS_PER_SECOND;
|
||||||
QScriptEngine sandbox;
|
QScriptEngine sandbox;
|
||||||
QScriptValue testConstructor = sandbox.evaluate(program);
|
sandbox.setProcessEventsInterval(SANDBOX_TIMEOUT);
|
||||||
|
QScriptValue testConstructor;
|
||||||
|
{
|
||||||
|
QTimer timeout;
|
||||||
|
timeout.setSingleShot(true);
|
||||||
|
timeout.start(SANDBOX_TIMEOUT);
|
||||||
|
connect(&timeout, &QTimer::timeout, [&sandbox, SANDBOX_TIMEOUT]{
|
||||||
|
auto context = sandbox.currentContext();
|
||||||
|
if (context) {
|
||||||
|
// Guard against infinite loops and non-performant code
|
||||||
|
context->throwError(QString("Timed out (entity constructors are limited to %1ms)").arg(SANDBOX_TIMEOUT));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
testConstructor = sandbox.evaluate(program);
|
||||||
|
}
|
||||||
if (hadUncaughtExceptions(sandbox, program.fileName())) {
|
if (hadUncaughtExceptions(sandbox, program.fileName())) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
|
@ -67,10 +67,7 @@ public:
|
||||||
class ScriptEngine : public QScriptEngine, public ScriptUser, public EntitiesScriptEngineProvider {
|
class ScriptEngine : public QScriptEngine, public ScriptUser, public EntitiesScriptEngineProvider {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
public:
|
public:
|
||||||
ScriptEngine(const QString& scriptContents = NO_SCRIPT,
|
ScriptEngine(const QString& scriptContents = NO_SCRIPT, const QString& fileNameString = QString(""));
|
||||||
const QString& fileNameString = QString(""),
|
|
||||||
bool wantSignals = true);
|
|
||||||
|
|
||||||
~ScriptEngine();
|
~ScriptEngine();
|
||||||
|
|
||||||
/// run the script in a dedicated thread. This will have the side effect of evalulating
|
/// run the script in a dedicated thread. This will have the side effect of evalulating
|
||||||
|
@ -83,6 +80,15 @@ public:
|
||||||
/// run the script in the callers thread, exit when stop() is called.
|
/// run the script in the callers thread, exit when stop() is called.
|
||||||
void run();
|
void run();
|
||||||
|
|
||||||
|
QString getFilename() const;
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// NOTE - this is intended to be a public interface for Agent scripts, and local scripts, but not for EntityScripts
|
||||||
|
Q_INVOKABLE void stop();
|
||||||
|
|
||||||
|
// Stop any evaluating scripts and wait for the scripting thread to finish.
|
||||||
|
void waitTillDoneRunning();
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
// NOTE - these are NOT intended to be public interfaces available to scripts, the are only Q_INVOKABLE so we can
|
// 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
|
// properly ensure they are only called on the correct thread
|
||||||
|
@ -138,10 +144,6 @@ public:
|
||||||
|
|
||||||
Q_INVOKABLE void requestGarbageCollection() { collectGarbage(); }
|
Q_INVOKABLE void requestGarbageCollection() { collectGarbage(); }
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
||||||
// 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 isFinished() const { return _isFinished; } // used by Application and ScriptWidget
|
||||||
bool isRunning() const { return _isRunning; } // used by ScriptWidget
|
bool isRunning() const { return _isRunning; } // used by ScriptWidget
|
||||||
|
|
||||||
|
@ -191,7 +193,6 @@ protected:
|
||||||
bool _isInitialized { false };
|
bool _isInitialized { false };
|
||||||
QHash<QTimer*, CallbackData> _timerFunctionMap;
|
QHash<QTimer*, CallbackData> _timerFunctionMap;
|
||||||
QSet<QUrl> _includedURLs;
|
QSet<QUrl> _includedURLs;
|
||||||
bool _wantSignals { true };
|
|
||||||
QHash<EntityItemID, EntityScriptDetails> _entityScripts;
|
QHash<EntityItemID, EntityScriptDetails> _entityScripts;
|
||||||
bool _isThreaded { false };
|
bool _isThreaded { false };
|
||||||
QScriptEngineDebugger* _debugger { nullptr };
|
QScriptEngineDebugger* _debugger { nullptr };
|
||||||
|
@ -199,8 +200,7 @@ protected:
|
||||||
qint64 _lastUpdate;
|
qint64 _lastUpdate;
|
||||||
|
|
||||||
void init();
|
void init();
|
||||||
QString getFilename() const;
|
|
||||||
void waitTillDoneRunning();
|
|
||||||
bool evaluatePending() const { return _evaluatesPending > 0; }
|
bool evaluatePending() const { return _evaluatesPending > 0; }
|
||||||
void timerFired();
|
void timerFired();
|
||||||
void stopAllTimers();
|
void stopAllTimers();
|
||||||
|
@ -232,9 +232,6 @@ protected:
|
||||||
QUrl currentSandboxURL {}; // The toplevel url string for the entity script that loaded the code being executed, else empty.
|
QUrl currentSandboxURL {}; // The toplevel url string for the entity script that loaded the code being executed, else empty.
|
||||||
void doWithEnvironment(const EntityItemID& entityID, const QUrl& sandboxURL, std::function<void()> operation);
|
void doWithEnvironment(const EntityItemID& entityID, const QUrl& sandboxURL, std::function<void()> operation);
|
||||||
void callWithEnvironment(const EntityItemID& entityID, const QUrl& sandboxURL, QScriptValue function, QScriptValue thisObject, QScriptValueList args);
|
void callWithEnvironment(const EntityItemID& entityID, const QUrl& sandboxURL, QScriptValue function, QScriptValue thisObject, QScriptValueList args);
|
||||||
|
|
||||||
friend class ScriptEngines;
|
|
||||||
static std::atomic<bool> _stoppingAllScripts;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // hifi_ScriptEngine_h
|
#endif // hifi_ScriptEngine_h
|
||||||
|
|
|
@ -119,26 +119,27 @@ void ScriptEngines::registerScriptInitializer(ScriptInitializer initializer) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void ScriptEngines::addScriptEngine(ScriptEngine* engine) {
|
void ScriptEngines::addScriptEngine(ScriptEngine* engine) {
|
||||||
_allScriptsMutex.lock();
|
if (_isStopped) {
|
||||||
|
engine->deleteLater();
|
||||||
|
} else {
|
||||||
|
QMutexLocker locker(&_allScriptsMutex);
|
||||||
_allKnownScriptEngines.insert(engine);
|
_allKnownScriptEngines.insert(engine);
|
||||||
_allScriptsMutex.unlock();
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void ScriptEngines::removeScriptEngine(ScriptEngine* engine) {
|
void ScriptEngines::removeScriptEngine(ScriptEngine* engine) {
|
||||||
// If we're not already in the middle of stopping all scripts, then we should remove ourselves
|
// If we're not already in the middle of stopping all scripts, then we should remove ourselves
|
||||||
// from the list of running scripts. We don't do this if we're in the process of stopping all scripts
|
// from the list of running scripts. We don't do this if we're in the process of stopping all scripts
|
||||||
// because that method removes scripts from its list as it iterates them
|
// because that method removes scripts from its list as it iterates them
|
||||||
if (!_stoppingAllScripts) {
|
if (!_isStopped) {
|
||||||
_allScriptsMutex.lock();
|
QMutexLocker locker(&_allScriptsMutex);
|
||||||
_allKnownScriptEngines.remove(engine);
|
_allKnownScriptEngines.remove(engine);
|
||||||
_allScriptsMutex.unlock();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void ScriptEngines::shutdownScripting() {
|
void ScriptEngines::shutdownScripting() {
|
||||||
_allScriptsMutex.lock();
|
_isStopped = true;
|
||||||
_stoppingAllScripts = true;
|
QMutexLocker locker(&_allScriptsMutex);
|
||||||
ScriptEngine::_stoppingAllScripts = true;
|
|
||||||
qCDebug(scriptengine) << "Stopping all scripts.... currently known scripts:" << _allKnownScriptEngines.size();
|
qCDebug(scriptengine) << "Stopping all scripts.... currently known scripts:" << _allKnownScriptEngines.size();
|
||||||
|
|
||||||
QMutableSetIterator<ScriptEngine*> i(_allKnownScriptEngines);
|
QMutableSetIterator<ScriptEngine*> i(_allKnownScriptEngines);
|
||||||
|
@ -149,6 +150,7 @@ void ScriptEngines::shutdownScripting() {
|
||||||
// NOTE: typically all script engines are running. But there's at least one known exception to this, the
|
// NOTE: typically all script engines are running. But there's at least one known exception to this, the
|
||||||
// "entities sandbox" which is only used to evaluate entities scripts to test their validity before using
|
// "entities sandbox" which is only used to evaluate entities scripts to test their validity before using
|
||||||
// them. We don't need to stop scripts that aren't running.
|
// them. We don't need to stop scripts that aren't running.
|
||||||
|
// TODO: Scripts could be shut down faster if we spread them across a threadpool.
|
||||||
if (scriptEngine->isRunning()) {
|
if (scriptEngine->isRunning()) {
|
||||||
qCDebug(scriptengine) << "about to shutdown script:" << scriptName;
|
qCDebug(scriptengine) << "about to shutdown script:" << scriptName;
|
||||||
|
|
||||||
|
@ -157,8 +159,7 @@ void ScriptEngines::shutdownScripting() {
|
||||||
// and stop. We can safely short circuit this because we know we're in the "quitting" process
|
// and stop. We can safely short circuit this because we know we're in the "quitting" process
|
||||||
scriptEngine->disconnect(this);
|
scriptEngine->disconnect(this);
|
||||||
|
|
||||||
// Calling stop on the script engine will set it's internal _isFinished state to true, and result
|
// Gracefully stop the engine's scripting thread
|
||||||
// in the ScriptEngine gracefully ending it's run() method.
|
|
||||||
scriptEngine->stop();
|
scriptEngine->stop();
|
||||||
|
|
||||||
// We need to wait for the engine to be done running before we proceed, because we don't
|
// We need to wait for the engine to be done running before we proceed, because we don't
|
||||||
|
@ -170,12 +171,10 @@ void ScriptEngines::shutdownScripting() {
|
||||||
|
|
||||||
scriptEngine->deleteLater();
|
scriptEngine->deleteLater();
|
||||||
|
|
||||||
// If the script is stopped, we can remove it from our set
|
// Once the script is stopped, we can remove it from our set
|
||||||
i.remove();
|
i.remove();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_stoppingAllScripts = false;
|
|
||||||
_allScriptsMutex.unlock();
|
|
||||||
qCDebug(scriptengine) << "DONE Stopping all scripts....";
|
qCDebug(scriptengine) << "DONE Stopping all scripts....";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -428,7 +427,7 @@ ScriptEngine* ScriptEngines::loadScript(const QUrl& scriptFilename, bool isUserL
|
||||||
return scriptEngine;
|
return scriptEngine;
|
||||||
}
|
}
|
||||||
|
|
||||||
scriptEngine = new ScriptEngine(NO_SCRIPT, "", true);
|
scriptEngine = new ScriptEngine(NO_SCRIPT, "");
|
||||||
scriptEngine->setUserLoaded(isUserLoaded);
|
scriptEngine->setUserLoaded(isUserLoaded);
|
||||||
connect(scriptEngine, &ScriptEngine::doneRunning, this, [scriptEngine] {
|
connect(scriptEngine, &ScriptEngine::doneRunning, this, [scriptEngine] {
|
||||||
scriptEngine->deleteLater();
|
scriptEngine->deleteLater();
|
||||||
|
@ -499,7 +498,6 @@ void ScriptEngines::launchScriptEngine(ScriptEngine* scriptEngine) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void ScriptEngines::onScriptFinished(const QString& rawScriptURL, ScriptEngine* engine) {
|
void ScriptEngines::onScriptFinished(const QString& rawScriptURL, ScriptEngine* engine) {
|
||||||
bool removed = false;
|
bool removed = false;
|
||||||
{
|
{
|
||||||
|
|
|
@ -86,16 +86,17 @@ protected:
|
||||||
void onScriptEngineLoaded(const QString& scriptFilename);
|
void onScriptEngineLoaded(const QString& scriptFilename);
|
||||||
void onScriptEngineError(const QString& scriptFilename);
|
void onScriptEngineError(const QString& scriptFilename);
|
||||||
void launchScriptEngine(ScriptEngine* engine);
|
void launchScriptEngine(ScriptEngine* engine);
|
||||||
|
bool isStopped() const { return _isStopped; }
|
||||||
|
|
||||||
QReadWriteLock _scriptEnginesHashLock;
|
QReadWriteLock _scriptEnginesHashLock;
|
||||||
QHash<QUrl, ScriptEngine*> _scriptEnginesHash;
|
QHash<QUrl, ScriptEngine*> _scriptEnginesHash;
|
||||||
QSet<ScriptEngine*> _allKnownScriptEngines;
|
QSet<ScriptEngine*> _allKnownScriptEngines;
|
||||||
QMutex _allScriptsMutex;
|
QMutex _allScriptsMutex;
|
||||||
std::atomic<bool> _stoppingAllScripts { false };
|
|
||||||
std::list<ScriptInitializer> _scriptInitializers;
|
std::list<ScriptInitializer> _scriptInitializers;
|
||||||
mutable Setting::Handle<QString> _scriptsLocationHandle;
|
mutable Setting::Handle<QString> _scriptsLocationHandle;
|
||||||
ScriptsModel _scriptsModel;
|
ScriptsModel _scriptsModel;
|
||||||
ScriptsModelFilter _scriptsModelFilter;
|
ScriptsModelFilter _scriptsModelFilter;
|
||||||
|
std::atomic<bool> _isStopped { false };
|
||||||
};
|
};
|
||||||
|
|
||||||
QUrl normalizeScriptURL(const QUrl& rawScriptURL);
|
QUrl normalizeScriptURL(const QUrl& rawScriptURL);
|
||||||
|
|
|
@ -21,8 +21,7 @@ var PopUpMenu = function(properties) {
|
||||||
MIN_MAX_BUTTON_SVG_WIDTH = 17.1,
|
MIN_MAX_BUTTON_SVG_WIDTH = 17.1,
|
||||||
MIN_MAX_BUTTON_SVG_HEIGHT = 32.5,
|
MIN_MAX_BUTTON_SVG_HEIGHT = 32.5,
|
||||||
MIN_MAX_BUTTON_WIDTH = 14,
|
MIN_MAX_BUTTON_WIDTH = 14,
|
||||||
MIN_MAX_BUTTON_HEIGHT = MIN_MAX_BUTTON_WIDTH;
|
MIN_MAX_BUTTON_HEIGHT = MIN_MAX_BUTTON_WIDTH,
|
||||||
|
|
||||||
MIN_MAX_BUTTON_SVG = Script.resolvePath("assets/images/tools/min-max-toggle.svg");
|
MIN_MAX_BUTTON_SVG = Script.resolvePath("assets/images/tools/min-max-toggle.svg");
|
||||||
|
|
||||||
function positionDisplayOptions() {
|
function positionDisplayOptions() {
|
||||||
|
@ -203,7 +202,7 @@ var PopUpMenu = function(properties) {
|
||||||
width: MIN_MAX_BUTTON_SVG_WIDTH,
|
width: MIN_MAX_BUTTON_SVG_WIDTH,
|
||||||
height: MIN_MAX_BUTTON_SVG_HEIGHT / 2
|
height: MIN_MAX_BUTTON_SVG_HEIGHT / 2
|
||||||
},
|
},
|
||||||
color: properties.buttonColor,
|
//color: properties.buttonColor,
|
||||||
alpha: properties.buttonAlpha,
|
alpha: properties.buttonAlpha,
|
||||||
visible: properties.visible
|
visible: properties.visible
|
||||||
});
|
});
|
||||||
|
@ -220,9 +219,8 @@ var PopUpMenu = function(properties) {
|
||||||
|
|
||||||
var usersWindow = (function () {
|
var usersWindow = (function () {
|
||||||
|
|
||||||
var baseURL = Script.resolvePath("assets/images/tools/");
|
var baseURL = Script.resolvePath("assets/images/tools/"),
|
||||||
|
WINDOW_WIDTH = 260,
|
||||||
var WINDOW_WIDTH = 260,
|
|
||||||
WINDOW_MARGIN = 12,
|
WINDOW_MARGIN = 12,
|
||||||
WINDOW_BASE_MARGIN = 6, // A little less is needed in order look correct
|
WINDOW_BASE_MARGIN = 6, // A little less is needed in order look correct
|
||||||
WINDOW_FONT = {
|
WINDOW_FONT = {
|
||||||
|
@ -248,6 +246,17 @@ var usersWindow = (function() {
|
||||||
WINDOW_BACKGROUND_ALPHA = 0.8,
|
WINDOW_BACKGROUND_ALPHA = 0.8,
|
||||||
windowPane,
|
windowPane,
|
||||||
windowHeading,
|
windowHeading,
|
||||||
|
|
||||||
|
// Window border is similar to that of edit.js.
|
||||||
|
WINDOW_BORDER_WIDTH = WINDOW_WIDTH + 2 * WINDOW_BASE_MARGIN,
|
||||||
|
WINDOW_BORDER_TOP_MARGIN = 2 * WINDOW_BASE_MARGIN,
|
||||||
|
WINDOW_BORDER_BOTTOM_MARGIN = WINDOW_BASE_MARGIN,
|
||||||
|
WINDOW_BORDER_LEFT_MARGIN = WINDOW_BASE_MARGIN,
|
||||||
|
WINDOW_BORDER_RADIUS = 4,
|
||||||
|
WINDOW_BORDER_COLOR = { red: 255, green: 255, blue: 255 },
|
||||||
|
WINDOW_BORDER_ALPHA = 0.5,
|
||||||
|
windowBorder,
|
||||||
|
|
||||||
MIN_MAX_BUTTON_SVG = baseURL + "min-max-toggle.svg",
|
MIN_MAX_BUTTON_SVG = baseURL + "min-max-toggle.svg",
|
||||||
MIN_MAX_BUTTON_SVG_WIDTH = 17.1,
|
MIN_MAX_BUTTON_SVG_WIDTH = 17.1,
|
||||||
MIN_MAX_BUTTON_SVG_HEIGHT = 32.5,
|
MIN_MAX_BUTTON_SVG_HEIGHT = 32.5,
|
||||||
|
@ -331,6 +340,7 @@ var usersWindow = (function() {
|
||||||
visibilityControl,
|
visibilityControl,
|
||||||
|
|
||||||
windowHeight,
|
windowHeight,
|
||||||
|
windowBorderHeight,
|
||||||
windowTextHeight,
|
windowTextHeight,
|
||||||
windowLineSpacing,
|
windowLineSpacing,
|
||||||
windowLineHeight, // = windowTextHeight + windowLineSpacing
|
windowLineHeight, // = windowTextHeight + windowLineSpacing
|
||||||
|
@ -356,14 +366,21 @@ var usersWindow = (function() {
|
||||||
MENU_ITEM_AFTER = "Chat...",
|
MENU_ITEM_AFTER = "Chat...",
|
||||||
|
|
||||||
SETTING_USERS_WINDOW_MINIMIZED = "UsersWindow.Minimized",
|
SETTING_USERS_WINDOW_MINIMIZED = "UsersWindow.Minimized",
|
||||||
|
SETINGS_USERS_WINDOW_OFFSET = "UsersWindow.Offset",
|
||||||
|
// +ve x, y values are offset from left, top of screen; -ve from right, bottom.
|
||||||
|
|
||||||
isVisible = true,
|
isVisible = true,
|
||||||
isMinimized = false,
|
isMinimized = false,
|
||||||
|
isBorderVisible = false,
|
||||||
|
|
||||||
viewportHeight,
|
viewport,
|
||||||
isMirrorDisplay = false,
|
isMirrorDisplay = false,
|
||||||
isFullscreenMirror = false,
|
isFullscreenMirror = false,
|
||||||
|
|
||||||
|
windowPosition = { }, // Bottom left corner of window pane.
|
||||||
|
isMovingWindow = false,
|
||||||
|
movingClickOffset = { x: 0, y: 0 },
|
||||||
|
|
||||||
isUsingScrollbars = false,
|
isUsingScrollbars = false,
|
||||||
isMovingScrollbar = false,
|
isMovingScrollbar = false,
|
||||||
scrollbarBackgroundPosition = {},
|
scrollbarBackgroundPosition = {},
|
||||||
|
@ -379,19 +396,23 @@ var usersWindow = (function() {
|
||||||
|
|
||||||
if (isMinimized) {
|
if (isMinimized) {
|
||||||
windowHeight = windowTextHeight + WINDOW_MARGIN + WINDOW_BASE_MARGIN;
|
windowHeight = windowTextHeight + WINDOW_MARGIN + WINDOW_BASE_MARGIN;
|
||||||
|
windowBorderHeight = windowHeight + WINDOW_BORDER_TOP_MARGIN + WINDOW_BORDER_BOTTOM_MARGIN;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reserve space for title, friends button, and option controls
|
// Reserve space for title, friends button, and option controls
|
||||||
nonUsersHeight = WINDOW_MARGIN + windowLineHeight + FRIENDS_BUTTON_SPACER + FRIENDS_BUTTON_HEIGHT + DISPLAY_SPACER + windowLineHeight + VISIBILITY_SPACER + windowLineHeight + WINDOW_BASE_MARGIN;
|
nonUsersHeight = WINDOW_MARGIN + windowLineHeight + FRIENDS_BUTTON_SPACER + FRIENDS_BUTTON_HEIGHT + DISPLAY_SPACER
|
||||||
|
+ windowLineHeight + VISIBILITY_SPACER
|
||||||
|
+ windowLineHeight + WINDOW_BASE_MARGIN;
|
||||||
|
|
||||||
// Limit window to height of viewport minus VU meter and mirror if displayed
|
// Limit window to height of viewport above window position minus VU meter and mirror if displayed
|
||||||
windowHeight = linesOfUsers.length * windowLineHeight - windowLineSpacing + nonUsersHeight;
|
windowHeight = linesOfUsers.length * windowLineHeight - windowLineSpacing + nonUsersHeight;
|
||||||
maxWindowHeight = viewportHeight - AUDIO_METER_HEIGHT;
|
maxWindowHeight = windowPosition.y - AUDIO_METER_HEIGHT;
|
||||||
if (isMirrorDisplay && !isFullscreenMirror) {
|
if (isMirrorDisplay && !isFullscreenMirror) {
|
||||||
maxWindowHeight -= MIRROR_HEIGHT;
|
maxWindowHeight -= MIRROR_HEIGHT;
|
||||||
}
|
}
|
||||||
windowHeight = Math.max(Math.min(windowHeight, maxWindowHeight), nonUsersHeight);
|
windowHeight = Math.max(Math.min(windowHeight, maxWindowHeight), nonUsersHeight);
|
||||||
|
windowBorderHeight = windowHeight + WINDOW_BORDER_TOP_MARGIN + WINDOW_BORDER_BOTTOM_MARGIN;
|
||||||
|
|
||||||
// Corresponding number of users to actually display
|
// Corresponding number of users to actually display
|
||||||
numUsersToDisplay = Math.max(Math.round((windowHeight - nonUsersHeight) / windowLineHeight), 0);
|
numUsersToDisplay = Math.max(Math.round((windowHeight - nonUsersHeight) / windowLineHeight), 0);
|
||||||
|
@ -405,38 +426,57 @@ var usersWindow = (function() {
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateOverlayPositions() {
|
function updateOverlayPositions() {
|
||||||
var y;
|
// Overlay positions are all relative to windowPosition; windowPosition is the position of the windowPane overlay.
|
||||||
|
var windowLeft = windowPosition.x,
|
||||||
|
windowTop = windowPosition.y - windowHeight,
|
||||||
|
x,
|
||||||
|
y;
|
||||||
|
|
||||||
|
Overlays.editOverlay(windowBorder, {
|
||||||
|
x: windowPosition.x - WINDOW_BORDER_LEFT_MARGIN,
|
||||||
|
y: windowTop - WINDOW_BORDER_TOP_MARGIN
|
||||||
|
});
|
||||||
Overlays.editOverlay(windowPane, {
|
Overlays.editOverlay(windowPane, {
|
||||||
y: viewportHeight - windowHeight
|
x: windowLeft,
|
||||||
|
y: windowTop
|
||||||
});
|
});
|
||||||
Overlays.editOverlay(windowHeading, {
|
Overlays.editOverlay(windowHeading, {
|
||||||
y: viewportHeight - windowHeight + WINDOW_MARGIN
|
x: windowLeft + WINDOW_MARGIN,
|
||||||
|
y: windowTop + WINDOW_MARGIN
|
||||||
});
|
});
|
||||||
|
|
||||||
Overlays.editOverlay(minimizeButton, {
|
Overlays.editOverlay(minimizeButton, {
|
||||||
y: viewportHeight - windowHeight + WINDOW_MARGIN / 2
|
x: windowLeft + WINDOW_WIDTH - WINDOW_MARGIN / 2 - MIN_MAX_BUTTON_WIDTH,
|
||||||
|
y: windowTop + WINDOW_MARGIN / 2
|
||||||
});
|
});
|
||||||
|
|
||||||
scrollbarBackgroundPosition.y = viewportHeight - windowHeight + WINDOW_MARGIN + windowTextHeight;
|
scrollbarBackgroundPosition.x = windowLeft + WINDOW_WIDTH - 0.5 * WINDOW_MARGIN - SCROLLBAR_BACKGROUND_WIDTH;
|
||||||
|
scrollbarBackgroundPosition.y = windowTop + WINDOW_MARGIN + windowTextHeight;
|
||||||
Overlays.editOverlay(scrollbarBackground, {
|
Overlays.editOverlay(scrollbarBackground, {
|
||||||
|
x: scrollbarBackgroundPosition.x,
|
||||||
y: scrollbarBackgroundPosition.y
|
y: scrollbarBackgroundPosition.y
|
||||||
});
|
});
|
||||||
scrollbarBarPosition.y = scrollbarBackgroundPosition.y + 1 + scrollbarValue * (scrollbarBackgroundHeight - scrollbarBarHeight - 2);
|
scrollbarBarPosition.y = scrollbarBackgroundPosition.y + 1
|
||||||
|
+ scrollbarValue * (scrollbarBackgroundHeight - scrollbarBarHeight - 2);
|
||||||
Overlays.editOverlay(scrollbarBar, {
|
Overlays.editOverlay(scrollbarBar, {
|
||||||
|
x: scrollbarBackgroundPosition.x + 1,
|
||||||
y: scrollbarBarPosition.y
|
y: scrollbarBarPosition.y
|
||||||
});
|
});
|
||||||
|
|
||||||
y = viewportHeight - FRIENDS_BUTTON_HEIGHT - DISPLAY_SPACER - windowLineHeight - VISIBILITY_SPACER - windowLineHeight - WINDOW_BASE_MARGIN;
|
x = windowLeft + WINDOW_MARGIN;
|
||||||
|
y = windowPosition.y - FRIENDS_BUTTON_HEIGHT - DISPLAY_SPACER
|
||||||
|
- windowLineHeight - VISIBILITY_SPACER
|
||||||
|
- windowLineHeight - WINDOW_BASE_MARGIN;
|
||||||
Overlays.editOverlay(friendsButton, {
|
Overlays.editOverlay(friendsButton, {
|
||||||
|
x: x,
|
||||||
y: y
|
y: y
|
||||||
});
|
});
|
||||||
|
|
||||||
y += FRIENDS_BUTTON_HEIGHT + DISPLAY_SPACER;
|
y += FRIENDS_BUTTON_HEIGHT + DISPLAY_SPACER;
|
||||||
displayControl.updatePosition(WINDOW_MARGIN, y);
|
displayControl.updatePosition(x, y);
|
||||||
|
|
||||||
y += windowLineHeight + VISIBILITY_SPACER;
|
y += windowLineHeight + VISIBILITY_SPACER;
|
||||||
visibilityControl.updatePosition(WINDOW_MARGIN, y);
|
visibilityControl.updatePosition(x, y);
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateUsersDisplay() {
|
function updateUsersDisplay() {
|
||||||
|
@ -487,6 +527,10 @@ var usersWindow = (function() {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Overlays.editOverlay(windowBorder, {
|
||||||
|
height: windowBorderHeight
|
||||||
|
});
|
||||||
|
|
||||||
Overlays.editOverlay(windowPane, {
|
Overlays.editOverlay(windowPane, {
|
||||||
height: windowHeight,
|
height: windowHeight,
|
||||||
text: displayText
|
text: displayText
|
||||||
|
@ -571,6 +615,9 @@ var usersWindow = (function() {
|
||||||
};
|
};
|
||||||
|
|
||||||
function updateOverlayVisibility() {
|
function updateOverlayVisibility() {
|
||||||
|
Overlays.editOverlay(windowBorder, {
|
||||||
|
visible: isVisible && isBorderVisible
|
||||||
|
});
|
||||||
Overlays.editOverlay(windowPane, {
|
Overlays.editOverlay(windowPane, {
|
||||||
visible: isVisible
|
visible: isVisible
|
||||||
});
|
});
|
||||||
|
@ -670,7 +717,7 @@ var usersWindow = (function() {
|
||||||
if (clickedOverlay === windowPane) {
|
if (clickedOverlay === windowPane) {
|
||||||
|
|
||||||
overlayX = event.x - WINDOW_MARGIN;
|
overlayX = event.x - WINDOW_MARGIN;
|
||||||
overlayY = event.y - viewportHeight + windowHeight - WINDOW_MARGIN - windowLineHeight;
|
overlayY = event.y - windowPosition.y + windowHeight - WINDOW_MARGIN - windowLineHeight;
|
||||||
|
|
||||||
numLinesBefore = Math.round(overlayY / windowLineHeight);
|
numLinesBefore = Math.round(overlayY / windowLineHeight);
|
||||||
minY = numLinesBefore * windowLineHeight;
|
minY = numLinesBefore * windowLineHeight;
|
||||||
|
@ -683,7 +730,8 @@ var usersWindow = (function() {
|
||||||
|
|
||||||
userClicked = firstUserToDisplay + lineClicked;
|
userClicked = firstUserToDisplay + lineClicked;
|
||||||
|
|
||||||
if (0 <= userClicked && userClicked < linesOfUsers.length && 0 <= overlayX && overlayX <= usersOnline[linesOfUsers[userClicked]].textWidth) {
|
if (0 <= userClicked && userClicked < linesOfUsers.length && 0 <= overlayX
|
||||||
|
&& overlayX <= usersOnline[linesOfUsers[userClicked]].textWidth) {
|
||||||
//print("Go to " + usersOnline[linesOfUsers[userClicked]].username);
|
//print("Go to " + usersOnline[linesOfUsers[userClicked]].username);
|
||||||
location.goToUser(usersOnline[linesOfUsers[userClicked]].username);
|
location.goToUser(usersOnline[linesOfUsers[userClicked]].username);
|
||||||
}
|
}
|
||||||
|
@ -735,13 +783,29 @@ var usersWindow = (function() {
|
||||||
friendsWindow.setURL(FRIENDS_WINDOW_URL);
|
friendsWindow.setURL(FRIENDS_WINDOW_URL);
|
||||||
friendsWindow.setVisible(true);
|
friendsWindow.setVisible(true);
|
||||||
friendsWindow.raise();
|
friendsWindow.raise();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (clickedOverlay === windowBorder) {
|
||||||
|
movingClickOffset = {
|
||||||
|
x: event.x - windowPosition.x,
|
||||||
|
y: event.y - windowPosition.y
|
||||||
|
};
|
||||||
|
|
||||||
|
isMovingWindow = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function onMouseMoveEvent(event) {
|
function onMouseMoveEvent(event) {
|
||||||
|
var isVisible;
|
||||||
|
|
||||||
if (isMovingScrollbar) {
|
if (isMovingScrollbar) {
|
||||||
if (scrollbarBackgroundPosition.x - WINDOW_MARGIN <= event.x && event.x <= scrollbarBackgroundPosition.x + SCROLLBAR_BACKGROUND_WIDTH + WINDOW_MARGIN && scrollbarBackgroundPosition.y - WINDOW_MARGIN <= event.y && event.y <= scrollbarBackgroundPosition.y + scrollbarBackgroundHeight + WINDOW_MARGIN) {
|
if (scrollbarBackgroundPosition.x - WINDOW_MARGIN <= event.x
|
||||||
scrollbarValue = (event.y - scrollbarBarClickedAt * scrollbarBarHeight - scrollbarBackgroundPosition.y) / (scrollbarBackgroundHeight - scrollbarBarHeight - 2);
|
&& event.x <= scrollbarBackgroundPosition.x + SCROLLBAR_BACKGROUND_WIDTH + WINDOW_MARGIN
|
||||||
|
&& scrollbarBackgroundPosition.y - WINDOW_MARGIN <= event.y
|
||||||
|
&& event.y <= scrollbarBackgroundPosition.y + scrollbarBackgroundHeight + WINDOW_MARGIN) {
|
||||||
|
scrollbarValue = (event.y - scrollbarBarClickedAt * scrollbarBarHeight - scrollbarBackgroundPosition.y)
|
||||||
|
/ (scrollbarBackgroundHeight - scrollbarBarHeight - 2);
|
||||||
scrollbarValue = Math.min(Math.max(scrollbarValue, 0.0), 1.0);
|
scrollbarValue = Math.min(Math.max(scrollbarValue, 0.0), 1.0);
|
||||||
firstUserToDisplay = Math.floor(scrollbarValue * (linesOfUsers.length - numUsersToDisplay));
|
firstUserToDisplay = Math.floor(scrollbarValue * (linesOfUsers.length - numUsersToDisplay));
|
||||||
updateOverlayPositions();
|
updateOverlayPositions();
|
||||||
|
@ -753,35 +817,95 @@ var usersWindow = (function() {
|
||||||
isMovingScrollbar = false;
|
isMovingScrollbar = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (isMovingWindow) {
|
||||||
|
windowPosition = {
|
||||||
|
x: event.x - movingClickOffset.x,
|
||||||
|
y: event.y - movingClickOffset.y
|
||||||
|
};
|
||||||
|
calculateWindowHeight();
|
||||||
|
updateOverlayPositions();
|
||||||
|
updateUsersDisplay();
|
||||||
|
|
||||||
|
} else {
|
||||||
|
|
||||||
|
isVisible = isBorderVisible;
|
||||||
|
if (isVisible) {
|
||||||
|
isVisible = windowPosition.x - WINDOW_BORDER_LEFT_MARGIN <= event.x
|
||||||
|
&& event.x <= windowPosition.x - WINDOW_BORDER_LEFT_MARGIN + WINDOW_BORDER_WIDTH
|
||||||
|
&& windowPosition.y - windowHeight - WINDOW_BORDER_TOP_MARGIN <= event.y
|
||||||
|
&& event.y <= windowPosition.y + WINDOW_BORDER_BOTTOM_MARGIN;
|
||||||
|
} else {
|
||||||
|
isVisible = windowPosition.x <= event.x && event.x <= windowPosition.x + WINDOW_WIDTH
|
||||||
|
&& windowPosition.y - windowHeight <= event.y && event.y <= windowPosition.y;
|
||||||
|
}
|
||||||
|
if (isVisible !== isBorderVisible) {
|
||||||
|
isBorderVisible = isVisible;
|
||||||
|
Overlays.editOverlay(windowBorder, {
|
||||||
|
visible: isBorderVisible
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function onMouseReleaseEvent() {
|
function onMouseReleaseEvent() {
|
||||||
|
var offset = {};
|
||||||
|
|
||||||
|
if (isMovingScrollbar) {
|
||||||
Overlays.editOverlay(scrollbarBar, {
|
Overlays.editOverlay(scrollbarBar, {
|
||||||
backgroundAlpha: SCROLLBAR_BAR_ALPHA
|
backgroundAlpha: SCROLLBAR_BAR_ALPHA
|
||||||
});
|
});
|
||||||
isMovingScrollbar = false;
|
isMovingScrollbar = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (isMovingWindow) {
|
||||||
|
// Save offset of bottom of window to nearest edge of the window.
|
||||||
|
offset.x = (windowPosition.x + WINDOW_WIDTH / 2 < viewport.x / 2) ? windowPosition.x : windowPosition.x - viewport.x;
|
||||||
|
offset.y = (windowPosition.y < viewport.y / 2) ? windowPosition.y : windowPosition.y - viewport.y;
|
||||||
|
Settings.setValue(SETINGS_USERS_WINDOW_OFFSET, JSON.stringify(offset));
|
||||||
|
isMovingWindow = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function onScriptUpdate() {
|
function onScriptUpdate() {
|
||||||
var oldViewportHeight = viewportHeight,
|
var oldViewport = viewport,
|
||||||
oldIsMirrorDisplay = isMirrorDisplay,
|
oldIsMirrorDisplay = isMirrorDisplay,
|
||||||
oldIsFullscreenMirror = isFullscreenMirror,
|
oldIsFullscreenMirror = isFullscreenMirror,
|
||||||
MIRROR_MENU_ITEM = "Mirror",
|
MIRROR_MENU_ITEM = "Mirror",
|
||||||
FULLSCREEN_MIRROR_MENU_ITEM = "Fullscreen Mirror";
|
FULLSCREEN_MIRROR_MENU_ITEM = "Fullscreen Mirror";
|
||||||
|
|
||||||
viewportHeight = Controller.getViewportDimensions().y;
|
viewport = Controller.getViewportDimensions();
|
||||||
isMirrorDisplay = Menu.isOptionChecked(MIRROR_MENU_ITEM);
|
isMirrorDisplay = Menu.isOptionChecked(MIRROR_MENU_ITEM);
|
||||||
isFullscreenMirror = Menu.isOptionChecked(FULLSCREEN_MIRROR_MENU_ITEM);
|
isFullscreenMirror = Menu.isOptionChecked(FULLSCREEN_MIRROR_MENU_ITEM);
|
||||||
|
|
||||||
if (viewportHeight !== oldViewportHeight || isMirrorDisplay !== oldIsMirrorDisplay || isFullscreenMirror !== oldIsFullscreenMirror) {
|
if (viewport.y !== oldViewport.y || isMirrorDisplay !== oldIsMirrorDisplay
|
||||||
|
|| isFullscreenMirror !== oldIsFullscreenMirror) {
|
||||||
calculateWindowHeight();
|
calculateWindowHeight();
|
||||||
updateUsersDisplay();
|
updateUsersDisplay();
|
||||||
updateOverlayPositions();
|
}
|
||||||
|
|
||||||
|
if (viewport.y !== oldViewport.y) {
|
||||||
|
if (windowPosition.y > oldViewport.y / 2) {
|
||||||
|
// Maintain position w.r.t. bottom of window.
|
||||||
|
windowPosition.y = viewport.y - (oldViewport.y - windowPosition.y);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (viewport.x !== oldViewport.x) {
|
||||||
|
if (windowPosition.x + (WINDOW_WIDTH / 2) > oldViewport.x / 2) {
|
||||||
|
// Maintain position w.r.t. right of window.
|
||||||
|
windowPosition.x = viewport.x - (oldViewport.x - windowPosition.x);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
updateOverlayPositions();
|
||||||
|
}
|
||||||
|
|
||||||
function setUp() {
|
function setUp() {
|
||||||
var textSizeOverlay;
|
var textSizeOverlay,
|
||||||
|
offsetSetting,
|
||||||
|
offset = {},
|
||||||
|
hmdViewport;
|
||||||
|
|
||||||
textSizeOverlay = Overlays.addOverlay("text", {
|
textSizeOverlay = Overlays.addOverlay("text", {
|
||||||
font: WINDOW_FONT,
|
font: WINDOW_FONT,
|
||||||
|
@ -792,13 +916,40 @@ var usersWindow = (function() {
|
||||||
windowLineHeight = windowTextHeight + windowLineSpacing;
|
windowLineHeight = windowTextHeight + windowLineSpacing;
|
||||||
Overlays.deleteOverlay(textSizeOverlay);
|
Overlays.deleteOverlay(textSizeOverlay);
|
||||||
|
|
||||||
viewportHeight = Controller.getViewportDimensions().y;
|
viewport = Controller.getViewportDimensions();
|
||||||
|
|
||||||
|
offsetSetting = Settings.getValue(SETINGS_USERS_WINDOW_OFFSET);
|
||||||
|
if (offsetSetting !== "") {
|
||||||
|
offset = JSON.parse(Settings.getValue(SETINGS_USERS_WINDOW_OFFSET));
|
||||||
|
}
|
||||||
|
if (offset.hasOwnProperty("x") && offset.hasOwnProperty("y")) {
|
||||||
|
windowPosition.x = offset.x < 0 ? viewport.x + offset.x : offset.x;
|
||||||
|
windowPosition.y = offset.y <= 0 ? viewport.y + offset.y : offset.y;
|
||||||
|
|
||||||
|
} else {
|
||||||
|
hmdViewport = Controller.getRecommendedOverlayRect();
|
||||||
|
windowPosition = {
|
||||||
|
x: (viewport.x - hmdViewport.width) / 2, // HMD viewport is narrower than screen.
|
||||||
|
y: hmdViewport.height // HMD viewport starts at top of screen but only extends down so far.
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
calculateWindowHeight();
|
calculateWindowHeight();
|
||||||
|
|
||||||
|
windowBorder = Overlays.addOverlay("rectangle", {
|
||||||
|
x: 0,
|
||||||
|
y: viewport.y, // Start up off-screen
|
||||||
|
width: WINDOW_BORDER_WIDTH,
|
||||||
|
height: windowBorderHeight,
|
||||||
|
radius: WINDOW_BORDER_RADIUS,
|
||||||
|
color: WINDOW_BORDER_COLOR,
|
||||||
|
alpha: WINDOW_BORDER_ALPHA,
|
||||||
|
visible: isVisible && isBorderVisible
|
||||||
|
});
|
||||||
|
|
||||||
windowPane = Overlays.addOverlay("text", {
|
windowPane = Overlays.addOverlay("text", {
|
||||||
x: 0,
|
x: 0,
|
||||||
y: viewportHeight, // Start up off-screen
|
y: viewport.y,
|
||||||
width: WINDOW_WIDTH,
|
width: WINDOW_WIDTH,
|
||||||
height: windowHeight,
|
height: windowHeight,
|
||||||
topMargin: WINDOW_MARGIN + windowLineHeight,
|
topMargin: WINDOW_MARGIN + windowLineHeight,
|
||||||
|
@ -813,8 +964,8 @@ var usersWindow = (function() {
|
||||||
});
|
});
|
||||||
|
|
||||||
windowHeading = Overlays.addOverlay("text", {
|
windowHeading = Overlays.addOverlay("text", {
|
||||||
x: WINDOW_MARGIN,
|
x: 0,
|
||||||
y: viewportHeight,
|
y: viewport.y,
|
||||||
width: WINDOW_WIDTH - 2 * WINDOW_MARGIN,
|
width: WINDOW_WIDTH - 2 * WINDOW_MARGIN,
|
||||||
height: windowTextHeight,
|
height: windowTextHeight,
|
||||||
topMargin: 0,
|
topMargin: 0,
|
||||||
|
@ -828,8 +979,8 @@ var usersWindow = (function() {
|
||||||
});
|
});
|
||||||
|
|
||||||
minimizeButton = Overlays.addOverlay("image", {
|
minimizeButton = Overlays.addOverlay("image", {
|
||||||
x: WINDOW_WIDTH - WINDOW_MARGIN / 2 - MIN_MAX_BUTTON_WIDTH,
|
x: 0,
|
||||||
y: viewportHeight,
|
y: viewport.y,
|
||||||
width: MIN_MAX_BUTTON_WIDTH,
|
width: MIN_MAX_BUTTON_WIDTH,
|
||||||
height: MIN_MAX_BUTTON_HEIGHT,
|
height: MIN_MAX_BUTTON_HEIGHT,
|
||||||
imageURL: MIN_MAX_BUTTON_SVG,
|
imageURL: MIN_MAX_BUTTON_SVG,
|
||||||
|
@ -845,11 +996,11 @@ var usersWindow = (function() {
|
||||||
});
|
});
|
||||||
|
|
||||||
scrollbarBackgroundPosition = {
|
scrollbarBackgroundPosition = {
|
||||||
x: WINDOW_WIDTH - 0.5 * WINDOW_MARGIN - SCROLLBAR_BACKGROUND_WIDTH,
|
x: 0,
|
||||||
y: viewportHeight
|
y: viewport.y
|
||||||
};
|
};
|
||||||
scrollbarBackground = Overlays.addOverlay("text", {
|
scrollbarBackground = Overlays.addOverlay("text", {
|
||||||
x: scrollbarBackgroundPosition.x,
|
x: 0,
|
||||||
y: scrollbarBackgroundPosition.y,
|
y: scrollbarBackgroundPosition.y,
|
||||||
width: SCROLLBAR_BACKGROUND_WIDTH,
|
width: SCROLLBAR_BACKGROUND_WIDTH,
|
||||||
height: windowTextHeight,
|
height: windowTextHeight,
|
||||||
|
@ -860,11 +1011,11 @@ var usersWindow = (function() {
|
||||||
});
|
});
|
||||||
|
|
||||||
scrollbarBarPosition = {
|
scrollbarBarPosition = {
|
||||||
x: WINDOW_WIDTH - 0.5 * WINDOW_MARGIN - SCROLLBAR_BACKGROUND_WIDTH + 1,
|
x: 0,
|
||||||
y: viewportHeight
|
y: viewport.y
|
||||||
};
|
};
|
||||||
scrollbarBar = Overlays.addOverlay("text", {
|
scrollbarBar = Overlays.addOverlay("text", {
|
||||||
x: scrollbarBarPosition.x,
|
x: 0,
|
||||||
y: scrollbarBarPosition.y,
|
y: scrollbarBarPosition.y,
|
||||||
width: SCROLLBAR_BACKGROUND_WIDTH - 2,
|
width: SCROLLBAR_BACKGROUND_WIDTH - 2,
|
||||||
height: windowTextHeight,
|
height: windowTextHeight,
|
||||||
|
@ -875,8 +1026,8 @@ var usersWindow = (function() {
|
||||||
});
|
});
|
||||||
|
|
||||||
friendsButton = Overlays.addOverlay("image", {
|
friendsButton = Overlays.addOverlay("image", {
|
||||||
x: WINDOW_MARGIN,
|
x: 0,
|
||||||
y: viewportHeight,
|
y: viewport.y,
|
||||||
width: FRIENDS_BUTTON_WIDTH,
|
width: FRIENDS_BUTTON_WIDTH,
|
||||||
height: FRIENDS_BUTTON_HEIGHT,
|
height: FRIENDS_BUTTON_HEIGHT,
|
||||||
imageURL: FRIENDS_BUTTON_SVG,
|
imageURL: FRIENDS_BUTTON_SVG,
|
||||||
|
@ -895,8 +1046,8 @@ var usersWindow = (function() {
|
||||||
value: DISPLAY_VALUES[0],
|
value: DISPLAY_VALUES[0],
|
||||||
values: DISPLAY_VALUES,
|
values: DISPLAY_VALUES,
|
||||||
displayValues: DISPLAY_DISPLAY_VALUES,
|
displayValues: DISPLAY_DISPLAY_VALUES,
|
||||||
x: WINDOW_MARGIN,
|
x: 0,
|
||||||
y: viewportHeight,
|
y: viewport.y,
|
||||||
width: WINDOW_WIDTH - 1.5 * WINDOW_MARGIN,
|
width: WINDOW_WIDTH - 1.5 * WINDOW_MARGIN,
|
||||||
promptWidth: DISPLAY_PROMPT_WIDTH,
|
promptWidth: DISPLAY_PROMPT_WIDTH,
|
||||||
lineHeight: windowLineHeight,
|
lineHeight: windowLineHeight,
|
||||||
|
@ -928,8 +1079,8 @@ var usersWindow = (function() {
|
||||||
value: myVisibility,
|
value: myVisibility,
|
||||||
values: VISIBILITY_VALUES,
|
values: VISIBILITY_VALUES,
|
||||||
displayValues: VISIBILITY_DISPLAY_VALUES,
|
displayValues: VISIBILITY_DISPLAY_VALUES,
|
||||||
x: WINDOW_MARGIN,
|
x: 0,
|
||||||
y: viewportHeight,
|
y: viewport.y,
|
||||||
width: WINDOW_WIDTH - 1.5 * WINDOW_MARGIN,
|
width: WINDOW_WIDTH - 1.5 * WINDOW_MARGIN,
|
||||||
promptWidth: VISIBILITY_PROMPT_WIDTH,
|
promptWidth: VISIBILITY_PROMPT_WIDTH,
|
||||||
lineHeight: windowLineHeight,
|
lineHeight: windowLineHeight,
|
||||||
|
@ -979,6 +1130,7 @@ var usersWindow = (function() {
|
||||||
Menu.removeMenuItem(MENU_NAME, MENU_ITEM);
|
Menu.removeMenuItem(MENU_NAME, MENU_ITEM);
|
||||||
|
|
||||||
Script.clearTimeout(usersTimer);
|
Script.clearTimeout(usersTimer);
|
||||||
|
Overlays.deleteOverlay(windowBorder);
|
||||||
Overlays.deleteOverlay(windowPane);
|
Overlays.deleteOverlay(windowPane);
|
||||||
Overlays.deleteOverlay(windowHeading);
|
Overlays.deleteOverlay(windowHeading);
|
||||||
Overlays.deleteOverlay(minimizeButton);
|
Overlays.deleteOverlay(minimizeButton);
|
||||||
|
|
Loading…
Reference in a new issue