Merge branch 'master' of https://github.com/highfidelity/hifi into lemon

This commit is contained in:
samcake 2016-03-25 15:29:01 -07:00
commit c23640249e
44 changed files with 1161 additions and 868 deletions

View file

@ -369,14 +369,6 @@ void AudioMixer::sendAudioEnvironmentPacket(SharedNodePointer node) {
reverbTime = _zoneReverbSettings[i].reverbTime; reverbTime = _zoneReverbSettings[i].reverbTime;
wetLevel = _zoneReverbSettings[i].wetLevel; wetLevel = _zoneReverbSettings[i].wetLevel;
// Modulate wet level with distance to wall
float MIN_ATTENUATION_DISTANCE = 2.0f;
float MAX_ATTENUATION = -12; // dB
glm::vec3 distanceToWalls = (box.getDimensions() / 2.0f) - glm::abs(streamPosition - box.calcCenter());
float distanceToClosestWall = glm::min(distanceToWalls.x, distanceToWalls.z);
if (distanceToClosestWall < MIN_ATTENUATION_DISTANCE) {
wetLevel += MAX_ATTENUATION * (1.0f - distanceToClosestWall / MIN_ATTENUATION_DISTANCE);
}
break; break;
} }
} }

View file

@ -308,7 +308,7 @@
"name": "reverb", "name": "reverb",
"type": "table", "type": "table",
"label": "Reverb Settings", "label": "Reverb Settings",
"help": "In this table you can set reverb levels for audio zones. For a medium-sized (e.g., 100 square meter) meeting room, try a decay time of around 1.5 seconds and a wet level of -10 db. For an airplane hangar or cathedral, try a decay time of 4 seconds and a wet level of -5 db.", "help": "In this table you can set reverb levels for audio zones. For a medium-sized (e.g., 100 square meter) meeting room, try a decay time of around 1.5 seconds and a wet/dry mix of 25%. For an airplane hangar or cathedral, try a decay time of 4 seconds and a wet/dry mix of 50%.",
"numbered": true, "numbered": true,
"columns": [ "columns": [
{ {
@ -325,9 +325,9 @@
}, },
{ {
"name": "wet_level", "name": "wet_level",
"label": "Wet Level", "label": "Wet/Dry Mix",
"can_set": true, "can_set": true,
"placeholder": "(in db)" "placeholder": "(in percent)"
} }
] ]
} }

View file

@ -10,11 +10,50 @@
var EventBridge; var EventBridge;
openEventBridge = function(callback) { EventBridgeConnectionProxy = function(parent) {
new QWebChannel(qt.webChannelTransport, function(channel) { this.parent = parent;
console.log("uid " + EventBridgeUid); this.realSignal = this.parent.realBridge.scriptEventReceived
EventBridge = channel.objects[EventBridgeUid]; this.webWindowId = this.parent.webWindow.windowId;
callback(EventBridge); }
EventBridgeConnectionProxy.prototype.connect = function(callback) {
var that = this;
this.realSignal.connect(function(id, message) {
if (id === that.webWindowId) { callback(message); }
}); });
} }
EventBridgeProxy = function(webWindow) {
this.webWindow = webWindow;
this.realBridge = this.webWindow.eventBridge;
this.scriptEventReceived = new EventBridgeConnectionProxy(this);
}
EventBridgeProxy.prototype.emitWebEvent = function(data) {
this.realBridge.emitWebEvent(data);
}
openEventBridge = function(callback) {
EVENT_BRIDGE_URI = "ws://localhost:51016";
socket = new WebSocket(this.EVENT_BRIDGE_URI);
socket.onclose = function() {
console.error("web channel closed");
};
socket.onerror = function(error) {
console.error("web channel error: " + error);
};
socket.onopen = function() {
channel = new QWebChannel(socket, function(channel) {
console.log("Document url is " + document.URL);
var webWindow = channel.objects[document.URL.toLowerCase()];
console.log("WebWindow is " + webWindow)
eventBridgeProxy = new EventBridgeProxy(webWindow);
EventBridge = eventBridgeProxy;
if (callback) { callback(eventBridgeProxy); }
});
}
}

View file

@ -4,17 +4,21 @@
<script type="text/javascript" src="jquery-2.1.4.min.js"></script> <script type="text/javascript" src="jquery-2.1.4.min.js"></script>
<script type="text/javascript" src="qrc:///qtwebchannel/qwebchannel.js"></script> <script type="text/javascript" src="qrc:///qtwebchannel/qwebchannel.js"></script>
<script type="text/javascript" src="eventBridgeLoader.js"></script> <script type="text/javascript" src="eventBridgeLoader.js"></script>
<script> <script>
var myBridge;
window.onload = function() { window.onload = function() {
openEventBridge(function() { openEventBridge(function(eventBridge) {
EventBridge.scriptEventReceived.connect(function(message) { myBridge = eventBridge;
myBridge.scriptEventReceived.connect(function(message) {
console.log("HTML side received message: " + message); console.log("HTML side received message: " + message);
}); });
}); });
} }
testClick = function() { testClick = function() {
EventBridge.emitWebEvent(["Foo", "Bar", { "baz": 1} ]); myBridge.emitWebEvent("HTML side sending message - button click");
} }
</script> </script>
</head> </head>

View file

@ -8,14 +8,26 @@ webWindow.eventBridge.webEventReceived.connect(function(data) {
print("JS Side event received: " + data); print("JS Side event received: " + data);
}); });
var titles = ["A", "B", "C"];
var titleIndex = 0;
Script.setInterval(function() { Script.setInterval(function() {
var message = [ Math.random(), Math.random() ]; webWindow.eventBridge.emitScriptEvent("JS Event sent");
print("JS Side sending: " + message); var size = webWindow.size;
webWindow.emitScriptEvent(message); var position = webWindow.position;
}, 5 * 1000); print("Window url: " + webWindow.url)
print("Window visible: " + webWindow.visible)
print("Window size: " + size.x + "x" + size.y)
print("Window pos: " + position.x + "x" + position.y)
webWindow.setVisible(!webWindow.visible);
webWindow.setTitle(titles[titleIndex]);
webWindow.setSize(320 + Math.random() * 100, 240 + Math.random() * 100);
titleIndex += 1;
titleIndex %= titles.length;
}, 2 * 1000);
Script.scriptEnding.connect(function(){ Script.setTimeout(function() {
print("Closing script");
webWindow.close(); webWindow.close();
webWindow.deleteLater(); Script.stop();
}); }, 15 * 1000)

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 361 KiB

After

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 361 KiB

After

Width:  |  Height:  |  Size: 51 KiB

View file

@ -1,7 +1,6 @@
import QtQuick 2.3 import QtQuick 2.3
import QtQuick.Controls 1.2 import QtQuick.Controls 1.2
import QtWebEngine 1.1 import QtWebEngine 1.1
import QtWebChannel 1.0
import "windows" as Windows import "windows" as Windows
import "controls" as Controls import "controls" as Controls
@ -16,22 +15,11 @@ Windows.Window {
// Don't destroy on close... otherwise the JS/C++ will have a dangling pointer // Don't destroy on close... otherwise the JS/C++ will have a dangling pointer
destroyOnCloseButton: false destroyOnCloseButton: false
property alias source: webview.url property alias source: webview.url
property alias webChannel: webview.webChannel
// A unique identifier to let the HTML JS find the event bridge
// object (our C++ wrapper)
property string uid;
// This is for JS/QML communication, which is unused in a WebWindow,
// but not having this here results in spurious warnings about a
// missing signal
signal sendToScript(var message);
Controls.WebView { Controls.WebView {
id: webview id: webview
url: "about:blank" url: "about:blank"
anchors.fill: parent anchors.fill: parent
focus: true focus: true
onUrlChanged: webview.runJavaScript("EventBridgeUid = \"" + uid + "\";");
Component.onCompleted: webview.runJavaScript("EventBridgeUid = \"" + uid + "\";");
} }
} // dialog } // dialog

