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;
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;
}
}

View file

@ -308,7 +308,7 @@
"name": "reverb",
"type": "table",
"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,
"columns": [
{
@ -325,9 +325,9 @@
},
{
"name": "wet_level",
"label": "Wet Level",
"label": "Wet/Dry Mix",
"can_set": true,
"placeholder": "(in db)"
"placeholder": "(in percent)"
}
]
}

View file

@ -10,11 +10,50 @@
var EventBridge;
openEventBridge = function(callback) {
new QWebChannel(qt.webChannelTransport, function(channel) {
console.log("uid " + EventBridgeUid);
EventBridge = channel.objects[EventBridgeUid];
callback(EventBridge);
EventBridgeConnectionProxy = function(parent) {
this.parent = parent;
this.realSignal = this.parent.realBridge.scriptEventReceived
this.webWindowId = this.parent.webWindow.windowId;
}
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="qrc:///qtwebchannel/qwebchannel.js"></script>
<script type="text/javascript" src="eventBridgeLoader.js"></script>
<script>
var myBridge;
window.onload = function() {
openEventBridge(function() {
EventBridge.scriptEventReceived.connect(function(message) {
openEventBridge(function(eventBridge) {
myBridge = eventBridge;
myBridge.scriptEventReceived.connect(function(message) {
console.log("HTML side received message: " + message);
});
});
}
testClick = function() {
EventBridge.emitWebEvent(["Foo", "Bar", { "baz": 1} ]);
myBridge.emitWebEvent("HTML side sending message - button click");
}
</script>
</head>

View file

@ -8,14 +8,26 @@ webWindow.eventBridge.webEventReceived.connect(function(data) {
print("JS Side event received: " + data);
});
var titles = ["A", "B", "C"];
var titleIndex = 0;
Script.setInterval(function() {
var message = [ Math.random(), Math.random() ];
print("JS Side sending: " + message);
webWindow.emitScriptEvent(message);
}, 5 * 1000);
webWindow.eventBridge.emitScriptEvent("JS Event sent");
var size = webWindow.size;
var position = webWindow.position;
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.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.Controls 1.2
import QtWebEngine 1.1
import QtWebChannel 1.0
import "windows" as Windows
import "controls" as Controls
@ -16,22 +15,11 @@ Windows.Window {
// Don't destroy on close... otherwise the JS/C++ will have a dangling pointer
destroyOnCloseButton: false
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 {
id: webview
url: "about:blank"
anchors.fill: parent
focus: true
onUrlChanged: webview.runJavaScript("EventBridgeUid = \"" + uid + "\";");
Component.onCompleted: webview.runJavaScript("EventBridgeUid = \"" + uid + "\";");
}
} // dialog

View file

@ -37,33 +37,14 @@ Windows.Window {
Repeater {
model: 4
Tab {
// Force loading of the content even if the tab is not visible
// (required for letting the C++ code access the webview)
active: true
enabled: false
enabled: false;
// we need to store the original url here for future identification
property string originalUrl: "";
onEnabledChanged: toolWindow.updateVisiblity();
Controls.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
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);
tab.title = "";
tab.enabled = false;
tab.originalUrl = "";
tab.item.url = "about:blank";
tab.item.enabled = false;
tab.enabled = false;
}
function addWebTab(properties) {
if (!properties.source) {
console.warn("Attempted to open Web Tool Pane without URL");
console.warn("Attempted to open Web Tool Pane without URL")
return;
}
var existingTabIndex = findIndexForUrl(properties.source);
if (existingTabIndex >= 0) {
console.log("Existing tab " + existingTabIndex + " found with URL " + properties.source);
var tab = tabView.getTab(existingTabIndex);
return tab.item;
console.log("Existing tab " + existingTabIndex + " found with URL " + properties.source)
return tabView.getTab(existingTabIndex);
}
var freeTabIndex = findFreeTab();
@ -157,22 +135,25 @@ Windows.Window {
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) {
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) {
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);
tab.title = properties.title || "Unknown";
tab.enabled = true;
tab.originalUrl = properties.source;
var result = tab.item;
result.enabled = true;
result.url = properties.source;
return result;
console.log("Updating visibility based on child tab added");
newTab.enabledChanged.connect(updateVisiblity)
updateVisiblity();
return newTab
}
}

View file

@ -59,7 +59,6 @@ WebEngineView {
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();
_eyePosition = calculateAverageEyePosition();
if (!billboard && _owningAvatar) {
auto skeletonModel = static_cast<Avatar*>(_owningAvatar)->getSkeletonModel();
if (skeletonModel) {
skeletonModel->getEyePositions(_leftEyePosition, _rightEyePosition);
}
}
}
void Head::calculateMouthShapes() {

View file

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

View file

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

View file

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

View file

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

View file

@ -113,14 +113,18 @@ QVariantMap RenderableModelEntityItem::parseTexturesToMap(QString textures) {
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;
QJsonDocument texturesAsJson = QJsonDocument::fromJson(jsonTextures.toUtf8(), &error);
QJsonDocument texturesJson = QJsonDocument::fromJson(textures.toUtf8(), &error);
if (error.error != QJsonParseError::NoError) {
qCWarning(entitiesrenderer) << "Could not evaluate textures property value:" << _textures;
return _originalTexturesMap;
}
QJsonObject texturesAsJsonObject = texturesAsJson.object();
return texturesAsJsonObject.toVariantMap();
return texturesJson.object().toVariantMap();
}
void RenderableModelEntityItem::remapTextures() {

File diff suppressed because it is too large Load diff

View file

@ -48,6 +48,8 @@ public:
virtual ~RenderablePolyVoxEntityItem();
void initializePolyVox();
virtual void somethingChangedNotification() {
// 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
@ -114,17 +116,28 @@ public:
virtual void setYPNeighborID(const EntityItemID& yPNeighborID);
virtual void setZPNeighborID(const EntityItemID& zPNeighborID);
virtual void rebakeMesh();
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:
// The PolyVoxEntityItem class has _voxelData which contains dimensions and compressed voxel data. The dimensions
// may not match _voxelVolumeSize.
model::MeshPointer _mesh;
bool _meshDirty; // does collision-shape need to be recomputed?
mutable QReadWriteLock _meshLock{QReadWriteLock::Recursive};
bool _meshDirty { true }; // does collision-shape need to be recomputed?
bool _meshInitialized { false };
NetworkTexturePointer _xTexture;
NetworkTexturePointer _yTexture;
@ -135,44 +148,35 @@ private:
static gpu::PipelinePointer _pipeline;
ShapeInfo _shapeInfo;
mutable QReadWriteLock _shapeInfoLock;
PolyVox::SimpleVolume<uint8_t>* _volData = nullptr;
mutable QReadWriteLock _volDataLock{QReadWriteLock::Recursive}; // lock for _volData
bool _volDataDirty = false; // does getMesh need to be called?
int _onCount; // how many non-zero voxels are in _volData
bool inUserBounds(const PolyVox::SimpleVolume<uint8_t>* vol, PolyVoxEntityItem::PolyVoxSurfaceStyle surfaceStyle,
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 _neighborsNeedUpdate { false };
bool updateOnCount(int x, int y, int z, uint8_t toValue);
PolyVox::RaycastResult doRayCast(glm::vec4 originInVoxel, glm::vec4 farInVoxel, glm::vec4& result) const;
// these are run off the main thread
void decompressVolumeData();
void decompressVolumeDataAsync();
void compressVolumeDataAndSendEditPacket();
void compressVolumeDataAndSendEditPacketAsync();
void getMesh();
void getMeshAsync();
virtual void getMesh(); // recompute mesh
void computeShapeInfoWorker();
void computeShapeInfoWorkerAsync();
QSemaphore _threadRunning{1};
// 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 _zNNeighbor;
EntityItemWeakPointer _xPNeighbor; // neighor found by going along positive X axis
EntityItemWeakPointer _xPNeighbor; // neighbor found by going along positive X axis
EntityItemWeakPointer _yPNeighbor;
EntityItemWeakPointer _zPNeighbor;
void clearOutOfDateNeighbors();
void cacheNeighbors();
void copyUpperEdgesFromNeighbors();
void bonkNeighbors();
};
bool inUserBounds(const PolyVox::SimpleVolume<uint8_t>* vol, PolyVoxEntityItem::PolyVoxSurfaceStyle surfaceStyle,
int x, int y, int z);
#endif // hifi_RenderablePolyVoxEntityItem_h

View file

@ -101,12 +101,15 @@ void EntitySimulation::expireMortalEntities(const quint64& now) {
prepareEntityForDelete(entity);
} else {
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;
}
++itemItr;
}
}
if (_mortalEntities.size() < 1) {
_nextExpiry = -1;
}
}
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -198,7 +198,7 @@ public:
signals:
/// Fired when the resource has been downloaded.
/// 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.
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 (params.isEmpty()) {
params.jsonCallbackReceiver = this;
params.jsonCallbackMethod = "requestFinished";
params.errorCallbackReceiver = this;
params.errorCallbackMethod = "requestError";
}
@ -70,10 +68,6 @@ void UserActivityLogger::logAction(QString action, QJsonObject details, JSONCall
params, NULL, multipart);
}
void UserActivityLogger::requestFinished(QNetworkReply& requestReply) {
// qCDebug(networking) << object;
}
void UserActivityLogger::requestError(QNetworkReply& errorReply) {
qCDebug(networking) << errorReply.error() << "-" << errorReply.errorString();
}

View file

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

View file

@ -17,6 +17,7 @@
#include <QtCore/QCoreApplication>
#include <QtCore/QDateTime>
#include <QtCore/QJsonObject>
#include <QtCore/QThread>
#include <LogHandler.h>
@ -27,6 +28,7 @@
#include "ControlPacket.h"
#include "Packet.h"
#include "PacketList.h"
#include "../UserActivityLogger.h"
#include "Socket.h"
using namespace udt;
@ -328,7 +330,39 @@ void SendQueue::run() {
nextPacketTimestamp += std::chrono::microseconds(nextPacketDelta);
// 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);
}

View file

@ -57,16 +57,18 @@ void PhysicalEntitySimulation::addEntityInternal(EntityItemPointer entity) {
}
void PhysicalEntitySimulation::removeEntityInternal(EntityItemPointer entity) {
EntitySimulation::removeEntityInternal(entity);
QMutexLocker lock(&_mutex);
_entitiesToAddToPhysics.remove(entity);
if (entity->isSimulated()) {
EntitySimulation::removeEntityInternal(entity);
QMutexLocker lock(&_mutex);
_entitiesToAddToPhysics.remove(entity);
EntityMotionState* motionState = static_cast<EntityMotionState*>(entity->getPhysicsInfo());
if (motionState) {
_outgoingChanges.remove(motionState);
_entitiesToRemoveFromPhysics.insert(entity);
} else {
_entitiesToDelete.insert(entity);
EntityMotionState* motionState = static_cast<EntityMotionState*>(entity->getPhysicsInfo());
if (motionState) {
_outgoingChanges.remove(motionState);
_entitiesToRemoveFromPhysics.insert(entity);
} else {
_entitiesToDelete.insert(entity);
}
}
}
@ -175,7 +177,7 @@ void PhysicalEntitySimulation::getObjectsToRemoveFromPhysics(VectorOfMotionState
_entitiesToRelease.insert(entity);
}
if (entity->isSimulated() && entity->isDead()) {
if (entity->isDead()) {
_entitiesToDelete.insert(entity);
}
}
@ -190,7 +192,7 @@ void PhysicalEntitySimulation::deleteObjectsRemovedFromPhysics() {
entity->setPhysicsInfo(nullptr);
delete motionState;
if (entity->isSimulated() && entity->isDead()) {
if (entity->isDead()) {
_entitiesToDelete.insert(entity);
}
}

View file

@ -114,6 +114,7 @@ void FetchSpatialTree::run(const SceneContextPointer& sceneContext, const Render
void CullSpatialSelection::configure(const Config& config) {
_justFrozeFrustum = _justFrozeFrustum || (config.freezeFrustum && !_freezeFrustum);
_freezeFrustum = config.freezeFrustum;
_skipCulling = config.skipCulling;
}
void CullSpatialSelection::run(const SceneContextPointer& sceneContext, const RenderContextPointer& renderContext,
@ -191,60 +192,112 @@ void CullSpatialSelection::run(const SceneContextPointer& sceneContext, const Re
// 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)
// inside & fit items: easy, just filter
{
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 & distance cull
{
PerformanceTimer perfTimer("insideSmallItems");
for (auto id : inSelection.insideSubcellItems) {
auto& item = scene->getItem(id);
if (_filter.test(item.getKey())) {
ItemBound itemBound(id, item.getBound());
if (test.solidAngleTest(itemBound.bound)) {
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);
}
}
}
}
// partial & fit items: filter & frustum cull
{
PerformanceTimer perfTimer("partialFitItems");
for (auto id : inSelection.partialItems) {
auto& item = scene->getItem(id);
if (_filter.test(item.getKey())) {
ItemBound itemBound(id, item.getBound());
if (test.frustumTest(itemBound.bound)) {
// 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 & subcell items:: filter & frutum cull & solidangle cull
{
PerformanceTimer perfTimer("partialSmallItems");
for (auto id : inSelection.partialSubcellItems) {
auto& item = scene->getItem(id);
if (_filter.test(item.getKey())) {
ItemBound itemBound(id, item.getBound());
if (test.frustumTest(itemBound.bound)) {
// 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
{
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 & distance cull
{
PerformanceTimer perfTimer("insideSmallItems");
for (auto id : inSelection.insideSubcellItems) {
auto& item = scene->getItem(id);
if (_filter.test(item.getKey())) {
ItemBound itemBound(id, item.getBound());
if (test.solidAngleTest(itemBound.bound)) {
outItems.emplace_back(itemBound);
}
}
}
}
// partial & fit items: filter & frustum cull
{
PerformanceTimer perfTimer("partialFitItems");
for (auto id : inSelection.partialItems) {
auto& item = scene->getItem(id);
if (_filter.test(item.getKey())) {
ItemBound itemBound(id, item.getBound());
if (test.frustumTest(itemBound.bound)) {
outItems.emplace_back(itemBound);
}
}
}
}
// partial & subcell items:: filter & frutum cull & solidangle cull
{
PerformanceTimer perfTimer("partialSmallItems");
for (auto id : inSelection.partialSubcellItems) {
auto& item = scene->getItem(id);
if (_filter.test(item.getKey())) {
ItemBound itemBound(id, item.getBound());
if (test.frustumTest(itemBound.bound)) {
if (test.solidAngleTest(itemBound.bound)) {
outItems.emplace_back(itemBound);
}
}
}
}
}
}
details._rendered += (int)outItems.size();

View file

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

View file

@ -14,8 +14,6 @@
#include <QtQml/QQmlContext>
#include <QtWebChannel/QWebChannel>
#include <QtScript/QScriptContext>
#include <QtScript/QScriptEngine>
@ -37,29 +35,8 @@ QScriptValue QmlWebWindowClass::constructor(QScriptContext* context, QScriptEngi
}
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 {
QVariant result = DependencyManager::get<OffscreenUi>()->returnFromUiThread([&]()->QVariant {

View file

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

View file

@ -26,6 +26,10 @@
#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 TITLE_PROPERTY = "title";
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 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,
QScriptContext* context, QScriptEngine* engine,
std::function<QmlWindowClass*(QObject*)> builder)
@ -116,8 +168,10 @@ QScriptValue QmlWindowClass::internalConstructor(const QString& qmlSource,
}
offscreenUi->returnFromUiThread([&] {
setupServer();
retVal = builder(newTab);
retVal->_toolWindow = true;
registerObject(url.toLower(), retVal);
return QVariant();
});
} else {
@ -125,8 +179,10 @@ QScriptValue QmlWindowClass::internalConstructor(const QString& qmlSource,
QMetaObject::invokeMethod(offscreenUi.data(), "load", Qt::BlockingQueuedConnection,
Q_ARG(const QString&, qmlSource),
Q_ARG(std::function<void(QQmlContext*, QObject*)>, [&](QQmlContext* context, QObject* object) {
setupServer();
retVal = builder(object);
context->engine()->setObjectOwnership(retVal->_qmlWindow, QQmlEngine::CppOwnership);
registerObject(url.toLower(), retVal);
if (!title.isEmpty()) {
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(dynamic_cast<const QQuickItem*>(_qmlWindow.data()));
// Forward messages received from QML on to the script
@ -169,6 +228,14 @@ QmlWindowClass::~QmlWindowClass() {
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 {
if (_toolWindow) {
return DependencyManager::get<OffscreenUi>()->getToolWindow();

View file

@ -13,16 +13,38 @@
#include <QtCore/QPointer>
#include <QtScript/QScriptValue>
#include <QtQuick/QQuickItem>
#include <QtWebChannel/QWebChannelAbstractTransport>
#include <GLMHelpers.h>
class QScriptEngine;
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
class QmlWindowClass : public QObject {
Q_OBJECT
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 size READ getSize WRITE setSize NOTIFY sizeChanged)
Q_PROPERTY(bool visible READ isVisible WRITE setVisible NOTIFY visibilityChanged)
@ -47,7 +69,8 @@ public slots:
Q_INVOKABLE void raise();
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
void sendToQml(const QVariant& message);
@ -69,12 +92,18 @@ protected:
static QScriptValue internalConstructor(const QString& qmlSource,
QScriptContext* context, QScriptEngine* engine,
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;
QmlScriptEventBridge* const _eventBridge { new QmlScriptEventBridge(this) };
// FIXME needs to be initialized in the ctor once we have support
// for tool window panes in QML
bool _toolWindow { false };
const int _windowId;
QPointer<QObject> _qmlWindow;
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 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 = {
'ds': {
},
@ -83,7 +91,7 @@ ready = function() {
var logTail = new Tail(cleanFilePath, '\n', { start: start, interval: 500 });
logTail.on('line', function(msg) {
appendLogMessage(msg, stream);
pendingLines[stream].push(msg);
});
logTail.on('error', function(error) {
@ -107,6 +115,7 @@ ready = function() {
}
window.onbeforeunload = function(e) {
clearInterval(interval);
domainServer.removeListener('logs-updated', updateLogFiles);
acMonitor.removeListener('logs-updated', updateLogFiles);
};
@ -164,14 +173,23 @@ ready = function() {
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 $pidLog = $('#' + id);
var size = ++tabStates[id].size;
var size = tabStates[id].size + array.length;
if (size > maxLogLines) {
$pidLog.find('div.log-line:first').remove();
removed = true;
$pidLog.find('div.log-line:lt(' + (size - maxLogLines) + ')').remove();
}
var wasAtBottom = false;
@ -179,17 +197,25 @@ ready = function() {
wasAtBottom = $pidLog[0].scrollTop >= ($pidLog[0].scrollHeight - $pidLog.height());
}
var $logLine = $('<div class="log-line">').text(msg);
if (!shouldDisplayLogMessage(msg)) {
$logLine.hide();
for (line in array) {
var $logLine = $('<div class="log-line">').text(array[line]);
if (!shouldDisplayLogMessage(array[line])) {
$logLine.hide();
}
$pidLog.append($logLine);
}
$pidLog.append($logLine);
delete pendingLines[name];
pendingLines[name] = new Array();
if (wasAtBottom) {
$pidLog.scrollTop($pidLog[0].scrollHeight);
}
}
function flushPendingLines() {
appendLogMessages('ds');
appendLogMessages('ac');
}
// handle filtering of table rows on input change