diff --git a/assignment-client/src/audio/AudioMixer.cpp b/assignment-client/src/audio/AudioMixer.cpp index 459f8a4b59..c86d37e283 100644 --- a/assignment-client/src/audio/AudioMixer.cpp +++ b/assignment-client/src/audio/AudioMixer.cpp @@ -380,11 +380,11 @@ void AudioMixer::prepareMixForListeningNode(Node* node) { // enumerate the ARBs attached to the otherNode and add all that should be added to mix for (int i = 0; i < otherNodeClientData->getRingBuffers().size(); i++) { PositionalAudioRingBuffer* otherNodeBuffer = otherNodeClientData->getRingBuffers()[i]; - + if ((*otherNode != *node || otherNodeBuffer->shouldLoopbackForNode()) && otherNodeBuffer->willBeAddedToMix() - && otherNodeBuffer->getNextOutputTrailingLoudness() > 0) { + && otherNodeBuffer->getNextOutputTrailingLoudness() > 0.0f) { addBufferToMixForListeningNodeWithBuffer(otherNodeBuffer, nodeRingBuffer); } } diff --git a/assignment-client/src/audio/AudioMixerClientData.cpp b/assignment-client/src/audio/AudioMixerClientData.cpp index 94bbdc6a6b..6559b57959 100644 --- a/assignment-client/src/audio/AudioMixerClientData.cpp +++ b/assignment-client/src/audio/AudioMixerClientData.cpp @@ -280,12 +280,12 @@ QString AudioMixerClientData::getAudioStreamStatsString() const { + " silents_dropped: ?" + " lost%:" + QString::number(streamStats._packetStreamStats.getLostRate() * 100.0f, 'f', 2) + " lost%_30s:" + QString::number(streamStats._packetStreamWindowStats.getLostRate() * 100.0f, 'f', 2) - + " min_gap:" + QString::number(streamStats._timeGapMin) - + " max_gap:" + QString::number(streamStats._timeGapMax) - + " avg_gap:" + QString::number(streamStats._timeGapAverage, 'f', 2) - + " min_gap_30s:" + QString::number(streamStats._timeGapWindowMin) - + " max_gap_30s:" + QString::number(streamStats._timeGapWindowMax) - + " avg_gap_30s:" + QString::number(streamStats._timeGapWindowAverage, 'f', 2); + + " min_gap:" + formatUsecTime(streamStats._timeGapMin) + + " max_gap:" + formatUsecTime(streamStats._timeGapMax) + + " avg_gap:" + formatUsecTime(streamStats._timeGapAverage) + + " min_gap_30s:" + formatUsecTime(streamStats._timeGapWindowMin) + + " max_gap_30s:" + formatUsecTime(streamStats._timeGapWindowMax) + + " avg_gap_30s:" + formatUsecTime(streamStats._timeGapWindowAverage); AvatarAudioRingBuffer* avatarRingBuffer = getAvatarAudioRingBuffer(); if (avatarRingBuffer) { @@ -299,12 +299,12 @@ QString AudioMixerClientData::getAudioStreamStatsString() const { + " silents_dropped:" + QString::number(streamStats._ringBufferSilentFramesDropped) + " lost%:" + QString::number(streamStats._packetStreamStats.getLostRate() * 100.0f, 'f', 2) + " lost%_30s:" + QString::number(streamStats._packetStreamWindowStats.getLostRate() * 100.0f, 'f', 2) - + " min_gap:" + QString::number(streamStats._timeGapMin) - + " max_gap:" + QString::number(streamStats._timeGapMax) - + " avg_gap:" + QString::number(streamStats._timeGapAverage, 'f', 2) - + " min_gap_30s:" + QString::number(streamStats._timeGapWindowMin) - + " max_gap_30s:" + QString::number(streamStats._timeGapWindowMax) - + " avg_gap_30s:" + QString::number(streamStats._timeGapWindowAverage, 'f', 2); + + " min_gap:" + formatUsecTime(streamStats._timeGapMin) + + " max_gap:" + formatUsecTime(streamStats._timeGapMax) + + " avg_gap:" + formatUsecTime(streamStats._timeGapAverage) + + " min_gap_30s:" + formatUsecTime(streamStats._timeGapWindowMin) + + " max_gap_30s:" + formatUsecTime(streamStats._timeGapWindowMax) + + " avg_gap_30s:" + formatUsecTime(streamStats._timeGapWindowAverage); } else { result = "mic unknown"; } @@ -321,12 +321,12 @@ QString AudioMixerClientData::getAudioStreamStatsString() const { + " silents_dropped:" + QString::number(streamStats._ringBufferSilentFramesDropped) + " lost%:" + QString::number(streamStats._packetStreamStats.getLostRate() * 100.0f, 'f', 2) + " lost%_30s:" + QString::number(streamStats._packetStreamWindowStats.getLostRate() * 100.0f, 'f', 2) - + " min_gap:" + QString::number(streamStats._timeGapMin) - + " max_gap:" + QString::number(streamStats._timeGapMax) - + " avg_gap:" + QString::number(streamStats._timeGapAverage, 'f', 2) - + " min_gap_30s:" + QString::number(streamStats._timeGapWindowMin) - + " max_gap_30s:" + QString::number(streamStats._timeGapWindowMax) - + " avg_gap_30s:" + QString::number(streamStats._timeGapWindowAverage, 'f', 2); + + " min_gap:" + formatUsecTime(streamStats._timeGapMin) + + " max_gap:" + formatUsecTime(streamStats._timeGapMax) + + " avg_gap:" + formatUsecTime(streamStats._timeGapAverage) + + " min_gap_30s:" + formatUsecTime(streamStats._timeGapWindowMin) + + " max_gap_30s:" + formatUsecTime(streamStats._timeGapWindowMax) + + " avg_gap_30s:" + formatUsecTime(streamStats._timeGapWindowAverage); } } return result; diff --git a/examples/Test.js b/examples/Test.js index 056ec3cbbf..36dee7bd90 100644 --- a/examples/Test.js +++ b/examples/Test.js @@ -20,7 +20,7 @@ test = function(name, func) { unitTest.run(); print(" Success: " + unitTest.numAssertions + " assertions passed"); } catch (error) { - print(" Failure: " + error.message); + print(" Failure: " + error.name + " " + error.message); } }; @@ -30,6 +30,12 @@ AssertionException = function(expected, actual, message) { this.name = 'AssertionException'; }; +UnthrownException = function(message) { + print("Creating exception"); + this.message = message + "\n"; + this.name = 'UnthrownException'; +}; + UnitTest = function(name, func) { this.numAssertions = 0; this.func = func; @@ -65,4 +71,27 @@ UnitTest.prototype.assertNull = function(value, message) { if (value !== null) { throw new AssertionException(value, null, message); } -}; +} + +UnitTest.prototype.arrayEqual = function(array1, array2, message) { + this.numAssertions++; + if (array1.length !== array2.length) { + throw new AssertionException(array1.length , array2.length , message); + } + for (var i = 0; i < array1.length; ++i) { + if (array1[i] !== array2[i]) { + throw new AssertionException(array1[i], array2[i], i + " " + message); + } + } +} + +UnitTest.prototype.raises = function(func, message) { + this.numAssertions++; + try { + func(); + } catch (error) { + return; + } + + throw new UnthrownException(message); +} \ No newline at end of file diff --git a/examples/avatarLocalLight.js b/examples/avatarLocalLight.js index 57f9d84ffe..225ed4d755 100644 --- a/examples/avatarLocalLight.js +++ b/examples/avatarLocalLight.js @@ -10,12 +10,14 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -var localLightDirections = [ {x: 1.0, y:0.0, z: 0.0}, {x: 0.0, y:1.0, z: 1.0}, {x: 0.0, y:0.0, z: 1.0}, {x: 1.0, y:1.0, z: 1.0} ]; -var localLightColors = [ {x: 0.0, y:0.0, z: 0.0}, {x: 0.0, y:0.0, z: 0.0}, {x: 0.0, y:0.0, z: 0.0}, {x: 0.0, y:0.0, z: 0.0} ]; +var localLightDirections = [ {x: 1.0, y:1.0, z: 0.0}, {x: 0.0, y:1.0, z: 1.0} ]; +var localLightColors = [ {x: 0.0, y:1.0, z: 0.0}, {x: 1.0, y:0.0, z: 0.0} ]; var currentSelection = 0; var currentNumLights = 1; var maxNumLights = 2; +var currentNumAvatars = 0; +var avatarHashIDs = []; function keyPressEvent(event) { @@ -43,7 +45,8 @@ function keyPressEvent(event) { localLightColors[currentSelection].x = 0.0; } - MyAvatar.setLocalLightColor(localLightColors[currentSelection], currentSelection); + setAllLightColors(); + print("CHANGE RED light " + currentSelection + " color (" + localLightColors[currentSelection].x + ", " + localLightColors[currentSelection].y + ", " + localLightColors[currentSelection].z + " )" ); } else if (event.text == "6" ) { localLightColors[currentSelection].y += 0.01; @@ -51,7 +54,8 @@ function keyPressEvent(event) { localLightColors[currentSelection].y = 0.0; } - MyAvatar.setLocalLightColor(localLightColors[currentSelection], currentSelection); + setAllLightColors(); + print("CHANGE GREEN light " + currentSelection + " color (" + localLightColors[currentSelection].x + ", " + localLightColors[currentSelection].y + ", " + localLightColors[currentSelection].z + " )" ); } else if (event.text == "7" ) { localLightColors[currentSelection].z += 0.01; @@ -59,7 +63,8 @@ function keyPressEvent(event) { localLightColors[currentSelection].z = 0.0; } - MyAvatar.setLocalLightColor(localLightColors[currentSelection], currentSelection); + setAllLightColors(); + print("CHANGE BLUE light " + currentSelection + " color (" + localLightColors[currentSelection].x + ", " + localLightColors[currentSelection].y + ", " + localLightColors[currentSelection].z + " )" ); } else if (event.text == "8" ) { localLightDirections[currentSelection].x += 0.01; @@ -67,7 +72,8 @@ function keyPressEvent(event) { localLightDirections[currentSelection].x = -1.0; } - MyAvatar.setLocalLightDirection(localLightDirections[currentSelection], currentSelection); + setAllLightDirections(); + print("PLUS X light " + currentSelection + " direction (" + localLightDirections[currentSelection].x + ", " + localLightDirections[currentSelection].y + ", " + localLightDirections[currentSelection].z + " )" ); } else if (event.text == "9" ) { localLightDirections[currentSelection].x -= 0.01; @@ -75,52 +81,117 @@ function keyPressEvent(event) { localLightDirections[currentSelection].x = 1.0; } - MyAvatar.setLocalLightDirection(localLightDirections[currentSelection], currentSelection); + setAllLightDirections(); + print("MINUS X light " + currentSelection + " direction (" + localLightDirections[currentSelection].x + ", " + localLightDirections[currentSelection].y + ", " + localLightDirections[currentSelection].z + " )" ); } - else if (event.text == "[" ) { + else if (event.text == "0" ) { localLightDirections[currentSelection].y += 0.01; if (localLightDirections[currentSelection].y > 1.0) { localLightDirections[currentSelection].y = -1.0; } - MyAvatar.setLocalLightDirection(localLightDirections[currentSelection], currentSelection); + setAllLightDirections(); + print("PLUS Y light " + currentSelection + " direction (" + localLightDirections[currentSelection].x + ", " + localLightDirections[currentSelection].y + ", " + localLightDirections[currentSelection].z + " )" ); } - else if (event.text == "]" ) { + else if (event.text == "-" ) { localLightDirections[currentSelection].y -= 0.01; if (localLightDirections[currentSelection].y < -1.0) { localLightDirections[currentSelection].y = 1.0; } - MyAvatar.setLocalLightDirection(localLightDirections[currentSelection], currentSelection); + setAllLightDirections(); + print("MINUS Y light " + currentSelection + " direction (" + localLightDirections[currentSelection].x + ", " + localLightDirections[currentSelection].y + ", " + localLightDirections[currentSelection].z + " )" ); } else if (event.text == "," ) { if (currentNumLights + 1 <= maxNumLights) { - var darkGrayColor = {x:0.3, y:0.3, z:0.3}; - - // default light - localLightColors[currentNumLights].x = darkGrayColor.x; - localLightColors[currentNumLights].y = darkGrayColor.y; - localLightColors[currentNumLights].z = darkGrayColor.z; - - MyAvatar.addLocalLight(); - MyAvatar.setLocalLightColor(localLightColors[currentNumLights], currentNumLights); - MyAvatar.setLocalLightDirection(localLightDirections[currentNumLights], currentNumLights); - ++currentNumLights; + + for (var i = 0; i < currentNumAvatars; i++) { + AvatarManager.addAvatarLocalLight(i); + + for (var j = 0; j < currentNumLights; j++) { + AvatarManager.setAvatarLightColor(localLightColors[j], j, i); + AvatarManager.setAvatarLightDirection(localLightDirections[j], j, i); + } + } } + + print("ADD LIGHT, number of lights " + currentNumLights); } else if (event.text == "." ) { if (currentNumLights - 1 >= 0 ) { - - // no light contribution - localLightColors[currentNumLights - 1].x = 0.0; - localLightColors[currentNumLights - 1].y = 0.0; - localLightColors[currentNumLights - 1].z = 0.0; - - MyAvatar.removeLocalLight(); --currentNumLights; + + for (var i = 0; i < currentNumAvatars; i++) { + AvatarManager.removeAvatarLocalLight(i); + + for (var j = 0; j < currentNumLights; j++) { + AvatarManager.setAvatarLightColor(localLightColors[j], j, i); + AvatarManager.setAvatarLightDirection(localLightDirections[j], j, i); + } + } + } + + print("REMOVE LIGHT, number of lights " + currentNumLights); } } +function updateLocalLights() +{ + // new avatars, so add lights + var numAvatars = AvatarManager.getNumAvatars(); + if (numAvatars != currentNumAvatars) { + + for (var i = 0; i < numAvatars; i++) { + var id = AvatarManager.getAvatarHashKey(i); + + // check if avatar has already been registered + var hasRegistered = false; + for (var j = 0; j < numAvatars; j++) { + if (avatarHashIDs[j] == id) { + hasRegistered = true; + break; + } + } + + // add new id and set light params + if (!hasRegistered) { + + avatarHashIDs.push(id); + AvatarManager.addAvatarLocalLight(i); + + // set color and direction for new avatar + for (var j = 0; j < maxNumLights; j++) { + AvatarManager.setAvatarLightColor(localLightColors[j], j, i); + AvatarManager.setAvatarLightDirection(localLightDirections[j], j, i); + } + } + } + + currentNumAvatars = numAvatars; + } +} + +function setAllLightColors() +{ + for (var i = 0; i < currentNumAvatars; i++) { + for (var j = 0; j < maxNumLights; j++) { + AvatarManager.setAvatarLightColor(localLightColors[j], j, i); + } + } +} + +function setAllLightDirections() +{ + for (var i = 0; i < currentNumAvatars; i++) { + for (var j = 0; j < maxNumLights; j++) { + AvatarManager.setAvatarLightDirection(localLightDirections[j], j, i); + } + } +} + +// main +Script.update.connect(updateLocalLights); Controller.keyPressEvent.connect(keyPressEvent); + diff --git a/examples/typedArraysUnitTest.js b/examples/typedArraysUnitTest.js new file mode 100644 index 0000000000..e86a07289d --- /dev/null +++ b/examples/typedArraysUnitTest.js @@ -0,0 +1,708 @@ +// +// typedArraysunitTest.js +// examples +// +// Created by Clément Brisset on 7/7/14 +// Copyright 2014 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +Script.include("Test.js"); + +// e.g. extractbits([0xff, 0x80, 0x00, 0x00], 23, 30); inclusive +function extractbits(bytes, lo, hi) { + var out = 0; + bytes = bytes.slice(); // make a copy + var lsb = bytes.pop(), sc = 0, sh = 0; + + for (; lo > 0; lo--, hi--) { + lsb >>= 1; + if (++sc === 8) { sc = 0; lsb = bytes.pop(); } + } + + for (; hi >= 0; hi--) { + out = out | (lsb & 0x01) << sh++; + lsb >>= 1; + if (++sc === 8) { sc = 0; lsb = bytes.pop(); } + } + + return out; +} + +test('ArrayBuffer', function(finished) { + this.assertEquals(new ArrayBuffer(0).byteLength, 0, 'no length'); + + this.assertEquals(typeof(new ArrayBuffer(0)), 'object', 'creation'); + this.assertEquals(typeof(new ArrayBuffer(1)), 'object', 'creation'); + this.assertEquals(typeof(new ArrayBuffer(123)), 'object', 'creation'); + + this.assertEquals(new ArrayBuffer(123).byteLength, 123, 'length'); + + this.raises(function () { return new ArrayBuffer(-1); }, 'negative length'); + this.raises(function () { return new ArrayBuffer(0x80000000); }, 'absurd length'); +}); + +test('DataView constructors', function (finished) { + var d = new DataView(new ArrayBuffer(8)); + + d.setUint32(0, 0x12345678); + this.assertEquals(d.getUint32(0), 0x12345678, 'big endian/big endian'); + + d.setUint32(0, 0x12345678, true); + this.assertEquals(d.getUint32(0, true), 0x12345678, 'little endian/little endian'); + + d.setUint32(0, 0x12345678, true); + this.assertEquals(d.getUint32(0), 0x78563412, 'little endian/big endian'); + + d.setUint32(0, 0x12345678); + this.assertEquals(d.getUint32(0, true), 0x78563412, 'big endian/little endian'); + + this.raises(function () { return new DataView({}); }, 'non-ArrayBuffer argument'); + this.raises(function () { return new DataView("bogus"); }, 'non-ArrayBuffer argument'); +}); + + +test('ArrayBufferView', function () { + var ab = new ArrayBuffer(48); + var i32 = new Int32Array(ab, 16); + i32.set([1, 2, 3, 4, 5, 6, 7, 8]); + + this.assertEquals(i32.buffer, ab, 'ArrayBuffers equal'); + this.assertEquals(i32.byteOffset, 16, 'byteOffset'); + this.assertEquals(i32.byteLength, 32, 'byteLength'); + + var da = new DataView(i32.buffer, 8); + this.assertEquals(da.buffer, ab, 'DataView: ArrayBuffers equal'); + this.assertEquals(da.byteOffset, 8, 'DataView: byteOffset'); + this.assertEquals(da.byteLength, 40, 'DataView: byteLength'); +}); + +test('TypedArrays', function () { + var a; + + this.assertEquals(Int8Array.BYTES_PER_ELEMENT, 1, 'Int8Array.BYTES_PER_ELEMENT'); + a = new Int8Array([1, 2, 3, 4, 5, 6, 7, 8]); + this.assertEquals(a.BYTES_PER_ELEMENT, 1, 'int8Array.BYTES_PER_ELEMENT'); + this.assertEquals(a.byteOffset, 0, 'int8Array.byteOffset'); + this.assertEquals(a.byteLength, 8, 'int8Array.byteLength'); + + this.assertEquals(Uint8Array.BYTES_PER_ELEMENT, 1, 'Uint8Array.BYTES_PER_ELEMENT'); + a = new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8]); + this.assertEquals(a.BYTES_PER_ELEMENT, 1, 'uint8Array.BYTES_PER_ELEMENT'); + this.assertEquals(a.byteOffset, 0, 'uint8Array.byteOffset'); + this.assertEquals(a.byteLength, 8, 'uint8Array.byteLength'); + + this.assertEquals(Int16Array.BYTES_PER_ELEMENT, 2, 'Int16Array.BYTES_PER_ELEMENT'); + a = new Int16Array([1, 2, 3, 4, 5, 6, 7, 8]); + this.assertEquals(a.BYTES_PER_ELEMENT, 2, 'int16Array.BYTES_PER_ELEMENT'); + this.assertEquals(a.byteOffset, 0, 'int16Array.byteOffset'); + this.assertEquals(a.byteLength, 16, 'int16Array.byteLength'); + + this.assertEquals(Uint16Array.BYTES_PER_ELEMENT, 2, 'Uint16Array.BYTES_PER_ELEMENT'); + a = new Uint16Array([1, 2, 3, 4, 5, 6, 7, 8]); + this.assertEquals(a.BYTES_PER_ELEMENT, 2, 'uint16Array.BYTES_PER_ELEMENT'); + this.assertEquals(a.byteOffset, 0, 'uint16Array.byteOffset'); + this.assertEquals(a.byteLength, 16, 'uint16Array.byteLength'); + + this.assertEquals(Int32Array.BYTES_PER_ELEMENT, 4, 'Int32Array.BYTES_PER_ELEMENT'); + a = new Int32Array([1, 2, 3, 4, 5, 6, 7, 8]); + this.assertEquals(a.BYTES_PER_ELEMENT, 4, 'int32Array.BYTES_PER_ELEMENT'); + this.assertEquals(a.byteOffset, 0, 'int32Array.byteOffset'); + this.assertEquals(a.byteLength, 32, 'int32Array.byteLength'); + + this.assertEquals(Uint32Array.BYTES_PER_ELEMENT, 4, 'Uint32Array.BYTES_PER_ELEMENT'); + a = new Uint32Array([1, 2, 3, 4, 5, 6, 7, 8]); + this.assertEquals(a.BYTES_PER_ELEMENT, 4, 'uint32Array.BYTES_PER_ELEMENT'); + this.assertEquals(a.byteOffset, 0, 'uint32Array.byteOffset'); + this.assertEquals(a.byteLength, 32, 'uint32Array.byteLength'); + + this.assertEquals(Float32Array.BYTES_PER_ELEMENT, 4, 'Float32Array.BYTES_PER_ELEMENT'); + a = new Float32Array([1, 2, 3, 4, 5, 6, 7, 8]); + this.assertEquals(a.BYTES_PER_ELEMENT, 4, 'float32Array.BYTES_PER_ELEMENT'); + this.assertEquals(a.byteOffset, 0, 'float32Array.byteOffset'); + this.assertEquals(a.byteLength, 32, 'float32Array.byteLength'); + + this.assertEquals(Float64Array.BYTES_PER_ELEMENT, 8, 'Float64Array.BYTES_PER_ELEMENT'); + a = new Float64Array([1, 2, 3, 4, 5, 6, 7, 8]); + this.assertEquals(a.BYTES_PER_ELEMENT, 8, 'float64Array.BYTES_PER_ELEMENT'); + this.assertEquals(a.byteOffset, 0, 'float64Array.byteOffset'); + this.assertEquals(a.byteLength, 64, 'float64Array.byteLength'); +}); + + +test('typed array constructors', function () { + this.arrayEqual(new Int8Array({ length: 3 }), [0, 0, 0], 'array equal -1'); + var rawbuf = (new Uint8Array([0, 1, 2, 3, 4, 5, 6, 7])).buffer; + + var int8 = new Int8Array(); + this.assertEquals(int8.length, 0, 'no args 0'); + this.raises(function () { return new Int8Array(-1); }, 'bogus length'); + this.raises(function () { return new Int8Array(0x80000000); }, 'bogus length'); + + int8 = new Int8Array(4); + this.assertEquals(int8.BYTES_PER_ELEMENT, 1); + this.assertEquals(int8.length, 4, 'length 1'); + this.assertEquals(int8.byteLength, 4, 'length 2'); + this.assertEquals(int8.byteOffset, 0, 'length 3'); + this.assertEquals(int8.get(-1), undefined, 'length, out of bounds 4'); + this.assertEquals(int8.get(4), undefined, 'length, out of bounds 5'); + + int8 = new Int8Array([1, 2, 3, 4, 5, 6]); + this.assertEquals(int8.length, 6, 'array 6'); + this.assertEquals(int8.byteLength, 6, 'array 7'); + this.assertEquals(int8.byteOffset, 0, 'array 8'); + this.assertEquals(int8.get(3), 4, 'array 9'); + this.assertEquals(int8.get(-1), undefined, 'array, out of bounds 10'); + this.assertEquals(int8.get(6), undefined, 'array, out of bounds 11'); + + int8 = new Int8Array(rawbuf); + this.assertEquals(int8.length, 8, 'buffer 12'); + this.assertEquals(int8.byteLength, 8, 'buffer 13'); + this.assertEquals(int8.byteOffset, 0, 'buffer 14'); + this.assertEquals(int8.get(7), 7, 'buffer 15'); + int8.set([111]); + this.assertEquals(int8.get(0), 111, 'buffer 16'); + this.assertEquals(int8.get(-1), undefined, 'buffer, out of bounds 17'); + this.assertEquals(int8.get(8), undefined, 'buffer, out of bounds 18'); + + int8 = new Int8Array(rawbuf, 2); + this.assertEquals(int8.length, 6, 'buffer, byteOffset 19'); + this.assertEquals(int8.byteLength, 6, 'buffer, byteOffset 20'); + this.assertEquals(int8.byteOffset, 2, 'buffer, byteOffset 21'); + this.assertEquals(int8.get(5), 7, 'buffer, byteOffset 22'); + int8.set([112]); + this.assertEquals(int8.get(0), 112, 'buffer 23'); + this.assertEquals(int8.get(-1), undefined, 'buffer, byteOffset, out of bounds 24'); + this.assertEquals(int8.get(6), undefined, 'buffer, byteOffset, out of bounds 25'); + + int8 = new Int8Array(rawbuf, 8); + this.assertEquals(int8.length, 0, 'buffer, byteOffset 26'); + + this.raises(function () { return new Int8Array(rawbuf, -1); }, 'invalid byteOffset 27'); + this.raises(function () { return new Int8Array(rawbuf, 9); }, 'invalid byteOffset 28'); + this.raises(function () { return new Int32Array(rawbuf, -1); }, 'invalid byteOffset 29'); + this.raises(function () { return new Int32Array(rawbuf, 5); }, 'invalid byteOffset 30'); + + int8 = new Int8Array(rawbuf, 2, 4); + this.assertEquals(int8.length, 4, 'buffer, byteOffset, length 31'); + this.assertEquals(int8.byteLength, 4, 'buffer, byteOffset, length 32'); + this.assertEquals(int8.byteOffset, 2, 'buffer, byteOffset, length 33'); + this.assertEquals(int8.get(3), 5, 'buffer, byteOffset, length 34'); + int8.set([113]); + this.assertEquals(int8.get(0), 113, 'buffer, byteOffset, length 35'); + this.assertEquals(int8.get(-1), undefined, 'buffer, byteOffset, length, out of bounds 36'); + this.assertEquals(int8.get(4), undefined, 'buffer, byteOffset, length, out of bounds 37'); + + this.raises(function () { return new Int8Array(rawbuf, 0, 9); }, 'invalid byteOffset+length'); + this.raises(function () { return new Int8Array(rawbuf, 8, 1); }, 'invalid byteOffset+length'); + this.raises(function () { return new Int8Array(rawbuf, 9, -1); }, 'invalid byteOffset+length'); +}); + +test('TypedArray clone constructor', function () { + var src = new Int32Array([1, 2, 3, 4, 5, 6, 7, 8]); + var dst = new Int32Array(src); + this.arrayEqual(dst, [1, 2, 3, 4, 5, 6, 7, 8], '1'); + src.set([99]); + this.arrayEqual(src, [99, 2, 3, 4, 5, 6, 7, 8], '2'); + this.arrayEqual(dst, [1, 2, 3, 4, 5, 6, 7, 8], '3'); +}); + + +test('conversions', function () { + var uint8 = new Uint8Array([1, 2, 3, 4]), + uint16 = new Uint16Array(uint8.buffer), + uint32 = new Uint32Array(uint8.buffer); + + // Note: can't probe individual bytes without endianness awareness + this.arrayEqual(uint8, [1, 2, 3, 4]); + uint16.set([0xffff]); + this.arrayEqual(uint8, [0xff, 0xff, 3, 4]); + uint16.set([0xeeee], 1); + this.arrayEqual(uint8, [0xff, 0xff, 0xee, 0xee]); + uint32.set([0x11111111]); + this.assertEquals(uint16.get(0), 0x1111); + this.assertEquals(uint16.get(1), 0x1111); + this.arrayEqual(uint8, [0x11, 0x11, 0x11, 0x11]); +}); + + +test('signed/unsigned conversions', function () { + + var int8 = new Int8Array(1), uint8 = new Uint8Array(int8.buffer); + uint8.set([123]); + this.assertEquals(int8.get(0), 123, 'int8/uint8'); + uint8.set([161]); + this.assertEquals(int8.get(0), -95, 'int8/uint8'); + int8.set([-120]); + this.assertEquals(uint8.get(0), 136, 'uint8/int8'); + int8.set([-1]); + this.assertEquals(uint8.get(0), 0xff, 'uint8/int8'); + + var int16 = new Int16Array(1), uint16 = new Uint16Array(int16.buffer); + uint16.set([3210]); + this.assertEquals(int16.get(0), 3210, 'int16/uint16'); + uint16.set([49232]); + this.assertEquals(int16.get(0), -16304, 'int16/uint16'); + int16.set([-16384]); + this.assertEquals(uint16.get(0), 49152, 'uint16/int16'); + int16.set([-1]); + this.assertEquals(uint16.get(0), 0xffff, 'uint16/int16'); + + var int32 = new Int32Array(1), uint32 = new Uint32Array(int32.buffer); + uint32.set([0x80706050]); + this.assertEquals(int32.get(0), -2140118960, 'int32/uint32'); + int32.set([-2023406815]); + this.assertEquals(uint32.get(0), 0x87654321, 'uint32/int32'); + int32.set([-1]); + this.assertEquals(uint32.get(0), 0xffffffff, 'uint32/int32'); +}); + + +test('IEEE754 single precision unpacking', function () { + function fromBytes(bytes) { + var uint8 = new Uint8Array(bytes), + dv = new DataView(uint8.buffer); + return dv.getFloat32(0); + } + + this.assertEquals(isNaN(fromBytes([0xff, 0xff, 0xff, 0xff])), true, 'Q-NaN'); + this.assertEquals(isNaN(fromBytes([0xff, 0xc0, 0x00, 0x01])), true, 'Q-NaN'); + + this.assertEquals(isNaN(fromBytes([0xff, 0xc0, 0x00, 0x00])), true, 'Indeterminate'); + + this.assertEquals(isNaN(fromBytes([0xff, 0xbf, 0xff, 0xff])), true, 'S-NaN'); + this.assertEquals(isNaN(fromBytes([0xff, 0x80, 0x00, 0x01])), true, 'S-NaN'); + + this.assertEquals(fromBytes([0xff, 0x80, 0x00, 0x00]), -Infinity, '-Infinity'); + + this.assertEquals(fromBytes([0xff, 0x7f, 0xff, 0xff]), -3.4028234663852886E+38, '-Normalized'); + this.assertEquals(fromBytes([0x80, 0x80, 0x00, 0x00]), -1.1754943508222875E-38, '-Normalized'); + this.assertEquals(fromBytes([0xff, 0x7f, 0xff, 0xff]), -3.4028234663852886E+38, '-Normalized'); + this.assertEquals(fromBytes([0x80, 0x80, 0x00, 0x00]), -1.1754943508222875E-38, '-Normalized'); + + // TODO: Denormalized values fail on Safari on iOS/ARM + this.assertEquals(fromBytes([0x80, 0x7f, 0xff, 0xff]), -1.1754942106924411E-38, '-Denormalized'); + this.assertEquals(fromBytes([0x80, 0x00, 0x00, 0x01]), -1.4012984643248170E-45, '-Denormalized'); + + this.assertEquals(fromBytes([0x80, 0x00, 0x00, 0x00]), -0, '-0'); + this.assertEquals(fromBytes([0x00, 0x00, 0x00, 0x00]), +0, '+0'); + + // TODO: Denormalized values fail on Safari on iOS/ARM + this.assertEquals(fromBytes([0x00, 0x00, 0x00, 0x01]), 1.4012984643248170E-45, '+Denormalized'); + this.assertEquals(fromBytes([0x00, 0x7f, 0xff, 0xff]), 1.1754942106924411E-38, '+Denormalized'); + + this.assertEquals(fromBytes([0x00, 0x80, 0x00, 0x00]), 1.1754943508222875E-38, '+Normalized'); + this.assertEquals(fromBytes([0x7f, 0x7f, 0xff, 0xff]), 3.4028234663852886E+38, '+Normalized'); + + this.assertEquals(fromBytes([0x7f, 0x80, 0x00, 0x00]), +Infinity, '+Infinity'); + + this.assertEquals(isNaN(fromBytes([0x7f, 0x80, 0x00, 0x01])), true, 'S+NaN'); + this.assertEquals(isNaN(fromBytes([0x7f, 0xbf, 0xff, 0xff])), true, 'S+NaN'); + + this.assertEquals(isNaN(fromBytes([0x7f, 0xc0, 0x00, 0x00])), true, 'Q+NaN'); + this.assertEquals(isNaN(fromBytes([0x7f, 0xff, 0xff, 0xff])), true, 'Q+NaN'); +}); + +test('IEEE754 single precision packing', function () { + + function toBytes(v) { + var uint8 = new Uint8Array(4), dv = new DataView(uint8.buffer); + dv.setFloat32(0, v); + var bytes = []; + for (var i = 0; i < 4; i += 1) { + bytes.push(uint8.get(i)); + } + return bytes; + } + + this.arrayEqual(toBytes(-Infinity), [0xff, 0x80, 0x00, 0x00], '-Infinity'); + + this.arrayEqual(toBytes(-3.4028235677973366e+38), [0xff, 0x80, 0x00, 0x00], '-Overflow'); + this.arrayEqual(toBytes(-3.402824E+38), [0xff, 0x80, 0x00, 0x00], '-Overflow'); + + this.arrayEqual(toBytes(-3.4028234663852886E+38), [0xff, 0x7f, 0xff, 0xff], '-Normalized'); + this.arrayEqual(toBytes(-1.1754943508222875E-38), [0x80, 0x80, 0x00, 0x00], '-Normalized'); + + // TODO: Denormalized values fail on Safari iOS/ARM + this.arrayEqual(toBytes(-1.1754942106924411E-38), [0x80, 0x7f, 0xff, 0xff], '-Denormalized'); + this.arrayEqual(toBytes(-1.4012984643248170E-45), [0x80, 0x00, 0x00, 0x01], '-Denormalized'); + + this.arrayEqual(toBytes(-7.006492321624085e-46), [0x80, 0x00, 0x00, 0x00], '-Underflow'); + + this.arrayEqual(toBytes(-0), [0x80, 0x00, 0x00, 0x00], '-0'); + this.arrayEqual(toBytes(0), [0x00, 0x00, 0x00, 0x00], '+0'); + + this.arrayEqual(toBytes(7.006492321624085e-46), [0x00, 0x00, 0x00, 0x00], '+Underflow'); + + // TODO: Denormalized values fail on Safari iOS/ARM + this.arrayEqual(toBytes(1.4012984643248170E-45), [0x00, 0x00, 0x00, 0x01], '+Denormalized'); + this.arrayEqual(toBytes(1.1754942106924411E-38), [0x00, 0x7f, 0xff, 0xff], '+Denormalized'); + + this.arrayEqual(toBytes(1.1754943508222875E-38), [0x00, 0x80, 0x00, 0x00], '+Normalized'); + this.arrayEqual(toBytes(3.4028234663852886E+38), [0x7f, 0x7f, 0xff, 0xff], '+Normalized'); + + this.arrayEqual(toBytes(+3.402824E+38), [0x7f, 0x80, 0x00, 0x00], '+Overflow'); + this.arrayEqual(toBytes(+3.402824E+38), [0x7f, 0x80, 0x00, 0x00], '+Overflow'); + this.arrayEqual(toBytes(+Infinity), [0x7f, 0x80, 0x00, 0x00], '+Infinity'); + + // Allow any NaN pattern (exponent all 1's, fraction non-zero) + var nanbytes = toBytes(NaN), + sign = extractbits(nanbytes, 31, 31), + exponent = extractbits(nanbytes, 23, 30), + fraction = extractbits(nanbytes, 0, 22); + this.assertEquals(exponent === 255 && fraction !== 0, true, 'NaN'); +}); + + +test('IEEE754 double precision unpacking', function () { + + function fromBytes(bytes) { + var uint8 = new Uint8Array(bytes), + dv = new DataView(uint8.buffer); + return dv.getFloat64(0); + } + + this.assertEquals(isNaN(fromBytes([0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff])), true, 'Q-NaN'); + this.assertEquals(isNaN(fromBytes([0xff, 0xf8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01])), true, 'Q-NaN'); + + this.assertEquals(isNaN(fromBytes([0xff, 0xf8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00])), true, 'Indeterminate'); + + this.assertEquals(isNaN(fromBytes([0xff, 0xf7, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff])), true, 'S-NaN'); + this.assertEquals(isNaN(fromBytes([0xff, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01])), true, 'S-NaN'); + + this.assertEquals(fromBytes([0xff, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]), -Infinity, '-Infinity'); + + this.assertEquals(fromBytes([0xff, 0xef, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff]), -1.7976931348623157E+308, '-Normalized'); + this.assertEquals(fromBytes([0x80, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]), -2.2250738585072014E-308, '-Normalized'); + + // TODO: Denormalized values fail on Safari iOS/ARM + this.assertEquals(fromBytes([0x80, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff]), -2.2250738585072010E-308, '-Denormalized'); + this.assertEquals(fromBytes([0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01]), -4.9406564584124654E-324, '-Denormalized'); + + this.assertEquals(fromBytes([0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]), -0, '-0'); + this.assertEquals(fromBytes([0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]), +0, '+0'); + + // TODO: Denormalized values fail on Safari iOS/ARM + this.assertEquals(fromBytes([0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01]), 4.9406564584124654E-324, '+Denormalized'); + this.assertEquals(fromBytes([0x00, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff]), 2.2250738585072010E-308, '+Denormalized'); + + this.assertEquals(fromBytes([0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]), 2.2250738585072014E-308, '+Normalized'); + this.assertEquals(fromBytes([0x7f, 0xef, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff]), 1.7976931348623157E+308, '+Normalized'); + + this.assertEquals(fromBytes([0x7f, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]), +Infinity, '+Infinity'); + + this.assertEquals(isNaN(fromBytes([0x7f, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01])), true, 'S+NaN'); + this.assertEquals(isNaN(fromBytes([0x7f, 0xf7, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff])), true, 'S+NaN'); + + this.assertEquals(isNaN(fromBytes([0x7f, 0xf8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00])), true, 'Q+NaN'); + this.assertEquals(isNaN(fromBytes([0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff])), true, 'Q+NaN'); +}); + + +test('IEEE754 double precision packing', function () { + + function toBytes(v) { + var uint8 = new Uint8Array(8), + dv = new DataView(uint8.buffer); + dv.setFloat64(0, v); + var bytes = []; + for (var i = 0; i < 8; i += 1) { + bytes.push(uint8.get(i)); + } + return bytes; + } + + this.arrayEqual(toBytes(-Infinity), [0xff, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00], '-Infinity'); + + this.arrayEqual(toBytes(-1.7976931348623157E+308), [0xff, 0xef, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff], '-Normalized'); + this.arrayEqual(toBytes(-2.2250738585072014E-308), [0x80, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00], '-Normalized'); + + // TODO: Denormalized values fail on Safari iOS/ARM + this.arrayEqual(toBytes(-2.2250738585072010E-308), [0x80, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff], '-Denormalized'); + this.arrayEqual(toBytes(-4.9406564584124654E-324), [0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01], '-Denormalized'); + + this.arrayEqual(toBytes(-0), [0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00], '-0'); + this.arrayEqual(toBytes(0), [0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00], '+0'); + + // TODO: Denormalized values fail on Safari iOS/ARM + this.arrayEqual(toBytes(4.9406564584124654E-324), [0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01], '+Denormalized'); + this.arrayEqual(toBytes(2.2250738585072010E-308), [0x00, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff], '+Denormalized'); + + this.arrayEqual(toBytes(2.2250738585072014E-308), [0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00], '+Normalized'); + this.arrayEqual(toBytes(1.7976931348623157E+308), [0x7f, 0xef, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff], '+Normalized'); + + this.arrayEqual(toBytes(+Infinity), [0x7f, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00], '+Infinity'); + + // Allow any NaN pattern (exponent all 1's, fraction non-zero) + var nanbytes = toBytes(NaN), + sign = extractbits(nanbytes, 63, 63), + exponent = extractbits(nanbytes, 52, 62), + fraction = extractbits(nanbytes, 0, 51); + this.assertEquals(exponent === 2047 && fraction !== 0, true, 'NaN'); +}); + +test('Int32Array round trips', function () { + var i32 = new Int32Array([0]); + var data = [ + 0, + 1, + -1, + 123, + -456, + 0x80000000 >> 0, + 0x7fffffff >> 0, + 0x12345678 >> 0, + 0x87654321 >> 0 + ]; + + for (var i = 0; i < data.length; i += 1) { + var datum = data[i]; + i32.set([datum]); + this.assertEquals(datum, i32.get(0), String(datum)); + } +}); + + +test('Int16Array round trips', function () { + var i16 = new Int16Array([0]); + var data = [ + 0, + 1, + -1, + 123, + -456, + 0xffff8000 >> 0, + 0x00007fff >> 0, + 0x00001234 >> 0, + 0xffff8765 >> 0 + ]; + + for (var i = 0; i < data.length; i += 1) { + var datum = data[i]; + i16.set([datum]); + this.assertEquals(datum, i16.get(0), String(datum)); + } +}); + + +test('Int8Array round trips', function () { + var i8 = new Int8Array([0]); + var data = [ + 0, + 1, + -1, + 123, + -45, + 0xffffff80 >> 0, + 0x0000007f >> 0, + 0x00000012 >> 0, + 0xffffff87 >> 0 + ]; + + for (var i = 0; i < data.length; i += 1) { + var datum = data[i]; + i8.set([datum]); + this.assertEquals(datum, i8.get(0), String(datum)); + } +}); + +test('TypedArray setting', function () { + + var a = new Int32Array([1, 2, 3, 4, 5]); + var b = new Int32Array(5); + b.set(a); + this.arrayEqual(b, [1, 2, 3, 4, 5], '1'); + this.raises(function () { b.set(a, 1); }); + + b.set(new Int32Array([99, 98]), 2); + this.arrayEqual(b, [1, 2, 99, 98, 5], '2'); + + b.set(new Int32Array([99, 98, 97]), 2); + this.arrayEqual(b, [1, 2, 99, 98, 97], '3'); + + this.raises(function () { b.set(new Int32Array([99, 98, 97, 96]), 2); }); + this.raises(function () { b.set([101, 102, 103, 104], 4); }); + + // ab = [ 0, 1, 2, 3, 4, 5, 6, 7 ] + // a1 = [ ^, ^, ^, ^, ^, ^, ^, ^ ] + // a2 = [ ^, ^, ^, ^ ] + var ab = new ArrayBuffer(8); + var a1 = new Uint8Array(ab); + for (var i = 0; i < a1.length; i += 1) { a1.set([i], i); } + var a2 = new Uint8Array(ab, 4); + a1.set(a2, 2); + this.arrayEqual(a1, [0, 1, 4, 5, 6, 7, 6, 7]); + this.arrayEqual(a2, [6, 7, 6, 7]); +}); + + +test('TypedArray.subarray', function () { + + var a = new Int32Array([1, 2, 3, 4, 5]); + this.arrayEqual(a.subarray(3), [4, 5]); + this.arrayEqual(a.subarray(1, 3), [2, 3]); + this.arrayEqual(a.subarray(-3), [3, 4, 5]); + this.arrayEqual(a.subarray(-3, -1), [3, 4]); + this.arrayEqual(a.subarray(3, 2), []); + this.arrayEqual(a.subarray(-2, -3), []); + this.arrayEqual(a.subarray(4, 1), []); + this.arrayEqual(a.subarray(-1, -4), []); + this.arrayEqual(a.subarray(1).subarray(1), [3, 4, 5]); + this.arrayEqual(a.subarray(1, 4).subarray(1, 2), [3]); +}); + + +test('DataView constructors', function () { + + var d = new DataView(new ArrayBuffer(8)); + + d.setUint32(0, 0x12345678); + this.assertEquals(d.getUint32(0), 0x12345678, 'big endian/big endian'); + + d.setUint32(0, 0x12345678, true); + this.assertEquals(d.getUint32(0, true), 0x12345678, 'little endian/little endian'); + + d.setUint32(0, 0x12345678, true); + this.assertEquals(d.getUint32(0), 0x78563412, 'little endian/big endian'); + + d.setUint32(0, 0x12345678); + this.assertEquals(d.getUint32(0, true), 0x78563412, 'big endian/little endian'); + + // Chrome allows no arguments, throws if non-ArrayBuffer + //stricterEqual(new DataView().buffer.byteLength, 0, 'no arguments'); + + // Safari (iOS 5) does not + //raises(function () { return new DataView(); }, TypeError, 'no arguments'); + + // Chrome raises TypeError, Safari iOS5 raises isDOMException(INDEX_SIZE_ERR) + this.raises(function () { return new DataView({}); }, 'non-ArrayBuffer argument'); + + this.raises(function () { return new DataView("bogus"); }, TypeError, 'non-ArrayBuffer argument'); +}); + + + +test('DataView accessors', function () { + var u = new Uint8Array(8), d = new DataView(u.buffer); + this.arrayEqual(u, [0, 0, 0, 0, 0, 0, 0, 0], '1'); + + d.setUint8(0, 255); + this.arrayEqual(u, [0xff, 0, 0, 0, 0, 0, 0, 0], '2'); + + d.setInt8(1, -1); + this.arrayEqual(u, [0xff, 0xff, 0, 0, 0, 0, 0, 0], '3'); + + d.setUint16(2, 0x1234); + this.arrayEqual(u, [0xff, 0xff, 0x12, 0x34, 0, 0, 0, 0]), '4'; + + d.setInt16(4, -1); + this.arrayEqual(u, [0xff, 0xff, 0x12, 0x34, 0xff, 0xff, 0, 0], '5'); + + d.setUint32(1, 0x12345678); + this.arrayEqual(u, [0xff, 0x12, 0x34, 0x56, 0x78, 0xff, 0, 0], '6'); + + d.setInt32(4, -2023406815); + this.arrayEqual(u, [0xff, 0x12, 0x34, 0x56, 0x87, 0x65, 0x43, 0x21], '7'); + + d.setFloat32(2, 1.2E+38); + this.arrayEqual(u, [0xff, 0x12, 0x7e, 0xb4, 0x8e, 0x52, 0x43, 0x21], '8'); + + d.setFloat64(0, -1.2345678E+301); + this.arrayEqual(u, [0xfe, 0x72, 0x6f, 0x51, 0x5f, 0x61, 0x77, 0xe5], '9'); + + u.set([0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87]); + this.assertEquals(d.getUint8(0), 128, '10'); + this.assertEquals(d.getInt8(1), -127, '11'); + this.assertEquals(d.getUint16(2), 33411, '12'); + this.assertEquals(d.getInt16(3), -31868, '13'); + this.assertEquals(d.getUint32(4), 2223343239, '14'); + this.assertEquals(d.getInt32(2), -2105310075, '15'); + this.assertEquals(d.getFloat32(2), -1.932478247535851e-37, '16'); + this.assertEquals(d.getFloat64(0), -3.116851295377095e-306, '17'); + +}); + + +test('DataView endian', function () { + var rawbuf = (new Uint8Array([0, 1, 2, 3, 4, 5, 6, 7])).buffer; + var d; + + d = new DataView(rawbuf); + this.assertEquals(d.byteLength, 8, 'buffer'); + this.assertEquals(d.byteOffset, 0, 'buffer'); + this.raises(function () { d.getUint8(-2); }); // Chrome bug for index -, DOMException, 'bounds for buffer'? + this.raises(function () { d.getUint8(8); }, 'bounds for buffer'); + this.raises(function () { d.setUint8(-2, 0); }, 'bounds for buffer'); + this.raises(function () { d.setUint8(8, 0); }, 'bounds for buffer'); + + d = new DataView(rawbuf, 2); + this.assertEquals(d.byteLength, 6, 'buffer, byteOffset'); + this.assertEquals(d.byteOffset, 2, 'buffer, byteOffset'); + this.assertEquals(d.getUint8(5), 7, 'buffer, byteOffset'); + this.raises(function () { d.getUint8(-2); }, 'bounds for buffer, byteOffset'); + this.raises(function () { d.getUint8(6); }, 'bounds for buffer, byteOffset'); + this.raises(function () { d.setUint8(-2, 0); }, 'bounds for buffer, byteOffset'); + this.raises(function () { d.setUint8(6, 0); }, 'bounds for buffer, byteOffset'); + + d = new DataView(rawbuf, 8); + this.assertEquals(d.byteLength, 0, 'buffer, byteOffset'); + + this.raises(function () { return new DataView(rawbuf, -1); }, 'invalid byteOffset'); + this.raises(function () { return new DataView(rawbuf, 9); }, 'invalid byteOffset'); + this.raises(function () { return new DataView(rawbuf, -1); }, 'invalid byteOffset'); + + d = new DataView(rawbuf, 2, 4); + this.assertEquals(d.byteLength, 4, 'buffer, byteOffset, length'); + this.assertEquals(d.byteOffset, 2, 'buffer, byteOffset, length'); + this.assertEquals(d.getUint8(3), 5, 'buffer, byteOffset, length'); + this.raises(function () { return d.getUint8(-2); }, 'bounds for buffer, byteOffset, length'); + this.raises(function () { d.getUint8(4); }, 'bounds for buffer, byteOffset, length'); + this.raises(function () { d.setUint8(-2, 0); }, 'bounds for buffer, byteOffset, length'); + this.raises(function () { d.setUint8(4, 0); }, 'bounds for buffer, byteOffset, length'); + + this.raises(function () { return new DataView(rawbuf, 0, 9); }, 'invalid byteOffset+length'); + this.raises(function () { return new DataView(rawbuf, 8, 1); }, 'invalid byteOffset+length'); + this.raises(function () { return new DataView(rawbuf, 9, -1); }, 'invalid byteOffset+length'); +}); + + +test('Typed Array getters/setters', function () { + // Only supported if Object.defineProperty() is fully supported on non-DOM objects. + try { + var o = {}; + Object.defineProperty(o, 'x', { get: function() { return 1; } }); + if (o.x !== 1) throw Error(); + } catch (_) { + ok(true); + return; + } + + var bytes = new Uint8Array([1, 2, 3, 4]), + uint32s = new Uint32Array(bytes.buffer); + + this.assertEquals(bytes[1], 2); + uint32s[0] = 0xffffffff; + this.assertEquals(bytes[1], 0xff); +}); + + +test('Uint8ClampedArray', function () { + this.assertEquals(Uint8ClampedArray.BYTES_PER_ELEMENT, 1, 'Uint8ClampedArray.BYTES_PER_ELEMENT'); + var a = new Uint8ClampedArray([-Infinity, -Number.MAX_VALUE, -1, -Number.MIN_VALUE, -0, + 0, Number.MIN_VALUE, 1, 1.1, 1.9, 255, 255.1, 255.9, 256, Number.MAX_VALUE, Infinity, + NaN]); + this.assertEquals(a.BYTES_PER_ELEMENT, 1); + this.assertEquals(a.byteOffset, 0); + this.assertEquals(a.byteLength, 17); + this.arrayEqual(a, [0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0], "array test"); +}); + +test('Regression Tests', function() { + // Bug: https://github.com/inexorabletash/polyfill/issues/16 + var minFloat32 = 1.401298464324817e-45; + var truncated = new Float32Array([-minFloat32 / 2 - Math.pow(2, -202)]).get(0); + this.assertEquals(truncated, -minFloat32, 'smallest 32 bit float should not truncate to zero'); +}); + diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index f13a92bba0..48181d0244 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -3642,6 +3642,8 @@ ScriptEngine* Application::loadScript(const QString& scriptName, bool loadScript scriptEngine->registerGlobalObject("AudioReflector", &_audioReflector); scriptEngine->registerGlobalObject("Account", AccountScriptingInterface::getInstance()); + scriptEngine->registerGlobalObject("AvatarManager", &_avatarManager); + #ifdef HAVE_RTMIDI scriptEngine->registerGlobalObject("MIDI", &MIDIManager::getInstance()); #endif diff --git a/interface/src/Audio.cpp b/interface/src/Audio.cpp index 3689ff0143..6a26563e36 100644 --- a/interface/src/Audio.cpp +++ b/interface/src/Audio.cpp @@ -112,12 +112,11 @@ Audio::Audio(int16_t initialJitterBufferSamples, QObject* parent) : _scopeInput(0), _scopeOutputLeft(0), _scopeOutputRight(0), - _audioMixerAvatarStreamAudioStats(), + _starveCount(0), + _consecutiveNotMixedCount(0), _outgoingAvatarAudioSequenceNumber(0), _incomingMixedAudioSequenceNumberStats(INCOMING_SEQ_STATS_HISTORY_LENGTH), - _interframeTimeGapStats(TIME_GAPS_STATS_INTERVAL_SAMPLES, TIME_GAP_STATS_WINDOW_INTERVALS), - _starveCount(0), - _consecutiveNotMixedCount(0) + _interframeTimeGapStats(TIME_GAPS_STATS_INTERVAL_SAMPLES, TIME_GAP_STATS_WINDOW_INTERVALS) { // clear the array of locally injected samples memset(_localProceduralSamples, 0, NETWORK_BUFFER_LENGTH_BYTES_PER_CHANNEL); diff --git a/interface/src/avatar/Avatar.cpp b/interface/src/avatar/Avatar.cpp index 4645dd6363..fa061c9df6 100644 --- a/interface/src/avatar/Avatar.cpp +++ b/interface/src/avatar/Avatar.cpp @@ -62,7 +62,7 @@ Avatar::Avatar() : _mouseRayDirection(0.0f, 0.0f, 0.0f), _moving(false), _collisionGroups(0), - _numLocalLights(2), + _numLocalLights(0), _initialized(false), _shouldRenderBillboard(true) { @@ -84,23 +84,6 @@ void Avatar::init() { _skeletonModel.init(); _initialized = true; _shouldRenderBillboard = (getLODDistance() >= BILLBOARD_LOD_DISTANCE); - - for (int i = 0; i < MAX_LOCAL_LIGHTS; i++) { - _localLightColors[i] = glm::vec3(0.0f, 0.0f, 0.0f); - _localLightDirections[i] = glm::vec3(0.0f, 0.0f, 0.0f); - } - - glm::vec3 darkGrayColor(0.4f, 0.4f, 0.4f); - glm::vec3 greenColor(0.0f, 1.0f, 0.0f); - glm::vec3 directionX(1.0f, 0.0f, 0.0f); - glm::vec3 directionY(0.0f, 1.0f, 0.0f); - - // initialize local lights - _localLightColors[0] = darkGrayColor; - _localLightColors[1] = darkGrayColor; - - _localLightDirections[0] = directionX; - _localLightDirections[1] = directionY; } glm::vec3 Avatar::getChestPosition() const { diff --git a/interface/src/avatar/AvatarManager.cpp b/interface/src/avatar/AvatarManager.cpp index debe6489ea..bc84aa6b87 100644 --- a/interface/src/avatar/AvatarManager.cpp +++ b/interface/src/avatar/AvatarManager.cpp @@ -158,3 +158,56 @@ void AvatarManager::clearOtherAvatars() { } _myAvatar->clearLookAtTargetAvatar(); } + +Avatar* AvatarManager::getAvatarFromIndex(int avatarIndex) { + Avatar* avatar = NULL; + int numAvatars = _avatarHash.count(); + if (avatarIndex < numAvatars) { + QUuid key = (_avatarHash.keys())[avatarIndex]; + + const AvatarSharedPointer& avatarPointer = _avatarHash.value(key); + avatar = static_cast(avatarPointer.data()); + } + + return avatar; +} + +void AvatarManager::addAvatarLocalLight(int avatarIndex) { + Avatar* avatar = getAvatarFromIndex(avatarIndex); + if (avatar) { + avatar->addLocalLight(); + } +} + +void AvatarManager::removeAvatarLocalLight(int avatarIndex) { + Avatar* avatar = getAvatarFromIndex(avatarIndex); + if (avatar) { + avatar->removeLocalLight(); + } +} + +void AvatarManager::setAvatarLightDirection(const glm::vec3& direction, int lightIndex, int avatarIndex) { + Avatar* avatar = getAvatarFromIndex(avatarIndex); + if (avatar) { + avatar->setLocalLightDirection(direction, lightIndex); + } +} + +void AvatarManager::setAvatarLightColor(const glm::vec3& color, int lightIndex, int avatarIndex) { + Avatar* avatar = getAvatarFromIndex(avatarIndex); + if (avatar) { + avatar->setLocalLightColor(color, lightIndex); + } +} + +int AvatarManager::getNumAvatars() { + return _avatarHash.count(); +} + +QString AvatarManager::getAvatarHashKey(int index) { + QString id = ((_avatarHash.keys())[index]).toString(); + std::string idString = id.toStdString(); + return id; +} + + diff --git a/interface/src/avatar/AvatarManager.h b/interface/src/avatar/AvatarManager.h index 048844ddf2..f2b5904883 100644 --- a/interface/src/avatar/AvatarManager.h +++ b/interface/src/avatar/AvatarManager.h @@ -24,6 +24,7 @@ class MyAvatar; class AvatarManager : public AvatarHashMap { Q_OBJECT + public: AvatarManager(QObject* parent = 0); @@ -36,6 +37,14 @@ public: void clearOtherAvatars(); +public slots: + void setAvatarLightColor(const glm::vec3& color, int lightIndex, int avatarIndex); + void setAvatarLightDirection(const glm::vec3& direction, int lightIndex, int avatarIndex); + void removeAvatarLocalLight(int avatarIndex); + void addAvatarLocalLight(int avatarIndex); + int getNumAvatars(); + QString getAvatarHashKey(int index); + private: AvatarManager(const AvatarManager& other); @@ -44,6 +53,8 @@ private: AvatarSharedPointer newSharedAvatar(); + Avatar* getAvatarFromIndex(int avatarIndex); + // virtual override AvatarHash::iterator erase(const AvatarHash::iterator& iterator); diff --git a/interface/src/devices/MIDIManager.cpp b/interface/src/devices/MIDIManager.cpp index 52ae2eb645..54428d273d 100644 --- a/interface/src/devices/MIDIManager.cpp +++ b/interface/src/devices/MIDIManager.cpp @@ -44,7 +44,9 @@ MIDIManager::~MIDIManager() { #endif } +#ifdef HAVE_RTMIDI const int DEFAULT_MIDI_PORT = 0; +#endif void MIDIManager::openDefaultPort() { #ifdef HAVE_RTMIDI diff --git a/interface/src/location/LocationManager.cpp b/interface/src/location/LocationManager.cpp index 32172d6e38..cbf7d3dfd0 100644 --- a/interface/src/location/LocationManager.cpp +++ b/interface/src/location/LocationManager.cpp @@ -13,6 +13,7 @@ #include "Application.h" #include "LocationManager.h" +#include const QString GET_USER_ADDRESS = "/api/v1/users/%1/address"; const QString GET_PLACE_ADDRESS = "/api/v1/places/%1"; @@ -60,23 +61,30 @@ void LocationManager::createNamedLocation(NamedLocation* namedLocation) { } void LocationManager::goTo(QString destination) { - + const QString USER_DESTINATION_TYPE = "user"; + const QString PLACE_DESTINATION_TYPE = "place"; + const QString OTHER_DESTINATION_TYPE = "coordinate_or_username"; + if (destination.startsWith("@")) { // remove '@' and go to user - goToUser(destination.remove(0, 1)); + QString destinationUser = destination.remove(0, 1); + UserActivityLogger::getInstance().wentTo(USER_DESTINATION_TYPE, destinationUser); + goToUser(destinationUser); return; } if (destination.startsWith("#")) { // remove '#' and go to named place - goToPlace(destination.remove(0, 1)); + QString destinationPlace = destination.remove(0, 1); + UserActivityLogger::getInstance().wentTo(PLACE_DESTINATION_TYPE, destinationPlace); + goToPlace(destinationPlace); return; } // go to coordinate destination or to Username if (!goToDestination(destination)) { destination = QString(QUrl::toPercentEncoding(destination)); - + UserActivityLogger::getInstance().wentTo(OTHER_DESTINATION_TYPE, destination); JSONCallbackParameters callbackParams; callbackParams.jsonCallbackReceiver = this; callbackParams.jsonCallbackMethod = "goToAddressFromResponse"; diff --git a/interface/src/ui/Stats.cpp b/interface/src/ui/Stats.cpp index b40db71132..d865fc8004 100644 --- a/interface/src/ui/Stats.cpp +++ b/interface/src/ui/Stats.cpp @@ -335,26 +335,28 @@ void Stats::display( verticalOffset += STATS_PELS_PER_LINE; drawText(horizontalOffset, verticalOffset, scale, rotation, font, downstreamLabelString, color); - char downstreamAudioStatsString[30]; + char downstreamAudioStatsString[512]; AudioStreamStats downstreamAudioStreamStats = audio->getDownstreamAudioStreamStats(); - sprintf(downstreamAudioStatsString, " mix: %.1f%%/%.1f%%, %u/?/%u", downstreamAudioStreamStats._packetStreamStats.getLostRate()*100.0f, + sprintf(downstreamAudioStatsString, " mix: %.2f%%/%.2f%%, %u/?/%u", downstreamAudioStreamStats._packetStreamStats.getLostRate()*100.0f, downstreamAudioStreamStats._packetStreamWindowStats.getLostRate() * 100.0f, downstreamAudioStreamStats._ringBufferFramesAvailable, downstreamAudioStreamStats._ringBufferDesiredJitterBufferFrames); verticalOffset += STATS_PELS_PER_LINE; drawText(horizontalOffset, verticalOffset, scale, rotation, font, downstreamAudioStatsString, color); - sprintf(downstreamAudioStatsString, " %llu/%llu/%.2f, %u/%u", downstreamAudioStreamStats._timeGapMin, - downstreamAudioStreamStats._timeGapMax, downstreamAudioStreamStats._timeGapAverage, + sprintf(downstreamAudioStatsString, " %s/%s/%s, %u/%u", formatUsecTime(downstreamAudioStreamStats._timeGapMin).toLatin1().data(), + formatUsecTime(downstreamAudioStreamStats._timeGapMax).toLatin1().data(), + formatUsecTime(downstreamAudioStreamStats._timeGapAverage).toLatin1().data(), downstreamAudioStreamStats._ringBufferStarveCount, downstreamAudioStreamStats._ringBufferOverflowCount); verticalOffset += STATS_PELS_PER_LINE; drawText(horizontalOffset, verticalOffset, scale, rotation, font, downstreamAudioStatsString, color); - sprintf(downstreamAudioStatsString, " %llu/%llu/%.2f, %u/?", downstreamAudioStreamStats._timeGapWindowMin, - downstreamAudioStreamStats._timeGapWindowMax, downstreamAudioStreamStats._timeGapWindowAverage, + sprintf(downstreamAudioStatsString, " %s/%s/%s, %u/?", formatUsecTime(downstreamAudioStreamStats._timeGapWindowMin).toLatin1().data(), + formatUsecTime(downstreamAudioStreamStats._timeGapWindowMax).toLatin1().data(), + formatUsecTime(downstreamAudioStreamStats._timeGapWindowAverage).toLatin1().data(), downstreamAudioStreamStats._ringBufferConsecutiveNotMixedCount); verticalOffset += STATS_PELS_PER_LINE; @@ -365,11 +367,11 @@ void Stats::display( verticalOffset += STATS_PELS_PER_LINE; drawText(horizontalOffset, verticalOffset, scale, rotation, font, upstreamLabelString, color); - char upstreamAudioStatsString[30]; + char upstreamAudioStatsString[512]; const AudioStreamStats& audioMixerAvatarAudioStreamStats = audio->getAudioMixerAvatarStreamAudioStats(); - sprintf(upstreamAudioStatsString, " mic: %.1f%%/%.1f%%, %u/%u/%u", audioMixerAvatarAudioStreamStats._packetStreamStats.getLostRate()*100.0f, + sprintf(upstreamAudioStatsString, " mic: %.2f%%/%.2f%%, %u/%u/%u", audioMixerAvatarAudioStreamStats._packetStreamStats.getLostRate()*100.0f, audioMixerAvatarAudioStreamStats._packetStreamWindowStats.getLostRate() * 100.0f, audioMixerAvatarAudioStreamStats._ringBufferFramesAvailable, audioMixerAvatarAudioStreamStats._ringBufferCurrentJitterBufferFrames, audioMixerAvatarAudioStreamStats._ringBufferDesiredJitterBufferFrames); @@ -377,15 +379,17 @@ void Stats::display( verticalOffset += STATS_PELS_PER_LINE; drawText(horizontalOffset, verticalOffset, scale, rotation, font, upstreamAudioStatsString, color); - sprintf(upstreamAudioStatsString, " %llu/%llu/%.2f, %u/%u", audioMixerAvatarAudioStreamStats._timeGapMin, - audioMixerAvatarAudioStreamStats._timeGapMax, audioMixerAvatarAudioStreamStats._timeGapAverage, + sprintf(upstreamAudioStatsString, " %s/%s/%s, %u/%u", formatUsecTime(audioMixerAvatarAudioStreamStats._timeGapMin).toLatin1().data(), + formatUsecTime(audioMixerAvatarAudioStreamStats._timeGapMax).toLatin1().data(), + formatUsecTime(audioMixerAvatarAudioStreamStats._timeGapAverage).toLatin1().data(), audioMixerAvatarAudioStreamStats._ringBufferStarveCount, audioMixerAvatarAudioStreamStats._ringBufferOverflowCount); verticalOffset += STATS_PELS_PER_LINE; drawText(horizontalOffset, verticalOffset, scale, rotation, font, upstreamAudioStatsString, color); - sprintf(upstreamAudioStatsString, " %llu/%llu/%.2f, %u/%u", audioMixerAvatarAudioStreamStats._timeGapWindowMin, - audioMixerAvatarAudioStreamStats._timeGapWindowMax, audioMixerAvatarAudioStreamStats._timeGapWindowAverage, + sprintf(upstreamAudioStatsString, " %s/%s/%s, %u/%u", formatUsecTime(audioMixerAvatarAudioStreamStats._timeGapWindowMin).toLatin1().data(), + formatUsecTime(audioMixerAvatarAudioStreamStats._timeGapWindowMax).toLatin1().data(), + formatUsecTime(audioMixerAvatarAudioStreamStats._timeGapWindowAverage).toLatin1().data(), audioMixerAvatarAudioStreamStats._ringBufferConsecutiveNotMixedCount, audioMixerAvatarAudioStreamStats._ringBufferSilentFramesDropped); verticalOffset += STATS_PELS_PER_LINE; @@ -393,7 +397,7 @@ void Stats::display( foreach(const AudioStreamStats& injectedStreamAudioStats, audioMixerInjectedStreamAudioStatsMap) { - sprintf(upstreamAudioStatsString, " inj: %.1f%%/%.1f%%, %u/%u/%u", injectedStreamAudioStats._packetStreamStats.getLostRate()*100.0f, + sprintf(upstreamAudioStatsString, " inj: %.2f%%/%.2f%%, %u/%u/%u", injectedStreamAudioStats._packetStreamStats.getLostRate()*100.0f, injectedStreamAudioStats._packetStreamWindowStats.getLostRate() * 100.0f, injectedStreamAudioStats._ringBufferFramesAvailable, injectedStreamAudioStats._ringBufferCurrentJitterBufferFrames, injectedStreamAudioStats._ringBufferDesiredJitterBufferFrames); @@ -401,15 +405,17 @@ void Stats::display( verticalOffset += STATS_PELS_PER_LINE; drawText(horizontalOffset, verticalOffset, scale, rotation, font, upstreamAudioStatsString, color); - sprintf(upstreamAudioStatsString, " %llu/%llu/%.2f, %u/%u", injectedStreamAudioStats._timeGapMin, - injectedStreamAudioStats._timeGapMax, injectedStreamAudioStats._timeGapAverage, + sprintf(upstreamAudioStatsString, " %s/%s/%s, %u/%u", formatUsecTime(injectedStreamAudioStats._timeGapMin).toLatin1().data(), + formatUsecTime(injectedStreamAudioStats._timeGapMax).toLatin1().data(), + formatUsecTime(injectedStreamAudioStats._timeGapAverage).toLatin1().data(), injectedStreamAudioStats._ringBufferStarveCount, injectedStreamAudioStats._ringBufferOverflowCount); verticalOffset += STATS_PELS_PER_LINE; drawText(horizontalOffset, verticalOffset, scale, rotation, font, upstreamAudioStatsString, color); - sprintf(upstreamAudioStatsString, " %llu/%llu/%.2f, %u/%u", injectedStreamAudioStats._timeGapWindowMin, - injectedStreamAudioStats._timeGapWindowMax, injectedStreamAudioStats._timeGapWindowAverage, + sprintf(upstreamAudioStatsString, " %s/%s/%s, %u/%u", formatUsecTime(injectedStreamAudioStats._timeGapWindowMin).toLatin1().data(), + formatUsecTime(injectedStreamAudioStats._timeGapWindowMax).toLatin1().data(), + formatUsecTime(injectedStreamAudioStats._timeGapWindowAverage).toLatin1().data(), injectedStreamAudioStats._ringBufferConsecutiveNotMixedCount, injectedStreamAudioStats._ringBufferSilentFramesDropped); verticalOffset += STATS_PELS_PER_LINE; diff --git a/libraries/audio/src/PositionalAudioRingBuffer.cpp b/libraries/audio/src/PositionalAudioRingBuffer.cpp index 411b02400d..8cba6d72b0 100644 --- a/libraries/audio/src/PositionalAudioRingBuffer.cpp +++ b/libraries/audio/src/PositionalAudioRingBuffer.cpp @@ -32,6 +32,7 @@ PositionalAudioRingBuffer::PositionalAudioRingBuffer(PositionalAudioRingBuffer:: _shouldLoopbackForNode(false), _shouldOutputStarveDebug(true), _isStereo(isStereo), + _nextOutputTrailingLoudness(0.0f), _listenerUnattenuatedZone(NULL), _lastFrameReceivedTime(0), _interframeTimeGapStatsForJitterCalc(TIME_GAPS_FOR_JITTER_CALC_INTERVAL_SAMPLES, TIME_GAPS_FOR_JITTER_CALC_WINDOW_INTERVALS), @@ -121,27 +122,35 @@ void PositionalAudioRingBuffer::updateNextOutputTrailingLoudness() { // ForBoundarySamples means that we expect the number of samples not to roll of the end of the ring buffer float nextLoudness = 0; - for (int i = 0; i < _numFrameSamples; ++i) { - nextLoudness += fabsf(_nextOutput[i]); + if (samplesAvailable() >= _numFrameSamples) { + for (int i = 0; i < _numFrameSamples; ++i) { + nextLoudness += fabsf(_nextOutput[i]); + } + nextLoudness /= _numFrameSamples; + nextLoudness /= MAX_SAMPLE_VALUE; } - nextLoudness /= _numFrameSamples; - nextLoudness /= MAX_SAMPLE_VALUE; - const int TRAILING_AVERAGE_FRAMES = 100; const float CURRENT_FRAME_RATIO = 1.0f / TRAILING_AVERAGE_FRAMES; const float PREVIOUS_FRAMES_RATIO = 1.0f - CURRENT_FRAME_RATIO; const float LOUDNESS_EPSILON = 0.000001f; + float oldNextOutputTrailingLoudness = _nextOutputTrailingLoudness; if (nextLoudness >= _nextOutputTrailingLoudness) { _nextOutputTrailingLoudness = nextLoudness; } else { _nextOutputTrailingLoudness = (_nextOutputTrailingLoudness * PREVIOUS_FRAMES_RATIO) + (CURRENT_FRAME_RATIO * nextLoudness); - + if (_nextOutputTrailingLoudness < LOUDNESS_EPSILON) { _nextOutputTrailingLoudness = 0; } } + + // fixes bug on Windows where _nextOutputTrailingLoudness sometimes becomes NaN. In that case, + // revert _nextOutputTrailingLoudness to its previous value + if (isNaN(_nextOutputTrailingLoudness)) { + _nextOutputTrailingLoudness = oldNextOutputTrailingLoudness; + } } bool PositionalAudioRingBuffer::shouldBeAddedToMix() { diff --git a/libraries/metavoxels/src/MetavoxelUtil.cpp b/libraries/metavoxels/src/MetavoxelUtil.cpp index f2f434b24d..d35b9a698a 100644 --- a/libraries/metavoxels/src/MetavoxelUtil.cpp +++ b/libraries/metavoxels/src/MetavoxelUtil.cpp @@ -26,6 +26,7 @@ #include "MetavoxelUtil.h" #include "ScriptCache.h" +#include "StreamUtils.h" static int scriptHashType = qRegisterMetaType(); static int parameterizedURLType = qRegisterMetaType(); @@ -310,9 +311,8 @@ Box operator*(const glm::mat4& matrix, const Box& box) { return newBox; } -QDebug& operator<<(QDebug& out, const Box& box) { - return out << '(' << box.minimum.x << box.minimum.y << box.minimum.z << ") (" << - box.maximum.x << box.maximum.y << box.maximum.z << ')'; +QDebug& operator<<(QDebug& dbg, const Box& box) { + return dbg.nospace() << "{type='Box', minimum=" << box.minimum << ", maximum=" << box.maximum << "}"; } QMetaObjectEditor::QMetaObjectEditor(QWidget* parent) : QWidget(parent) { diff --git a/libraries/metavoxels/src/MetavoxelUtil.h b/libraries/metavoxels/src/MetavoxelUtil.h index 34c2bac6be..83aac1318e 100644 --- a/libraries/metavoxels/src/MetavoxelUtil.h +++ b/libraries/metavoxels/src/MetavoxelUtil.h @@ -42,7 +42,7 @@ public: STREAM glm::vec3 minimum; STREAM glm::vec3 maximum; - Box(const glm::vec3& minimum = glm::vec3(), const glm::vec3& maximum = glm::vec3()); + explicit Box(const glm::vec3& minimum = glm::vec3(), const glm::vec3& maximum = glm::vec3()); bool contains(const glm::vec3& point) const; diff --git a/libraries/networking/src/UserActivityLogger.cpp b/libraries/networking/src/UserActivityLogger.cpp index 5b20c82263..0217fb9106 100644 --- a/libraries/networking/src/UserActivityLogger.cpp +++ b/libraries/networking/src/UserActivityLogger.cpp @@ -146,3 +146,15 @@ void UserActivityLogger::loadedScript(QString scriptName) { logAction(ACTION_NAME, actionDetails); } + +void UserActivityLogger::wentTo(QString destinationType, QString destinationName) { + const QString ACTION_NAME = "went_to"; + QJsonObject actionDetails; + const QString DESTINATION_TYPE_KEY = "destination_type"; + const QString DESTINATION_NAME_KEY = "detination_name"; + + actionDetails.insert(DESTINATION_TYPE_KEY, destinationType); + actionDetails.insert(DESTINATION_NAME_KEY, destinationName); + + logAction(ACTION_NAME, actionDetails); +} diff --git a/libraries/networking/src/UserActivityLogger.h b/libraries/networking/src/UserActivityLogger.h index 4823143234..7c48a72b73 100644 --- a/libraries/networking/src/UserActivityLogger.h +++ b/libraries/networking/src/UserActivityLogger.h @@ -35,6 +35,7 @@ public slots: void changedDomain(QString domainURL); void connectedDevice(QString typeOfDevice, QString deviceName); void loadedScript(QString scriptName); + void wentTo(QString destinationType, QString destinationName); private slots: void requestFinished(const QJsonObject& object); diff --git a/libraries/particles/src/Particle.cpp b/libraries/particles/src/Particle.cpp index 76890afafe..5fffefd8b1 100644 --- a/libraries/particles/src/Particle.cpp +++ b/libraries/particles/src/Particle.cpp @@ -368,7 +368,7 @@ Particle Particle::fromEditPacket(const unsigned char* data, int length, int& pr uint32_t editID; // check to make sure we have enough content to keep reading... - if (processedBytes + sizeof(editID) > length) { + if (length - (processedBytes + (int)sizeof(editID)) < 0) { valid = false; processedBytes = length; return newParticle; // fail as if we read the entire buffer @@ -387,7 +387,7 @@ Particle Particle::fromEditPacket(const unsigned char* data, int length, int& pr uint32_t creatorTokenID; // check to make sure we have enough content to keep reading... - if (processedBytes + sizeof(creatorTokenID) > length) { + if (length - (processedBytes + (int)sizeof(creatorTokenID)) < 0) { valid = false; processedBytes = length; return newParticle; // fail as if we read the entire buffer @@ -426,7 +426,7 @@ Particle Particle::fromEditPacket(const unsigned char* data, int length, int& pr // lastEdited // check to make sure we have enough content to keep reading... - if (processedBytes + sizeof(newParticle._lastEdited) > length) { + if (length - (processedBytes + (int)sizeof(newParticle._lastEdited)) < 0) { valid = false; processedBytes = length; return newParticle; // fail as if we read the entire buffer @@ -439,7 +439,7 @@ Particle Particle::fromEditPacket(const unsigned char* data, int length, int& pr // properties included bits uint16_t packetContainsBits = 0; if (!isNewParticle) { - if (processedBytes + sizeof(packetContainsBits) > length) { + if (length - (processedBytes + (int)sizeof(packetContainsBits)) < 0) { valid = false; processedBytes = length; return newParticle; // fail as if we read the entire buffer @@ -452,7 +452,7 @@ Particle Particle::fromEditPacket(const unsigned char* data, int length, int& pr // radius if (isNewParticle || ((packetContainsBits & CONTAINS_RADIUS) == CONTAINS_RADIUS)) { - if (processedBytes + sizeof(newParticle._radius) > length) { + if (length - (processedBytes + (int)sizeof(newParticle._radius)) < 0) { valid = false; processedBytes = length; return newParticle; // fail as if we read the entire buffer @@ -464,7 +464,7 @@ Particle Particle::fromEditPacket(const unsigned char* data, int length, int& pr // position if (isNewParticle || ((packetContainsBits & CONTAINS_POSITION) == CONTAINS_POSITION)) { - if (processedBytes + sizeof(newParticle._position) > length) { + if (length - (processedBytes + (int)sizeof(newParticle._position)) < 0) { valid = false; processedBytes = length; return newParticle; // fail as if we read the entire buffer @@ -476,7 +476,7 @@ Particle Particle::fromEditPacket(const unsigned char* data, int length, int& pr // color if (isNewParticle || ((packetContainsBits & CONTAINS_COLOR) == CONTAINS_COLOR)) { - if (processedBytes + sizeof(newParticle._color) > length) { + if (length - (processedBytes + (int)sizeof(newParticle._color)) < 0) { valid = false; processedBytes = length; return newParticle; // fail as if we read the entire buffer @@ -488,7 +488,7 @@ Particle Particle::fromEditPacket(const unsigned char* data, int length, int& pr // velocity if (isNewParticle || ((packetContainsBits & CONTAINS_VELOCITY) == CONTAINS_VELOCITY)) { - if (processedBytes + sizeof(newParticle._velocity) > length) { + if (length - (processedBytes + (int)sizeof(newParticle._velocity)) < 0) { valid = false; processedBytes = length; return newParticle; // fail as if we read the entire buffer @@ -500,7 +500,7 @@ Particle Particle::fromEditPacket(const unsigned char* data, int length, int& pr // gravity if (isNewParticle || ((packetContainsBits & CONTAINS_GRAVITY) == CONTAINS_GRAVITY)) { - if (processedBytes + sizeof(newParticle._gravity) > length) { + if (length - (processedBytes + (int)sizeof(newParticle._gravity)) < 0) { valid = false; processedBytes = length; return newParticle; // fail as if we read the entire buffer @@ -512,7 +512,7 @@ Particle Particle::fromEditPacket(const unsigned char* data, int length, int& pr // damping if (isNewParticle || ((packetContainsBits & CONTAINS_DAMPING) == CONTAINS_DAMPING)) { - if (processedBytes + sizeof(newParticle._damping) > length) { + if (length - (processedBytes + (int)sizeof(newParticle._damping)) < 0) { valid = false; processedBytes = length; return newParticle; // fail as if we read the entire buffer @@ -524,7 +524,7 @@ Particle Particle::fromEditPacket(const unsigned char* data, int length, int& pr // lifetime if (isNewParticle || ((packetContainsBits & CONTAINS_LIFETIME) == CONTAINS_LIFETIME)) { - if (processedBytes + sizeof(newParticle._lifetime) > length) { + if (length - (processedBytes + (int)sizeof(newParticle._lifetime)) < 0) { valid = false; processedBytes = length; return newParticle; // fail as if we read the entire buffer @@ -537,7 +537,7 @@ Particle Particle::fromEditPacket(const unsigned char* data, int length, int& pr // TODO: make inHand and shouldDie into single bits // inHand if (isNewParticle || ((packetContainsBits & CONTAINS_INHAND) == CONTAINS_INHAND)) { - if (processedBytes + sizeof(newParticle._inHand) > length) { + if (length - (processedBytes + (int)sizeof(newParticle._inHand)) < 0) { valid = false; processedBytes = length; return newParticle; // fail as if we read the entire buffer @@ -549,7 +549,7 @@ Particle Particle::fromEditPacket(const unsigned char* data, int length, int& pr // shouldDie if (isNewParticle || ((packetContainsBits & CONTAINS_SHOULDDIE) == CONTAINS_SHOULDDIE)) { - if (processedBytes + sizeof(newParticle._shouldDie) > length) { + if (length - (processedBytes + (int)sizeof(newParticle._shouldDie)) < 0) { valid = false; processedBytes = length; return newParticle; // fail as if we read the entire buffer @@ -562,7 +562,7 @@ Particle Particle::fromEditPacket(const unsigned char* data, int length, int& pr // script if (isNewParticle || ((packetContainsBits & CONTAINS_SCRIPT) == CONTAINS_SCRIPT)) { uint16_t scriptLength; - if (processedBytes + sizeof(scriptLength) > length) { + if (length - (processedBytes + (int)sizeof(scriptLength)) < 0) { valid = false; processedBytes = length; return newParticle; // fail as if we read the entire buffer @@ -571,7 +571,7 @@ Particle Particle::fromEditPacket(const unsigned char* data, int length, int& pr dataAt += sizeof(scriptLength); processedBytes += sizeof(scriptLength); - if (processedBytes + scriptLength > length) { + if (length - (processedBytes + (int)scriptLength) < 0) { valid = false; processedBytes = length; return newParticle; // fail as if we read the entire buffer @@ -585,7 +585,7 @@ Particle Particle::fromEditPacket(const unsigned char* data, int length, int& pr // modelURL if (isNewParticle || ((packetContainsBits & CONTAINS_MODEL_URL) == CONTAINS_MODEL_URL)) { uint16_t modelURLLength; - if (processedBytes + sizeof(modelURLLength) > length) { + if (length - (processedBytes + (int)sizeof(modelURLLength)) < 0) { valid = false; processedBytes = length; return newParticle; // fail as if we read the entire buffer @@ -594,7 +594,7 @@ Particle Particle::fromEditPacket(const unsigned char* data, int length, int& pr dataAt += sizeof(modelURLLength); processedBytes += sizeof(modelURLLength); - if (processedBytes + modelURLLength > length) { + if (length - (processedBytes + (int)modelURLLength) < 0) { valid = false; processedBytes = length; return newParticle; // fail as if we read the entire buffer @@ -607,7 +607,7 @@ Particle Particle::fromEditPacket(const unsigned char* data, int length, int& pr // modelScale if (isNewParticle || ((packetContainsBits & CONTAINS_MODEL_SCALE) == CONTAINS_MODEL_SCALE)) { - if (processedBytes + sizeof(newParticle._modelScale) > length) { + if (length - (processedBytes + (int)sizeof(newParticle._modelScale)) < 0) { valid = false; processedBytes = length; return newParticle; // fail as if we read the entire buffer @@ -619,7 +619,7 @@ Particle Particle::fromEditPacket(const unsigned char* data, int length, int& pr // modelTranslation if (isNewParticle || ((packetContainsBits & CONTAINS_MODEL_TRANSLATION) == CONTAINS_MODEL_TRANSLATION)) { - if (processedBytes + sizeof(newParticle._modelTranslation) > length) { + if (length - (processedBytes + (int)sizeof(newParticle._modelTranslation)) < 0) { valid = false; processedBytes = length; return newParticle; // fail as if we read the entire buffer @@ -632,7 +632,7 @@ Particle Particle::fromEditPacket(const unsigned char* data, int length, int& pr // modelRotation if (isNewParticle || ((packetContainsBits & CONTAINS_MODEL_ROTATION) == CONTAINS_MODEL_ROTATION)) { const int expectedBytesForPackedQuat = sizeof(uint16_t) * 4; // this is how we pack the quats - if (processedBytes + expectedBytesForPackedQuat > length) { + if (length - (processedBytes + expectedBytesForPackedQuat) < 0) { valid = false; processedBytes = length; return newParticle; // fail as if we read the entire buffer diff --git a/libraries/particles/src/ParticleCollisionSystem.cpp b/libraries/particles/src/ParticleCollisionSystem.cpp index 0291690c3d..17d1dd3bc6 100644 --- a/libraries/particles/src/ParticleCollisionSystem.cpp +++ b/libraries/particles/src/ParticleCollisionSystem.cpp @@ -181,9 +181,6 @@ void ParticleCollisionSystem::updateCollisionWithParticles(Particle* particleA) } } -// MIN_VALID_SPEED is obtained by computing speed gained at one gravity after the shortest expected frame -const float MIN_EXPECTED_FRAME_PERIOD = 0.0167f; // 1/60th of a second - void ParticleCollisionSystem::updateCollisionWithAvatars(Particle* particle) { // particles that are in hand, don't collide with avatars if (!_avatars || particle->getInHand()) { diff --git a/libraries/script-engine/src/ArrayBufferClass.cpp b/libraries/script-engine/src/ArrayBufferClass.cpp new file mode 100644 index 0000000000..ab33b5ffe7 --- /dev/null +++ b/libraries/script-engine/src/ArrayBufferClass.cpp @@ -0,0 +1,155 @@ +// +// ArrayBufferClass.cpp +// +// +// Created by Clement on 7/3/14. +// Copyright 2014 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#include + +#include "ArrayBufferPrototype.h" +#include "DataViewClass.h" +#include "ScriptEngine.h" +#include "TypedArrays.h" + +#include "ArrayBufferClass.h" + +static const QString CLASS_NAME = "ArrayBuffer"; + +Q_DECLARE_METATYPE(QByteArray*) + +ArrayBufferClass::ArrayBufferClass(ScriptEngine* scriptEngine) : +QObject(scriptEngine->getEngine()), +QScriptClass(scriptEngine->getEngine()), +_scriptEngine(scriptEngine) { + qScriptRegisterMetaType(engine(), toScriptValue, fromScriptValue); + QScriptValue global = engine()->globalObject(); + + // Save string handles for quick lookup + _name = engine()->toStringHandle(CLASS_NAME.toLatin1()); + _byteLength = engine()->toStringHandle(BYTE_LENGTH_PROPERTY_NAME.toLatin1()); + + // build prototype + _proto = engine()->newQObject(new ArrayBufferPrototype(this), + QScriptEngine::QtOwnership, + QScriptEngine::SkipMethodsInEnumeration | + QScriptEngine::ExcludeSuperClassMethods | + QScriptEngine::ExcludeSuperClassProperties); + _proto.setPrototype(global.property("Object").property("prototype")); + + // Register constructor + _ctor = engine()->newFunction(construct, _proto); + _ctor.setData(engine()->toScriptValue(this)); + + engine()->globalObject().setProperty(name(), _ctor); + + // Registering other array types + // The script engine is there parent so it'll delete them with itself + new DataViewClass(scriptEngine); + new Int8ArrayClass(scriptEngine); + new Uint8ArrayClass(scriptEngine); + new Uint8ClampedArrayClass(scriptEngine); + new Int16ArrayClass(scriptEngine); + new Uint16ArrayClass(scriptEngine); + new Int32ArrayClass(scriptEngine); + new Uint32ArrayClass(scriptEngine); + new Float32ArrayClass(scriptEngine); + new Float64ArrayClass(scriptEngine); +} + +QScriptValue ArrayBufferClass::newInstance(qint32 size) { + const qint32 MAX_LENGTH = 100000000; + if (size < 0) { + engine()->evaluate("throw \"ArgumentError: negative length\""); + return QScriptValue(); + } + if (size > MAX_LENGTH) { + engine()->evaluate("throw \"ArgumentError: absurd length\""); + return QScriptValue(); + } + + engine()->reportAdditionalMemoryCost(size); + QScriptEngine* eng = engine(); + QVariant variant = QVariant::fromValue(QByteArray(size, 0)); + QScriptValue data = eng->newVariant(variant); + return engine()->newObject(this, data); +} + +QScriptValue ArrayBufferClass::newInstance(const QByteArray& ba) { + QScriptValue data = engine()->newVariant(QVariant::fromValue(ba)); + return engine()->newObject(this, data); +} + +QScriptValue ArrayBufferClass::construct(QScriptContext* context, QScriptEngine* engine) { + ArrayBufferClass* cls = qscriptvalue_cast(context->callee().data()); + if (!cls) { + // return if callee (function called) is not of type ArrayBuffer + return QScriptValue(); + } + QScriptValue arg = context->argument(0); + if (!arg.isValid() || !arg.isNumber()) { + return QScriptValue(); + } + + quint32 size = arg.toInt32(); + QScriptValue newObject = cls->newInstance(size); + + if (context->isCalledAsConstructor()) { + // if called with keyword new, replace this object. + context->setThisObject(newObject); + return engine->undefinedValue(); + } + + return newObject; +} + +QScriptClass::QueryFlags ArrayBufferClass::queryProperty(const QScriptValue& object, + const QScriptString& name, + QueryFlags flags, uint* id) { + QByteArray* ba = qscriptvalue_cast(object.data()); + if (ba && name == _byteLength) { + // if the property queried is byteLength, only handle read access + return flags &= HandlesReadAccess; + } + return 0; // No access +} + +QScriptValue ArrayBufferClass::property(const QScriptValue& object, + const QScriptString& name, uint id) { + QByteArray* ba = qscriptvalue_cast(object.data()); + if (ba && name == _byteLength) { + return ba->length(); + } + return QScriptValue(); +} + +QScriptValue::PropertyFlags ArrayBufferClass::propertyFlags(const QScriptValue& object, + const QScriptString& name, uint id) { + return QScriptValue::Undeletable; +} + +QString ArrayBufferClass::name() const { + return _name.toString(); +} + +QScriptValue ArrayBufferClass::prototype() const { + return _proto; +} + +QScriptValue ArrayBufferClass::toScriptValue(QScriptEngine* engine, const QByteArray& ba) { + QScriptValue ctor = engine->globalObject().property(CLASS_NAME); + ArrayBufferClass* cls = qscriptvalue_cast(ctor.data()); + if (!cls) { + return engine->newVariant(QVariant::fromValue(ba)); + } + return cls->newInstance(ba); +} + +void ArrayBufferClass::fromScriptValue(const QScriptValue& obj, QByteArray& ba) { + ba = qvariant_cast(obj.data().toVariant()); +} + diff --git a/libraries/script-engine/src/ArrayBufferClass.h b/libraries/script-engine/src/ArrayBufferClass.h new file mode 100644 index 0000000000..f7ad8ad4bd --- /dev/null +++ b/libraries/script-engine/src/ArrayBufferClass.h @@ -0,0 +1,61 @@ +// +// ArrayBufferClass.h +// +// +// Created by Clement on 7/3/14. +// Copyright 2014 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#ifndef hifi_ArrayBufferClass_h +#define hifi_ArrayBufferClass_h + +#include +#include +#include +#include +#include +#include +#include + +class ScriptEngine; + +class ArrayBufferClass : public QObject, public QScriptClass { + Q_OBJECT +public: + ArrayBufferClass(ScriptEngine* scriptEngine); + QScriptValue newInstance(qint32 size); + QScriptValue newInstance(const QByteArray& ba); + + QueryFlags queryProperty(const QScriptValue& object, + const QScriptString& name, + QueryFlags flags, uint* id); + QScriptValue property(const QScriptValue& object, + const QScriptString& name, uint id); + QScriptValue::PropertyFlags propertyFlags(const QScriptValue& object, + const QScriptString& name, uint id); + + QString name() const; + QScriptValue prototype() const; + + ScriptEngine* getEngine() { return _scriptEngine; } + +private: + static QScriptValue construct(QScriptContext* context, QScriptEngine* engine); + + static QScriptValue toScriptValue(QScriptEngine* eng, const QByteArray& ba); + static void fromScriptValue(const QScriptValue& obj, QByteArray& ba); + + QScriptValue _proto; + QScriptValue _ctor; + + // JS Object attributes + QScriptString _name; + QScriptString _byteLength; + + ScriptEngine* _scriptEngine; +}; + +#endif // hifi_ArrayBufferClass_h \ No newline at end of file diff --git a/libraries/script-engine/src/ArrayBufferPrototype.cpp b/libraries/script-engine/src/ArrayBufferPrototype.cpp new file mode 100644 index 0000000000..53ebebc740 --- /dev/null +++ b/libraries/script-engine/src/ArrayBufferPrototype.cpp @@ -0,0 +1,48 @@ +// +// ArrayBufferPrototype.cpp +// +// +// Created by Clement on 7/3/14. +// Copyright 2014 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#include + +#include "ArrayBufferClass.h" +#include "ArrayBufferPrototype.h" + +Q_DECLARE_METATYPE(QByteArray*) + +ArrayBufferPrototype::ArrayBufferPrototype(QObject* parent) : QObject(parent) { +} + +QByteArray ArrayBufferPrototype::slice(qint32 begin, qint32 end) const { + QByteArray* ba = thisArrayBuffer(); + // if indices < 0 then they start from the end of the array + begin = (begin < 0) ? ba->size() + begin : begin; + end = (end < 0) ? ba->size() + end : end; + + // here we clamp the indices to fit the array + begin = glm::clamp(begin, 0, (ba->size() - 1)); + end = glm::clamp(end, 0, (ba->size() - 1)); + + return (end - begin > 0) ? ba->mid(begin, end - begin) : QByteArray(); +} + +QByteArray ArrayBufferPrototype::slice(qint32 begin) const { + QByteArray* ba = thisArrayBuffer(); + // if indices < 0 then they start from the end of the array + begin = (begin < 0) ? ba->size() + begin : begin; + + // here we clamp the indices to fit the array + begin = glm::clamp(begin, 0, (ba->size() - 1)); + + return ba->mid(begin, -1); +} + +QByteArray* ArrayBufferPrototype::thisArrayBuffer() const { + return qscriptvalue_cast(thisObject().data()); +} diff --git a/libraries/script-engine/src/ArrayBufferPrototype.h b/libraries/script-engine/src/ArrayBufferPrototype.h new file mode 100644 index 0000000000..09d4596f28 --- /dev/null +++ b/libraries/script-engine/src/ArrayBufferPrototype.h @@ -0,0 +1,31 @@ +// +// ArrayBufferPrototype.h +// +// +// Created by Clement on 7/3/14. +// Copyright 2014 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#ifndef hifi_ArrayBufferPrototype_h +#define hifi_ArrayBufferPrototype_h + +#include +#include + +class ArrayBufferPrototype : public QObject, public QScriptable { + Q_OBJECT +public: + ArrayBufferPrototype(QObject* parent = NULL); + +public slots: + QByteArray slice(qint32 begin, qint32 end) const; + QByteArray slice(qint32 begin) const; + +private: + QByteArray* thisArrayBuffer() const; +}; + +#endif // hifi_ArrayBufferPrototype_h \ No newline at end of file diff --git a/libraries/script-engine/src/ArrayBufferViewClass.cpp b/libraries/script-engine/src/ArrayBufferViewClass.cpp new file mode 100644 index 0000000000..aad2e6add7 --- /dev/null +++ b/libraries/script-engine/src/ArrayBufferViewClass.cpp @@ -0,0 +1,52 @@ +// +// ArrayBufferViewClass.cpp +// +// +// Created by Clement on 7/8/14. +// Copyright 2014 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#include "ArrayBufferViewClass.h" + +Q_DECLARE_METATYPE(QByteArray*) + +ArrayBufferViewClass::ArrayBufferViewClass(ScriptEngine* scriptEngine) : +QObject(scriptEngine->getEngine()), +QScriptClass(scriptEngine->getEngine()), +_scriptEngine(scriptEngine) { + // Save string handles for quick lookup + _bufferName = engine()->toStringHandle(BUFFER_PROPERTY_NAME.toLatin1()); + _byteOffsetName = engine()->toStringHandle(BYTE_OFFSET_PROPERTY_NAME.toLatin1()); + _byteLengthName = engine()->toStringHandle(BYTE_LENGTH_PROPERTY_NAME.toLatin1()); +} + +QScriptClass::QueryFlags ArrayBufferViewClass::queryProperty(const QScriptValue& object, + const QScriptString& name, + QueryFlags flags, uint* id) { + if (name == _bufferName || name == _byteOffsetName || name == _byteLengthName) { + return flags &= HandlesReadAccess; // Only keep read access flags + } + return 0; // No access +} + +QScriptValue ArrayBufferViewClass::property(const QScriptValue& object, + const QScriptString& name, uint id) { + if (name == _bufferName) { + return object.data().property(_bufferName); + } + if (name == _byteOffsetName) { + return object.data().property(_byteOffsetName); + } + if (name == _byteLengthName) { + return object.data().property(_byteLengthName); + } + return QScriptValue(); +} + +QScriptValue::PropertyFlags ArrayBufferViewClass::propertyFlags(const QScriptValue& object, + const QScriptString& name, uint id) { + return QScriptValue::Undeletable; +} diff --git a/libraries/script-engine/src/ArrayBufferViewClass.h b/libraries/script-engine/src/ArrayBufferViewClass.h new file mode 100644 index 0000000000..b673ebf280 --- /dev/null +++ b/libraries/script-engine/src/ArrayBufferViewClass.h @@ -0,0 +1,52 @@ +// +// ArrayBufferViewClass.h +// +// +// Created by Clement on 7/8/14. +// Copyright 2014 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#ifndef hifi_ArrayBufferViewClass_h +#define hifi_ArrayBufferViewClass_h + +#include +#include +#include +#include +#include +#include +#include + +#include "ScriptEngine.h" + +static const QString BUFFER_PROPERTY_NAME = "buffer"; +static const QString BYTE_OFFSET_PROPERTY_NAME = "byteOffset"; +static const QString BYTE_LENGTH_PROPERTY_NAME = "byteLength"; + +class ArrayBufferViewClass : public QObject, public QScriptClass { + Q_OBJECT +public: + ArrayBufferViewClass(ScriptEngine* scriptEngine); + + ScriptEngine* getScriptEngine() { return _scriptEngine; } + + virtual QueryFlags queryProperty(const QScriptValue& object, + const QScriptString& name, + QueryFlags flags, uint* id); + virtual QScriptValue property(const QScriptValue& object, + const QScriptString& name, uint id); + virtual QScriptValue::PropertyFlags propertyFlags(const QScriptValue& object, + const QScriptString& name, uint id); +protected: + // JS Object attributes + QScriptString _bufferName; + QScriptString _byteOffsetName; + QScriptString _byteLengthName; + + ScriptEngine* _scriptEngine; +}; + +#endif // hifi_ArrayBufferViewClass_h \ No newline at end of file diff --git a/libraries/script-engine/src/DataViewClass.cpp b/libraries/script-engine/src/DataViewClass.cpp new file mode 100644 index 0000000000..a65bdff617 --- /dev/null +++ b/libraries/script-engine/src/DataViewClass.cpp @@ -0,0 +1,94 @@ +// +// DataViewClass.cpp +// +// +// Created by Clement on 7/8/14. +// Copyright 2014 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#include "DataViewPrototype.h" + +#include "DataViewClass.h" + +Q_DECLARE_METATYPE(QByteArray*) + +static const QString DATA_VIEW_NAME = "DataView"; + +DataViewClass::DataViewClass(ScriptEngine* scriptEngine) : ArrayBufferViewClass(scriptEngine) { + QScriptValue global = engine()->globalObject(); + + // Save string handles for quick lookup + _name = engine()->toStringHandle(DATA_VIEW_NAME.toLatin1()); + + // build prototype + _proto = engine()->newQObject(new DataViewPrototype(this), + QScriptEngine::QtOwnership, + QScriptEngine::SkipMethodsInEnumeration | + QScriptEngine::ExcludeSuperClassMethods | + QScriptEngine::ExcludeSuperClassProperties); + _proto.setPrototype(global.property("Object").property("prototype")); + + // Register constructor + _ctor = engine()->newFunction(construct, _proto); + _ctor.setData(engine()->toScriptValue(this)); + engine()->globalObject().setProperty(name(), _ctor); +} + +QScriptValue DataViewClass::newInstance(QScriptValue buffer, quint32 byteOffset, quint32 byteLentgh) { + QScriptValue data = engine()->newObject(); + data.setProperty(_bufferName, buffer); + data.setProperty(_byteOffsetName, byteOffset); + data.setProperty(_byteLengthName, byteLentgh); + + return engine()->newObject(this, data); +} + +QScriptValue DataViewClass::construct(QScriptContext* context, QScriptEngine* engine) { + DataViewClass* cls = qscriptvalue_cast(context->callee().data()); + if (!cls || context->argumentCount() < 1) { + return QScriptValue(); + } + + QScriptValue bufferArg = context->argument(0); + QScriptValue byteOffsetArg = (context->argumentCount() >= 2) ? context->argument(1) : QScriptValue(); + QScriptValue byteLengthArg = (context->argumentCount() >= 3) ? context->argument(2) : QScriptValue(); + + QByteArray* arrayBuffer = (bufferArg.isValid()) ? qscriptvalue_cast(bufferArg.data()) :NULL; + if (!arrayBuffer) { + engine->evaluate("throw \"TypeError: 1st argument not a ArrayBuffer\""); + return QScriptValue(); + } + if (byteOffsetArg.isNumber() && + (byteOffsetArg.toInt32() < 0 || + byteOffsetArg.toInt32() > arrayBuffer->size())) { + engine->evaluate("throw \"RangeError: byteOffset out of range\""); + return QScriptValue(); + } + if (byteLengthArg.isNumber() && + (byteLengthArg.toInt32() < 0 || + byteOffsetArg.toInt32() + byteLengthArg.toInt32() > arrayBuffer->size())) { + engine->evaluate("throw \"RangeError: byteLength out of range\""); + return QScriptValue(); + } + quint32 byteOffset = (byteOffsetArg.isNumber()) ? byteOffsetArg.toInt32() : 0; + quint32 byteLength = (byteLengthArg.isNumber()) ? byteLengthArg.toInt32() : arrayBuffer->size() - byteOffset; + QScriptValue newObject = cls->newInstance(bufferArg, byteOffset, byteLength); + + if (context->isCalledAsConstructor()) { + context->setThisObject(newObject); + return engine->undefinedValue(); + } + + return newObject; +} + +QString DataViewClass::name() const { + return _name.toString(); +} + +QScriptValue DataViewClass::prototype() const { + return _proto; +} \ No newline at end of file diff --git a/libraries/script-engine/src/DataViewClass.h b/libraries/script-engine/src/DataViewClass.h new file mode 100644 index 0000000000..b87803f4b4 --- /dev/null +++ b/libraries/script-engine/src/DataViewClass.h @@ -0,0 +1,36 @@ +// +// DataViewClass.h +// +// +// Created by Clement on 7/8/14. +// Copyright 2014 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#ifndef hifi_DataViewClass_h +#define hifi_DataViewClass_h + +#include "ArrayBufferViewClass.h" + +class DataViewClass : public ArrayBufferViewClass { + Q_OBJECT +public: + DataViewClass(ScriptEngine* scriptEngine); + QScriptValue newInstance(QScriptValue buffer, quint32 byteOffset, quint32 byteLength); + + QString name() const; + QScriptValue prototype() const; + +private: + static QScriptValue construct(QScriptContext* context, QScriptEngine* engine); + + QScriptValue _proto; + QScriptValue _ctor; + + QScriptString _name; +}; + + +#endif // hifi_DataViewClass_h \ No newline at end of file diff --git a/libraries/script-engine/src/DataViewPrototype.cpp b/libraries/script-engine/src/DataViewPrototype.cpp new file mode 100644 index 0000000000..8bab574f33 --- /dev/null +++ b/libraries/script-engine/src/DataViewPrototype.cpp @@ -0,0 +1,255 @@ +// +// DataViewPrototype.cpp +// +// +// Created by Clement on 7/8/14. +// Copyright 2014 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// +#include + +#include + +#include "DataViewClass.h" + +#include "DataViewPrototype.h" + +Q_DECLARE_METATYPE(QByteArray*) + +DataViewPrototype::DataViewPrototype(QObject* parent) : QObject(parent) { +} + +QByteArray* DataViewPrototype::thisArrayBuffer() const { + QScriptValue bufferObject = thisObject().data().property(BUFFER_PROPERTY_NAME); + return qscriptvalue_cast(bufferObject.data()); +} + +bool DataViewPrototype::realOffset(qint32& offset, size_t size) const { + if (offset < 0) { + return false; + } + quint32 viewOffset = thisObject().data().property(BYTE_OFFSET_PROPERTY_NAME).toInt32(); + quint32 viewLength = thisObject().data().property(BYTE_LENGTH_PROPERTY_NAME).toInt32(); + offset += viewOffset; + return (offset + size) <= viewOffset + viewLength; +} + +qint32 DataViewPrototype::getInt8(qint32 byteOffset) { + if (realOffset(byteOffset, sizeof(qint8))) { + QDataStream stream(*thisArrayBuffer()); + stream.skipRawData(byteOffset); + + qint8 result; + stream >> result; + return result; + } + thisObject().engine()->evaluate("throw \"RangeError: byteOffset out of range\""); + return 0; +} + +quint32 DataViewPrototype::getUint8(qint32 byteOffset) { + if (realOffset(byteOffset, sizeof(quint8))) { + QDataStream stream(*thisArrayBuffer()); + stream.skipRawData(byteOffset); + + quint8 result; + stream >> result; + return result; + } + thisObject().engine()->evaluate("throw \"RangeError: byteOffset out of range\""); + return 0; +} + +qint32 DataViewPrototype::getInt16(qint32 byteOffset, bool littleEndian) { + if (realOffset(byteOffset, sizeof(qint16))) { + QDataStream stream(*thisArrayBuffer()); + stream.skipRawData(byteOffset); + stream.setByteOrder((littleEndian) ? QDataStream::LittleEndian : QDataStream::BigEndian); + + qint16 result; + stream >> result; + return result; + } + thisObject().engine()->evaluate("throw \"RangeError: byteOffset out of range\""); + return 0; +} + +quint32 DataViewPrototype::getUint16(qint32 byteOffset, bool littleEndian) { + if (realOffset(byteOffset, sizeof(quint16))) { + QDataStream stream(*thisArrayBuffer()); + stream.skipRawData(byteOffset); + stream.setByteOrder((littleEndian) ? QDataStream::LittleEndian : QDataStream::BigEndian); + + quint16 result; + stream >> result; + return result; + } + thisObject().engine()->evaluate("throw \"RangeError: byteOffset out of range\""); + return 0; +} + +qint32 DataViewPrototype::getInt32(qint32 byteOffset, bool littleEndian) { + if (realOffset(byteOffset, sizeof(qint32))) { + QDataStream stream(*thisArrayBuffer()); + stream.skipRawData(byteOffset); + stream.setByteOrder((littleEndian) ? QDataStream::LittleEndian : QDataStream::BigEndian); + + qint32 result; + stream >> result; + return result; + } + thisObject().engine()->evaluate("throw \"RangeError: byteOffset out of range\""); + return 0; +} + +quint32 DataViewPrototype::getUint32(qint32 byteOffset, bool littleEndian) { + if (realOffset(byteOffset, sizeof(quint32))) { + QDataStream stream(*thisArrayBuffer()); + stream.skipRawData(byteOffset); + stream.setByteOrder((littleEndian) ? QDataStream::LittleEndian : QDataStream::BigEndian); + + quint32 result; + stream >> result; + return result; + } + thisObject().engine()->evaluate("throw \"RangeError: byteOffset out of range\""); + return 0; +} + +QScriptValue DataViewPrototype::getFloat32(qint32 byteOffset, bool littleEndian) { + if (realOffset(byteOffset, sizeof(float))) { + QDataStream stream(*thisArrayBuffer()); + stream.skipRawData(byteOffset); + stream.setByteOrder((littleEndian) ? QDataStream::LittleEndian : QDataStream::BigEndian); + stream.setFloatingPointPrecision(QDataStream::SinglePrecision); + + float result; + stream >> result; + if (isNaN(result)) { + return QScriptValue(); + } + + return QScriptValue(result); + } + thisObject().engine()->evaluate("throw \"RangeError: byteOffset out of range\""); + return QScriptValue(); +} + +QScriptValue DataViewPrototype::getFloat64(qint32 byteOffset, bool littleEndian) { + if (realOffset(byteOffset, sizeof(double))) { + QDataStream stream(*thisArrayBuffer()); + stream.skipRawData(byteOffset); + stream.setByteOrder((littleEndian) ? QDataStream::LittleEndian : QDataStream::BigEndian); + stream.setFloatingPointPrecision(QDataStream::DoublePrecision); + + double result; + stream >> result; + if (isNaN(result)) { + return QScriptValue(); + } + + return result; + } + thisObject().engine()->evaluate("throw \"RangeError: byteOffset out of range\""); + return QScriptValue(); +} + +void DataViewPrototype::setInt8(qint32 byteOffset, qint32 value) { + if (realOffset(byteOffset, sizeof(qint8))) { + QDataStream stream(thisArrayBuffer(), QIODevice::ReadWrite); + stream.skipRawData(byteOffset); + + stream << (qint8)value; + } else { + thisObject().engine()->evaluate("throw \"RangeError: byteOffset out of range\""); + } +} + +void DataViewPrototype::setUint8(qint32 byteOffset, quint32 value) { + if (realOffset(byteOffset, sizeof(quint8))) { + QDataStream stream(thisArrayBuffer(), QIODevice::ReadWrite); + stream.skipRawData(byteOffset); + + stream << (quint8)value; + } else { + thisObject().engine()->evaluate("throw \"RangeError: byteOffset out of range\""); + } +} + +void DataViewPrototype::setInt16(qint32 byteOffset, qint32 value, bool littleEndian) { + if (realOffset(byteOffset, sizeof(qint16))) { + QDataStream stream(thisArrayBuffer(), QIODevice::ReadWrite); + stream.skipRawData(byteOffset); + stream.setByteOrder((littleEndian) ? QDataStream::LittleEndian : QDataStream::BigEndian); + + stream << (qint16)value; + } else { + thisObject().engine()->evaluate("throw \"RangeError: byteOffset out of range\""); + } +} + +void DataViewPrototype::setUint16(qint32 byteOffset, quint32 value, bool littleEndian) { + if (realOffset(byteOffset, sizeof(quint16))) { + QDataStream stream(thisArrayBuffer(), QIODevice::ReadWrite); + stream.skipRawData(byteOffset); + stream.setByteOrder((littleEndian) ? QDataStream::LittleEndian : QDataStream::BigEndian); + + stream << (quint16)value; + } else { + thisObject().engine()->evaluate("throw \"RangeError: byteOffset out of range\""); + } +} + +void DataViewPrototype::setInt32(qint32 byteOffset, qint32 value, bool littleEndian) { + if (realOffset(byteOffset, sizeof(qint32))) { + QDataStream stream(thisArrayBuffer(), QIODevice::ReadWrite); + stream.skipRawData(byteOffset); + stream.setByteOrder((littleEndian) ? QDataStream::LittleEndian : QDataStream::BigEndian); + + stream << (qint32)value; + } else { + thisObject().engine()->evaluate("throw \"RangeError: byteOffset out of range\""); + } +} + +void DataViewPrototype::setUint32(qint32 byteOffset, quint32 value, bool littleEndian) { + if (realOffset(byteOffset, sizeof(quint32))) { + QDataStream stream(thisArrayBuffer(), QIODevice::ReadWrite); + stream.skipRawData(byteOffset); + stream.setByteOrder((littleEndian) ? QDataStream::LittleEndian : QDataStream::BigEndian); + + stream << (quint32)value; + } else { + thisObject().engine()->evaluate("throw \"RangeError: byteOffset out of range\""); + } +} + +void DataViewPrototype::setFloat32(qint32 byteOffset, float value, bool littleEndian) { + if (realOffset(byteOffset, sizeof(float))) { + QDataStream stream(thisArrayBuffer(), QIODevice::ReadWrite); + stream.skipRawData(byteOffset); + stream.setByteOrder((littleEndian) ? QDataStream::LittleEndian : QDataStream::BigEndian); + stream.setFloatingPointPrecision(QDataStream::SinglePrecision); + + stream << value; + } else { + thisObject().engine()->evaluate("throw \"RangeError: byteOffset out of range\""); + } +} + +void DataViewPrototype::setFloat64(qint32 byteOffset, double value, bool littleEndian) { + if (realOffset(byteOffset, sizeof(double))) { + QDataStream stream(thisArrayBuffer(), QIODevice::ReadWrite); + stream.skipRawData(byteOffset); + stream.setByteOrder((littleEndian) ? QDataStream::LittleEndian : QDataStream::BigEndian); + stream.setFloatingPointPrecision(QDataStream::DoublePrecision); + + stream << value; + } else { + thisObject().engine()->evaluate("throw \"RangeError: byteOffset out of range\""); + } +} + + diff --git a/libraries/script-engine/src/DataViewPrototype.h b/libraries/script-engine/src/DataViewPrototype.h new file mode 100644 index 0000000000..da0261dad4 --- /dev/null +++ b/libraries/script-engine/src/DataViewPrototype.h @@ -0,0 +1,68 @@ +// +// DataViewPrototype.h +// +// +// Created by Clement on 7/8/14. +// Copyright 2014 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#ifndef hifi_DataViewPrototype_h +#define hifi_DataViewPrototype_h + +#include +#include + +class DataViewPrototype : public QObject, public QScriptable { + Q_OBJECT +public: + DataViewPrototype(QObject* parent = NULL); + +public slots: + // Gets the value of the given type at the specified byte offset + // from the start of the view. There is no alignment constraint; + // multi-byte values may be fetched from any offset. + // + // For multi-byte values, the optional littleEndian argument + // indicates whether a big-endian or little-endian value should be + // read. If false or undefined, a big-endian value is read. + // + // These methods raise an exception if they would read + // beyond the end of the view. + qint32 getInt8(qint32 byteOffset); + quint32 getUint8(qint32 byteOffset); + qint32 getInt16(qint32 byteOffset, bool littleEndian = false); + quint32 getUint16(qint32 byteOffset, bool littleEndian = false); + qint32 getInt32(qint32 byteOffset, bool littleEndian = false); + quint32 getUint32(qint32 byteOffset, bool littleEndian = false); + QScriptValue getFloat32(qint32 byteOffset, bool littleEndian = false); + QScriptValue getFloat64(qint32 byteOffset, bool littleEndian = false); + + // Stores a value of the given type at the specified byte offset + // from the start of the view. There is no alignment constraint; + // multi-byte values may be stored at any offset. + // + // For multi-byte values, the optional littleEndian argument + // indicates whether the value should be stored in big-endian or + // little-endian byte order. If false or undefined, the value is + // stored in big-endian byte order. + // + // These methods raise an exception if they would write + // beyond the end of the view. + void setInt8(qint32 byteOffset, qint32 value); + void setUint8(qint32 byteOffset, quint32 value); + void setInt16(qint32 byteOffset, qint32 value, bool littleEndian = false); + void setUint16(qint32 byteOffset, quint32 value, bool littleEndian = false); + void setInt32(qint32 byteOffset, qint32 value, bool littleEndian = false); + void setUint32(qint32 byteOffset, quint32 value, bool littleEndian = false); + void setFloat32(qint32 byteOffset, float value, bool littleEndian = false); + void setFloat64(qint32 byteOffset, double value, bool littleEndian = false); + +private: + QByteArray* thisArrayBuffer() const; + bool realOffset(qint32& offset, size_t size) const; +}; + +#endif // hifi_DataViewPrototype_h \ No newline at end of file diff --git a/libraries/script-engine/src/ScriptEngine.cpp b/libraries/script-engine/src/ScriptEngine.cpp index f5f15331ac..cd7c2670ec 100644 --- a/libraries/script-engine/src/ScriptEngine.cpp +++ b/libraries/script-engine/src/ScriptEngine.cpp @@ -32,10 +32,13 @@ #include #include "AnimationObject.h" +#include "ArrayBufferViewClass.h" +#include "DataViewClass.h" #include "MenuItemProperties.h" #include "MIDIEvent.h" #include "LocalVoxels.h" #include "ScriptEngine.h" +#include "TypedArrays.h" #include "XMLHttpRequestClass.h" VoxelsScriptingInterface ScriptEngine::_voxelsScriptingInterface; @@ -90,7 +93,8 @@ ScriptEngine::ScriptEngine(const QString& scriptContents, const QString& fileNam _quatLibrary(), _vec3Library(), _uuidLibrary(), - _animationCache(this) + _animationCache(this), + _arrayBufferClass(new ArrayBufferClass(this)) { } @@ -115,13 +119,14 @@ ScriptEngine::ScriptEngine(const QUrl& scriptURL, _quatLibrary(), _vec3Library(), _uuidLibrary(), - _animationCache(this) + _animationCache(this), + _arrayBufferClass(new ArrayBufferClass(this)) { QString scriptURLString = scriptURL.toString(); _fileNameString = scriptURLString; QUrl url(scriptURL); - + // if the scheme length is one or lower, maybe they typed in a file, let's try const int WINDOWS_DRIVE_LETTER_SIZE = 1; if (url.scheme().size() <= WINDOWS_DRIVE_LETTER_SIZE) { @@ -210,7 +215,7 @@ void ScriptEngine::init() { if (_isInitialized) { return; // only initialize once } - + _isInitialized = true; _voxelsScriptingInterface.init(); diff --git a/libraries/script-engine/src/ScriptEngine.h b/libraries/script-engine/src/ScriptEngine.h index 5b01b8124a..0eda74914f 100644 --- a/libraries/script-engine/src/ScriptEngine.h +++ b/libraries/script-engine/src/ScriptEngine.h @@ -25,6 +25,7 @@ #include #include "AbstractControllerScriptingInterface.h" +#include "ArrayBufferClass.h" #include "Quat.h" #include "ScriptUUID.h" #include "Vec3.h" @@ -56,6 +57,9 @@ public: /// Access the ModelsScriptingInterface in order to initialize it with a custom packet sender and jurisdiction listener static ModelsScriptingInterface* getModelsScriptingInterface() { return &_modelsScriptingInterface; } + QScriptEngine* getEngine() { return &_engine; } + ArrayBufferClass* getArrayBufferClass() { return _arrayBufferClass; } + /// sets the script contents, will return false if failed, will fail if script is already running bool setScriptContents(const QString& scriptContents, const QString& fileNameString = QString("")); @@ -147,6 +151,8 @@ private: Vec3 _vec3Library; ScriptUUID _uuidLibrary; AnimationCache _animationCache; + + ArrayBufferClass* _arrayBufferClass; QHash _outgoingScriptAudioSequenceNumbers; }; diff --git a/libraries/script-engine/src/TypedArrayPrototype.cpp b/libraries/script-engine/src/TypedArrayPrototype.cpp new file mode 100644 index 0000000000..37274dd080 --- /dev/null +++ b/libraries/script-engine/src/TypedArrayPrototype.cpp @@ -0,0 +1,106 @@ +// +// TypedArrayPrototype.cpp +// +// +// Created by Clement on 7/14/14. +// Copyright 2014 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#include "TypedArrays.h" + +#include "TypedArrayPrototype.h" + +Q_DECLARE_METATYPE(QByteArray*) + +TypedArrayPrototype::TypedArrayPrototype(QObject* parent) : QObject(parent) { +} + +QByteArray* TypedArrayPrototype::thisArrayBuffer() const { + QScriptValue bufferObject = thisObject().data().property(BUFFER_PROPERTY_NAME); + return qscriptvalue_cast(bufferObject.data()); +} + +void TypedArrayPrototype::set(QScriptValue array, qint32 offset) { + TypedArray* typedArray = static_cast(parent()); + if (array.isArray() || typedArray) { + if (offset < 0) { + engine()->evaluate("throw \"ArgumentError: negative offset\""); + } + quint32 length = array.property("length").toInt32(); + if (offset + length > thisObject().data().property(typedArray->_lengthName).toInt32()) { + engine()->evaluate("throw \"ArgumentError: array does not fit\""); + return; + } + for (int i = 0; i < length; ++i) { + thisObject().setProperty(QString::number(offset + i), array.property(QString::number(i))); + } + } else { + engine()->evaluate("throw \"ArgumentError: not an array\""); + } +} + +QScriptValue TypedArrayPrototype::subarray(qint32 begin) { + TypedArray* typedArray = static_cast(parent()); + QScriptValue arrayBuffer = thisObject().data().property(typedArray->_bufferName); + qint32 byteOffset = thisObject().data().property(typedArray->_byteOffsetName).toInt32(); + qint32 length = thisObject().data().property(typedArray->_lengthName).toInt32(); + qint32 bytesPerElement = typedArray->_bytesPerElement; + + // if indices < 0 then they start from the end of the array + begin = (begin < 0) ? length + begin : begin; + + // here we clamp the indices to fit the array + begin = glm::clamp(begin, 0, (length - 1)); + + byteOffset += begin * bytesPerElement; + return typedArray->newInstance(arrayBuffer, byteOffset, length - begin); +} + +QScriptValue TypedArrayPrototype::subarray(qint32 begin, qint32 end) { + TypedArray* typedArray = static_cast(parent()); + QScriptValue arrayBuffer = thisObject().data().property(typedArray->_bufferName); + qint32 byteOffset = thisObject().data().property(typedArray->_byteOffsetName).toInt32(); + qint32 length = thisObject().data().property(typedArray->_lengthName).toInt32(); + qint32 bytesPerElement = typedArray->_bytesPerElement; + + // if indices < 0 then they start from the end of the array + begin = (begin < 0) ? length + begin : begin; + end = (end < 0) ? length + end : end; + + // here we clamp the indices to fit the array + begin = glm::clamp(begin, 0, (length - 1)); + end = glm::clamp(end, 0, (length - 1)); + + byteOffset += begin * bytesPerElement; + length = (end - begin > 0) ? end - begin : 0; + return typedArray->newInstance(arrayBuffer, byteOffset, length); +} + +QScriptValue TypedArrayPrototype::get(quint32 index) { + TypedArray* typedArray = static_cast(parent()); + QScriptString name = engine()->toStringHandle(QString::number(index)); + uint id; + QScriptClass::QueryFlags flags = typedArray->queryProperty(thisObject(), + name, + QScriptClass::HandlesReadAccess, &id); + if (QScriptClass::HandlesReadAccess & flags) { + return typedArray->property(thisObject(), name, id); + } + return QScriptValue(); +} + +void TypedArrayPrototype::set(quint32 index, QScriptValue& value) { + TypedArray* typedArray = static_cast(parent()); + QScriptValue object = thisObject(); + QScriptString name = engine()->toStringHandle(QString::number(index)); + uint id; + QScriptClass::QueryFlags flags = typedArray->queryProperty(object, + name, + QScriptClass::HandlesWriteAccess, &id); + if (QScriptClass::HandlesWriteAccess & flags) { + typedArray->setProperty(object, name, id, value); + } +} diff --git a/libraries/script-engine/src/TypedArrayPrototype.h b/libraries/script-engine/src/TypedArrayPrototype.h new file mode 100644 index 0000000000..86d578ace0 --- /dev/null +++ b/libraries/script-engine/src/TypedArrayPrototype.h @@ -0,0 +1,33 @@ +// +// TypedArrayPrototype.h +// +// +// Created by Clement on 7/14/14. +// Copyright 2014 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#ifndef hifi_TypedArrayPrototype_h +#define hifi_TypedArrayPrototype_h + +#include "ArrayBufferViewClass.h" + +class TypedArrayPrototype : public QObject, public QScriptable { + Q_OBJECT +public: + TypedArrayPrototype(QObject* parent = NULL); + +public slots: + void set(QScriptValue array, qint32 offset = 0); + QScriptValue subarray(qint32 begin); + QScriptValue subarray(qint32 begin, qint32 end); + + QScriptValue get(quint32 index); + void set(quint32 index, QScriptValue& value); +private: + QByteArray* thisArrayBuffer() const; +}; + +#endif // hifi_TypedArrayPrototype_h \ No newline at end of file diff --git a/libraries/script-engine/src/TypedArrays.cpp b/libraries/script-engine/src/TypedArrays.cpp new file mode 100644 index 0000000000..751d1385e5 --- /dev/null +++ b/libraries/script-engine/src/TypedArrays.cpp @@ -0,0 +1,418 @@ +// +// TypedArrays.cpp +// +// +// Created by Clement on 7/9/14. +// Copyright 2014 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#include + +#include "ScriptEngine.h" +#include "TypedArrayPrototype.h" + +#include "TypedArrays.h" + +Q_DECLARE_METATYPE(QByteArray*) + +TypedArray::TypedArray(ScriptEngine* scriptEngine, QString name) : ArrayBufferViewClass(scriptEngine) { + _bytesPerElementName = engine()->toStringHandle(BYTES_PER_ELEMENT_PROPERTY_NAME.toLatin1()); + _lengthName = engine()->toStringHandle(LENGTH_PROPERTY_NAME.toLatin1()); + _name = engine()->toStringHandle(name.toLatin1()); + + QScriptValue global = engine()->globalObject(); + + // build prototype + _proto = engine()->newQObject(new TypedArrayPrototype(this), + QScriptEngine::QtOwnership, + QScriptEngine::SkipMethodsInEnumeration | + QScriptEngine::ExcludeSuperClassMethods | + QScriptEngine::ExcludeSuperClassProperties); + _proto.setPrototype(global.property("Object").property("prototype")); + + // Register constructor + _ctor = engine()->newFunction(construct, _proto); + _ctor.setData(engine()->toScriptValue(this)); + engine()->globalObject().setProperty(_name, _ctor); +} + +QScriptValue TypedArray::newInstance(quint32 length) { + ArrayBufferClass* array = getScriptEngine()->getArrayBufferClass(); + QScriptValue buffer = array->newInstance(length * _bytesPerElement); + return newInstance(buffer, 0, length); +} + +QScriptValue TypedArray::newInstance(QScriptValue array) { + const QString ARRAY_LENGTH_HANDLE = "length"; + if (array.property(ARRAY_LENGTH_HANDLE).isValid()) { + quint32 length = array.property(ARRAY_LENGTH_HANDLE).toInt32(); + QScriptValue newArray = newInstance(length); + for (int i = 0; i < length; ++i) { + QScriptValue value = array.property(QString::number(i)); + setProperty(newArray, engine()->toStringHandle(QString::number(i)), + i * _bytesPerElement, (value.isNumber()) ? value : QScriptValue(0)); + } + return newArray; + } + engine()->evaluate("throw \"ArgumentError: not an array\""); + return QScriptValue(); +} + +QScriptValue TypedArray::newInstance(QScriptValue buffer, quint32 byteOffset, quint32 length) { + QScriptValue data = engine()->newObject(); + data.setProperty(_bufferName, buffer); + data.setProperty(_byteOffsetName, byteOffset); + data.setProperty(_byteLengthName, length * _bytesPerElement); + data.setProperty(_lengthName, length); + + return engine()->newObject(this, data); +} + +QScriptValue TypedArray::construct(QScriptContext* context, QScriptEngine* engine) { + TypedArray* cls = qscriptvalue_cast(context->callee().data()); + if (!cls) { + return QScriptValue(); + } + if (context->argumentCount() == 0) { + return cls->newInstance(0); + } + + QScriptValue newObject; + QScriptValue bufferArg = context->argument(0); + QByteArray* arrayBuffer = qscriptvalue_cast(bufferArg.data()); + + // parse arguments + if (arrayBuffer) { + if (context->argumentCount() == 1) { + // Case for entire ArrayBuffer + newObject = cls->newInstance(bufferArg, 0, arrayBuffer->size()); + } else { + QScriptValue byteOffsetArg = context->argument(1); + if (!byteOffsetArg.isNumber()) { + engine->evaluate("throw \"ArgumentError: 2nd arg is not a number\""); + return QScriptValue(); + } + if (byteOffsetArg.toInt32() < 0 || byteOffsetArg.toInt32() > arrayBuffer->size()) { + engine->evaluate("throw \"RangeError: byteOffset out of range\""); + return QScriptValue(); + } + if (byteOffsetArg.toInt32() % cls->_bytesPerElement != 0) { + engine->evaluate("throw \"RangeError: byteOffset not a multiple of BYTES_PER_ELEMENT\""); + } + quint32 byteOffset = byteOffsetArg.toInt32(); + + if (context->argumentCount() == 2) { + // case for end of ArrayBuffer + if ((arrayBuffer->size() - byteOffset) % cls->_bytesPerElement != 0) { + engine->evaluate("throw \"RangeError: byteLength - byteOffset not a multiple of BYTES_PER_ELEMENT\""); + } + quint32 length = (arrayBuffer->size() - byteOffset) / cls->_bytesPerElement; + newObject = cls->newInstance(bufferArg, byteOffset, length); + } else { + + QScriptValue lengthArg = (context->argumentCount() > 2) ? context->argument(2) : QScriptValue(); + if (!lengthArg.isNumber()) { + engine->evaluate("throw \"ArgumentError: 3nd arg is not a number\""); + return QScriptValue(); + } + if (lengthArg.toInt32() < 0 || + byteOffsetArg.toInt32() + lengthArg.toInt32() * cls->_bytesPerElement > arrayBuffer->size()) { + engine->evaluate("throw \"RangeError: byteLength out of range\""); + return QScriptValue(); + } + quint32 length = lengthArg.toInt32(); + + // case for well-defined range + newObject = cls->newInstance(bufferArg, byteOffset, length); + } + } + } else if (context->argument(0).isNumber()) { + // case for new ArrayBuffer + newObject = cls->newInstance(context->argument(0).toInt32()); + } else { + newObject = cls->newInstance(bufferArg); + } + + if (context->isCalledAsConstructor()) { + // if called with the new keyword, replace this object + context->setThisObject(newObject); + return engine->undefinedValue(); + } + + return newObject; +} + +QScriptClass::QueryFlags TypedArray::queryProperty(const QScriptValue& object, + const QScriptString& name, + QueryFlags flags, uint* id) { + if (name == _bytesPerElementName || name == _lengthName) { + return flags &= HandlesReadAccess; // Only keep read access flags + } + + quint32 byteOffset = object.data().property(_byteOffsetName).toInt32(); + quint32 length = object.data().property(_lengthName).toInt32(); + bool ok = false; + int pos = name.toArrayIndex(&ok); + + // Check that name is a valid index and arrayBuffer exists + if (ok && pos >= 0 && pos < length) { + *id = byteOffset + pos * _bytesPerElement; // save pos to avoid recomputation + return HandlesReadAccess | HandlesWriteAccess; // Read/Write access + } + + return ArrayBufferViewClass::queryProperty(object, name, flags, id); +} + +QScriptValue TypedArray::property(const QScriptValue& object, + const QScriptString& name, uint id) { + if (name == _bytesPerElementName) { + return QScriptValue(_bytesPerElement); + } + if (name == _lengthName) { + return object.data().property(_lengthName); + } + return ArrayBufferViewClass::property(object, name, id); +} + +QScriptValue::PropertyFlags TypedArray::propertyFlags(const QScriptValue& object, + const QScriptString& name, uint id) { + return QScriptValue::Undeletable; +} + +QString TypedArray::name() const { + return _name.toString(); +} + +QScriptValue TypedArray::prototype() const { + return _proto; +} + +void TypedArray::setBytesPerElement(quint32 bytesPerElement) { + _bytesPerElement = bytesPerElement; + _ctor.setProperty(_bytesPerElementName, _bytesPerElement); +} + +// templated helper functions +// don't work for floats as they require single precision settings +template +QScriptValue propertyHelper(const QByteArray* arrayBuffer, const QScriptString& name, uint id) { + bool ok = false; + name.toArrayIndex(&ok); + + if (ok && arrayBuffer) { + QDataStream stream(*arrayBuffer); + stream.skipRawData(id); + + T result; + stream >> result; + return result; + } + return QScriptValue(); +} + +template +void setPropertyHelper(QByteArray* arrayBuffer, const QScriptString& name, uint id, const QScriptValue& value) { + if (arrayBuffer && value.isNumber()) { + QDataStream stream(arrayBuffer, QIODevice::ReadWrite); + stream.skipRawData(id); + + stream << (T)value.toNumber(); + } +} + +Int8ArrayClass::Int8ArrayClass(ScriptEngine* scriptEngine) : TypedArray(scriptEngine, INT_8_ARRAY_CLASS_NAME) { + setBytesPerElement(sizeof(qint8)); +} + +QScriptValue Int8ArrayClass::property(const QScriptValue& object, const QScriptString& name, uint id) { + QByteArray* arrayBuffer = qscriptvalue_cast(object.data().property(_bufferName).data()); + QScriptValue result = propertyHelper(arrayBuffer, name, id); + return (result.isValid()) ? result : TypedArray::property(object, name, id); +} + +void Int8ArrayClass::setProperty(QScriptValue &object, const QScriptString &name, + uint id, const QScriptValue& value) { + QByteArray* ba = qscriptvalue_cast(object.data().property(_bufferName).data()); + setPropertyHelper(ba, name, id, value); +} + +Uint8ArrayClass::Uint8ArrayClass(ScriptEngine* scriptEngine) : TypedArray(scriptEngine, UINT_8_ARRAY_CLASS_NAME) { + setBytesPerElement(sizeof(quint8)); +} + +QScriptValue Uint8ArrayClass::property(const QScriptValue& object, const QScriptString& name, uint id) { + QByteArray* arrayBuffer = qscriptvalue_cast(object.data().property(_bufferName).data()); + QScriptValue result = propertyHelper(arrayBuffer, name, id); + return (result.isValid()) ? result : TypedArray::property(object, name, id); +} + +void Uint8ArrayClass::setProperty(QScriptValue& object, const QScriptString& name, + uint id, const QScriptValue& value) { + QByteArray* ba = qscriptvalue_cast(object.data().property(_bufferName).data()); + setPropertyHelper(ba, name, id, value); +} + +Uint8ClampedArrayClass::Uint8ClampedArrayClass(ScriptEngine* scriptEngine) : TypedArray(scriptEngine, UINT_8_CLAMPED_ARRAY_CLASS_NAME) { + setBytesPerElement(sizeof(quint8)); +} + +QScriptValue Uint8ClampedArrayClass::property(const QScriptValue& object, const QScriptString& name, uint id) { + QByteArray* arrayBuffer = qscriptvalue_cast(object.data().property(_bufferName).data()); + QScriptValue result = propertyHelper(arrayBuffer, name, id); + return (result.isValid()) ? result : TypedArray::property(object, name, id); +} + +void Uint8ClampedArrayClass::setProperty(QScriptValue& object, const QScriptString& name, + uint id, const QScriptValue& value) { + QByteArray* ba = qscriptvalue_cast(object.data().property(_bufferName).data()); + if (ba && value.isNumber()) { + QDataStream stream(ba, QIODevice::ReadWrite); + stream.skipRawData(id); + if (value.toNumber() > 255) { + stream << (quint8)255; + } else if (value.toNumber() < 0) { + stream << (quint8)0; + } else { + stream << (quint8)glm::clamp(qRound(value.toNumber()), 0, 255); + } + } +} + +Int16ArrayClass::Int16ArrayClass(ScriptEngine* scriptEngine) : TypedArray(scriptEngine, INT_16_ARRAY_CLASS_NAME) { + setBytesPerElement(sizeof(qint16)); +} + +QScriptValue Int16ArrayClass::property(const QScriptValue& object, const QScriptString& name, uint id) { + QByteArray* arrayBuffer = qscriptvalue_cast(object.data().property(_bufferName).data()); + QScriptValue result = propertyHelper(arrayBuffer, name, id); + return (result.isValid()) ? result : TypedArray::property(object, name, id); +} + +void Int16ArrayClass::setProperty(QScriptValue& object, const QScriptString& name, + uint id, const QScriptValue& value) { + QByteArray* ba = qscriptvalue_cast(object.data().property(_bufferName).data()); + setPropertyHelper(ba, name, id, value); +} + +Uint16ArrayClass::Uint16ArrayClass(ScriptEngine* scriptEngine) : TypedArray(scriptEngine, UINT_16_ARRAY_CLASS_NAME) { + setBytesPerElement(sizeof(quint16)); +} + +QScriptValue Uint16ArrayClass::property(const QScriptValue& object, const QScriptString& name, uint id) { + QByteArray* arrayBuffer = qscriptvalue_cast(object.data().property(_bufferName).data()); + QScriptValue result = propertyHelper(arrayBuffer, name, id); + return (result.isValid()) ? result : TypedArray::property(object, name, id); +} + +void Uint16ArrayClass::setProperty(QScriptValue& object, const QScriptString& name, + uint id, const QScriptValue& value) { + QByteArray* ba = qscriptvalue_cast(object.data().property(_bufferName).data()); + setPropertyHelper(ba, name, id, value); +} + +Int32ArrayClass::Int32ArrayClass(ScriptEngine* scriptEngine) : TypedArray(scriptEngine, INT_32_ARRAY_CLASS_NAME) { + setBytesPerElement(sizeof(qint32)); +} + +QScriptValue Int32ArrayClass::property(const QScriptValue& object, const QScriptString& name, uint id) { + QByteArray* arrayBuffer = qscriptvalue_cast(object.data().property(_bufferName).data()); + QScriptValue result = propertyHelper(arrayBuffer, name, id); + return (result.isValid()) ? result : TypedArray::property(object, name, id); +} + +void Int32ArrayClass::setProperty(QScriptValue& object, const QScriptString& name, + uint id, const QScriptValue& value) { + QByteArray* ba = qscriptvalue_cast(object.data().property(_bufferName).data()); + setPropertyHelper(ba, name, id, value); +} + +Uint32ArrayClass::Uint32ArrayClass(ScriptEngine* scriptEngine) : TypedArray(scriptEngine, UINT_32_ARRAY_CLASS_NAME) { + setBytesPerElement(sizeof(quint32)); +} + +QScriptValue Uint32ArrayClass::property(const QScriptValue& object, const QScriptString& name, uint id) { + + QByteArray* arrayBuffer = qscriptvalue_cast(object.data().property(_bufferName).data()); + QScriptValue result = propertyHelper(arrayBuffer, name, id); + return (result.isValid()) ? result : TypedArray::property(object, name, id); +} + +void Uint32ArrayClass::setProperty(QScriptValue& object, const QScriptString& name, + uint id, const QScriptValue& value) { + QByteArray* ba = qscriptvalue_cast(object.data().property(_bufferName).data()); + setPropertyHelper(ba, name, id, value); +} + +Float32ArrayClass::Float32ArrayClass(ScriptEngine* scriptEngine) : TypedArray(scriptEngine, FLOAT_32_ARRAY_CLASS_NAME) { + setBytesPerElement(sizeof(float)); +} + +QScriptValue Float32ArrayClass::property(const QScriptValue& object, const QScriptString& name, uint id) { + QByteArray* arrayBuffer = qscriptvalue_cast(object.data().property(_bufferName).data());bool ok = false; + name.toArrayIndex(&ok); + + if (ok && arrayBuffer) { + QDataStream stream(*arrayBuffer); + stream.skipRawData(id); + stream.setFloatingPointPrecision(QDataStream::SinglePrecision); + + float result; + stream >> result; + if (isNaN(result)) { + return QScriptValue(); + } + return result; + } + return TypedArray::property(object, name, id); +} + +void Float32ArrayClass::setProperty(QScriptValue& object, const QScriptString& name, + uint id, const QScriptValue& value) { + QByteArray* ba = qscriptvalue_cast(object.data().property(_bufferName).data()); + if (ba && value.isNumber()) { + QDataStream stream(ba, QIODevice::ReadWrite); + stream.skipRawData(id); + stream.setFloatingPointPrecision(QDataStream::SinglePrecision); + + stream << (float)value.toNumber(); + } +} + +Float64ArrayClass::Float64ArrayClass(ScriptEngine* scriptEngine) : TypedArray(scriptEngine, FLOAT_64_ARRAY_CLASS_NAME) { + setBytesPerElement(sizeof(double)); +} + +QScriptValue Float64ArrayClass::property(const QScriptValue& object, const QScriptString& name, uint id) { + QByteArray* arrayBuffer = qscriptvalue_cast(object.data().property(_bufferName).data());bool ok = false; + name.toArrayIndex(&ok); + + if (ok && arrayBuffer) { + QDataStream stream(*arrayBuffer); + stream.skipRawData(id); + stream.setFloatingPointPrecision(QDataStream::DoublePrecision); + + double result; + stream >> result; + if (isNaN(result)) { + return QScriptValue(); + } + return result; + } + return TypedArray::property(object, name, id); +} + +void Float64ArrayClass::setProperty(QScriptValue& object, const QScriptString& name, + uint id, const QScriptValue& value) { + QByteArray* ba = qscriptvalue_cast(object.data().property(_bufferName).data()); + if (ba && value.isNumber()) { + QDataStream stream(ba, QIODevice::ReadWrite); + stream.skipRawData(id); + stream.setFloatingPointPrecision(QDataStream::DoublePrecision); + + stream << (double)value.toNumber(); + } +} + diff --git a/libraries/script-engine/src/TypedArrays.h b/libraries/script-engine/src/TypedArrays.h new file mode 100644 index 0000000000..a1a288c3c9 --- /dev/null +++ b/libraries/script-engine/src/TypedArrays.h @@ -0,0 +1,148 @@ +// +// TypedArrays.h +// +// +// Created by Clement on 7/9/14. +// Copyright 2014 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#ifndef hifi_TypedArrays_h +#define hifi_TypedArrays_h + +#include "ArrayBufferViewClass.h" + +static const QString BYTES_PER_ELEMENT_PROPERTY_NAME = "BYTES_PER_ELEMENT"; +static const QString LENGTH_PROPERTY_NAME = "length"; + +static const QString INT_8_ARRAY_CLASS_NAME = "Int8Array"; +static const QString UINT_8_ARRAY_CLASS_NAME = "Uint8Array"; +static const QString UINT_8_CLAMPED_ARRAY_CLASS_NAME = "Uint8ClampedArray"; +static const QString INT_16_ARRAY_CLASS_NAME = "Int16Array"; +static const QString UINT_16_ARRAY_CLASS_NAME = "Uint16Array"; +static const QString INT_32_ARRAY_CLASS_NAME = "Int32Array"; +static const QString UINT_32_ARRAY_CLASS_NAME = "Uint32Array"; +static const QString FLOAT_32_ARRAY_CLASS_NAME = "Float32Array"; +static const QString FLOAT_64_ARRAY_CLASS_NAME = "Float64Array"; + +class TypedArray : public ArrayBufferViewClass { + Q_OBJECT +public: + TypedArray(ScriptEngine* scriptEngine, QString name); + virtual QScriptValue newInstance(quint32 length); + virtual QScriptValue newInstance(QScriptValue array); + virtual QScriptValue newInstance(QScriptValue buffer, quint32 byteOffset, quint32 length); + + virtual QueryFlags queryProperty(const QScriptValue& object, + const QScriptString& name, + QueryFlags flags, uint* id); + virtual QScriptValue property(const QScriptValue& object, + const QScriptString& name, uint id); + virtual void setProperty(QScriptValue& object, const QScriptString& name, uint id, const QScriptValue& value) = 0; + virtual QScriptValue::PropertyFlags propertyFlags(const QScriptValue& object, + const QScriptString& name, uint id); + + QString name() const; + QScriptValue prototype() const; + +protected: + static QScriptValue construct(QScriptContext* context, QScriptEngine* engine); + + void setBytesPerElement(quint32 bytesPerElement); + + QScriptValue _proto; + QScriptValue _ctor; + + QScriptString _name; + QScriptString _bytesPerElementName; + QScriptString _lengthName; + + quint32 _bytesPerElement; + + friend class TypedArrayPrototype; +}; + +class Int8ArrayClass : public TypedArray { + Q_OBJECT +public: + Int8ArrayClass(ScriptEngine* scriptEngine); + + QScriptValue property(const QScriptValue& object, const QScriptString& name, uint id); + void setProperty(QScriptValue& object, const QScriptString& name, uint id, const QScriptValue& value); +}; + +class Uint8ArrayClass : public TypedArray { + Q_OBJECT +public: + Uint8ArrayClass(ScriptEngine* scriptEngine); + + QScriptValue property(const QScriptValue& object, const QScriptString& name, uint id); + void setProperty(QScriptValue& object, const QScriptString& name, uint id, const QScriptValue& value); +}; + +class Uint8ClampedArrayClass : public TypedArray { + Q_OBJECT +public: + Uint8ClampedArrayClass(ScriptEngine* scriptEngine); + + QScriptValue property(const QScriptValue& object, const QScriptString& name, uint id); + void setProperty(QScriptValue& object, const QScriptString& name, uint id, const QScriptValue& value); +}; + +class Int16ArrayClass : public TypedArray { + Q_OBJECT +public: + Int16ArrayClass(ScriptEngine* scriptEngine); + + QScriptValue property(const QScriptValue& object, const QScriptString& name, uint id); + void setProperty(QScriptValue& object, const QScriptString& name, uint id, const QScriptValue& value); +}; + +class Uint16ArrayClass : public TypedArray { + Q_OBJECT +public: + Uint16ArrayClass(ScriptEngine* scriptEngine); + + QScriptValue property(const QScriptValue& object, const QScriptString& name, uint id); + void setProperty(QScriptValue& object, const QScriptString& name, uint id, const QScriptValue& value); +}; + +class Int32ArrayClass : public TypedArray { + Q_OBJECT +public: + Int32ArrayClass(ScriptEngine* scriptEngine); + + QScriptValue property(const QScriptValue& object, const QScriptString& name, uint id); + void setProperty(QScriptValue& object, const QScriptString& name, uint id, const QScriptValue& value); +}; + +class Uint32ArrayClass : public TypedArray { + Q_OBJECT +public: + Uint32ArrayClass(ScriptEngine* scriptEngine); + + QScriptValue property(const QScriptValue& object, const QScriptString& name, uint id); + void setProperty(QScriptValue& object, const QScriptString& name, uint id, const QScriptValue& value); +}; + +class Float32ArrayClass : public TypedArray { + Q_OBJECT +public: + Float32ArrayClass(ScriptEngine* scriptEngine); + + QScriptValue property(const QScriptValue& object, const QScriptString& name, uint id); + void setProperty(QScriptValue& object, const QScriptString& name, uint id, const QScriptValue& value); +}; + +class Float64ArrayClass : public TypedArray { + Q_OBJECT +public: + Float64ArrayClass(ScriptEngine* scriptEngine); + + QScriptValue property(const QScriptValue& object, const QScriptString& name, uint id); + void setProperty(QScriptValue& object, const QScriptString& name, uint id, const QScriptValue& value); +}; + +#endif // hifi_TypedArrays_h \ No newline at end of file diff --git a/libraries/shared/src/SharedUtil.cpp b/libraries/shared/src/SharedUtil.cpp index e4d2e1c835..b5be502ed5 100644 --- a/libraries/shared/src/SharedUtil.cpp +++ b/libraries/shared/src/SharedUtil.cpp @@ -837,3 +837,20 @@ bool isSimilarPosition(const glm::vec3& positionA, const glm::vec3& positionB, f QByteArray createByteArray(const glm::vec3& vector) { return QByteArray::number(vector.x) + ',' + QByteArray::number(vector.y) + ',' + QByteArray::number(vector.z); } + +QString formatUsecTime(float usecs, int prec) { + static const quint64 SECONDS_PER_MINUTE = 60; + static const quint64 USECS_PER_MINUTE = USECS_PER_SECOND * SECONDS_PER_MINUTE; + + QString result; + if (usecs > USECS_PER_MINUTE) { + result = QString::number(usecs / USECS_PER_MINUTE, 'f', prec) + "min"; + } else if (usecs > USECS_PER_SECOND) { + result = QString::number(usecs / USECS_PER_SECOND, 'f', prec) + 's'; + } else if (usecs > USECS_PER_MSEC) { + result = QString::number(usecs / USECS_PER_MSEC, 'f', prec) + "ms"; + } else { + result = QString::number(usecs, 'f', prec) + "us"; + } + return result; +} diff --git a/libraries/shared/src/SharedUtil.h b/libraries/shared/src/SharedUtil.h index e5c2a0afc9..6bb39f7e12 100644 --- a/libraries/shared/src/SharedUtil.h +++ b/libraries/shared/src/SharedUtil.h @@ -189,4 +189,6 @@ bool isNaN(float value); QByteArray createByteArray(const glm::vec3& vector); +QString formatUsecTime(float usecs, int prec = 3); + #endif // hifi_SharedUtil_h diff --git a/libraries/shared/src/StreamUtils.cpp b/libraries/shared/src/StreamUtils.cpp index 5356c45a51..1723068eee 100644 --- a/libraries/shared/src/StreamUtils.cpp +++ b/libraries/shared/src/StreamUtils.cpp @@ -95,3 +95,34 @@ std::ostream& operator<<(std::ostream& s, const CapsuleShape& capsule) { #endif // DEBUG +#ifndef QT_NO_DEBUG_STREAM +#include + +QDebug& operator<<(QDebug& dbg, const glm::vec3& v) { + dbg.nospace() << "{type='glm::vec3'" + ", x=" << v.x << + ", y=" << v.y << + ", z=" << v.z << + "}"; + return dbg; +} + +QDebug& operator<<(QDebug& dbg, const glm::quat& q) { + dbg.nospace() << "{type='glm::quat'" + ", x=" << q.x << + ", y=" << q.y << + ", z=" << q.z << + ", w=" << q.w << + "}"; + return dbg; +} + +QDebug& operator<<(QDebug& dbg, const glm::mat4& m) { + dbg.nospace() << "{type='glm::mat4', ["; + for (int j = 0; j < 4; ++j) { + dbg << ' ' << m[0][j] << ' ' << m[1][j] << ' ' << m[2][j] << ' ' << m[3][j] << ';'; + } + return dbg << " ]}"; +} + +#endif // QT_NO_DEBUG_STREAM diff --git a/libraries/shared/src/StreamUtils.h b/libraries/shared/src/StreamUtils.h index 2a42a3ea7b..6d00c0e354 100644 --- a/libraries/shared/src/StreamUtils.h +++ b/libraries/shared/src/StreamUtils.h @@ -38,13 +38,20 @@ QDataStream& operator>>(QDataStream& in, glm::quat& quaternion); // less common utils can be enabled with DEBUG #ifdef DEBUG -#include "CollisionInfo.h" -#include "SphereShape.h" -#include "CapsuleShape.h" +class CollisionInfo; +class SphereShape; +class CapsuleShape; std::ostream& operator<<(std::ostream& s, const CollisionInfo& c); std::ostream& operator<<(std::ostream& s, const SphereShape& shape); std::ostream& operator<<(std::ostream& s, const CapsuleShape& capsule); #endif // DEBUG +#ifndef QT_NO_DEBUG_STREAM +class QDebug; +// Add support for writing these to qDebug(). +QDebug& operator<<(QDebug& s, const glm::vec3& v); +QDebug& operator<<(QDebug& s, const glm::quat& q); +QDebug& operator<<(QDebug& s, const glm::mat4& m); +#endif // QT_NO_DEBUG_STREAM #endif // hifi_StreamUtils_h diff --git a/tests/physics/src/PhysicsTestUtil.cpp b/tests/physics/src/PhysicsTestUtil.cpp index f176d5e637..a11d4f2add 100644 --- a/tests/physics/src/PhysicsTestUtil.cpp +++ b/tests/physics/src/PhysicsTestUtil.cpp @@ -12,25 +12,7 @@ #include #include "PhysicsTestUtil.h" - -std::ostream& operator<<(std::ostream& s, const glm::vec3& v) { - s << "<" << v.x << "," << v.y << "," << v.z << ">"; - return s; -} - -std::ostream& operator<<(std::ostream& s, const glm::quat& q) { - s << "<" << q.x << "," << q.y << "," << q.z << "," << q.w << ">"; - return s; -} - -std::ostream& operator<<(std::ostream& s, const glm::mat4& m) { - s << "["; - for (int j = 0; j < 4; ++j) { - s << " " << m[0][j] << " " << m[1][j] << " " << m[2][j] << " " << m[3][j] << ";"; - } - s << " ]"; - return s; -} +#include "StreamUtils.h" std::ostream& operator<<(std::ostream& s, const CollisionInfo& c) { s << "[penetration=" << c._penetration diff --git a/tests/physics/src/PhysicsTestUtil.h b/tests/physics/src/PhysicsTestUtil.h index 998a18ff5d..c93545dd76 100644 --- a/tests/physics/src/PhysicsTestUtil.h +++ b/tests/physics/src/PhysicsTestUtil.h @@ -21,9 +21,6 @@ const glm::vec3 xAxis(1.f, 0.f, 0.f); const glm::vec3 yAxis(0.f, 1.f, 0.f); const glm::vec3 zAxis(0.f, 0.f, 1.f); -std::ostream& operator<<(std::ostream& s, const glm::vec3& v); -std::ostream& operator<<(std::ostream& s, const glm::quat& q); -std::ostream& operator<<(std::ostream& s, const glm::mat4& m); std::ostream& operator<<(std::ostream& s, const CollisionInfo& c); #endif // hifi_PhysicsTestUtil_h