View file

@ -37,33 +37,14 @@ Windows.Window {
Repeater { Repeater {
model: 4 model: 4
Tab { Tab {
// Force loading of the content even if the tab is not visible
// (required for letting the C++ code access the webview)
active: true active: true
enabled: false enabled: false;
// we need to store the original url here for future identification
property string originalUrl: ""; property string originalUrl: "";
onEnabledChanged: toolWindow.updateVisiblity();
Controls.WebView { Controls.WebView {
id: webView; id: webView;
// we need to store the original url here for future identification
// A unique identifier to let the HTML JS find the event bridge
// object (our C++ wrapper)
property string uid;
anchors.fill: parent anchors.fill: parent
enabled: false
// This is for JS/QML communication, which is unused in a WebWindow,
// but not having this here results in spurious warnings about a
// missing signal
signal sendToScript(var message);
onUrlChanged: webView.runJavaScript("EventBridgeUid = \"" + uid + "\";");
onEnabledChanged: toolWindow.updateVisiblity();
onLoadingChanged: {
if (loadRequest.status == WebEngineView.LoadSucceededStatus) {
webView.runJavaScript("EventBridgeUid = \"" + uid + "\";");
}
}
} }
} }
} }
@ -132,23 +113,20 @@ Windows.Window {
var tab = tabView.getTab(index); var tab = tabView.getTab(index);
tab.title = ""; tab.title = "";
tab.enabled = false;
tab.originalUrl = ""; tab.originalUrl = "";
tab.item.url = "about:blank"; tab.enabled = false;
tab.item.enabled = false;
} }
function addWebTab(properties) { function addWebTab(properties) {
if (!properties.source) { if (!properties.source) {
console.warn("Attempted to open Web Tool Pane without URL"); console.warn("Attempted to open Web Tool Pane without URL")
return; return;
} }
var existingTabIndex = findIndexForUrl(properties.source); var existingTabIndex = findIndexForUrl(properties.source);
if (existingTabIndex >= 0) { if (existingTabIndex >= 0) {
console.log("Existing tab " + existingTabIndex + " found with URL " + properties.source); console.log("Existing tab " + existingTabIndex + " found with URL " + properties.source)
var tab = tabView.getTab(existingTabIndex); return tabView.getTab(existingTabIndex);
return tab.item;
} }
var freeTabIndex = findFreeTab(); var freeTabIndex = findFreeTab();
@ -157,22 +135,25 @@ Windows.Window {
return; return;
} }
var newTab = tabView.getTab(freeTabIndex);
newTab.title = properties.title || "Unknown";
newTab.originalUrl = properties.source;
newTab.item.url = properties.source;
newTab.active = true;
if (properties.width) { if (properties.width) {
tabView.width = Math.min(Math.max(tabView.width, properties.width), toolWindow.maxSize.x); tabView.width = Math.min(Math.max(tabView.width, properties.width),
toolWindow.maxSize.x);
} }
if (properties.height) { if (properties.height) {
tabView.height = Math.min(Math.max(tabView.height, properties.height), toolWindow.maxSize.y); tabView.height = Math.min(Math.max(tabView.height, properties.height),
toolWindow.maxSize.y);
} }
var tab = tabView.getTab(freeTabIndex); console.log("Updating visibility based on child tab added");
tab.title = properties.title || "Unknown"; newTab.enabledChanged.connect(updateVisiblity)
tab.enabled = true; updateVisiblity();
tab.originalUrl = properties.source; return newTab
var result = tab.item;
result.enabled = true;
result.url = properties.source;
return result;
} }
} }

View file

@ -59,7 +59,6 @@ WebEngineView {
request.openIn(newWindow.webView) request.openIn(newWindow.webView)
} }
// This breaks the webchannel used for passing messages. Fixed in Qt 5.6
// See https://bugreports.qt.io/browse/QTBUG-49521 profile: desktop.browserProfile
//profile: desktop.browserProfile
} }

View file

