Skip to content

Commit

Permalink
Implement data manipulation methods for dataview
Browse files Browse the repository at this point in the history
This change implements additional methods for manipulating data in the
dataview object. This change includes the following things:
  - Get/Set${type} methods to manipulate data
  - Tests for the methods

PR-URL: nodejs/node-addon-api#218
Reviewed-By: Michael Dawson <michael_dawson@ca.ibm.com>
  • Loading branch information
kevindavies8 committed Mar 1, 2018
1 parent fb485e8 commit 44f5c6f
Show file tree
Hide file tree
Showing 7 changed files with 336 additions and 15 deletions.
111 changes: 101 additions & 10 deletions napi-inl.h
Original file line number Diff line number Diff line change
Expand Up @@ -1211,6 +1211,14 @@ inline DataView::DataView() : Object() {
}

inline DataView::DataView(napi_env env, napi_value value) : Object(env, value) {
napi_status status = napi_get_dataview_info(
_env,
_value /* dataView */,
&_length /* byteLength */,
&_data /* data */,
nullptr /* arrayBuffer */,
nullptr /* byteOffset */);
NAPI_THROW_IF_FAILED(_env, status);
}

inline Napi::ArrayBuffer DataView::ArrayBuffer() const {
Expand Down Expand Up @@ -1240,16 +1248,99 @@ inline size_t DataView::ByteOffset() const {
}

inline size_t DataView::ByteLength() const {
size_t byteLength;
napi_status status = napi_get_dataview_info(
_env,
_value /* dataView */,
&byteLength /* byteLength */,
nullptr /* data */,
nullptr /* arrayBuffer */,
nullptr /* byteOffset */);
NAPI_THROW_IF_FAILED(_env, status, 0);
return byteLength;
return _length;
}

inline void* DataView::Data() const {
return _data;
}

inline float DataView::GetFloat32(size_t byteOffset) const {
return ReadData<float>(byteOffset);
}

inline double DataView::GetFloat64(size_t byteOffset) const {
return ReadData<double>(byteOffset);
}

inline int8_t DataView::GetInt8(size_t byteOffset) const {
return ReadData<int8_t>(byteOffset);
}

inline int16_t DataView::GetInt16(size_t byteOffset) const {
return ReadData<int16_t>(byteOffset);
}

inline int32_t DataView::GetInt32(size_t byteOffset) const {
return ReadData<int32_t>(byteOffset);
}

inline uint8_t DataView::GetUint8(size_t byteOffset) const {
return ReadData<uint8_t>(byteOffset);
}

inline uint16_t DataView::GetUint16(size_t byteOffset) const {
return ReadData<uint16_t>(byteOffset);
}

inline uint32_t DataView::GetUint32(size_t byteOffset) const {
return ReadData<uint32_t>(byteOffset);
}

inline void DataView::SetFloat32(size_t byteOffset, float value) const {
WriteData<float>(byteOffset, value);
}

inline void DataView::SetFloat64(size_t byteOffset, double value) const {
WriteData<double>(byteOffset, value);
}

inline void DataView::SetInt8(size_t byteOffset, int8_t value) const {
WriteData<int8_t>(byteOffset, value);
}

inline void DataView::SetInt16(size_t byteOffset, int16_t value) const {
WriteData<int16_t>(byteOffset, value);
}

inline void DataView::SetInt32(size_t byteOffset, int32_t value) const {
WriteData<int32_t>(byteOffset, value);
}

inline void DataView::SetUint8(size_t byteOffset, uint8_t value) const {
WriteData<uint8_t>(byteOffset, value);
}

inline void DataView::SetUint16(size_t byteOffset, uint16_t value) const {
WriteData<uint16_t>(byteOffset, value);
}

inline void DataView::SetUint32(size_t byteOffset, uint32_t value) const {
WriteData<uint32_t>(byteOffset, value);
}

template <typename T>
inline T DataView::ReadData(size_t byteOffset) const {
if (byteOffset + sizeof(T) > _length ||
byteOffset + sizeof(T) < byteOffset) { // overflow
NAPI_THROW(RangeError::New(_env,
"Offset is outside the bounds of the DataView"));
return 0;
}

return *reinterpret_cast<T*>(static_cast<uint8_t*>(_data) + byteOffset);
}

template <typename T>
inline void DataView::WriteData(size_t byteOffset, T value) const {
if (byteOffset + sizeof(T) > _length ||
byteOffset + sizeof(T) < byteOffset) { // overflow
NAPI_THROW(RangeError::New(_env,
"Offset is outside the bounds of the DataView"));
return;
}

*reinterpret_cast<T*>(static_cast<uint8_t*>(_data) + byteOffset) = value;
}
#endif

Expand Down
34 changes: 29 additions & 5 deletions napi.h
Original file line number Diff line number Diff line change
Expand Up @@ -800,11 +800,35 @@ namespace Napi {
size_t ByteOffset() const; ///< Gets the offset into the buffer where the array starts.
size_t ByteLength() const; ///< Gets the length of the array in bytes.

// TODO: This class isn't a complete implementation yet, and will
// incrementally add additional methods to read/write data into buffer.
// Currently, this class is wrapped by the NAPI_DATA_VIEW_FEATURE macro flag
// and this should be enabled only in the tests until the implementation is
// completed.
void* Data() const;

float GetFloat32(size_t byteOffset) const;
double GetFloat64(size_t byteOffset) const;
int8_t GetInt8(size_t byteOffset) const;
int16_t GetInt16(size_t byteOffset) const;
int32_t GetInt32(size_t byteOffset) const;
uint8_t GetUint8(size_t byteOffset) const;
uint16_t GetUint16(size_t byteOffset) const;
uint32_t GetUint32(size_t byteOffset) const;

void SetFloat32(size_t byteOffset, float value) const;
void SetFloat64(size_t byteOffset, double value) const;
void SetInt8(size_t byteOffset, int8_t value) const;
void SetInt16(size_t byteOffset, int16_t value) const;
void SetInt32(size_t byteOffset, int32_t value) const;
void SetUint8(size_t byteOffset, uint8_t value) const;
void SetUint16(size_t byteOffset, uint16_t value) const;
void SetUint32(size_t byteOffset, uint32_t value) const;

private:
template <typename T>
T ReadData(size_t byteOffset) const;

template <typename T>
void WriteData(size_t byteOffset, T value) const;

void* _data;
size_t _length;
};
#endif

Expand Down
3 changes: 3 additions & 0 deletions test/binding.cc
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ Object InitBasicTypesNumber(Env env);
Object InitBasicTypesValue(Env env);
Object InitBuffer(Env env);
Object InitDataView(Env env);
Object InitDataViewReadWrite(Env env);
Object InitError(Env env);
Object InitExternal(Env env);
Object InitFunction(Env env);
Expand All @@ -24,6 +25,8 @@ Object Init(Env env, Object exports) {
exports.Set("basic_types_value", InitBasicTypesValue(env));
exports.Set("buffer", InitBuffer(env));
exports.Set("dataview", InitDataView(env));
exports.Set("dataview_read_write", InitDataView(env));
exports.Set("dataview_read_write", InitDataViewReadWrite(env));
exports.Set("error", InitError(env));
exports.Set("external", InitExternal(env));
exports.Set("function", InitFunction(env));
Expand Down
1 change: 1 addition & 0 deletions test/binding.gyp
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
'binding.cc',
'buffer.cc',
'dataview/dataview.cc',
'dataview/dataview_read_write.cc',
'error.cc',
'external.cc',
'function.cc',
Expand Down
115 changes: 115 additions & 0 deletions test/dataview/dataview_read_write.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
#include "napi.h"

using namespace Napi;

static Value GetFloat32(const CallbackInfo& info) {
size_t byteOffset = info[1].As<Number>().Uint32Value();
return Number::New(info.Env(), info[0].As<DataView>().GetFloat32(byteOffset));
}

static Value GetFloat64(const CallbackInfo& info) {
size_t byteOffset = info[1].As<Number>().Uint32Value();
return Number::New(info.Env(), info[0].As<DataView>().GetFloat64(byteOffset));
}

static Value GetInt8(const CallbackInfo& info) {
size_t byteOffset = info[1].As<Number>().Uint32Value();
return Number::New(info.Env(), info[0].As<DataView>().GetInt8(byteOffset));
}

static Value GetInt16(const CallbackInfo& info) {
size_t byteOffset = info[1].As<Number>().Uint32Value();
return Number::New(info.Env(), info[0].As<DataView>().GetInt16(byteOffset));
}

static Value GetInt32(const CallbackInfo& info) {
size_t byteOffset = info[1].As<Number>().Uint32Value();
return Number::New(info.Env(), info[0].As<DataView>().GetInt32(byteOffset));
}

static Value GetUint8(const CallbackInfo& info) {
size_t byteOffset = info[1].As<Number>().Uint32Value();
return Number::New(info.Env(), info[0].As<DataView>().GetUint8(byteOffset));
}

static Value GetUint16(const CallbackInfo& info) {
size_t byteOffset = info[1].As<Number>().Uint32Value();
return Number::New(info.Env(), info[0].As<DataView>().GetUint16(byteOffset));
}

static Value GetUint32(const CallbackInfo& info) {
size_t byteOffset = info[1].As<Number>().Uint32Value();
return Number::New(info.Env(), info[0].As<DataView>().GetUint32(byteOffset));
}

static void SetFloat32(const CallbackInfo& info) {
size_t byteOffset = info[1].As<Number>().Uint32Value();
auto value = info[2].As<Number>().FloatValue();
info[0].As<DataView>().SetFloat32(byteOffset, value);
}

static void SetFloat64(const CallbackInfo& info) {
size_t byteOffset = info[1].As<Number>().Uint32Value();
auto value = info[2].As<Number>().DoubleValue();
info[0].As<DataView>().SetFloat64(byteOffset, value);
}

static void SetInt8(const CallbackInfo& info) {
size_t byteOffset = info[1].As<Number>().Uint32Value();
auto value = info[2].As<Number>().Int32Value();
info[0].As<DataView>().SetInt8(byteOffset, value);
}

static void SetInt16(const CallbackInfo& info) {
size_t byteOffset = info[1].As<Number>().Uint32Value();
auto value = info[2].As<Number>().Int32Value();
info[0].As<DataView>().SetInt16(byteOffset, value);
}

static void SetInt32(const CallbackInfo& info) {
size_t byteOffset = info[1].As<Number>().Uint32Value();
auto value = info[2].As<Number>().Int32Value();
info[0].As<DataView>().SetInt32(byteOffset, value);
}

static void SetUint8(const CallbackInfo& info) {
size_t byteOffset = info[1].As<Number>().Uint32Value();
auto value = info[2].As<Number>().Uint32Value();
info[0].As<DataView>().SetUint8(byteOffset, value);
}

static void SetUint16(const CallbackInfo& info) {
size_t byteOffset = info[1].As<Number>().Uint32Value();
auto value = info[2].As<Number>().Uint32Value();
info[0].As<DataView>().SetUint16(byteOffset, value);
}

static void SetUint32(const CallbackInfo& info) {
size_t byteOffset = info[1].As<Number>().Uint32Value();
auto value = info[2].As<Number>().Uint32Value();
info[0].As<DataView>().SetUint32(byteOffset, value);
}

Object InitDataViewReadWrite(Env env) {
Object exports = Object::New(env);

exports["getFloat32"] = Function::New(env, GetFloat32);
exports["getFloat64"] = Function::New(env, GetFloat64);
exports["getInt8"] = Function::New(env, GetInt8);
exports["getInt16"] = Function::New(env, GetInt16);
exports["getInt32"] = Function::New(env, GetInt32);
exports["getUint8"] = Function::New(env, GetUint8);
exports["getUint16"] = Function::New(env, GetUint16);
exports["getUint32"] = Function::New(env, GetUint32);

exports["setFloat32"] = Function::New(env, SetFloat32);
exports["setFloat64"] = Function::New(env, SetFloat64);
exports["setInt8"] = Function::New(env, SetInt8);
exports["setInt16"] = Function::New(env, SetInt16);
exports["setInt32"] = Function::New(env, SetInt32);
exports["setUint8"] = Function::New(env, SetUint8);
exports["setUint16"] = Function::New(env, SetUint16);
exports["setUint32"] = Function::New(env, SetUint32);

return exports;
}
86 changes: 86 additions & 0 deletions test/dataview/dataview_read_write.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
'use strict';

const buildType = process.config.target_defaults.default_configuration;
const assert = require('assert');

test(require(`../build/${buildType}/binding.node`));
test(require(`../build/${buildType}/binding_noexcept.node`));

function test(binding) {
function expected(type, value) {
return eval(`(new ${type}Array([${value}]))[0]`);
}

function nativeReadDataView(dataview, type, offset, value) {
return eval(`binding.dataview_read_write.get${type}(dataview, offset)`);
}

function nativeWriteDataView(dataview, type, offset, value) {
eval(`binding.dataview_read_write.set${type}(dataview, offset, value)`);
}

function jsReadDataView(dataview, type, offset, value) {
return eval(`dataview.get${type}(offset, true)`);
}

function jsWriteDataView(dataview, type, offset, value) {
eval(`dataview.set${type}(offset, value, true)`);
}

function testReadData(dataview, type, offset, value) {
jsWriteDataView(dataview, type, offset, 0);
assert.strictEqual(jsReadDataView(dataview, type, offset), 0);

jsWriteDataView(dataview, type, offset, value);
assert.strictEqual(
nativeReadDataView(dataview, type, offset), expected(type, value));
}

function testWriteData(dataview, type, offset, value) {
jsWriteDataView(dataview, type, offset, 0);
assert.strictEqual(jsReadDataView(dataview, type, offset), 0);

nativeWriteDataView(dataview, type, offset, value);
assert.strictEqual(
jsReadDataView(dataview, type, offset), expected(type, value));
}

function testInvalidOffset(dataview, type, offset, value) {
assert.throws(() => {
nativeReadDataView(dataview, type, offset);
}, RangeError);

assert.throws(() => {
nativeWriteDataView(dataview, type, offset, value);
}, RangeError);
}

const dataview = new DataView(new ArrayBuffer(22));

testReadData(dataview, 'Float32', 0, 10.2);
testReadData(dataview, 'Float64', 4, 20.3);
testReadData(dataview, 'Int8', 5, 120);
testReadData(dataview, 'Int16', 7, 15000);
testReadData(dataview, 'Int32', 11, 200000);
testReadData(dataview, 'Uint8', 12, 128);
testReadData(dataview, 'Uint16', 14, 32768);
testReadData(dataview, 'Uint32', 18, 1000000);

testWriteData(dataview, 'Float32', 0, 10.2);
testWriteData(dataview, 'Float64', 4, 20.3);
testWriteData(dataview, 'Int8', 5, 120);
testWriteData(dataview, 'Int16', 7, 15000);
testWriteData(dataview, 'Int32', 11, 200000);
testWriteData(dataview, 'Uint8', 12, 128);
testWriteData(dataview, 'Uint16', 14, 32768);
testWriteData(dataview, 'Uint32', 18, 1000000);

testInvalidOffset(dataview, 'Float32', 22, 10.2);
testInvalidOffset(dataview, 'Float64', 22, 20.3);
testInvalidOffset(dataview, 'Int8', 22, 120);
testInvalidOffset(dataview, 'Int16', 22, 15000);
testInvalidOffset(dataview, 'Int32', 22, 200000);
testInvalidOffset(dataview, 'Uint8', 22, 128);
testInvalidOffset(dataview, 'Uint16', 22, 32768);
testInvalidOffset(dataview, 'Uint32', 22, 1000000);
}
Loading

0 comments on commit 44f5c6f

Please sign in to comment.