418 lines · 17.2 KB · C++
RawDownload
////  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 <glm/glm.hpp> #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 (quint32 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<TypedArray*>(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<QByteArray*>(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() * (qint32)(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;    quint32 pos = name.toArrayIndex(&ok);        // Check that name is a valid index and arrayBuffer exists    if (ok && 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 settingstemplate<class T>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<class T>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<QByteArray*>(object.data().property(_bufferName).data());    QScriptValue result = propertyHelper<qint8>(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<QByteArray*>(object.data().property(_bufferName).data());    setPropertyHelper<qint8>(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<QByteArray*>(object.data().property(_bufferName).data());    QScriptValue result = propertyHelper<quint8>(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<QByteArray*>(object.data().property(_bufferName).data());    setPropertyHelper<quint8>(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<QByteArray*>(object.data().property(_bufferName).data());    QScriptValue result = propertyHelper<quint8>(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<QByteArray*>(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<QByteArray*>(object.data().property(_bufferName).data());    QScriptValue result = propertyHelper<qint16>(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<QByteArray*>(object.data().property(_bufferName).data());    setPropertyHelper<qint16>(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<QByteArray*>(object.data().property(_bufferName).data());    QScriptValue result = propertyHelper<quint16>(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<QByteArray*>(object.data().property(_bufferName).data());    setPropertyHelper<quint16>(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<QByteArray*>(object.data().property(_bufferName).data());    QScriptValue result = propertyHelper<qint32>(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<QByteArray*>(object.data().property(_bufferName).data());    setPropertyHelper<qint32>(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<QByteArray*>(object.data().property(_bufferName).data());    QScriptValue result = propertyHelper<quint32>(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<QByteArray*>(object.data().property(_bufferName).data());    setPropertyHelper<quint32>(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<QByteArray*>(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<QByteArray*>(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<QByteArray*>(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<QByteArray*>(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();    }}