@ -231,6 +231,13 @@ void Head::simulate(float deltaTime, bool isMine, bool billboard) {
_leftEyePosition = _rightEyePosition = getPosition(); _leftEyePosition = _rightEyePosition = getPosition();
_eyePosition = calculateAverageEyePosition(); _eyePosition = calculateAverageEyePosition();
if (!billboard && _owningAvatar) {
auto skeletonModel = static_cast<Avatar*>(_owningAvatar)->getSkeletonModel();
if (skeletonModel) {
skeletonModel->getEyePositions(_leftEyePosition, _rightEyePosition);
}
}
} }
void Head::calculateMouthShapes() { void Head::calculateMouthShapes() {

View file

@ -897,8 +897,10 @@ void MyAvatar::updateLookAtTargetAvatar() {
// Scale by proportional differences between avatar and human. // Scale by proportional differences between avatar and human.
float humanEyeSeparationInModelSpace = glm::length(humanLeftEye - humanRightEye) * ipdScale; float humanEyeSeparationInModelSpace = glm::length(humanLeftEye - humanRightEye) * ipdScale;
float avatarEyeSeparation = glm::length(avatarLeftEye - avatarRightEye); float avatarEyeSeparation = glm::length(avatarLeftEye - avatarRightEye);
if (avatarEyeSeparation > 0.0f) {
gazeOffset = gazeOffset * humanEyeSeparationInModelSpace / avatarEyeSeparation; gazeOffset = gazeOffset * humanEyeSeparationInModelSpace / avatarEyeSeparation;
} }
}
// And now we can finally add that offset to the camera. // And now we can finally add that offset to the camera.
glm::vec3 corrected = qApp->getViewFrustum()->getPosition() + gazeOffset; glm::vec3 corrected = qApp->getViewFrustum()->getPosition() + gazeOffset;

View file

@ -611,7 +611,7 @@ AnimNode::Pointer AnimNodeLoader::load(const QByteArray& contents, const QUrl& j
return loadNode(rootVal.toObject(), jsonUrl); return loadNode(rootVal.toObject(), jsonUrl);
} }
void AnimNodeLoader::onRequestDone(const QByteArray& data) { void AnimNodeLoader::onRequestDone(const QByteArray data) {
auto node = load(data, _url); auto node = load(data, _url);
if (node) { if (node) {
emit success(node); emit success(node);

View file

@ -36,7 +36,7 @@ protected:
static AnimNode::Pointer load(const QByteArray& contents, const QUrl& jsonUrl); static AnimNode::Pointer load(const QByteArray& contents, const QUrl& jsonUrl);
protected slots: protected slots:
void onRequestDone(const QByteArray& data); void onRequestDone(const QByteArray data);
void onRequestError(QNetworkReply::NetworkError error); void onRequestError(QNetworkReply::NetworkError error);
protected: protected:

View file

@ -565,10 +565,10 @@ void AudioClient::updateReverbOptions() {
_zoneReverbOptions.setReverbTime(_receivedAudioStream.getRevebTime()); _zoneReverbOptions.setReverbTime(_receivedAudioStream.getRevebTime());
reverbChanged = true; reverbChanged = true;
} }
//if (_zoneReverbOptions.getWetLevel() != _receivedAudioStream.getWetLevel()) { if (_zoneReverbOptions.getWetDryMix() != _receivedAudioStream.getWetLevel()) {
// _zoneReverbOptions.setWetLevel(_receivedAudioStream.getWetLevel()); _zoneReverbOptions.setWetDryMix(_receivedAudioStream.getWetLevel());
// reverbChanged = true; reverbChanged = true;
//} }
if (_reverbOptions != &_zoneReverbOptions) { if (_reverbOptions != &_zoneReverbOptions) {
_reverbOptions = &_zoneReverbOptions; _reverbOptions = &_zoneReverbOptions;

View file

@ -113,14 +113,18 @@ QVariantMap RenderableModelEntityItem::parseTexturesToMap(QString textures) {
return _originalTexturesMap; return _originalTexturesMap;
} }
QString jsonTextures = "{\"" + textures.replace(":\"", "\":\"").replace(",\n", ",\"") + "}"; // Legacy: a ,\n-delimited list of filename:"texturepath"
if (*textures.cbegin() != '{') {
textures = "{\"" + textures.replace(":\"", "\":\"").replace(",\n", ",\"") + "}";
}
QJsonParseError error; QJsonParseError error;
QJsonDocument texturesAsJson = QJsonDocument::fromJson(jsonTextures.toUtf8(), &error); QJsonDocument texturesJson = QJsonDocument::fromJson(textures.toUtf8(), &error);
if (error.error != QJsonParseError::NoError) { if (error.error != QJsonParseError::NoError) {
qCWarning(entitiesrenderer) << "Could not evaluate textures property value:" << _textures; qCWarning(entitiesrenderer) << "Could not evaluate textures property value:" << _textures;
return _originalTexturesMap;
} }
QJsonObject texturesAsJsonObject = texturesAsJson.object(); return texturesJson.object().toVariantMap();
return texturesAsJsonObject.toVariantMap();
} }
void RenderableModelEntityItem::remapTextures() { void RenderableModelEntityItem::remapTextures() {

View file

@ -48,6 +48,8 @@ public:
virtual ~RenderablePolyVoxEntityItem(); virtual ~RenderablePolyVoxEntityItem();
void initializePolyVox();
virtual void somethingChangedNotification() { virtual void somethingChangedNotification() {
// This gets called from EnityItem::readEntityDataFromBuffer every time a packet describing // This gets called from EnityItem::readEntityDataFromBuffer every time a packet describing
// this entity comes from the entity-server. It gets called even if nothing has actually changed // this entity comes from the entity-server. It gets called even if nothing has actually changed
@ -114,17 +116,28 @@ public:
virtual void setYPNeighborID(const EntityItemID& yPNeighborID); virtual void setYPNeighborID(const EntityItemID& yPNeighborID);
virtual void setZPNeighborID(const EntityItemID& zPNeighborID); virtual void setZPNeighborID(const EntityItemID& zPNeighborID);
virtual void rebakeMesh();
virtual void updateRegistrationPoint(const glm::vec3& value); virtual void updateRegistrationPoint(const glm::vec3& value);
void setVoxelsFromData(QByteArray uncompressedData, quint16 voxelXSize, quint16 voxelYSize, quint16 voxelZSize);
void forEachVoxelValue(quint16 voxelXSize, quint16 voxelYSize, quint16 voxelZSize,
std::function<void(int, int, int, uint8_t)> thunk);
void setMesh(model::MeshPointer mesh);
void setCollisionPoints(const QVector<QVector<glm::vec3>> points, AABox box);
PolyVox::SimpleVolume<uint8_t>* getVolData() { return _volData; }
uint8_t getVoxelInternal(int x, int y, int z);
bool setVoxelInternal(int x, int y, int z, uint8_t toValue);
void setVolDataDirty() { withWriteLock([&] { _volDataDirty = true; }); }
private: private:
// The PolyVoxEntityItem class has _voxelData which contains dimensions and compressed voxel data. The dimensions // The PolyVoxEntityItem class has _voxelData which contains dimensions and compressed voxel data. The dimensions
// may not match _voxelVolumeSize. // may not match _voxelVolumeSize.
model::MeshPointer _mesh; model::MeshPointer _mesh;
bool _meshDirty; // does collision-shape need to be recomputed? bool _meshDirty { true }; // does collision-shape need to be recomputed?
mutable QReadWriteLock _meshLock{QReadWriteLock::Recursive}; bool _meshInitialized { false };
NetworkTexturePointer _xTexture; NetworkTexturePointer _xTexture;
NetworkTexturePointer _yTexture; NetworkTexturePointer _yTexture;
@ -135,44 +148,35 @@ private:
static gpu::PipelinePointer _pipeline; static gpu::PipelinePointer _pipeline;
ShapeInfo _shapeInfo; ShapeInfo _shapeInfo;
mutable QReadWriteLock _shapeInfoLock;
PolyVox::SimpleVolume<uint8_t>* _volData = nullptr; PolyVox::SimpleVolume<uint8_t>* _volData = nullptr;
mutable QReadWriteLock _volDataLock{QReadWriteLock::Recursive}; // lock for _volData
bool _volDataDirty = false; // does getMesh need to be called? bool _volDataDirty = false; // does getMesh need to be called?
int _onCount; // how many non-zero voxels are in _volData int _onCount; // how many non-zero voxels are in _volData
bool inUserBounds(const PolyVox::SimpleVolume<uint8_t>* vol, PolyVoxEntityItem::PolyVoxSurfaceStyle surfaceStyle, bool _neighborsNeedUpdate { false };
int x, int y, int z) const;
uint8_t getVoxelInternal(int x, int y, int z);
bool setVoxelInternal(int x, int y, int z, uint8_t toValue);
bool updateOnCount(int x, int y, int z, uint8_t toValue); bool updateOnCount(int x, int y, int z, uint8_t toValue);
PolyVox::RaycastResult doRayCast(glm::vec4 originInVoxel, glm::vec4 farInVoxel, glm::vec4& result) const; PolyVox::RaycastResult doRayCast(glm::vec4 originInVoxel, glm::vec4 farInVoxel, glm::vec4& result) const;
// these are run off the main thread // these are run off the main thread
void decompressVolumeData(); void decompressVolumeData();
void decompressVolumeDataAsync();
void compressVolumeDataAndSendEditPacket(); void compressVolumeDataAndSendEditPacket();
void compressVolumeDataAndSendEditPacketAsync(); virtual void getMesh(); // recompute mesh
void getMesh();
void getMeshAsync();
void computeShapeInfoWorker(); void computeShapeInfoWorker();
void computeShapeInfoWorkerAsync();
QSemaphore _threadRunning{1};
// these are cached lookups of _xNNeighborID, _yNNeighborID, _zNNeighborID, _xPNeighborID, _yPNeighborID, _zPNeighborID // these are cached lookups of _xNNeighborID, _yNNeighborID, _zNNeighborID, _xPNeighborID, _yPNeighborID, _zPNeighborID
EntityItemWeakPointer _xNNeighbor; // neighor found by going along negative X axis EntityItemWeakPointer _xNNeighbor; // neighbor found by going along negative X axis
EntityItemWeakPointer _yNNeighbor; EntityItemWeakPointer _yNNeighbor;
EntityItemWeakPointer _zNNeighbor; EntityItemWeakPointer _zNNeighbor;
EntityItemWeakPointer _xPNeighbor; // neighor found by going along positive X axis EntityItemWeakPointer _xPNeighbor; // neighbor found by going along positive X axis
EntityItemWeakPointer _yPNeighbor; EntityItemWeakPointer _yPNeighbor;
EntityItemWeakPointer _zPNeighbor; EntityItemWeakPointer _zPNeighbor;
void clearOutOfDateNeighbors();
void cacheNeighbors(); void cacheNeighbors();
void copyUpperEdgesFromNeighbors(); void copyUpperEdgesFromNeighbors();
void bonkNeighbors(); void bonkNeighbors();
}; };
bool inUserBounds(const PolyVox::SimpleVolume<uint8_t>* vol, PolyVoxEntityItem::PolyVoxSurfaceStyle surfaceStyle,
int x, int y, int z);
#endif // hifi_RenderablePolyVoxEntityItem_h #endif // hifi_RenderablePolyVoxEntityItem_h

View file

@ -101,12 +101,15 @@ void EntitySimulation::expireMortalEntities(const quint64& now) {
prepareEntityForDelete(entity); prepareEntityForDelete(entity);
} else { } else {
if (expiry < _nextExpiry) { if (expiry < _nextExpiry) {
// remeber the smallest _nextExpiry so we know when to start the next search // remember the smallest _nextExpiry so we know when to start the next search
_nextExpiry = expiry; _nextExpiry = expiry;
} }
++itemItr; ++itemItr;
} }
} }
if (_mortalEntities.size() < 1) {
_nextExpiry = -1;
}
} }
} }

