Merge branch 'master' into 21055

Conflicts:
	libraries/gl/src/gl/OffscreenQmlSurface.cpp
	libraries/gl/src/gl/OffscreenQmlSurface.h
This commit is contained in:
David Rowe 2016-10-07 14:44:23 +13:00
commit a1458a26fd
22 changed files with 792 additions and 516 deletions

View file

@ -61,6 +61,7 @@ Agent::Agent(ReceivedMessage& message) :
DependencyManager::set<recording::Deck>(); DependencyManager::set<recording::Deck>();
DependencyManager::set<recording::Recorder>(); DependencyManager::set<recording::Recorder>();
DependencyManager::set<RecordingScriptingInterface>(); DependencyManager::set<RecordingScriptingInterface>();
DependencyManager::set<ScriptCache>();
auto& packetReceiver = DependencyManager::get<NodeList>()->getPacketReceiver(); auto& packetReceiver = DependencyManager::get<NodeList>()->getPacketReceiver();

View file

@ -32,11 +32,13 @@
#include <AbstractUriHandler.h> #include <AbstractUriHandler.h>
#include <AccountManager.h> #include <AccountManager.h>
#include <NetworkAccessManager.h> #include <NetworkAccessManager.h>
#include <GLMHelpers.h>
#include "OffscreenGLCanvas.h" #include "OffscreenGLCanvas.h"
#include "GLHelpers.h" #include "GLHelpers.h"
#include "GLLogging.h" #include "GLLogging.h"
#include "TextureRecycler.h"
#include "Context.h"
QString fixupHifiUrl(const QString& urlString) { QString fixupHifiUrl(const QString& urlString) {
static const QString ACCESS_TOKEN_PARAMETER = "access_token"; static const QString ACCESS_TOKEN_PARAMETER = "access_token";
@ -114,257 +116,8 @@ QNetworkAccessManager* QmlNetworkAccessManagerFactory::create(QObject* parent) {
Q_DECLARE_LOGGING_CATEGORY(offscreenFocus) Q_DECLARE_LOGGING_CATEGORY(offscreenFocus)
Q_LOGGING_CATEGORY(offscreenFocus, "hifi.offscreen.focus") Q_LOGGING_CATEGORY(offscreenFocus, "hifi.offscreen.focus")
static const QEvent::Type INIT = QEvent::Type(QEvent::User + 1); void OffscreenQmlSurface::setupFbo() {
static const QEvent::Type RENDER = QEvent::Type(QEvent::User + 2); _canvas->makeCurrent();
static const QEvent::Type RESIZE = QEvent::Type(QEvent::User + 3);
static const QEvent::Type STOP = QEvent::Type(QEvent::User + 4);
class RawTextureRecycler {
public:
using TexturePtr = GLuint;
RawTextureRecycler(bool useMipmaps) : _useMipmaps(useMipmaps) {}
void setSize(const uvec2& size);
void clear();
TexturePtr getNextTexture();
void recycleTexture(GLuint texture);
private:
struct TexInfo {
TexturePtr _tex { 0 };
uvec2 _size;
bool _active { false };
TexInfo() {}
TexInfo(TexturePtr tex, const uvec2& size) : _tex(tex), _size(size) {}
};
using Map = std::map<GLuint, TexInfo>;
using Queue = std::queue<TexturePtr>;
Map _allTextures;
Queue _readyTextures;
uvec2 _size { 1920, 1080 };
bool _useMipmaps;
};
void RawTextureRecycler::setSize(const uvec2& size) {
if (size == _size) {
return;
}
_size = size;
while (!_readyTextures.empty()) {
_readyTextures.pop();
}
std::set<Map::key_type> toDelete;
std::for_each(_allTextures.begin(), _allTextures.end(), [&](Map::const_reference item) {
if (!item.second._active && item.second._size != _size) {
toDelete.insert(item.first);
}
});
std::for_each(toDelete.begin(), toDelete.end(), [&](Map::key_type key) {
_allTextures.erase(key);
});
}
void RawTextureRecycler::clear() {
while (!_readyTextures.empty()) {
_readyTextures.pop();
}
_allTextures.clear();
}
RawTextureRecycler::TexturePtr RawTextureRecycler::getNextTexture() {
if (_readyTextures.empty()) {
TexturePtr newTexture;
glGenTextures(1, &newTexture);
glBindTexture(GL_TEXTURE_2D, newTexture);
if (_useMipmaps) {
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
} else {
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
}
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, 8.0f);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_LOD_BIAS, -0.2f);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, 8.0f);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, _size.x, _size.y, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0);
_allTextures[newTexture] = TexInfo { newTexture, _size };
_readyTextures.push(newTexture);
}
TexturePtr result = _readyTextures.front();
_readyTextures.pop();
auto& item = _allTextures[result];
item._active = true;
return result;
}
void RawTextureRecycler::recycleTexture(GLuint texture) {
Q_ASSERT(_allTextures.count(texture));
auto& item = _allTextures[texture];
Q_ASSERT(item._active);
item._active = false;
if (item._size != _size) {
// Buh-bye
_allTextures.erase(texture);
return;
}
_readyTextures.push(item._tex);
}
class OffscreenQmlRenderThread : public QThread {
public:
OffscreenQmlRenderThread(OffscreenQmlSurface* surface, QOpenGLContext* shareContext);
virtual ~OffscreenQmlRenderThread() = default;
virtual void run() override;
virtual bool event(QEvent *e) override;
protected:
class Queue : private QQueue<QEvent*> {
public:
void add(QEvent::Type type);
QEvent* take();
private:
QMutex _mutex;
QWaitCondition _waitCondition;
bool _isWaiting{ false };
};
friend class OffscreenQmlSurface;
QJsonObject getGLContextData();
Queue _queue;
QMutex _mutex;
QWaitCondition _waitCondition;
std::atomic<bool> _rendering { false };
QJsonObject _glData;
QMutex _glMutex;
QWaitCondition _glWait;
private:
// Event-driven methods
void init();
void render();
void resize();
void cleanup();
// Helper methods
void setupFbo();
bool allowNewFrame(uint8_t fps);
bool fetchTexture(OffscreenQmlSurface::TextureAndFence& textureAndFence);
void releaseTexture(const OffscreenQmlSurface::TextureAndFence& textureAndFence);
// Texture management
std::mutex _textureMutex;
GLuint _latestTexture { 0 };
GLsync _latestTextureFence { 0 };
std::list<OffscreenQmlSurface::TextureAndFence> _returnedTextures;
// Rendering members
OffscreenGLCanvas _canvas;
OffscreenQmlSurface* _surface{ nullptr };
QQuickWindow* _quickWindow{ nullptr };
QMyQuickRenderControl* _renderControl{ nullptr };
GLuint _fbo { 0 };
GLuint _depthStencil { 0 };
RawTextureRecycler _textures { true };
uint64_t _lastRenderTime{ 0 };
uvec2 _size{ 1920, 1080 };
QSize _newSize;
bool _quit{ false };
};
void OffscreenQmlRenderThread::Queue::add(QEvent::Type type) {
QMutexLocker locker(&_mutex);
enqueue(new QEvent(type));
if (_isWaiting) {
_waitCondition.wakeOne();
}
}
QEvent* OffscreenQmlRenderThread::Queue::take() {
QMutexLocker locker(&_mutex);
while (isEmpty()) {
_isWaiting = true;
_waitCondition.wait(&_mutex);
_isWaiting = false;
}
QEvent* e = dequeue();
return e;
}
OffscreenQmlRenderThread::OffscreenQmlRenderThread(OffscreenQmlSurface* surface, QOpenGLContext* shareContext) : _surface(surface) {
_canvas.setObjectName("OffscreenQmlRenderCanvas");
qCDebug(glLogging) << "Building QML Renderer";
if (!_canvas.create(shareContext)) {
qWarning("Failed to create OffscreenGLCanvas");
_quit = true;
return;
};
_renderControl = new QMyQuickRenderControl();
QQuickWindow::setDefaultAlphaBuffer(true);
// Create a QQuickWindow that is associated with our render control.
// This window never gets created or shown, meaning that it will never get an underlying native (platform) window.
// NOTE: Must be created on the main thread so that OffscreenQmlSurface can send it events
// NOTE: Must be created on the rendering thread or it will refuse to render,
// so we wait until after its ctor to move object/context to this thread.
_quickWindow = new QQuickWindow(_renderControl);
_quickWindow->setColor(QColor(255, 255, 255, 0));
_quickWindow->setFlags(_quickWindow->flags() | static_cast<Qt::WindowFlags>(Qt::WA_TranslucentBackground));
// We can prepare, but we must wait to start() the thread until after the ctor
_renderControl->prepareThread(this);
_canvas.getContextObject()->moveToThread(this);
moveToThread(this);
_queue.add(INIT);
}
void OffscreenQmlRenderThread::run() {
qCDebug(glLogging) << "Starting QML Renderer thread";
while (!_quit) {
QEvent* e = _queue.take();
event(e);
delete e;
}
}
bool OffscreenQmlRenderThread::event(QEvent *e) {
switch (int(e->type())) {
case INIT:
init();
return true;
case RENDER:
render();
return true;
case RESIZE:
resize();
return true;
case STOP:
cleanup();
return true;
default:
return QObject::event(e);
}
}
void OffscreenQmlRenderThread::setupFbo() {
_textures.setSize(_size); _textures.setSize(_size);
if (_depthStencil) { if (_depthStencil) {
glDeleteRenderbuffers(1, &_depthStencil); glDeleteRenderbuffers(1, &_depthStencil);
@ -382,41 +135,12 @@ void OffscreenQmlRenderThread::setupFbo() {
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, _fbo); glBindFramebuffer(GL_DRAW_FRAMEBUFFER, _fbo);
glFramebufferRenderbuffer(GL_DRAW_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, _depthStencil); glFramebufferRenderbuffer(GL_DRAW_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, _depthStencil);
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0); glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
_canvas->doneCurrent();
} }
QJsonObject OffscreenQmlRenderThread::getGLContextData() { void OffscreenQmlSurface::cleanup() {
_glMutex.lock(); _canvas->makeCurrent();
if (_glData.isEmpty()) {
_glWait.wait(&_glMutex);
}
_glMutex.unlock();
return _glData;
}
void OffscreenQmlRenderThread::init() {
qCDebug(glLogging) << "Initializing QML Renderer";
if (!_canvas.makeCurrent()) {
qWarning("Failed to make context current on QML Renderer Thread");
_quit = true;
return;
}
_glMutex.lock();
_glData = ::getGLContextData();
_glMutex.unlock();
_glWait.wakeAll();
connect(_renderControl, &QQuickRenderControl::renderRequested, _surface, &OffscreenQmlSurface::requestRender);
connect(_renderControl, &QQuickRenderControl::sceneChanged, _surface, &OffscreenQmlSurface::requestUpdate);
_renderControl->initialize(_canvas.getContext());
setupFbo();
}
void OffscreenQmlRenderThread::cleanup() {
_renderControl->invalidate(); _renderControl->invalidate();
if (_depthStencil) { if (_depthStencil) {
glDeleteRenderbuffers(1, &_depthStencil); glDeleteRenderbuffers(1, &_depthStencil);
_depthStencil = 0; _depthStencil = 0;
@ -427,65 +151,17 @@ void OffscreenQmlRenderThread::cleanup() {
} }
_textures.clear(); _textures.clear();
_canvas->doneCurrent();
_canvas.doneCurrent();
_canvas.getContextObject()->moveToThread(QCoreApplication::instance()->thread());
_quit = true;
} }
void OffscreenQmlRenderThread::resize() { void OffscreenQmlSurface::render() {
// Lock _newSize changes if (_paused) {
{
QMutexLocker locker(&_mutex);
// Update our members
if (_quickWindow) {
_quickWindow->setGeometry(QRect(QPoint(), _newSize));
_quickWindow->contentItem()->setSize(_newSize);
}
// Qt bug in 5.4 forces this check of pixel ratio,
// even though we're rendering offscreen.
qreal pixelRatio = 1.0;
if (_renderControl && _renderControl->_renderWindow) {
pixelRatio = _renderControl->_renderWindow->devicePixelRatio();
}
uvec2 newOffscreenSize = toGlm(_newSize * pixelRatio);
if (newOffscreenSize == _size) {
return;
}
qCDebug(glLogging) << "Offscreen UI resizing to " << _newSize.width() << "x" << _newSize.height() << " with pixel ratio " << pixelRatio;
_size = newOffscreenSize;
}
_textures.setSize(_size);
setupFbo();
}
void OffscreenQmlRenderThread::render() {
// Ensure we always release the main thread
Finally releaseMainThread([this] {
_waitCondition.wakeOne();
});
if (_surface->_paused) {
return; return;
} }
_rendering = true; _canvas->makeCurrent();
Finally unmarkRenderingFlag([this] {
_rendering = false;
});
{
QMutexLocker locker(&_mutex);
_renderControl->sync();
releaseMainThread.trigger();
}
_renderControl->sync();
_quickWindow->setRenderTarget(_fbo, QSize(_size.x, _size.y)); _quickWindow->setRenderTarget(_fbo, QSize(_size.x, _size.y));
// Clear out any pending textures to be returned // Clear out any pending textures to be returned
@ -507,70 +183,61 @@ void OffscreenQmlRenderThread::render() {
} }
} }
try { GLuint texture = _textures.getNextTexture();
GLuint texture = _textures.getNextTexture(); glBindFramebuffer(GL_DRAW_FRAMEBUFFER, _fbo);
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, _fbo); glFramebufferTexture(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, texture, 0);
glFramebufferTexture(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, texture, 0); PROFILE_RANGE("qml_render->rendercontrol")
PROFILE_RANGE("qml_render->rendercontrol") _renderControl->render();
_renderControl->render(); glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0); glBindTexture(GL_TEXTURE_2D, texture);
glBindTexture(GL_TEXTURE_2D, texture); glGenerateMipmap(GL_TEXTURE_2D);
glGenerateMipmap(GL_TEXTURE_2D); glBindTexture(GL_TEXTURE_2D, 0);
glBindTexture(GL_TEXTURE_2D, 0);
{ {
std::unique_lock<std::mutex> lock(_textureMutex); std::unique_lock<std::mutex> lock(_textureMutex);
// If the most recent texture was unused, we can directly recycle it // If the most recent texture was unused, we can directly recycle it
if (_latestTextureFence) { if (_latestTextureAndFence.first) {
} _textures.recycleTexture(_latestTextureAndFence.first);
if (_latestTexture) { glDeleteSync(static_cast<GLsync>(_latestTextureAndFence.second));
_textures.recycleTexture(_latestTexture); _latestTextureAndFence = { 0, 0 };
glDeleteSync(_latestTextureFence);
_latestTexture = 0;
_latestTextureFence = 0;
}
_latestTextureFence = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0);
_latestTexture = texture;
// Fence will be used in another thread / context, so a flush is required
glFlush();
} }
_quickWindow->resetOpenGLState(); _latestTextureAndFence = { texture, glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0) };
_lastRenderTime = usecTimestampNow(); // Fence will be used in another thread / context, so a flush is required
} catch (std::runtime_error& error) { glFlush();
qWarning() << "Failed to render QML: " << error.what();
} }
_quickWindow->resetOpenGLState();
_lastRenderTime = usecTimestampNow();
_canvas->doneCurrent();
} }
bool OffscreenQmlRenderThread::fetchTexture(OffscreenQmlSurface::TextureAndFence& textureAndFence) { bool OffscreenQmlSurface::fetchTexture(TextureAndFence& textureAndFence) {
textureAndFence = { 0, 0 }; textureAndFence = { 0, 0 };
std::unique_lock<std::mutex> lock(_textureMutex); std::unique_lock<std::mutex> lock(_textureMutex);
if (0 == _latestTexture) { if (0 == _latestTextureAndFence.first) {
return false; return false;
} }
// Ensure writes to the latest texture are complete before before returning it for reading // Ensure writes to the latest texture are complete before before returning it for reading
Q_ASSERT(0 != _latestTextureFence); textureAndFence = _latestTextureAndFence;
textureAndFence = { _latestTexture, _latestTextureFence }; _latestTextureAndFence = { 0, 0 };
_latestTextureFence = 0;
_latestTexture = 0;
return true; return true;
} }
void OffscreenQmlRenderThread::releaseTexture(const OffscreenQmlSurface::TextureAndFence& textureAndFence) { void OffscreenQmlSurface::releaseTexture(const TextureAndFence& textureAndFence) {
std::unique_lock<std::mutex> lock(_textureMutex); std::unique_lock<std::mutex> lock(_textureMutex);
_returnedTextures.push_back(textureAndFence); _returnedTextures.push_back(textureAndFence);
} }
bool OffscreenQmlRenderThread::allowNewFrame(uint8_t fps) { bool OffscreenQmlSurface::allowNewFrame(uint8_t fps) {
// If we already have a pending texture, don't render another one // If we already have a pending texture, don't render another one
// i.e. don't render faster than the consumer context, since it wastes // i.e. don't render faster than the consumer context, since it wastes
// GPU cycles on producing output that will never be seen // GPU cycles on producing output that will never be seen
{ {
std::unique_lock<std::mutex> lock(_textureMutex); std::unique_lock<std::mutex> lock(_textureMutex);
if (0 != _latestTexture) { if (0 != _latestTextureAndFence.first) {
return false; return false;
} }
} }
@ -588,33 +255,46 @@ OffscreenQmlSurface::~OffscreenQmlSurface() {
QObject::disconnect(&_updateTimer); QObject::disconnect(&_updateTimer);
QObject::disconnect(qApp); QObject::disconnect(qApp);
qCDebug(glLogging) << "Stopping QML Renderer Thread " << _renderer->currentThreadId();
_renderer->_queue.add(STOP);
if (!_renderer->wait(MAX_SHUTDOWN_WAIT_SECS * USECS_PER_SECOND)) {
qWarning() << "Failed to shut down the QML Renderer Thread";
}
delete _rootItem; cleanup();
delete _renderer;
delete _qmlComponent; _canvas->deleteLater();
delete _qmlEngine; _rootItem->deleteLater();
_qmlComponent->deleteLater();
_qmlEngine->deleteLater();
_quickWindow->deleteLater();
} }
void OffscreenQmlSurface::onAboutToQuit() { void OffscreenQmlSurface::onAboutToQuit() {
_paused = true;
QObject::disconnect(&_updateTimer); QObject::disconnect(&_updateTimer);
} }
void OffscreenQmlSurface::create(QOpenGLContext* shareContext) { void OffscreenQmlSurface::create(QOpenGLContext* shareContext) {
qCDebug(glLogging) << "Building QML surface"; qCDebug(glLogging) << "Building QML surface";
_renderer = new OffscreenQmlRenderThread(this, shareContext); _renderControl = new QMyQuickRenderControl();
_renderer->moveToThread(_renderer);
_renderer->setObjectName("QML Renderer Thread");
_renderer->start();
_renderer->_renderControl->_renderWindow = _proxyWindow; QQuickWindow::setDefaultAlphaBuffer(true);
connect(_renderer->_quickWindow, &QQuickWindow::focusObjectChanged, this, &OffscreenQmlSurface::onFocusObjectChanged); // Create a QQuickWindow that is associated with our render control.
// This window never gets created or shown, meaning that it will never get an underlying native (platform) window.
// NOTE: Must be created on the main thread so that OffscreenQmlSurface can send it events
// NOTE: Must be created on the rendering thread or it will refuse to render,
// so we wait until after its ctor to move object/context to this thread.
_quickWindow = new QQuickWindow(_renderControl);
_quickWindow->setColor(QColor(255, 255, 255, 0));
_quickWindow->setFlags(_quickWindow->flags() | static_cast<Qt::WindowFlags>(Qt::WA_TranslucentBackground));
_renderControl->_renderWindow = _proxyWindow;
_canvas = new OffscreenGLCanvas();
if (!_canvas->create(shareContext)) {
qFatal("Failed to create OffscreenGLCanvas");
return;
};
connect(_quickWindow, &QQuickWindow::focusObjectChanged, this, &OffscreenQmlSurface::onFocusObjectChanged);
// Create a QML engine. // Create a QML engine.
_qmlEngine = new QQmlEngine; _qmlEngine = new QQmlEngine;
@ -625,13 +305,26 @@ void OffscreenQmlSurface::create(QOpenGLContext* shareContext) {
importList.insert(importList.begin(), PathUtils::resourcesPath()); importList.insert(importList.begin(), PathUtils::resourcesPath());
_qmlEngine->setImportPathList(importList); _qmlEngine->setImportPathList(importList);
if (!_qmlEngine->incubationController()) { if (!_qmlEngine->incubationController()) {
_qmlEngine->setIncubationController(_renderer->_quickWindow->incubationController()); _qmlEngine->setIncubationController(_quickWindow->incubationController());
} }
_qmlEngine->rootContext()->setContextProperty("GL", _renderer->getGLContextData()); // FIXME
_qmlEngine->rootContext()->setContextProperty("GL", _glData);
_qmlEngine->rootContext()->setContextProperty("offscreenWindow", QVariant::fromValue(getWindow())); _qmlEngine->rootContext()->setContextProperty("offscreenWindow", QVariant::fromValue(getWindow()));
_qmlComponent = new QQmlComponent(_qmlEngine); _qmlComponent = new QQmlComponent(_qmlEngine);
connect(_renderControl, &QQuickRenderControl::renderRequested, [this] { _render = true; });
connect(_renderControl, &QQuickRenderControl::sceneChanged, [this] { _render = _polish = true; });
if (!_canvas->makeCurrent()) {
qWarning("Failed to make context current for QML Renderer");
return;
}
_glData = ::getGLContextData();
_renderControl->initialize(_canvas->getContext());
setupFbo();
// When Quick says there is a need to render, we will not render immediately. Instead, // When Quick says there is a need to render, we will not render immediately. Instead,
// a timer with a small interval is used to get better performance. // a timer with a small interval is used to get better performance.
QObject::connect(&_updateTimer, &QTimer::timeout, this, &OffscreenQmlSurface::updateQuick); QObject::connect(&_updateTimer, &QTimer::timeout, this, &OffscreenQmlSurface::updateQuick);
@ -646,7 +339,7 @@ void OffscreenQmlSurface::create(QOpenGLContext* shareContext) {
void OffscreenQmlSurface::resize(const QSize& newSize_, bool forceResize) { void OffscreenQmlSurface::resize(const QSize& newSize_, bool forceResize) {
if (!_renderer || !_renderer->_quickWindow) { if (!_quickWindow) {
return; return;
} }
@ -662,7 +355,7 @@ void OffscreenQmlSurface::resize(const QSize& newSize_, bool forceResize) {
std::max(static_cast<int>(scale * newSize.height()), 10)); std::max(static_cast<int>(scale * newSize.height()), 10));
} }
QSize currentSize = _renderer->_quickWindow->geometry().size(); QSize currentSize = _quickWindow->geometry().size();
if (newSize == currentSize && !forceResize) { if (newSize == currentSize && !forceResize) {
return; return;
} }
@ -673,12 +366,26 @@ void OffscreenQmlSurface::resize(const QSize& newSize_, bool forceResize) {
_rootItem->setSize(newSize); _rootItem->setSize(newSize);
} }
{ // Update our members
QMutexLocker locker(&(_renderer->_mutex)); _quickWindow->setGeometry(QRect(QPoint(), newSize));
_renderer->_newSize = newSize; _quickWindow->contentItem()->setSize(newSize);
// Qt bug in 5.4 forces this check of pixel ratio,
// even though we're rendering offscreen.
qreal pixelRatio = 1.0;
if (_renderControl && _renderControl->_renderWindow) {
pixelRatio = _renderControl->_renderWindow->devicePixelRatio();
} }
_renderer->_queue.add(RESIZE); uvec2 newOffscreenSize = toGlm(newSize * pixelRatio);
if (newOffscreenSize == _size) {
return;
}
qCDebug(glLogging) << "Offscreen UI resizing to " << newSize.width() << "x" << newSize.height() << " with pixel ratio " << pixelRatio;
_size = newOffscreenSize;
_textures.setSize(_size);
setupFbo();
} }
QQuickItem* OffscreenQmlSurface::getRootItem() { QQuickItem* OffscreenQmlSurface::getRootItem() {
@ -710,15 +417,6 @@ void OffscreenQmlSurface::clearCache() {
getRootContext()->engine()->clearComponentCache(); getRootContext()->engine()->clearComponentCache();
} }
void OffscreenQmlSurface::requestUpdate() {
_polish = true;
_render = true;
}
void OffscreenQmlSurface::requestRender() {
_render = true;
}
QObject* OffscreenQmlSurface::finishQmlLoad(std::function<void(QQmlContext*, QObject*)> f) { QObject* OffscreenQmlSurface::finishQmlLoad(std::function<void(QQmlContext*, QObject*)> f) {
disconnect(_qmlComponent, &QQmlComponent::statusChanged, this, 0); disconnect(_qmlComponent, &QQmlComponent::statusChanged, this, 0);
if (_qmlComponent->isError()) { if (_qmlComponent->isError()) {
@ -786,8 +484,8 @@ QObject* OffscreenQmlSurface::finishQmlLoad(std::function<void(QQmlContext*, QOb
} }
// The root item is ready. Associate it with the window. // The root item is ready. Associate it with the window.
_rootItem = newItem; _rootItem = newItem;
_rootItem->setParentItem(_renderer->_quickWindow->contentItem()); _rootItem->setParentItem(_quickWindow->contentItem());
_rootItem->setSize(_renderer->_quickWindow->renderTargetSize()); _rootItem->setSize(_quickWindow->renderTargetSize());
return _rootItem; return _rootItem;
} }
@ -797,34 +495,22 @@ void OffscreenQmlSurface::updateQuick() {
// b) already rendering a frame // b) already rendering a frame
// c) rendering too fast // c) rendering too fast
// then skip this // then skip this
if (!_renderer || _renderer->_rendering || !_renderer->allowNewFrame(_maxFps)) { if (!allowNewFrame(_maxFps)) {
return; return;
} }
if (_polish) { if (_polish) {
_renderer->_renderControl->polishItems(); _renderControl->polishItems();
_polish = false; _polish = false;
} }
if (_render) { if (_render) {
PROFILE_RANGE(__FUNCTION__); PROFILE_RANGE(__FUNCTION__);
// Lock the GUI size while syncing render();
QMutexLocker locker(&(_renderer->_mutex));
_renderer->_queue.add(RENDER);
// FIXME need to find a better way to handle the render lockout than this locking of the main thread
_renderer->_waitCondition.wait(&(_renderer->_mutex));
_render = false; _render = false;
} }
} }
bool OffscreenQmlSurface::fetchTexture(TextureAndFence& texture) {
return _renderer->fetchTexture(texture);
}
void OffscreenQmlSurface::releaseTexture(const TextureAndFence& texture) {
_renderer->releaseTexture(texture);
}
QPointF OffscreenQmlSurface::mapWindowToUi(const QPointF& sourcePosition, QObject* sourceObject) { QPointF OffscreenQmlSurface::mapWindowToUi(const QPointF& sourcePosition, QObject* sourceObject) {
vec2 sourceSize; vec2 sourceSize;
if (dynamic_cast<QWidget*>(sourceObject)) { if (dynamic_cast<QWidget*>(sourceObject)) {
@ -834,7 +520,7 @@ QPointF OffscreenQmlSurface::mapWindowToUi(const QPointF& sourcePosition, QObjec
} }
vec2 offscreenPosition = toGlm(sourcePosition); vec2 offscreenPosition = toGlm(sourcePosition);
offscreenPosition /= sourceSize; offscreenPosition /= sourceSize;
offscreenPosition *= vec2(toGlm(_renderer->_quickWindow->size())); offscreenPosition *= vec2(toGlm(_quickWindow->size()));
return QPointF(offscreenPosition.x, offscreenPosition.y); return QPointF(offscreenPosition.x, offscreenPosition.y);
} }
@ -848,7 +534,7 @@ QPointF OffscreenQmlSurface::mapToVirtualScreen(const QPointF& originalPoint, QO
// //
bool OffscreenQmlSurface::filterEnabled(QObject* originalDestination, QEvent* event) const { bool OffscreenQmlSurface::filterEnabled(QObject* originalDestination, QEvent* event) const {
if (_renderer->_quickWindow == originalDestination) { if (_quickWindow == originalDestination) {
return false; return false;
} }
// Only intercept events while we're in an active state // Only intercept events while we're in an active state
@ -866,7 +552,7 @@ bool OffscreenQmlSurface::eventFilter(QObject* originalDestination, QEvent* even
// Don't intercept our own events, or we enter an infinite recursion // Don't intercept our own events, or we enter an infinite recursion
QObject* recurseTest = originalDestination; QObject* recurseTest = originalDestination;
while (recurseTest) { while (recurseTest) {
Q_ASSERT(recurseTest != _rootItem && recurseTest != _renderer->_quickWindow); Q_ASSERT(recurseTest != _rootItem && recurseTest != _quickWindow);
recurseTest = recurseTest->parent(); recurseTest = recurseTest->parent();
} }
#endif #endif
@ -884,7 +570,7 @@ bool OffscreenQmlSurface::eventFilter(QObject* originalDestination, QEvent* even
case QEvent::KeyPress: case QEvent::KeyPress:
case QEvent::KeyRelease: { case QEvent::KeyRelease: {
event->ignore(); event->ignore();
if (QCoreApplication::sendEvent(_renderer->_quickWindow, event)) { if (QCoreApplication::sendEvent(_quickWindow, event)) {
return event->isAccepted(); return event->isAccepted();
} }
break; break;
@ -898,7 +584,7 @@ bool OffscreenQmlSurface::eventFilter(QObject* originalDestination, QEvent* even
wheelEvent->delta(), wheelEvent->buttons(), wheelEvent->delta(), wheelEvent->buttons(),
wheelEvent->modifiers(), wheelEvent->orientation()); wheelEvent->modifiers(), wheelEvent->orientation());
mappedEvent.ignore(); mappedEvent.ignore();
if (QCoreApplication::sendEvent(_renderer->_quickWindow, &mappedEvent)) { if (QCoreApplication::sendEvent(_quickWindow, &mappedEvent)) {
return mappedEvent.isAccepted(); return mappedEvent.isAccepted();
} }
break; break;
@ -919,7 +605,7 @@ bool OffscreenQmlSurface::eventFilter(QObject* originalDestination, QEvent* even
_qmlEngine->rootContext()->setContextProperty("lastMousePosition", transformedPos); _qmlEngine->rootContext()->setContextProperty("lastMousePosition", transformedPos);
} }
mappedEvent.ignore(); mappedEvent.ignore();
if (QCoreApplication::sendEvent(_renderer->_quickWindow, &mappedEvent)) { if (QCoreApplication::sendEvent(_quickWindow, &mappedEvent)) {
return mappedEvent.isAccepted(); return mappedEvent.isAccepted();
} }
break; break;
@ -938,7 +624,7 @@ void OffscreenQmlSurface::pause() {
void OffscreenQmlSurface::resume() { void OffscreenQmlSurface::resume() {
_paused = false; _paused = false;
requestRender(); _render = true;
getRootItem()->setProperty("eventBridge", QVariant::fromValue(this)); getRootItem()->setProperty("eventBridge", QVariant::fromValue(this));
getRootContext()->setContextProperty("webEntity", this); getRootContext()->setContextProperty("webEntity", this);
@ -950,8 +636,8 @@ bool OffscreenQmlSurface::isPaused() const {
void OffscreenQmlSurface::setProxyWindow(QWindow* window) { void OffscreenQmlSurface::setProxyWindow(QWindow* window) {
_proxyWindow = window; _proxyWindow = window;
if (_renderer && _renderer->_renderControl) { if (_renderControl) {
_renderer->_renderControl->_renderWindow = window; _renderControl->_renderWindow = window;
} }
} }
@ -960,11 +646,11 @@ QObject* OffscreenQmlSurface::getEventHandler() {
} }
QQuickWindow* OffscreenQmlSurface::getWindow() { QQuickWindow* OffscreenQmlSurface::getWindow() {
return _renderer->_quickWindow; return _quickWindow;
} }
QSize OffscreenQmlSurface::size() const { QSize OffscreenQmlSurface::size() const {
return _renderer->_quickWindow->geometry().size(); return _quickWindow->geometry().size();
} }
QQmlContext* OffscreenQmlSurface::getRootContext() { QQmlContext* OffscreenQmlSurface::getRootContext() {

View file

@ -9,24 +9,27 @@
#ifndef hifi_OffscreenQmlSurface_h #ifndef hifi_OffscreenQmlSurface_h
#define hifi_OffscreenQmlSurface_h #define hifi_OffscreenQmlSurface_h
#include <QTimer>
#include <QUrl>
#include <atomic> #include <atomic>
#include <functional> #include <functional>
#include <QtCore/QJsonObject>
#include <QTimer>
#include <QUrl>
#include <GLMHelpers.h> #include <GLMHelpers.h>
#include <ThreadHelpers.h> #include <ThreadHelpers.h>
#include "TextureRecycler.h"
class QWindow; class QWindow;
class QMyQuickRenderControl; class QMyQuickRenderControl;
class OffscreenGLCanvas;
class QOpenGLContext; class QOpenGLContext;
class QQmlEngine; class QQmlEngine;
class QQmlContext; class QQmlContext;
class QQmlComponent; class QQmlComponent;
class QQuickWindow; class QQuickWindow;
class QQuickItem; class QQuickItem;
class OffscreenQmlRenderThread;
class OffscreenQmlSurface : public QObject { class OffscreenQmlSurface : public QObject {
Q_OBJECT Q_OBJECT
Q_PROPERTY(bool focusText READ isFocusText NOTIFY focusTextChanged) Q_PROPERTY(bool focusText READ isFocusText NOTIFY focusTextChanged)
@ -88,8 +91,6 @@ signals:
void focusTextChanged(bool focusText); void focusTextChanged(bool focusText);
public slots: public slots:
void requestUpdate();
void requestRender();
void onAboutToQuit(); void onAboutToQuit();
void focusDestroyed(QObject *obj); void focusDestroyed(QObject *obj);
@ -109,24 +110,44 @@ protected:
private: private:
QObject* finishQmlLoad(std::function<void(QQmlContext*, QObject*)> f); QObject* finishQmlLoad(std::function<void(QQmlContext*, QObject*)> f);
QPointF mapWindowToUi(const QPointF& sourcePosition, QObject* sourceObject); QPointF mapWindowToUi(const QPointF& sourcePosition, QObject* sourceObject);
void setupFbo();
bool allowNewFrame(uint8_t fps);
void render();
void cleanup();
QJsonObject getGLContextData();
private slots: private slots:
void updateQuick(); void updateQuick();
void onFocusObjectChanged(QObject* newFocus); void onFocusObjectChanged(QObject* newFocus);
private: private:
friend class OffscreenQmlRenderThread; QQuickWindow* _quickWindow { nullptr };
OffscreenQmlRenderThread* _renderer{ nullptr }; QMyQuickRenderControl* _renderControl{ nullptr };
QQmlEngine* _qmlEngine{ nullptr }; QQmlEngine* _qmlEngine { nullptr };
QQmlComponent* _qmlComponent{ nullptr }; QQmlComponent* _qmlComponent { nullptr };
QQuickItem* _rootItem{ nullptr }; QQuickItem* _rootItem { nullptr };
OffscreenGLCanvas* _canvas { nullptr };
QJsonObject _glData;
QTimer _updateTimer; QTimer _updateTimer;
bool _render{ false }; uint32_t _fbo { 0 };
bool _polish{ true }; uint32_t _depthStencil { 0 };
bool _paused{ true }; uint64_t _lastRenderTime { 0 };
uvec2 _size { 1920, 1080 };
TextureRecycler _textures { true };
// Texture management
std::mutex _textureMutex;
TextureAndFence _latestTextureAndFence { 0, 0 };
std::list<TextureAndFence> _returnedTextures;
bool _render { false };
bool _polish { true };
bool _paused { true };
bool _focusText { false }; bool _focusText { false };
uint8_t _maxFps{ 60 }; uint8_t _maxFps { 60 };
MouseTranslator _mouseTranslator{ [](const QPointF& p) { return p.toPoint(); } }; MouseTranslator _mouseTranslator { [](const QPointF& p) { return p.toPoint(); } };
QWindow* _proxyWindow { nullptr }; QWindow* _proxyWindow { nullptr };
QQuickItem* _currentFocusItem { nullptr }; QQuickItem* _currentFocusItem { nullptr };

View file

@ -0,0 +1,83 @@
//
// Created by Bradley Austin Davis on 2016-10-05
// Copyright 2015 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#include "TextureRecycler.h"
#include "Config.h"
#include <set>
void TextureRecycler::setSize(const uvec2& size) {
if (size == _size) {
return;
}
_size = size;
while (!_readyTextures.empty()) {
_readyTextures.pop();
}
std::set<Map::key_type> toDelete;
std::for_each(_allTextures.begin(), _allTextures.end(), [&](Map::const_reference item) {
if (!item.second._active && item.second._size != _size) {
toDelete.insert(item.first);
}
});
std::for_each(toDelete.begin(), toDelete.end(), [&](Map::key_type key) {
_allTextures.erase(key);
});
}
void TextureRecycler::clear() {
while (!_readyTextures.empty()) {
_readyTextures.pop();
}
_allTextures.clear();
}
uint32_t TextureRecycler::getNextTexture() {
if (_readyTextures.empty()) {
uint32_t newTexture;
glGenTextures(1, &newTexture);
glBindTexture(GL_TEXTURE_2D, newTexture);
if (_useMipmaps) {
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
} else {
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
}
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, 8.0f);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_LOD_BIAS, -0.2f);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, 8.0f);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, _size.x, _size.y, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0);
_allTextures.emplace(std::piecewise_construct, std::forward_as_tuple(newTexture), std::forward_as_tuple(newTexture, _size));
_readyTextures.push(newTexture);
}
uint32_t result = _readyTextures.front();
_readyTextures.pop();
auto& item = _allTextures[result];
item._active = true;
return result;
}
void TextureRecycler::recycleTexture(GLuint texture) {
Q_ASSERT(_allTextures.count(texture));
auto& item = _allTextures[texture];
Q_ASSERT(item._active);
item._active = false;
if (item._size != _size) {
// Buh-bye
_allTextures.erase(texture);
return;
}
_readyTextures.push(item._tex);
}

View file

@ -0,0 +1,47 @@
//
// Created by Bradley Austin Davis on 2015-04-04
// Copyright 2015 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#pragma once
#ifndef hifi_TextureRecycler_h
#define hifi_TextureRecycler_h
#include <atomic>
#include <queue>
#include <map>
#include <GLMHelpers.h>
class TextureRecycler {
public:
TextureRecycler(bool useMipmaps) : _useMipmaps(useMipmaps) {}
void setSize(const uvec2& size);
void clear();
uint32_t getNextTexture();
void recycleTexture(uint32_t texture);
private:
struct TexInfo {
const uint32_t _tex{ 0 };
const uvec2 _size;
bool _active { false };
TexInfo() {}
TexInfo(uint32_t tex, const uvec2& size) : _tex(tex), _size(size) {}
TexInfo(const TexInfo& other) : _tex(other._tex), _size(other._size) {}
};
using Map = std::map<uint32_t, TexInfo>;
using Queue = std::queue<uint32_t>;
Map _allTextures;
Queue _readyTextures;
uvec2 _size{ 1920, 1080 };
bool _useMipmaps;
};
#endif

View file

@ -394,12 +394,14 @@ void AssetClient::handleCompleteCallback(const QWeakPointer<Node>& node, Message
auto senderNode = node.toStrongRef(); auto senderNode = node.toStrongRef();
if (!senderNode) { if (!senderNode) {
qCWarning(asset_client) << "Got completed asset for node that no longer exists";
return; return;
} }
// Check if we have any pending requests for this node // Check if we have any pending requests for this node
auto messageMapIt = _pendingRequests.find(senderNode); auto messageMapIt = _pendingRequests.find(senderNode);
if (messageMapIt == _pendingRequests.end()) { if (messageMapIt == _pendingRequests.end()) {
qCWarning(asset_client) << "Got completed asset for a node that doesn't have any pending requests";
return; return;
} }
@ -409,6 +411,7 @@ void AssetClient::handleCompleteCallback(const QWeakPointer<Node>& node, Message
// Check if we have this pending request // Check if we have this pending request
auto requestIt = messageCallbackMap.find(messageID); auto requestIt = messageCallbackMap.find(messageID);
if (requestIt == messageCallbackMap.end()) { if (requestIt == messageCallbackMap.end()) {
qCWarning(asset_client) << "Got completed asset for a request that doesn't exist";
return; return;
} }
@ -416,6 +419,7 @@ void AssetClient::handleCompleteCallback(const QWeakPointer<Node>& node, Message
auto& message = callbacks.message; auto& message = callbacks.message;
if (!message) { if (!message) {
qCWarning(asset_client) << "Got completed asset for a message that doesn't exist";
return; return;
} }

View file

@ -107,9 +107,11 @@ void AssetRequest::start() {
auto assetClient = DependencyManager::get<AssetClient>(); auto assetClient = DependencyManager::get<AssetClient>();
auto that = QPointer<AssetRequest>(this); // Used to track the request's lifetime auto that = QPointer<AssetRequest>(this); // Used to track the request's lifetime
auto hash = _hash;
_assetRequestID = assetClient->getAsset(_hash, start, end, _assetRequestID = assetClient->getAsset(_hash, start, end,
[this, that, start, end](bool responseReceived, AssetServerError serverError, const QByteArray& data) { [this, that, hash, start, end](bool responseReceived, AssetServerError serverError, const QByteArray& data) {
if (!that) { if (!that) {
qCWarning(asset_client) << "Got reply for dead asset request " << hash << "- error code" << _error;
// If the request is dead, return // If the request is dead, return
return; return;
} }

View file

@ -49,6 +49,7 @@ public:
const State& getState() const { return _state; } const State& getState() const { return _state; }
const Error& getError() const { return _error; } const Error& getError() const { return _error; }
QUrl getUrl() const { return ::getATPUrl(_hash); } QUrl getUrl() const { return ::getATPUrl(_hash); }
QString getHash() const { return _hash; }
signals: signals:
void finished(AssetRequest* thisRequest); void finished(AssetRequest* thisRequest);

View file

@ -14,6 +14,7 @@
#include "AssetClient.h" #include "AssetClient.h"
#include "AssetUtils.h" #include "AssetUtils.h"
#include "MappingRequest.h" #include "MappingRequest.h"
#include <QtCore/qloggingcategory.h>
AssetResourceRequest::~AssetResourceRequest() { AssetResourceRequest::~AssetResourceRequest() {
if (_assetMappingRequest) { if (_assetMappingRequest) {
@ -23,6 +24,10 @@ AssetResourceRequest::~AssetResourceRequest() {
if (_assetRequest) { if (_assetRequest) {
_assetRequest->deleteLater(); _assetRequest->deleteLater();
} }
if (_sendTimer) {
cleanupTimer();
}
} }
bool AssetResourceRequest::urlIsAssetHash() const { bool AssetResourceRequest::urlIsAssetHash() const {
@ -32,6 +37,24 @@ bool AssetResourceRequest::urlIsAssetHash() const {
return hashRegex.exactMatch(_url.toString()); return hashRegex.exactMatch(_url.toString());
} }
void AssetResourceRequest::setupTimer() {
Q_ASSERT(!_sendTimer);
static const int TIMEOUT_MS = 2000;
_sendTimer = new QTimer(this);
connect(_sendTimer, &QTimer::timeout, this, &AssetResourceRequest::onTimeout);
_sendTimer->setSingleShot(true);
_sendTimer->start(TIMEOUT_MS);
}
void AssetResourceRequest::cleanupTimer() {
Q_ASSERT(_sendTimer);
disconnect(_sendTimer, 0, this, 0);
_sendTimer->deleteLater();
_sendTimer = nullptr;
}
void AssetResourceRequest::doSend() { void AssetResourceRequest::doSend() {
// We'll either have a hash or an ATP path to a file (that maps to a hash) // We'll either have a hash or an ATP path to a file (that maps to a hash)
if (urlIsAssetHash()) { if (urlIsAssetHash()) {
@ -58,6 +81,8 @@ void AssetResourceRequest::requestMappingForPath(const AssetPath& path) {
Q_ASSERT(_state == InProgress); Q_ASSERT(_state == InProgress);
Q_ASSERT(request == _assetMappingRequest); Q_ASSERT(request == _assetMappingRequest);
cleanupTimer();
switch (request->getError()) { switch (request->getError()) {
case MappingRequest::NoError: case MappingRequest::NoError:
// we have no error, we should have a resulting hash - use that to send of a request for that asset // we have no error, we should have a resulting hash - use that to send of a request for that asset
@ -93,6 +118,7 @@ void AssetResourceRequest::requestMappingForPath(const AssetPath& path) {
_assetMappingRequest = nullptr; _assetMappingRequest = nullptr;
}); });
setupTimer();
_assetMappingRequest->start(); _assetMappingRequest->start();
} }
@ -102,11 +128,13 @@ void AssetResourceRequest::requestHash(const AssetHash& hash) {
auto assetClient = DependencyManager::get<AssetClient>(); auto assetClient = DependencyManager::get<AssetClient>();
_assetRequest = assetClient->createRequest(hash); _assetRequest = assetClient->createRequest(hash);
connect(_assetRequest, &AssetRequest::progress, this, &AssetResourceRequest::progress); connect(_assetRequest, &AssetRequest::progress, this, &AssetResourceRequest::onDownloadProgress);
connect(_assetRequest, &AssetRequest::finished, this, [this](AssetRequest* req) { connect(_assetRequest, &AssetRequest::finished, this, [this](AssetRequest* req) {
Q_ASSERT(_state == InProgress); Q_ASSERT(_state == InProgress);
Q_ASSERT(req == _assetRequest); Q_ASSERT(req == _assetRequest);
Q_ASSERT(req->getState() == AssetRequest::Finished); Q_ASSERT(req->getState() == AssetRequest::Finished);
cleanupTimer();
switch (req->getError()) { switch (req->getError()) {
case AssetRequest::Error::NoError: case AssetRequest::Error::NoError:
@ -134,9 +162,35 @@ void AssetResourceRequest::requestHash(const AssetHash& hash) {
_assetRequest = nullptr; _assetRequest = nullptr;
}); });
setupTimer();
_assetRequest->start(); _assetRequest->start();
} }
void AssetResourceRequest::onDownloadProgress(qint64 bytesReceived, qint64 bytesTotal) { void AssetResourceRequest::onDownloadProgress(qint64 bytesReceived, qint64 bytesTotal) {
Q_ASSERT(_state == InProgress);
// We've received data, so reset the timer
_sendTimer->start();
emit progress(bytesReceived, bytesTotal); emit progress(bytesReceived, bytesTotal);
} }
void AssetResourceRequest::onTimeout() {
if (_state == InProgress) {
qWarning() << "Asset request timed out: " << _url;
if (_assetRequest) {
disconnect(_assetRequest, 0, this, 0);
_assetRequest->deleteLater();
_assetRequest = nullptr;
}
if (_assetMappingRequest) {
disconnect(_assetMappingRequest, 0, this, 0);
_assetMappingRequest->deleteLater();
_assetMappingRequest = nullptr;
}
_result = Timeout;
_state = Finished;
emit finished();
}
cleanupTimer();
}

View file

@ -28,13 +28,19 @@ protected:
private slots: private slots:
void onDownloadProgress(qint64 bytesReceived, qint64 bytesTotal); void onDownloadProgress(qint64 bytesReceived, qint64 bytesTotal);
void onTimeout();
private: private:
void setupTimer();
void cleanupTimer();
bool urlIsAssetHash() const; bool urlIsAssetHash() const;
void requestMappingForPath(const AssetPath& path); void requestMappingForPath(const AssetPath& path);
void requestHash(const AssetHash& hash); void requestHash(const AssetHash& hash);
QTimer* _sendTimer { nullptr };
GetMappingRequest* _assetMappingRequest { nullptr }; GetMappingRequest* _assetMappingRequest { nullptr };
AssetRequest* _assetRequest { nullptr }; AssetRequest* _assetRequest { nullptr };
}; };

View file

@ -21,7 +21,6 @@
AssetScriptingInterface::AssetScriptingInterface(QScriptEngine* engine) : AssetScriptingInterface::AssetScriptingInterface(QScriptEngine* engine) :
_engine(engine) _engine(engine)
{ {
} }
void AssetScriptingInterface::uploadData(QString data, QScriptValue callback) { void AssetScriptingInterface::uploadData(QString data, QScriptValue callback) {

View file

@ -13,12 +13,14 @@
#include <QNetworkReply> #include <QNetworkReply>
#include <QFile> #include <QFile>
#include <QPointer>
#include "ScriptEngineLogging.h" #include "ScriptEngineLogging.h"
#include "BatchLoader.h" #include "BatchLoader.h"
#include <NetworkAccessManager.h> #include <NetworkAccessManager.h>
#include <SharedUtil.h> #include <SharedUtil.h>
#include "ResourceManager.h" #include "ResourceManager.h"
#include "ScriptEngines.h" #include "ScriptEngines.h"
#include "ScriptCache.h"
BatchLoader::BatchLoader(const QList<QUrl>& urls) BatchLoader::BatchLoader(const QList<QUrl>& urls)
: QObject(), : QObject(),
@ -38,30 +40,25 @@ void BatchLoader::start() {
for (const auto& rawURL : _urls) { for (const auto& rawURL : _urls) {
QUrl url = expandScriptUrl(normalizeScriptURL(rawURL)); QUrl url = expandScriptUrl(normalizeScriptURL(rawURL));
auto request = ResourceManager::createResourceRequest(this, url);
if (!request) { qCDebug(scriptengine) << "Loading script at " << url;
_data.insert(url, QString());
qCDebug(scriptengine) << "Could not load" << url; QPointer<BatchLoader> self = this;
continue; DependencyManager::get<ScriptCache>()->getScriptContents(url.toString(), [this, self](const QString& url, const QString& contents, bool isURL, bool success) {
} if (!self) {
connect(request, &ResourceRequest::finished, this, [=]() { return;
if (request->getResult() == ResourceRequest::Success) { }
_data.insert(url, request->getData()); if (isURL && success) {
_data.insert(url, contents);
qCDebug(scriptengine) << "Loaded: " << url;
} else { } else {
_data.insert(url, QString()); _data.insert(url, QString());
qCDebug(scriptengine) << "Could not load" << url; qCDebug(scriptengine) << "Could not load" << url;
} }
request->deleteLater();
checkFinished(); checkFinished();
}); }, false);
// If we end up being destroyed before the reply finishes, clean it up
connect(this, &QObject::destroyed, request, &QObject::deleteLater);
qCDebug(scriptengine) << "Loading script at " << url;
request->send();
} }
checkFinished(); checkFinished();
} }

View file

@ -25,6 +25,7 @@
#include "ScriptEngines.h" #include "ScriptEngines.h"
#include "ScriptEngineLogging.h" #include "ScriptEngineLogging.h"
#include <QtCore/QTimer>
ScriptCache::ScriptCache(QObject* parent) { ScriptCache::ScriptCache(QObject* parent) {
// nothing to do here... // nothing to do here...
@ -133,8 +134,11 @@ void ScriptCache::getScriptContents(const QString& scriptOrURL, contentAvailable
qCDebug(scriptengine) << "Found script in cache:" << url.toString(); qCDebug(scriptengine) << "Found script in cache:" << url.toString();
contentAvailable(url.toString(), scriptContent, true, true); contentAvailable(url.toString(), scriptContent, true, true);
} else { } else {
bool alreadyWaiting = _contentCallbacks.contains(url); auto& scriptRequest = _activeScriptRequests[url];
_contentCallbacks.insert(url, contentAvailable);
bool alreadyWaiting = scriptRequest.scriptUsers.size() > 0;
scriptRequest.scriptUsers.push_back(contentAvailable);
lock.unlock(); lock.unlock();
if (alreadyWaiting) { if (alreadyWaiting) {
@ -152,6 +156,9 @@ void ScriptCache::getScriptContents(const QString& scriptOrURL, contentAvailable
} }
} }
static const int MAX_RETRIES = 5;
static int START_DELAY_BETWEEN_RETRIES = 200;
void ScriptCache::scriptContentAvailable() { void ScriptCache::scriptContentAvailable() {
#ifdef THREAD_DEBUGGING #ifdef THREAD_DEBUGGING
qCDebug(scriptengine) << "ScriptCache::scriptContentAvailable() on thread [" << QThread::currentThread() << "] expected thread [" << thread() << "]"; qCDebug(scriptengine) << "ScriptCache::scriptContentAvailable() on thread [" << QThread::currentThread() << "] expected thread [" << thread() << "]";
@ -160,29 +167,59 @@ void ScriptCache::scriptContentAvailable() {
QUrl url = req->getUrl(); QUrl url = req->getUrl();
QString scriptContent; QString scriptContent;
QList<contentAvailableCallback> allCallbacks; std::vector<contentAvailableCallback> allCallbacks;
bool finished { false };
bool success { false }; bool success { false };
{ {
Lock lock(_containerLock);
allCallbacks = _contentCallbacks.values(url);
_contentCallbacks.remove(url);
Q_ASSERT(req->getState() == ResourceRequest::Finished); Q_ASSERT(req->getState() == ResourceRequest::Finished);
success = req->getResult() == ResourceRequest::Success; success = req->getResult() == ResourceRequest::Success;
if (success) { Lock lock(_containerLock);
_scriptCache[url] = scriptContent = req->getData();
qCDebug(scriptengine) << "Done downloading script at:" << url.toString(); if (_activeScriptRequests.contains(url)) {
} else { auto& scriptRequest = _activeScriptRequests[url];
// Dubious, but retained here because it matches the behavior before fixing the threading
scriptContent = _scriptCache[url]; if (success) {
qCWarning(scriptengine) << "Error loading script from URL " << url; allCallbacks = scriptRequest.scriptUsers;
_activeScriptRequests.remove(url);
_scriptCache[url] = scriptContent = req->getData();
finished = true;
qCDebug(scriptengine) << "Done downloading script at:" << url.toString();
} else {
if (scriptRequest.numRetries < MAX_RETRIES) {
++scriptRequest.numRetries;
qDebug() << "Script request failed: " << url;
int timeout = exp(scriptRequest.numRetries) * START_DELAY_BETWEEN_RETRIES;
QTimer::singleShot(timeout, this, [this, url]() {
qDebug() << "Retrying script request: " << url;
auto request = ResourceManager::createResourceRequest(nullptr, url);
Q_ASSERT(request);
// We've already made a request, so the cache must be disabled or it wasn't there, so enabling
// it will do nothing.
request->setCacheEnabled(false);
connect(request, &ResourceRequest::finished, this, &ScriptCache::scriptContentAvailable);
request->send();
});
} else {
// Dubious, but retained here because it matches the behavior before fixing the threading
scriptContent = _scriptCache[url];
finished = true;
qCWarning(scriptengine) << "Error loading script from URL " << url;
}
}
} }
} }
req->deleteLater(); req->deleteLater();
if (!DependencyManager::get<ScriptEngines>()->isStopped()) { if (finished && !DependencyManager::get<ScriptEngines>()->isStopped()) {
foreach(contentAvailableCallback thisCallback, allCallbacks) { foreach(contentAvailableCallback thisCallback, allCallbacks) {
thisCallback(url.toString(), scriptContent, true, success); thisCallback(url.toString(), scriptContent, true, success);
} }

View file

@ -15,13 +15,19 @@
#include <mutex> #include <mutex>
#include <ResourceCache.h> #include <ResourceCache.h>
using contentAvailableCallback = std::function<void(const QString& scriptOrURL, const QString& contents, bool isURL, bool contentAvailable)>;
class ScriptUser { class ScriptUser {
public: public:
virtual void scriptContentsAvailable(const QUrl& url, const QString& scriptContents) = 0; virtual void scriptContentsAvailable(const QUrl& url, const QString& scriptContents) = 0;
virtual void errorInLoadingScript(const QUrl& url) = 0; virtual void errorInLoadingScript(const QUrl& url) = 0;
}; };
using contentAvailableCallback = std::function<void(const QString& scriptOrURL, const QString& contents, bool isURL, bool contentAvailable)>; class ScriptRequest {
public:
std::vector<contentAvailableCallback> scriptUsers { };
int numRetries { 0 };
};
/// Interface for loading scripts /// Interface for loading scripts
class ScriptCache : public QObject, public Dependency { class ScriptCache : public QObject, public Dependency {
@ -51,11 +57,11 @@ private:
ScriptCache(QObject* parent = NULL); ScriptCache(QObject* parent = NULL);
Mutex _containerLock; Mutex _containerLock;
QMultiMap<QUrl, contentAvailableCallback> _contentCallbacks; QMap<QUrl, ScriptRequest> _activeScriptRequests;
QHash<QUrl, QString> _scriptCache; QHash<QUrl, QString> _scriptCache;
QMultiMap<QUrl, ScriptUser*> _scriptUsers; QMultiMap<QUrl, ScriptUser*> _scriptUsers;
QSet<QUrl> _badScripts; QSet<QUrl> _badScripts;
}; };
#endif // hifi_ScriptCache_h #endif // hifi_ScriptCache_h

View file

@ -34,7 +34,6 @@ namespace Setting {
DependencyManager::destroy<Manager>(); DependencyManager::destroy<Manager>();
// //
globalManager->deleteLater();
globalManager.reset(); globalManager.reset();
// quit the settings manager thread and wait on it to make sure it's gone // quit the settings manager thread and wait on it to make sure it's gone
@ -72,9 +71,9 @@ namespace Setting {
globalManager = DependencyManager::set<Manager>(); globalManager = DependencyManager::set<Manager>();
QObject::connect(globalManager.data(), SIGNAL(destroyed()), thread, SLOT(quit()));
QObject::connect(thread, SIGNAL(started()), globalManager.data(), SLOT(startTimer())); QObject::connect(thread, SIGNAL(started()), globalManager.data(), SLOT(startTimer()));
QObject::connect(thread, SIGNAL(finished()), thread, SLOT(deleteLater())); QObject::connect(thread, SIGNAL(finished()), thread, SLOT(deleteLater()));
QObject::connect(thread, SIGNAL(finished()), globalManager.data(), SLOT(deleteLater()));
globalManager->moveToThread(thread); globalManager->moveToThread(thread);
thread->start(); thread->start();
qCDebug(shared) << "Settings thread started."; qCDebug(shared) << "Settings thread started.";

View file

@ -10,3 +10,6 @@ set_target_properties(vhacd-util PROPERTIES FOLDER "Tools")
add_subdirectory(ice-client) add_subdirectory(ice-client)
set_target_properties(ice-client PROPERTIES FOLDER "Tools") set_target_properties(ice-client PROPERTIES FOLDER "Tools")
add_subdirectory(ac-client)
set_target_properties(ac-client PROPERTIES FOLDER "Tools")

View file

@ -0,0 +1,3 @@
set(TARGET_NAME ac-client)
setup_hifi_project(Core Widgets)
link_hifi_libraries(shared networking)

View file

@ -0,0 +1,252 @@
//
// ACClientApp.cpp
// tools/ac-client/src
//
// Created by Seth Alves on 2016-10-5
// Copyright 2015 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#include <QDataStream>
#include <QThread>
#include <QLoggingCategory>
#include <QCommandLineParser>
#include <NetworkLogging.h>
#include <SharedLogging.h>
#include <AddressManager.h>
#include <DependencyManager.h>
#include <SettingHandle.h>
#include "ACClientApp.h"
ACClientApp::ACClientApp(int argc, char* argv[]) :
QCoreApplication(argc, argv)
{
// parse command-line
QCommandLineParser parser;
parser.setApplicationDescription("High Fidelity AC client");
const QCommandLineOption helpOption = parser.addHelpOption();
const QCommandLineOption verboseOutput("v", "verbose output");
parser.addOption(verboseOutput);
const QCommandLineOption domainAddressOption("d", "domain-server address", "127.0.0.1");
parser.addOption(domainAddressOption);
const QCommandLineOption cacheSTUNOption("s", "cache stun-server response");
parser.addOption(cacheSTUNOption);
const QCommandLineOption listenPortOption("listenPort", "listen port", QString::number(INVALID_PORT));
parser.addOption(listenPortOption);
if (!parser.parse(QCoreApplication::arguments())) {
qCritical() << parser.errorText() << endl;
parser.showHelp();
Q_UNREACHABLE();
}
if (parser.isSet(helpOption)) {
parser.showHelp();
Q_UNREACHABLE();
}
_verbose = parser.isSet(verboseOutput);
if (!_verbose) {
QLoggingCategory::setFilterRules("qt.network.ssl.warning=false");
const_cast<QLoggingCategory*>(&networking())->setEnabled(QtDebugMsg, false);
const_cast<QLoggingCategory*>(&networking())->setEnabled(QtInfoMsg, false);
const_cast<QLoggingCategory*>(&networking())->setEnabled(QtWarningMsg, false);
const_cast<QLoggingCategory*>(&shared())->setEnabled(QtDebugMsg, false);
const_cast<QLoggingCategory*>(&shared())->setEnabled(QtInfoMsg, false);
const_cast<QLoggingCategory*>(&shared())->setEnabled(QtWarningMsg, false);
}
QString domainServerAddress = "127.0.0.1:40103";
if (parser.isSet(domainAddressOption)) {
domainServerAddress = parser.value(domainAddressOption);
}
if (_verbose) {
qDebug() << "domain-server address is" << domainServerAddress;
}
int listenPort = INVALID_PORT;
if (parser.isSet(listenPortOption)) {
listenPort = parser.value(listenPortOption).toInt();
}
Setting::preInit();
DependencyManager::registerInheritance<LimitedNodeList, NodeList>();
Setting::init();
DependencyManager::set<AccountManager>([&]{ return QString("Mozilla/5.0 (HighFidelityACClient)"); });
DependencyManager::set<AddressManager>();
DependencyManager::set<NodeList>(NodeType::Agent, listenPort);
auto nodeList = DependencyManager::get<NodeList>();
// start the nodeThread so its event loop is running
QThread* nodeThread = new QThread(this);
nodeThread->setObjectName("NodeList Thread");
nodeThread->start();
// make sure the node thread is given highest priority
nodeThread->setPriority(QThread::TimeCriticalPriority);
// setup a timer for domain-server check ins
QTimer* domainCheckInTimer = new QTimer(nodeList.data());
connect(domainCheckInTimer, &QTimer::timeout, nodeList.data(), &NodeList::sendDomainServerCheckIn);
domainCheckInTimer->start(DOMAIN_SERVER_CHECK_IN_MSECS);
// put the NodeList and datagram processing on the node thread
nodeList->moveToThread(nodeThread);
const DomainHandler& domainHandler = nodeList->getDomainHandler();
connect(&domainHandler, SIGNAL(hostnameChanged(const QString&)), SLOT(domainChanged(const QString&)));
// connect(&domainHandler, SIGNAL(resetting()), SLOT(resettingDomain()));
// connect(&domainHandler, SIGNAL(disconnectedFromDomain()), SLOT(clearDomainOctreeDetails()));
connect(&domainHandler, &DomainHandler::domainConnectionRefused, this, &ACClientApp::domainConnectionRefused);
connect(nodeList.data(), &NodeList::nodeAdded, this, &ACClientApp::nodeAdded);
connect(nodeList.data(), &NodeList::nodeKilled, this, &ACClientApp::nodeKilled);
connect(nodeList.data(), &NodeList::nodeActivated, this, &ACClientApp::nodeActivated);
// connect(nodeList.data(), &NodeList::uuidChanged, getMyAvatar(), &MyAvatar::setSessionUUID);
// connect(nodeList.data(), &NodeList::uuidChanged, this, &ACClientApp::setSessionUUID);
connect(nodeList.data(), &NodeList::packetVersionMismatch, this, &ACClientApp::notifyPacketVersionMismatch);
nodeList->addSetOfNodeTypesToNodeInterestSet(NodeSet() << NodeType::AudioMixer << NodeType::AvatarMixer
<< NodeType::EntityServer << NodeType::AssetServer << NodeType::MessagesMixer);
DependencyManager::get<AddressManager>()->handleLookupString(domainServerAddress, false);
QTimer* doTimer = new QTimer(this);
doTimer->setSingleShot(true);
connect(doTimer, &QTimer::timeout, this, &ACClientApp::timedOut);
doTimer->start(4000);
}
ACClientApp::~ACClientApp() {
}
void ACClientApp::domainConnectionRefused(const QString& reasonMessage, int reasonCodeInt, const QString& extraInfo) {
qDebug() << "domainConnectionRefused";
}
void ACClientApp::domainChanged(const QString& domainHostname) {
if (_verbose) {
qDebug() << "domainChanged";
}
}
void ACClientApp::nodeAdded(SharedNodePointer node) {
if (_verbose) {
qDebug() << "node added: " << node->getType();
}
}
void ACClientApp::nodeActivated(SharedNodePointer node) {
if (node->getType() == NodeType::EntityServer) {
if (_verbose) {
qDebug() << "saw EntityServer";
}
_sawEntityServer = true;
}
else if (node->getType() == NodeType::AudioMixer) {
if (_verbose) {
qDebug() << "saw AudioMixer";
}
_sawAudioMixer = true;
}
else if (node->getType() == NodeType::AvatarMixer) {
if (_verbose) {
qDebug() << "saw AvatarMixer";
}
_sawAvatarMixer = true;
}
else if (node->getType() == NodeType::AssetServer) {
if (_verbose) {
qDebug() << "saw AssetServer";
}
_sawAssetServer = true;
}
else if (node->getType() == NodeType::MessagesMixer) {
if (_verbose) {
qDebug() << "saw MessagesMixer";
}
_sawMessagesMixer = true;
}
if (_sawEntityServer && _sawAudioMixer && _sawAvatarMixer && _sawAssetServer && _sawMessagesMixer) {
if (_verbose) {
qDebug() << "success";
}
finish(0);
}
}
void ACClientApp::nodeKilled(SharedNodePointer node) {
qDebug() << "nodeKilled";
}
void ACClientApp::timedOut() {
if (_verbose) {
qDebug() << "timed out: " << _sawEntityServer << _sawAudioMixer <<
_sawAvatarMixer << _sawAssetServer << _sawMessagesMixer;
}
finish(1);
}
void ACClientApp::notifyPacketVersionMismatch() {
if (_verbose) {
qDebug() << "packet version mismatch";
}
finish(1);
}
void ACClientApp::printFailedServers() {
if (!_sawEntityServer) {
qDebug() << "EntityServer";
}
if (!_sawAudioMixer) {
qDebug() << "AudioMixer";
}
if (!_sawAvatarMixer) {
qDebug() << "AvatarMixer";
}
if (!_sawAssetServer) {
qDebug() << "AssetServer";
}
if (!_sawMessagesMixer) {
qDebug() << "MessagesMixer";
}
}
void ACClientApp::finish(int exitCode) {
auto nodeList = DependencyManager::get<NodeList>();
// send the domain a disconnect packet, force stoppage of domain-server check-ins
nodeList->getDomainHandler().disconnect();
nodeList->setIsShuttingDown(true);
// tell the packet receiver we're shutting down, so it can drop packets
nodeList->getPacketReceiver().setShouldDropPackets(true);
QThread* nodeThread = DependencyManager::get<NodeList>()->thread();
// remove the NodeList from the DependencyManager
DependencyManager::destroy<NodeList>();
// ask the node thread to quit and wait until it is done
nodeThread->quit();
nodeThread->wait();
printFailedServers();
QCoreApplication::exit(exitCode);
}

View file

@ -0,0 +1,52 @@
//
// ACClientApp.h
// tools/ac-client/src
//
// Created by Seth Alves on 2016-10-5
// Copyright 2016 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#ifndef hifi_ACClientApp_h
#define hifi_ACClientApp_h
#include <QApplication>
#include <udt/Constants.h>
#include <udt/Socket.h>
#include <ReceivedMessage.h>
#include <NetworkPeer.h>
#include <NodeList.h>
class ACClientApp : public QCoreApplication {
Q_OBJECT
public:
ACClientApp(int argc, char* argv[]);
~ACClientApp();
private slots:
void domainConnectionRefused(const QString& reasonMessage, int reasonCodeInt, const QString& extraInfo);
void domainChanged(const QString& domainHostname);
void nodeAdded(SharedNodePointer node);
void nodeActivated(SharedNodePointer node);
void nodeKilled(SharedNodePointer node);
void notifyPacketVersionMismatch();
private:
NodeList* _nodeList;
void timedOut();
void printFailedServers();
void finish(int exitCode);
bool _verbose;
bool _sawEntityServer { false };
bool _sawAudioMixer { false };
bool _sawAvatarMixer { false };
bool _sawAssetServer { false };
bool _sawMessagesMixer { false };
};
#endif //hifi_ACClientApp_h

View file

@ -0,0 +1,23 @@
//
// main.cpp
// tools/ice-client/src
//
// Created by Seth Alves on 2016-10-5
// Copyright 2016 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
#include <iostream>
#include <iomanip>
#include <string>
#include <vector>
#include "ACClientApp.h"
using namespace std;
int main(int argc, char * argv[]) {
ACClientApp app(argc, argv);
return app.exec();
}

View file

@ -178,16 +178,9 @@ void ICEClientApp::doSomething() {
qDebug() << "sending STUN request"; qDebug() << "sending STUN request";
} }
_socket->writeDatagram(stunRequestPacket, sizeof(stunRequestPacket), _stunSockAddr); _socket->writeDatagram(stunRequestPacket, sizeof(stunRequestPacket), _stunSockAddr);
_stunResponseTimerCanceled = false; _stunResponseTimer.setSingleShot(true);
_stunResponseTimer.singleShot(stunResponseTimeoutMilliSeconds, this, [&] { connect(&_stunResponseTimer, SIGNAL(timeout()), this, SLOT(stunResponseTimeout()));
if (_stunResponseTimerCanceled) { _stunResponseTimer.start(stunResponseTimeoutMilliSeconds);
return;
}
if (_verbose) {
qDebug() << "timeout waiting for stun-server response";
}
QCoreApplication::exit(stunFailureExitStatus);
});
setState(waitForStunResponse); setState(waitForStunResponse);
} else { } else {
@ -215,16 +208,9 @@ void ICEClientApp::doSomething() {
} }
sendPacketToIceServer(PacketType::ICEServerQuery, _iceServerAddr, _sessionUUID, peerID); sendPacketToIceServer(PacketType::ICEServerQuery, _iceServerAddr, _sessionUUID, peerID);
_iceResponseTimerCanceled = false; _iceResponseTimer.setSingleShot(true);
_iceResponseTimer.singleShot(iceResponseTimeoutMilliSeconds, this, [=] { connect(&_iceResponseTimer, SIGNAL(timeout()), this, SLOT(iceResponseTimeout()));
if (_iceResponseTimerCanceled) { _iceResponseTimer.start(iceResponseTimeoutMilliSeconds);
return;
}
if (_verbose) {
qDebug() << "timeout waiting for ice-server response";
}
QCoreApplication::exit(iceFailureExitStatus);
});
} else if (_state == pause0) { } else if (_state == pause0) {
setState(pause1); setState(pause1);
} else if (_state == pause1) { } else if (_state == pause1) {
@ -237,6 +223,20 @@ void ICEClientApp::doSomething() {
} }
} }
void ICEClientApp::iceResponseTimeout() {
if (_verbose) {
qDebug() << "timeout waiting for ice-server response";
}
QCoreApplication::exit(iceFailureExitStatus);
}
void ICEClientApp::stunResponseTimeout() {
if (_verbose) {
qDebug() << "timeout waiting for stun-server response";
}
QCoreApplication::exit(stunFailureExitStatus);
}
void ICEClientApp::sendPacketToIceServer(PacketType packetType, const HifiSockAddr& iceServerSockAddr, void ICEClientApp::sendPacketToIceServer(PacketType packetType, const HifiSockAddr& iceServerSockAddr,
const QUuid& clientID, const QUuid& peerID) { const QUuid& clientID, const QUuid& peerID) {
std::unique_ptr<NLPacket> icePacket = NLPacket::create(packetType); std::unique_ptr<NLPacket> icePacket = NLPacket::create(packetType);
@ -298,7 +298,6 @@ void ICEClientApp::processSTUNResponse(std::unique_ptr<udt::BasePacket> packet)
} }
_stunResponseTimer.stop(); _stunResponseTimer.stop();
_stunResponseTimerCanceled = true;
uint16_t newPublicPort; uint16_t newPublicPort;
QHostAddress newPublicAddress; QHostAddress newPublicAddress;
@ -331,7 +330,6 @@ void ICEClientApp::processPacket(std::unique_ptr<udt::Packet> packet) {
if (nlPacket->getType() == PacketType::ICEServerPeerInformation) { if (nlPacket->getType() == PacketType::ICEServerPeerInformation) {
// cancel the timeout timer // cancel the timeout timer
_iceResponseTimer.stop(); _iceResponseTimer.stop();
_iceResponseTimerCanceled = true;
QDataStream iceResponseStream(message->getMessage()); QDataStream iceResponseStream(message->getMessage());
if (!_domainServerPeerSet) { if (!_domainServerPeerSet) {

View file

@ -33,6 +33,10 @@ public:
const int stunResponseTimeoutMilliSeconds { 2000 }; const int stunResponseTimeoutMilliSeconds { 2000 };
const int iceResponseTimeoutMilliSeconds { 2000 }; const int iceResponseTimeoutMilliSeconds { 2000 };
public slots:
void iceResponseTimeout();
void stunResponseTimeout();
private: private:
enum State { enum State {
lookUpStunServer, // 0 lookUpStunServer, // 0
@ -83,9 +87,7 @@ private:
int _state { 0 }; int _state { 0 };
QTimer _stunResponseTimer; QTimer _stunResponseTimer;
bool _stunResponseTimerCanceled { false };
QTimer _iceResponseTimer; QTimer _iceResponseTimer;
bool _iceResponseTimerCanceled { false };
int _domainPingCount { 0 }; int _domainPingCount { 0 };
}; };