Cap the max concurrent webviews, and deallocate unrendered views after 30 seconds

This commit is contained in:
Brad Davis 2016-02-19 15:41:40 -08:00
parent 64ec56bf89
commit 021b86c3fe
2 changed files with 164 additions and 118 deletions

View file

@ -28,6 +28,11 @@
const float DPI = 30.47f;
const float METERS_TO_INCHES = 39.3701f;
static uint32_t _currentWebCount { 0 };
// Don't allow more than 100 concurrent web views
static const uint32_t MAX_CONCURRENT_WEB_VIEWS = 100;
// If a web-view hasn't been rendered for 30 seconds, de-allocate the framebuffer
static uint64_t MAX_NO_RENDER_INTERVAL = 30 * USECS_PER_SECOND;
EntityItemPointer RenderableWebEntityItem::factory(const EntityItemID& entityID, const EntityItemProperties& properties) {
EntityItemPointer entity{ new RenderableWebEntityItem(entityID) };
@ -41,28 +46,120 @@ RenderableWebEntityItem::RenderableWebEntityItem(const EntityItemID& entityItemI
}
RenderableWebEntityItem::~RenderableWebEntityItem() {
if (_webSurface) {
_webSurface->pause();
_webSurface->disconnect(_connection);
// The lifetime of the QML surface MUST be managed by the main thread
// Additionally, we MUST use local variables copied by value, rather than
// member variables, since they would implicitly refer to a this that
// is no longer valid
auto webSurface = _webSurface;
AbstractViewStateInterface::instance()->postLambdaEvent([webSurface] {
webSurface->deleteLater();
});
}
QObject::disconnect(_mousePressConnection);
QObject::disconnect(_mouseReleaseConnection);
QObject::disconnect(_mouseMoveConnection);
QObject::disconnect(_hoverLeaveConnection);
destroyWebSurface();
qDebug() << "Destroyed web entity " << getID();
}
bool RenderableWebEntityItem::buildWebSurface(EntityTreeRenderer* renderer) {
if (_currentWebCount >= MAX_CONCURRENT_WEB_VIEWS) {
qWarning() << "Too many concurrent web views to create new view";
return false;
}
qDebug() << "Building web surface";
++_currentWebCount;
QOpenGLContext * currentContext = QOpenGLContext::currentContext();
_webSurface = new OffscreenQmlSurface();
_webSurface->create(currentContext);
_webSurface->setBaseUrl(QUrl::fromLocalFile(PathUtils::resourcesPath() + "/qml/"));
_webSurface->load("WebEntity.qml");
_webSurface->resume();
_webSurface->getRootItem()->setProperty("url", _sourceUrl);
_connection = QObject::connect(_webSurface, &OffscreenQmlSurface::textureUpdated, [&](GLuint textureId) {
_texture = textureId;
});
auto forwardMouseEvent = [=](const RayToEntityIntersectionResult& intersection, const QMouseEvent* event, unsigned int deviceId) {
// Ignore mouse interaction if we're locked
if (this->getLocked()) {
return;
}
if (event->button() == Qt::MouseButton::RightButton) {
if (event->type() == QEvent::MouseButtonPress) {
const QMouseEvent* mouseEvent = static_cast<const QMouseEvent*>(event);
_lastPress = toGlm(mouseEvent->pos());
}
}
if (intersection.entityID == getID()) {
if (event->button() == Qt::MouseButton::RightButton) {
if (event->type() == QEvent::MouseButtonRelease) {
const QMouseEvent* mouseEvent = static_cast<const QMouseEvent*>(event);
ivec2 dist = glm::abs(toGlm(mouseEvent->pos()) - _lastPress);
if (!glm::any(glm::greaterThan(dist, ivec2(1)))) {
AbstractViewStateInterface::instance()->postLambdaEvent([this] {
QMetaObject::invokeMethod(_webSurface->getRootItem(), "goBack");
});
}
_lastPress = ivec2(INT_MIN);
}
return;
}
// FIXME doesn't work... double click events not received
if (event->type() == QEvent::MouseButtonDblClick) {
AbstractViewStateInterface::instance()->postLambdaEvent([this] {
_webSurface->getRootItem()->setProperty("url", _sourceUrl);
});
}
if (event->button() == Qt::MouseButton::MiddleButton) {
if (event->type() == QEvent::MouseButtonRelease) {
AbstractViewStateInterface::instance()->postLambdaEvent([this] {
_webSurface->getRootItem()->setProperty("url", _sourceUrl);
});
}
return;
}
// Map the intersection point to an actual offscreen pixel
glm::vec3 point = intersection.intersection;
point -= getPosition();
point = glm::inverse(getRotation()) * point;
point /= getDimensions();
point += 0.5f;
point.y = 1.0f - point.y;
point *= getDimensions() * METERS_TO_INCHES * DPI;
if (event->button() == Qt::MouseButton::LeftButton) {
if (event->type() == QEvent::MouseButtonPress) {
this->_pressed = true;
this->_lastMove = ivec2((int)point.x, (int)point.y);
} else if (event->type() == QEvent::MouseButtonRelease) {
this->_pressed = false;
}
}
if (event->type() == QEvent::MouseMove) {
this->_lastMove = ivec2((int)point.x, (int)point.y);
}
// Forward the mouse event.
QMouseEvent mappedEvent(event->type(),
QPoint((int)point.x, (int)point.y),
event->screenPos(), event->button(),
event->buttons(), event->modifiers());
QCoreApplication::sendEvent(_webSurface->getWindow(), &mappedEvent);
}
};
_mousePressConnection = QObject::connect(renderer, &EntityTreeRenderer::mousePressOnEntity, forwardMouseEvent);
_mouseReleaseConnection = QObject::connect(renderer, &EntityTreeRenderer::mouseReleaseOnEntity, forwardMouseEvent);
_mouseMoveConnection = QObject::connect(renderer, &EntityTreeRenderer::mouseMoveOnEntity, forwardMouseEvent);
_hoverLeaveConnection = QObject::connect(renderer, &EntityTreeRenderer::hoverLeaveEntity, [=](const EntityItemID& entityItemID, const MouseEvent& event) {
if (this->_pressed && this->getID() == entityItemID) {
// If the user mouses off the entity while the button is down, simulate a mouse release
QMouseEvent mappedEvent(QEvent::MouseButtonRelease,
QPoint(_lastMove.x, _lastMove.y),
Qt::MouseButton::LeftButton,
Qt::MouseButtons(), Qt::KeyboardModifiers());
QCoreApplication::sendEvent(_webSurface->getWindow(), &mappedEvent);
}
});
return true;
}
void RenderableWebEntityItem::render(RenderArgs* args) {
#ifdef WANT_EXTRA_DEBUGGING
{
gpu::Batch& batch = *args->_batch;
@ -72,108 +169,15 @@ void RenderableWebEntityItem::render(RenderArgs* args) {
}
#endif
if (!_webSurface) {
if (!buildWebSurface(static_cast<EntityTreeRenderer*>(args->_renderer))) {
return;
}
}
_lastRenderTime = usecTimestampNow();
QOpenGLContext * currentContext = QOpenGLContext::currentContext();
QSurface * currentSurface = currentContext->surface();
if (!_webSurface) {
_webSurface = new OffscreenQmlSurface();
_webSurface->create(currentContext);
_webSurface->setBaseUrl(QUrl::fromLocalFile(PathUtils::resourcesPath() + "/qml/"));
_webSurface->load("WebEntity.qml");
_webSurface->resume();
_webSurface->getRootItem()->setProperty("url", _sourceUrl);
_connection = QObject::connect(_webSurface, &OffscreenQmlSurface::textureUpdated, [&](GLuint textureId) {
_texture = textureId;
});
auto forwardMouseEvent = [=](const RayToEntityIntersectionResult& intersection, const QMouseEvent* event, unsigned int deviceId) {
// Ignore mouse interaction if we're locked
if (this->getLocked()) {
return;
}
if (event->button() == Qt::MouseButton::RightButton) {
if (event->type() == QEvent::MouseButtonPress) {
const QMouseEvent* mouseEvent = static_cast<const QMouseEvent*>(event);
_lastPress = toGlm(mouseEvent->pos());
}
}
if (intersection.entityID == getID()) {
if (event->button() == Qt::MouseButton::RightButton) {
if (event->type() == QEvent::MouseButtonRelease) {
const QMouseEvent* mouseEvent = static_cast<const QMouseEvent*>(event);
ivec2 dist = glm::abs(toGlm(mouseEvent->pos()) - _lastPress);
if (!glm::any(glm::greaterThan(dist, ivec2(1)))) {
AbstractViewStateInterface::instance()->postLambdaEvent([this] {
QMetaObject::invokeMethod(_webSurface->getRootItem(), "goBack");
});
}
_lastPress = ivec2(INT_MIN);
}
return;
}
// FIXME doesn't work... double click events not received
if (event->type() == QEvent::MouseButtonDblClick) {
AbstractViewStateInterface::instance()->postLambdaEvent([this] {
_webSurface->getRootItem()->setProperty("url", _sourceUrl);
});
}
if (event->button() == Qt::MouseButton::MiddleButton) {
if (event->type() == QEvent::MouseButtonRelease) {
AbstractViewStateInterface::instance()->postLambdaEvent([this] {
_webSurface->getRootItem()->setProperty("url", _sourceUrl);
});
}
return;
}
// Map the intersection point to an actual offscreen pixel
glm::vec3 point = intersection.intersection;
point -= getPosition();
point = glm::inverse(getRotation()) * point;
point /= getDimensions();
point += 0.5f;
point.y = 1.0f - point.y;
point *= getDimensions() * METERS_TO_INCHES * DPI;
if (event->button() == Qt::MouseButton::LeftButton) {
if (event->type() == QEvent::MouseButtonPress) {
this->_pressed = true;
this->_lastMove = ivec2((int)point.x, (int)point.y);
} else if (event->type() == QEvent::MouseButtonRelease) {
this->_pressed = false;
}
}
if (event->type() == QEvent::MouseMove) {
this->_lastMove = ivec2((int)point.x, (int)point.y);
}
// Forward the mouse event.
QMouseEvent mappedEvent(event->type(),
QPoint((int)point.x, (int)point.y),
event->screenPos(), event->button(),
event->buttons(), event->modifiers());
QCoreApplication::sendEvent(_webSurface->getWindow(), &mappedEvent);
}
};
EntityTreeRenderer* renderer = static_cast<EntityTreeRenderer*>(args->_renderer);
_mousePressConnection = QObject::connect(renderer, &EntityTreeRenderer::mousePressOnEntity, forwardMouseEvent);
_mouseReleaseConnection = QObject::connect(renderer, &EntityTreeRenderer::mouseReleaseOnEntity, forwardMouseEvent);
_mouseMoveConnection = QObject::connect(renderer, &EntityTreeRenderer::mouseMoveOnEntity, forwardMouseEvent);
_hoverLeaveConnection = QObject::connect(renderer, &EntityTreeRenderer::hoverLeaveEntity, [=](const EntityItemID& entityItemID, const MouseEvent& event) {
if (this->_pressed && this->getID() == entityItemID) {
// If the user mouses off the entity while the button is down, simulate a mouse release
QMouseEvent mappedEvent(QEvent::MouseButtonRelease,
QPoint(_lastMove.x, _lastMove.y),
Qt::MouseButton::LeftButton,
Qt::MouseButtons(), Qt::KeyboardModifiers());
QCoreApplication::sendEvent(_webSurface->getWindow(), &mappedEvent);
}
});
}
glm::vec2 dims = glm::vec2(getDimensions());
dims *= METERS_TO_INCHES * DPI;
@ -223,3 +227,37 @@ void RenderableWebEntityItem::setProxyWindow(QWindow* proxyWindow) {
QObject* RenderableWebEntityItem::getEventHandler() {
return _webSurface->getEventHandler();
}
void RenderableWebEntityItem::destroyWebSurface() {
if (_webSurface) {
--_currentWebCount;
_webSurface->pause();
_webSurface->disconnect(_connection);
QObject::disconnect(_mousePressConnection);
_mousePressConnection = QMetaObject::Connection();
QObject::disconnect(_mouseReleaseConnection);
_mouseReleaseConnection = QMetaObject::Connection();
QObject::disconnect(_mouseMoveConnection);
_mouseMoveConnection = QMetaObject::Connection();
QObject::disconnect(_hoverLeaveConnection);
_hoverLeaveConnection = QMetaObject::Connection();
// The lifetime of the QML surface MUST be managed by the main thread
// Additionally, we MUST use local variables copied by value, rather than
// member variables, since they would implicitly refer to a this that
// is no longer valid
auto webSurface = _webSurface;
AbstractViewStateInterface::instance()->postLambdaEvent([webSurface] {
webSurface->deleteLater();
});
_webSurface = nullptr;
}
}
void RenderableWebEntityItem::update(const quint64& now) {
auto interval = now - _lastRenderTime;
if (interval > MAX_NO_RENDER_INTERVAL) {
destroyWebSurface();
}
}

View file

@ -18,6 +18,7 @@
class OffscreenQmlSurface;
class QWindow;
class QObject;
class EntityTreeRenderer;
class RenderableWebEntityItem : public WebEntityItem {
public:
@ -31,15 +32,22 @@ public:
void setProxyWindow(QWindow* proxyWindow);
QObject* getEventHandler();
void update(const quint64& now) override;
bool needsToCallUpdate() const { return _webSurface != nullptr; }
SIMPLE_RENDERABLE();
private:
bool buildWebSurface(EntityTreeRenderer* renderer);
void destroyWebSurface();
OffscreenQmlSurface* _webSurface{ nullptr };
QMetaObject::Connection _connection;
uint32_t _texture{ 0 };
ivec2 _lastPress{ INT_MIN };
bool _pressed{ false };
ivec2 _lastMove{ INT_MIN };
uint64_t _lastRenderTime{ 0 };
QMetaObject::Connection _mousePressConnection;
QMetaObject::Connection _mouseReleaseConnection;