View file

@ -64,8 +64,7 @@ PolyVoxEntityItem::PolyVoxEntityItem(const EntityItemID& entityItemID) :
} }
void PolyVoxEntityItem::setVoxelVolumeSize(glm::vec3 voxelVolumeSize) { void PolyVoxEntityItem::setVoxelVolumeSize(glm::vec3 voxelVolumeSize) {
QWriteLocker(&this->_voxelDataLock); withWriteLock([&] {
assert((int)_voxelVolumeSize.x == _voxelVolumeSize.x); assert((int)_voxelVolumeSize.x == _voxelVolumeSize.x);
assert((int)_voxelVolumeSize.y == _voxelVolumeSize.y); assert((int)_voxelVolumeSize.y == _voxelVolumeSize.y);
assert((int)_voxelVolumeSize.z == _voxelVolumeSize.z); assert((int)_voxelVolumeSize.z == _voxelVolumeSize.z);
@ -97,11 +96,15 @@ void PolyVoxEntityItem::setVoxelVolumeSize(glm::vec3 voxelVolumeSize) {
qDebug() << "PolyVoxEntityItem::setVoxelVolumeSize clamping z of" << _voxelVolumeSize.z << "to max"; qDebug() << "PolyVoxEntityItem::setVoxelVolumeSize clamping z of" << _voxelVolumeSize.z << "to max";
_voxelVolumeSize.z = MAX_VOXEL_DIMENSION; _voxelVolumeSize.z = MAX_VOXEL_DIMENSION;
} }
});
} }
const glm::vec3& PolyVoxEntityItem::getVoxelVolumeSize() const { glm::vec3 PolyVoxEntityItem::getVoxelVolumeSize() const {
QWriteLocker locker(&this->_voxelDataLock); glm::vec3 voxelVolumeSize;
return _voxelVolumeSize; withReadLock([&] {
voxelVolumeSize = _voxelVolumeSize;
});
return voxelVolumeSize;
} }
@ -226,12 +229,16 @@ void PolyVoxEntityItem::debugDump() const {
} }
void PolyVoxEntityItem::setVoxelData(QByteArray voxelData) { void PolyVoxEntityItem::setVoxelData(QByteArray voxelData) {
QWriteLocker(&this->_voxelDataLock); withWriteLock([&] {
_voxelData = voxelData; _voxelData = voxelData;
_voxelDataDirty = true; _voxelDataDirty = true;
});
} }
const QByteArray PolyVoxEntityItem::getVoxelData() const { const QByteArray PolyVoxEntityItem::getVoxelData() const {
QReadLocker(&this->_voxelDataLock); QByteArray voxelDataCopy;
return _voxelData; withReadLock([&] {
voxelDataCopy = _voxelData;
});
return voxelDataCopy;
} }

View file

