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/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/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