@ -52,7 +52,7 @@ class PolyVoxEntityItem : public EntityItem {
virtual void debugDump() const; virtual void debugDump() const;
virtual void setVoxelVolumeSize(glm::vec3 voxelVolumeSize); virtual void setVoxelVolumeSize(glm::vec3 voxelVolumeSize);
virtual const glm::vec3& getVoxelVolumeSize() const; virtual glm::vec3 getVoxelVolumeSize() const;
virtual void setVoxelData(QByteArray voxelData); virtual void setVoxelData(QByteArray voxelData);
virtual const QByteArray getVoxelData() const; virtual const QByteArray getVoxelData() const;
@ -128,12 +128,14 @@ class PolyVoxEntityItem : public EntityItem {
virtual void rebakeMesh() {}; virtual void rebakeMesh() {};
void setVoxelDataDirty(bool value) { withWriteLock([&] { _voxelDataDirty = value; }); }
virtual void getMesh() {}; // recompute mesh
protected: protected:
glm::vec3 _voxelVolumeSize; // this is always 3 bytes glm::vec3 _voxelVolumeSize; // this is always 3 bytes
mutable QReadWriteLock _voxelDataLock;
QByteArray _voxelData; QByteArray _voxelData;
bool _voxelDataDirty; bool _voxelDataDirty; // _voxelData has changed, things that depend on it should be updated
PolyVoxSurfaceStyle _voxelSurfaceStyle; PolyVoxSurfaceStyle _voxelSurfaceStyle;

View file

@ -293,7 +293,7 @@ void NetworkGeometry::requestModel(const QUrl& url) {
connect(_resource, &Resource::failed, this, &NetworkGeometry::modelRequestError); connect(_resource, &Resource::failed, this, &NetworkGeometry::modelRequestError);
} }
void NetworkGeometry::mappingRequestDone(const QByteArray& data) { void NetworkGeometry::mappingRequestDone(const QByteArray data) {
assert(_state == RequestMappingState); assert(_state == RequestMappingState);
// parse the mapping file // parse the mapping file
@ -325,7 +325,7 @@ void NetworkGeometry::mappingRequestError(QNetworkReply::NetworkError error) {
emit onFailure(*this, MappingRequestError); emit onFailure(*this, MappingRequestError);
} }
void NetworkGeometry::modelRequestDone(const QByteArray& data) { void NetworkGeometry::modelRequestDone(const QByteArray data) {
assert(_state == RequestModelState); assert(_state == RequestModelState);
_state = ParsingModelState; _state = ParsingModelState;

View file

@ -113,10 +113,10 @@ public slots:
void textureLoaded(const QWeakPointer<NetworkTexture>& networkTexture); void textureLoaded(const QWeakPointer<NetworkTexture>& networkTexture);
protected slots: protected slots:
void mappingRequestDone(const QByteArray& data); void mappingRequestDone(const QByteArray data);
void mappingRequestError(QNetworkReply::NetworkError error); void mappingRequestError(QNetworkReply::NetworkError error);
void modelRequestDone(const QByteArray& data); void modelRequestDone(const QByteArray data);
void modelRequestError(QNetworkReply::NetworkError error); void modelRequestError(QNetworkReply::NetworkError error);
void modelParseSuccess(FBXGeometry* geometry); void modelParseSuccess(FBXGeometry* geometry);

View file

@ -81,6 +81,8 @@ AccountManager::AccountManager() :
qRegisterMetaType<QHttpMultiPart*>("QHttpMultiPart*"); qRegisterMetaType<QHttpMultiPart*>("QHttpMultiPart*");
qRegisterMetaType<AccountManagerAuth::Type>();
connect(&_accountInfo, &DataServerAccountInfo::balanceChanged, this, &AccountManager::accountInfoBalanceChanged); connect(&_accountInfo, &DataServerAccountInfo::balanceChanged, this, &AccountManager::accountInfoBalanceChanged);
} }
@ -215,12 +217,13 @@ void AccountManager::sendRequest(const QString& path,
if (thread() != QThread::currentThread()) { if (thread() != QThread::currentThread()) {
QMetaObject::invokeMethod(this, "sendRequest", QMetaObject::invokeMethod(this, "sendRequest",
Q_ARG(const QString&, path), Q_ARG(const QString&, path),
Q_ARG(AccountManagerAuth::Type, AccountManagerAuth::Required), Q_ARG(AccountManagerAuth::Type, authType),
Q_ARG(QNetworkAccessManager::Operation, operation), Q_ARG(QNetworkAccessManager::Operation, operation),
Q_ARG(const JSONCallbackParameters&, callbackParams), Q_ARG(const JSONCallbackParameters&, callbackParams),
Q_ARG(const QByteArray&, dataByteArray), Q_ARG(const QByteArray&, dataByteArray),
Q_ARG(QHttpMultiPart*, dataMultiPart), Q_ARG(QHttpMultiPart*, dataMultiPart),
Q_ARG(QVariantMap, propertyMap)); Q_ARG(QVariantMap, propertyMap));
return;
} }
QNetworkAccessManager& networkAccessManager = NetworkAccessManager::getInstance(); QNetworkAccessManager& networkAccessManager = NetworkAccessManager::getInstance();

View file

@ -198,7 +198,7 @@ public:
signals: signals:
/// Fired when the resource has been downloaded. /// Fired when the resource has been downloaded.
/// This can be used instead of downloadFinished to access data before it is processed. /// This can be used instead of downloadFinished to access data before it is processed.
void loaded(const QByteArray& request); void loaded(const QByteArray request);
/// Fired when the resource has finished loading. /// Fired when the resource has finished loading.
void finished(bool success); void finished(bool success);

View file

@ -58,8 +58,6 @@ void UserActivityLogger::logAction(QString action, QJsonObject details, JSONCall
// if no callbacks specified, call our owns // if no callbacks specified, call our owns
if (params.isEmpty()) { if (params.isEmpty()) {
params.jsonCallbackReceiver = this;
params.jsonCallbackMethod = "requestFinished";
params.errorCallbackReceiver = this; params.errorCallbackReceiver = this;
params.errorCallbackMethod = "requestError"; params.errorCallbackMethod = "requestError";
} }
@ -70,10 +68,6 @@ void UserActivityLogger::logAction(QString action, QJsonObject details, JSONCall
params, NULL, multipart); params, NULL, multipart);
} }
void UserActivityLogger::requestFinished(QNetworkReply& requestReply) {
// qCDebug(networking) << object;
}
void UserActivityLogger::requestError(QNetworkReply& errorReply) { void UserActivityLogger::requestError(QNetworkReply& errorReply) {
qCDebug(networking) << errorReply.error() << "-" << errorReply.errorString(); qCDebug(networking) << errorReply.error() << "-" << errorReply.errorString();
} }

View file

@ -39,7 +39,6 @@ public slots:
void wentTo(QString destinationType, QString destinationName); void wentTo(QString destinationType, QString destinationName);
private slots: private slots:
void requestFinished(QNetworkReply& requestReply);
void requestError(QNetworkReply& errorReply); void requestError(QNetworkReply& errorReply);
private: private:

View file

@ -17,6 +17,7 @@
#include <QtCore/QCoreApplication> #include <QtCore/QCoreApplication>
#include <QtCore/QDateTime> #include <QtCore/QDateTime>
#include <QtCore/QJsonObject>
#include <QtCore/QThread> #include <QtCore/QThread>
#include <LogHandler.h> #include <LogHandler.h>
@ -27,6 +28,7 @@
#include "ControlPacket.h" #include "ControlPacket.h"
#include "Packet.h" #include "Packet.h"
#include "PacketList.h" #include "PacketList.h"
#include "../UserActivityLogger.h"
#include "Socket.h" #include "Socket.h"
using namespace udt; using namespace udt;
@ -328,7 +330,39 @@ void SendQueue::run() {
nextPacketTimestamp += std::chrono::microseconds(nextPacketDelta); nextPacketTimestamp += std::chrono::microseconds(nextPacketDelta);
// sleep as long as we need until next packet send, if we can // sleep as long as we need until next packet send, if we can
const auto timeToSleep = duration_cast<microseconds>(nextPacketTimestamp - p_high_resolution_clock::now()); auto now = p_high_resolution_clock::now();
auto timeToSleep = duration_cast<microseconds>(nextPacketTimestamp - now);
// we're seeing SendQueues sleep for a long period of time here,
// which can lock the NodeList if it's attempting to clear connections
// for now we guard this by capping the time this thread and sleep for
const microseconds MAX_SEND_QUEUE_SLEEP_USECS { 2000000 };
if (timeToSleep > MAX_SEND_QUEUE_SLEEP_USECS) {
qWarning() << "udt::SendQueue wanted to sleep for" << timeToSleep.count() << "microseconds";
qWarning() << "Capping sleep to" << MAX_SEND_QUEUE_SLEEP_USECS.count();
qWarning() << "PSP:" << _packetSendPeriod << "NPD:" << nextPacketDelta
<< "NPT:" << nextPacketTimestamp.time_since_epoch().count()
<< "NOW:" << now.time_since_epoch().count();
// alright, we're in a weird state
// we want to know why this is happening so we can implement a better fix than this guard
// send some details up to the API (if the user allows us) that indicate how we could such a large timeToSleep
static const QString SEND_QUEUE_LONG_SLEEP_ACTION = "sendqueue-sleep";
// setup a json object with the details we want
QJsonObject longSleepObject;
longSleepObject["timeToSleep"] = qint64(timeToSleep.count());
longSleepObject["packetSendPeriod"] = _packetSendPeriod.load();
longSleepObject["nextPacketDelta"] = nextPacketDelta;
longSleepObject["nextPacketTimestamp"] = qint64(nextPacketTimestamp.time_since_epoch().count());
longSleepObject["then"] = qint64(now.time_since_epoch().count());
// hopefully send this event using the user activity logger
UserActivityLogger::getInstance().logAction(SEND_QUEUE_LONG_SLEEP_ACTION, longSleepObject);
timeToSleep = MAX_SEND_QUEUE_SLEEP_USECS;
}
std::this_thread::sleep_for(timeToSleep); std::this_thread::sleep_for(timeToSleep);
} }

View file

@ -57,6 +57,7 @@ void PhysicalEntitySimulation::addEntityInternal(EntityItemPointer entity) {
} }
void PhysicalEntitySimulation::removeEntityInternal(EntityItemPointer entity) { void PhysicalEntitySimulation::removeEntityInternal(EntityItemPointer entity) {
if (entity->isSimulated()) {
EntitySimulation::removeEntityInternal(entity); EntitySimulation::removeEntityInternal(entity);
QMutexLocker lock(&_mutex); QMutexLocker lock(&_mutex);
_entitiesToAddToPhysics.remove(entity); _entitiesToAddToPhysics.remove(entity);
@ -68,6 +69,7 @@ void PhysicalEntitySimulation::removeEntityInternal(EntityItemPointer entity) {
} else { } else {
_entitiesToDelete.insert(entity); _entitiesToDelete.insert(entity);
} }
}
} }
void PhysicalEntitySimulation::takeEntitiesToDelete(VectorOfEntities& entitiesToDelete) { void PhysicalEntitySimulation::takeEntitiesToDelete(VectorOfEntities& entitiesToDelete) {
@ -175,7 +177,7 @@ void PhysicalEntitySimulation::getObjectsToRemoveFromPhysics(VectorOfMotionState
_entitiesToRelease.insert(entity); _entitiesToRelease.insert(entity);
} }
if (entity->isSimulated() && entity->isDead()) { if (entity->isDead()) {
_entitiesToDelete.insert(entity); _entitiesToDelete.insert(entity);
} }
} }
@ -190,7 +192,7 @@ void PhysicalEntitySimulation::deleteObjectsRemovedFromPhysics() {
entity->setPhysicsInfo(nullptr); entity->setPhysicsInfo(nullptr);
delete motionState; delete motionState;
if (entity->isSimulated() && entity->isDead()) { if (entity->isDead()) {
_entitiesToDelete.insert(entity); _entitiesToDelete.insert(entity);
} }
} }

View file

@ -114,6 +114,7 @@ void FetchSpatialTree::run(const SceneContextPointer& sceneContext, const Render
void CullSpatialSelection::configure(const Config& config) { void CullSpatialSelection::configure(const Config& config) {
_justFrozeFrustum = _justFrozeFrustum || (config.freezeFrustum && !_freezeFrustum); _justFrozeFrustum = _justFrozeFrustum || (config.freezeFrustum && !_freezeFrustum);
_freezeFrustum = config.freezeFrustum; _freezeFrustum = config.freezeFrustum;
_skipCulling = config.skipCulling;
} }
void CullSpatialSelection::run(const SceneContextPointer& sceneContext, const RenderContextPointer& renderContext, void CullSpatialSelection::run(const SceneContextPointer& sceneContext, const RenderContextPointer& renderContext,
@ -191,6 +192,57 @@ void CullSpatialSelection::run(const SceneContextPointer& sceneContext, const Re
// visibility cull if partially selected ( octree cell contianing it was partial) // visibility cull if partially selected ( octree cell contianing it was partial)
// distance cull if was a subcell item ( octree cell is way bigger than the item bound itself, so now need to test per item) // distance cull if was a subcell item ( octree cell is way bigger than the item bound itself, so now need to test per item)
if (_skipCulling) {
// inside & fit items: filter only, culling is disabled
{
PerformanceTimer perfTimer("insideFitItems");
for (auto id : inSelection.insideItems) {
auto& item = scene->getItem(id);
if (_filter.test(item.getKey())) {
ItemBound itemBound(id, item.getBound());
outItems.emplace_back(itemBound);
}
}
}
// inside & subcell items: filter only, culling is disabled
{
PerformanceTimer perfTimer("insideSmallItems");
for (auto id : inSelection.insideSubcellItems) {
auto& item = scene->getItem(id);
if (_filter.test(item.getKey())) {
ItemBound itemBound(id, item.getBound());
outItems.emplace_back(itemBound);
}
}
}
// partial & fit items: filter only, culling is disabled
{
PerformanceTimer perfTimer("partialFitItems");
for (auto id : inSelection.partialItems) {
auto& item = scene->getItem(id);
if (_filter.test(item.getKey())) {
ItemBound itemBound(id, item.getBound());
outItems.emplace_back(itemBound);
}
}
}
// partial & subcell items: filter only, culling is disabled
{
PerformanceTimer perfTimer("partialSmallItems");
for (auto id : inSelection.partialSubcellItems) {
auto& item = scene->getItem(id);
if (_filter.test(item.getKey())) {
ItemBound itemBound(id, item.getBound());
outItems.emplace_back(itemBound);
}
}
}
} else {
// inside & fit items: easy, just filter // inside & fit items: easy, just filter
{ {
PerformanceTimer perfTimer("insideFitItems"); PerformanceTimer perfTimer("insideFitItems");
@ -246,6 +298,7 @@ void CullSpatialSelection::run(const SceneContextPointer& sceneContext, const Re
} }
} }
} }
}
details._rendered += (int)outItems.size(); details._rendered += (int)outItems.size();

View file

@ -70,14 +70,16 @@ namespace render {
Q_OBJECT Q_OBJECT
Q_PROPERTY(int numItems READ getNumItems) Q_PROPERTY(int numItems READ getNumItems)
Q_PROPERTY(bool freezeFrustum MEMBER freezeFrustum WRITE setFreezeFrustum) Q_PROPERTY(bool freezeFrustum MEMBER freezeFrustum WRITE setFreezeFrustum)
Q_PROPERTY(bool skipCulling MEMBER skipCulling WRITE setSkipCulling)
public: public:
int numItems{ 0 }; int numItems{ 0 };
int getNumItems() { return numItems; } int getNumItems() { return numItems; }
bool freezeFrustum{ false }; bool freezeFrustum{ false };
bool skipCulling{ false };
public slots: public slots:
void setFreezeFrustum(bool enabled) { freezeFrustum = enabled; emit dirty(); } void setFreezeFrustum(bool enabled) { freezeFrustum = enabled; emit dirty(); }
void setSkipCulling(bool enabled) { skipCulling = enabled; emit dirty(); }
signals: signals:
void dirty(); void dirty();
}; };
@ -85,6 +87,7 @@ namespace render {
class CullSpatialSelection { class CullSpatialSelection {
bool _freezeFrustum{ false }; // initialized by Config bool _freezeFrustum{ false }; // initialized by Config
bool _justFrozeFrustum{ false }; bool _justFrozeFrustum{ false };
bool _skipCulling{ false };
ViewFrustum _frozenFrutstum; ViewFrustum _frozenFrutstum;
public: public:
using Config = CullSpatialSelectionConfig; using Config = CullSpatialSelectionConfig;

View file

@ -14,8 +14,6 @@
#include <QtQml/QQmlContext> #include <QtQml/QQmlContext>
#include <QtWebChannel/QWebChannel>
#include <QtScript/QScriptContext> #include <QtScript/QScriptContext>
#include <QtScript/QScriptEngine> #include <QtScript/QScriptEngine>
@ -37,29 +35,8 @@ QScriptValue QmlWebWindowClass::constructor(QScriptContext* context, QScriptEngi
} }
QmlWebWindowClass::QmlWebWindowClass(QObject* qmlWindow) : QmlWindowClass(qmlWindow) { QmlWebWindowClass::QmlWebWindowClass(QObject* qmlWindow) : QmlWindowClass(qmlWindow) {
_uid = QUuid::createUuid().toString();
asQuickItem()->setProperty("uid", _uid);
auto webchannelVar = qmlWindow->property("webChannel");
_webchannel = qvariant_cast<QWebChannel*>(webchannelVar);
Q_ASSERT(_webchannel);
_webchannel->registerObject(_uid, this);
} }
void QmlWebWindowClass::emitScriptEvent(const QVariant& scriptMessage) {
if (QThread::currentThread() != thread()) {
QMetaObject::invokeMethod(this, "emitScriptEvent", Qt::QueuedConnection, Q_ARG(QVariant, scriptMessage));
} else {
emit scriptEventReceived(scriptMessage);
}
}
void QmlWebWindowClass::emitWebEvent(const QVariant& webMessage) {
if (QThread::currentThread() != thread()) {
QMetaObject::invokeMethod(this, "emitWebEvent", Qt::QueuedConnection, Q_ARG(QVariant, webMessage));
} else {
emit webEventReceived(webMessage);
}
}
QString QmlWebWindowClass::getURL() const { QString QmlWebWindowClass::getURL() const {
QVariant result = DependencyManager::get<OffscreenUi>()->returnFromUiThread([&]()->QVariant { QVariant result = DependencyManager::get<OffscreenUi>()->returnFromUiThread([&]()->QVariant {

View file

@ -11,13 +11,10 @@
#include "QmlWindowClass.h" #include "QmlWindowClass.h"
class QWebChannel;
// FIXME refactor this class to be a QQuickItem derived type and eliminate the needless wrapping // FIXME refactor this class to be a QQuickItem derived type and eliminate the needless wrapping
class QmlWebWindowClass : public QmlWindowClass { class QmlWebWindowClass : public QmlWindowClass {
Q_OBJECT Q_OBJECT
Q_PROPERTY(QString url READ getURL CONSTANT) Q_PROPERTY(QString url READ getURL CONSTANT)
Q_PROPERTY(QString uid READ getUid CONSTANT)
public: public:
static QScriptValue constructor(QScriptContext* context, QScriptEngine* engine); static QScriptValue constructor(QScriptContext* context, QScriptEngine* engine);
@ -26,18 +23,9 @@ public:
public slots: public slots:
QString getURL() const; QString getURL() const;
void setURL(const QString& url); void setURL(const QString& url);
const QString& getUid() const { return _uid; }
void emitScriptEvent(const QVariant& scriptMessage);
void emitWebEvent(const QVariant& webMessage);
signals: signals:
void urlChanged(); void urlChanged();
void scriptEventReceived(const QVariant& message);
void webEventReceived(const QVariant& message);
private:
QString _uid;
QWebChannel* _webchannel { nullptr };
}; };
#endif #endif

View file

@ -26,6 +26,10 @@
#include "OffscreenUi.h" #include "OffscreenUi.h"
QWebSocketServer* QmlWindowClass::_webChannelServer { nullptr };
static QWebChannel webChannel;
static const uint16_t WEB_CHANNEL_PORT = 51016;
static std::atomic<int> nextWindowId;
static const char* const SOURCE_PROPERTY = "source"; static const char* const SOURCE_PROPERTY = "source";
static const char* const TITLE_PROPERTY = "title"; static const char* const TITLE_PROPERTY = "title";
static const char* const WIDTH_PROPERTY = "width"; static const char* const WIDTH_PROPERTY = "width";
@ -33,6 +37,54 @@ static const char* const HEIGHT_PROPERTY = "height";
static const char* const VISIBILE_PROPERTY = "visible"; static const char* const VISIBILE_PROPERTY = "visible";
static const char* const TOOLWINDOW_PROPERTY = "toolWindow"; static const char* const TOOLWINDOW_PROPERTY = "toolWindow";
void QmlScriptEventBridge::emitWebEvent(const QString& data) {
QMetaObject::invokeMethod(this, "webEventReceived", Qt::QueuedConnection, Q_ARG(QString, data));
}
void QmlScriptEventBridge::emitScriptEvent(const QString& data) {
QMetaObject::invokeMethod(this, "scriptEventReceived", Qt::QueuedConnection,
Q_ARG(int, _webWindow->getWindowId()), Q_ARG(QString, data));
}
class QmlWebTransport : public QWebChannelAbstractTransport {
Q_OBJECT
public:
QmlWebTransport(QWebSocket* webSocket) : _webSocket(webSocket) {
// Translate from the websocket layer to the webchannel layer
connect(webSocket, &QWebSocket::textMessageReceived, [this](const QString& message) {
QJsonParseError error;
QJsonDocument document = QJsonDocument::fromJson(message.toUtf8(), &error);
if (error.error || !document.isObject()) {
qWarning() << "Unable to parse incoming JSON message" << message;
return;
}
emit messageReceived(document.object(), this);
});
}
virtual void sendMessage(const QJsonObject &message) override {
// Translate from the webchannel layer to the websocket layer
_webSocket->sendTextMessage(QJsonDocument(message).toJson(QJsonDocument::Compact));
}
private:
QWebSocket* const _webSocket;
};
void QmlWindowClass::setupServer() {
if (!_webChannelServer) {
_webChannelServer = new QWebSocketServer("EventBridge Server", QWebSocketServer::NonSecureMode);
if (!_webChannelServer->listen(QHostAddress::LocalHost, WEB_CHANNEL_PORT)) {
qFatal("Failed to open web socket server.");
}
QObject::connect(_webChannelServer, &QWebSocketServer::newConnection, [] {
webChannel.connectTo(new QmlWebTransport(_webChannelServer->nextPendingConnection()));
});
}
}
QScriptValue QmlWindowClass::internalConstructor(const QString& qmlSource, QScriptValue QmlWindowClass::internalConstructor(const QString& qmlSource,
QScriptContext* context, QScriptEngine* engine, QScriptContext* context, QScriptEngine* engine,
std::function<QmlWindowClass*(QObject*)> builder) std::function<QmlWindowClass*(QObject*)> builder)
@ -116,8 +168,10 @@ QScriptValue QmlWindowClass::internalConstructor(const QString& qmlSource,
} }
offscreenUi->returnFromUiThread([&] { offscreenUi->returnFromUiThread([&] {
setupServer();
retVal = builder(newTab); retVal = builder(newTab);
retVal->_toolWindow = true; retVal->_toolWindow = true;
registerObject(url.toLower(), retVal);
return QVariant(); return QVariant();
}); });
} else { } else {
@ -125,8 +179,10 @@ QScriptValue QmlWindowClass::internalConstructor(const QString& qmlSource,
QMetaObject::invokeMethod(offscreenUi.data(), "load", Qt::BlockingQueuedConnection, QMetaObject::invokeMethod(offscreenUi.data(), "load", Qt::BlockingQueuedConnection,
Q_ARG(const QString&, qmlSource), Q_ARG(const QString&, qmlSource),
Q_ARG(std::function<void(QQmlContext*, QObject*)>, [&](QQmlContext* context, QObject* object) { Q_ARG(std::function<void(QQmlContext*, QObject*)>, [&](QQmlContext* context, QObject* object) {
setupServer();
retVal = builder(object); retVal = builder(object);
context->engine()->setObjectOwnership(retVal->_qmlWindow, QQmlEngine::CppOwnership); context->engine()->setObjectOwnership(retVal->_qmlWindow, QQmlEngine::CppOwnership);
registerObject(url.toLower(), retVal);
if (!title.isEmpty()) { if (!title.isEmpty()) {
retVal->setTitle(title); retVal->setTitle(title);
} }
@ -153,7 +209,10 @@ QScriptValue QmlWindowClass::constructor(QScriptContext* context, QScriptEngine*
}); });
} }
QmlWindowClass::QmlWindowClass(QObject* qmlWindow) : _qmlWindow(qmlWindow) { QmlWindowClass::QmlWindowClass(QObject* qmlWindow)
: _windowId(++nextWindowId), _qmlWindow(qmlWindow)
{
qDebug() << "Created window with ID " << _windowId;
Q_ASSERT(_qmlWindow); Q_ASSERT(_qmlWindow);
Q_ASSERT(dynamic_cast<const QQuickItem*>(_qmlWindow.data())); Q_ASSERT(dynamic_cast<const QQuickItem*>(_qmlWindow.data()));
// Forward messages received from QML on to the script // Forward messages received from QML on to the script
@ -169,6 +228,14 @@ QmlWindowClass::~QmlWindowClass() {
close(); close();
} }
void QmlWindowClass::registerObject(const QString& name, QObject* object) {
webChannel.registerObject(name, object);
}
void QmlWindowClass::deregisterObject(QObject* object) {
webChannel.deregisterObject(object);
}
QQuickItem* QmlWindowClass::asQuickItem() const { QQuickItem* QmlWindowClass::asQuickItem() const {
if (_toolWindow) { if (_toolWindow) {
return DependencyManager::get<OffscreenUi>()->getToolWindow(); return DependencyManager::get<OffscreenUi>()->getToolWindow();

View file

@ -13,16 +13,38 @@
#include <QtCore/QPointer> #include <QtCore/QPointer>
#include <QtScript/QScriptValue> #include <QtScript/QScriptValue>
#include <QtQuick/QQuickItem> #include <QtQuick/QQuickItem>
#include <QtWebChannel/QWebChannelAbstractTransport>
#include <GLMHelpers.h> #include <GLMHelpers.h>
class QScriptEngine; class QScriptEngine;
class QScriptContext; class QScriptContext;
class QmlWindowClass;
class QWebSocketServer;
class QWebSocket;
class QmlScriptEventBridge : public QObject {
Q_OBJECT
public:
QmlScriptEventBridge(const QmlWindowClass* webWindow) : _webWindow(webWindow) {}
public slots :
void emitWebEvent(const QString& data);
void emitScriptEvent(const QString& data);
signals:
void webEventReceived(const QString& data);
void scriptEventReceived(int windowId, const QString& data);
private:
const QmlWindowClass* _webWindow { nullptr };
QWebSocket *_socket { nullptr };
};
// FIXME refactor this class to be a QQuickItem derived type and eliminate the needless wrapping // FIXME refactor this class to be a QQuickItem derived type and eliminate the needless wrapping
class QmlWindowClass : public QObject { class QmlWindowClass : public QObject {
Q_OBJECT Q_OBJECT
Q_PROPERTY(QObject* eventBridge READ getEventBridge CONSTANT) Q_PROPERTY(QObject* eventBridge READ getEventBridge CONSTANT)
Q_PROPERTY(int windowId READ getWindowId CONSTANT)
Q_PROPERTY(glm::vec2 position READ getPosition WRITE setPosition NOTIFY positionChanged) Q_PROPERTY(glm::vec2 position READ getPosition WRITE setPosition NOTIFY positionChanged)
Q_PROPERTY(glm::vec2 size READ getSize WRITE setSize NOTIFY sizeChanged) Q_PROPERTY(glm::vec2 size READ getSize WRITE setSize NOTIFY sizeChanged)
Q_PROPERTY(bool visible READ isVisible WRITE setVisible NOTIFY visibilityChanged) Q_PROPERTY(bool visible READ isVisible WRITE setVisible NOTIFY visibilityChanged)
@ -47,7 +69,8 @@ public slots:
Q_INVOKABLE void raise(); Q_INVOKABLE void raise();
Q_INVOKABLE void close(); Q_INVOKABLE void close();
Q_INVOKABLE QObject* getEventBridge() { return this; }; Q_INVOKABLE int getWindowId() const { return _windowId; };
Q_INVOKABLE QmlScriptEventBridge* getEventBridge() const { return _eventBridge; };
// Scripts can use this to send a message to the QML object // Scripts can use this to send a message to the QML object
void sendToQml(const QVariant& message); void sendToQml(const QVariant& message);
@ -69,12 +92,18 @@ protected:
static QScriptValue internalConstructor(const QString& qmlSource, static QScriptValue internalConstructor(const QString& qmlSource,
QScriptContext* context, QScriptEngine* engine, QScriptContext* context, QScriptEngine* engine,
std::function<QmlWindowClass*(QObject*)> function); std::function<QmlWindowClass*(QObject*)> function);
static void setupServer();
static void registerObject(const QString& name, QObject* object);
static void deregisterObject(QObject* object);
static QWebSocketServer* _webChannelServer;
QQuickItem* asQuickItem() const; QQuickItem* asQuickItem() const;
QmlScriptEventBridge* const _eventBridge { new QmlScriptEventBridge(this) };
// FIXME needs to be initialized in the ctor once we have support // FIXME needs to be initialized in the ctor once we have support
// for tool window panes in QML // for tool window panes in QML
bool _toolWindow { false }; bool _toolWindow { false };
const int _windowId;
QPointer<QObject> _qmlWindow; QPointer<QObject> _qmlWindow;
QString _source; QString _source;
}; };

Binary file not shown.

Before

Width:  |  Height:  |  Size: 61 KiB

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 320 KiB

After

Width:  |  Height:  |  Size: 110 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 65 KiB

After

Width:  |  Height:  |  Size: 43 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 271 KiB

After

Width:  |  Height:  |  Size: 78 KiB

View file

@ -44,6 +44,14 @@ ready = function() {
var domainServer = remote.getGlobal('domainServer'); var domainServer = remote.getGlobal('domainServer');
var acMonitor = remote.getGlobal('acMonitor'); var acMonitor = remote.getGlobal('acMonitor');
var pendingLines = {
'ds': new Array(),
'ac': new Array()
};
var UPDATE_INTERVAL = 16; // Update log at ~60 fps
var interval = setInterval(flushPendingLines, UPDATE_INTERVAL);
var logWatchers = { var logWatchers = {
'ds': { 'ds': {
}, },
@ -83,7 +91,7 @@ ready = function() {
var logTail = new Tail(cleanFilePath, '\n', { start: start, interval: 500 }); var logTail = new Tail(cleanFilePath, '\n', { start: start, interval: 500 });
logTail.on('line', function(msg) { logTail.on('line', function(msg) {
appendLogMessage(msg, stream); pendingLines[stream].push(msg);
}); });
logTail.on('error', function(error) { logTail.on('error', function(error) {
@ -107,6 +115,7 @@ ready = function() {
} }
window.onbeforeunload = function(e) { window.onbeforeunload = function(e) {
clearInterval(interval);
domainServer.removeListener('logs-updated', updateLogFiles); domainServer.removeListener('logs-updated', updateLogFiles);
acMonitor.removeListener('logs-updated', updateLogFiles); acMonitor.removeListener('logs-updated', updateLogFiles);
}; };
@ -164,14 +173,23 @@ ready = function() {
return !filter || message.toLowerCase().indexOf(filter) >= 0; return !filter || message.toLowerCase().indexOf(filter) >= 0;
} }
function appendLogMessage(msg, name) { function appendLogMessages(name) {
var array = pendingLines[name];
if (array.length === 0) {
return;
}
if (array.length > maxLogLines) {
array = array.slice(-maxLogLines);
}
console.log(name, array.length);
var id = name == "ds" ? "domain-server" : "assignment-client"; var id = name == "ds" ? "domain-server" : "assignment-client";
var $pidLog = $('#' + id); var $pidLog = $('#' + id);
var size = ++tabStates[id].size; var size = tabStates[id].size + array.length;
if (size > maxLogLines) { if (size > maxLogLines) {
$pidLog.find('div.log-line:first').remove(); $pidLog.find('div.log-line:lt(' + (size - maxLogLines) + ')').remove();
removed = true;
} }
var wasAtBottom = false; var wasAtBottom = false;
@ -179,17 +197,25 @@ ready = function() {
wasAtBottom = $pidLog[0].scrollTop >= ($pidLog[0].scrollHeight - $pidLog.height()); wasAtBottom = $pidLog[0].scrollTop >= ($pidLog[0].scrollHeight - $pidLog.height());
} }
var $logLine = $('<div class="log-line">').text(msg); for (line in array) {
if (!shouldDisplayLogMessage(msg)) { var $logLine = $('<div class="log-line">').text(array[line]);
if (!shouldDisplayLogMessage(array[line])) {
$logLine.hide(); $logLine.hide();
} }
$pidLog.append($logLine); $pidLog.append($logLine);
}
delete pendingLines[name];
pendingLines[name] = new Array();
if (wasAtBottom) { if (wasAtBottom) {
$pidLog.scrollTop($pidLog[0].scrollHeight); $pidLog.scrollTop($pidLog[0].scrollHeight);
} }
}
function flushPendingLines() {
appendLogMessages('ds');
appendLogMessages('ac');
} }
// handle filtering of table rows on input change // handle filtering of table rows on input change